Bug 2933: Implemented DataTreeChangeService 26/17526/4
authorTony Tkacik <ttkacik@cisco.com>
Wed, 1 Apr 2015 10:37:52 +0000 (12:37 +0200)
committerTony Tkacik <ttkacik@cisco.com>
Tue, 7 Apr 2015 11:29:21 +0000 (13:29 +0200)
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 <ttkacik@cisco.com>
opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/BindingDOMDataBrokerAdapter.java
opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/BindingDOMDataTreeChangeListenerAdapter.java [new file with mode: 0644]
opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/BindingDOMDataTreeChangeServiceAdapter.java [new file with mode: 0644]
opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/BindingDataTreeChangeListenerRegistration.java [new file with mode: 0644]
opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/BindingStructuralType.java [new file with mode: 0644]
opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/BindingToNormalizedNodeCodec.java
opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/LazyDataObjectModification.java [new file with mode: 0644]
opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/LazyDataTreeModification.java [new file with mode: 0644]
opendaylight/md-sal/sal-binding-broker/src/test/java/org/opendaylight/controller/md/sal/binding/impl/test/DataTreeChangeListenerTest.java [new file with mode: 0644]

index b17be166156574b9a7381fae6155682cd40044a9..f63db76b4fc1a106672e0c28e7c7f00cf60604a8 100644 (file)
@@ -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<DOMService> delegates) {
-            DOMDataBroker domDataBroker = delegates.getInstance(DOMDataBroker.class);
+        protected DataBroker createInstance(final BindingToNormalizedNodeCodec codec,
+                final ClassToInstanceMap<DOMService> delegates) {
+            final DOMDataBroker domDataBroker = delegates.getInstance(DOMDataBroker.class);
             return new BindingDOMDataBrokerAdapter(domDataBroker, codec);
         }
 
+    }
 
