BUG-8291: expose additional DataTreeFactory methods 25/56025/2
authorRobert Varga <robert.varga@pantheon.tech>
Tue, 25 Apr 2017 20:32:06 +0000 (22:32 +0200)
committerRobert Varga <robert.varga@pantheon.tech>
Tue, 25 Apr 2017 20:43:13 +0000 (22:43 +0200)
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 <robert.varga@pantheon.tech>
yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/DataTreeFactory.java
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/InMemoryDataTreeFactory.java

index e82982d4f354cc5ff9ea178b190cd35e50d39187..ef0958f2fbc4009be3e20a5ab471c382a238a479 100644 (file)
@@ -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;
 }
index 51c12bc4a7394cfc807d4e5642d9ffea61677583..77624b0915fc7cb024981bd3f4b6a7a522d7cf21 100644 (file)
@@ -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;
index ca32f6c1532167b43efb1bdcf35f6a99a260d756..381184e4dd6a87236378992f5859b8c95b10acc8 100644 (file)
@@ -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.
      *