--- /dev/null
+<?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
--- /dev/null
+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);
+ }
+}
--- /dev/null
+/**
+ * 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);
+ }
+}
--- /dev/null
+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
<module>restconf</module>
<module>websocket</module>
<module>yang</module>
- <!-- module>third-party</module -->
+ <module>benchmarks</module>
+ <!-- module>third-party</module -->
</modules>
<build>
@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
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
}
}
- 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);
}
}
* @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);
+ }
}
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);
}
/**
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) {
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;
}
}
- 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());
@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
@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
@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());
@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);
@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) //
@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 {
@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) //
@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);
@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)
@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());
@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());