BUG-4355: mandatory node presence enforcement 66/28666/18
authorPeter Kajsa <pkajsa@cisco.com>
Tue, 1 Dec 2015 10:25:17 +0000 (11:25 +0100)
committerTony Tkacik <ttkacik@cisco.com>
Fri, 8 Jan 2016 08:44:03 +0000 (09:44 +0100)
Introduce mandatory leaf validation as specified in RFC6020. Mandatory
node presence is enforced whenever a presence container, a list entry,
and a case node.

The presence of mandatory leaves which do not have any ancestors other
than structural containers are not enforced as mandatory. The reason for
this is that the first transaction on such a DataTree would have to
introduce all mandatory leaves, as their non-presence would make other
transactions fail consistency checks.

Change-Id: I19df441026854955fd37f6e1ff7b4ea3c48c60b8
Signed-off-by: Robert Varga <rovarga@cisco.com>
Signed-off-by: Peter Kajsa <pkajsa@cisco.com>
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/CaseEnforcer.java
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/ChoiceModificationStrategy.java
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/ContainerModificationStrategy.java [new file with mode: 0644]
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTree.java
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/ListEntryModificationStrategy.java
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/MandatoryLeafEnforcer.java [new file with mode: 0644]
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/PresenceContainerModificationStrategy.java
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/StructuralContainerModificationStrategy.java
yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/MandatoryLeafTest.java [new file with mode: 0644]
yang/yang-data-impl/src/test/resources/list-constraints-validation-test-model.yang
yang/yang-data-impl/src/test/resources/mandatory-leaf-test.yang [new file with mode: 0644]

