c99e0edc4588948468010d22a7498cecbc50232a
[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.translate.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.translate.impl.TraversalType;
26 import io.fd.honeycomb.v3po.translate.util.ReflectionUtils;
27 import io.fd.honeycomb.v3po.translate.util.RWUtils;
28 import io.fd.honeycomb.v3po.translate.read.ChildReader;
29 import io.fd.honeycomb.v3po.translate.read.ReadContext;
30 import io.fd.honeycomb.v3po.translate.read.ReadFailedException;
31 import io.fd.honeycomb.v3po.translate.read.Reader;
32 import java.lang.reflect.InvocationTargetException;
33 import java.lang.reflect.Method;
34 import java.util.Collections;
35 import java.util.List;
36 import java.util.Map;
37 import javax.annotation.Nonnull;
38 import javax.annotation.Nullable;
39 import org.opendaylight.yangtools.concepts.Builder;
40 import org.opendaylight.yangtools.yang.binding.Augmentation;
41 import org.opendaylight.yangtools.yang.binding.ChildOf;
42 import org.opendaylight.yangtools.yang.binding.DataObject;
43 import org.opendaylight.yangtools.yang.binding.Identifier;
44 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
47
48 @Beta
49 abstract class AbstractCompositeReader<D extends DataObject, B extends Builder<D>> implements Reader<D> {
50
51     private static final Logger LOG = LoggerFactory.getLogger(AbstractCompositeReader.class);
52
53     private final Map<Class<? extends DataObject>, ChildReader<? extends ChildOf<D>>> childReaders;
54     private final Map<Class<? extends DataObject>, ChildReader<? extends Augmentation<D>>> augReaders;
55     private final InstanceIdentifier<D> instanceIdentifier;
56     private final TraversalType traversalType;
57
58     AbstractCompositeReader(final Class<D> managedDataObjectType,
59                             final List<ChildReader<? extends ChildOf<D>>> childReaders,
60                             final List<ChildReader<? extends Augmentation<D>>> augReaders,
61                             final TraversalType traversalType) {
62         this.traversalType = traversalType;
63         this.childReaders = RWUtils.uniqueLinkedIndex(childReaders, RWUtils.MANAGER_CLASS_FUNCTION);
64         this.augReaders = RWUtils.uniqueLinkedIndex(augReaders, RWUtils.MANAGER_CLASS_AUG_FUNCTION);
65         this.instanceIdentifier = InstanceIdentifier.create(managedDataObjectType);
66     }
67
68     @Nonnull
69     @Override
70     public final InstanceIdentifier<D> getManagedDataObjectType() {
71         return instanceIdentifier;
72     }
73
74     /**
75      * @param id {@link InstanceIdentifier} pointing to current node. In case of keyed list, key must be present.
76      *
77      */
78     protected Optional<D> readCurrent(final InstanceIdentifier<D> id,
79                                       @Nonnull final ReadContext ctx) throws ReadFailedException {
80         LOG.debug("{}: Reading current: {}", this, id);
81         final B builder = getBuilder(id);
82         // Cache empty value to determine if anything has changed later TODO cache in a field
83         final D emptyValue = builder.build();
84
85         switch (traversalType) {
86             case PREORDER: {
87                 LOG.trace("{}: Reading current attributes", this);
88                 readCurrentAttributes(id, builder, ctx);
89                 readChildren(id, ctx, builder);
90                 break;
91             }
92             case POSTORDER: {
93                 readChildren(id, ctx, builder);
94                 LOG.trace("{}: Reading current attributes", this);
95                 readCurrentAttributes(id, builder, ctx);
96                 break;
97             }
98         }
99
100         // Need to check whether anything was filled in to determine if data is present or not.
101         final D built = builder.build();
102         final Optional<D> read = built.equals(emptyValue)
103             ? Optional.<D>absent()
104             : Optional.of(built);
105
106         LOG.debug("{}: Current node read successfully. Result: {}", this, read);
107         return read;
108     }
109
110     private void readChildren(final InstanceIdentifier<D> id, final @Nonnull ReadContext ctx, final B builder)
111         throws ReadFailedException {
112         // TODO expect exceptions from reader
113         for (ChildReader<? extends ChildOf<D>> child : childReaders.values()) {
114             LOG.debug("{}: Reading child from: {}", this, child);
115             child.read(id, builder, ctx);
116         }
117
118         for (ChildReader<? extends Augmentation<D>> child : augReaders.values()) {
119             LOG.debug("{}: Reading augment from: {}", this, child);
120             child.read(id, builder, ctx);
121         }
122     }
123
124     @Nonnull
125     @Override
126     @SuppressWarnings("unchecked")
127     public Optional<? extends DataObject> read(@Nonnull final InstanceIdentifier<? extends DataObject> id,
128                                                @Nonnull final ReadContext ctx)
129             throws ReadFailedException {
130         LOG.trace("{}: Reading : {}", this, id);
131         if (id.getTargetType().equals(getManagedDataObjectType().getTargetType())) {
132             return readCurrent((InstanceIdentifier<D>) id, ctx);
133         } else {
134             return readSubtree(id, ctx);
135         }
136     }
137
138     private Optional<? extends DataObject> readSubtree(final InstanceIdentifier<? extends DataObject> id,
139                                                        @Nonnull final ReadContext ctx)
140             throws ReadFailedException {
141         LOG.debug("{}: Reading subtree: {}", this, id);
142         final Class<? extends DataObject> next = RWUtils.getNextId(id, getManagedDataObjectType()).getType();
143         final ChildReader<? extends ChildOf<D>> reader = childReaders.get(next);
144         final ChildReader<? extends Augmentation<D>> augReader = augReaders.get(next);
145
146         if (reader != null) {
147             LOG.debug("{}: Reading subtree: {} from: {}", this, id, reader);
148             return reader.read(id, ctx);
149         }if (augReader != null) {
150             LOG.debug("{}: Reading subtree: {} from: {}", this, id, augReader);
151             return augReader.read(id, ctx);
152         } else {
153             LOG.debug("{}: Dedicated subtree reader missing for: {}. Reading current and filtering", this, next);
154             // If there's no dedicated reader, use read current
155             final InstanceIdentifier<D> currentId = RWUtils.cutId(id, getManagedDataObjectType());
156             final Optional<D> current = readCurrent(currentId, ctx);
157             // then perform post-reading filtering (return only requested sub-node)
158             final Optional<? extends DataObject> readSubtree = current.isPresent()
159                 ? filterSubtree(current.get(), id, getManagedDataObjectType().getTargetType())
160                 : current;
161
162             LOG.debug("{}: Subtree: {} read successfully. Result: {}", this, id, readSubtree);
163             return readSubtree;
164         }
165     }
166
167     /**
168      * Fill in current node's attributes
169      *
170      * @param id {@link InstanceIdentifier} pointing to current node. In case of keyed list, key must be present.
171      * @param builder Builder object for current node where the read attributes must be placed
172      * @param ctx Current read context
173      */
174     protected abstract void readCurrentAttributes(@Nonnull final InstanceIdentifier<D> id, @Nonnull final B builder,
175                                                   @Nonnull final ReadContext ctx) throws ReadFailedException;
176
177     /**
178      * Return new instance of a builder object for current node
179      *
180      * @param id {@link InstanceIdentifier} pointing to current node. In case of keyed list, key must be present.
181      * @return Builder object for current node type
182      */
183     protected abstract B getBuilder(InstanceIdentifier<D> id);
184
185     // TODO move filtering out of here into a dedicated Filter ifc
186     @Nonnull
187     private static Optional<? extends DataObject> filterSubtree(@Nonnull final DataObject parent,
188                                                                 @Nonnull final InstanceIdentifier<? extends DataObject> absolutPath,
189                                                                 @Nonnull final Class<?> managedType) {
190         final InstanceIdentifier.PathArgument nextId =
191                 RWUtils.getNextId(absolutPath, InstanceIdentifier.create(parent.getClass()));
192
193         final Optional<? extends DataObject> nextParent = findNextParent(parent, nextId, managedType);
194
195         if (Iterables.getLast(absolutPath.getPathArguments()).equals(nextId)) {
196             return nextParent; // we found the dataObject identified by absolutePath
197         } else if (nextParent.isPresent()) {
198             return filterSubtree(nextParent.get(), absolutPath, nextId.getType());
199         } else {
200             return nextParent; // we can't go further, return Optional.absent()
201         }
202     }
203
204     private static Optional<? extends DataObject> findNextParent(@Nonnull final DataObject parent,
205                                                                  @Nonnull final InstanceIdentifier.PathArgument nextId,
206                                                                  @Nonnull final Class<?> managedType) {
207         // TODO is there a better way than reflection ? e.g. convert into NN and filter out with a utility
208         Optional<Method> method = ReflectionUtils.findMethodReflex(managedType, "get",
209                 Collections.<Class<?>>emptyList(), nextId.getType());
210
211         if (method.isPresent()) {
212             return Optional.fromNullable(filterSingle(parent, nextId, method.get()));
213         } else {
214             // List child nodes
215             method = ReflectionUtils.findMethodReflex(managedType,
216                     "get" + nextId.getType().getSimpleName(), Collections.<Class<?>>emptyList(), List.class);
217
218             if (method.isPresent()) {
219                 return filterList(parent, nextId, method.get());
220             } else {
221                 throw new IllegalStateException(
222                         "Unable to filter " + nextId + " from " + parent + " getters not found using reflexion");
223             }
224         }
225     }
226
227     @SuppressWarnings("unchecked")
228     private static Optional<? extends DataObject> filterList(final DataObject parent,
229                                                              final InstanceIdentifier.PathArgument nextId,
230                                                              final Method method) {
231         final List<? extends DataObject> invoke = (List<? extends DataObject>) invoke(method, nextId, parent);
232
233         checkArgument(nextId instanceof InstanceIdentifier.IdentifiableItem<?, ?>,
234             "Unable to perform wildcarded read for %s", nextId);
235         final Identifier key = ((InstanceIdentifier.IdentifiableItem) nextId).getKey();
236         return Iterables.tryFind(invoke, new Predicate<DataObject>() {
237             @Override
238             public boolean apply(@Nullable final DataObject input) {
239                 final Optional<Method> keyGetter =
240                     ReflectionUtils.findMethodReflex(nextId.getType(), "get",
241                         Collections.<Class<?>>emptyList(), key.getClass());
242                 final Object actualKey;
243                 actualKey = invoke(keyGetter.get(), nextId, input);
244                 return key.equals(actualKey);
245             }
246         });
247     }
248
249     private static DataObject filterSingle(final DataObject parent,
250                                            final InstanceIdentifier.PathArgument nextId, final Method method) {
251         return nextId.getType().cast(invoke(method, nextId, parent));
252     }
253
254     private static Object invoke(final Method method,
255                                  final InstanceIdentifier.PathArgument nextId, final DataObject parent) {
256         try {
257             return method.invoke(parent);
258         } catch (IllegalAccessException | InvocationTargetException e) {
259             throw new IllegalArgumentException("Unable to get " + nextId + " from " + parent, e);
260         }
261     }
262
263     @Override
264     public String toString() {
265         return String.format("Reader[%s]", getManagedDataObjectType().getTargetType().getSimpleName());
266     }
267 }