50a20656e3bf2e0d9ff56831742ec6ae5f3ab055
[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.util.read.registry;
18
19 import static com.google.common.base.Preconditions.checkArgument;
20
21 import com.google.common.base.Optional;
22 import com.google.common.base.Predicate;
23 import com.google.common.collect.Iterables;
24 import io.fd.honeycomb.v3po.translate.read.ListReader;
25 import io.fd.honeycomb.v3po.translate.read.ReadContext;
26 import io.fd.honeycomb.v3po.translate.read.ReadFailedException;
27 import io.fd.honeycomb.v3po.translate.read.Reader;
28 import io.fd.honeycomb.v3po.translate.util.RWUtils;
29 import io.fd.honeycomb.v3po.translate.util.ReflectionUtils;
30 import java.lang.reflect.InvocationTargetException;
31 import java.lang.reflect.Method;
32 import java.util.Collections;
33 import java.util.HashSet;
34 import java.util.List;
35 import java.util.Set;
36 import javax.annotation.Nonnull;
37 import javax.annotation.Nullable;
38 import org.opendaylight.yangtools.concepts.Builder;
39 import org.opendaylight.yangtools.yang.binding.DataObject;
40 import org.opendaylight.yangtools.yang.binding.Identifiable;
41 import org.opendaylight.yangtools.yang.binding.Identifier;
42 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
43 import org.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
45
46 /**
47  * Simple Reader delegate for subtree Readers (Readers handling also children nodes) providing a list of all the
48  * children nodes being handled.
49  */
50 class SubtreeReader<D extends DataObject, B extends Builder<D>> implements Reader<D, B> {
51
52     private static final Logger LOG = LoggerFactory.getLogger(SubtreeReader.class);
53
54     private final Reader<D, B> delegate;
55     private final Set<InstanceIdentifier<?>> handledChildTypes = new HashSet<>();
56
57     private SubtreeReader(final Reader<D, B> delegate, Set<InstanceIdentifier<?>> handledTypes) {
58         this.delegate = delegate;
59         for (InstanceIdentifier<?> handledType : handledTypes) {
60             // Iid has to start with Reader's handled root type
61             checkArgument(delegate.getManagedDataObjectType().getTargetType().equals(
62                     handledType.getPathArguments().iterator().next().getType()),
63                     "Handled node from subtree has to be identified by an instance identifier starting from: %s."
64                             + "Instance identifier was: %s", getManagedDataObjectType().getTargetType(), handledType);
65             checkArgument(Iterables.size(handledType.getPathArguments()) > 1,
66                     "Handled node from subtree identifier too short: %s", handledType);
67             handledChildTypes.add(InstanceIdentifier.create(Iterables.concat(
68                     getManagedDataObjectType().getPathArguments(), Iterables.skip(handledType.getPathArguments(), 1))));
69         }
70     }
71
72     /**
73      * Return set of types also handled by this Reader. All of the types are children of the type managed by this Reader
74      * excluding the type of this Reader.
75      */
76     Set<InstanceIdentifier<?>> getHandledChildTypes() {
77         return handledChildTypes;
78     }
79
80     @Override
81     @Nonnull
82     public Optional<? extends DataObject> read(
83             @Nonnull final InstanceIdentifier<? extends DataObject> id,
84             @Nonnull final ReadContext ctx) throws ReadFailedException {
85         final InstanceIdentifier<?> wildcarded = RWUtils.makeIidWildcarded(id);
86
87         // Reading entire subtree and filtering if is current reader responsible
88         if (getHandledChildTypes().contains(wildcarded)) {
89             LOG.debug("{}: Subtree node managed by this writer requested: {}. Reading current and filtering", this, id);
90             // If there's no dedicated reader, use read current
91             final InstanceIdentifier<D> currentId = RWUtils.cutId(id, getManagedDataObjectType());
92             final Optional<? extends DataObject> current = delegate.read(currentId, ctx);
93             // then perform post-reading filtering (return only requested sub-node)
94             final Optional<? extends DataObject> readSubtree = current.isPresent()
95                 ? filterSubtree(current.get(), id, getManagedDataObjectType().getTargetType())
96                 : current;
97
98             LOG.debug("{}: Subtree: {} read successfully. Result: {}", this, id, readSubtree);
99             return readSubtree;
100
101         // Fallback solution, try delegate, maybe it can read the ID
102         } else {
103             return delegate.read(id, ctx);
104         }
105     }
106
107     @Override
108     public void readCurrentAttributes(@Nonnull final InstanceIdentifier<D> id, @Nonnull final B builder,
109                                       @Nonnull final ReadContext ctx)
110             throws ReadFailedException {
111         delegate.readCurrentAttributes(id, builder, ctx);
112     }
113
114     @Nonnull
115     @Override
116     public B getBuilder(final InstanceIdentifier<D> id) {
117         return delegate.getBuilder(id);
118     }
119
120     @Override
121     public void merge(@Nonnull final Builder<? extends DataObject> parentBuilder, @Nonnull final D readValue) {
122         delegate.merge(parentBuilder, readValue);
123     }
124
125     @Nonnull
126     private static Optional<? extends DataObject> filterSubtree(@Nonnull final DataObject parent,
127                                                                 @Nonnull final InstanceIdentifier<? extends DataObject> absolutPath,
128                                                                 @Nonnull final Class<?> managedType) {
129         final InstanceIdentifier.PathArgument nextId =
130                 RWUtils.getNextId(absolutPath, InstanceIdentifier.create(parent.getClass()));
131
132         final Optional<? extends DataObject> nextParent = findNextParent(parent, nextId, managedType);
133
134         if (Iterables.getLast(absolutPath.getPathArguments()).equals(nextId)) {
135             return nextParent; // we found the dataObject identified by absolutePath
136         } else if (nextParent.isPresent()) {
137             return filterSubtree(nextParent.get(), absolutPath, nextId.getType());
138         } else {
139             return nextParent; // we can't go further, return Optional.absent()
140         }
141     }
142
143     private static Optional<? extends DataObject> findNextParent(@Nonnull final DataObject parent,
144                                                                  @Nonnull final InstanceIdentifier.PathArgument nextId,
145                                                                  @Nonnull final Class<?> managedType) {
146         // TODO is there a better way than reflection ? e.g. convert into NN and filter out with a utility
147         Optional<Method> method = ReflectionUtils.findMethodReflex(managedType, "get",
148                 Collections.emptyList(), nextId.getType());
149
150         if (method.isPresent()) {
151             return Optional.fromNullable(filterSingle(parent, nextId, method.get()));
152         } else {
153             // List child nodes
154             method = ReflectionUtils.findMethodReflex(managedType,
155                     "get" + nextId.getType().getSimpleName(), Collections.emptyList(), List.class);
156
157             if (method.isPresent()) {
158                 return filterList(parent, nextId, method.get());
159             } else {
160                 throw new IllegalStateException(
161                         "Unable to filter " + nextId + " from " + parent + " getters not found using reflexion");
162             }
163         }
164     }
165
166     @SuppressWarnings("unchecked")
167     private static Optional<? extends DataObject> filterList(final DataObject parent,
168                                                              final InstanceIdentifier.PathArgument nextId,
169                                                              final Method method) {
170         final List<? extends DataObject> invoke = (List<? extends DataObject>) invoke(method, nextId, parent);
171
172         checkArgument(nextId instanceof InstanceIdentifier.IdentifiableItem<?, ?>,
173                 "Unable to perform wildcarded read for %s", nextId);
174         final Identifier key = ((InstanceIdentifier.IdentifiableItem) nextId).getKey();
175         // TODO replace with stream().filter().findFirst() when we switch to using java's Optional instead of Guava's
176         // because now we would have to do awkward Optional transformation since findFirstReturns guava's optional
177         return Iterables.tryFind(invoke, new Predicate<DataObject>() {
178
179             @Override
180             public boolean apply(@Nullable final DataObject input) {
181                 final Optional<Method> keyGetter = ReflectionUtils.findMethodReflex(nextId.getType(), "get",
182                                 Collections.emptyList(), key.getClass());
183                 final Object actualKey;
184                 actualKey = invoke(keyGetter.get(), nextId, input);
185                 return key.equals(actualKey);
186             }
187         });
188     }
189
190     private static DataObject filterSingle(final DataObject parent,
191                                            final InstanceIdentifier.PathArgument nextId, final Method method) {
192         return nextId.getType().cast(invoke(method, nextId, parent));
193     }
194
195     private static Object invoke(final Method method,
196                                  final InstanceIdentifier.PathArgument nextId, final DataObject parent) {
197         try {
198             return method.invoke(parent);
199         } catch (IllegalAccessException | InvocationTargetException e) {
200             throw new IllegalArgumentException("Unable to get " + nextId + " from " + parent, e);
201         }
202     }
203
204     @Override
205     @Nonnull
206     public InstanceIdentifier<D> getManagedDataObjectType() {
207         return delegate.getManagedDataObjectType();
208     }
209
210     /**
211      * Wrap a Reader as a subtree Reader.
212      */
213     static <D extends DataObject, B extends Builder<D>> Reader<D, B> createForReader(@Nonnull final Set<InstanceIdentifier<?>> handledChildren,
214                                                                                      @Nonnull final Reader<D, B> reader) {
215         return (reader instanceof ListReader)
216                 ? new SubtreeListReader<>((ListReader) reader, handledChildren)
217                 : new SubtreeReader<>(reader, handledChildren);
218     }
219
220     private static final class SubtreeListReader<D extends DataObject & Identifiable<K>, B extends Builder<D>, K extends Identifier<D>>
221             extends SubtreeReader<D, B> implements ListReader<D, K, B> {
222
223         private final ListReader<D, K, B> delegate;
224
225         private SubtreeListReader(final ListReader<D, K, B> delegate,
226                                   final Set<InstanceIdentifier<?>> handledTypes) {
227             super(delegate, handledTypes);
228             this.delegate = delegate;
229         }
230
231         @Nonnull
232         @Override
233         public List<D> readList(@Nonnull final InstanceIdentifier<D> id, @Nonnull final ReadContext ctx)
234                 throws ReadFailedException {
235             return delegate.readList(id, ctx);
236         }
237
238         @Override
239         public void merge(@Nonnull final Builder<? extends DataObject> builder, @Nonnull final List<D> readData) {
240             delegate.merge(builder, readData);
241         }
242
243         @Override
244         public List<K> getAllIds(@Nonnull final InstanceIdentifier<D> id,
245                                  @Nonnull final ReadContext ctx) throws ReadFailedException {
246             return delegate.getAllIds(id, ctx);
247         }
248     }
249
250 }