Merge "Bug 499: Initial implementation of data tree modifications"
authorEd Warnicke <eaw@cisco.com>
Mon, 31 Mar 2014 20:13:53 +0000 (20:13 +0000)
committerGerrit Code Review <gerrit@opendaylight.org>
Mon, 31 Mar 2014 20:13:53 +0000 (20:13 +0000)
opendaylight/md-sal/sal-dom-broker/pom.xml
opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/DataAndMetadataSnapshot.java [new file with mode: 0644]
opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/ModificationApplyOperation.java [new file with mode: 0644]
opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/MutableDataTree.java [new file with mode: 0644]
opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/SchemaAwareApplyOperation.java [new file with mode: 0644]
opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/SchemaAwareApplyOperationRoot.java [new file with mode: 0644]
opendaylight/md-sal/sal-dom-broker/src/test/java/org/opendaylight/controller/md/sal/dom/store/impl/ModificationMetadataTreeTest.java [new file with mode: 0644]
opendaylight/md-sal/sal-dom-broker/src/test/java/org/opendaylight/controller/md/sal/dom/store/impl/TestModel.java [new file with mode: 0644]
opendaylight/md-sal/sal-dom-broker/src/test/resources/odl-datastore-test.yang [new file with mode: 0644]

index d192bea54036ff62d3680c8561e816b330fbebbb..e3e5043e91bdfc16015f4e29f8596e461e257714 100644 (file)
   </scm>
 
     <dependencies>
+    <dependency>
+        <groupId>junit</groupId>
+        <artifactId>junit</artifactId>
+    </dependency>
         <dependency>
             <groupId>org.opendaylight.controller</groupId>
             <artifactId>sal-core-api</artifactId>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-api</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+            <version>${slf4j.version}</version>
+            <scope>test</scope>
+        </dependency>
         <dependency>
             <groupId>com.google.guava</groupId>
             <artifactId>guava</artifactId>
