*/
package org.opendaylight.mdsal.binding.dom.adapter;
+import com.google.common.annotations.Beta;
import java.util.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.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.UnkeyedListEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListNode;
import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode;
/**
- * Defines structural mapping of Normalized Node to Binding data
- * addressable by Instance Identifier.
+ * 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.
*
* <p>
- * 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.
*
* <p>
- * See {@link #NOT_ADDRESSABLE},{@link #INVISIBLE_CONTAINER},{@link #VISIBLE_CONTAINER}
- * for more details.
+ * NOTE: this class is exposed for migration purposes only, no new users outside of its package should be introduced.
*/
-enum BindingStructuralType {
-
+@Beta
+public enum BindingStructuralType {
/**
- * DOM Item is not addressable in Binding Instance Identifier,
- * data is not lost, but are available only via parent object.
- *
- * <p>
- * Such types of data are leaf-lists, leafs, list without keys
- * or anyxml.
- *
+ * DOM Item is not addressable in Binding InstanceIdentifier, 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.
+ * Data container is addressable in NormalizedNode format, but in Binding it is not represented in
+ * InstanceIdentifier. These are choice / case nodes.
*
* <p>
- * This are choice / case nodes.
- *
- * <p>
- * This data is still accessible using parent object and their
- * children are addressable.
- *
+ * 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.
- *
- * <p>
- * This are list nodes.
+ * Data container is addressable in NormalizedNode format, but in Binding it is not represented in
+ * InstanceIdentifier. These are list nodes.
*
* <p>
- * This data is still accessible using parent object and their
- * children are addressable.
- *
+ * 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.
- *
+ * Data container is addressable in Binding InstanceIdentifier format and also YangInstanceIdentifier format.
*/
VISIBLE_CONTAINER,
/**
- * Mapping algorithm was unable to detect type or was not updated after introduction
- * of new NormalizedNode type.
+ * Mapping algorithm was unable to detect type or was not updated after introduction of new NormalizedNode type.
*/
UNKNOWN;
- static BindingStructuralType from(final DataTreeCandidateNode domChildNode) {
+ public static BindingStructuralType from(final DataTreeCandidateNode domChildNode) {
Optional<NormalizedNode<?, ?>> dataBased = domChildNode.getDataAfter();
if (!dataBased.isPresent()) {
dataBased = domChildNode.getDataBefore();
return UNKNOWN;
}
+ public static BindingStructuralType recursiveFrom(final DataTreeCandidateNode node) {
+ final BindingStructuralType type = BindingStructuralType.from(node);
+ switch (type) {
+ case INVISIBLE_CONTAINER:
+ case INVISIBLE_LIST:
+ // This node is invisible, try to resolve using a child node
+ for (final DataTreeCandidateNode child : node.getChildNodes()) {
+ final BindingStructuralType childType = recursiveFrom(child);
+ switch (childType) {
+ case INVISIBLE_CONTAINER:
+ case INVISIBLE_LIST:
+ // Invisible nodes are not addressable
+ return BindingStructuralType.NOT_ADDRESSABLE;
+ case NOT_ADDRESSABLE:
+ case UNKNOWN:
+ case VISIBLE_CONTAINER:
+ return childType;
+ default:
+ throw new IllegalStateException("Unhandled child type " + childType + " for child "
+ + child);
+ }
+ }
+
+ return BindingStructuralType.NOT_ADDRESSABLE;
+ default:
+ return type;
+ }
+ }
+
private static boolean isVisibleContainer(final NormalizedNode<?, ?> data) {
return data instanceof MapEntryNode || data instanceof ContainerNode || data instanceof AugmentationNode;
}
return normalizedNode instanceof LeafNode
|| normalizedNode instanceof AnyXmlNode
|| normalizedNode instanceof LeafSetNode
- || normalizedNode instanceof LeafSetEntryNode;
+ || normalizedNode instanceof LeafSetEntryNode
+ || normalizedNode instanceof UnkeyedListNode
+ || normalizedNode instanceof UnkeyedListEntryNode;
}
-
}
--- /dev/null
+/*
+ * Copyright (c) 2018 Pantheon Technologies, s.ro.. 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.mdsal.binding.dom.adapter.test;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.opendaylight.mdsal.common.api.LogicalDatastoreType.CONFIGURATION;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.opendaylight.mdsal.binding.api.DataObjectModification;
+import org.opendaylight.mdsal.binding.api.DataObjectModification.ModificationType;
+import org.opendaylight.mdsal.binding.api.DataTreeChangeListener;
+import org.opendaylight.mdsal.binding.api.DataTreeIdentifier;
+import org.opendaylight.mdsal.binding.api.DataTreeModification;
+import org.opendaylight.mdsal.binding.api.WriteTransaction;
+import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction;
+import org.opendaylight.yang.gen.v1.urn.test.opendaylight.mdsal298.rev180129.AddressableCont;
+import org.opendaylight.yang.gen.v1.urn.test.opendaylight.mdsal298.rev180129.AddressableContBuilder;
+import org.opendaylight.yang.gen.v1.urn.test.opendaylight.mdsal298.rev180129.Container;
+import org.opendaylight.yang.gen.v1.urn.test.opendaylight.mdsal298.rev180129.ContainerBuilder;
+import org.opendaylight.yang.gen.v1.urn.test.opendaylight.mdsal298.rev180129.UnaddressableCont;
+import org.opendaylight.yang.gen.v1.urn.test.opendaylight.mdsal298.rev180129.UnaddressableContBuilder;
+import org.opendaylight.yang.gen.v1.urn.test.opendaylight.mdsal298.rev180129.WithChoice;
+import org.opendaylight.yang.gen.v1.urn.test.opendaylight.mdsal298.rev180129.WithChoiceBuilder;
+import org.opendaylight.yang.gen.v1.urn.test.opendaylight.mdsal298.rev180129.addressable.cont.AddressableChild;
+import org.opendaylight.yang.gen.v1.urn.test.opendaylight.mdsal298.rev180129.addressable.cont.AddressableChildBuilder;
+import org.opendaylight.yang.gen.v1.urn.test.opendaylight.mdsal298.rev180129.container.Keyed;
+import org.opendaylight.yang.gen.v1.urn.test.opendaylight.mdsal298.rev180129.container.KeyedBuilder;
+import org.opendaylight.yang.gen.v1.urn.test.opendaylight.mdsal298.rev180129.container.KeyedKey;
+import org.opendaylight.yang.gen.v1.urn.test.opendaylight.mdsal298.rev180129.container.Unkeyed;
+import org.opendaylight.yang.gen.v1.urn.test.opendaylight.mdsal298.rev180129.container.UnkeyedBuilder;
+import org.opendaylight.yang.gen.v1.urn.test.opendaylight.mdsal298.rev180129.with.choice.Foo;
+import org.opendaylight.yang.gen.v1.urn.test.opendaylight.mdsal298.rev180129.with.choice.foo.addressable._case.Addressable;
+import org.opendaylight.yang.gen.v1.urn.test.opendaylight.mdsal298.rev180129.with.choice.foo.addressable._case.AddressableBuilder;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.Item;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
+
+public class Mdsal298Test extends AbstractDataBrokerTest {
+ private static final InstanceIdentifier<Container> CONTAINER = InstanceIdentifier.create(Container.class);
+ private static final DataTreeIdentifier<Container> CONTAINER_TID = DataTreeIdentifier.create(CONFIGURATION,
+ CONTAINER);
+ private static final NodeIdentifier CONTAINER_NID = new NodeIdentifier(Container.QNAME);
+ private static final QName FOO_QNAME = QName.create(Container.QNAME, "foo");
+ private static final QName BAZ_QNAME = QName.create(UnaddressableCont.QNAME, "baz");
+
+ private static final InstanceIdentifier<WithChoice> CHOICE_CONTAINER = InstanceIdentifier.create(WithChoice.class);
+ private static final DataTreeIdentifier<WithChoice> CHOICE_CONTAINER_TID = DataTreeIdentifier.create(CONFIGURATION,
+ CHOICE_CONTAINER);
+ private static final NodeIdentifier CHOICE_CONTAINER_NID = new NodeIdentifier(WithChoice.QNAME);
+ private static final NodeIdentifier CHOICE_NID = new NodeIdentifier(Foo.QNAME);
+ private static final InstanceIdentifier<Addressable> ADDRESSABLE_CASE = CHOICE_CONTAINER
+ .child((Class)Addressable.class);
+
+ private static final InstanceIdentifier<AddressableCont> ADDRESSABLE_CONTAINER =
+ InstanceIdentifier.create(AddressableCont.class);
+ private static final DataTreeIdentifier<AddressableCont> ADDRESSABLE_CONTAINER_TID = DataTreeIdentifier.create(
+ CONFIGURATION, ADDRESSABLE_CONTAINER);
+ private static final NodeIdentifier ADDRESSABLE_CONTAINER_NID = new NodeIdentifier(AddressableCont.QNAME);
+
+ private static final InstanceIdentifier<UnaddressableCont> UNADDRESSABLE_CONTAINER =
+ InstanceIdentifier.create(UnaddressableCont.class);
+ private static final DataTreeIdentifier<UnaddressableCont> UNADDRESSABLE_CONTAINER_TID = DataTreeIdentifier.create(
+ CONFIGURATION, UNADDRESSABLE_CONTAINER);
+ private static final NodeIdentifier UNADDRESSABLE_CONTAINER_NID = new NodeIdentifier(UnaddressableCont.QNAME);
+
+ @Test
+ public void testKeyedDataTreeModification() throws InterruptedException, ExecutionException {
+ final DataTreeChangeListener<Container> listener = assertWrittenContainer(Container.QNAME, Container.class,
+ new ContainerBuilder().build());
+
+ final DOMDataTreeWriteTransaction domTx = getDomBroker().newWriteOnlyTransaction();
+ domTx.put(CONFIGURATION, YangInstanceIdentifier.create(CONTAINER_NID).node(Keyed.QNAME),
+ Builders.orderedMapBuilder()
+ .withNodeIdentifier(new NodeIdentifier(Keyed.QNAME))
+ .addChild(Builders.mapEntryBuilder()
+ .withNodeIdentifier(new NodeIdentifierWithPredicates(Keyed.QNAME, ImmutableMap.of(FOO_QNAME, "foo")))
+ .addChild(ImmutableNodes.leafNode(FOO_QNAME, "foo"))
+ .build())
+ .addChild(Builders.mapEntryBuilder()
+ .withNodeIdentifier(new NodeIdentifierWithPredicates(Keyed.QNAME, ImmutableMap.of(FOO_QNAME, "bar")))
+ .addChild(ImmutableNodes.leafNode(FOO_QNAME, "bar"))
+ .build())
+ .build());
+ domTx.submit().get();
+
+ final ArgumentCaptor<Collection> captor = ArgumentCaptor.forClass(Collection.class);
+ verify(listener).onDataTreeChanged(captor.capture());
+ Collection<DataTreeModification<Container>> capture = captor.getValue();
+ assertEquals(1, capture.size());
+
+ final DataTreeModification<Container> change = capture.iterator().next();
+ assertEquals(CONTAINER_TID, change.getRootPath());
+ final DataObjectModification<Container> changedContainer = change.getRootNode();
+ assertEquals(new Item<>(Container.class), changedContainer.getIdentifier());
+ assertEquals(ModificationType.SUBTREE_MODIFIED, changedContainer.getModificationType());
+
+ final Container containerAfter = changedContainer.getDataAfter();
+ assertEquals(new ContainerBuilder()
+ .setKeyed(ImmutableList.of(
+ new KeyedBuilder().setFoo("foo").setKey(new KeyedKey("foo")).build(),
+ new KeyedBuilder().setFoo("bar").setKey(new KeyedKey("bar")).build()))
+ .build(), containerAfter);
+
+ final Collection<DataObjectModification<?>> changedChildren = changedContainer.getModifiedChildren();
+ assertEquals(2, changedChildren.size());
+
+ final Iterator<DataObjectModification<?>> it = changedChildren.iterator();
+ final DataObjectModification<?> changedChild1 = it.next();
+ assertEquals(ModificationType.WRITE, changedChild1.getModificationType());
+ assertEquals(Collections.emptyList(), changedChild1.getModifiedChildren());
+ final Keyed child1After = (Keyed) changedChild1.getDataAfter();
+ assertEquals("foo", child1After.getFoo());
+
+ final DataObjectModification<?> changedChild2 = it.next();
+ assertEquals(ModificationType.WRITE, changedChild2.getModificationType());
+ assertEquals(Collections.emptyList(), changedChild2.getModifiedChildren());
+ final Keyed child2After = (Keyed) changedChild2.getDataAfter();
+ assertEquals("bar", child2After.getFoo());
+ }
+
+ @Test
+ public void testUnkeyedDataTreeModification() throws InterruptedException, ExecutionException {
+ final DataTreeChangeListener<Container> listener = assertWrittenContainer(Container.QNAME, Container.class,
+ new ContainerBuilder().build());
+
+ final DOMDataTreeWriteTransaction domTx = getDomBroker().newWriteOnlyTransaction();
+ domTx.put(CONFIGURATION, YangInstanceIdentifier.create(CONTAINER_NID).node(Unkeyed.QNAME),
+ Builders.unkeyedListBuilder()
+ .withNodeIdentifier(new NodeIdentifier(Unkeyed.QNAME))
+ .withChild(Builders.unkeyedListEntryBuilder()
+ .withNodeIdentifier(new NodeIdentifier(Unkeyed.QNAME))
+ .addChild(ImmutableNodes.leafNode(FOO_QNAME, "foo"))
+ .build())
+ .withChild(Builders.unkeyedListEntryBuilder()
+ .withNodeIdentifier(new NodeIdentifier(Unkeyed.QNAME))
+ .addChild(ImmutableNodes.leafNode(FOO_QNAME, "bar"))
+ .build())
+ .build());
+ domTx.submit().get();
+
+ final ArgumentCaptor<Collection> captor = ArgumentCaptor.forClass(Collection.class);
+ verify(listener).onDataTreeChanged(captor.capture());
+ Collection<DataTreeModification<Container>> capture = captor.getValue();
+ assertEquals(1, capture.size());
+
+ final DataTreeModification<Container> change = capture.iterator().next();
+ assertEquals(CONTAINER_TID, change.getRootPath());
+ final DataObjectModification<Container> changedContainer = change.getRootNode();
+ assertEquals(new Item<>(Container.class), changedContainer.getIdentifier());
+ assertEquals(ModificationType.WRITE, changedContainer.getModificationType());
+
+ final Container containerAfter = changedContainer.getDataAfter();
+ assertEquals(new ContainerBuilder()
+ .setUnkeyed(ImmutableList.of(
+ new UnkeyedBuilder().setFoo("foo").build(),
+ new UnkeyedBuilder().setFoo("bar").build()))
+ .build(), containerAfter);
+
+ final Collection<DataObjectModification<?>> changedChildren = changedContainer.getModifiedChildren();
+ assertEquals(0, changedChildren.size());
+ }
+
+ @Test
+ public void testChoiceDataTreeModificationAddressable() throws InterruptedException, ExecutionException {
+ final DataTreeChangeListener<WithChoice> listener = assertWrittenWithChoice();
+
+ doNothing().when(listener).onDataTreeChanged(any(Collection.class));
+
+ final WriteTransaction writeTx = getDataBroker().newWriteOnlyTransaction();
+ writeTx.put(CONFIGURATION, ADDRESSABLE_CASE, new AddressableBuilder().build());
+ writeTx.submit().get();
+
+ final ArgumentCaptor<Collection> captor = ArgumentCaptor.forClass(Collection.class);
+ verify(listener).onDataTreeChanged(captor.capture());
+ Collection<DataTreeModification<WithChoice>> capture = captor.getValue();
+ assertEquals(1, capture.size());
+
+ final DataTreeModification<WithChoice> choiceChange = capture.iterator().next();
+ assertEquals(CHOICE_CONTAINER_TID, choiceChange.getRootPath());
+ final DataObjectModification<WithChoice> changedContainer = choiceChange.getRootNode();
+ assertEquals(ModificationType.SUBTREE_MODIFIED, changedContainer.getModificationType());
+ assertEquals(new Item<>(WithChoice.class), changedContainer.getIdentifier());
+
+ final Collection<DataObjectModification<?>> choiceChildren = changedContainer.getModifiedChildren();
+ assertEquals(1, choiceChildren.size());
+
+ final DataObjectModification<Addressable> changedCase = (DataObjectModification<Addressable>) choiceChildren
+ .iterator().next();
+ assertEquals(ModificationType.WRITE, changedCase.getModificationType());
+ assertEquals(new Item<>(Addressable.class), changedCase.getIdentifier());
+ assertEquals(new AddressableBuilder().build(), changedCase.getDataAfter());
+ }
+
+ @Test
+ public void testDataTreeModificationAddressable() throws InterruptedException, ExecutionException {
+ final DataTreeChangeListener<AddressableCont> listener = assertWrittenContainer(AddressableCont.QNAME,
+ AddressableCont.class, new AddressableContBuilder().build());
+
+ doNothing().when(listener).onDataTreeChanged(any(Collection.class));
+
+ final WriteTransaction writeTx = getDataBroker().newWriteOnlyTransaction();
+ writeTx.put(CONFIGURATION, ADDRESSABLE_CONTAINER.child(AddressableChild.class),
+ new AddressableChildBuilder().build());
+ writeTx.submit().get();
+
+ final ArgumentCaptor<Collection> captor = ArgumentCaptor.forClass(Collection.class);
+ verify(listener).onDataTreeChanged(captor.capture());
+ Collection<DataTreeModification<AddressableCont>> capture = captor.getValue();
+ assertEquals(1, capture.size());
+
+ final DataTreeModification<AddressableCont> contChange = capture.iterator().next();
+ assertEquals(ADDRESSABLE_CONTAINER_TID, contChange.getRootPath());
+ final DataObjectModification<AddressableCont> changedContainer = contChange.getRootNode();
+ assertEquals(ModificationType.SUBTREE_MODIFIED, changedContainer.getModificationType());
+ assertEquals(new Item<>(AddressableCont.class), changedContainer.getIdentifier());
+
+ final Collection<DataObjectModification<?>> contChildren = changedContainer.getModifiedChildren();
+ assertEquals(1, contChildren.size());
+
+ final DataObjectModification<Addressable> changedChild = (DataObjectModification<Addressable>) contChildren
+ .iterator().next();
+ assertEquals(ModificationType.WRITE, changedChild.getModificationType());
+ assertEquals(new Item<>(AddressableChild.class), changedChild.getIdentifier());
+ assertEquals(new AddressableChildBuilder().build(), changedChild.getDataAfter());
+ }
+
+ @Test
+ public void testDataTreeModificationUnaddressable() throws InterruptedException, ExecutionException {
+ final DataTreeChangeListener<UnaddressableCont> listener = assertWrittenContainer(UnaddressableCont.QNAME,
+ UnaddressableCont.class, new UnaddressableContBuilder().build());
+
+ doNothing().when(listener).onDataTreeChanged(any(Collection.class));
+
+ final DOMDataTreeWriteTransaction domTx = getDomBroker().newWriteOnlyTransaction();
+ domTx.put(CONFIGURATION, YangInstanceIdentifier.create(UNADDRESSABLE_CONTAINER_NID)
+ .node(QName.create(UnaddressableCont.QNAME, "baz")),
+ ImmutableNodes.leafNode(BAZ_QNAME, "baz"));
+ domTx.submit().get();
+
+ final ArgumentCaptor<Collection> captor = ArgumentCaptor.forClass(Collection.class);
+ verify(listener).onDataTreeChanged(captor.capture());
+ Collection<DataTreeModification<UnaddressableCont>> capture = captor.getValue();
+ assertEquals(1, capture.size());
+
+ final DataTreeModification<UnaddressableCont> contChange = capture.iterator().next();
+ assertEquals(UNADDRESSABLE_CONTAINER_TID, contChange.getRootPath());
+ final DataObjectModification<UnaddressableCont> changedContainer = contChange.getRootNode();
+ assertEquals(ModificationType.WRITE, changedContainer.getModificationType());
+ assertEquals(new Item<>(UnaddressableCont.class), changedContainer.getIdentifier());
+
+ final Collection<DataObjectModification<?>> contChildren = changedContainer.getModifiedChildren();
+ assertEquals(0, contChildren.size());
+ }
+
+ @Test
+ public void testChoiceDataTreeModificationUnaddressable() throws InterruptedException, ExecutionException {
+ final DataTreeChangeListener<WithChoice> listener = assertWrittenWithChoice();
+
+ doNothing().when(listener).onDataTreeChanged(any(Collection.class));
+
+ final DOMDataTreeWriteTransaction domTx = getDomBroker().newWriteOnlyTransaction();
+ domTx.put(CONFIGURATION, YangInstanceIdentifier.create(CHOICE_CONTAINER_NID).node(Foo.QNAME),
+ Builders.choiceBuilder()
+ .withNodeIdentifier(new NodeIdentifier(Foo.QNAME))
+ .withChild(Builders.leafSetBuilder()
+ .withNodeIdentifier(new NodeIdentifier(QName.create(Foo.QNAME, "unaddressable")))
+ .withChildValue("foo")
+ .build())
+ .build());
+ domTx.submit().get();
+
+ final ArgumentCaptor<Collection> captor = ArgumentCaptor.forClass(Collection.class);
+ verify(listener).onDataTreeChanged(captor.capture());
+ Collection<DataTreeModification<WithChoice>> capture = captor.getValue();
+ assertEquals(1, capture.size());
+
+ final DataTreeModification<WithChoice> choiceChange = capture.iterator().next();
+ assertEquals(CHOICE_CONTAINER_TID, choiceChange.getRootPath());
+ final DataObjectModification<WithChoice> changedContainer = choiceChange.getRootNode();
+
+ // Should be write
+ assertEquals(ModificationType.WRITE, changedContainer.getModificationType());
+ assertEquals(new Item<>(WithChoice.class), changedContainer.getIdentifier());
+
+ final Collection<DataObjectModification<?>> choiceChildren = changedContainer.getModifiedChildren();
+ assertEquals(0, choiceChildren.size());
+ }
+
+ private <T extends DataObject> DataTreeChangeListener<T> assertWrittenContainer(final QName qname,
+ final Class<T> bindingClass, final T expected)
+ throws InterruptedException, ExecutionException {
+ final DataTreeChangeListener<T> listener = mock(DataTreeChangeListener.class);
+ doNothing().when(listener).onDataTreeChanged(any(Collection.class));
+
+ final DataTreeIdentifier<T> dti = DataTreeIdentifier.create(CONFIGURATION,
+ InstanceIdentifier.create(bindingClass));
+ getDataBroker().registerDataTreeChangeListener(dti, listener);
+
+ final DOMDataTreeWriteTransaction domTx = getDomBroker().newWriteOnlyTransaction();
+ domTx.put(CONFIGURATION, YangInstanceIdentifier.create(new NodeIdentifier(qname)),
+ ImmutableNodes.containerNode(qname));
+ domTx.submit().get();
+
+ final ArgumentCaptor<Collection> captor = ArgumentCaptor.forClass(Collection.class);
+ verify(listener).onDataTreeChanged(captor.capture());
+ Collection<DataTreeModification<T>> capture = captor.getValue();
+ assertEquals(1, capture.size());
+
+ final DataTreeModification<T> change = capture.iterator().next();
+ assertEquals(dti, change.getRootPath());
+ final DataObjectModification<T> changedContainer = change.getRootNode();
+ assertEquals(ModificationType.WRITE, changedContainer.getModificationType());
+ assertEquals(new Item<>(bindingClass), changedContainer.getIdentifier());
+
+ final T containerAfter = changedContainer.getDataAfter();
+ assertEquals(expected, containerAfter);
+
+ // No further modifications should occur
+ assertEquals(Collections.emptyList(), changedContainer.getModifiedChildren());
+
+ reset(listener);
+ doNothing().when(listener).onDataTreeChanged(any(Collection.class));
+ return listener;
+ }
+
+ private DataTreeChangeListener<WithChoice> assertWrittenWithChoice() throws InterruptedException,
+ ExecutionException {
+ final DataTreeChangeListener<WithChoice> listener = mock(DataTreeChangeListener.class);
+ doNothing().when(listener).onDataTreeChanged(any(Collection.class));
+ getDataBroker().registerDataTreeChangeListener(CHOICE_CONTAINER_TID, listener);
+
+ final DOMDataTreeWriteTransaction domTx = getDomBroker().newWriteOnlyTransaction();
+ domTx.put(CONFIGURATION, YangInstanceIdentifier.create(CHOICE_CONTAINER_NID),
+ Builders.containerBuilder()
+ .withNodeIdentifier(CHOICE_CONTAINER_NID)
+ .withChild(Builders.choiceBuilder().withNodeIdentifier(CHOICE_NID).build())
+ .build());
+ domTx.submit().get();
+
+ final ArgumentCaptor<Collection> captor = ArgumentCaptor.forClass(Collection.class);
+ verify(listener).onDataTreeChanged(captor.capture());
+ Collection<DataTreeModification<WithChoice>> capture = captor.getValue();
+ assertEquals(1, capture.size());
+
+ final DataTreeModification<WithChoice> change = capture.iterator().next();
+ assertEquals(CHOICE_CONTAINER_TID, change.getRootPath());
+ final DataObjectModification<WithChoice> changedContainer = change.getRootNode();
+ assertEquals(ModificationType.WRITE, changedContainer.getModificationType());
+ assertEquals(new Item<>(WithChoice.class), changedContainer.getIdentifier());
+
+ final WithChoice containerAfter = changedContainer.getDataAfter();
+ assertEquals(new WithChoiceBuilder().build(), containerAfter);
+
+ // No further modifications should occur
+ assertEquals(Collections.emptyList(), changedContainer.getModifiedChildren());
+
+ reset(listener);
+ doNothing().when(listener).onDataTreeChanged(any(Collection.class));
+
+ return listener;
+ }
+}
import com.google.common.collect.Iterables;
import javax.annotation.concurrent.GuardedBy;
+import org.opendaylight.mdsal.binding.dom.codec.api.BindingCodecTreeNode.ChildAddressabilitySummary;
import org.opendaylight.mdsal.binding.dom.codec.impl.NodeCodecContext.CodecContextFactory;
import org.opendaylight.yangtools.yang.binding.DataRoot;
import org.opendaylight.yangtools.yang.binding.Identifiable;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.model.api.AnyDataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode;
import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode;
import org.opendaylight.yangtools.yang.model.api.CaseSchemaNode;
import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
import org.opendaylight.yangtools.yang.model.api.DocumentedNode.WithStatus;
import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.TypedDataSchemaNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
final class DataContainerCodecPrototype<T extends WithStatus> implements NodeContextSupplier {
+ private static final Logger LOG = LoggerFactory.getLogger(DataContainerCodecPrototype.class);
private final T schema;
private final QNameModule namespace;
private final Class<?> bindingClass;
private final InstanceIdentifier.Item<?> bindingArg;
private final YangInstanceIdentifier.PathArgument yangArg;
+ private final ChildAddressabilitySummary childAddressabilitySummary;
+
private volatile DataContainerCodecContext<?, T> instance = null;
@SuppressWarnings({"rawtypes", "unchecked"})
} else {
this.namespace = arg.getNodeType().getModule();
}
+
+ this.childAddressabilitySummary = computeChildAddressabilitySummary(nodeSchema);
+ }
+
+ private static ChildAddressabilitySummary computeChildAddressabilitySummary(final WithStatus nodeSchema) {
+ if (nodeSchema instanceof DataNodeContainer) {
+ boolean haveAddressable = false;
+ boolean haveUnaddressable = false;
+ for (DataSchemaNode child : ((DataNodeContainer) nodeSchema).getChildNodes()) {
+ if (child instanceof ContainerSchemaNode || child instanceof AugmentationSchemaNode) {
+ haveAddressable = true;
+ } else if (child instanceof ListSchemaNode) {
+ if (((ListSchemaNode) child).getKeyDefinition().isEmpty()) {
+ haveUnaddressable = true;
+ } else {
+ haveAddressable = true;
+ }
+ } else if (child instanceof AnyDataSchemaNode || child instanceof AnyXmlSchemaNode
+ || child instanceof TypedDataSchemaNode) {
+ haveUnaddressable = true;
+ } else if (child instanceof ChoiceSchemaNode) {
+ switch (computeChildAddressabilitySummary(child)) {
+ case ADDRESSABLE:
+ haveAddressable = true;
+ break;
+ case MIXED:
+ haveAddressable = true;
+ haveUnaddressable = true;
+ break;
+ case UNADDRESSABLE:
+ haveUnaddressable = true;
+ break;
+ default:
+ throw new IllegalStateException("Unhandled accessibility summary for " + child);
+ }
+ } else {
+ LOG.warn("Unhandled child node {}", child);
+ }
+ }
+
+ if (!haveAddressable) {
+ // Empty or all are unaddressable
+ return ChildAddressabilitySummary.UNADDRESSABLE;
+ }
+
+ return haveUnaddressable ? ChildAddressabilitySummary.MIXED : ChildAddressabilitySummary.ADDRESSABLE;
+ } else if (nodeSchema instanceof ChoiceSchemaNode) {
+ boolean haveAddressable = false;
+ boolean haveUnaddressable = false;
+ for (CaseSchemaNode child : ((ChoiceSchemaNode) nodeSchema).getCases().values()) {
+ switch (computeChildAddressabilitySummary(child)) {
+ case ADDRESSABLE:
+ haveAddressable = true;
+ break;
+ case UNADDRESSABLE:
+ haveUnaddressable = true;
+ break;
+ case MIXED:
+ // A child is mixed, which means we are mixed, too
+ return ChildAddressabilitySummary.MIXED;
+ default:
+ throw new IllegalStateException("Unhandled accessibility summary for " + child);
+ }
+ }
+
+ if (!haveAddressable) {
+ // Empty or all are unaddressable
+ return ChildAddressabilitySummary.UNADDRESSABLE;
+ }
+
+ return haveUnaddressable ? ChildAddressabilitySummary.MIXED : ChildAddressabilitySummary.ADDRESSABLE;
+ }
+
+ // No child nodes possible: return unaddressable
+ return ChildAddressabilitySummary.UNADDRESSABLE;
}
static DataContainerCodecPrototype<SchemaContext> rootPrototype(final CodecContextFactory factory) {
return schema;
}
+ ChildAddressabilitySummary getChildAddressabilitySummary() {
+ return childAddressabilitySummary;
+ }
+
protected QNameModule getNamespace() {
return namespace;
}