2856937bac9f097577c923c3d1edb0a7bd14a7c1
[hc2vpp.git] /
1 /*
2  * Copyright (c) 2016 Cisco and/or its affiliates.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at:
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package io.fd.honeycomb.v3po.vpp.facade.impl.read;
18
19 import static com.google.common.base.Preconditions.checkArgument;
20
21 import com.google.common.annotations.Beta;
22 import com.google.common.base.Optional;
23 import com.google.common.base.Predicate;
24 import com.google.common.collect.Iterables;
25 import io.fd.honeycomb.v3po.vpp.facade.impl.util.ReflectionUtils;
26 import io.fd.honeycomb.v3po.vpp.facade.impl.util.VppRWUtils;
27 import io.fd.honeycomb.v3po.vpp.facade.read.ChildVppReader;
28 import io.fd.honeycomb.v3po.vpp.facade.read.ReadContext;
29 import io.fd.honeycomb.v3po.vpp.facade.read.ReadFailedException;
30 import io.fd.honeycomb.v3po.vpp.facade.read.VppReader;
31 import java.lang.reflect.InvocationTargetException;
32 import java.lang.reflect.Method;
33 import java.util.Collections;
34 import java.util.List;
35 import java.util.Map;
36 import javax.annotation.Nonnull;
37 import javax.annotation.Nullable;
38 import org.opendaylight.yangtools.concepts.Builder;
39 import org.opendaylight.yangtools.yang.binding.Augmentation;
40 import org.opendaylight.yangtools.yang.binding.ChildOf;
41 import org.opendaylight.yangtools.yang.binding.DataObject;
42 import org.opendaylight.yangtools.yang.binding.Identifier;
43 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
46
47 @Beta
48 abstract class AbstractCompositeVppReader<D extends DataObject, B extends Builder<D>> implements VppReader<D> {
49
50     private static final Logger LOG = LoggerFactory.getLogger(AbstractCompositeVppReader.class);
51
52     private final Map<Class<? extends DataObject>, ChildVppReader<? extends ChildOf<D>>> childReaders;
53     private final Map<Class<? extends DataObject>, ChildVppReader<? extends Augmentation<D>>> augReaders;
54     private final InstanceIdentifier<D> instanceIdentifier;
55
56     AbstractCompositeVppReader(final Class<D> managedDataObjectType,
57             final List<ChildVppReader<? extends ChildOf<D>>> childReaders,
58             final List<ChildVppReader<? extends Augmentation<D>>> augReaders) {
59         this.childReaders = VppRWUtils.uniqueLinkedIndex(childReaders, VppRWUtils.MANAGER_CLASS_FUNCTION);
60         this.augReaders = VppRWUtils.uniqueLinkedIndex(augReaders, VppRWUtils.MANAGER_CLASS_AUG_FUNCTION);
61         this.instanceIdentifier = InstanceIdentifier.create(managedDataObjectType);
62     }
63
64     @Nonnull
65     @Override
66     public final InstanceIdentifier<D> getManagedDataObjectType() {
67         return instanceIdentifier;
68     }
69
70     /**
71      * @param id {@link InstanceIdentifier} pointing to current node. In case of keyed list, key must be present.
72      *
73      */
74     protected Optional<D> readCurrent(final InstanceIdentifier<D> id,
75                                       @Nonnull final ReadContext ctx) throws ReadFailedException {
76         LOG.debug("{}: Reading current: {}", this, id);
77         final B builder = getBuilder(id);
78         // Cache empty value to determine if anything has changed later TODO cache in a field
79         final D emptyValue = builder.build();
80
81         LOG.trace("{}: Reading current attributes", this);
82         readCurrentAttributes(id, builder, ctx);
83
84         // TODO expect exceptions from reader
85         for (ChildVppReader<? extends ChildOf<D>> child : childReaders.values()) {
86             LOG.debug("{}: Reading child from: {}", this, child);
87             child.read(id, builder, ctx);
88         }
89
90         for (ChildVppReader<? extends Augmentation<D>> child : augReaders.values()) {
91             LOG.debug("{}: Reading augment from: {}", this, child);
92             child.read(id, builder, ctx);
93         }
94
95         // Need to check whether anything was filled in to determine if data is present or not.
96         final D built = builder.build();
97         final Optional<D> read = built.equals(emptyValue)
98             ? Optional.<D>absent()
99             : Optional.of(built);
100
101         LOG.debug("{}: Current node read successfully. Result: {}", this, read);
102         return read;
103     }
104
105     @Nonnull
106     @Override
107     @SuppressWarnings("unchecked")
108     public Optional<? extends DataObject> read(@Nonnull final InstanceIdentifier<? extends DataObject> id,
109                                                @Nonnull final ReadContext ctx)
110             throws ReadFailedException {
111         LOG.trace("{}: Reading : {}", this, id);
112         if (id.getTargetType().equals(getManagedDataObjectType().getTargetType())) {
113             return readCurrent((InstanceIdentifier<D>) id, ctx);
114         } else {
115             return readSubtree(id, ctx);
116         }
117     }
118
119     private Optional<? extends DataObject> readSubtree(final InstanceIdentifier<? extends DataObject> id,
120                                                        @Nonnull final ReadContext ctx)
121             throws ReadFailedException {
122         LOG.debug("{}: Reading subtree: {}", this, id);
123         final Class<? extends DataObject> next = VppRWUtils.getNextId(id, getManagedDataObjectType()).getType();
124         final ChildVppReader<? extends ChildOf<D>> vppReader = childReaders.get(next);
125
126         if (vppReader != null) {
127             LOG.debug("{}: Reading subtree: {} from: {}", this, id, vppReader);
128             return vppReader.read(id, ctx);
129         } else {
130             LOG.debug("{}: Dedicated subtree reader missing for: {}. Reading current and filtering", this, next);
131             // If there's no dedicated reader, use read current
132             final InstanceIdentifier<D> currentId = VppRWUtils.cutId(id, getManagedDataObjectType());
133             final Optional<D> current = readCurrent(currentId, ctx);
134             // then perform post-reading filtering (return only requested sub-node)
135             final Optional<? extends DataObject> readSubtree = current.isPresent()
136                 ? filterSubtree(current.get(), id, getManagedDataObjectType().getTargetType())
137                 : current;
138
139             LOG.debug("{}: Subtree: {} read successfully. Result: {}", this, id, readSubtree);
140             return readSubtree;
141         }
142     }
143
144     /**
145      * Fill in current node's attributes
146      *
147      * @param id {@link InstanceIdentifier} pointing to current node. In case of keyed list, key must be present.
148      * @param builder Builder object for current node where the read attributes must be placed
149      * @param ctx Current read context
150      */
151     protected abstract void readCurrentAttributes(@Nonnull final InstanceIdentifier<D> id, @Nonnull final B builder,
152                                                   @Nonnull final ReadContext ctx) throws ReadFailedException;
153
154     /**
155      * Return new instance of a builder object for current node
156      *
157      * @param id {@link InstanceIdentifier} pointing to current node. In case of keyed list, key must be present.
158      * @return Builder object for current node type
159      */
160     protected abstract B getBuilder(InstanceIdentifier<D> id);
161
162     // TODO move filtering out of here into a dedicated Filter ifc
163     @Nonnull
164     private static Optional<? extends DataObject> filterSubtree(@Nonnull final DataObject parent,
165                                                             @Nonnull final InstanceIdentifier<? extends DataObject> absolutPath,
166                                                             @Nonnull final Class<?> managedType) {
167         // TODO is there a better way than reflection ? e.g. convert into NN and filter out with a utility
168         // FIXME this needs to be recursive. right now it expects only 1 additional element in ID + test
169
170         final InstanceIdentifier.PathArgument nextId =
171             VppRWUtils.getNextId(absolutPath, InstanceIdentifier.create(parent.getClass()));
172
173         Optional<Method> method = ReflectionUtils.findMethodReflex(managedType, "get",
174             Collections.<Class<?>>emptyList(), nextId.getType());
175
176         if (method.isPresent()) {
177             return Optional.fromNullable(filterSingle(parent, nextId, method.get()));
178         } else {
179             // List child nodes
180             method = ReflectionUtils.findMethodReflex(managedType,
181                 "get" + nextId.getType().getSimpleName(), Collections.<Class<?>>emptyList(), List.class);
182
183             if (method.isPresent()) {
184                 return filterList(parent, nextId, method.get());
185             } else {
186                 throw new IllegalStateException(
187                     "Unable to filter " + nextId + " from " + parent + " getters not found using reflexion");
188             }
189         }
190     }
191
192     @SuppressWarnings("unchecked")
193     private static Optional<? extends DataObject> filterList(final DataObject parent,
194                                                              final InstanceIdentifier.PathArgument nextId,
195                                                              final Method method) {
196         final List<? extends DataObject> invoke = (List<? extends DataObject>) invoke(method, nextId, parent);
197
198         checkArgument(nextId instanceof InstanceIdentifier.IdentifiableItem<?, ?>,
199             "Unable to perform wildcarded read for %s", nextId);
200         final Identifier key = ((InstanceIdentifier.IdentifiableItem) nextId).getKey();
201         return Iterables.tryFind(invoke, new Predicate<DataObject>() {
202             @Override
203             public boolean apply(@Nullable final DataObject input) {
204                 final Optional<Method> keyGetter =
205                     ReflectionUtils.findMethodReflex(nextId.getType(), "get",
206                         Collections.<Class<?>>emptyList(), key.getClass());
207                 final Object actualKey;
208                 actualKey = invoke(keyGetter.get(), nextId, input);
209                 return key.equals(actualKey);
210             }
211         });
212     }
213
214     private static DataObject filterSingle(final DataObject parent,
215                                            final InstanceIdentifier.PathArgument nextId, final Method method) {
216         return nextId.getType().cast(invoke(method, nextId, parent));
217     }
218
219     private static Object invoke(final Method method,
220                                  final InstanceIdentifier.PathArgument nextId, final DataObject parent) {
221         try {
222             return method.invoke(parent);
223         } catch (IllegalAccessException | InvocationTargetException e) {
224             throw new IllegalArgumentException("Unable to get " + nextId + " from " + parent, e);
225         }
226     }
227
228     @Override
229     public String toString() {
230         return String.format("Reader[%s]", getManagedDataObjectType().getTargetType().getSimpleName());
231     }
232 }