-
+    public <L extends DataTreeChangeListener> ListenerRegistration<L> 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 (file)
index 0000000..bc60bdc
--- /dev/null
@@ -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<DataTreeCandidate> domChanges) {
+        final Collection<DataTreeModification> 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 (file)
index 0000000..04c6ad5
--- /dev/null
@@ -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 <L extends DataTreeChangeListener> ListenerRegistration<L> registerDataTreeChangeListener(
+            final DataTreeIdentifier treeId, final L listener) {
+        final DOMDataTreeIdentifier domIdentifier = toDomTreeIdentifier(treeId);
+        final BindingDOMDataTreeChangeListenerAdapter domListener = new BindingDOMDataTreeChangeListenerAdapter(codec,listener, treeId.getDatastoreType());
+        final ListenerRegistration<BindingDOMDataTreeChangeListenerAdapter> 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 (file)
index 0000000..524d97c
--- /dev/null
@@ -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<L extends DataTreeChangeListener> extends AbstractListenerRegistration<L> {
+
+    private final ListenerRegistration<BindingDOMDataTreeChangeListenerAdapter> domReg;
+
+    BindingDataTreeChangeListenerRegistration(final L listener,
+            final ListenerRegistration<BindingDOMDataTreeChangeListenerAdapter> 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 (file)
index 0000000..7cd17dc
--- /dev/null
@@ -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<NormalizedNode<?, ?>> 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;
+    }
+
+}
index d9e58e538d54256fa8a5121b325a40c493b3777b..b727e5317b087cb7784cb83be78e596dc397698e 100644 (file)
@@ -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 <T extends DataObject> Entry<YangInstanceIdentifier, NormalizedNode<?, ?>> toNormalizedNode(
-            InstanceIdentifier<T> path, T data) {
+            final InstanceIdentifier<T> path, final T data) {
         return codecRegistry.toNormalizedNode(path, data);
     }
 
@@ -78,33 +82,33 @@ public class BindingToNormalizedNodeCodec implements BindingCodecTreeFactory, Bi
     }
 
     @Override
-    public Entry<InstanceIdentifier<?>, DataObject> fromNormalizedNode(YangInstanceIdentifier path,
-            NormalizedNode<?, ?> data) {
+    public Entry<InstanceIdentifier<?>, 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<InstanceIdentifier<?>, 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<InstanceIdentifier<?>, 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 (file)
index 0000000..b29ed38
--- /dev/null
@@ -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 <T> Type of Binding Data Object
+ */
+class LazyDataObjectModification<T extends DataObject> implements DataObjectModification<T> {
+
+    private final static Logger LOG = LoggerFactory.getLogger(LazyDataObjectModification.class);
+
+    private final BindingCodecTreeNode<T> codec;
+    private final DataTreeCandidateNode domData;
+    private final PathArgument identifier;
+    private Collection<DataObjectModification<? extends DataObject>> childNodesCache;
+
+    private LazyDataObjectModification(final BindingCodecTreeNode<T> codec, final DataTreeCandidateNode domData) {
+        this.codec = Preconditions.checkNotNull(codec);
+        this.domData = Preconditions.checkNotNull(domData);
+        this.identifier = codec.deserializePathArgument(domData.getIdentifier());
+    }
+
+    static DataObjectModification<? extends DataObject> create(final BindingCodecTreeNode<?> codec,
+            final DataTreeCandidateNode domData) {
+        return new LazyDataObjectModification<>(codec,domData);
+    }
+
+    static Collection<DataObjectModification<? extends DataObject>> from(final BindingCodecTreeNode<?> parentCodec,
+            final Collection<DataTreeCandidateNode> domChildNodes) {
+        final ArrayList<DataObjectModification<? extends DataObject>> result = new ArrayList<>(domChildNodes.size());
+        populateList(result, parentCodec, domChildNodes);
+        return result;
+    }
+
+    private static void populateList(final List<DataObjectModification<? extends DataObject>> result,
+            final BindingCodecTreeNode<?> parentCodec, final Collection<DataTreeCandidateNode> 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<DataObjectModification<? extends DataObject>> 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<DataObjectModification<? extends DataObject>> result,
+            final BindingCodecTreeNode<?> codec, final Collection<DataTreeCandidateNode> childNodes) {
+        for (final DataTreeCandidateNode child : childNodes) {
+            result.add(create(codec, child));
+        }
+    }
+
+    @Override
+    public T getDataAfter() {
+        return deserialize(domData.getDataAfter());
+    }
+
+    @Override
+    public Class<T> 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<DataObjectModification<? extends DataObject>> getModifiedChildren() {
+        if(childNodesCache == null) {
+            childNodesCache = from(codec,domData.getChildNodes());
+        }
+        return childNodesCache;
+    }
+
+    public DataObjectModification<? extends DataObject> getModifiedChild(final PathArgument arg) {
+        final List<YangInstanceIdentifier.PathArgument> domArgumentList = new ArrayList<>();
+        final BindingCodecTreeNode<?> childCodec = codec.bindingPathArgumentChild(arg, domArgumentList);
+        final Iterator<YangInstanceIdentifier.PathArgument> 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 <C extends ChildOf<T>> DataObjectModification<C> getModifiedChild(final Class<C> arg) {
+        return (DataObjectModification<C>) getModifiedChild(new InstanceIdentifier.Item<>(arg));
+    }
+
+    private T deserialize(final Optional<NormalizedNode<?, ?>> 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 (file)
index 0000000..26fd420
--- /dev/null
@@ -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<? extends DataObject> getRootNode() {
+        return rootNode;
+    }
+
+    @Override
+    public DataTreeIdentifier getRootPath() {
+        return path;
+    }
+
+    static DataTreeModification create(final BindingToNormalizedNodeCodec codec, final DataTreeCandidate domChange,
+            final LogicalDatastoreType datastoreType) {
+        final Entry<InstanceIdentifier<?>, BindingCodecTreeNode<?>> codecCtx =
+                codec.getSubtreeCodec(domChange.getRootPath());
+        return new LazyDataTreeModification(datastoreType, codecCtx.getKey(), codecCtx.getValue(), domChange);
+    }
+
+    static Collection<DataTreeModification> from(final BindingToNormalizedNodeCodec codec,
+            final Collection<DataTreeCandidate> domChanges, final LogicalDatastoreType datastoreType) {
+        final List<DataTreeModification> 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 (file)
index 0000000..218233e
--- /dev/null
@@ -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> TOP_PATH = InstanceIdentifier.create(Top.class);
+    private static final InstanceIdentifier<TopLevelList> 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<TopLevelList> 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<Collection<DataTreeModification>> changes = SettableFuture.create();
+
+        @Override
+        public void onDataTreeChanged(final Collection<DataTreeModification> changes) {
+            this.changes.set(changes);
+
+        }
+
+        Collection<DataTreeModification> nextEvent() throws Exception {
+            final Collection<DataTreeModification> result = changes.get(200,TimeUnit.MILLISECONDS);
+            changes = SettableFuture.create();
+            return result;
+        }
+
+    }
+
+    @Override
+    protected Iterable<YangModuleInfo> 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<? extends DataObject> 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<? extends DataObject> 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<DataTreeModification> events = listener.nextEvent();
+
+        assertFalse("Non empty collection should be received.",events.isEmpty());
+        final DataTreeModification initialWrite = Iterables.getOnlyElement(events);
+        final DataObjectModification<? extends DataObject> initialNode = initialWrite.getRootNode();
+        verifyModification(initialNode,TOP_PATH.getPathArguments().iterator().next(),ModificationType.WRITE);
+        assertEquals(TOP_INITIAL_DATA, initialNode.getDataAfter());
+    }
+
+    private DataObjectModification<? extends DataObject> getOnlyChildModification(final DataObjectModification<? extends DataObject> parentMod,final DataObjectModification.ModificationType eventType) throws Exception {
+        assertEquals(eventType,parentMod.getModificationType());
+        final Collection<DataObjectModification<? extends DataObject>> childMod = parentMod.getModifiedChildren();
+        assertFalse("Non empty children modification",childMod.isEmpty());
+        return Iterables.getOnlyElement(childMod);
+    }
+
+    private void verifyModification(final DataObjectModification<? extends DataObject> barWrite, final PathArgument pathArg,
+            final ModificationType eventType) {
+        assertEquals(pathArg.getType(), barWrite.getDataType());
+        assertEquals(eventType,barWrite.getModificationType());
+        assertEquals(pathArg, barWrite.getIdentifier());
+    }
+
+    private <T extends DataObject> WriteTransaction putTx(final InstanceIdentifier<T> 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;
+    }
+}