From cbb18c74acab8a9b77ddf1dc19d988e3d0fe37fc Mon Sep 17 00:00:00 2001 From: Robert Varga Date: Tue, 25 Apr 2017 22:32:06 +0200 Subject: [PATCH] BUG-8291: expose additional DataTreeFactory methods There are use cases when we want to instantiate a DataTree rooted at a list itself, which is something we cannot discover from YangInstanceIdentifier because lists and containers share the same identifier. Therefore we need to expose a factory method which takes an initial SchemaContext, from which we can derive the appropriate root node. While we are at it, we also expose another factory method, which takes additionally the initial tree state. Since the state is not empty, we can run full validation on it before we give out the DataTree -- allowing us to perform full enforcement of mandatory nodes. Change-Id: Id362234b9ef42d93ae01577809fb004864d05d1c Signed-off-by: Robert Varga --- .../data/api/schema/tree/DataTreeFactory.java | 59 +++++++-- .../impl/schema/tree/InMemoryDataTree.java | 27 ++-- .../schema/tree/InMemoryDataTreeFactory.java | 118 +++++++++++++++--- 3 files changed, 167 insertions(+), 37 deletions(-) diff --git a/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/DataTreeFactory.java b/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/DataTreeFactory.java index e82982d4f3..ef0958f2fb 100644 --- a/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/DataTreeFactory.java +++ b/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/DataTreeFactory.java @@ -7,7 +7,10 @@ */ package org.opendaylight.yangtools.yang.data.api.schema.tree; +import com.google.common.annotations.Beta; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; /** * Factory interface for creating data trees. @@ -17,28 +20,64 @@ public interface DataTreeFactory { * Create a new data tree. * @param type * Tree type. - * * @return A data tree instance. + * + * @deprecated Use {@link #create(DataTreeConfiguration)} instead. */ + @Deprecated DataTree create(TreeType type); /** - * Create a new data tree. - * @param treeConfig - * Tree config. + * Create a new data tree rooted at a particular node. + * @param treeType + * Tree type. + * @param rootPath + * Root. + * @return A data tree instance. + * + * @deprecated Use {@link #create(DataTreeConfiguration)} instead. + */ + @Deprecated + DataTree create(TreeType treeType, YangInstanceIdentifier rootPath); + + /** + * Create a new data tree based on specified configuration, with a best-guess root. Use this method only if you + * do not have a corresponding SchemaContext handy. Mandatory nodes whose enforcement point is the root node will + * not be enforced even if some are present in the SchemaContext and validation is requested in configuration. * + * @param treeConfig + * Tree configuration. * @return A data tree instance. + * @throws NullPointerException if treeConfig is null */ DataTree create(DataTreeConfiguration treeConfig); /** - * Create a new data tree rooted at a particular node. - * @param treeType - * Tree type. - * @param rootPath - * Root. + * Create a new data tree based on specified configuration, with a root node derived from the schema context lookup + * of the configuration. Mandatory nodes whose enforcement point is the root node will not be enforced even if some + * are present in the SchemaContext and validation is requested in configuration. + * + * @param treeConfig + * Tree configuration. + * @return A data tree instance. + * @throws NullPointerException if any of the arguments are null + * @throws IllegalArgumentException if tree configuration does not match the SchemaContext, for example by root path + * referring to a node which does not exist in the SchemaContext + */ + @Beta + DataTree create(DataTreeConfiguration treeConfig, SchemaContext initialSchemaContext); + + /** + * Create a new data tree based on specified configuration, with the specified node. * + * @param treeConfig + * Tree configuration. * @return A data tree instance. + * @throws DataValidationFailedException if initial root is not valid according to the schema context + * @throws NullPointerException if any of the arguments are null + * @throws IllegalArgumentException if a mismatch between the arguments is detected */ - DataTree create(TreeType treeType, YangInstanceIdentifier rootPath); + @Beta + DataTree create(DataTreeConfiguration treeConfig, SchemaContext initialSchemaContext, + NormalizedNodeContainer initialRoot) throws DataValidationFailedException; } 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 51c12bc4a7..77624b0915 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 @@ -35,6 +35,7 @@ final class InMemoryDataTree extends AbstractDataTreeTip implements TipProducing private static final Logger LOG = LoggerFactory.getLogger(InMemoryDataTree.class); private final DataTreeConfiguration treeConfig; + private final boolean maskMandatory; /** * Current data store state generation. @@ -44,12 +45,29 @@ final class InMemoryDataTree extends AbstractDataTreeTip implements TipProducing InMemoryDataTree(final TreeNode rootNode, final DataTreeConfiguration treeConfig, final SchemaContext schemaContext) { this.treeConfig = Preconditions.checkNotNull(treeConfig, "treeConfig"); + maskMandatory = true; state = DataTreeState.createInitial(rootNode); if (schemaContext != null) { setSchemaContext(schemaContext); } } + InMemoryDataTree(final TreeNode rootNode, final DataTreeConfiguration treeConfig, final SchemaContext schemaContext, + final DataSchemaNode rootSchemaNode, final boolean maskMandatory) { + this.treeConfig = Preconditions.checkNotNull(treeConfig, "treeConfig"); + this.maskMandatory = maskMandatory; + + state = DataTreeState.createInitial(rootNode).withSchemaContext(schemaContext, getOperation(rootSchemaNode)); + } + + private ModificationApplyOperation getOperation(final DataSchemaNode rootSchemaNode) { + if (maskMandatory && rootSchemaNode instanceof ContainerSchemaNode) { + return new ContainerModificationStrategy((ContainerSchemaNode) rootSchemaNode, treeConfig); + } + + return SchemaAwareApplyOperation.from(rootSchemaNode, treeConfig); + } + /* * This method is synchronized to guard against user attempting to install * multiple contexts. Otherwise it runs in a lock-free manner. @@ -73,14 +91,7 @@ final class InMemoryDataTree extends AbstractDataTreeTip implements TipProducing return; } - final ModificationApplyOperation rootNode; - if (rootSchemaNode instanceof ContainerSchemaNode) { - // FIXME: real root needs to enfore presence, but that require pre-population - rootNode = new ContainerModificationStrategy((ContainerSchemaNode) rootSchemaNode, treeConfig); - } else { - rootNode = SchemaAwareApplyOperation.from(rootSchemaNode, treeConfig); - } - + final ModificationApplyOperation rootNode = getOperation(rootSchemaNode); DataTreeState currentState, newState; do { currentState = state; diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTreeFactory.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTreeFactory.java index ca32f6c153..381184e4dd 100644 --- a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTreeFactory.java +++ b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTreeFactory.java @@ -7,20 +7,32 @@ */ package org.opendaylight.yangtools.yang.data.impl.schema.tree; +import com.google.common.base.Preconditions; 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.api.YangInstanceIdentifier.PathArgument; 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.DataTree; +import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate; import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeConfiguration; import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeConfiguration.Builder; import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeFactory; +import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModification; +import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException; 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.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.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder; +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.ListSchemaNode; import org.opendaylight.yangtools.yang.model.api.SchemaContext; /** @@ -34,17 +46,104 @@ public final class InMemoryDataTreeFactory implements DataTreeFactory { // Never instantiated externally } + @Deprecated @Override public TipProducingDataTree create(final TreeType treeType) { return create(DataTreeConfiguration.getDefault(treeType)); } + @Deprecated + @Override + public TipProducingDataTree create(final TreeType treeType, final YangInstanceIdentifier rootPath) { + if (rootPath.isEmpty()) { + return create(treeType); + } + + final DataTreeConfiguration defConfig = DataTreeConfiguration.getDefault(treeType); + final DataTreeConfiguration config; + if (!rootPath.isEmpty()) { + config = new Builder(treeType).setMandatoryNodesValidation(defConfig.isMandatoryNodesValidationEnabled()) + .setRootPath(rootPath).setUniqueIndexes(defConfig.isUniqueIndexEnabled()).build(); + } else { + config = defConfig; + } + + return new InMemoryDataTree(TreeNodeFactory.createTreeNode(createRoot(rootPath), Version.initial()), config, + null); + } + @Override public TipProducingDataTree create(final DataTreeConfiguration treeConfig) { return new InMemoryDataTree(TreeNodeFactory.createTreeNode(createRoot(treeConfig.getRootPath()), Version.initial()), treeConfig, null); } + @Override + public DataTree create(final DataTreeConfiguration treeConfig, final SchemaContext initialSchemaContext) { + return create(treeConfig, initialSchemaContext, true); + } + + @Override + public DataTree create(final DataTreeConfiguration treeConfig, final SchemaContext initialSchemaContext, + final NormalizedNodeContainer initialRoot) throws DataValidationFailedException { + final DataTree ret = create(treeConfig, initialSchemaContext, false); + + final DataTreeModification mod = ret.takeSnapshot().newModification(); + mod.write(YangInstanceIdentifier.EMPTY, initialRoot); + mod.ready(); + + ret.validate(mod); + final DataTreeCandidate candidate = ret.prepare(mod); + ret.commit(candidate); + return ret; + } + + private static DataTree create(final DataTreeConfiguration treeConfig, final SchemaContext initialSchemaContext, + final boolean maskMandatory) { + final DataSchemaNode rootSchemaNode = getRootSchemaNode(initialSchemaContext, treeConfig.getRootPath()); + final NormalizedNode rootDataNode = createRoot((DataNodeContainer)rootSchemaNode, + treeConfig.getRootPath()); + return new InMemoryDataTree(TreeNodeFactory.createTreeNode(rootDataNode, Version.initial()), treeConfig, + initialSchemaContext, rootSchemaNode, maskMandatory); + } + + private static NormalizedNode createRoot(final DataNodeContainer schemaNode, + final YangInstanceIdentifier path) { + if (path.isEmpty()) { + Preconditions.checkArgument(schemaNode instanceof ContainerSchemaNode, + "Conceptual tree root has to be a container, not %s", schemaNode); + return ROOT_CONTAINER; + } + + final PathArgument arg = path.getLastPathArgument(); + if (schemaNode instanceof ContainerSchemaNode) { + Preconditions.checkArgument(arg instanceof NodeIdentifier, "Mismatched container %s path %s", schemaNode, + path); + return ImmutableContainerNodeBuilder.create().withNodeIdentifier((NodeIdentifier) arg).build(); + } else if (schemaNode instanceof ListSchemaNode) { + // This can either be a top-level list or its individual entry + if (arg instanceof NodeIdentifierWithPredicates) { + return ImmutableNodes.mapEntryBuilder().withNodeIdentifier((NodeIdentifierWithPredicates) arg).build(); + } + Preconditions.checkArgument(arg instanceof NodeIdentifier, "Mismatched list %s path %s", schemaNode, path); + return ImmutableNodes.mapNodeBuilder().withNodeIdentifier((NodeIdentifier) arg).build(); + } else { + throw new IllegalArgumentException("Unsupported root schema " + schemaNode); + } + } + + private static DataSchemaNode getRootSchemaNode(final SchemaContext schemaContext, + final YangInstanceIdentifier rootPath) { + final DataSchemaContextTree contextTree = DataSchemaContextTree.from(schemaContext); + final DataSchemaContextNode rootContextNode = contextTree.getChild(rootPath); + Preconditions.checkArgument(rootContextNode != null, "Failed to find root %s in schema context", rootPath); + + final DataSchemaNode rootSchemaNode = rootContextNode.getDataSchemaNode(); + Preconditions.checkArgument(rootSchemaNode instanceof DataNodeContainer, + "Root %s resolves to non-container type %s", rootPath, rootSchemaNode); + return rootSchemaNode; + } + private static NormalizedNode createRoot(final YangInstanceIdentifier path) { if (path.isEmpty()) { return ROOT_CONTAINER; @@ -62,25 +161,6 @@ public final class InMemoryDataTreeFactory implements DataTreeFactory { throw new IllegalArgumentException("Unsupported root node " + arg); } - @Override - public TipProducingDataTree create(final TreeType treeType, final YangInstanceIdentifier rootPath) { - if (rootPath.isEmpty()) { - return create(treeType); - } - - final DataTreeConfiguration defConfig = DataTreeConfiguration.getDefault(treeType); - final DataTreeConfiguration config; - if (!rootPath.isEmpty()) { - config = new Builder(treeType).setMandatoryNodesValidation(defConfig.isMandatoryNodesValidationEnabled()) - .setRootPath(rootPath).setUniqueIndexes(defConfig.isUniqueIndexEnabled()).build(); - } else { - config = defConfig; - } - - return new InMemoryDataTree(TreeNodeFactory.createTreeNode(createRoot(rootPath), Version.initial()), config, - null); - } - /** * Get an instance of this factory. This method cannot fail. * -- 2.36.6