From d50fd36087206b887af371ab36ca56f4727ca756 Mon Sep 17 00:00:00 2001 From: Tony Tkacik Date: Mon, 5 May 2014 20:19:55 +0200 Subject: [PATCH] Bug 949: Fixed support for DataChangeListeners listening directly on Augmentation Used InstanceIdentifier codec does not support AugmentIdentifier, since it is designed for CompositeNode format (serialization as defined by YANG Spec). Instance Identifier for NormalizedNode format allows to directly reference augmentation, for which support was missing. Added a special case handling of Instance Identifiers which targets augmentation directly, since Binding-Aware API and NormalizedNode API allows it. Change-Id: I6371a54dae0e32cac4e1c9b9ab309e91ec3192d9 Signed-off-by: Tony Tkacik --- .../impl/BindingToNormalizedNodeCodec.java | 259 ++++++++++++++++-- .../bugfix/WriteParentListenAugmentTest.java | 89 ++++++ 2 files changed, 326 insertions(+), 22 deletions(-) create mode 100644 opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/bugfix/WriteParentListenAugmentTest.java diff --git a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/BindingToNormalizedNodeCodec.java b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/BindingToNormalizedNodeCodec.java index 35ebe76499..6329637dd0 100644 --- a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/BindingToNormalizedNodeCodec.java +++ b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/BindingToNormalizedNodeCodec.java @@ -7,28 +7,43 @@ */ package org.opendaylight.controller.md.sal.binding.impl; +import java.lang.reflect.Method; +import java.lang.reflect.Type; import java.util.AbstractMap.SimpleEntry; +import java.util.LinkedList; +import java.util.List; import java.util.Map.Entry; +import java.util.concurrent.Callable; import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizationException; import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizer; +import org.opendaylight.yangtools.concepts.util.ClassLoaderUtils; import org.opendaylight.yangtools.yang.binding.Augmentation; +import org.opendaylight.yangtools.yang.binding.DataContainer; import org.opendaylight.yangtools.yang.binding.DataObject; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.Item; +import org.opendaylight.yangtools.yang.binding.util.BindingReflections; +import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.data.api.CompositeNode; +import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.AugmentationIdentifier; +import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifier; import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument; import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode; +import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild; import org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; import org.opendaylight.yangtools.yang.data.impl.codec.BindingIndependentMappingService; import org.opendaylight.yangtools.yang.data.impl.codec.DeserializationException; +import org.opendaylight.yangtools.yang.data.impl.schema.Builders; import org.opendaylight.yangtools.yang.model.api.SchemaContext; import org.opendaylight.yangtools.yang.model.api.SchemaContextListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; public class BindingToNormalizedNodeCodec implements SchemaContextListener { @@ -44,10 +59,15 @@ public class BindingToNormalizedNodeCodec implements SchemaContextListener { public org.opendaylight.yangtools.yang.data.api.InstanceIdentifier toNormalized( final InstanceIdentifier binding) { - final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier legacyPath = bindingToLegacy.toDataDom(binding); - final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier normalized = legacyToNormalized.toNormalized(legacyPath); - LOG.trace("InstanceIdentifier Path {} Serialization: Legacy representation {}, Normalized representation: {}",binding,legacyPath,normalized); - return normalized; + + // Used instance-identifier codec do not support serialization of last path + // argument if it is Augmentation (behaviour expected by old datastore) + // in this case, we explicitly check if last argument is augmentation + // to process it separately + if (isAugmentationIdentifier(binding)) { + return toNormalizedAugmented(binding); + } + return toNormalizedImpl(binding); } public Entry> toNormalizedNode( @@ -58,39 +78,85 @@ public class BindingToNormalizedNodeCodec implements SchemaContextListener { public Entry> toNormalizedNode( final Entry, DataObject> binding) { - Entry legacyEntry = bindingToLegacy.toDataDom(binding); - Entry> normalizedEntry = legacyToNormalized.toNormalized(legacyEntry); - LOG.trace("Serialization of {}, Legacy Representation: {}, Normalized Representation: {}",binding,legacyEntry,normalizedEntry); - if(Augmentation.class.isAssignableFrom(binding.getKey().getTargetType())) { - - for(DataContainerChild child : ((DataContainerNode) normalizedEntry.getValue()).getValue()) { - if(child instanceof AugmentationNode) { - ImmutableList childArgs = ImmutableList.builder() - .addAll(normalizedEntry.getKey().getPath()) - .add(child.getIdentifier()) - .build(); - org.opendaylight.yangtools.yang.data.api.InstanceIdentifier childPath = new org.opendaylight.yangtools.yang.data.api.InstanceIdentifier(childArgs); - return new SimpleEntry>(childPath,child); - } + Entry legacyEntry = bindingToLegacy + .toDataDom(binding); + Entry> normalizedEntry = legacyToNormalized + .toNormalized(legacyEntry); + LOG.trace("Serialization of {}, Legacy Representation: {}, Normalized Representation: {}", binding, + legacyEntry, normalizedEntry); + if (Augmentation.class.isAssignableFrom(binding.getKey().getTargetType())) { + + for (DataContainerChild child : ((DataContainerNode) normalizedEntry + .getValue()).getValue()) { + if (child instanceof AugmentationNode) { + ImmutableList childArgs = ImmutableList. builder() + .addAll(normalizedEntry.getKey().getPath()).add(child.getIdentifier()).build(); + org.opendaylight.yangtools.yang.data.api.InstanceIdentifier childPath = new org.opendaylight.yangtools.yang.data.api.InstanceIdentifier( + childArgs); + return new SimpleEntry>( + childPath, child); + } } } return normalizedEntry; - } public InstanceIdentifier toBinding( final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier normalized) throws DeserializationException { + PathArgument lastArgument = Iterables.getLast(normalized.getPath()); + // Used instance-identifier codec do not support serialization of last path + // argument if it is AugmentationIdentifier (behaviour expected by old datastore) + // in this case, we explicitly check if last argument is augmentation + // to process it separately + if (lastArgument instanceof AugmentationIdentifier) { + return toBindingAugmented(normalized); + } + return toBindingImpl(normalized); + } + + private InstanceIdentifier toBindingAugmented( + final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier normalized) throws DeserializationException { + InstanceIdentifier potential = toBindingImpl(normalized); + // Shorthand check, if codec already supports deserialization + // of AugmentationIdentifier we will return + if(isAugmentationIdentifier(potential)) { + return potential; + } + + AugmentationIdentifier lastArgument = (AugmentationIdentifier) Iterables.getLast(normalized.getPath()); + + // Here we employ small trick - Binding-aware Codec injects an pointer to augmentation class + // if child is referenced - so we will reference child and then shorten path. + for (QName child : lastArgument.getPossibleChildNames()) { + org.opendaylight.yangtools.yang.data.api.InstanceIdentifier childPath = new org.opendaylight.yangtools.yang.data.api.InstanceIdentifier( + ImmutableList. builder() + .addAll(normalized.getPath()).add(new NodeIdentifier(child)).build()); + try { + + InstanceIdentifier potentialPath = shortenToLastAugment(toBindingImpl(childPath)); + return potentialPath; + } catch (Exception e) { + LOG.trace("Unable to deserialize aug. child path for {}",childPath,e); + } + } + return toBindingImpl(normalized); + } + + private InstanceIdentifier toBindingImpl( + final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier normalized) + throws DeserializationException { org.opendaylight.yangtools.yang.data.api.InstanceIdentifier legacyPath; try { legacyPath = legacyToNormalized.toLegacy(normalized); } catch (DataNormalizationException e) { - throw new IllegalStateException("Could not denormalize path.",e); + throw new IllegalStateException("Could not denormalize path.", e); } - LOG.trace("InstanceIdentifier Path Deserialization: Legacy representation {}, Normalized representation: {}",legacyPath,normalized); + LOG.trace("InstanceIdentifier Path Deserialization: Legacy representation {}, Normalized representation: {}", + legacyPath, normalized); return bindingToLegacy.fromDataDom(legacyPath); } @@ -103,7 +169,19 @@ public class BindingToNormalizedNodeCodec implements SchemaContextListener { public DataObject toBinding(final InstanceIdentifier path, final NormalizedNode normalizedNode) throws DeserializationException { - return bindingToLegacy.dataObjectFromDataDom(path, (CompositeNode) DataNormalizer.toLegacy(normalizedNode)); + CompositeNode legacy = null; + if(isAugmentationIdentifier(path) && normalizedNode instanceof AugmentationNode) { + QName augIdentifier = BindingReflections.findQName(path.getTargetType()); + ContainerNode virtualNode = Builders.containerBuilder() // + .withNodeIdentifier(new NodeIdentifier(augIdentifier)) // + .withChild((DataContainerChild) normalizedNode) // + .build(); + legacy = (CompositeNode) DataNormalizer.toLegacy(virtualNode); + } else { + legacy = (CompositeNode) DataNormalizer.toLegacy(normalizedNode); + } + + return bindingToLegacy.dataObjectFromDataDom(path, legacy); } public DataNormalizer getDataNormalizer() { @@ -123,4 +201,141 @@ public class BindingToNormalizedNodeCodec implements SchemaContextListener { legacyToNormalized = new DataNormalizer(arg0); } + private org.opendaylight.yangtools.yang.data.api.InstanceIdentifier toNormalizedAugmented( + final InstanceIdentifier augPath) { + org.opendaylight.yangtools.yang.data.api.InstanceIdentifier processed = toNormalizedImpl(augPath); + // If used instance identifier codec added supports for deserialization + // of last AugmentationIdentifier we will just reuse it + if(isAugmentationIdentifier(processed)) { + return processed; + } + // Here we employ small trick - DataNormalizer injecst augmentation identifier if child is + // also part of the path (since using a child we can safely identify augmentation) + // so, we scan augmentation for children add it to path + // and use original algorithm, then shorten it to last augmentation + for (@SuppressWarnings("rawtypes") Class augChild : getAugmentationChildren(augPath.getTargetType())) { + @SuppressWarnings("unchecked") + InstanceIdentifier childPath = augPath.child(augChild); + org.opendaylight.yangtools.yang.data.api.InstanceIdentifier normalized = toNormalizedImpl(childPath); + org.opendaylight.yangtools.yang.data.api.InstanceIdentifier potentialDiscovered = shortenToLastAugmentation(normalized); + if (potentialDiscovered != null) { + return potentialDiscovered; + } + } + return processed; + } + + + + private org.opendaylight.yangtools.yang.data.api.InstanceIdentifier shortenToLastAugmentation( + final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier normalized) { + int position = 0; + int foundPosition = -1; + for (PathArgument arg : normalized.getPath()) { + position++; + if (arg instanceof AugmentationIdentifier) { + foundPosition = position; + } + } + if (foundPosition > 0) { + return new org.opendaylight.yangtools.yang.data.api.InstanceIdentifier(normalized.getPath().subList(0, + foundPosition)); + } + return null; + } + + private InstanceIdentifier shortenToLastAugment( + final InstanceIdentifier binding) { + int position = 0; + int foundPosition = -1; + for(org.opendaylight.yangtools.yang.binding.InstanceIdentifier.PathArgument arg : binding.getPathArguments()) { + position++; + if (isAugmentation(arg.getType())) { + foundPosition = position; + } + } + return InstanceIdentifier.create(Iterables.limit(binding.getPathArguments(), foundPosition)); + } + + + + private org.opendaylight.yangtools.yang.data.api.InstanceIdentifier toNormalizedImpl( + final InstanceIdentifier binding) { + final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier legacyPath = bindingToLegacy + .toDataDom(binding); + final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier normalized = legacyToNormalized + .toNormalized(legacyPath); + return normalized; + } + + @SuppressWarnings("unchecked") + private Iterable> getAugmentationChildren(final Class targetType) { + List> ret = new LinkedList<>(); + for (Method method : targetType.getMethods()) { + Class entity = getYangModeledType(method); + if (entity != null) { + ret.add((Class) entity); + } + } + return ret; + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + private Class getYangModeledType(final Method method) { + if (method.getName().equals("getClass") || !method.getName().startsWith("get") + || method.getParameterTypes().length > 0) { + return null; + } + + Class returnType = method.getReturnType(); + if (DataContainer.class.isAssignableFrom(returnType)) { + return (Class) returnType; + } else if (List.class.isAssignableFrom(returnType)) { + try { + return ClassLoaderUtils.withClassLoader(method.getDeclaringClass().getClassLoader(), + new Callable() { + + @SuppressWarnings("rawtypes") + @Override + public Class call() throws Exception { + Type listResult = ClassLoaderUtils.getFirstGenericParameter(method + .getGenericReturnType()); + if (listResult instanceof Class + && DataObject.class.isAssignableFrom((Class) listResult)) { + return (Class) listResult; + } + return null; + } + + }); + } catch (Exception e) { + LOG.debug("Could not get YANG modeled entity for {}", method, e); + return null; + } + + } + return null; + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private static InstanceIdentifier toWildcarded(final InstanceIdentifier orig) { + List wildArgs = new LinkedList<>(); + for (org.opendaylight.yangtools.yang.binding.InstanceIdentifier.PathArgument arg : orig.getPathArguments()) { + wildArgs.add(new Item(arg.getType())); + } + return InstanceIdentifier.create(wildArgs); + } + + + private static boolean isAugmentation(final Class type) { + return Augmentation.class.isAssignableFrom(type); + } + + private static boolean isAugmentationIdentifier(final InstanceIdentifier path) { + return Augmentation.class.isAssignableFrom(path.getTargetType()); + } + + private boolean isAugmentationIdentifier(final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier processed) { + return Iterables.getLast(processed.getPath()) instanceof AugmentationIdentifier; + } } diff --git a/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/bugfix/WriteParentListenAugmentTest.java b/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/bugfix/WriteParentListenAugmentTest.java new file mode 100644 index 0000000000..cb31885f02 --- /dev/null +++ b/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/bugfix/WriteParentListenAugmentTest.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.sal.binding.test.bugfix; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.concurrent.TimeUnit; + +import org.junit.Test; +import org.opendaylight.controller.md.sal.common.api.data.DataChangeEvent; +import org.opendaylight.controller.sal.binding.api.data.DataChangeListener; +import org.opendaylight.controller.sal.binding.api.data.DataModificationTransaction; +import org.opendaylight.controller.sal.binding.test.AbstractDataServiceTest; +import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNode; +import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNodeBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeKey; +import org.opendaylight.yangtools.concepts.ListenerRegistration; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; + +import com.google.common.util.concurrent.SettableFuture; + +public class WriteParentListenAugmentTest extends AbstractDataServiceTest { + + private static final String NODE_ID = "node:1"; + + private static final NodeKey NODE_KEY = new NodeKey(new NodeId(NODE_ID)); + private static final InstanceIdentifier NODE_INSTANCE_ID_BA = InstanceIdentifier.builder(Nodes.class) // + .child(Node.class, NODE_KEY).toInstance(); + + private static final InstanceIdentifier AUGMENT_WILDCARDED_PATH = InstanceIdentifier + .builder(Nodes.class).child(Node.class).augmentation(FlowCapableNode.class).toInstance(); + + private static final InstanceIdentifier AUGMENT_NODE_PATH = InstanceIdentifier + .builder(Nodes.class).child(Node.class, NODE_KEY).augmentation(FlowCapableNode.class).toInstance(); + + @Test + public void writeNodeListenAugment() throws Exception { + + final SettableFuture, DataObject>> event = SettableFuture.create(); + + ListenerRegistration dclRegistration = baDataService.registerDataChangeListener( + AUGMENT_WILDCARDED_PATH, new DataChangeListener() { + + @Override + public void onDataChanged(final DataChangeEvent, DataObject> change) { + event.set(change); + } + }); + + DataModificationTransaction modification = baDataService.beginTransaction(); + + Node node = new NodeBuilder() // + .setKey(NODE_KEY) // + .addAugmentation(FlowCapableNode.class, flowCapableNode("one")).build(); + modification.putOperationalData(NODE_INSTANCE_ID_BA, node); + modification.commit().get(); + + DataChangeEvent, DataObject> receivedEvent = event.get(1000, TimeUnit.MILLISECONDS); + assertTrue(receivedEvent.getCreatedOperationalData().containsKey(AUGMENT_NODE_PATH)); + + dclRegistration.close(); + + DataModificationTransaction mod2 = baDataService.beginTransaction(); + mod2.putOperationalData(AUGMENT_NODE_PATH, flowCapableNode("two")); + mod2.commit().get(); + + FlowCapableNode readedAug = (FlowCapableNode) baDataService.readOperationalData(AUGMENT_NODE_PATH); + assertEquals("two", readedAug.getDescription()); + + } + + private FlowCapableNode flowCapableNode(final String description) { + return new FlowCapableNodeBuilder() // + .setDescription(description) // + .build(); + } +} \ No newline at end of file -- 2.36.6