From 8542d448f20b0eb4ace904fa0babd24a98d5c4e4 Mon Sep 17 00:00:00 2001 From: Tony Tkacik Date: Wed, 1 Apr 2015 12:37:52 +0200 Subject: [PATCH] Bug 2933: Implemented DataTreeChangeService DataTreeChangeService implementation is based on DOM Data Tree change service and serves only as an adapter, which lazily translates events and data in events as they are accessed by user code. This is rather different behaviour from DataChangeListener where API contract required semi-eager deserialization of supplied instance identifier. Change-Id: If4954e966acc21ffec36e8cc37f95717492a1675 Signed-off-by: Tony Tkacik --- .../impl/BindingDOMDataBrokerAdapter.java | 33 +++- ...ndingDOMDataTreeChangeListenerAdapter.java | 42 ++++ ...indingDOMDataTreeChangeServiceAdapter.java | 59 ++++++ ...ingDataTreeChangeListenerRegistration.java | 29 +++ .../binding/impl/BindingStructuralType.java | 130 +++++++++++++ .../impl/BindingToNormalizedNodeCodec.java | 40 ++-- .../impl/LazyDataObjectModification.java | 180 ++++++++++++++++++ .../impl/LazyDataTreeModification.java | 67 +++++++ .../impl/test/DataTreeChangeListenerTest.java | 167 ++++++++++++++++ 9 files changed, 726 insertions(+), 21 deletions(-) create mode 100644 opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/BindingDOMDataTreeChangeListenerAdapter.java create mode 100644 opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/BindingDOMDataTreeChangeServiceAdapter.java create mode 100644 opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/BindingDataTreeChangeListenerRegistration.java create mode 100644 opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/BindingStructuralType.java create mode 100644 opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/LazyDataObjectModification.java create mode 100644 opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/LazyDataTreeModification.java create mode 100644 opendaylight/md-sal/sal-binding-broker/src/test/java/org/opendaylight/controller/md/sal/binding/impl/test/DataTreeChangeListenerTest.java diff --git a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/BindingDOMDataBrokerAdapter.java b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/BindingDOMDataBrokerAdapter.java index b17be16615..f63db76b4f 100644 --- a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/BindingDOMDataBrokerAdapter.java +++ b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/BindingDOMDataBrokerAdapter.java @@ -13,14 +13,18 @@ import com.google.common.collect.ImmutableSet; import java.util.Set; import org.opendaylight.controller.md.sal.binding.api.BindingTransactionChain; import org.opendaylight.controller.md.sal.binding.api.DataBroker; +import org.opendaylight.controller.md.sal.binding.api.DataTreeChangeListener; +import org.opendaylight.controller.md.sal.binding.api.DataTreeChangeService; +import org.opendaylight.controller.md.sal.binding.api.DataTreeIdentifier; import org.opendaylight.controller.md.sal.binding.api.ReadOnlyTransaction; import org.opendaylight.controller.md.sal.binding.api.ReadWriteTransaction; import org.opendaylight.controller.md.sal.binding.api.WriteTransaction; import org.opendaylight.controller.md.sal.binding.impl.BindingDOMAdapterBuilder.Factory; import org.opendaylight.controller.md.sal.common.api.data.TransactionChainListener; import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker; +import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeChangeService; import org.opendaylight.controller.md.sal.dom.api.DOMService; -import org.opendaylight.controller.sal.core.api.model.SchemaService; +import org.opendaylight.yangtools.concepts.ListenerRegistration; /** * The DataBrokerImpl simply defers to the DOMDataBroker for all its operations. @@ -43,14 +47,16 @@ public class BindingDOMDataBrokerAdapter extends AbstractForwardedDataBroker imp } }; + private final DataTreeChangeService treeChangeService; public BindingDOMDataBrokerAdapter(final DOMDataBroker domDataBroker, final BindingToNormalizedNodeCodec codec) { super(domDataBroker, codec); - } - - @Deprecated - public BindingDOMDataBrokerAdapter(final DOMDataBroker domDataBroker, final BindingToNormalizedNodeCodec codec, final SchemaService schemaService) { - super(domDataBroker, codec,schemaService); + final DOMDataTreeChangeService domTreeChange = (DOMDataTreeChangeService) domDataBroker.getSupportedExtensions().get(DOMDataTreeChangeService.class); + if(domTreeChange != null) { + treeChangeService = BindingDOMDataTreeChangeServiceAdapter.create(codec, domTreeChange); + } else { + treeChangeService = null; + } } @Override @@ -82,13 +88,20 @@ public class BindingDOMDataBrokerAdapter extends AbstractForwardedDataBroker imp } @Override - protected DataBroker createInstance(BindingToNormalizedNodeCodec codec, - ClassToInstanceMap delegates) { - DOMDataBroker domDataBroker = delegates.getInstance(DOMDataBroker.class); + protected DataBroker createInstance(final BindingToNormalizedNodeCodec codec, + final ClassToInstanceMap delegates) { + final DOMDataBroker domDataBroker = delegates.getInstance(DOMDataBroker.class); return new BindingDOMDataBrokerAdapter(domDataBroker, codec); } + } - + public ListenerRegistration registerDataTreeChangeListener( + final DataTreeIdentifier treeId, final L listener) { + if(treeChangeService == null) { + throw new UnsupportedOperationException("Underlying data broker does not expose DOMDataTreeChangeService."); + } + return treeChangeService.registerDataTreeChangeListener(treeId, listener); } + } diff --git a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/BindingDOMDataTreeChangeListenerAdapter.java b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/BindingDOMDataTreeChangeListenerAdapter.java new file mode 100644 index 0000000000..bc60bdcba2 --- /dev/null +++ b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/BindingDOMDataTreeChangeListenerAdapter.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2015 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.md.sal.binding.impl; + +import com.google.common.base.Preconditions; +import java.util.Collection; +import org.opendaylight.controller.md.sal.binding.api.DataTreeChangeListener; +import org.opendaylight.controller.md.sal.binding.api.DataTreeModification; +import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; +import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeChangeListener; +import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate; + +/** + * Adapter wrapping Binding {@link DataTreeChangeListener} and exposing + * it as {@link DOMDataTreeChangeListener} and translated DOM events + * to their Binding equivalent. + * + */ +final class BindingDOMDataTreeChangeListenerAdapter implements DOMDataTreeChangeListener { + + private final BindingToNormalizedNodeCodec codec; + private final DataTreeChangeListener listener; + private final LogicalDatastoreType store; + + BindingDOMDataTreeChangeListenerAdapter(final BindingToNormalizedNodeCodec codec, final DataTreeChangeListener listener, + final LogicalDatastoreType store) { + this.codec = Preconditions.checkNotNull(codec); + this.listener = Preconditions.checkNotNull(listener); + this.store = Preconditions.checkNotNull(store); + } + + @Override + public void onDataTreeChanged(final Collection domChanges) { + final Collection bindingChanges = LazyDataTreeModification.from(codec, domChanges, store); + listener.onDataTreeChanged(bindingChanges); + } +} diff --git a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/BindingDOMDataTreeChangeServiceAdapter.java b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/BindingDOMDataTreeChangeServiceAdapter.java new file mode 100644 index 0000000000..04c6ad5702 --- /dev/null +++ b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/BindingDOMDataTreeChangeServiceAdapter.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2015 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.md.sal.binding.impl; + +import com.google.common.base.Preconditions; +import org.opendaylight.controller.md.sal.binding.api.DataTreeChangeListener; +import org.opendaylight.controller.md.sal.binding.api.DataTreeChangeService; +import org.opendaylight.controller.md.sal.binding.api.DataTreeIdentifier; +import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeChangeService; +import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeIdentifier; +import org.opendaylight.yangtools.concepts.ListenerRegistration; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; + + +/** + * + * Adapter exposing Binding {@link DataTreeChangeService} and wrapping + * {@link DOMDataTreeChangeService} and is responsible for translation + * and instantiation of {@link BindingDOMDataTreeChangeListenerAdapter} + * adapters. + * + * Each registered {@link DataTreeChangeListener} is wrapped using + * adapter and registered directly to DOM service. + */ +final class BindingDOMDataTreeChangeServiceAdapter implements DataTreeChangeService { + + private final BindingToNormalizedNodeCodec codec; + private final DOMDataTreeChangeService dataTreeChangeService; + + private BindingDOMDataTreeChangeServiceAdapter(final BindingToNormalizedNodeCodec codec, + final DOMDataTreeChangeService dataTreeChangeService) { + this.codec = Preconditions.checkNotNull(codec); + this.dataTreeChangeService = Preconditions.checkNotNull(dataTreeChangeService); + } + + static DataTreeChangeService create(final BindingToNormalizedNodeCodec codec, + final DOMDataTreeChangeService dataTreeChangeService) { + return new BindingDOMDataTreeChangeServiceAdapter(codec, dataTreeChangeService); + } + + @Override + public ListenerRegistration registerDataTreeChangeListener( + final DataTreeIdentifier treeId, final L listener) { + final DOMDataTreeIdentifier domIdentifier = toDomTreeIdentifier(treeId); + final BindingDOMDataTreeChangeListenerAdapter domListener = new BindingDOMDataTreeChangeListenerAdapter(codec,listener, treeId.getDatastoreType()); + final ListenerRegistration domReg = dataTreeChangeService.registerDataTreeChangeListener(domIdentifier, domListener); + return new BindingDataTreeChangeListenerRegistration<>(listener,domReg); + } + + private DOMDataTreeIdentifier toDomTreeIdentifier(final DataTreeIdentifier treeId) { + final YangInstanceIdentifier domPath = codec.toYangInstanceIdentifier(treeId.getRootIdentifier()); + return new DOMDataTreeIdentifier(treeId.getDatastoreType(), domPath); + } +} diff --git a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/BindingDataTreeChangeListenerRegistration.java b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/BindingDataTreeChangeListenerRegistration.java new file mode 100644 index 0000000000..524d97c497 --- /dev/null +++ b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/BindingDataTreeChangeListenerRegistration.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2015 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.md.sal.binding.impl; + +import com.google.common.base.Preconditions; +import org.opendaylight.controller.md.sal.binding.api.DataTreeChangeListener; +import org.opendaylight.yangtools.concepts.AbstractListenerRegistration; +import org.opendaylight.yangtools.concepts.ListenerRegistration; + +class BindingDataTreeChangeListenerRegistration extends AbstractListenerRegistration { + + private final ListenerRegistration domReg; + + BindingDataTreeChangeListenerRegistration(final L listener, + final ListenerRegistration domReg) { + super(listener); + this.domReg = Preconditions.checkNotNull(domReg); + } + + @Override + protected void removeRegistration() { + domReg.close(); + } +} diff --git a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/BindingStructuralType.java b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/BindingStructuralType.java new file mode 100644 index 0000000000..7cd17dc4d8 --- /dev/null +++ b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/BindingStructuralType.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2015 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.md.sal.binding.impl; + +import com.google.common.base.Optional; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument; +import org.opendaylight.yangtools.yang.data.api.schema.AnyXmlNode; +import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode; +import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode; +import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; +import org.opendaylight.yangtools.yang.data.api.schema.LeafNode; +import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode; +import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode; +import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode; +import org.opendaylight.yangtools.yang.data.api.schema.MapNode; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode; + +/** + * + * Defines structural mapping of Normalized Node to Binding data + * addressable by Instance Identifier. + * + * Not all binding data are addressable by instance identifier + * and there are some differences. + * + * See {@link #NOT_ADDRESSABLE},{@link #INVISIBLE_CONTAINER},{@link #VISIBLE_CONTAINER} + * for more details. + * + * + */ +enum BindingStructuralType { + + /** + * DOM Item is not addressable in Binding Instance Identifier, + * data is not lost, but are available only via parent object. + * + * Such types of data are leaf-lists, leafs, list without keys + * or anyxml. + * + */ + NOT_ADDRESSABLE, + /** + * Data container is addressable in NormalizedNode format, + * but in Binding it is not represented in Instance Identifier. + * + * This are choice / case nodes. + * + * This data is still accessible using parent object and their + * children are addressable. + * + */ + INVISIBLE_CONTAINER, + /** + * Data container is addressable in NormalizedNode format, + * but in Binding it is not represented in Instance Identifier. + * + * This are list nodes. + * + * This data is still accessible using parent object and their + * children are addressable. + * + */ + INVISIBLE_LIST, + /** + * Data container is addressable in Binding Instance Identifier format + * and also YangInstanceIdentifier format. + * + */ + VISIBLE_CONTAINER, + /** + * Mapping algorithm was unable to detect type or was not updated after introduction + * of new NormalizedNode type. + */ + UNKNOWN; + + static BindingStructuralType from(final DataTreeCandidateNode domChildNode) { + final Optional> dataBased = domChildNode.getDataAfter().or(domChildNode.getDataBefore()); + if(dataBased.isPresent()) { + return from(dataBased.get()); + } + return from(domChildNode.getIdentifier()); + } + + private static BindingStructuralType from(final PathArgument identifier) { + if(identifier instanceof NodeIdentifierWithPredicates || identifier instanceof AugmentationIdentifier) { + return VISIBLE_CONTAINER; + } + if(identifier instanceof NodeWithValue) { + return NOT_ADDRESSABLE; + } + return UNKNOWN; + } + + static BindingStructuralType from(final NormalizedNode data) { + if(isNotAddressable(data)) { + return NOT_ADDRESSABLE; + } + if(data instanceof MapNode) { + return INVISIBLE_LIST; + } + if(data instanceof ChoiceNode) { + return INVISIBLE_CONTAINER; + } + if(isVisibleContainer(data)) { + return VISIBLE_CONTAINER; + } + return UNKNOWN; + } + + private static boolean isVisibleContainer(final NormalizedNode data) { + return data instanceof MapEntryNode || data instanceof ContainerNode || data instanceof AugmentationNode; + } + + private static boolean isNotAddressable(final NormalizedNode d) { + return d instanceof LeafNode + || d instanceof AnyXmlNode + || d instanceof LeafSetNode + || d instanceof LeafSetEntryNode; + } + +} 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 d9e58e538d..b727e5317b 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 @@ -9,9 +9,12 @@ package org.opendaylight.controller.md.sal.binding.impl; import com.google.common.base.Function; import com.google.common.base.Optional; +import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableBiMap; import java.lang.reflect.Method; +import java.util.AbstractMap.SimpleEntry; import java.util.Iterator; +import java.util.Map; import java.util.Map.Entry; import javax.annotation.Nonnull; import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizationException; @@ -19,6 +22,7 @@ import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizat import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizer; import org.opendaylight.yangtools.binding.data.codec.api.BindingCodecTree; import org.opendaylight.yangtools.binding.data.codec.api.BindingCodecTreeFactory; +import org.opendaylight.yangtools.binding.data.codec.api.BindingCodecTreeNode; import org.opendaylight.yangtools.binding.data.codec.api.BindingNormalizedNodeSerializer; import org.opendaylight.yangtools.binding.data.codec.impl.BindingNormalizedNodeCodecRegistry; import org.opendaylight.yangtools.sal.binding.generator.impl.GeneratedClassLoadingStrategy; @@ -61,13 +65,13 @@ public class BindingToNormalizedNodeCodec implements BindingCodecTreeFactory, Bi } @Override - public YangInstanceIdentifier toYangInstanceIdentifier(InstanceIdentifier binding) { + public YangInstanceIdentifier toYangInstanceIdentifier(final InstanceIdentifier binding) { return codecRegistry.toYangInstanceIdentifier(binding); } @Override public Entry> toNormalizedNode( - InstanceIdentifier path, T data) { + final InstanceIdentifier path, final T data) { return codecRegistry.toNormalizedNode(path, data); } @@ -78,33 +82,33 @@ public class BindingToNormalizedNodeCodec implements BindingCodecTreeFactory, Bi } @Override - public Entry, DataObject> fromNormalizedNode(YangInstanceIdentifier path, - NormalizedNode data) { + public Entry, DataObject> fromNormalizedNode(final YangInstanceIdentifier path, + final NormalizedNode data) { return codecRegistry.fromNormalizedNode(path, data); } @Override - public Notification fromNormalizedNodeNotification(SchemaPath path, ContainerNode data) { + public Notification fromNormalizedNodeNotification(final SchemaPath path, final ContainerNode data) { return codecRegistry.fromNormalizedNodeNotification(path, data); } @Override - public DataObject fromNormalizedNodeRpcData(SchemaPath path, ContainerNode data) { + public DataObject fromNormalizedNodeRpcData(final SchemaPath path, final ContainerNode data) { return codecRegistry.fromNormalizedNodeRpcData(path, data); } @Override - public InstanceIdentifier fromYangInstanceIdentifier(YangInstanceIdentifier dom) { + public InstanceIdentifier fromYangInstanceIdentifier(final YangInstanceIdentifier dom) { return codecRegistry.fromYangInstanceIdentifier(dom); } @Override - public ContainerNode toNormalizedNodeNotification(Notification data) { + public ContainerNode toNormalizedNodeNotification(final Notification data) { return codecRegistry.toNormalizedNodeNotification(data); } @Override - public ContainerNode toNormalizedNodeRpcData(DataContainer data) { + public ContainerNode toNormalizedNodeRpcData(final DataContainer data) { return codecRegistry.toNormalizedNodeRpcData(data); } @@ -225,13 +229,27 @@ public class BindingToNormalizedNodeCodec implements BindingCodecTreeFactory, Bi } @Override - public BindingCodecTree create(BindingRuntimeContext context) { + public BindingCodecTree create(final BindingRuntimeContext context) { return codecRegistry.create(context); } @Override - public BindingCodecTree create(SchemaContext context, Class... bindingClasses) { + public BindingCodecTree create(final SchemaContext context, final Class... bindingClasses) { return codecRegistry.create(context, bindingClasses); } + @Nonnull protected Map.Entry, BindingCodecTreeNode> getSubtreeCodec( + final YangInstanceIdentifier domIdentifier) { + + final BindingCodecTree currentCodecTree = codecRegistry.getCodecContext(); + final InstanceIdentifier bindingPath = codecRegistry.fromYangInstanceIdentifier(domIdentifier); + Preconditions.checkArgument(bindingPath != null); + /** + * If we are able to deserialize YANG instance identifier, getSubtreeCodec must + * return non-null value. + */ + final BindingCodecTreeNode codecContext = currentCodecTree.getSubtreeCodec(bindingPath); + return new SimpleEntry, BindingCodecTreeNode>(bindingPath, codecContext); + } + } diff --git a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/LazyDataObjectModification.java b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/LazyDataObjectModification.java new file mode 100644 index 0000000000..b29ed3849b --- /dev/null +++ b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/LazyDataObjectModification.java @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2015 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.md.sal.binding.impl; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import org.opendaylight.controller.md.sal.binding.api.DataObjectModification; +import org.opendaylight.yangtools.binding.data.codec.api.BindingCodecTreeNode; +import org.opendaylight.yangtools.yang.binding.ChildOf; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.PathArgument; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Lazily translated {@link DataObjectModification} based on {@link DataTreeCandidateNode}. + * + * {@link LazyDataObjectModification} represents Data tree change event, + * but whole tree is not translated or resolved eagerly, but only child nodes + * which are directly accessed by user of data object modification. + * + * @param Type of Binding Data Object + */ +class LazyDataObjectModification implements DataObjectModification { + + private final static Logger LOG = LoggerFactory.getLogger(LazyDataObjectModification.class); + + private final BindingCodecTreeNode codec; + private final DataTreeCandidateNode domData; + private final PathArgument identifier; + private Collection> childNodesCache; + + private LazyDataObjectModification(final BindingCodecTreeNode codec, final DataTreeCandidateNode domData) { + this.codec = Preconditions.checkNotNull(codec); + this.domData = Preconditions.checkNotNull(domData); + this.identifier = codec.deserializePathArgument(domData.getIdentifier()); + } + + static DataObjectModification create(final BindingCodecTreeNode codec, + final DataTreeCandidateNode domData) { + return new LazyDataObjectModification<>(codec,domData); + } + + static Collection> from(final BindingCodecTreeNode parentCodec, + final Collection domChildNodes) { + final ArrayList> result = new ArrayList<>(domChildNodes.size()); + populateList(result, parentCodec, domChildNodes); + return result; + } + + private static void populateList(final List> result, + final BindingCodecTreeNode parentCodec, final Collection domChildNodes) { + for (final DataTreeCandidateNode domChildNode : domChildNodes) { + final BindingStructuralType type = BindingStructuralType.from(domChildNode); + if (type != BindingStructuralType.NOT_ADDRESSABLE) { + /* + * Even if type is UNKNOWN, from perspective of BindingStructuralType + * we try to load codec for it. We will use that type to further specify + * debug log. + */ + try { + final BindingCodecTreeNode childCodec = + parentCodec.yangPathArgumentChild(domChildNode.getIdentifier()); + populateList(result,type, childCodec, domChildNode); + } catch (final IllegalArgumentException e) { + if(type == BindingStructuralType.UNKNOWN) { + LOG.debug("Unable to deserialize unknown DOM node {}",domChildNode,e); + } else { + LOG.debug("Binding representation for DOM node {} was not found",domChildNode,e); + } + } + } + } + } + + + private static void populateList(final List> result, + final BindingStructuralType type, final BindingCodecTreeNode childCodec, + final DataTreeCandidateNode domChildNode) { + switch (type) { + case INVISIBLE_LIST: + // We use parent codec intentionally. + populateListWithSingleCodec(result, childCodec, domChildNode.getChildNodes()); + break; + case INVISIBLE_CONTAINER: + populateList(result, childCodec, domChildNode.getChildNodes()); + break; + case UNKNOWN: + case VISIBLE_CONTAINER: + result.add(create(childCodec, domChildNode)); + default: + break; + } + } + + private static void populateListWithSingleCodec(final List> result, + final BindingCodecTreeNode codec, final Collection childNodes) { + for (final DataTreeCandidateNode child : childNodes) { + result.add(create(codec, child)); + } + } + + @Override + public T getDataAfter() { + return deserialize(domData.getDataAfter()); + } + + @Override + public Class getDataType() { + return codec.getBindingClass(); + } + + @Override + public PathArgument getIdentifier() { + return identifier; + } + + @Override + public DataObjectModification.ModificationType getModificationType() { + switch(domData.getModificationType()) { + case WRITE: + return DataObjectModification.ModificationType.WRITE; + case SUBTREE_MODIFIED: + return DataObjectModification.ModificationType.SUBTREE_MODIFIED; + case DELETE: + return DataObjectModification.ModificationType.DELETE; + + default: + // TODO: Should we lie about modification type instead of exception? + throw new IllegalStateException("Unsupported DOM Modification type " + domData.getModificationType()); + } + } + + @Override + public Collection> getModifiedChildren() { + if(childNodesCache == null) { + childNodesCache = from(codec,domData.getChildNodes()); + } + return childNodesCache; + } + + public DataObjectModification getModifiedChild(final PathArgument arg) { + final List domArgumentList = new ArrayList<>(); + final BindingCodecTreeNode childCodec = codec.bindingPathArgumentChild(arg, domArgumentList); + final Iterator toEnter = domArgumentList.iterator(); + DataTreeCandidateNode current = domData; + while (toEnter.hasNext() && current != null) { + current = current.getModifiedChild(toEnter.next()); + } + if (current != null) { + return create(childCodec, current); + } + return null; + } + + @SuppressWarnings("unchecked") + public > DataObjectModification getModifiedChild(final Class arg) { + return (DataObjectModification) getModifiedChild(new InstanceIdentifier.Item<>(arg)); + } + + private T deserialize(final Optional> dataAfter) { + if(dataAfter.isPresent()) { + return codec.deserialize(dataAfter.get()); + } + return null; + } +} diff --git a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/LazyDataTreeModification.java b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/LazyDataTreeModification.java new file mode 100644 index 0000000000..26fd420c0e --- /dev/null +++ b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/LazyDataTreeModification.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2015 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.md.sal.binding.impl; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map.Entry; +import org.opendaylight.controller.md.sal.binding.api.DataObjectModification; +import org.opendaylight.controller.md.sal.binding.api.DataTreeIdentifier; +import org.opendaylight.controller.md.sal.binding.api.DataTreeModification; +import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; +import org.opendaylight.yangtools.binding.data.codec.api.BindingCodecTreeNode; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate; + +/** + * Lazily translated {@link DataTreeModification} based on {@link DataTreeCandidate}. + * + * {@link DataTreeModification} represents Data tree change event, + * but whole tree is not translated or resolved eagerly, but only child nodes + * which are directly accessed by user of data object modification. + * + */ +class LazyDataTreeModification implements DataTreeModification { + + private final DataTreeIdentifier path; + private final DataObjectModification rootNode; + + LazyDataTreeModification(final LogicalDatastoreType datastoreType, final InstanceIdentifier path, final BindingCodecTreeNode codec, final DataTreeCandidate domChange) { + this.path = new DataTreeIdentifier(datastoreType, path); + this.rootNode = LazyDataObjectModification.create(codec, domChange.getRootNode()); + } + + @Override + public DataObjectModification getRootNode() { + return rootNode; + } + + @Override + public DataTreeIdentifier getRootPath() { + return path; + } + + static DataTreeModification create(final BindingToNormalizedNodeCodec codec, final DataTreeCandidate domChange, + final LogicalDatastoreType datastoreType) { + final Entry, BindingCodecTreeNode> codecCtx = + codec.getSubtreeCodec(domChange.getRootPath()); + return new LazyDataTreeModification(datastoreType, codecCtx.getKey(), codecCtx.getValue(), domChange); + } + + static Collection from(final BindingToNormalizedNodeCodec codec, + final Collection domChanges, final LogicalDatastoreType datastoreType) { + final List result = new ArrayList<>(domChanges.size()); + for (final DataTreeCandidate domChange : domChanges) { + result.add(create(codec, domChange, datastoreType)); + } + return result; + } + +} diff --git a/opendaylight/md-sal/sal-binding-broker/src/test/java/org/opendaylight/controller/md/sal/binding/impl/test/DataTreeChangeListenerTest.java b/opendaylight/md-sal/sal-binding-broker/src/test/java/org/opendaylight/controller/md/sal/binding/impl/test/DataTreeChangeListenerTest.java new file mode 100644 index 0000000000..218233e8f1 --- /dev/null +++ b/opendaylight/md-sal/sal-binding-broker/src/test/java/org/opendaylight/controller/md/sal/binding/impl/test/DataTreeChangeListenerTest.java @@ -0,0 +1,167 @@ +package org.opendaylight.controller.md.sal.binding.impl.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.opendaylight.controller.md.sal.test.model.util.ListsBindingUtils.TOP_BAR_KEY; +import static org.opendaylight.controller.md.sal.test.model.util.ListsBindingUtils.TOP_FOO_KEY; +import static org.opendaylight.controller.md.sal.test.model.util.ListsBindingUtils.USES_ONE_KEY; +import static org.opendaylight.controller.md.sal.test.model.util.ListsBindingUtils.complexUsesAugment; +import static org.opendaylight.controller.md.sal.test.model.util.ListsBindingUtils.path; +import static org.opendaylight.controller.md.sal.test.model.util.ListsBindingUtils.top; +import static org.opendaylight.controller.md.sal.test.model.util.ListsBindingUtils.topLevelList; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.common.util.concurrent.SettableFuture; +import java.util.Collection; +import java.util.concurrent.TimeUnit; +import org.junit.Test; +import org.opendaylight.controller.md.sal.binding.api.DataBroker; +import org.opendaylight.controller.md.sal.binding.api.DataObjectModification; +import org.opendaylight.controller.md.sal.binding.api.DataObjectModification.ModificationType; +import org.opendaylight.controller.md.sal.binding.api.DataTreeChangeListener; +import org.opendaylight.controller.md.sal.binding.api.DataTreeIdentifier; +import org.opendaylight.controller.md.sal.binding.api.DataTreeModification; +import org.opendaylight.controller.md.sal.binding.api.WriteTransaction; +import org.opendaylight.controller.md.sal.binding.impl.BindingDOMDataBrokerAdapter; +import org.opendaylight.controller.md.sal.binding.test.AbstractDataBrokerTest; +import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.augment.rev140709.TreeComplexUsesAugment; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.Top; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.TwoLevelList; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.two.level.list.TopLevelList; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.PathArgument; +import org.opendaylight.yangtools.yang.binding.YangModuleInfo; +import org.opendaylight.yangtools.yang.binding.util.BindingReflections; + +public class DataTreeChangeListenerTest extends AbstractDataBrokerTest { + + private static final InstanceIdentifier TOP_PATH = InstanceIdentifier.create(Top.class); + private static final InstanceIdentifier FOO_PATH = path(TOP_FOO_KEY); + private static final PathArgument FOO_ARGUMENT = Iterables.getLast(FOO_PATH.getPathArguments()); + private static final TopLevelList FOO_DATA = topLevelList(TOP_FOO_KEY, complexUsesAugment(USES_ONE_KEY)); + private static final InstanceIdentifier BAR_PATH = path(TOP_BAR_KEY); + private static final PathArgument BAR_ARGUMENT = Iterables.getLast(BAR_PATH.getPathArguments()); + private static final TopLevelList BAR_DATA = topLevelList(TOP_BAR_KEY); + private static final DataTreeIdentifier TOP_IDENTIFIER = new DataTreeIdentifier(LogicalDatastoreType.OPERATIONAL, + TOP_PATH); + + private static final Top TOP_INITIAL_DATA = top(FOO_DATA); + + private BindingDOMDataBrokerAdapter dataBrokerImpl; + + private static final class EventCapturingListener implements DataTreeChangeListener { + + private SettableFuture> changes = SettableFuture.create(); + + @Override + public void onDataTreeChanged(final Collection changes) { + this.changes.set(changes); + + } + + Collection nextEvent() throws Exception { + final Collection result = changes.get(200,TimeUnit.MILLISECONDS); + changes = SettableFuture.create(); + return result; + } + + } + + @Override + protected Iterable getModuleInfos() throws Exception { + return ImmutableSet.of( + BindingReflections.getModuleInfo(TwoLevelList.class), + BindingReflections.getModuleInfo(TreeComplexUsesAugment.class) + ); + } + + @Override + protected void setupWithDataBroker(final DataBroker dataBroker) { + dataBrokerImpl = (BindingDOMDataBrokerAdapter) dataBroker; + } + + @Test + public void testTopLevelListener() throws Exception { + final EventCapturingListener listener = new EventCapturingListener(); + dataBrokerImpl.registerDataTreeChangeListener(TOP_IDENTIFIER, listener); + + createAndVerifyTop(listener); + + putTx(BAR_PATH, BAR_DATA).submit().checkedGet(); + final DataTreeModification afterBarPutEvent = Iterables.getOnlyElement(listener.nextEvent()); + final DataObjectModification barPutMod = + getOnlyChildModification(afterBarPutEvent.getRootNode(), ModificationType.SUBTREE_MODIFIED); + verifyModification(barPutMod, BAR_ARGUMENT, ModificationType.WRITE); + + deleteTx(BAR_PATH).submit().checkedGet(); + final DataTreeModification afterBarDeleteEvent = Iterables.getOnlyElement(listener.nextEvent()); + final DataObjectModification barDeleteMod = + getOnlyChildModification(afterBarDeleteEvent.getRootNode(), ModificationType.SUBTREE_MODIFIED); + verifyModification(barDeleteMod, BAR_ARGUMENT, ModificationType.DELETE); + } + + @Test + public void testWildcardedListListener() throws Exception { + final EventCapturingListener listener = new EventCapturingListener(); + final DataTreeIdentifier wildcard = new DataTreeIdentifier(LogicalDatastoreType.OPERATIONAL, TOP_PATH.child(TopLevelList.class)); + dataBrokerImpl.registerDataTreeChangeListener(wildcard, listener); + + putTx(TOP_PATH, TOP_INITIAL_DATA).submit().checkedGet(); + + final DataTreeModification fooWriteEvent = Iterables.getOnlyElement(listener.nextEvent()); + assertEquals(FOO_PATH, fooWriteEvent.getRootPath().getRootIdentifier()); + verifyModification(fooWriteEvent.getRootNode(), FOO_ARGUMENT, ModificationType.WRITE); + + putTx(BAR_PATH, BAR_DATA).submit().checkedGet(); + final DataTreeModification barWriteEvent = Iterables.getOnlyElement(listener.nextEvent()); + assertEquals(BAR_PATH, barWriteEvent.getRootPath().getRootIdentifier()); + verifyModification(barWriteEvent.getRootNode(), BAR_ARGUMENT, ModificationType.WRITE); + + deleteTx(BAR_PATH).submit().checkedGet(); + final DataTreeModification barDeleteEvent = Iterables.getOnlyElement(listener.nextEvent()); + assertEquals(BAR_PATH, barDeleteEvent.getRootPath().getRootIdentifier()); + verifyModification(barDeleteEvent.getRootNode(), BAR_ARGUMENT, ModificationType.DELETE); + } + + + + private void createAndVerifyTop(final EventCapturingListener listener) throws Exception { + putTx(TOP_PATH,TOP_INITIAL_DATA).submit().checkedGet(); + final Collection events = listener.nextEvent(); + + assertFalse("Non empty collection should be received.",events.isEmpty()); + final DataTreeModification initialWrite = Iterables.getOnlyElement(events); + final DataObjectModification initialNode = initialWrite.getRootNode(); + verifyModification(initialNode,TOP_PATH.getPathArguments().iterator().next(),ModificationType.WRITE); + assertEquals(TOP_INITIAL_DATA, initialNode.getDataAfter()); + } + + private DataObjectModification getOnlyChildModification(final DataObjectModification parentMod,final DataObjectModification.ModificationType eventType) throws Exception { + assertEquals(eventType,parentMod.getModificationType()); + final Collection> childMod = parentMod.getModifiedChildren(); + assertFalse("Non empty children modification",childMod.isEmpty()); + return Iterables.getOnlyElement(childMod); + } + + private void verifyModification(final DataObjectModification barWrite, final PathArgument pathArg, + final ModificationType eventType) { + assertEquals(pathArg.getType(), barWrite.getDataType()); + assertEquals(eventType,barWrite.getModificationType()); + assertEquals(pathArg, barWrite.getIdentifier()); + } + + private WriteTransaction putTx(final InstanceIdentifier path,final T data) { + final WriteTransaction tx = dataBrokerImpl.newWriteOnlyTransaction(); + tx.put(LogicalDatastoreType.OPERATIONAL, path, data); + return tx; + } + + private WriteTransaction deleteTx(final InstanceIdentifier path) { + final WriteTransaction tx = dataBrokerImpl.newWriteOnlyTransaction(); + tx.delete(LogicalDatastoreType.OPERATIONAL, path); + return tx; + } +} -- 2.36.6