Bug 1258: Implement DataTree partial indexing 96/9696/5
authorLukas Sedlak <lsedlak@cisco.com>
Tue, 5 Aug 2014 14:05:48 +0000 (16:05 +0200)
committerLukas Sedlak <lsedlak@cisco.com>
Tue, 26 Aug 2014 14:34:25 +0000 (16:34 +0200)
Now the TreeNode elements are instantiated lazily per applyWrite/applyMerge/applySubtreeChange operations.
Implemented createTreeNode in TreeNodeFactory to create non recursively single node instance in TreeNode
Renamed create to createTreeNodeRecursively.

Added benchmarks project directly into the root of yangtools project.
Benchmarks project contains benchmark for testing performance of InMemoryDataTree implementation.
Tool used for benchmarks is JHM.

Change-Id: I70c9745fc9ed8751c643d9bd3e9d58be10aa4a0e
Signed-off-by: Lukas Sedlak <lsedlak@cisco.com>
13 files changed:
benchmarks/pom.xml [new file with mode: 0644]
benchmarks/src/main/java/org/opendaylight/yangtools/yang/data/impl/tree/BenchmarkModel.java [new file with mode: 0644]
benchmarks/src/main/java/org/opendaylight/yangtools/yang/data/impl/tree/InMemoryDataTreeBenchmark.java [new file with mode: 0644]
benchmarks/src/main/resources/odl-datastore-test.yang [new file with mode: 0644]
pom.xml
yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/spi/ContainerNode.java
yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/spi/TreeNodeFactory.java
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTreeFactory.java
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/NormalizedNodeContainerModificationStrategy.java
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/SchemaAwareApplyOperation.java
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/ValueNodeModificationStrategy.java
yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/ModificationMetadataTreeTest.java
yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/TreeNodeUtilsTest.java

diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml
new file mode 100644 (file)
index 0000000..2f05f2e
--- /dev/null
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <parent>
+    <artifactId>yangtools-aggregator</artifactId>
+    <groupId>org.opendaylight.yangtools</groupId>
+    <version>0.6.2-SNAPSHOT</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+
+  <groupId>org.opendaylight.yangtools</groupId>
+  <artifactId>benchmarks</artifactId>
+
+  <properties>
+    <yangtools.version>0.6.2-SNAPSHOT</yangtools.version>
+    <yang.maven.plugin.version>0.6.2-SNAPSHOT</yang.maven.plugin.version>
+    <java.source.version>1.7</java.source.version>
+    <java.target.version>1.7</java.target.version>
+    <jmh.version>0.9.7</jmh.version>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>${project.groupId}</groupId>
+      <artifactId>yang-data-impl</artifactId>
+      <version>${yangtools.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>${project.groupId}</groupId>
+      <artifactId>yang-parser-impl</artifactId>
+      <version>${yangtools.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.openjdk.jmh</groupId>
+      <artifactId>jmh-core</artifactId>
+      <version>${jmh.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.openjdk.jmh</groupId>
+      <artifactId>jmh-generator-annprocess</artifactId>
+      <version>${jmh.version}</version>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <configuration>
+          <source>${java.source.version}</source>
+          <target>${java.source.version}</target>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+</project>
\ No newline at end of file
diff --git a/benchmarks/src/main/java/org/opendaylight/yangtools/yang/data/impl/tree/BenchmarkModel.java b/benchmarks/src/main/java/org/opendaylight/yangtools/yang/data/impl/tree/BenchmarkModel.java
new file mode 100644 (file)
index 0000000..e8cf728
--- /dev/null
@@ -0,0 +1,44 @@
+package org.opendaylight.yangtools.yang.data.impl.tree;
+
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.Set;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.parser.impl.YangParserImpl;
+
+/**
+ * @author Lukas Sedlak <lsedlak@cisco.com>
+ */
+public class BenchmarkModel {
+
+    public static final QName TEST_QNAME = QName.create("urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom:store:test", "2014-03-13",
+        "test");
+    public static final QName OUTER_LIST_QNAME = QName.create(TEST_QNAME, "outer-list");
+    public static final QName INNER_LIST_QNAME = QName.create(TEST_QNAME, "inner-list");
+    public static final QName OUTER_CHOICE_QNAME = QName.create(TEST_QNAME, "outer-choice");
+    public static final QName ID_QNAME = QName.create(TEST_QNAME, "id");
+    public static final QName NAME_QNAME = QName.create(TEST_QNAME, "name");
+    public static final QName VALUE_QNAME = QName.create(TEST_QNAME, "value");
+    private static final String DATASTORE_TEST_YANG = "/odl-datastore-test.yang";
+
+    public static final YangInstanceIdentifier TEST_PATH = YangInstanceIdentifier.of(TEST_QNAME);
+    public static final YangInstanceIdentifier OUTER_LIST_PATH = YangInstanceIdentifier.builder(TEST_PATH).node(OUTER_LIST_QNAME).build();
+
+    public static final InputStream getDatastoreBenchmarkInputStream() {
+        return getInputStream(DATASTORE_TEST_YANG);
+    }
+
+    private static InputStream getInputStream(final String resourceName) {
+        return BenchmarkModel.class.getResourceAsStream(resourceName);
+    }
+
+    public static SchemaContext createTestContext() {
+        YangParserImpl parser = new YangParserImpl();
+        Set<Module> modules = parser.parseYangModelsFromStreams(Collections.singletonList(
+            getDatastoreBenchmarkInputStream()));
+        return parser.resolveSchemaContext(modules);
+    }
+}
diff --git a/benchmarks/src/main/java/org/opendaylight/yangtools/yang/data/impl/tree/InMemoryDataTreeBenchmark.java b/benchmarks/src/main/java/org/opendaylight/yangtools/yang/data/impl/tree/InMemoryDataTreeBenchmark.java
new file mode 100644 (file)
index 0000000..306c14d
--- /dev/null
@@ -0,0 +1,194 @@
+/**
+ * Copyright (c) 2013 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.tree;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
+import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.*;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.CollectionNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.tree.InMemoryDataTreeFactory;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.openjdk.jmh.annotations.*;
+import org.openjdk.jmh.runner.Runner;
+import org.openjdk.jmh.runner.RunnerException;
+import org.openjdk.jmh.runner.options.Options;
+import org.openjdk.jmh.runner.options.OptionsBuilder;
+
+/**
+ * Benchmarking of InMemoryDataTree performance.
+ *
+ * JMH is used for microbenchmarking.
+ *
+ * @author Lukas Sedlak <lsedlak@cisco.com>
+ *
+ * @see <a href="http://openjdk.java.net/projects/code-tools/jmh/">JMH</a>
+ */
+@State(Scope.Thread)
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.MILLISECONDS)
+public class InMemoryDataTreeBenchmark {
+
+    private static final int OUTER_LIST_100K = 100000;
+    private static final int OUTER_LIST_50K = 50000;
+    private static final int OUTER_LIST_10K = 10000;
+
+    private static final YangInstanceIdentifier[] OUTER_LIST_100K_PATHS = initOuterListPaths(OUTER_LIST_100K);
+    private static final YangInstanceIdentifier[] OUTER_LIST_50K_PATHS = initOuterListPaths(OUTER_LIST_50K);
+    private static final YangInstanceIdentifier[] OUTER_LIST_10K_PATHS = initOuterListPaths(OUTER_LIST_10K);
+
+    private static YangInstanceIdentifier[] initOuterListPaths(final int outerListPathsCount) {
+        final YangInstanceIdentifier[] paths = new YangInstanceIdentifier[outerListPathsCount];
+
+        for (int outerListKey = 0; outerListKey < outerListPathsCount; ++outerListKey) {
+            paths[outerListKey] = YangInstanceIdentifier.builder(BenchmarkModel.OUTER_LIST_PATH)
+                .nodeWithKey(BenchmarkModel.OUTER_LIST_QNAME, BenchmarkModel.ID_QNAME, outerListKey)
+                .build();
+        }
+        return paths;
+    }
+
+    private static final MapNode ONE_ITEM_INNER_LIST = initInnerListItems(1);
+    private static final MapNode TWO_ITEM_INNER_LIST = initInnerListItems(2);
+    private static final MapNode TEN_ITEM_INNER_LIST = initInnerListItems(10);
+
+    private static MapNode initInnerListItems(final int count) {
+        final CollectionNodeBuilder<MapEntryNode, MapNode> mapEntryBuilder = ImmutableNodes
+            .mapNodeBuilder(BenchmarkModel.INNER_LIST_QNAME);
+
+        for (int i = 1; i <= count; ++i) {
+            mapEntryBuilder
+                .withChild(ImmutableNodes.mapEntry(BenchmarkModel.INNER_LIST_QNAME, BenchmarkModel.NAME_QNAME, i));
+        }
+
+        return mapEntryBuilder.build();
+    }
+
+    private static final NormalizedNode<?, ?>[] OUTER_LIST_ONE_ITEM_INNER_LIST = initOuterListItems(OUTER_LIST_100K, ONE_ITEM_INNER_LIST);
+    private static final NormalizedNode<?, ?>[] OUTER_LIST_TWO_ITEM_INNER_LIST = initOuterListItems(OUTER_LIST_50K, TWO_ITEM_INNER_LIST);
+    private static final NormalizedNode<?, ?>[] OUTER_LIST_TEN_ITEM_INNER_LIST = initOuterListItems(OUTER_LIST_10K, TEN_ITEM_INNER_LIST);
+
+    private static NormalizedNode<?,?>[] initOuterListItems(int outerListItemsCount, MapNode innerList) {
+        final NormalizedNode<?,?>[] outerListItems = new NormalizedNode[outerListItemsCount];
+
+        for (int i = 0; i < outerListItemsCount; ++i) {
+            int outerListKey = i;
+            outerListItems[i] = ImmutableNodes.mapEntryBuilder(BenchmarkModel.OUTER_LIST_QNAME, BenchmarkModel.ID_QNAME, outerListKey)
+                .withChild(innerList).build();
+        }
+        return outerListItems;
+    }
+
+    private SchemaContext schemaContext;
+    private DataTree datastore;
+
+    public static void main(String... args) throws IOException, RunnerException {
+        Options opt = new OptionsBuilder()
+            .include(".*" + InMemoryDataTreeBenchmark.class.getSimpleName() + ".*")
+            .forks(1)
+            .build();
+
+        new Runner(opt).run();
+    }
+
+    @Setup(Level.Trial)
+    public void setup() throws DataValidationFailedException {
+        schemaContext = BenchmarkModel.createTestContext();
+        final InMemoryDataTreeFactory factory = InMemoryDataTreeFactory.getInstance();
+        datastore = factory.create();
+        datastore.setSchemaContext(schemaContext);
+        final DataTreeSnapshot snapshot = datastore.takeSnapshot();
+        initTestNode(snapshot);
+    }
+
+    @TearDown
+    public void tearDown() {
+        schemaContext = null;
+        datastore = null;
+    }
+
+    private void initTestNode(final DataTreeSnapshot snapshot) throws DataValidationFailedException {
+        final DataTreeModification modification = snapshot.newModification();
+        final YangInstanceIdentifier testPath = YangInstanceIdentifier.builder(BenchmarkModel.TEST_PATH)
+            .build();
+
+        modification.write(testPath, provideOuterListNode());
+        datastore.validate(modification);
+        final DataTreeCandidate candidate = datastore.prepare(modification);
+        datastore.commit(candidate);
+    }
+
+    private DataContainerChild<?, ?> provideOuterListNode() {
+        return ImmutableContainerNodeBuilder
+            .create()
+            .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(BenchmarkModel.TEST_QNAME))
+            .withChild(
+                ImmutableNodes.mapNodeBuilder(BenchmarkModel.OUTER_LIST_QNAME)
+                    .build()).build();
+    }
+
+    @Benchmark
+    @Warmup(iterations = 10, timeUnit = TimeUnit.MILLISECONDS)
+    @Measurement(iterations = 20, timeUnit = TimeUnit.MILLISECONDS)
+    public void singleNodes100KWriteBenchmark() throws Exception {
+        applyWriteSingleNode(OUTER_LIST_100K);
+    }
+
+    private void applyWriteSingleNode(final int reps) throws DataValidationFailedException {
+        final DataTreeSnapshot snapshot = datastore.takeSnapshot();
+        final DataTreeModification modification = snapshot.newModification();
+        for (int outerListKey = 0; outerListKey < reps; ++outerListKey) {
+            modification.write(OUTER_LIST_100K_PATHS[outerListKey], OUTER_LIST_ONE_ITEM_INNER_LIST[outerListKey]);
+        }
+        datastore.validate(modification);
+        final DataTreeCandidate candidate = datastore.prepare(modification);
+        datastore.commit(candidate);
+    }
+
+    @Benchmark
+    @Warmup(iterations = 10, timeUnit = TimeUnit.MILLISECONDS)
+    @Measurement(iterations = 20, timeUnit = TimeUnit.MILLISECONDS)
+    public void twoNodes50KWriteBenchmark() throws Exception {
+        applyWriteTwoNodes(OUTER_LIST_50K);
+    }
+
+    private void applyWriteTwoNodes(final int reps) throws DataValidationFailedException {
+        final DataTreeSnapshot snapshot = datastore.takeSnapshot();
+        final DataTreeModification modification = snapshot.newModification();
+        for (int outerListKey = 0; outerListKey < reps; ++outerListKey) {
+            modification.write(OUTER_LIST_50K_PATHS[outerListKey], OUTER_LIST_TWO_ITEM_INNER_LIST[outerListKey]);
+        }
+        datastore.validate(modification);
+        final DataTreeCandidate candidate = datastore.prepare(modification);
+        datastore.commit(candidate);
+    }
+
+    @Benchmark
+    @Warmup(iterations = 10, timeUnit = TimeUnit.MILLISECONDS)
+    @Measurement(iterations = 20, timeUnit = TimeUnit.MILLISECONDS)
+    public void tenNodes10KWriteBenchmark() throws Exception {
+        applyWriteTenNodes(OUTER_LIST_10K);
+    }
+
+    private void applyWriteTenNodes(final int reps) throws DataValidationFailedException {
+        final DataTreeSnapshot snapshot = datastore.takeSnapshot();
+        final DataTreeModification modification = snapshot.newModification();
+        for (int outerListKey = 0; outerListKey < reps; ++outerListKey) {
+            modification.write(OUTER_LIST_10K_PATHS[outerListKey], OUTER_LIST_TEN_ITEM_INNER_LIST[outerListKey]);
+        }
+        datastore.validate(modification);
+        final DataTreeCandidate candidate = datastore.prepare(modification);
+        datastore.commit(candidate);
+    }
+}
diff --git a/benchmarks/src/main/resources/odl-datastore-test.yang b/benchmarks/src/main/resources/odl-datastore-test.yang
new file mode 100644 (file)
index 0000000..730ca17
--- /dev/null
@@ -0,0 +1,42 @@
+module odl-datastore-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 {
+        list outer-list {
+            key id;
+            leaf id {
+                type int32;
+            }
+            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 int32;
+                }
+                leaf value {
+                    type string;
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 90a3e97e4d01e74a1229dea69696d54e90a0b03e..7ad393fbc312e5dcc184b7c9d8f7f77f9b17a0c3 100644 (file)
--- a/pom.xml
+++ b/pom.xml
@@ -31,7 +31,8 @@
         <module>restconf</module>
         <module>websocket</module>
         <module>yang</module>
-        <!-- module>third-party</module -->
+        <module>benchmarks</module>
+      <!-- module>third-party</module -->
     </modules>
 
     <build>
index 6b9e36eb7a72aad68ed5874c3fb4adfc94f19639..588b884d8a9edf563af6d6b88d8977a56d7d68d0 100644 (file)
@@ -41,7 +41,18 @@ final class ContainerNode extends AbstractTreeNode {
 
     @Override
     public Optional<TreeNode> getChild(final PathArgument key) {
-        return Optional.fromNullable(children.get(key));
+        Optional<TreeNode> explicitNode = Optional.fromNullable(children.get(key));
+        if (explicitNode.isPresent()) {
+            return explicitNode;
+        }
+        final NormalizedNodeContainer<?, PathArgument, NormalizedNode<?, ?>> castedData = (NormalizedNodeContainer<?, PathArgument, NormalizedNode<?, ?>>) getData();
+        Optional<NormalizedNode<?, ?>> value = castedData.getChild(key);
+        if (value.isPresent()) {
+            //FIXME: consider caching created Tree Nodes.
+            //We are safe to not to cache them, since written Tree Nodes are in read only snapshot.
+            return Optional.of(TreeNodeFactory.createTreeNode(value.get(), getVersion()));
+        }
+        return Optional.absent();
     }
 
     @Override
@@ -60,6 +71,26 @@ final class ContainerNode extends AbstractTreeNode {
             this.children = MapAdaptor.getDefaultInstance().takeSnapshot(parent.children);
             this.subtreeVersion = parent.getSubtreeVersion();
             this.version = parent.getVersion();
+            materializeChildVersion();
+        }
+
+        /**
+         * Traverse whole data tree and instantiate children for each data node. Set version of each MutableTreeNode
+         * accordingly to version in data node.
+         *
+         * Use this method if TreeNode is lazy initialized.
+         */
+        private void materializeChildVersion() {
+            Preconditions.checkState(data instanceof NormalizedNodeContainer);
+            NormalizedNodeContainer<?, ?, NormalizedNode<?, ?>> castedData = (NormalizedNodeContainer<?, ?, NormalizedNode<?, ?>>) data;
+
+            for(NormalizedNode<?, ?> childData : castedData.getValue()) {
+                PathArgument id = childData.getIdentifier();
+
+                if (!children.containsKey(id)) {
+                    children.put(id, TreeNodeFactory.createTreeNode(childData, version));
+                }
+            }
         }
 
         @Override
@@ -97,22 +128,99 @@ final class ContainerNode extends AbstractTreeNode {
         }
     }
 
-    private static ContainerNode create(final Version version, final NormalizedNode<?, ?> data,
-            final Iterable<NormalizedNode<?, ?>> children) {
+    /**
+     * Method creates and returns Container root Node and whole subtree for each child node specified in children nodes.
+     * <br>
+     * Reason why is method used recursively is that for each child in children nodes there is call to
+     * {@link TreeNodeFactory#createTreeNodeRecursively}. Each call to <code>createTreeNodeRecursively</code>
+     * calls either {@link #createNormalizedNodeRecursively} or {@link #createOrderedNodeRecursively}
+     * which depends on type of child node.
+     * <br> The root node that is returned holds reference to data node and whole subtree of children also containing references
+     * to data nodes.
+     *
+     * @param version version of indexed data
+     * @param data reference to data node
+     * @param children direct children of root node that is being created
+     * @return Root node with reference to data node and whole subtree of child nodes
+     */
+    private static ContainerNode createNodeRecursively(final Version version, final NormalizedNode<?, ?> data,
+        final Iterable<NormalizedNode<?, ?>> children) {
 
         final Map<PathArgument, TreeNode> map = new HashMap<>();
         for (NormalizedNode<?, ?> child : children) {
-            map.put(child.getIdentifier(), TreeNodeFactory.createTreeNode(child, version));
+            map.put(child.getIdentifier(), TreeNodeFactory.createTreeNodeRecursively(child, version));
         }
 
         return new ContainerNode(data, version, map, version);
     }
 
-    public static ContainerNode create(final Version version, final NormalizedNodeContainer<?, ?, NormalizedNode<?, ?>> container) {
-        return create(version, container, container.getValue());
+    /**
+     * Method creates and returns Normalized Node Container as root and recursively creates whole subtree
+     * from all of the container child iterables stored in {@link org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer#getValue()}
+     * <br>
+     * The reason why is this method called recursively is that in background method calls {@link TreeNodeFactory#createTreeNodeRecursively}
+     * for each child stored in NormalizedNode and after each child is created the method calls again {@link #createNormalizedNodeRecursively} method
+     * until all of the children are resolved.
+     *
+     * @param version version of indexed data
+     * @param container Normalized Node Container
+     * @return Normalized Node Container as root and all whole subtree created from container iterables.
+     */
+    public static ContainerNode createNormalizedNodeRecursively(final Version version,
+        final NormalizedNodeContainer<?, ?, NormalizedNode<?, ?>> container) {
+        return createNodeRecursively(version, container, container.getValue());
+    }
+
+    /**
+     * Method creates and returns Ordered Node Container as root and recursively creates whole subtree
+     * from all of the container child iterables stored in {@link org.opendaylight.yangtools.yang.data.api.schema.OrderedNodeContainer#getValue()}
+     * <br>
+     * The reason why is this method called recursively is that in background method calls {@link TreeNodeFactory#createTreeNodeRecursively}
+     * for each child stored in NormalizedNode and after each child is created the method calls again {@link #createNormalizedNodeRecursively} method
+     * until all of the children are resolved.
+     *
+     * @param version version of indexed data
+     * @param container Ordered Node Container
+     * @return Normalized Ordered Container as root and all whole subtree created from container iterables.
+     */
+    public static ContainerNode createOrderedNodeRecursively(final Version version,
+        final OrderedNodeContainer<NormalizedNode<?, ?>> container) {
+        return createNodeRecursively(version, container, container.getValue());
     }
 
-    public static ContainerNode create(final Version version, final OrderedNodeContainer<NormalizedNode<?, ?>> container) {
-        return create(version, container, container.getValue());
+    /**
+     * Creates and returns single instance of Normalized Node Container with provided version and data reference stored in NormalizedNodeContainer.
+     *
+     * @param version version of indexed data
+     * @param container Normalized Node Container
+     * @return single instance of Normalized node with provided version and data reference stored in NormalizedNodeContainer
+     */
+    public static ContainerNode createNormalizedNode(final Version version,
+        final NormalizedNodeContainer<?, ?, NormalizedNode<?, ?>> container) {
+        return createNode(version, container);
+    }
+
+    /**
+     * Creates and returns single instance of Ordered Node Container with provided version and data reference stored in OrderedNodeContainer.
+     *
+     * @param version version of indexed data
+     * @param container Ordered Node Container
+     * @return single instance of Ordered Node Container with provided version and data reference stored in OrderedNodeContainer.
+     */
+    public static ContainerNode createOrderedNode(final Version version,
+        final OrderedNodeContainer<NormalizedNode<?, ?>> container) {
+        return createNode(version, container);
+    }
+
+    /**
+     * Creates and returns single instance of {@link ContainerNode} with provided version and data reference stored in NormalizedNode.
+     *
+     * @param version version of indexed data
+     * @param data NormalizedNode data container
+     * @return single instance of {@link ContainerNode} with provided version and data reference stored in NormalizedNode.
+     */
+    private static ContainerNode createNode(final Version version, final NormalizedNode<?, ?> data) {
+        final Map<PathArgument, TreeNode> map = new HashMap<>();
+        return new ContainerNode(data, version, map, version);
     }
 }
index 656814162f21d4a54c39900f7a4c6d64d09fbd26..4f0a2201d7c58aa59770e80099c1b68bd4608871 100644 (file)
@@ -28,19 +28,40 @@ public final class TreeNodeFactory {
      * @param version data node version
      * @return new AbstractTreeNode instance, covering the data tree provided
      */
-    public static final TreeNode createTreeNode(final NormalizedNode<?, ?> data, final Version version) {
+    public static final TreeNode createTreeNodeRecursively(final NormalizedNode<?, ?> data, final Version version) {
         if (data instanceof NormalizedNodeContainer<?, ?, ?>) {
             @SuppressWarnings("unchecked")
             NormalizedNodeContainer<?, ?, NormalizedNode<?, ?>> container = (NormalizedNodeContainer<?, ?, NormalizedNode<?, ?>>) data;
-            return ContainerNode.create(version, container);
+            return ContainerNode.createNormalizedNodeRecursively(version, container);
 
         }
         if (data instanceof OrderedNodeContainer<?>) {
             @SuppressWarnings("unchecked")
             OrderedNodeContainer<NormalizedNode<?, ?>> container = (OrderedNodeContainer<NormalizedNode<?, ?>>) data;
-            return ContainerNode.create(version, container);
+            return ContainerNode.createOrderedNodeRecursively(version, container);
         }
 
         return new ValueNode(data, version);
     }
+
+    /**
+     * Create a new AbstractTreeNode from a data node.
+     *
+     * @param data data node
+     * @param version data node version
+     * @return new AbstractTreeNode instance, covering the data tree provided
+     */
+    public static final TreeNode createTreeNode(final NormalizedNode<?, ?> data, final Version version) {
+        if (data instanceof NormalizedNodeContainer<?, ?, ?>) {
+            @SuppressWarnings("unchecked")
+            NormalizedNodeContainer<?, ?, NormalizedNode<?, ?>> container = (NormalizedNodeContainer<?, ?, NormalizedNode<?, ?>>) data;
+            return ContainerNode.createNormalizedNode(version, container);
+        }
+        if (data instanceof OrderedNodeContainer<?>) {
+            @SuppressWarnings("unchecked")
+            OrderedNodeContainer<NormalizedNode<?, ?>> container = (OrderedNodeContainer<NormalizedNode<?, ?>>) data;
+            return ContainerNode.createOrderedNode(version, container);
+        }
+        return new ValueNode(data, version);
+    }
 }
index 486c1c2cf5569641e6de28df1ea444f67925c998..6c24d4f06c84d3c63baf0e4f1ffe9d42716e66fe 100644 (file)
@@ -24,7 +24,7 @@ public final class InMemoryDataTreeFactory implements DataTreeFactory {
         final NodeIdentifier root = new NodeIdentifier(SchemaContext.NAME);
         final NormalizedNode<?, ?> data = Builders.containerBuilder().withNodeIdentifier(root).build();
 
-        return new InMemoryDataTree(TreeNodeFactory.createTreeNode(data, Version.initial()), null);
+        return new InMemoryDataTree(TreeNodeFactory.createTreeNodeRecursively(data, Version.initial()), null);
     }
 
     /**
index 6667076fb40d0f647658ffd302d95d19426136d7..045379629e3a7ccab5994f4e4471dfffb7db963b 100644 (file)
@@ -108,6 +108,16 @@ abstract class NormalizedNodeContainerModificationStrategy extends SchemaAwareAp
         return mutateChildren(mutable, dataBuilder, version, modification.getChildren());
     }
 
+    /**
+     * Applies write/remove diff operation for each modification child in modification subtree.
+     * Operation also sets the Data tree references for each Tree Node (Index Node) in meta (MutableTreeNode) structure.
+     *
+     * @param meta MutableTreeNode (IndexTreeNode)
+     * @param data DataBuilder
+     * @param nodeVersion Version of TreeNode
+     * @param modifications modification operations to apply
+     * @return Sealed immutable copy of TreeNode structure with all Data Node references set.
+     */
     @SuppressWarnings({ "rawtypes", "unchecked" })
     private TreeNode mutateChildren(final MutableTreeNode meta, final NormalizedNodeContainerBuilder data,
             final Version nodeVersion, final Iterable<ModifiedNode> modifications) {
index d0a0bcd33fe9d7d82df72dedd2700be4fc3f62e7..eecbf1c2c9a8588595360d886c39b58581ccab8b 100644 (file)
@@ -9,19 +9,24 @@ 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.Iterables;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
 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.UnkeyedListEntryNode;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.ConflictingModificationAppliedException;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.IncorrectDataStructureException;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.MutableTreeNode;
 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.builder.api.NormalizedNodeContainerBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableUnkeyedListEntryNodeBuilder;
 import org.opendaylight.yangtools.yang.model.api.AugmentationSchema;
 import org.opendaylight.yangtools.yang.model.api.AugmentationTarget;
 import org.opendaylight.yangtools.yang.model.api.ChoiceNode;
@@ -158,7 +163,18 @@ abstract class SchemaAwareApplyOperation implements ModificationApplyOperation {
         }
     }
 
-    protected void checkWriteApplicable(final YangInstanceIdentifier path, final NodeModification modification, final Optional<TreeNode> current) throws DataValidationFailedException {
+    /**
+     * Checks if write operation can be applied to current TreeNode.
+     * The operation checks if original tree node to which the modification is going to be applied exists and if
+     * current node in TreeNode structure exists.
+     *
+     * @param path Path from current node in TreeNode
+     * @param modification modification to apply
+     * @param current current node in TreeNode for modification to apply
+     * @throws DataValidationFailedException
+     */
+    protected void checkWriteApplicable(final YangInstanceIdentifier path, final NodeModification modification,
+        final Optional<TreeNode> current) throws DataValidationFailedException {
         Optional<TreeNode> original = modification.getOriginal();
         if (original.isPresent() && current.isPresent()) {
             checkNotConflicting(path, original.get(), current.get());
@@ -249,12 +265,65 @@ abstract class SchemaAwareApplyOperation implements ModificationApplyOperation {
         @Override
         protected TreeNode applyWrite(final ModifiedNode modification,
                 final Optional<TreeNode> currentMeta, final Version version) {
+            final NormalizedNode<?, ?> newValue = modification.getWrittenValue();
+            final TreeNode newValueMeta = TreeNodeFactory.createTreeNode(newValue, version);
+
+            if (Iterables.isEmpty(modification.getChildren())) {
+                return newValueMeta;
+            }
+
             /*
-             * FIXME: BUG-1258: This is inefficient: it needlessly creates index nodes for the entire subtree.
-             *        We can determine the depth into which metadata need to be created from the modification
-            *        -- if it does not have children, no need to bother with metadata.
+             * This is where things get interesting. The user has performed a write and
+             * then she applied some more modifications to it. So we need to make sense
+             * of that an apply the operations on top of the written value. We could have
+             * done it during the write, but this operation is potentially expensive, so
+             * we have left it out of the fast path.
+             *
+             * As it turns out, once we materialize the written data, we can share the
+             * code path with the subtree change. So let's create an unsealed TreeNode
+             * and run the common parts on it -- which end with the node being sealed.
              */
-            return TreeNodeFactory.createTreeNode(modification.getWrittenValue(), version);
+            final MutableTreeNode mutable = newValueMeta.mutable();
+            mutable.setSubtreeVersion(version);
+
+            @SuppressWarnings("rawtypes")
+            final NormalizedNodeContainerBuilder dataBuilder = ImmutableUnkeyedListEntryNodeBuilder
+                .create((UnkeyedListEntryNode) newValue);
+
+            return mutateChildren(mutable, dataBuilder, version, modification.getChildren());
+        }
+
+        /**
+         * Applies write/remove diff operation for each modification child in modification subtree.
+         * Operation also sets the Data tree references for each Tree Node (Index Node) in meta (MutableTreeNode) structure.
+         *
+         * @param meta MutableTreeNode (IndexTreeNode)
+         * @param data DataBuilder
+         * @param nodeVersion Version of TreeNode
+         * @param modifications modification operations to apply
+         * @return Sealed immutable copy of TreeNode structure with all Data Node references set.
+         */
+        @SuppressWarnings({ "rawtypes", "unchecked" })
+        private TreeNode mutateChildren(final MutableTreeNode meta, final NormalizedNodeContainerBuilder data,
+            final Version nodeVersion, final Iterable<ModifiedNode> modifications) {
+
+            for (ModifiedNode mod : modifications) {
+                final PathArgument id = mod.getIdentifier();
+                final Optional<TreeNode> cm = meta.getChild(id);
+
+                Optional<TreeNode> result = resolveChildOperation(id).apply(mod, cm, nodeVersion);
+                if (result.isPresent()) {
+                    final TreeNode tn = result.get();
+                    meta.addChild(tn);
+                    data.addChild(tn.getData());
+                } else {
+                    meta.removeChild(id);
+                    data.removeChild(id);
+                }
+            }
+
+            meta.setData(data.build());
+            return meta.seal();
         }
 
         @Override
index 6979a64f717c15d708f9d950fe0f93f99eff561a..3df0c80e959eef38d79d91e1ad45e28a20894d28 100644 (file)
@@ -62,7 +62,7 @@ abstract class ValueNodeModificationStrategy<T extends DataSchemaNode> extends S
     @Override
     protected TreeNode applyWrite(final ModifiedNode modification,
             final Optional<TreeNode> currentMeta, final Version version) {
-        return TreeNodeFactory.createTreeNode(modification.getWrittenValue(), version);
+        return TreeNodeFactory.createTreeNodeRecursively(modification.getWrittenValue(), version);
     }
 
     @Override
index a85acfd1e4c30f377fd768a1e7893e703f1bb477..2896675c156a0ddc5c2be0612d140dad75ed9c26 100644 (file)
@@ -146,7 +146,7 @@ public class ModificationMetadataTreeTest {
     @Test
     public void basicReadWrites() {
         DataTreeModification modificationTree = new InMemoryDataTreeModification(new InMemoryDataTreeSnapshot(schemaContext,
-                TreeNodeFactory.createTreeNode(createDocumentOne(), Version.initial()), rootOper),
+                TreeNodeFactory.createTreeNodeRecursively(createDocumentOne(), Version.initial()), rootOper),
                 rootOper);
         Optional<NormalizedNode<?, ?>> originalBarNode = modificationTree.readNode(OUTER_LIST_2_PATH);
         assertTrue(originalBarNode.isPresent());
index bc1f6cef4f50f835268179cf855b7093f63d7778..509037fbccc05915712c3fcd141f5658d079bd2e 100644 (file)
@@ -94,7 +94,7 @@ public class TreeNodeUtilsTest {
     @Test
     public void findNodeTestNodeFound() {
         InMemoryDataTreeSnapshot inMemoryDataTreeSnapshot = new InMemoryDataTreeSnapshot(schemaContext,
-                TreeNodeFactory.createTreeNode(createDocumentOne(), Version.initial()), rootOper);
+                TreeNodeFactory.createTreeNodeRecursively(createDocumentOne(), Version.initial()), rootOper);
         TreeNode rootNode = inMemoryDataTreeSnapshot.getRootNode();
         Optional<TreeNode> node = TreeNodeUtils.findNode(rootNode, OUTER_LIST_1_PATH);
         assertPresentAndType(node, TreeNode.class);
@@ -103,7 +103,7 @@ public class TreeNodeUtilsTest {
     @Test
     public void findNodeTestNodeNotFound() {
         InMemoryDataTreeSnapshot inMemoryDataTreeSnapshot = new InMemoryDataTreeSnapshot(schemaContext,
-                TreeNodeFactory.createTreeNode(createDocumentOne(), Version.initial()), rootOper);
+                TreeNodeFactory.createTreeNodeRecursively(createDocumentOne(), Version.initial()), rootOper);
         TreeNode rootNode = inMemoryDataTreeSnapshot.getRootNode();
         final YangInstanceIdentifier outerList1InvalidPath = YangInstanceIdentifier.builder(TestModel.OUTER_LIST_PATH)
                 .nodeWithKey(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 3) //
@@ -115,7 +115,7 @@ public class TreeNodeUtilsTest {
     @Test
     public void findNodeCheckedTestNodeFound() {
         InMemoryDataTreeSnapshot inMemoryDataTreeSnapshot = new InMemoryDataTreeSnapshot(schemaContext,
-                TreeNodeFactory.createTreeNode(createDocumentOne(), Version.initial()), rootOper);
+                TreeNodeFactory.createTreeNodeRecursively(createDocumentOne(), Version.initial()), rootOper);
         TreeNode rootNode = inMemoryDataTreeSnapshot.getRootNode();
         TreeNode foundNode = null;
         try {
@@ -129,7 +129,7 @@ public class TreeNodeUtilsTest {
     @Test
     public void findNodeCheckedTestNodeNotFound() {
         InMemoryDataTreeSnapshot inMemoryDataTreeSnapshot = new InMemoryDataTreeSnapshot(schemaContext,
-                TreeNodeFactory.createTreeNode(createDocumentOne(), Version.initial()), rootOper);
+                TreeNodeFactory.createTreeNodeRecursively(createDocumentOne(), Version.initial()), rootOper);
         TreeNode rootNode = inMemoryDataTreeSnapshot.getRootNode();
         final YangInstanceIdentifier outerList1InvalidPath = YangInstanceIdentifier.builder(TestModel.OUTER_LIST_PATH)
                 .nodeWithKey(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 3) //
@@ -145,7 +145,7 @@ public class TreeNodeUtilsTest {
     @Test
     public void findClosestOrFirstMatchTestNodeExists() {
         InMemoryDataTreeSnapshot inMemoryDataTreeSnapshot = new InMemoryDataTreeSnapshot(schemaContext,
-                TreeNodeFactory.createTreeNode(createDocumentOne(), Version.initial()), rootOper);
+                TreeNodeFactory.createTreeNodeRecursively(createDocumentOne(), Version.initial()), rootOper);
         TreeNode rootNode = inMemoryDataTreeSnapshot.getRootNode();
         Optional<TreeNode> expectedNode = TreeNodeUtils.findNode(rootNode, TWO_TWO_PATH);
         assertPresentAndType(expectedNode, TreeNode.class);
@@ -156,7 +156,7 @@ public class TreeNodeUtilsTest {
     @Test
     public void findClosestOrFirstMatchTestNodeDoesNotExist() {
         InMemoryDataTreeSnapshot inMemoryDataTreeSnapshot = new InMemoryDataTreeSnapshot(schemaContext,
-                TreeNodeFactory.createTreeNode(createDocumentOne(), Version.initial()), rootOper);
+                TreeNodeFactory.createTreeNodeRecursively(createDocumentOne(), Version.initial()), rootOper);
         TreeNode rootNode = inMemoryDataTreeSnapshot.getRootNode();
         final YangInstanceIdentifier outerListInnerListPath = YangInstanceIdentifier.builder(OUTER_LIST_2_PATH)
                 .node(TestModel.INNER_LIST_QNAME)
@@ -174,7 +174,7 @@ public class TreeNodeUtilsTest {
     @Test
     public void getChildTestChildFound() {
         InMemoryDataTreeSnapshot inMemoryDataTreeSnapshot = new InMemoryDataTreeSnapshot(schemaContext,
-                TreeNodeFactory.createTreeNode(createDocumentOne(), Version.initial()), rootOper);
+                TreeNodeFactory.createTreeNodeRecursively(createDocumentOne(), Version.initial()), rootOper);
         TreeNode rootNode = inMemoryDataTreeSnapshot.getRootNode();
         Optional<TreeNode> node = TreeNodeUtils.getChild(Optional.fromNullable(rootNode),
                 TestModel.TEST_PATH.getLastPathArgument());
@@ -184,7 +184,7 @@ public class TreeNodeUtilsTest {
     @Test
     public void getChildTestChildNotFound() {
         InMemoryDataTreeSnapshot inMemoryDataTreeSnapshot = new InMemoryDataTreeSnapshot(schemaContext,
-                TreeNodeFactory.createTreeNode(createDocumentOne(), Version.initial()), rootOper);
+                TreeNodeFactory.createTreeNodeRecursively(createDocumentOne(), Version.initial()), rootOper);
         TreeNode rootNode = inMemoryDataTreeSnapshot.getRootNode();
         Optional<TreeNode> node = TreeNodeUtils.getChild(Optional.fromNullable(rootNode),
                 TestModel.OUTER_LIST_PATH.getLastPathArgument());