/* * Copyright (c) 2016 Cisco and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.fd.honeycomb.v3po.impl.trans.impl; import com.google.common.annotations.Beta; import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.collect.Collections2; import com.google.common.collect.Lists; import io.fd.honeycomb.v3po.impl.trans.ChildVppReader; import io.fd.honeycomb.v3po.impl.trans.VppReader; import io.fd.honeycomb.v3po.impl.trans.util.ReflectionUtils; import io.fd.honeycomb.v3po.impl.trans.util.VppRWUtils; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Collections; import java.util.List; import java.util.Map; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.opendaylight.yangtools.concepts.Builder; import org.opendaylight.yangtools.yang.binding.Augmentation; import org.opendaylight.yangtools.yang.binding.ChildOf; import org.opendaylight.yangtools.yang.binding.DataObject; import org.opendaylight.yangtools.yang.binding.Identifier; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; @Beta abstract class AbstractCompositeVppReader> implements VppReader { // TODO add debug + trace logs here and there private final Map, ChildVppReader>> childReaders; private final Map, ChildVppReader>> augReaders; private final InstanceIdentifier instanceIdentifier; AbstractCompositeVppReader(final Class managedDataObjectType, final List>> childReaders, final List>> augReaders) { this.childReaders = VppRWUtils.uniqueLinkedIndex(childReaders, VppRWUtils.MANAGER_CLASS_FUNCTION); this.augReaders = VppRWUtils.uniqueLinkedIndex(augReaders, VppRWUtils.MANAGER_CLASS_AUG_FUNCTION); this.instanceIdentifier = InstanceIdentifier.create(managedDataObjectType); } @Nonnull @Override public final InstanceIdentifier getManagedDataObjectType() { return instanceIdentifier; } /** * @param id {@link InstanceIdentifier} pointing to current node. In case of keyed list, key must be present. */ protected List readCurrent(final InstanceIdentifier id) { final B builder = getBuilder(id); // Cache empty value to determine if anything has changed later TODO cache in a field final D emptyValue = builder.build(); readCurrentAttributes(id, builder); // TODO expect exceptions from reader for (ChildVppReader> child : childReaders.values()) { child.read(id, builder); } for (ChildVppReader> child : augReaders.values()) { child.read(id, builder); } // Need to check whether anything was filled in to determine if data is present or not. final D built = builder.build(); return built.equals(emptyValue) ? Collections.emptyList() : Collections.singletonList(built); } @Nonnull @Override @SuppressWarnings("unchecked") public List read(@Nonnull final InstanceIdentifier id) { // This is read for one of children, we need to read and then filter, not parent) // If this is target, just read if (id.getTargetType().equals(getManagedDataObjectType().getTargetType())) { return readCurrent((InstanceIdentifier) id); } else { return readSubtree(id); } } private List readSubtree(final InstanceIdentifier id) { // Read only specific subtree final Class next = VppRWUtils.getNextId(id, getManagedDataObjectType()).getType(); final ChildVppReader> vppReader = childReaders.get(next); if (vppReader != null) { return vppReader.read(id); } else { // If there's no dedicated reader, use read current final InstanceIdentifier currentId = VppRWUtils.cutId(id, getManagedDataObjectType()); final List current = readCurrent(currentId); // then perform post-reading filtering (return only requested sub-node) return current.isEmpty() ? current : filterSubtree(current, id, getManagedDataObjectType().getTargetType()) ; } } /** * Fill in current node's attributes * * @param id {@link InstanceIdentifier} pointing to current node. In case of keyed list, key must be present. * @param builder Builder object for current node where the read attributes must be placed */ protected abstract void readCurrentAttributes(final InstanceIdentifier id, B builder); /** * Return new instance of a builder object for current node * * @param id {@link InstanceIdentifier} pointing to current node. In case of keyed list, key must be present. * @return Builder object for current node type */ protected abstract B getBuilder(InstanceIdentifier id); // TODO move filtering out of here into a dedicated Filter ifc @Nonnull private static List filterSubtree(@Nonnull final List built, @Nonnull final InstanceIdentifier absolutPath, @Nonnull final Class managedType) { // TODO is there a better way than reflection ? e.g. convert into NN and filter out with a utility // FIXME this needs to be recursive. right now it expects only 1 additional element in ID + test List filtered = Lists.newArrayList(); for (DataObject parent : built) { final InstanceIdentifier.PathArgument nextId = VppRWUtils.getNextId(absolutPath, InstanceIdentifier.create(parent.getClass())); Optional method = ReflectionUtils.findMethodReflex(managedType, "get", Collections.>emptyList(), nextId.getType()); if (method.isPresent()) { filterSingle(filtered, parent, nextId, method); } else { // List child nodes method = ReflectionUtils.findMethodReflex(managedType, "get" + nextId.getType().getSimpleName(), Collections.>emptyList(), List.class); if (method.isPresent()) { filterList(filtered, parent, nextId, method); } else { throw new IllegalStateException( "Unable to filter " + nextId + " from " + parent + " getters not found using reflexion"); } } } return filtered; } @SuppressWarnings("unchecked") private static void filterList(final List filtered, final DataObject parent, final InstanceIdentifier.PathArgument nextId, final Optional method) { final List invoke = (List)invoke(method.get(), nextId, parent); if (nextId instanceof InstanceIdentifier.IdentifiableItem) { final Identifier key = ((InstanceIdentifier.IdentifiableItem) nextId).getKey(); filtered.addAll(Collections2.filter(invoke, new Predicate() { @Override public boolean apply(@Nullable final DataObject input) { final Optional keyGetter = ReflectionUtils.findMethodReflex(nextId.getType(), "get", Collections.>emptyList(), key.getClass()); final Object actualKey; actualKey = invoke(keyGetter.get(), nextId, input); return key.equals(actualKey); } })); } else { filtered.addAll(invoke); } } private static void filterSingle(final List filtered, final DataObject parent, final InstanceIdentifier.PathArgument nextId, final Optional method) { filtered.add(nextId.getType().cast(invoke(method.get(), nextId, parent))); } private static Object invoke(final Method method, final InstanceIdentifier.PathArgument nextId, final DataObject parent) { try { return method.invoke(parent); } catch (IllegalAccessException | InvocationTargetException e) { throw new IllegalArgumentException("Unable to get " + nextId + " from " + parent, e); } } }