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.
}
};
+ 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
}
@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);
}
+
}
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+/*
+ * 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();
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+
+}
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;
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;
}
@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);
}
}
@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);
}
}
@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);
+ }
+
}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+
+}
--- /dev/null
+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;
+ }
+}