index 62a5f837dddd1d4bbdba7d51c987cd90c46d3b98..87a7f786f27905b84b5b2bc0f32f0401c5bf6937 100644 (file)
@@ -15,29 +15,33 @@ import java.util.Map.Entry;
 import java.util.Set;
 import org.opendaylight.yangtools.concepts.Immutable;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.TreeType;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.TreeNode;
 import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
 
 final class CaseEnforcer implements Immutable {
     private final Map<NodeIdentifier, DataSchemaNode> children;
+    private final MandatoryLeafEnforcer enforcer;
 
-    private CaseEnforcer(final Map<NodeIdentifier, DataSchemaNode> children) {
+    private CaseEnforcer(final Map<NodeIdentifier, DataSchemaNode> children, final MandatoryLeafEnforcer enforcer) {
         this.children = Preconditions.checkNotNull(children);
+        this.enforcer = Preconditions.checkNotNull(enforcer);
     }
 
-    static CaseEnforcer forTree(final ChoiceCaseNode schema, final TreeType treeType) {
+    static CaseEnforcer forTree(final ChoiceCaseNode schema, final TreeType type) {
         final Builder<NodeIdentifier, DataSchemaNode> builder = ImmutableMap.builder();
-        if (SchemaAwareApplyOperation.belongsToTree(treeType, schema)) {
+        if (SchemaAwareApplyOperation.belongsToTree(type, schema)) {
             for (DataSchemaNode child : schema.getChildNodes()) {
-                if (SchemaAwareApplyOperation.belongsToTree(treeType, child)) {
+                if (SchemaAwareApplyOperation.belongsToTree(type, child)) {
                     builder.put(NodeIdentifier.create(child.getQName()), child);
                 }
             }
         }
 
         final Map<NodeIdentifier, DataSchemaNode> children = builder.build();
-        return children.isEmpty() ? null : new CaseEnforcer(children);
+        return children.isEmpty() ? null : new CaseEnforcer(children, MandatoryLeafEnforcer.forContainer(schema, type));
     }
 
     Set<Entry<NodeIdentifier, DataSchemaNode>> getChildEntries() {
@@ -47,4 +51,12 @@ final class CaseEnforcer implements Immutable {
     Set<NodeIdentifier> getChildIdentifiers() {
         return children.keySet();
     }
+
+    void enforceOnTreeNode(final TreeNode tree) {
+        enforcer.enforceOnTreeNode(tree);
+    }
+
+    void enforceOnTreeNode(final NormalizedNode<?, ?> normalizedNode) {
+        enforcer.enforceOnTreeNode(normalizedNode);
+    }
 }
index 56e2bac02f81dc33812e211513fd399191484b7c..38edd7bb2f088f5b7cb4f20d10edf1de37223f49 100644 (file)
@@ -108,6 +108,9 @@ final class ChoiceModificationStrategy extends AbstractNodeContainerModification
                         firstChild.getIdentifier(), enforcer, id, other, maybeChild.orNull());
                 }
             }
+
+            // Make sure all mandatory children are present
+            enforcer.enforceOnTreeNode(normalizedNode);
         }
     }
 
diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/ContainerModificationStrategy.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/ContainerModificationStrategy.java
new file mode 100644 (file)
index 0000000..58671b8
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * 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.yangtools.yang.data.impl.schema.tree;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.TreeType;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder;
+import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+
+/**
+ * General container modification strategy. Used by {@link PresenceContainerModificationStrategy} via subclassing
+ * and by {@link StructuralContainerModificationStrategy} as a delegate.
+ */
+class ContainerModificationStrategy extends AbstractDataNodeContainerModificationStrategy<ContainerSchemaNode> {
+    ContainerModificationStrategy(final ContainerSchemaNode schemaNode, final TreeType treeType) {
+        super(schemaNode, ContainerNode.class, treeType);
+    }
+
+    @Override
+    @SuppressWarnings("rawtypes")
+    protected final DataContainerNodeBuilder createBuilder(final NormalizedNode<?, ?> original) {
+        checkArgument(original instanceof ContainerNode);
+        return ImmutableContainerNodeBuilder.create((ContainerNode) original);
+    }
+}
index 08125b90971e5b2bccfdf3b2ef8f19afaba079fb..13070428ec896c1722f01454bdd2841a4b25fcab 100644 (file)
@@ -75,7 +75,8 @@ final class InMemoryDataTree extends AbstractDataTreeTip implements TipProducing
 
         final ModificationApplyOperation rootNode;
         if (rootSchemaNode instanceof ContainerSchemaNode) {
-            rootNode = new PresenceContainerModificationStrategy((ContainerSchemaNode) rootSchemaNode, treeType);
+            // FIXME: real root needs to enfore presence, but that require pre-population
+            rootNode = new ContainerModificationStrategy((ContainerSchemaNode) rootSchemaNode, treeType);
         } else {
             rootNode = SchemaAwareApplyOperation.from(rootSchemaNode, treeType);
         }
index 71f23f0715450ac29c559accb558c7cf306960f6..30b17b1c0e4aa76eebbaa6c9d16bb3b23ead3fde 100644 (file)
@@ -8,16 +8,44 @@
 package org.opendaylight.yangtools.yang.data.impl.schema.tree;
 
 import static com.google.common.base.Preconditions.checkArgument;
+import com.google.common.base.Optional;
 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.TreeType;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.TreeNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.Version;
 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeBuilder;
 import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableMapEntryNodeBuilder;
 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
 
 final class ListEntryModificationStrategy extends AbstractDataNodeContainerModificationStrategy<ListSchemaNode> {
+    private final MandatoryLeafEnforcer enforcer;
+
     ListEntryModificationStrategy(final ListSchemaNode schema, final TreeType treeType) {
         super(schema, MapEntryNode.class, treeType);
+        enforcer = MandatoryLeafEnforcer.forContainer(schema, treeType);
+    }
+
+    @Override
+    protected TreeNode applyMerge(final ModifiedNode modification, final TreeNode currentMeta, final Version version) {
+        final TreeNode ret = super.applyMerge(modification, currentMeta, version);
+        enforcer.enforceOnTreeNode(ret);
+        return ret;
+    }
+
+    @Override
+    protected TreeNode applyWrite(final ModifiedNode modification, final Optional<TreeNode> currentMeta,
+            final Version version) {
+        final TreeNode ret = super.applyWrite(modification, currentMeta, version);
+        enforcer.enforceOnTreeNode(ret);
+        return ret;
+    }
+
+    @Override
+    protected TreeNode applyTouch(final ModifiedNode modification, final TreeNode currentMeta, final Version version) {
+        final TreeNode ret = super.applyTouch(modification, currentMeta, version);
+        enforcer.enforceOnTreeNode(ret);
+        return ret;
     }
 
     @Override
diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/MandatoryLeafEnforcer.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/MandatoryLeafEnforcer.java
new file mode 100644 (file)
index 0000000..94af9b3
--- /dev/null
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.yang.data.impl.schema.tree;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableCollection.Builder;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import org.opendaylight.yangtools.concepts.Immutable;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.TreeType;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.TreeNode;
+import org.opendaylight.yangtools.yang.model.api.ConstraintDefinition;
+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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+// TODO: would making this Serializable be useful (for Functions and similar?)
+abstract class MandatoryLeafEnforcer implements Immutable {
+    private static final class NoOp extends MandatoryLeafEnforcer {
+        @Override
+        protected void enforceOnTreeNode(final TreeNode tree) {
+            // Intentional no-op
+        }
+
+        @Override
+        protected void enforceOnTreeNode(final NormalizedNode<?, ?> normalizedNode) {
+            // Intentional no-op
+        }
+    };
+    private static final class Strict extends MandatoryLeafEnforcer {
+        private final Collection<YangInstanceIdentifier> mandatoryNodes;
+
+        private Strict(final Collection<YangInstanceIdentifier> mandatoryNodes) {
+            this.mandatoryNodes = Preconditions.checkNotNull(mandatoryNodes);
+        }
+
+        @Override
+        protected void enforceOnTreeNode(final TreeNode tree) {
+            enforceOnTreeNode(tree.getData());
+        }
+
+        @Override
+        protected void enforceOnTreeNode(final NormalizedNode<?, ?> data) {
+            for (YangInstanceIdentifier id : mandatoryNodes) {
+                final Optional<NormalizedNode<?, ?>> descandant = NormalizedNodes.findNode(data, id);
+                Preconditions.checkArgument(descandant.isPresent(), "Node %s is missing mandatory descendant %s",
+                        data.getIdentifier(), id);
+            }
+        }
+    };
+
+    private static final Logger LOG = LoggerFactory.getLogger(MandatoryLeafEnforcer.class);
+    private static final MandatoryLeafEnforcer NOOP_ENFORCER = new NoOp();
+
+    protected abstract void enforceOnTreeNode(final TreeNode tree);
+
+    protected abstract void enforceOnTreeNode(final NormalizedNode<?, ?> normalizedNode);
+
+    private static void findMandatoryNodes(final Builder<YangInstanceIdentifier> builder,
+            final YangInstanceIdentifier id, final DataNodeContainer schema, final TreeType type) {
+        for (DataSchemaNode child : schema.getChildNodes()) {
+            if (SchemaAwareApplyOperation.belongsToTree(type, child)) {
+                if (child instanceof ContainerSchemaNode) {
+                    final ContainerSchemaNode container = (ContainerSchemaNode) child;
+                    if (!container.isPresenceContainer()) {
+                        findMandatoryNodes(builder, id.node(NodeIdentifier.create(child.getQName())), container, type);
+                    }
+                } else {
+                    final ConstraintDefinition constraints = child.getConstraints();
+                    final Integer minElements = constraints.getMinElements();
+                    if (constraints.isMandatory() || (minElements != null && minElements > 0)) {
+                        final YangInstanceIdentifier childId = id.node(NodeIdentifier.create(child.getQName()));
+                        LOG.debug("Adding mandatory child {}", childId);
+                        builder.add(childId.toOptimized());
+                    }
+                }
+            }
+        }
+    }
+
+    static MandatoryLeafEnforcer forContainer(final DataNodeContainer schema, final TreeType type) {
+        switch (type) {
+        case CONFIGURATION:
+            final Builder<YangInstanceIdentifier> builder = ImmutableList.builder();
+            findMandatoryNodes(builder, YangInstanceIdentifier.EMPTY, schema, type);
+            final Collection<YangInstanceIdentifier> mandatoryNodes = builder.build();
+            return mandatoryNodes.isEmpty() ? NOOP_ENFORCER : new Strict(mandatoryNodes);
+        case OPERATIONAL:
+            return NOOP_ENFORCER;
+        default:
+            throw new UnsupportedOperationException(String.format("Not supported tree type %s", type));
+        }
+    }
+}
index 54bdb21bb738a5ab7a267e63eab8006d9473e274..4becd4f709dfce301d85d5ae0f0ca43775154524 100644 (file)
@@ -8,25 +8,43 @@
 
 package org.opendaylight.yangtools.yang.data.impl.schema.tree;
 
-import static com.google.common.base.Preconditions.checkArgument;
-
-import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
-import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import com.google.common.base.Optional;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.TreeType;
-import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeBuilder;
-import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.TreeNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.Version;
 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
 
-final class PresenceContainerModificationStrategy extends
-        AbstractDataNodeContainerModificationStrategy<ContainerSchemaNode> {
+/**
+ * Presence container modification strategy. In addition to {@link ContainerModificationStrategy} it also enforces
+ * presence of mandatory leaves.
+ */
+final class PresenceContainerModificationStrategy extends ContainerModificationStrategy {
+    private final MandatoryLeafEnforcer enforcer;
+
     PresenceContainerModificationStrategy(final ContainerSchemaNode schemaNode, final TreeType treeType) {
-        super(schemaNode, ContainerNode.class, treeType);
+        super(schemaNode, treeType);
+        enforcer = MandatoryLeafEnforcer.forContainer(schemaNode, treeType);
+    }
+
+    @Override
+    protected TreeNode applyMerge(final ModifiedNode modification, final TreeNode currentMeta, final Version version) {
+        final TreeNode ret = super.applyMerge(modification, currentMeta, version);
+        enforcer.enforceOnTreeNode(ret);
+        return ret;
+    }
+
+    @Override
+    protected TreeNode applyWrite(final ModifiedNode modification, final Optional<TreeNode> currentMeta,
+            final Version version) {
+        final TreeNode ret = super.applyWrite(modification, currentMeta, version);
+        enforcer.enforceOnTreeNode(ret);
+        return ret;
     }
 
     @Override
-    @SuppressWarnings("rawtypes")
-    protected DataContainerNodeBuilder createBuilder(final NormalizedNode<?, ?> original) {
-        checkArgument(original instanceof ContainerNode);
-        return ImmutableContainerNodeBuilder.create((ContainerNode) original);
+    protected TreeNode applyTouch(final ModifiedNode modification, final TreeNode currentMeta, final Version version) {
+        final TreeNode ret = super.applyTouch(modification, currentMeta, version);
+        enforcer.enforceOnTreeNode(ret);
+        return ret;
     }
-}
\ No newline at end of file
+}
index 3cc6ae7d64470da1548dbb3c1c4375eb2ccf71a4..f2eefd293c7ea9b2982adf1538f2f60f817cd715 100644 (file)
@@ -23,12 +23,12 @@ import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
 
 /**
- * Structural containers are special in that they appear when implied by child
- * nodes and disappear whenever they are empty. We could implement this as a
- * subclass of {@link SchemaAwareApplyOperation}, but the automatic semantic
- * is quite different from all the other strategies. We create a
- * {@link PresenceContainerModificationStrategy} to tap into that logic, but
- * wrap it so we only call out into it
+ * Structural containers are special in that they appear when implied by child nodes and disappear whenever they are
+ * empty. We could implement this as a subclass of {@link SchemaAwareApplyOperation}, but the automatic semantic
+ * is quite different from all the other strategies. We create a {@link ContainerModificationStrategy} to tap into that
+ * logic, but wrap it so we only call out into it. We do not use {@link PresenceContainerModificationStrategy} because
+ * it enforces presence of mandatory leaves, which is not something we want here, as structural containers are not
+ * root anchors for that validation.
  */
 final class StructuralContainerModificationStrategy extends ModificationApplyOperation {
     /**
@@ -37,10 +37,10 @@ final class StructuralContainerModificationStrategy extends ModificationApplyOpe
      * {@link #apply(ModifiedNode, Optional, Version)} we will use the appropriate version as provided to us.
      */
     private static final Version FAKE_VERSION = Version.initial();
-    private final PresenceContainerModificationStrategy delegate;
+    private final ContainerModificationStrategy delegate;
 
     StructuralContainerModificationStrategy(final ContainerSchemaNode schemaNode, final TreeType treeType) {
-        this.delegate = new PresenceContainerModificationStrategy(schemaNode, treeType);
+        this.delegate = new ContainerModificationStrategy(schemaNode, treeType);
     }
 
     private Optional<TreeNode> fakeMeta(final Version version) {
diff --git a/yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/MandatoryLeafTest.java b/yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/MandatoryLeafTest.java
new file mode 100644 (file)
index 0000000..98cd9ae
--- /dev/null
@@ -0,0 +1,189 @@
+/*
+ * 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.yangtools.yang.data.impl.schema.tree;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes.leafNode;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+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.tree.DataTreeCandidate;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.TreeType;
+import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException;
+
+public class MandatoryLeafTest {
+
+    private SchemaContext schemaContext;
+
+    @Before
+    public void prepare() throws ReactorException {
+        schemaContext = RetestModel.createTestContext("/mandatory-leaf-test.yang");
+        assertNotNull("Schema context must not be null.", schemaContext);
+    }
+
+    private InMemoryDataTree initDataTree() {
+        InMemoryDataTree inMemoryDataTree = (InMemoryDataTree) InMemoryDataTreeFactory.getInstance().create(
+                TreeType.CONFIGURATION);
+        inMemoryDataTree.setSchemaContext(schemaContext);
+        return inMemoryDataTree;
+    }
+
+    @Test
+    public void testCorrectMandatoryLeafWrite() throws DataValidationFailedException {
+        final InMemoryDataTree inMemoryDataTree = initDataTree();
+        final NodeIdentifier choice1Id = new NodeIdentifier(QName.create(TestModel.TEST_QNAME, "choice1"));
+
+        final ContainerNode container = Builders
+                .containerBuilder()
+                .withNodeIdentifier(new NodeIdentifier(TestModel.TEST_QNAME))
+                .withChild(
+                        Builders.choiceBuilder()
+                                .withNodeIdentifier(choice1Id)
+                                .withChild(
+                                        Builders.containerBuilder()
+                                                .withNodeIdentifier(
+                                                        new NodeIdentifier(QName.create(TestModel.TEST_QNAME,
+                                                                "case2-cont")))
+                                                .withChild(
+                                                        leafNode(QName.create(TestModel.TEST_QNAME, "case2-leaf1"),
+                                                                "leaf-value"))
+                                                .withChild(
+                                                        leafNode(QName.create(TestModel.TEST_QNAME, "case2-leaf2"),
+                                                                "leaf-value2")).build()).build()).build();
+
+        final InMemoryDataTreeModification modificationTree = inMemoryDataTree.takeSnapshot().newModification();
+        modificationTree.write(TestModel.TEST_PATH, container);
+        modificationTree.ready();
+
+        inMemoryDataTree.validate(modificationTree);
+        final DataTreeCandidate prepare = inMemoryDataTree.prepare(modificationTree);
+        inMemoryDataTree.commit(prepare);
+    }
+
+    @Test
+    public void testCorrectMandatoryLeafChoiceWrite() throws DataValidationFailedException {
+        final InMemoryDataTree inMemoryDataTree = initDataTree();
+        // Container write
+        final ContainerNode container = Builders.containerBuilder()
+                .withNodeIdentifier(new NodeIdentifier(TestModel.TEST_QNAME)).build();
+
+        final InMemoryDataTreeModification modificationTree1 = inMemoryDataTree.takeSnapshot().newModification();
+        modificationTree1.write(TestModel.TEST_PATH, container);
+        modificationTree1.ready();
+
+        inMemoryDataTree.validate(modificationTree1);
+        final DataTreeCandidate prepare1 = inMemoryDataTree.prepare(modificationTree1);
+        inMemoryDataTree.commit(prepare1);
+
+        // Choice write
+        final NodeIdentifier choice1Id = new NodeIdentifier(QName.create(TestModel.TEST_QNAME, "choice1"));
+        final ChoiceNode choice = Builders
+                .choiceBuilder()
+                .withNodeIdentifier(choice1Id)
+                .withChild(
+                        Builders.containerBuilder()
+                                .withNodeIdentifier(
+                                        new NodeIdentifier(QName.create(TestModel.TEST_QNAME, "case2-cont")))
+                                .withChild(leafNode(QName.create(TestModel.TEST_QNAME, "case2-leaf1"), "leaf-value"))
+                                .withChild(leafNode(QName.create(TestModel.TEST_QNAME, "case2-leaf2"), "leaf-value2"))
+                                .build()).build();
+
+        final InMemoryDataTreeModification modificationTree2 = inMemoryDataTree.takeSnapshot().newModification();
+        modificationTree2.write(TestModel.TEST_PATH.node(choice1Id), choice);
+        modificationTree2.ready();
+
+        inMemoryDataTree.validate(modificationTree2);
+        final DataTreeCandidate prepare2 = inMemoryDataTree.prepare(modificationTree2);
+        inMemoryDataTree.commit(prepare2);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testMandatoryLeafViolation() throws DataValidationFailedException {
+        final InMemoryDataTree inMemoryDataTree = initDataTree();
+        final NodeIdentifier choice1Id = new NodeIdentifier(QName.create(TestModel.TEST_QNAME, "choice1"));
+
+        final ContainerNode container = Builders
+                .containerBuilder()
+                .withNodeIdentifier(new NodeIdentifier(TestModel.TEST_QNAME))
+                .withChild(
+                        Builders.choiceBuilder()
+                                .withNodeIdentifier(choice1Id)
+                                .withChild(
+                                        Builders.containerBuilder()
+                                                .withNodeIdentifier(
+                                                        new NodeIdentifier(QName.create(TestModel.TEST_QNAME,
+                                                                "case2-cont")))
+                                                .withChild(
+                                                        leafNode(QName.create(TestModel.TEST_QNAME, "case2-leaf2"),
+                                                                "leaf-value2")).build()).build()).build();
+        try {
+            final InMemoryDataTreeModification modificationTree = inMemoryDataTree.takeSnapshot().newModification();
+            modificationTree.write(TestModel.TEST_PATH, container);
+            modificationTree.ready();
+
+            inMemoryDataTree.validate(modificationTree);
+            final DataTreeCandidate prepare = inMemoryDataTree.prepare(modificationTree);
+            inMemoryDataTree.commit(prepare);
+        } catch (IllegalArgumentException e) {
+            assertEquals(
+                    "Node (urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom:store:test?revision=2014-03-13)choice1 is missing mandatory descendant /(urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom:store:test?revision=2014-03-13)case2-cont/case2-leaf1",
+                    e.getMessage());
+            throw e;
+        }
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testMandatoryLeafViolationChoiceWrite() throws DataValidationFailedException {
+        final InMemoryDataTree inMemoryDataTree = initDataTree();
+        // Container write
+        final ContainerNode container = Builders.containerBuilder()
+                .withNodeIdentifier(new NodeIdentifier(TestModel.TEST_QNAME)).build();
+
+        final InMemoryDataTreeModification modificationTree1 = inMemoryDataTree.takeSnapshot().newModification();
+        modificationTree1.write(TestModel.TEST_PATH, container);
+        modificationTree1.ready();
+
+        inMemoryDataTree.validate(modificationTree1);
+        final DataTreeCandidate prepare1 = inMemoryDataTree.prepare(modificationTree1);
+        inMemoryDataTree.commit(prepare1);
+
+        // Choice write
+        final NodeIdentifier choice1Id = new NodeIdentifier(QName.create(TestModel.TEST_QNAME, "choice1"));
+        final ChoiceNode choice = Builders
+                .choiceBuilder()
+                .withNodeIdentifier(choice1Id)
+                .withChild(
+                        Builders.containerBuilder()
+                                .withNodeIdentifier(
+                                        new NodeIdentifier(QName.create(TestModel.TEST_QNAME, "case2-cont")))
+                                .withChild(leafNode(QName.create(TestModel.TEST_QNAME, "case2-leaf2"), "leaf-value2"))
+                                .build()).build();
+
+        try {
+            final InMemoryDataTreeModification modificationTree2 = inMemoryDataTree.takeSnapshot().newModification();
+            modificationTree2.write(TestModel.TEST_PATH.node(choice1Id), choice);
+            modificationTree2.ready();
+            inMemoryDataTree.validate(modificationTree2);
+            final DataTreeCandidate prepare2 = inMemoryDataTree.prepare(modificationTree2);
+            inMemoryDataTree.commit(prepare2);
+        } catch (IllegalArgumentException e) {
+            assertEquals(
+                    "Node (urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom:store:test?revision=2014-03-13)choice1 is missing mandatory descendant /(urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom:store:test?revision=2014-03-13)case2-cont/case2-leaf1",
+                    e.getMessage());
+            throw e;
+        }
+    }
+}
index c5d4fde1e07ca9a303d5cd8cf64d501bcce4dc8c..f7b2a40a70c569e8300e274c0234ff965431e0c4 100644 (file)
@@ -8,7 +8,6 @@ module list-constraints-validation-test-model  {
     }
 
     container master-container {
-        presence true;
         list min-max-list {
             min-elements 2;
             max-elements 3;
diff --git a/yang/yang-data-impl/src/test/resources/mandatory-leaf-test.yang b/yang/yang-data-impl/src/test/resources/mandatory-leaf-test.yang
new file mode 100644 (file)
index 0000000..32e92e4
--- /dev/null
@@ -0,0 +1,74 @@
+module mandatory-leaf-test {
+    yang-version 1;
+    namespace "urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom:store:test";
+    prefix "store-test";
+
+    revision "2014-03-13" {
+        description "Initial revision.";
+    }
+
+    container test {
+        presence true;
+        config true;
+        choice choice1 {
+            case case1 {
+                leaf case1-leaf1 {
+                    type string;
+                }
+            }
+            case case2 {
+                container case2-cont {
+                    leaf case2-leaf1 {
+                        mandatory true;
+                        type string;
+                    }
+                    leaf case2-leaf2 {
+                        type string;
+                    }
+                }
+            }
+        }
+
+        list outer-list {
+
+            key id;
+            leaf id {
+                type uint16;
+            }
+            choice outer-choice {
+                case one {
+                    leaf one {
+                        type string;
+                    }
+                }
+                case two-three {
+                    leaf two {
+                        type string;
+                    }
+                    leaf three {
+                        type string;
+                    }
+                }
+            }
+            list inner-list {
+                key name;
+                leaf name {
+                    type string;
+                }
+                leaf value {
+                    type string;
+                }
+            }
+
+            list inner-list2 {
+                key name;
+                leaf name {
+                    type string;
+                }
+                leaf value {
+                    type string;
+                }
+            }
+        }
+    }
+}