diff --git a/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/DataAndMetadataSnapshot.java b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/DataAndMetadataSnapshot.java
new file mode 100644 (file)
index 0000000..52f0051
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2014 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.controller.md.sal.dom.store.impl;
+
+import org.opendaylight.controller.md.sal.dom.store.impl.tree.StoreMetadataNode;
+import org.opendaylight.controller.md.sal.dom.store.impl.tree.TreeNodeUtils;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+import com.google.common.base.Optional;
+
+class DataAndMetadataSnapshot {
+
+    private final StoreMetadataNode metadataTree;
+    private final Optional<SchemaContext> schemaContext;
+
+
+
+
+    private DataAndMetadataSnapshot(final StoreMetadataNode metadataTree, final Optional<SchemaContext> schemaCtx) {
+        this.metadataTree = metadataTree;
+        this.schemaContext = schemaCtx;
+    }
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    public static DataAndMetadataSnapshot createEmpty(final NodeIdentifier rootNode) {
+        NormalizedNode<?, ?> data = Builders.containerBuilder().withNodeIdentifier(rootNode).build();
+        StoreMetadataNode metadata = StoreMetadataNode.builder()
+                .setData(data)
+                .build();
+        return new DataAndMetadataSnapshot(metadata,Optional.<SchemaContext>absent());
+    }
+
+    public static DataAndMetadataSnapshot createEmpty(final SchemaContext ctx) {
+        NodeIdentifier rootNodeIdentifier = new NodeIdentifier(ctx.getQName());
+        NormalizedNode<?, ?> data = Builders.containerBuilder().withNodeIdentifier(rootNodeIdentifier).build();
+        StoreMetadataNode metadata = StoreMetadataNode.builder()
+                .setData(data)
+                .build();
+        return new DataAndMetadataSnapshot(metadata, Optional.of(ctx));
+    }
+
+    public Optional<SchemaContext> getSchemaContext() {
+        return schemaContext;
+    }
+
+    public NormalizedNode<?, ?> getDataTree() {
+        return metadataTree.getData();
+    }
+
+    public StoreMetadataNode getMetadataTree() {
+        return metadataTree;
+    }
+
+    public Optional<StoreMetadataNode> read(final InstanceIdentifier path) {
+        return TreeNodeUtils.findNode(metadataTree, path);
+    }
+
+    public static class Builder {
+        private NormalizedNode<?, ?> dataTree;
+        private StoreMetadataNode metadataTree;
+        private SchemaContext schemaContext;
+
+        public NormalizedNode<?, ?> getDataTree() {
+            return dataTree;
+        }
+
+        public Builder setMetadataTree(final StoreMetadataNode metadataTree) {
+            this.metadataTree = metadataTree;
+            return this;
+        }
+
+        public Builder setSchemaContext(final SchemaContext schemaContext) {
+            this.schemaContext = schemaContext;
+            return this;
+        }
+
+        public DataAndMetadataSnapshot build() {
+            return new DataAndMetadataSnapshot(metadataTree, Optional.fromNullable(schemaContext));
+        }
+
+    }
+}
diff --git a/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/ModificationApplyOperation.java b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/ModificationApplyOperation.java
new file mode 100644 (file)
index 0000000..3b9557c
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2014 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.controller.md.sal.dom.store.impl;
+
+import org.opendaylight.controller.md.sal.dom.store.impl.tree.NodeModification;
+import org.opendaylight.controller.md.sal.dom.store.impl.tree.StoreMetadataNode;
+import org.opendaylight.controller.md.sal.dom.store.impl.tree.StoreTreeNode;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
+
+import com.google.common.base.Optional;
+
+/**
+ *
+ * Operation responsible for applying {@link NodeModification} on tree.
+ *
+ * Operation is composite - operation on top level node consists of
+ * suboperations on child nodes. This allows to walk operation hierarchy and
+ * invoke suboperations independently.
+ *
+ * <b>Implementation notes</b>
+ * <ul>
+ * <li>
+ * Implementations MUST expose all nested suboperations which operates on child
+ * nodes expose via {@link #getChild(PathArgument)} method.
+ * <li>Same suboperations SHOULD be used when invoked via
+ * {@link #apply(NodeModification, Optional)} if applicable.
+ *
+ *
+ * Hierarchical composite operation which is responsible for applying
+ * modification on particular subtree and creating updated subtree
+ *
+ *
+ */
+public interface ModificationApplyOperation extends StoreTreeNode<ModificationApplyOperation> {
+
+    /**
+     *
+     * Implementation of this operation must be stateless and must not change
+     * state of this object.
+     *
+     * @param modification
+     *            NodeModification to be applied
+     * @param storeMeta
+     *            Store Metadata Node on which NodeModification should be
+     *            applied
+     * @throws IllegalArgumentException
+     *             If it is not possible to apply Operation on provided Metadata
+     *             node
+     * @return new {@link StoreMetadataNode} if operation resulted in updating
+     *         node, {@link Optional#absent()} if {@link NodeModification}
+     *         resulted in deletion of this node.
+     */
+    Optional<StoreMetadataNode> apply(NodeModification modification, Optional<StoreMetadataNode> storeMeta);
+
+    /**
+     * Returns a suboperation for specified tree node
+     *
+     * @return Reference to suboperation for specified tree node, {@link Optional#absent()}
+     *    if suboperation is not supported for specified tree node.
+     */
+    @Override
+    public Optional<ModificationApplyOperation> getChild(PathArgument child);
+
+}
diff --git a/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/MutableDataTree.java b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/MutableDataTree.java
new file mode 100644 (file)
index 0000000..ddf65b6
--- /dev/null
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2014 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.controller.md.sal.dom.store.impl;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+
+import java.util.Map.Entry;
+
+import org.opendaylight.controller.md.sal.dom.store.impl.tree.NodeModification;
+import org.opendaylight.controller.md.sal.dom.store.impl.tree.StoreMetadataNode;
+import org.opendaylight.controller.md.sal.dom.store.impl.tree.TreeNodeUtils;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Optional;
+
+class MutableDataTree {
+
+    private static final Logger log = LoggerFactory.getLogger(MutableDataTree.class);
+
+    final DataAndMetadataSnapshot snapshot;
+    final NodeModification rootModification;
+    final ModificationApplyOperation strategyTree;
+
+    private  boolean sealed = false;
+
+    private MutableDataTree(final DataAndMetadataSnapshot snapshot, final ModificationApplyOperation strategyTree) {
+        this.snapshot = snapshot;
+        this.strategyTree = strategyTree;
+        this.rootModification = NodeModification.createUnmodified(snapshot.getMetadataTree());
+    }
+
+    public void write(final InstanceIdentifier path, final NormalizedNode<?, ?> value) {
+        checkSealed();
+        resolveModificationFor(path).write(value);
+    }
+
+    public void delete(final InstanceIdentifier path) {
+        checkSealed();
+        resolveModificationFor(path).delete();
+    }
+
+    public Optional<NormalizedNode<?, ?>> read(final InstanceIdentifier path) {
+        Entry<InstanceIdentifier, NodeModification> modification = TreeNodeUtils.findClosest(rootModification, path);
+        return getModifiedVersion(path, modification);
+    }
+
+    private Optional<NormalizedNode<?, ?>> getModifiedVersion(final InstanceIdentifier path, final Entry<InstanceIdentifier, NodeModification> modification) {
+        Optional<StoreMetadataNode> result = resolveSnapshot(modification);
+        if(result.isPresent()) {
+            NormalizedNode<?, ?> data = result.get().getData();
+            return NormalizedNodeUtils.findNode(modification.getKey(),data, path);
+        }
+        return Optional.absent();
+
+    }
+
+    private Optional<StoreMetadataNode> resolveSnapshot(final Entry<InstanceIdentifier, NodeModification> keyModification) {
+        InstanceIdentifier path = keyModification.getKey();
+        NodeModification modification = keyModification.getValue();
+        return resolveSnapshot(path,modification);
+    }
+
+    private Optional<StoreMetadataNode> resolveSnapshot(final InstanceIdentifier path, final NodeModification modification) {
+        try {
+            return resolveModificationStrategy(path).apply(modification,modification.getOriginal());
+        } catch (Exception e) {
+            log.error("Could not create snapshot for {},",e);
+            throw e;
+        }
+    }
+
+    private ModificationApplyOperation resolveModificationStrategy(final InstanceIdentifier path) {
+        log.trace("Resolving modification apply strategy for {}",path);
+        Optional<ModificationApplyOperation> strategy = TreeNodeUtils.findNode(strategyTree, path);
+        checkArgument(strategy.isPresent(),"Provided path %s is not supported by data store. No schema available for it.",path);
+        return strategy.get();
+    }
+
+    private NodeModification resolveModificationFor(final InstanceIdentifier path) {
+        NodeModification modification = rootModification;
+        // We ensure strategy is present.
+        resolveModificationStrategy(path);
+        for (PathArgument pathArg : path.getPath()) {
+            modification = modification.modifyChild(pathArg);
+        }
+        return modification;
+    }
+
+    public static MutableDataTree from(final DataAndMetadataSnapshot snapshot, final ModificationApplyOperation resolver) {
+        return new MutableDataTree(snapshot, resolver);
+    }
+
+    public void seal() {
+        sealed = true;
+        rootModification.seal();
+    }
+
+    private void checkSealed() {
+        checkState(!sealed , "Data Tree is sealed. No further modifications allowed.");
+    }
+}
diff --git a/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/SchemaAwareApplyOperation.java b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/SchemaAwareApplyOperation.java
new file mode 100644 (file)
index 0000000..114595f
--- /dev/null
@@ -0,0 +1,343 @@
+package org.opendaylight.controller.md.sal.dom.store.impl;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import java.util.Set;
+
+import org.opendaylight.controller.md.sal.dom.store.impl.tree.NodeModification;
+import org.opendaylight.controller.md.sal.dom.store.impl.tree.StoreMetadataNode;
+import org.opendaylight.controller.md.sal.dom.store.impl.tree.StoreNodeCompositeBuilder;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeWithValue;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.NormalizedNodeContainerBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableLeafSetNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableMapEntryNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableMapNodeBuilder;
+import org.opendaylight.yangtools.yang.model.api.ChoiceNode;
+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.LeafListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSet.Builder;
+import com.google.common.primitives.UnsignedLong;
+
+public abstract class SchemaAwareApplyOperation implements ModificationApplyOperation {
+
+    public static SchemaAwareApplyOperation from(final DataSchemaNode schemaNode) {
+        if (schemaNode instanceof ContainerSchemaNode) {
+            return new ContainerModificationStrategy((ContainerSchemaNode) schemaNode);
+        } else if (schemaNode instanceof ListSchemaNode) {
+            return new ListMapModificationStrategy((ListSchemaNode) schemaNode);
+        } else if (schemaNode instanceof ChoiceNode) {
+            return new ChoiceModificationStrategy((ChoiceNode) schemaNode);
+        } else if (schemaNode instanceof LeafListSchemaNode) {
+            return new LeafSetEntryModificationStrategy((LeafListSchemaNode) schemaNode);
+        } else if (schemaNode instanceof LeafSchemaNode) {
+            return new LeafModificationStrategy((LeafSchemaNode) schemaNode);
+        }
+        throw new IllegalArgumentException("Not supported schema node type for " + schemaNode.getClass());
+    }
+
+    @Override
+    public Optional<ModificationApplyOperation> getChild(final PathArgument child) {
+        throw new IllegalArgumentException();
+    }
+
+    protected final ModificationApplyOperation resolveChildOperation(final PathArgument child) {
+        Optional<ModificationApplyOperation> potential = getChild(child);
+        checkArgument(potential.isPresent(), "Operation for child %s is not defined.", child);
+        return potential.get();
+    }
+
+    @Override
+    public final Optional<StoreMetadataNode> apply(final NodeModification modification,
+            final Optional<StoreMetadataNode> currentMeta) {
+        switch (modification.getModificationType()) {
+        case DELETE:
+            return Optional.absent();
+        case SUBTREE_MODIFIED:
+            return Optional.of(applySubtreeChange(modification, currentMeta.get()));
+        case WRITE:
+            return Optional.of(applyWrite(modification, currentMeta));
+        case UNMODIFIED:
+            return currentMeta;
+        default:
+            throw new IllegalArgumentException("Provided modification type is not supported.");
+        }
+    }
+
+    protected abstract StoreMetadataNode applyWrite(NodeModification modification,
+            Optional<StoreMetadataNode> currentMeta);
+
+    protected abstract StoreMetadataNode applySubtreeChange(NodeModification modification, StoreMetadataNode currentMeta);
+
+    public static abstract class ValueNodeModificationStrategy<T extends DataSchemaNode> extends
+            SchemaAwareApplyOperation {
+
+        private final T schema;
+
+        protected ValueNodeModificationStrategy(final T schema) {
+            super();
+            this.schema = schema;
+        }
+
+        @Override
+        public Optional<ModificationApplyOperation> getChild(final PathArgument child) {
+            throw new UnsupportedOperationException("Node " + schema.getPath()
+                    + "is leaf type node. Child nodes not allowed");
+        }
+
+        @Override
+        protected StoreMetadataNode applySubtreeChange(final NodeModification modification, final StoreMetadataNode currentMeta) {
+            throw new UnsupportedOperationException("Node " + schema.getPath()
+                    + "is leaf type node. Subtree change is not allowed.");
+        }
+
+        @Override
+        protected StoreMetadataNode applyWrite(final NodeModification modification, final Optional<StoreMetadataNode> currentMeta) {
+            return StoreMetadataNode.builder()
+            // FIXME Add .increaseNodeVersion()
+                    .setData(modification.getWritenValue()).build();
+        }
+
+    }
+
+    public static class LeafSetEntryModificationStrategy extends ValueNodeModificationStrategy<LeafListSchemaNode> {
+
+        protected LeafSetEntryModificationStrategy(final LeafListSchemaNode schema) {
+            super(schema);
+        }
+    }
+
+    public static class LeafModificationStrategy extends ValueNodeModificationStrategy<LeafSchemaNode> {
+
+        protected LeafModificationStrategy(final LeafSchemaNode schema) {
+            super(schema);
+        }
+    }
+
+    public static abstract class NormalizedNodeContainerModificationStrategy extends SchemaAwareApplyOperation {
+
+        @Override
+        protected StoreMetadataNode applyWrite(final NodeModification modification, final Optional<StoreMetadataNode> currentMeta) {
+            //
+            NormalizedNode<?, ?> newValue = modification.getWritenValue();
+
+            StoreMetadataNode newValueMeta = StoreMetadataNode.createRecursivelly(newValue, UnsignedLong.valueOf(0));
+
+            if(!modification.hasAdditionalModifications()) {
+                return newValueMeta;
+            }
+            StoreNodeCompositeBuilder builder = StoreNodeCompositeBuilder.from(newValueMeta,
+                    createBuilder(modification.getIdentifier()));
+
+            Set<PathArgument> processedPreexisting = applyPreexistingChildren(modification, newValueMeta.getChildren(), builder);
+            applyNewChildren(modification, processedPreexisting, builder);
+
+            return builder.build();
+
+        }
+
+        @Override
+        @SuppressWarnings("rawtypes")
+        public StoreMetadataNode applySubtreeChange(final NodeModification modification, final StoreMetadataNode currentMeta) {
+
+            StoreNodeCompositeBuilder builder = StoreNodeCompositeBuilder.from(currentMeta,
+                    createBuilder(modification.getIdentifier()));
+            builder.setIdentifier(modification.getIdentifier());
+
+            // We process preexisting nodes
+            Set<PathArgument> processedPreexisting = applyPreexistingChildren(modification,
+                    currentMeta.getChildren(), builder);
+            applyNewChildren(modification, processedPreexisting, builder);
+            return builder.build();
+        }
+
+        private void applyNewChildren(final NodeModification modification, final Set<PathArgument> ignore,
+                final StoreNodeCompositeBuilder builder) {
+            for (NodeModification childModification : modification.getModifications()) {
+                PathArgument childIdentifier = childModification.getIdentifier();
+                // We skip allready processed modifications
+                if (ignore.contains(childIdentifier)) {
+                    continue;
+                }
+                Optional<StoreMetadataNode> childResult = resolveChildOperation(childIdentifier) //
+                        .apply(childModification, Optional.<StoreMetadataNode> absent());
+                if (childResult.isPresent()) {
+                    builder.add(childResult.get());
+                }
+            }
+        }
+
+        private Set<PathArgument> applyPreexistingChildren(final NodeModification modification,
+                final Iterable<StoreMetadataNode> children, final StoreNodeCompositeBuilder nodeBuilder) {
+            Builder<PathArgument> processedModifications = ImmutableSet.<PathArgument> builder();
+            for (StoreMetadataNode childMeta : children) {
+                PathArgument childIdentifier = childMeta.getIdentifier();
+                // We retrieve Child modification metadata
+                Optional<NodeModification> childModification = modification.getChild(childIdentifier);
+                // Node is modified
+                if (childModification.isPresent()) {
+                    processedModifications.add(childIdentifier);
+                    Optional<StoreMetadataNode> change = resolveChildOperation(childIdentifier) //
+                            .apply(childModification.get(), Optional.of(childMeta));
+                } else {
+                    // Child is unmodified - reuse existing metadata and data
+                    // snapshot
+                    nodeBuilder.add(childMeta);
+                }
+            }
+            return processedModifications.build();
+        }
+
+        @SuppressWarnings("rawtypes")
+        protected abstract NormalizedNodeContainerBuilder createBuilder(PathArgument identifier);
+    }
+
+    public static abstract class DataNodeContainerModificationStrategy<T extends DataNodeContainer> extends
+            NormalizedNodeContainerModificationStrategy {
+
+        private final T schema;
+
+        protected DataNodeContainerModificationStrategy(final T schema) {
+            super();
+            this.schema = schema;
+        }
+
+        protected T getSchema() {
+            return schema;
+        }
+
+        @Override
+        public Optional<ModificationApplyOperation> getChild(final PathArgument identifier) {
+            DataSchemaNode child = schema.getDataChildByName(identifier.getNodeType());
+            if (child == null || child.isAugmenting()) {
+                return Optional.absent();
+            }
+            return Optional.<ModificationApplyOperation> of(from(child));
+        }
+
+        @Override
+        @SuppressWarnings("rawtypes")
+        protected abstract DataContainerNodeBuilder createBuilder(PathArgument identifier);
+
+        @Override
+        public String toString() {
+            return getClass().getSimpleName() + " [" + schema + "]";
+        }
+
+    }
+
+    public static class ContainerModificationStrategy extends
+            DataNodeContainerModificationStrategy<ContainerSchemaNode> {
+
+        public ContainerModificationStrategy(final ContainerSchemaNode schemaNode) {
+            super(schemaNode);
+        }
+
+        @Override
+        @SuppressWarnings("rawtypes")
+        protected DataContainerNodeBuilder createBuilder(final PathArgument identifier) {
+            // TODO Auto-generated method stub
+            checkArgument(identifier instanceof NodeIdentifier);
+            return ImmutableContainerNodeBuilder.create().withNodeIdentifier((NodeIdentifier) identifier);
+        }
+
+    }
+
+    public static class ChoiceModificationStrategy extends NormalizedNodeContainerModificationStrategy {
+
+        private final ChoiceNode schema;
+
+        public ChoiceModificationStrategy(final ChoiceNode schemaNode) {
+            this.schema = schemaNode;
+        }
+
+        @Override
+        @SuppressWarnings("rawtypes")
+        protected DataContainerNodeBuilder createBuilder(final PathArgument identifier) {
+            checkArgument(identifier instanceof NodeIdentifier);
+            return ImmutableContainerNodeBuilder.create().withNodeIdentifier((NodeIdentifier) identifier);
+        }
+
+    }
+
+    public static class ListEntryModificationStrategy extends DataNodeContainerModificationStrategy<ListSchemaNode> {
+
+        protected ListEntryModificationStrategy(final ListSchemaNode schema) {
+            super(schema);
+        }
+
+        @Override
+        @SuppressWarnings("rawtypes")
+        protected final DataContainerNodeBuilder createBuilder(final PathArgument identifier) {
+            return ImmutableMapEntryNodeBuilder.create().withNodeIdentifier((NodeIdentifierWithPredicates) identifier);
+        }
+
+    }
+
+    public static class LeafSetModificationStrategy extends NormalizedNodeContainerModificationStrategy {
+
+        private final Optional<ModificationApplyOperation> entryStrategy;
+
+        protected LeafSetModificationStrategy(final LeafListSchemaNode schema) {
+            entryStrategy = Optional.<ModificationApplyOperation> of(new LeafSetEntryModificationStrategy(schema));
+        }
+
+        @Override
+        protected NormalizedNodeContainerBuilder createBuilder(final PathArgument identifier) {
+            return ImmutableLeafSetNodeBuilder.create().withNodeIdentifier((NodeIdentifier) identifier);
+        }
+
+        @Override
+        public Optional<ModificationApplyOperation> getChild(final PathArgument identifier) {
+            if (identifier instanceof NodeWithValue) {
+                return entryStrategy;
+            }
+            return Optional.absent();
+        }
+
+    }
+
+    public static class ListMapModificationStrategy extends NormalizedNodeContainerModificationStrategy {
+
+        private final Optional<ModificationApplyOperation> entryStrategy;
+
+        protected ListMapModificationStrategy(final ListSchemaNode schema) {
+            entryStrategy = Optional.<ModificationApplyOperation> of(new ListEntryModificationStrategy(schema));
+        }
+
+        @Override
+        protected NormalizedNodeContainerBuilder createBuilder(final PathArgument identifier) {
+            return ImmutableMapNodeBuilder.create().withNodeIdentifier((NodeIdentifier) identifier);
+        }
+
+        @Override
+        public Optional<ModificationApplyOperation> getChild(final PathArgument identifier) {
+            if (identifier instanceof NodeIdentifierWithPredicates) {
+                return entryStrategy;
+            }
+            return Optional.absent();
+        }
+
+        @Override
+        public String toString() {
+            return "ListMapModificationStrategy [entry=" + entryStrategy + "]";
+        }
+    }
+
+    public void verifyIdentifier(final PathArgument identifier) {
+
+    }
+
+}
diff --git a/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/SchemaAwareApplyOperationRoot.java b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/SchemaAwareApplyOperationRoot.java
new file mode 100644 (file)
index 0000000..bca5069
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2014 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.controller.md.sal.dom.store.impl;
+
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder;
+import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+public class SchemaAwareApplyOperationRoot extends SchemaAwareApplyOperation.DataNodeContainerModificationStrategy<ContainerSchemaNode> {
+
+    private final SchemaContext context;
+
+    public SchemaAwareApplyOperationRoot(final SchemaContext context) {
+        super(context);
+        this.context = context;
+    }
+
+    @Override
+    protected DataContainerNodeBuilder createBuilder(final PathArgument identifier) {
+        return ImmutableContainerNodeBuilder.create().withNodeIdentifier((NodeIdentifier) identifier);
+    }
+
+    public SchemaContext getContext() {
+        return context;
+    }
+
+    @Override
+    public String toString() {
+        return "SchemaAwareApplyOperationRoot [context=" + context + "]";
+    }
+
+}
diff --git a/opendaylight/md-sal/sal-dom-broker/src/test/java/org/opendaylight/controller/md/sal/dom/store/impl/ModificationMetadataTreeTest.java b/opendaylight/md-sal/sal-dom-broker/src/test/java/org/opendaylight/controller/md/sal/dom/store/impl/ModificationMetadataTreeTest.java
new file mode 100644 (file)
index 0000000..7610a78
--- /dev/null
@@ -0,0 +1,255 @@
+package org.opendaylight.controller.md.sal.dom.store.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.opendaylight.controller.md.sal.dom.store.impl.TestModel.ID_QNAME;
+import static org.opendaylight.controller.md.sal.dom.store.impl.TestModel.INNER_LIST_QNAME;
+import static org.opendaylight.controller.md.sal.dom.store.impl.TestModel.NAME_QNAME;
+import static org.opendaylight.controller.md.sal.dom.store.impl.TestModel.OUTER_LIST_PATH;
+import static org.opendaylight.controller.md.sal.dom.store.impl.TestModel.OUTER_LIST_QNAME;
+import static org.opendaylight.controller.md.sal.dom.store.impl.TestModel.TEST_PATH;
+import static org.opendaylight.controller.md.sal.dom.store.impl.TestModel.TEST_QNAME;
+import static org.opendaylight.controller.md.sal.dom.store.impl.TestModel.VALUE_QNAME;
+import static org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes.mapEntry;
+import static org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes.mapEntryBuilder;
+import static org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes.mapNodeBuilder;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.opendaylight.controller.md.sal.dom.store.impl.tree.StoreMetadataNode;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+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.impl.schema.ImmutableNodes;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+import com.google.common.base.Optional;
+import com.google.common.primitives.UnsignedLong;
+
+/**
+ *
+ * Schema structure of document is
+ *
+ * <pre>
+ * container root { 
+ *      list list-a {
+ *              key leaf-a;
+ *              leaf leaf-a;
+ *              choice choice-a {
+ *                      case one {
+ *                              leaf one;
+ *                      }
+ *                      case two-three {
+ *                              leaf two;
+ *                              leaf three;
+ *                      }
+ *              }
+ *              list list-b {
+ *                      key leaf-b;
+ *                      leaf leaf-b;
+ *              }
+ *      }
+ * }
+ * </pre>
+ *
+ */
+public class ModificationMetadataTreeTest {
+
+    private static final Short ONE_ID = 1;
+    private static final Short TWO_ID = 2;
+    private static final String TWO_ONE_NAME = "one";
+    private static final String TWO_TWO_NAME = "two";
+
+    private static final InstanceIdentifier OUTER_LIST_1_PATH = InstanceIdentifier.builder(OUTER_LIST_PATH)
+            .nodeWithKey(OUTER_LIST_QNAME, ID_QNAME, ONE_ID) //
+            .build();
+
+    private static final InstanceIdentifier OUTER_LIST_2_PATH = InstanceIdentifier.builder(OUTER_LIST_PATH)
+            .nodeWithKey(OUTER_LIST_QNAME, ID_QNAME, TWO_ID) //
+            .build();
+
+    private static final InstanceIdentifier TWO_TWO_PATH = InstanceIdentifier.builder(OUTER_LIST_2_PATH)
+            .node(INNER_LIST_QNAME) //
+            .nodeWithKey(INNER_LIST_QNAME, NAME_QNAME, TWO_TWO_NAME) //
+            .build();
+
+    private static final InstanceIdentifier TWO_TWO_VALUE_PATH = InstanceIdentifier.builder(TWO_TWO_PATH)
+            .node(VALUE_QNAME) //
+            .build();
+
+    private static final MapEntryNode BAR_NODE = mapEntryBuilder(OUTER_LIST_QNAME, ID_QNAME, TWO_ID) //
+            .withChild(mapNodeBuilder(INNER_LIST_QNAME) //
+                    .withChild(mapEntry(INNER_LIST_QNAME, NAME_QNAME, TWO_ONE_NAME)) //
+                    .withChild(mapEntry(INNER_LIST_QNAME, NAME_QNAME, TWO_TWO_NAME)) //
+                    .build()) //
+            .build();
+
+    private SchemaContext schemaContext;
+
+    @Before
+    public void prepare() {
+        schemaContext = TestModel.createTestContext();
+        assertNotNull("Schema context must not be null.", schemaContext);
+    }
+
+    /**
+     * Returns a test document
+     *
+     * <pre>
+     * test
+     *     outer-list
+     *          id 1
+     *     outer-list
+     *          id 2
+     *          inner-list
+     *                  name "one"
+     *          inner-list
+     *                  name "two"
+     *
+     * </pre>
+     *
+     * @return
+     */
+    public NormalizedNode<?, ?> createDocumentOne() {
+        return ImmutableContainerNodeBuilder
+                .create()
+                .withNodeIdentifier(new NodeIdentifier(schemaContext.getQName()))
+                .withChild(createTestContainer()).build();
+
+    }
+
+    private ContainerNode createTestContainer() {
+        return ImmutableContainerNodeBuilder
+                .create()
+                .withNodeIdentifier(new NodeIdentifier(TEST_QNAME))
+                .withChild(
+                        mapNodeBuilder(OUTER_LIST_QNAME)
+                                .withChild(mapEntry(OUTER_LIST_QNAME, ID_QNAME, ONE_ID))
+                                .withChild(BAR_NODE).build()).build();
+    }
+
+    private StoreMetadataNode createDocumentOneMetadata() {
+        UnsignedLong version = UnsignedLong.valueOf(0);
+        return StoreMetadataNode.createRecursivelly(createDocumentOne(), version);
+    }
+
+    @Test
+    public void basicReadWrites() {
+        MutableDataTree modificationTree = MutableDataTree.from(
+                DataAndMetadataSnapshot.builder() //
+                        .setMetadataTree(createDocumentOneMetadata()) //
+                        .setSchemaContext(schemaContext) //
+                        .build(), new SchemaAwareApplyOperationRoot(schemaContext));
+        Optional<NormalizedNode<?, ?>> originalBarNode = modificationTree.read(OUTER_LIST_2_PATH);
+        assertTrue(originalBarNode.isPresent());
+        assertSame(BAR_NODE, originalBarNode.get());
+
+        // writes node to /outer-list/1/inner_list/two/value
+        modificationTree.write(TWO_TWO_VALUE_PATH, ImmutableNodes.leafNode(VALUE_QNAME, "test"));
+
+        // reads node to /outer-list/1/inner_list/two/value
+        // and checks if node is already present
+        Optional<NormalizedNode<?, ?>> barTwoCModified = modificationTree.read(TWO_TWO_VALUE_PATH);
+        assertTrue(barTwoCModified.isPresent());
+        assertEquals(ImmutableNodes.leafNode(VALUE_QNAME, "test"), barTwoCModified.get());
+
+        // delete node to /outer-list/1/inner_list/two/value
+        modificationTree.delete(TWO_TWO_VALUE_PATH);
+        Optional<NormalizedNode<?, ?>> barTwoCAfterDelete = modificationTree.read(TWO_TWO_VALUE_PATH);
+        assertFalse(barTwoCAfterDelete.isPresent());
+    }
+
+
+    public MutableDataTree createEmptyModificationTree() {
+        /**
+         * Creates empty Snapshot with associated schema context.
+         */
+        DataAndMetadataSnapshot emptySnapshot = DataAndMetadataSnapshot.createEmpty(schemaContext);
+
+        /**
+         *
+         * Creates Mutable Data Tree based on provided snapshot and schema
+         * context.
+         *
+         */
+        MutableDataTree modificationTree = MutableDataTree.from(emptySnapshot, new SchemaAwareApplyOperationRoot(
+                schemaContext));
+        return modificationTree;
+    }
+
+    @Test
+    public void createFromEmptyState() {
+
+        MutableDataTree modificationTree = createEmptyModificationTree();
+        /**
+         * Writes empty container node to /test
+         *
+         */
+        modificationTree.write(TEST_PATH, ImmutableNodes.containerNode(TEST_QNAME));
+
+        /**
+         * Writes empty list node to /test/outer-list
+         */
+        modificationTree.write(OUTER_LIST_PATH, ImmutableNodes.mapNodeBuilder(OUTER_LIST_QNAME).build());
+
+        /**
+         * Reads list node from /test/outer-list
+         */
+        Optional<NormalizedNode<?, ?>> potentialOuterList = modificationTree.read(OUTER_LIST_PATH);
+        assertTrue(potentialOuterList.isPresent());
+
+        /**
+         * Reads container node from /test and verifies that it contains test
+         * node
+         */
+        Optional<NormalizedNode<?, ?>> potentialTest = modificationTree.read(TEST_PATH);
+        ContainerNode containerTest = assertPresentAndType(potentialTest, ContainerNode.class);
+
+        /**
+         *
+         * Gets list from returned snapshot of /test and verifies it contains
+         * outer-list
+         *
+         */
+        assertPresentAndType(containerTest.getChild(new NodeIdentifier(OUTER_LIST_QNAME)), MapNode.class);
+
+    }
+
+    @Test
+    public void writeSubtreeReadChildren() {
+        MutableDataTree modificationTree = createEmptyModificationTree();
+        modificationTree.write(TEST_PATH, createTestContainer());
+        Optional<NormalizedNode<?, ?>> potential = modificationTree.read(TWO_TWO_PATH);
+        MapEntryNode node = assertPresentAndType(potential, MapEntryNode.class);
+    }
+
+    @Test
+    public void writeSubtreeDeleteChildren() {
+        MutableDataTree modificationTree = createEmptyModificationTree();
+        modificationTree.write(TEST_PATH, createTestContainer());
+
+        // We verify data are present
+        Optional<NormalizedNode<?, ?>> potentialBeforeDelete = modificationTree.read(TWO_TWO_PATH);
+        MapEntryNode node = assertPresentAndType(potentialBeforeDelete, MapEntryNode.class);
+
+        modificationTree.delete(TWO_TWO_PATH);
+        Optional<NormalizedNode<?, ?>> potentialAfterDelete = modificationTree.read(TWO_TWO_PATH);
+        assertFalse(potentialAfterDelete.isPresent());
+
+    }
+
+    private static <T> T assertPresentAndType(final Optional<?> potential, final Class<T> type) {
+        assertNotNull(potential);
+        assertTrue(potential.isPresent());
+        assertTrue(type.isInstance(potential.get()));
+        return type.cast(potential.get());
+    }
+
+}
diff --git a/opendaylight/md-sal/sal-dom-broker/src/test/java/org/opendaylight/controller/md/sal/dom/store/impl/TestModel.java b/opendaylight/md-sal/sal-dom-broker/src/test/java/org/opendaylight/controller/md/sal/dom/store/impl/TestModel.java
new file mode 100644 (file)
index 0000000..cab7e57
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2014 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.controller.md.sal.dom.store.impl;
+
+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.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.parser.impl.YangParserImpl;
+
+public class TestModel {
+
+    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 InstanceIdentifier TEST_PATH = InstanceIdentifier.of(TEST_QNAME);
+    public static final InstanceIdentifier OUTER_LIST_PATH = InstanceIdentifier.builder(TEST_PATH).node(OUTER_LIST_QNAME).build();
+
+
+    public static final InputStream getDatastoreTestInputStream() {
+        return getInputStream(DATASTORE_TEST_YANG);
+    }
+
+    private static InputStream getInputStream(final String resourceName) {
+        return TestModel.class.getResourceAsStream(DATASTORE_TEST_YANG);
+    }
+
+    public static SchemaContext createTestContext() {
+        YangParserImpl parser = new YangParserImpl();
+        Set<Module> modules = parser.parseYangModelsFromStreams(Collections.singletonList(getDatastoreTestInputStream()));
+        return parser.resolveSchemaContext(modules);
+    }
+}
diff --git a/opendaylight/md-sal/sal-dom-broker/src/test/resources/odl-datastore-test.yang b/opendaylight/md-sal/sal-dom-broker/src/test/resources/odl-datastore-test.yang
new file mode 100644 (file)
index 0000000..17541fe
--- /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 uint16;
+            }
+            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 string;
+                }
+                leaf value {
+                    type string;
+                }
+            }
+        }
+    }
+}
\ No newline at end of file