From: Jan Hajnar Date: Wed, 20 May 2015 09:47:30 +0000 (+0200) Subject: BUG-2399: Implement automatic container removal X-Git-Tag: release/beryllium~179 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=commitdiff_plain;h=2b948733443f6f540d7e1f32355fc00462c995ed;p=yangtools.git BUG-2399: Implement automatic container removal Structural containers all all container nodes which do not contain a presence statement. RFC6020 specifies that structural containers have no semantic meaning aside from being containment nodes for their children and therefore can be created when a child would appear and should disappear when they lose their last child. Containers which should not be subject to this lifecycle need to be marked with a 'presence' statement, which attaches semantic meaning to their presence. Change-Id: Icb69c959a27726b549f563b84b52727592b67cb1 Signed-off-by: Robert Varga Signed-off-by: Jan Hajnar --- diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/AbstractModifiedNodeBasedCandidateNode.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/AbstractModifiedNodeBasedCandidateNode.java index 25fb2df240..876b2855a3 100644 --- a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/AbstractModifiedNodeBasedCandidateNode.java +++ b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/AbstractModifiedNodeBasedCandidateNode.java @@ -84,6 +84,8 @@ abstract class AbstractModifiedNodeBasedCandidateNode implements DataTreeCandida @Override public Collection getChildNodes() { switch (mod.getModificationType()) { + case APPEARED: + case DISAPPEARED: case SUBTREE_MODIFIED: return Collections2.transform(mod.getChildren(), new Function() { @Override @@ -139,6 +141,8 @@ abstract class AbstractModifiedNodeBasedCandidateNode implements DataTreeCandida @Override public final DataTreeCandidateNode getModifiedChild(final PathArgument identifier) { switch (mod.getModificationType()) { + case APPEARED: + case DISAPPEARED: case SUBTREE_MODIFIED: final Optional childMod = mod.getChild(identifier); if (childMod.isPresent()) { @@ -178,4 +182,4 @@ abstract class AbstractModifiedNodeBasedCandidateNode implements DataTreeCandida return getMod().getIdentifier(); } } -} \ No newline at end of file +} diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/ChoiceModificationStrategy.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/ChoiceModificationStrategy.java index c418c16c00..47f1e66b4e 100644 --- a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/ChoiceModificationStrategy.java +++ b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/ChoiceModificationStrategy.java @@ -30,10 +30,10 @@ final class ChoiceModificationStrategy extends AbstractNodeContainerModification final ImmutableMap.Builder child = ImmutableMap.builder(); for (final ChoiceCaseNode caze : schemaNode.getCases()) { - if(SchemaAwareApplyOperation.belongsToTree(treeType,caze)) { + if (SchemaAwareApplyOperation.belongsToTree(treeType,caze)) { for (final DataSchemaNode cazeChild : caze.getChildNodes()) { - if(SchemaAwareApplyOperation.belongsToTree(treeType,cazeChild)) { - final SchemaAwareApplyOperation childNode = SchemaAwareApplyOperation.from(cazeChild,treeType); + if (SchemaAwareApplyOperation.belongsToTree(treeType,cazeChild)) { + final ModificationApplyOperation childNode = SchemaAwareApplyOperation.from(cazeChild, treeType); child.put(NodeIdentifier.create(cazeChild.getQName()), childNode); } } @@ -53,4 +53,4 @@ final class ChoiceModificationStrategy extends AbstractNodeContainerModification checkArgument(original instanceof ChoiceNode); return ImmutableChoiceNodeBuilder.create((ChoiceNode) original); } -} \ No newline at end of file +} diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/DataTreeState.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/DataTreeState.java index 12dfc5f557..49e29ae2a0 100644 --- a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/DataTreeState.java +++ b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/DataTreeState.java @@ -48,7 +48,7 @@ final class DataTreeState { return new InMemoryDataTreeSnapshot(schemaContext, root, holder.newSnapshot()); } - DataTreeState withSchemaContext(final SchemaContext newSchemaContext, final SchemaAwareApplyOperation operation) { + DataTreeState withSchemaContext(final SchemaContext newSchemaContext, final ModificationApplyOperation operation) { holder.setCurrent(operation); return new DataTreeState(root, holder, newSchemaContext); } diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTree.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTree.java index 21f4b98226..08125b9097 100644 --- a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTree.java +++ b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTree.java @@ -8,16 +8,19 @@ package org.opendaylight.yangtools.yang.data.impl.schema.tree; import com.google.common.base.MoreObjects; -import com.google.common.base.Optional; import com.google.common.base.Preconditions; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes; import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate; -import org.opendaylight.yangtools.yang.data.api.schema.tree.StoreTreeNodes; import org.opendaylight.yangtools.yang.data.api.schema.tree.TipProducingDataTree; 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.util.DataSchemaContextNode; +import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree; +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.SchemaContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -57,23 +60,30 @@ final class InMemoryDataTree extends AbstractDataTreeTip implements TipProducing LOG.debug("Following schema contexts will be attempted {}", newSchemaContext); - final ModificationApplyOperation op = SchemaAwareApplyOperation.from(newSchemaContext, treeType); - final Optional maybeRootNode = StoreTreeNodes.findNode(op, rootPath); - if (!maybeRootNode.isPresent()) { + final DataSchemaContextTree contextTree = DataSchemaContextTree.from(newSchemaContext); + final DataSchemaContextNode rootContextNode = contextTree.getChild(rootPath); + if (rootContextNode == null) { LOG.debug("Could not find root {} in new schema context, not upgrading", rootPath); return; } - final ModificationApplyOperation rootNode = maybeRootNode.get(); - if (!(rootNode instanceof AbstractNodeContainerModificationStrategy)) { - LOG.warn("Root {} resolves to non-container type {}, not upgrading", rootPath, rootNode); + final DataSchemaNode rootSchemaNode = rootContextNode.getDataSchemaNode(); + if (!(rootSchemaNode instanceof DataNodeContainer)) { + LOG.warn("Root {} resolves to non-container type {}, not upgrading", rootPath, rootSchemaNode); return; } + final ModificationApplyOperation rootNode; + if (rootSchemaNode instanceof ContainerSchemaNode) { + rootNode = new PresenceContainerModificationStrategy((ContainerSchemaNode) rootSchemaNode, treeType); + } else { + rootNode = SchemaAwareApplyOperation.from(rootSchemaNode, treeType); + } + DataTreeState currentState, newState; do { currentState = state; - newState = currentState.withSchemaContext(newSchemaContext, (SchemaAwareApplyOperation) rootNode); + newState = currentState.withSchemaContext(newSchemaContext, rootNode); } while (!STATE_UPDATER.compareAndSet(this, currentState, newState)); } 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/PresenceContainerModificationStrategy.java similarity index 83% rename from yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/ContainerModificationStrategy.java rename to yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/PresenceContainerModificationStrategy.java index 909a6dd37e..54bdb21bb7 100644 --- 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/PresenceContainerModificationStrategy.java @@ -17,8 +17,9 @@ import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContaine import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder; import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; -final class ContainerModificationStrategy extends AbstractDataNodeContainerModificationStrategy { - ContainerModificationStrategy(final ContainerSchemaNode schemaNode, final TreeType treeType) { +final class PresenceContainerModificationStrategy extends + AbstractDataNodeContainerModificationStrategy { + PresenceContainerModificationStrategy(final ContainerSchemaNode schemaNode, final TreeType treeType) { super(schemaNode, ContainerNode.class, treeType); } @@ -28,4 +29,4 @@ final class ContainerModificationStrategy extends AbstractDataNodeContainerModif checkArgument(original instanceof ContainerNode); return ImmutableContainerNodeBuilder.create((ContainerNode) original); } -} +} \ No newline at end of file diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/SchemaAwareApplyOperation.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/SchemaAwareApplyOperation.java index 1f33b7c7a7..b119a64664 100644 --- a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/SchemaAwareApplyOperation.java +++ b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/SchemaAwareApplyOperation.java @@ -36,16 +36,17 @@ import org.slf4j.LoggerFactory; abstract class SchemaAwareApplyOperation extends ModificationApplyOperation { private static final Logger LOG = LoggerFactory.getLogger(SchemaAwareApplyOperation.class); - static SchemaAwareApplyOperation from(final ContainerSchemaNode schemaNode, final TreeType treeType) { - return new ContainerModificationStrategy(schemaNode, treeType); - } - - public static SchemaAwareApplyOperation from(final DataSchemaNode schemaNode, final TreeType treeType) { - if(treeType == TreeType.CONFIGURATION) { + public static ModificationApplyOperation from(final DataSchemaNode schemaNode, final TreeType treeType) { + if (treeType == TreeType.CONFIGURATION) { Preconditions.checkArgument(schemaNode.isConfiguration(), "Supplied %s does not belongs to configuration tree.", schemaNode.getPath()); } if (schemaNode instanceof ContainerSchemaNode) { - return new ContainerModificationStrategy((ContainerSchemaNode) schemaNode, treeType); + final ContainerSchemaNode containerSchema = (ContainerSchemaNode) schemaNode; + if (containerSchema.isPresenceContainer()) { + return new PresenceContainerModificationStrategy(containerSchema, treeType); + } else { + return new StructuralContainerModificationStrategy(containerSchema, treeType); + } } else if (schemaNode instanceof ListSchemaNode) { return fromListSchemaNode((ListSchemaNode) schemaNode, treeType); } else if (schemaNode instanceof ChoiceSchemaNode) { diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/StructuralContainerModificationStrategy.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/StructuralContainerModificationStrategy.java new file mode 100644 index 0000000000..3cc6ae7d64 --- /dev/null +++ b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/StructuralContainerModificationStrategy.java @@ -0,0 +1,115 @@ +/* + * 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 com.google.common.base.Optional; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument; +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.NormalizedNodeContainer; +import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException; +import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType; +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.TreeNodeFactory; +import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.Version; +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 + */ +final class StructuralContainerModificationStrategy extends ModificationApplyOperation { + /** + * Fake TreeNode version used in {@link #checkApplicable(YangInstanceIdentifier, NodeModification, Optional)}. + * It is okay to use a global constant, as the delegate will ignore it anyway. For + * {@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; + + StructuralContainerModificationStrategy(final ContainerSchemaNode schemaNode, final TreeType treeType) { + this.delegate = new PresenceContainerModificationStrategy(schemaNode, treeType); + } + + private Optional fakeMeta(final Version version) { + final ContainerNode container = ImmutableNodes.containerNode(delegate.getSchema().getQName()); + return Optional.of(TreeNodeFactory.createTreeNode(container, version)); + } + + @Override + Optional apply(final ModifiedNode modification, final Optional storeMeta, final Version version) { + final Optional ret; + if (modification.getOperation() == LogicalOperation.TOUCH && !storeMeta.isPresent()) { + // Container is not present, let's take care of the 'magically appear' part of our job + ret = delegate.apply(modification, fakeMeta(version), version); + + // Fake container got removed: that is a no-op + if (!ret.isPresent()) { + modification.resolveModificationType(ModificationType.UNMODIFIED); + return ret; + } + + // If the delegate indicated SUBTREE_MODIFIED, account for the fake and report APPEARED + if (modification.getModificationType() == ModificationType.SUBTREE_MODIFIED) { + modification.resolveModificationType(ModificationType.APPEARED); + } + } else { + // Container is present, run normal apply operation + ret = delegate.apply(modification, storeMeta, version); + + // Container was explicitly deleted, no magic required + if (!ret.isPresent()) { + return ret; + } + } + + /* + * At this point ret is guaranteed to be present. We need to take care of the 'magically disappear' part of + * our job. Check if there are any child nodes left. If there are none, remove this container and turn the + * modification into a DISAPPEARED. + */ + if (((NormalizedNodeContainer) ret.get().getData()).getValue().isEmpty()) { + modification.resolveModificationType(ModificationType.DISAPPEARED); + return Optional.absent(); + } + + return ret; + } + + @Override + void checkApplicable(final YangInstanceIdentifier path, final NodeModification modification, final Optional current) throws DataValidationFailedException { + if (modification.getOperation() == LogicalOperation.TOUCH && !current.isPresent()) { + // Structural containers are created as needed, so we pretend this container is here + delegate.checkApplicable(path, modification, fakeMeta(FAKE_VERSION)); + } else { + delegate.checkApplicable(path, modification, current); + } + } + + @Override + void verifyStructure(final NormalizedNode modification, final boolean verifyChildren) throws IllegalArgumentException { + delegate.verifyStructure(modification, verifyChildren); + } + + @Override + ChildTrackingPolicy getChildPolicy() { + return delegate.getChildPolicy(); + } + + @Override + public Optional getChild(final PathArgument child) { + return delegate.getChild(child); + } +} diff --git a/yang/yang-data-impl/src/test/resources/list-constraints-validation-test-model.yang b/yang/yang-data-impl/src/test/resources/list-constraints-validation-test-model.yang index f7b2a40a70..c5d4fde1e0 100644 --- a/yang/yang-data-impl/src/test/resources/list-constraints-validation-test-model.yang +++ b/yang/yang-data-impl/src/test/resources/list-constraints-validation-test-model.yang @@ -8,6 +8,7 @@ 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/odl-datastore-test.yang b/yang/yang-data-impl/src/test/resources/odl-datastore-test.yang index 94b70f4d15..c9df1e0b3b 100644 --- a/yang/yang-data-impl/src/test/resources/odl-datastore-test.yang +++ b/yang/yang-data-impl/src/test/resources/odl-datastore-test.yang @@ -8,6 +8,7 @@ module odl-datastore-test { } container test { + presence true; choice choice1 { case case1 { leaf case1-leaf1 { @@ -47,7 +48,7 @@ module odl-datastore-test { } } list inner-list { - config false; + config false; key name; leaf name { type string; diff --git a/yang/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/DataSchemaContextTree.java b/yang/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/DataSchemaContextTree.java index d11efb4f84..e30b524488 100644 --- a/yang/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/DataSchemaContextTree.java +++ b/yang/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/DataSchemaContextTree.java @@ -11,18 +11,18 @@ import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import java.util.Iterator; +import javax.annotation.Nonnull; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument; import org.opendaylight.yangtools.yang.model.api.SchemaContext; public final class DataSchemaContextTree { - private static final LoadingCache TREES = CacheBuilder.newBuilder() .weakKeys() .build(new CacheLoader() { @Override - public DataSchemaContextTree load(SchemaContext key) throws Exception { + public DataSchemaContextTree load(final SchemaContext key) throws Exception { return new DataSchemaContextTree(key); } @@ -34,8 +34,7 @@ public final class DataSchemaContextTree { root = DataSchemaContextNode.from(ctx); } - - public static DataSchemaContextTree from(SchemaContext ctx) { + @Nonnull public static DataSchemaContextTree from(@Nonnull final SchemaContext ctx) { return TREES.getUnchecked(ctx); } @@ -51,5 +50,4 @@ public final class DataSchemaContextTree { public DataSchemaContextNode getRoot() { return root; } - }