</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>
--- /dev/null
+/*
+ * 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));
+ }
+
+ }
+}
--- /dev/null
+/*
+ * 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);
+
+}
--- /dev/null
+/*
+ * 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.");
+ }
+}
--- /dev/null
+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) {
+
+ }
+
+}
--- /dev/null
+/*
+ * 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 + "]";
+ }
+
+}
--- /dev/null
+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());
+ }
+
+}
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /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 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