Changes the way mandatory nodes are extracted from the schema.
Previously all mandatory nodes were approached as direct children of
the parent node. Even if they came from an augmentation which caused
"missing mandatory descendant" exceptions. This patch preserves both
options of building data nodes containing augmented mandatory nodes:
- setting the mandatory node as a direct child of the parent node
- setting the mandatory node as an AugmentationNode to the parent node
However the former option seems to be incorrect and might be removed
in the future major release.
This patch also covers the case where the mandatory leaf is deeper in
the augmented subtree.
JIRA: YANGTOOLS-1276
Change-Id: Ic5cd1950b935720bf56f52cfbc24c0f9e776a474
Signed-off-by: Tibor Král <tibor.kral@pantheon.tech>
import static com.google.common.base.Verify.verify;
import static java.util.Objects.requireNonNull;
+import java.util.List;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
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.YangInstanceIdentifier.PathArgument;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes;
import org.opendaylight.yangtools.yang.data.util.DataSchemaContextNode;
}
static @NonNull MandatoryDescendant create(final YangInstanceIdentifier parentId,
- final DataNodeContainer parentSchema, final DataSchemaNode childSchema) {
+ final DataNodeContainer parentSchema, final DataSchemaNode childSchema, final boolean inAugmentedSubtree) {
final NodeIdentifier childId = NodeIdentifier.create(childSchema.getQName());
if (childSchema.isAugmenting()) {
- verify(parentSchema instanceof AugmentationTarget, "Unexpected augmenting child %s in non-target %s at %s",
- childSchema, parentSchema, parentId);
-
- final AugmentationSchemaNode aug = ((AugmentationTarget) parentSchema).getAvailableAugmentations().stream()
- .filter(augment -> augment.findDataChildByName(childSchema.getQName()).isPresent())
- .findFirst()
- .orElseThrow(() -> new IllegalArgumentException(String.format(
- "Node %s is marked as augmenting but is not present in the schema of %s", childSchema.getQName(),
- parentSchema)));
+ if (!inAugmentedSubtree) {
+ final AugmentationSchemaNode aug = getAugIdentifierOfChild(parentSchema, childSchema);
+ return new MandatoryDescendant(
+ parentId.node(DataSchemaContextNode.augmentationIdentifierFrom(aug)).node(childId).toOptimized(),
+ parentId.node(childId).toOptimized());
+ }
- return new MandatoryDescendant(
- parentId.node(DataSchemaContextNode.augmentationIdentifierFrom(aug)).node(childId).toOptimized(),
- parentId.node(childId).toOptimized());
+ final List<PathArgument> augSubtreePath = parentId.getPathArguments();
+ // in case of augmented choice-case the pathArguments might be empty
+ final YangInstanceIdentifier legacyPath = !augSubtreePath.isEmpty()
+ ? YangInstanceIdentifier.create(augSubtreePath.subList(1, augSubtreePath.size())) : null;
+ return new MandatoryDescendant(parentId.node(childId).toOptimized(), legacyPath);
}
return new MandatoryDescendant(parentId.node(childId).toOptimized(), null);
data.getIdentifier(), path));
}
+ static AugmentationSchemaNode getAugIdentifierOfChild(final DataNodeContainer parent, final DataSchemaNode child) {
+ verify(parent instanceof AugmentationTarget,
+ "Unexpected augmenting child %s in non-target parent %s", child, parent);
+ return ((AugmentationTarget) parent).getAvailableAugmentations().stream()
+ .filter(augment -> augment.findDataChildByName(child.getQName()).isPresent())
+ .findFirst()
+ .orElseThrow(() -> new IllegalArgumentException(String.format(
+ "Node %s is marked as augmenting but is not present in the schema of %s", child.getQName(), parent)));
+ }
+
@Override
public String toString() {
return legacyPath == null ? path.toString() : "(" + path + " || " + legacyPath + ")";
package org.opendaylight.yangtools.yang.data.impl.schema.tree;
import static java.util.Objects.requireNonNull;
+import static org.opendaylight.yangtools.yang.data.impl.schema.tree.MandatoryDescendant.getAugIdentifierOfChild;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeConfiguration;
import org.opendaylight.yangtools.yang.data.api.schema.tree.TreeType;
import org.opendaylight.yangtools.yang.data.spi.tree.TreeNode;
+import org.opendaylight.yangtools.yang.data.util.DataSchemaContextNode;
+import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode;
import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.CopyableNode;
import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
import org.opendaylight.yangtools.yang.model.api.ElementCountConstraintAware;
}
final Builder<MandatoryDescendant> builder = ImmutableList.builder();
- findMandatoryNodes(builder, YangInstanceIdentifier.empty(), schema, treeConfig.getTreeType());
+ final boolean isAugmentingNode = schema instanceof CopyableNode && ((CopyableNode) schema).isAugmenting();
+ findMandatoryNodes(builder, YangInstanceIdentifier.empty(), schema, treeConfig.getTreeType(), isAugmentingNode);
final ImmutableList<MandatoryDescendant> mandatoryNodes = builder.build();
return mandatoryNodes.isEmpty() ? Optional.empty() : Optional.of(new MandatoryLeafEnforcer(mandatoryNodes));
}
}
private static void findMandatoryNodes(final Builder<MandatoryDescendant> builder,
- final YangInstanceIdentifier id, final DataNodeContainer schema, final TreeType type) {
+ final YangInstanceIdentifier id, final DataNodeContainer schema, final TreeType type,
+ final boolean augmentedSubtree) {
for (final 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(container.getQName())), container, type);
+ if (!augmentedSubtree) {
+ // this container is not part of augmented subtree.
+ final boolean parentSchemaAugmenting = schema instanceof CopyableNode
+ && ((CopyableNode)schema).isAugmenting();
+ if (container.isAugmenting() && !parentSchemaAugmenting) {
+ // the container is augmenting, but the parent schema is not. Meaning this is the root
+ // of the augmentation (the augmented subtree starts here). The container has to be
+ // represented by AugmentationID and the whole subtree needs to be based on it.
+ final AugmentationSchemaNode aug = getAugIdentifierOfChild(schema, child);
+ findMandatoryNodes(builder, id.node(DataSchemaContextNode
+ .augmentationIdentifierFrom(aug)).node(NodeIdentifier.create(container.getQName())),
+ container, type, true);
+ continue;
+ }
+ }
+ // the container is either:
+ // - not in an augmented subtree and not augmenting
+ // - in an augmented subtree
+ // in both cases just append the NodeID to the ongoing ID and continue the search.
+ findMandatoryNodes(builder, id.node(NodeIdentifier.create(container.getQName())),
+ container, type, augmentedSubtree);
}
} else {
boolean needEnforce = child instanceof MandatoryAware && ((MandatoryAware) child).isMandatory();
.orElse(Boolean.FALSE);
}
if (needEnforce) {
- final MandatoryDescendant desc = MandatoryDescendant.create(id, schema, child);
+ final MandatoryDescendant desc = MandatoryDescendant.create(id, schema, child,
+ augmentedSubtree);
LOG.debug("Adding mandatory child {}", desc);
builder.add(desc);
}
import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException;
import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder;
import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
private static final QName BAZ = QName.create(FOO, "baz");
private static final QName XYZZY_LEAF = QName.create(FOO, "xyzzy-leaf");
private static final QName XYZZY_AUGMENT = QName.create(FOO, "xyzzy-augment");
+ private static final QName XYZZY_AUGMENT_CONT = QName.create(FOO, "xyzzy-augment-container");
+ private static final QName XYZZY_AUGMENT_CONT_INNER = QName.create(FOO, "xyzzy-augment-container-inner");
+ private static final QName XYZZY_AUGMENT_CONT_LEAF = QName.create(FOO, "xyzzy-augment-container-leaf");
+ private static final QName BAZ_AUG_CASE_MANDAT_LEAF = QName.create(FOO, "augmented-case-mandatory");
+ private static final QName BAZ_AUG_CASE_NON_MANDAT_LEAF = QName.create(FOO, "augmented-case-non-mandatory");
+ private static final QName NESTED_BAZ_CHOICE = QName.create(FOO, "nested-baz");
+ private static final QName NESTED_BAZ_XYZ_CASE_MANDATORY = QName.create(FOO, "nested-xyz-mandatory");
+ private static final QName NESTED_BAZ_XYZ_CASE_NON_MANDATORY = QName.create(FOO, "nested-xyz-non-mandatory");
private static EffectiveModelContext MODEL;
}
@Test
- public void testBarWithXyzzy() throws DataValidationFailedException {
+ public void testBarWithXyzzyWithSubtree() throws DataValidationFailedException {
applyOperation(mod -> {
mod.write(YangInstanceIdentifier.of(BAR), Builders.containerBuilder()
.withNodeIdentifier(new NodeIdentifier(BAR))
.withNodeIdentifier(new NodeIdentifier(BAZ))
.withChild(ImmutableNodes.leafNode(XYZZY_LEAF, "xyzzy"))
.withChild(Builders.augmentationBuilder()
- .withNodeIdentifier(new AugmentationIdentifier(Set.of(XYZZY_AUGMENT)))
+ .withNodeIdentifier(new AugmentationIdentifier(Set.of(XYZZY_AUGMENT, XYZZY_AUGMENT_CONT)))
.withChild(ImmutableNodes.leafNode(XYZZY_AUGMENT, "xyzzy"))
+ .withChild(ImmutableContainerNodeBuilder.create()
+ .withNodeIdentifier(new NodeIdentifier(XYZZY_AUGMENT_CONT))
+ .withChild(ImmutableContainerNodeBuilder.create()
+ .withNodeIdentifier(new NodeIdentifier(XYZZY_AUGMENT_CONT_INNER))
+ .withChild(ImmutableNodes.leafNode(XYZZY_AUGMENT_CONT_LEAF, "aug-cont-leaf"))
+ .build())
+ .build())
+ .build())
+ .build())
+ .build());
+ });
+ }
+
+ @Test
+ public void testBazWithAugmentedCaseWithMandatoryLeaf() throws DataValidationFailedException {
+ applyOperation(mod -> {
+ mod.write(YangInstanceIdentifier.of(BAR), Builders.containerBuilder()
+ .withNodeIdentifier(new NodeIdentifier(BAR))
+ .withChild(Builders.choiceBuilder()
+ .withNodeIdentifier(new NodeIdentifier(BAZ))
+ .withChild(ImmutableNodes.leafNode(BAZ_AUG_CASE_MANDAT_LEAF, "augmentedCaseMandatory"))
+ .withChild(ImmutableNodes.leafNode(BAZ_AUG_CASE_NON_MANDAT_LEAF, "augmentedCaseNonMandatory"))
+ .build())
+ .build());
+ });
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testBazWithAugmentedCaseWithoutMandatoryLeaf() throws DataValidationFailedException {
+ applyOperation(mod -> {
+ mod.write(YangInstanceIdentifier.of(BAR), Builders.containerBuilder()
+ .withNodeIdentifier(new NodeIdentifier(BAR))
+ .withChild(Builders.choiceBuilder()
+ .withNodeIdentifier(new NodeIdentifier(BAZ))
+ .withChild(ImmutableNodes.leafNode(BAZ_AUG_CASE_NON_MANDAT_LEAF, "augmentedCaseNonMandatory"))
+ .build())
+ .build());
+ });
+ }
+
+ @Test
+ public void testWithAugmentedNestedBazWithMandatoryLeaf() throws DataValidationFailedException {
+ applyOperation(mod -> {
+ mod.write(YangInstanceIdentifier.of(BAR), Builders.containerBuilder()
+ .withNodeIdentifier(new NodeIdentifier(BAR))
+ .withChild(Builders.choiceBuilder()
+ .withNodeIdentifier(new NodeIdentifier(BAZ))
+ .withChild(Builders.choiceBuilder()
+ .withNodeIdentifier(new NodeIdentifier(NESTED_BAZ_CHOICE))
+ .withChild(ImmutableNodes.leafNode(NESTED_BAZ_XYZ_CASE_MANDATORY, "nestedMandatory"))
+ .withChild(ImmutableNodes.leafNode(NESTED_BAZ_XYZ_CASE_NON_MANDATORY, "nestedNonMandatory"))
+ .build())
+ .build())
+ .build());
+ });
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testWithAugmentedNestedBazWithhoutMandatoryLeaf() throws DataValidationFailedException {
+ applyOperation(mod -> {
+ mod.write(YangInstanceIdentifier.of(BAR), Builders.containerBuilder()
+ .withNodeIdentifier(new NodeIdentifier(BAR))
+ .withChild(Builders.choiceBuilder()
+ .withNodeIdentifier(new NodeIdentifier(BAZ))
+ .withChild(Builders.choiceBuilder()
+ .withNodeIdentifier(new NodeIdentifier(NESTED_BAZ_CHOICE))
+ .withChild(ImmutableNodes.leafNode(NESTED_BAZ_XYZ_CASE_NON_MANDATORY, "nestedNonMandatory"))
.build())
.build())
.build());
.withNodeIdentifier(new NodeIdentifier(BAZ))
.withChild(ImmutableNodes.leafNode(XYZZY_LEAF, "xyzzy"))
.withChild(ImmutableNodes.leafNode(XYZZY_AUGMENT, "xyzzy"))
+ .withChild(ImmutableContainerNodeBuilder.create()
+ .withNodeIdentifier(NodeIdentifier.create(XYZZY_AUGMENT_CONT))
+ .withChild(ImmutableContainerNodeBuilder.create()
+ .withNodeIdentifier(NodeIdentifier.create(XYZZY_AUGMENT_CONT_INNER))
+ .withChild(ImmutableNodes.leafNode(XYZZY_AUGMENT_CONT_LEAF, "aug-cont-leaf"))
+ .build())
+ .build())
.build())
.build());
});
.withChild(Builders.choiceBuilder()
.withNodeIdentifier(new NodeIdentifier(BAZ))
.withChild(Builders.augmentationBuilder()
- .withNodeIdentifier(new AugmentationIdentifier(Set.of(XYZZY_AUGMENT)))
+ .withNodeIdentifier(new AugmentationIdentifier(Set.of(XYZZY_AUGMENT, XYZZY_AUGMENT_CONT)))
.withChild(ImmutableNodes.leafNode(XYZZY_AUGMENT, "xyzzy"))
+ .withChild(ImmutableContainerNodeBuilder.create()
+ .withNodeIdentifier(NodeIdentifier.create(XYZZY_AUGMENT_CONT))
+ .withChild(ImmutableContainerNodeBuilder.create()
+ .withNodeIdentifier(NodeIdentifier.create(XYZZY_AUGMENT_CONT_INNER))
+ .withChild(ImmutableNodes.leafNode(XYZZY_AUGMENT_CONT_LEAF, "aug-cont-leaf"))
+ .build())
+ .build())
.build())
.build())
.build());
.build())
.build());
});
- assertEquals("Node (foo)baz is missing mandatory descendant "
- + "/AugmentationIdentifier{childNames=[(foo)xyzzy-augment]}/(foo)xyzzy-augment",
- ex.getMessage());
+ assertEquals("Node (foo)baz is missing mandatory descendant /AugmentationIdentifier{childNames="
+ + "[(foo)xyzzy-augment, (foo)xyzzy-augment-container]}/(foo)xyzzy-augment", ex.getMessage());
}
type string;
mandatory true;
}
+
+ container xyzzy-augment-container {
+ container xyzzy-augment-container-inner {
+ leaf xyzzy-augment-container-leaf {
+ type string;
+ mandatory true;
+ }
+ }
+ }
+ }
+
+ augment /bar/baz {
+ case augmented-case {
+ leaf augmented-case-mandatory {
+ type string;
+ mandatory true;
+ }
+
+ leaf augmented-case-non-mandatory {
+ type string;
+ }
+ }
+ }
+
+ augment /bar/baz {
+ case case-nested-choice {
+ choice nested-baz {
+ case nested-xyz-case {
+ leaf nested-xyz-mandatory {
+ type string;
+ mandatory true;
+ }
+
+ leaf nested-xyz-non-mandatory {
+ type string;
+ }
+ }
+ }
+ }
}
}