2 * Copyright (c) 2016 Cisco and/or its affiliates.
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:
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package io.fd.honeycomb.v3po.translate.impl.read;
19 import static com.google.common.base.Preconditions.checkArgument;
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;
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;
49 abstract class AbstractCompositeReader<D extends DataObject, B extends Builder<D>> implements Reader<D> {
51 private static final Logger LOG = LoggerFactory.getLogger(AbstractCompositeReader.class);
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;
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);
70 public final InstanceIdentifier<D> getManagedDataObjectType() {
71 return instanceIdentifier;
75 * @param id {@link InstanceIdentifier} pointing to current node. In case of keyed list, key must be present.
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();
85 switch (traversalType) {
87 LOG.trace("{}: Reading current attributes", this);
88 readCurrentAttributes(id, builder, ctx);
89 readChildren(id, ctx, builder);
93 readChildren(id, ctx, builder);
94 LOG.trace("{}: Reading current attributes", this);
95 readCurrentAttributes(id, builder, ctx);
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);
106 LOG.debug("{}: Current node read successfully. Result: {}", this, read);
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);
118 for (ChildReader<? extends Augmentation<D>> child : augReaders.values()) {
119 LOG.debug("{}: Reading augment from: {}", this, child);
120 child.read(id, builder, ctx);
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);
134 return readSubtree(id, ctx);
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);
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);
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())
162 LOG.debug("{}: Subtree: {} read successfully. Result: {}", this, id, readSubtree);
168 * Fill in current node's attributes
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
174 protected abstract void readCurrentAttributes(@Nonnull final InstanceIdentifier<D> id, @Nonnull final B builder,
175 @Nonnull final ReadContext ctx) throws ReadFailedException;
178 * Return new instance of a builder object for current node
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
183 protected abstract B getBuilder(InstanceIdentifier<D> id);
185 // TODO move filtering out of here into a dedicated Filter ifc
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()));
193 final Optional<? extends DataObject> nextParent = findNextParent(parent, nextId, managedType);
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());
200 return nextParent; // we can't go further, return Optional.absent()
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());
211 if (method.isPresent()) {
212 return Optional.fromNullable(filterSingle(parent, nextId, method.get()));
215 method = ReflectionUtils.findMethodReflex(managedType,
216 "get" + nextId.getType().getSimpleName(), Collections.<Class<?>>emptyList(), List.class);
218 if (method.isPresent()) {
219 return filterList(parent, nextId, method.get());
221 throw new IllegalStateException(
222 "Unable to filter " + nextId + " from " + parent + " getters not found using reflexion");
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);
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>() {
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);
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));
254 private static Object invoke(final Method method,
255 final InstanceIdentifier.PathArgument nextId, final DataObject parent) {
257 return method.invoke(parent);
258 } catch (IllegalAccessException | InvocationTargetException e) {
259 throw new IllegalArgumentException("Unable to get " + nextId + " from " + parent, e);
264 public String toString() {
265 return String.format("Reader[%s]", getManagedDataObjectType().getTargetType().getSimpleName());