<opendaylight.l2.types.version>2013.08.27.4-SNAPSHOT</opendaylight.l2.types.version>
<yang.ext.version>2013.09.07.4-SNAPSHOT</yang.ext.version>
<maven.javadoc.version>2.9.1</maven.javadoc.version>
+ <jsr305.version>2.0.1</jsr305.version>
+
+
</properties>
<dependencyManagement>
<description>${project.artifactId}</description>
<dependencies>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>util</artifactId>
+ </dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>yang-common</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.yangtools.yang.data.api.schema.tree;
+
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+
+/**
+ * Exception thrown when a proposed change fails validation before being
+ * applied into the Data Tree because the Data Tree has been modified
+ * in way that a conflicting
+ * node is present.
+ */
+public class ConflictingModificationAppliedException extends DataValidationFailedException {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 1L;
+
+ public ConflictingModificationAppliedException(final InstanceIdentifier path, final String message, final Throwable cause) {
+ super(path, message, cause);
+ }
+
+ public ConflictingModificationAppliedException(final InstanceIdentifier path, final String message) {
+ super(path, message);
+ }
+
+}
--- /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.yangtools.yang.data.api.schema.tree;
+
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+/**
+ * Interface representing a data tree which can be modified in an MVCC fashion.
+ */
+public interface DataTree {
+ /**
+ * Take a read-only point-in-time snapshot of the tree.
+ *
+ * @return Data tree snapshot.
+ */
+ DataTreeSnapshot takeSnapshot();
+
+ /**
+ * Make the data tree use a new schema context. The context will be used
+ * only by subsequent operations.
+ *
+ * @param newSchemaContext new SchemaContext
+ * @throws IllegalArgumentException if the new context is incompatible
+ */
+ void setSchemaContext(SchemaContext newSchemaContext);
+
+ /**
+ * Validate whether a particular modification can be applied to the data tree.
+ */
+ void validate(DataTreeModification modification) throws DataValidationFailedException;
+
+ /**
+ * Prepare a modification for commit.
+ *
+ * @param modification
+ * @return candidate data tree
+ */
+ DataTreeCandidate prepare(DataTreeModification modification);
+
+ /**
+ * Commit a data tree candidate.
+ *
+ * @param candidate data tree candidate
+ */
+ void commit(DataTreeCandidate candidate);
+}
--- /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.yangtools.yang.data.api.schema.tree;
+
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+
+/**
+ * An encapsulation of a validated data tree modification. This candidate
+ * is ready for atomic commit to the datastore. It allows access to before-
+ * and after-state as it will be seen in to subsequent commit. This capture
+ * can be accessed for reference, but cannot be modified and the content
+ * is limited to nodes which were affected by the modification from which
+ * this instance originated.
+ */
+public interface DataTreeCandidate {
+ /**
+ * Get the candidate tree root node.
+ *
+ * @return Candidate tree root node
+ */
+ DataTreeCandidateNode getRootNode();
+
+ /**
+ * Get the candidate tree root path. This is the path of the root node
+ * relative to the root of InstanceIdentifier namespace.
+ *
+ * @return Relative path of the root node
+ */
+ InstanceIdentifier getRootPath();
+}
--- /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.yangtools.yang.data.api.schema.tree;
+
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+import com.google.common.base.Optional;
+
+/**
+ * A single node within a {@link DataTreeCandidate}. The nodes are organized
+ * in tree hierarchy, reflecting the modification from which this candidate
+ * was created. The node itself exposes the before- and after-image of the
+ * tree restricted to the modified nodes.
+ */
+public interface DataTreeCandidateNode {
+ /**
+ * Get the node identifier.
+ *
+ * @return The node identifier.
+ */
+ PathArgument getIdentifier();
+
+ /**
+ * Get an unmodifiable iterable of modified child nodes.
+ *
+ * @return Unmodifiable iterable of modified child nodes.
+ */
+ Iterable<DataTreeCandidateNode> getChildNodes();
+
+ /**
+ * Return the type of modification this node is undergoing.
+ *
+ * @return Node modification type.
+ */
+ ModificationType getModificationType();
+
+ /**
+ * Return the before-image of data corresponding to the node.
+ *
+ * @return Node data as they were present in the tree before
+ * the modification was applied.
+ */
+ Optional<NormalizedNode<?, ?>> getDataAfter();
+
+ /**
+ * Return the after-image of data corresponding to the node.
+ *
+ * @return Node data as they will be present in the tree after
+ * the modification is applied.
+ */
+ Optional<NormalizedNode<?, ?>> getDataBefore();
+}
--- /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.yangtools.yang.data.api.schema.tree;
+
+/**
+ * Factory interface for creating data trees.
+ */
+public interface DataTreeFactory {
+ /**
+ * Create a new data tree.
+ *
+ * @return A data tree instance.
+ */
+ DataTree create();
+}
--- /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.yangtools.yang.data.api.schema.tree;
+
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+/**
+ * Class encapsulation of set of modifications to a base tree. This tree is backed
+ * by a read-only snapshot and tracks modifications on top of that. The modification
+ * has the ability to rebase itself to a new snapshot.
+ */
+public interface DataTreeModification extends DataTreeSnapshot {
+ /**
+ * Delete the node at specified path.
+ *
+ * @param path Node path
+ */
+ void delete(InstanceIdentifier path);
+
+ /**
+ * Merge the specified data with the currently-present data
+ * at specified path.
+ *
+ * @param path Node path
+ * @param data Data to be merged
+ */
+ void merge(InstanceIdentifier path, NormalizedNode<?, ?> data);
+
+ /**
+ * Replace the data at specified path with supplied data.
+ *
+ * @param path Node path
+ * @param data New node data
+ */
+ void write(InstanceIdentifier path, NormalizedNode<?, ?> data);
+
+ /**
+ * Finish creation of a modification, making it ready for application
+ * to the data tree. Any calls to this object's methods will result
+ * in undefined behavior, possibly with an
+ * {@link IllegalStateException} being thrown.
+ */
+ void ready();
+}
--- /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.yangtools.yang.data.api.schema.tree;
+
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+import com.google.common.base.Optional;
+
+/**
+ * Read-only snapshot of a {@link DataTree}. The snapshot is stable and isolated,
+ * e.g. data tree changes occurring after the snapshot has been taken are not
+ * visible through the snapshot.
+ */
+public interface DataTreeSnapshot {
+ /**
+ * Read a particular node from the snapshot.
+ *
+ * @param path Path of the node
+ * @return Optional result encapsulating the presence and value of the node
+ */
+ Optional<NormalizedNode<?, ?>> readNode(InstanceIdentifier path);
+
+ /**
+ * Create a new data tree modification based on this snapshot, using the
+ * specified data application strategy.
+ *
+ * @param strategy data modification strategy
+ * @return A new data tree modification
+ */
+ DataTreeModification newModification();
+}
--- /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.yangtools.yang.data.api.schema.tree;
+
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+
+import com.google.common.base.Preconditions;
+
+/**
+ * Exception thrown when a proposed change fails validation before being
+ * applied into the datastore. This can have multiple reasons, for example
+ * the datastore has been concurrently modified such that a conflicting
+ * node is present, or the modification is structurally incorrect.
+ */
+public class DataValidationFailedException extends Exception {
+ private static final long serialVersionUID = 1L;
+ private final InstanceIdentifier path;
+
+ /**
+ * Create a new instance.
+ *
+ * @param path Object path which caused this exception
+ * @param message Specific message describing the failure
+ */
+ public DataValidationFailedException(final InstanceIdentifier path, final String message) {
+ this(path, message, null);
+ }
+ /**
+ * Create a new instance, initializing
+ *
+ * @param path Object path which caused this exception
+ * @param message Specific message describing the failure
+ * @param cause Exception which triggered this failure, may be null
+ */
+ public DataValidationFailedException(final InstanceIdentifier path, final String message, final Throwable cause) {
+ super(message, cause);
+ this.path = Preconditions.checkNotNull(path);
+ }
+
+ /**
+ * Returns the offending object path.
+ *
+ * @return Path of the offending object
+ */
+ public InstanceIdentifier getPath() {
+ return path;
+ }
+}
--- /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.yangtools.yang.data.api.schema.tree;
+
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+
+/**
+ * Exception thrown when a proposed change fails validation before being
+ * applied into the datastore because of incorrect structure of user supplied
+ * data.
+ *
+ */
+public class IncorrectDataStructureException extends DataValidationFailedException {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 1L;
+
+ public IncorrectDataStructureException(final InstanceIdentifier path, final String message, final Throwable cause) {
+ super(path, message, cause);
+ }
+
+ public IncorrectDataStructureException(final InstanceIdentifier path, final String message) {
+ super(path, message);
+ }
+
+}
--- /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.yangtools.yang.data.api.schema.tree;
+
+/**
+ * Enumeration of all possible node modification states. These are used in
+ * data tree modification context to quickly assess what sort of modification
+ * the node is undergoing.
+ */
+public enum ModificationType {
+ /**
+ * Node is currently unmodified.
+ */
+ UNMODIFIED,
+
+ /**
+ * A child node, either direct or indirect, has been modified. This means
+ * that the data representation of this node has potentially changed.
+ */
+ SUBTREE_MODIFIED,
+
+ /**
+ * This node has been placed into the tree, potentially completely replacing
+ * pre-existing contents.
+ */
+ WRITE,
+
+ /**
+ * This node has been deleted along with any of its child nodes.
+ */
+ DELETE,
+
+ /**
+ * Node has been written into the tree, but instead of replacing pre-existing
+ * contents, it has been merged. This means that any incoming nodes which
+ * were present in the tree have been replaced, but their child nodes have
+ * been retained.
+ */
+ MERGE,
+}
--- /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.yangtools.yang.data.api.schema.tree;
+
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
+
+import com.google.common.base.Optional;
+
+/**
+ * A tree node which has references to its child leaves. This are typically
+ * internal non-data leaves, such as containers, lists, etc.
+ *
+ * @param <C> Final node type
+ */
+public interface StoreTreeNode<C extends StoreTreeNode<C>> {
+
+ /**
+ * Returns a direct child of the node
+ *
+ * @param child Identifier of child
+ * @return Optional with node if the child is existing, {@link Optional#absent()} otherwise.
+ */
+ Optional<C> 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.yangtools.yang.data.api.schema.tree.spi;
+
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+import com.google.common.base.Preconditions;
+
+/**
+ * A very basic data tree node.
+ */
+abstract class AbstractTreeNode implements TreeNode {
+ private final NormalizedNode<?, ?> data;
+ private final Version version;
+
+ protected AbstractTreeNode(final NormalizedNode<?, ?> data, final Version version) {
+ this.data = Preconditions.checkNotNull(data);
+ this.version = Preconditions.checkNotNull(version);
+ }
+
+ @Override
+ public PathArgument getIdentifier() {
+ return data.getIdentifier();
+ }
+
+ @Override
+ public final Version getVersion() {
+ return version;
+ }
+
+ @Override
+ public final NormalizedNode<?, ?> getData() {
+ return data;
+ }
+}
--- /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.yangtools.yang.data.api.schema.tree.spi;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.opendaylight.yangtools.util.MapAdaptor;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
+import org.opendaylight.yangtools.yang.data.api.schema.OrderedNodeContainer;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+
+/**
+ * A TreeNode capable of holding child nodes. The fact that any of the children
+ * changed is tracked by the subtree version.
+ */
+final class ContainerNode extends AbstractTreeNode {
+ private final Map<PathArgument, TreeNode> children;
+ private final Version subtreeVersion;
+
+ protected ContainerNode(final NormalizedNode<?, ?> data, final Version version,
+ final Map<PathArgument, TreeNode> children, final Version subtreeVersion) {
+ super(data, version);
+ this.children = Preconditions.checkNotNull(children);
+ this.subtreeVersion = Preconditions.checkNotNull(subtreeVersion);
+ }
+
+ @Override
+ public Version getSubtreeVersion() {
+ return subtreeVersion;
+ }
+
+ @Override
+ public Optional<TreeNode> getChild(final PathArgument key) {
+ return Optional.fromNullable(children.get(key));
+ }
+
+ @Override
+ public MutableTreeNode mutable() {
+ return new Mutable(this);
+ }
+
+ private static final class Mutable implements MutableTreeNode {
+ private final Version version;
+ private Map<PathArgument, TreeNode> children;
+ private NormalizedNode<?, ?> data;
+ private Version subtreeVersion;
+
+ private Mutable(final ContainerNode parent) {
+ this.data = parent.getData();
+ this.children = MapAdaptor.getDefaultInstance().takeSnapshot(parent.children);
+ this.subtreeVersion = parent.getSubtreeVersion();
+ this.version = parent.getVersion();
+ }
+
+ @Override
+ public Optional<TreeNode> getChild(final PathArgument child) {
+ return Optional.fromNullable(children.get(child));
+ }
+
+ @Override
+ public void setSubtreeVersion(final Version subtreeVersion) {
+ this.subtreeVersion = Preconditions.checkNotNull(subtreeVersion);
+ }
+
+ @Override
+ public void addChild(final TreeNode child) {
+ children.put(child.getIdentifier(), child);
+ }
+
+ @Override
+ public void removeChild(final PathArgument id) {
+ children.remove(id);
+ }
+
+ @Override
+ public TreeNode seal() {
+ final TreeNode ret = new ContainerNode(data, version, MapAdaptor.getDefaultInstance().optimize(children), subtreeVersion);
+
+ // This forces a NPE if this class is accessed again. Better than corruption.
+ children = null;
+ return ret;
+ }
+
+ @Override
+ public void setData(final NormalizedNode<?, ?> data) {
+ this.data = Preconditions.checkNotNull(data);
+ }
+ }
+
+ private static ContainerNode create(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));
+ }
+
+ return new ContainerNode(data, version, map, version);
+ }
+
+ public static ContainerNode create(final Version version, final NormalizedNodeContainer<?, ?, NormalizedNode<?, ?>> container) {
+ return create(version, container, container.getValue());
+ }
+
+ public static ContainerNode create(final Version version, final OrderedNodeContainer<NormalizedNode<?, ?>> container) {
+ return create(version, container, container.getValue());
+ }
+}
--- /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.yangtools.yang.data.api.schema.tree.spi;
+
+
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.StoreTreeNode;
+
+/**
+ * A mutable tree node. This is a transient view materialized from a pre-existing
+ * node. Modifications are isolated. Once this object is {@link #seal()}-ed,
+ * any interactions with it will result in undefined behavior.
+ */
+public interface MutableTreeNode extends StoreTreeNode<TreeNode> {
+ /**
+ * Set the data component of the node.
+ *
+ * @param data New data component, may not be null.
+ */
+ void setData(NormalizedNode<?, ?> data);
+
+ /**
+ * Set the new subtree version. This is typically invoked when the user
+ * has modified some of this node's children.
+ *
+ * @param subtreeVersion New subtree version.
+ */
+ void setSubtreeVersion(Version subtreeVersion);
+
+ /**
+ * Add a new child node. This acts as add-or-replace operation, e.g. it
+ * succeeds even if a conflicting child is already present.
+ *
+ * @param child New child node.
+ */
+ void addChild(TreeNode child);
+
+ /**
+ * Remove a child node. This acts as delete-or-nothing operation, e.g. it
+ * succeeds even if the corresponding child is not present.
+ *
+ * @param id Child identificator.
+ */
+ void removeChild(PathArgument id);
+
+ /**
+ * Finish node modification and return a read-only view of this node. After
+ * this method is invoked, any further calls to this object's method result
+ * in undefined behavior.
+ *
+ * @return Read-only view of this node.
+ */
+ TreeNode seal();
+}
--- /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.yangtools.yang.data.api.schema.tree.spi;
+
+
+import org.opendaylight.yangtools.concepts.Identifiable;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.StoreTreeNode;
+
+/**
+ * A very basic data tree node. It has a version (when it was last modified),
+ * a subtree version (when any of its children were modified) and some read-only
+ * data.
+ */
+public interface TreeNode extends Identifiable<PathArgument>, StoreTreeNode<TreeNode> {
+ /**
+ * Get the data node version.
+ *
+ * @return Current data node version.
+ */
+ Version getVersion();
+
+ /**
+ * Get the subtree version.
+ *
+ * @return Current subtree version.
+ */
+ Version getSubtreeVersion();
+
+ /**
+ * Get a read-only view of the underlying data.
+ *
+ * @return Unmodifiable view of the underlying data.
+ */
+ NormalizedNode<?, ?> getData();
+
+ /**
+ * Get a mutable, isolated copy of the node.
+ *
+ * @return Mutable copy
+ */
+ MutableTreeNode mutable();
+}
--- /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.yangtools.yang.data.api.schema.tree.spi;
+
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
+import org.opendaylight.yangtools.yang.data.api.schema.OrderedNodeContainer;
+
+/**
+ * Public entrypoint for other packages. Allows instantiating a tree node
+ * with specified version.
+ */
+public final class TreeNodeFactory {
+ private TreeNodeFactory() {
+ throw new UnsupportedOperationException("Utility class should not be instantiated");
+ }
+
+ /**
+ * Create a new AbstractTreeNode from a data node, descending recursively as needed.
+ * This method should only ever be used for new data.
+ *
+ * @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.create(version, container);
+
+ }
+ if (data instanceof OrderedNodeContainer<?>) {
+ @SuppressWarnings("unchecked")
+ OrderedNodeContainer<NormalizedNode<?, ?>> container = (OrderedNodeContainer<NormalizedNode<?, ?>>) data;
+ return ContainerNode.create(version, container);
+ }
+
+ return new ValueNode(data, version);
+ }
+}
--- /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.yangtools.yang.data.api.schema.tree.spi;
+
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Optional;import java.lang.Override;import java.lang.String;import java.lang.UnsupportedOperationException;
+
+/**
+ * Concretization of AbstractTreeNode for leaf nodes which only contain data.
+ * Instances of this class report all children as absent, subtree version
+ * equal to this node's version and do not support mutable view.
+ */
+final class ValueNode extends AbstractTreeNode {
+ private static final Logger LOG = LoggerFactory.getLogger(ValueNode.class);
+
+ protected ValueNode(final NormalizedNode<?, ?> data, final Version version) {
+ super(data, version);
+ }
+
+ @Override
+ public Optional<TreeNode> getChild(final PathArgument childId) {
+ LOG.warn("Attempted to access child {} of value-node {}", childId, this);
+ return Optional.absent();
+ }
+
+ @Override
+ public Version getSubtreeVersion() {
+ return getVersion();
+ }
+
+ @Override
+ public MutableTreeNode mutable() {
+ /**
+ * Value nodes can only we read/written/delete, which does a straight
+ * replace. That means they don't haver need to be made mutable.
+ */
+ throw new UnsupportedOperationException(String.format("Attempted to mutate value-node %s", this));
+ }
+}
--- /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.yangtools.yang.data.api.schema.tree.spi;
+
+/**
+ * The concept of a version, either node version, or a subtree version. The
+ * only interface contract this class has is that no two versions are the
+ * same.
+ */
+public final class Version {
+ private Version() {
+
+ }
+
+ /**
+ * Create a new version, distinct from any other version.
+ *
+ * @return a new version.
+ */
+ public Version next() {
+ return new Version();
+ }
+
+ /**
+ * Create an initial version.
+ *
+ * @return a new version.
+ */
+ public static final Version initial() {
+ return new Version();
+ }
+}
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
</dependency>
+ <dependency>
+ <groupId>com.google.code.findbugs</groupId>
+ <artifactId>jsr305</artifactId>
+ <version>2.0.1</version>
+ </dependency>
+
</dependencies>
</project>
--- /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.yangtools.yang.data.impl.schema.tree;
+
+
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
+
+import com.google.common.base.Preconditions;
+
+abstract class AbstractDataTreeCandidate implements DataTreeCandidate {
+ private final InstanceIdentifier rootPath;
+
+ protected AbstractDataTreeCandidate(final InstanceIdentifier rootPath) {
+ this.rootPath = Preconditions.checkNotNull(rootPath);
+ }
+
+ @Override
+ public final InstanceIdentifier getRootPath() {
+ return rootPath;
+ }
+
+}
--- /dev/null
+package org.opendaylight.yangtools.yang.data.impl.schema.tree;
+
+
+import com.google.common.base.Optional;
+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.tree.spi.TreeNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.Version;
+
+/**
+ * An implementation of apply operation which fails to do anything,
+ * consistently. An instance of this class is used by the data tree
+ * if it does not have a SchemaContext attached and hence cannot
+ * perform anything meaningful.
+ */
+final class AlwaysFailOperation implements ModificationApplyOperation {
+ @Override
+ public Optional<TreeNode> apply(final ModifiedNode modification,
+ final Optional<TreeNode> storeMeta, final Version version) {
+ throw new IllegalStateException("Schema Context is not available.");
+ }
+
+ @Override
+ public void checkApplicable(final InstanceIdentifier path,final NodeModification modification, final Optional<TreeNode> storeMetadata) {
+ throw new IllegalStateException("Schema Context is not available.");
+ }
+
+ @Override
+ public Optional<ModificationApplyOperation> getChild(final PathArgument child) {
+ throw new IllegalStateException("Schema Context is not available.");
+ }
+
+ @Override
+ public void verifyStructure(final ModifiedNode modification) {
+ throw new IllegalStateException("Schema Context is not available.");
+ }
+}
\ No newline at end of file
--- /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.yangtools.yang.data.impl.schema.tree;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.AugmentationIdentifier;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode;
+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.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListEntryNode;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableAugmentationNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableMapEntryNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableUnkeyedListEntryNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.transform.base.AugmentationSchemaProxy;
+import org.opendaylight.yangtools.yang.model.api.AugmentationSchema;
+import org.opendaylight.yangtools.yang.model.api.AugmentationTarget;
+import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
+
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+
+/**
+ * Base strategy for applying changes to a ContainerNode, irrespective of its
+ * actual type.
+ *
+ * @param <T> Type of the container node
+ */
+abstract class DataNodeContainerModificationStrategy<T extends DataNodeContainer> extends NormalizedNodeContainerModificationStrategy {
+
+ private final T schema;
+ private final LoadingCache<PathArgument, ModificationApplyOperation> childCache = CacheBuilder.newBuilder()
+ .build(CacheLoader.from(new Function<PathArgument, ModificationApplyOperation>() {
+
+ @Override
+ public ModificationApplyOperation apply(final PathArgument identifier) {
+ if (identifier instanceof AugmentationIdentifier && schema instanceof AugmentationTarget) {
+ return SchemaAwareApplyOperation.from(schema, (AugmentationTarget) schema, (AugmentationIdentifier) identifier);
+ }
+
+ DataSchemaNode child = schema.getDataChildByName(identifier.getNodeType());
+ if (child == null) {
+ return null;
+ }
+ return SchemaAwareApplyOperation.from(child);
+ }
+ }));
+
+ protected DataNodeContainerModificationStrategy(final T schema,
+ final Class<? extends NormalizedNode<?, ?>> nodeClass) {
+ super(nodeClass);
+ this.schema = schema;
+ }
+
+ protected T getSchema() {
+ return schema;
+ }
+
+ @Override
+ public Optional<ModificationApplyOperation> getChild(final PathArgument identifier) {
+ try {
+ return Optional.<ModificationApplyOperation> fromNullable(childCache.get(identifier));
+ } catch (ExecutionException e) {
+ return Optional.absent();
+ }
+ }
+
+ @Override
+ @SuppressWarnings("rawtypes")
+ protected abstract DataContainerNodeBuilder createBuilder(NormalizedNode<?, ?> original);
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + " [" + schema + "]";
+ }
+
+ public static class AugmentationModificationStrategy extends DataNodeContainerModificationStrategy<AugmentationSchema> {
+
+ protected AugmentationModificationStrategy(final AugmentationSchema schema, final DataNodeContainer resolved) {
+ super(createAugmentProxy(schema,resolved), AugmentationNode.class);
+ }
+
+ @Override
+ @SuppressWarnings("rawtypes")
+ protected DataContainerNodeBuilder createBuilder(final NormalizedNode<?, ?> original) {
+ checkArgument(original instanceof AugmentationNode);
+ return ImmutableAugmentationNodeBuilder.create((AugmentationNode) original);
+ }
+
+
+ private static AugmentationSchema createAugmentProxy(final AugmentationSchema schema, final DataNodeContainer resolved) {
+ Set<DataSchemaNode> realChildSchemas = new HashSet<>();
+ for(DataSchemaNode augChild : schema.getChildNodes()) {
+ realChildSchemas.add(resolved.getDataChildByName(augChild.getQName()));
+ }
+ return new AugmentationSchemaProxy(schema, realChildSchemas);
+ }
+ }
+
+ public static class ContainerModificationStrategy extends DataNodeContainerModificationStrategy<ContainerSchemaNode> {
+
+ public ContainerModificationStrategy(final ContainerSchemaNode schemaNode) {
+ super(schemaNode, ContainerNode.class);
+ }
+
+ @Override
+ @SuppressWarnings("rawtypes")
+ protected DataContainerNodeBuilder createBuilder(final NormalizedNode<?, ?> original) {
+ checkArgument(original instanceof ContainerNode);
+ return ImmutableContainerNodeBuilder.create((ContainerNode) original);
+ }
+ }
+
+ public static class ListEntryModificationStrategy extends DataNodeContainerModificationStrategy<ListSchemaNode> {
+
+ protected ListEntryModificationStrategy(final ListSchemaNode schema) {
+ super(schema, MapEntryNode.class);
+ }
+
+ @Override
+ @SuppressWarnings("rawtypes")
+ protected final DataContainerNodeBuilder createBuilder(final NormalizedNode<?, ?> original) {
+ checkArgument(original instanceof MapEntryNode);
+ return ImmutableMapEntryNodeBuilder.create((MapEntryNode) original);
+ }
+ }
+
+ public static class UnkeyedListItemModificationStrategy extends DataNodeContainerModificationStrategy<ListSchemaNode> {
+
+ public UnkeyedListItemModificationStrategy(final ListSchemaNode schemaNode) {
+ super(schemaNode, UnkeyedListEntryNode.class);
+ }
+
+ @Override
+ @SuppressWarnings("rawtypes")
+ protected DataContainerNodeBuilder createBuilder(final NormalizedNode<?, ?> original) {
+ checkArgument(original instanceof UnkeyedListEntryNode);
+ return ImmutableUnkeyedListEntryNodeBuilder.create((UnkeyedListEntryNode) original);
+ }
+ }
+}
\ No newline at end of file
--- /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.yangtools.yang.data.impl.schema.tree;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTree;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModification;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.TreeNode;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+/**
+ * Read-only snapshot of the data tree.
+ */
+final class InMemoryDataTree implements DataTree {
+ private static final Logger LOG = LoggerFactory.getLogger(InMemoryDataTree.class);
+ private static final InstanceIdentifier PUBLIC_ROOT_PATH = InstanceIdentifier.builder().build();
+
+ private final ReadWriteLock rwLock = new ReentrantReadWriteLock(true);
+ private ModificationApplyOperation applyOper = new AlwaysFailOperation();
+ private SchemaContext currentSchemaContext;
+ private TreeNode rootNode;
+
+ public InMemoryDataTree(final TreeNode rootNode, final SchemaContext schemaContext) {
+ this.rootNode = Preconditions.checkNotNull(rootNode);
+
+ if (schemaContext != null) {
+ // Also sets applyOper
+ setSchemaContext(schemaContext);
+ }
+ }
+
+ @Override
+ public synchronized void setSchemaContext(final SchemaContext newSchemaContext) {
+ Preconditions.checkNotNull(newSchemaContext);
+
+ LOG.info("Attepting to install schema context {}", newSchemaContext);
+
+ /*
+ * FIXME: we should walk the schema contexts, both current and new and see
+ * whether they are compatible here. Reject incompatible changes.
+ */
+
+ // Instantiate new apply operation, this still may fail
+ final ModificationApplyOperation newApplyOper = SchemaAwareApplyOperation.from(newSchemaContext);
+
+ // Ready to change the context now, make sure no operations are running
+ rwLock.writeLock().lock();
+ try {
+ this.applyOper = newApplyOper;
+ this.currentSchemaContext = newSchemaContext;
+ } finally {
+ rwLock.writeLock().unlock();
+ }
+ }
+
+ @Override
+ public InMemoryDataTreeSnapshot takeSnapshot() {
+ rwLock.readLock().lock();
+ try {
+ return new InMemoryDataTreeSnapshot(currentSchemaContext, rootNode, applyOper);
+ } finally {
+ rwLock.readLock().unlock();
+ }
+ }
+
+ @Override
+ public void validate(final DataTreeModification modification) throws DataValidationFailedException {
+ Preconditions.checkArgument(modification instanceof InMemoryDataTreeModification, "Invalid modification class %s", modification.getClass());
+
+ final InMemoryDataTreeModification m = (InMemoryDataTreeModification)modification;
+ m.getStrategy().checkApplicable(PUBLIC_ROOT_PATH, m.getRootModification(), Optional.<TreeNode>of(rootNode));
+ }
+
+ @Override
+ public synchronized DataTreeCandidate prepare(final DataTreeModification modification) {
+ Preconditions.checkArgument(modification instanceof InMemoryDataTreeModification, "Invalid modification class %s", modification.getClass());
+
+ final InMemoryDataTreeModification m = (InMemoryDataTreeModification)modification;
+ final ModifiedNode root = m.getRootModification();
+
+ if (root.getType() == ModificationType.UNMODIFIED) {
+ return new NoopDataTreeCandidate(PUBLIC_ROOT_PATH, root);
+ }
+
+ rwLock.writeLock().lock();
+ try {
+ final Optional<TreeNode> newRoot = m.getStrategy().apply(m.getRootModification(),
+ Optional.<TreeNode>of(rootNode), rootNode.getSubtreeVersion().next());
+ Preconditions.checkState(newRoot.isPresent(), "Apply strategy failed to produce root node");
+ return new InMemoryDataTreeCandidate(PUBLIC_ROOT_PATH, root, rootNode, newRoot.get());
+ } finally {
+ rwLock.writeLock().unlock();
+ }
+ }
+
+ @Override
+ public synchronized void commit(final DataTreeCandidate candidate) {
+ if (candidate instanceof NoopDataTreeCandidate) {
+ return;
+ }
+
+ Preconditions.checkArgument(candidate instanceof InMemoryDataTreeCandidate, "Invalid candidate class %s", candidate.getClass());
+ final InMemoryDataTreeCandidate c = (InMemoryDataTreeCandidate)candidate;
+
+ LOG.debug("Updating datastore from {} to {}", rootNode, c.getAfterRoot());
+
+ if (LOG.isTraceEnabled()) {
+ LOG.trace("Data Tree is {}", StoreUtils.toStringTree(c.getAfterRoot().getData()));
+ }
+
+ // Ready to change the context now, make sure no operations are running
+ rwLock.writeLock().lock();
+ try {
+ Preconditions.checkState(c.getBeforeRoot() == rootNode,
+ String.format("Store tree %s and candidate base %s differ.", rootNode, c.getBeforeRoot()));
+ this.rootNode = c.getAfterRoot();
+ } finally {
+ rwLock.writeLock().unlock();
+ }
+ }
+}
--- /dev/null
+package org.opendaylight.yangtools.yang.data.impl.schema.tree;
+
+
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Iterables;
+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.api.schema.tree.DataTreeCandidateNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.TreeNode;
+
+final class InMemoryDataTreeCandidate extends AbstractDataTreeCandidate {
+ private static abstract class AbstractNode implements DataTreeCandidateNode {
+ private final ModifiedNode mod;
+ private final TreeNode newMeta;
+ private final TreeNode oldMeta;
+
+ protected AbstractNode(final ModifiedNode mod,
+ final TreeNode oldMeta, final TreeNode newMeta) {
+ this.newMeta = newMeta;
+ this.oldMeta = oldMeta;
+ this.mod = Preconditions.checkNotNull(mod);
+ }
+
+ protected final ModifiedNode getMod() {
+ return mod;
+ }
+
+ protected final TreeNode getNewMeta() {
+ return newMeta;
+ }
+
+ protected final TreeNode getOldMeta() {
+ return oldMeta;
+ }
+
+ private static final TreeNode childMeta(final TreeNode parent, final PathArgument id) {
+ if (parent != null) {
+ return parent.getChild(id).orNull();
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public Iterable<DataTreeCandidateNode> getChildNodes() {
+ return Iterables.transform(mod.getChildren(), new Function<ModifiedNode, DataTreeCandidateNode>() {
+ @Override
+ public DataTreeCandidateNode apply(final ModifiedNode input) {
+ final PathArgument id = input.getIdentifier();
+ return new ChildNode(input, childMeta(oldMeta, id), childMeta(newMeta, id));
+ }
+ });
+ }
+
+ @Override
+ public ModificationType getModificationType() {
+ return mod.getType();
+ }
+
+ private Optional<NormalizedNode<?, ?>> optionalData(final TreeNode meta) {
+ if (meta != null) {
+ return Optional.<NormalizedNode<?,?>>of(meta.getData());
+ } else {
+ return Optional.absent();
+ }
+ }
+
+ @Override
+ public Optional<NormalizedNode<?, ?>> getDataAfter() {
+ return optionalData(newMeta);
+ }
+
+ @Override
+ public Optional<NormalizedNode<?, ?>> getDataBefore() {
+ return optionalData(oldMeta);
+ }
+ }
+
+ private static final class ChildNode extends AbstractNode {
+ public ChildNode(final ModifiedNode mod, final TreeNode oldMeta, final TreeNode newMeta) {
+ super(mod, oldMeta, newMeta);
+ }
+
+ @Override
+ public PathArgument getIdentifier() {
+ return getMod().getIdentifier();
+ }
+ }
+
+ private static final class RootNode extends AbstractNode {
+ public RootNode(final ModifiedNode mod, final TreeNode oldMeta, final TreeNode newMeta) {
+ super(mod, oldMeta, newMeta);
+ }
+
+ @Override
+ public PathArgument getIdentifier() {
+ throw new IllegalStateException("Attempted to get identifier of the root node");
+ }
+ }
+
+ private final RootNode root;
+
+ InMemoryDataTreeCandidate(final InstanceIdentifier rootPath, final ModifiedNode modificationRoot,
+ final TreeNode beforeRoot, final TreeNode afterRoot) {
+ super(rootPath);
+ this.root = new RootNode(modificationRoot, beforeRoot, afterRoot);
+ }
+
+ TreeNode getAfterRoot() {
+ return root.getNewMeta();
+ }
+
+ TreeNode getBeforeRoot() {
+ return root.getOldMeta();
+ }
+
+ @Override
+ public DataTreeCandidateNode getRootNode() {
+ return root;
+ }
+}
--- /dev/null
+package org.opendaylight.yangtools.yang.data.impl.schema.tree;
+
+
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeFactory;
+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.Builders;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+/**
+ * A factory for creating in-memory data trees.
+ */
+public final class InMemoryDataTreeFactory implements DataTreeFactory {
+ private static final InMemoryDataTreeFactory INSTANCE = new InMemoryDataTreeFactory();
+
+ private InMemoryDataTreeFactory() {
+ // Never instantiated externally
+ }
+
+ @Override
+ public InMemoryDataTree create() {
+ final NodeIdentifier root = new NodeIdentifier(SchemaContext.NAME);
+ final NormalizedNode<?, ?> data = Builders.containerBuilder().withNodeIdentifier(root).build();
+
+ return new InMemoryDataTree(TreeNodeFactory.createTreeNode(data, Version.initial()), null);
+ }
+
+ /**
+ * Get an instance of this factory. This method cannot fail.
+ *
+ * @return Data tree factory instance.
+ */
+ public static final InMemoryDataTreeFactory getInstance() {
+ return INSTANCE;
+ }
+}
--- /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.yangtools.yang.data.impl.schema.tree;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+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.api.schema.NormalizedNodeContainer;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModification;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.TreeNode;
+import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.annotation.concurrent.GuardedBy;
+import java.util.Map.Entry;
+
+final class InMemoryDataTreeModification implements DataTreeModification {
+ private static final Logger LOG = LoggerFactory.getLogger(InMemoryDataTreeModification.class);
+ private final ModificationApplyOperation strategyTree;
+ private final InMemoryDataTreeSnapshot snapshot;
+ private final ModifiedNode rootNode;
+
+ @GuardedBy("this")
+ private boolean sealed = false;
+
+ InMemoryDataTreeModification(final InMemoryDataTreeSnapshot snapshot, final ModificationApplyOperation resolver) {
+ this.snapshot = Preconditions.checkNotNull(snapshot);
+ this.strategyTree = Preconditions.checkNotNull(resolver);
+ this.rootNode = ModifiedNode.createUnmodified(snapshot.getRootNode());
+ }
+
+ ModifiedNode getRootModification() {
+ return rootNode;
+ }
+
+ ModificationApplyOperation getStrategy() {
+ return strategyTree;
+ }
+
+ @Override
+ public synchronized void write(final InstanceIdentifier path, final NormalizedNode<?, ?> value) {
+ checkSealed();
+ resolveModificationFor(path).write(value);
+ }
+
+ @Override
+ public synchronized void merge(final InstanceIdentifier path, final NormalizedNode<?, ?> data) {
+ checkSealed();
+ mergeImpl(resolveModificationFor(path),data);
+ }
+
+ private void mergeImpl(final OperationWithModification op,final NormalizedNode<?,?> data) {
+
+ if(data instanceof NormalizedNodeContainer<?,?,?>) {
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ NormalizedNodeContainer<?,?,NormalizedNode<PathArgument, ?>> dataContainer = (NormalizedNodeContainer) data;
+ for(NormalizedNode<PathArgument, ?> child : dataContainer.getValue()) {
+ PathArgument childId = child.getIdentifier();
+ mergeImpl(op.forChild(childId), child);
+ }
+ }
+ op.merge(data);
+ }
+
+ @Override
+ public synchronized void delete(final InstanceIdentifier path) {
+ checkSealed();
+ resolveModificationFor(path).delete();
+ }
+
+ @Override
+ public synchronized Optional<NormalizedNode<?, ?>> readNode(final InstanceIdentifier path) {
+ /*
+ * Walk the tree from the top, looking for the first node between root and
+ * the requested path which has been modified. If no such node exists,
+ * we use the node itself.
+ */
+ final Entry<InstanceIdentifier, ModifiedNode> entry = TreeNodeUtils.findClosestsOrFirstMatch(rootNode, path, ModifiedNode.IS_TERMINAL_PREDICATE);
+ final InstanceIdentifier key = entry.getKey();
+ final ModifiedNode mod = entry.getValue();
+
+ final Optional<TreeNode> result = resolveSnapshot(key, mod);
+ if (result.isPresent()) {
+ NormalizedNode<?, ?> data = result.get().getData();
+ return NormalizedNodeUtils.findNode(key, data, path);
+ } else {
+ return Optional.absent();
+ }
+ }
+
+ private Optional<TreeNode> resolveSnapshot(final InstanceIdentifier path,
+ final ModifiedNode modification) {
+ final Optional<Optional<TreeNode>> potentialSnapshot = modification.getSnapshotCache();
+ if(potentialSnapshot.isPresent()) {
+ return potentialSnapshot.get();
+ }
+
+ try {
+ return resolveModificationStrategy(path).apply(modification, modification.getOriginal(),
+ snapshot.getRootNode().getSubtreeVersion().next());
+ } catch (Exception e) {
+ LOG.error("Could not create snapshot for {}:{}", path,modification,e);
+ throw e;
+ }
+ }
+
+ private ModificationApplyOperation resolveModificationStrategy(final InstanceIdentifier path) {
+ LOG.trace("Resolving modification apply strategy for {}", path);
+ return TreeNodeUtils.findNodeChecked(strategyTree, path);
+ }
+
+ private OperationWithModification resolveModificationFor(final InstanceIdentifier path) {
+ ModifiedNode modification = rootNode;
+ // We ensure strategy is present.
+ ModificationApplyOperation operation = resolveModificationStrategy(path);
+ for (PathArgument pathArg : path.getPath()) {
+ modification = modification.modifyChild(pathArg);
+ }
+ return OperationWithModification.from(operation, modification);
+ }
+
+ @Override
+ public synchronized void ready() {
+ Preconditions.checkState(!sealed, "Attempted to seal an already-sealed Data Tree.");
+ sealed = true;
+ rootNode.seal();
+ }
+
+ @GuardedBy("this")
+ private void checkSealed() {
+ Preconditions.checkState(!sealed, "Data Tree is sealed. No further modifications allowed.");
+ }
+
+ @Override
+ public String toString() {
+ return "MutableDataTree [modification=" + rootNode + "]";
+ }
+
+ @Override
+ public synchronized DataTreeModification newModification() {
+ Preconditions.checkState(sealed, "Attempted to chain on an unsealed modification");
+
+ if(rootNode.getType() == ModificationType.UNMODIFIED) {
+ return snapshot.newModification();
+ }
+
+ /*
+ * FIXME: Add advanced transaction chaining for modification of not rebased
+ * modification.
+ *
+ * Current computation of tempRoot may yeld incorrect subtree versions
+ * if there are multiple concurrent transactions, which may break
+ * versioning preconditions for modification of previously occured write,
+ * directly nested under parent node, since node version is derived from
+ * subtree version.
+ *
+ * For deeper nodes subtree version is derived from their respective metadata
+ * nodes, so this incorrect root subtree version is not affecting us.
+ */
+ TreeNode originalSnapshotRoot = snapshot.getRootNode();
+ Optional<TreeNode> tempRoot = strategyTree.apply(rootNode, Optional.of(originalSnapshotRoot), originalSnapshotRoot.getSubtreeVersion().next());
+
+ InMemoryDataTreeSnapshot tempTree = new InMemoryDataTreeSnapshot(snapshot.getSchemaContext(), tempRoot.get(), strategyTree);
+ return tempTree.newModification();
+ }
+}
--- /dev/null
+package org.opendaylight.yangtools.yang.data.impl.schema.tree;
+
+
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeSnapshot;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.TreeNode;
+import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeUtils;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+final class InMemoryDataTreeSnapshot implements DataTreeSnapshot {
+ private final ModificationApplyOperation applyOper;
+ private final SchemaContext schemaContext;
+ private final TreeNode rootNode;
+
+ InMemoryDataTreeSnapshot(final SchemaContext schemaContext, final TreeNode rootNode,
+ final ModificationApplyOperation applyOper) {
+ this.schemaContext = Preconditions.checkNotNull(schemaContext);
+ this.rootNode = Preconditions.checkNotNull(rootNode);
+ this.applyOper = Preconditions.checkNotNull(applyOper);
+ }
+
+ TreeNode getRootNode() {
+ return rootNode;
+ }
+
+ SchemaContext getSchemaContext() {
+ return schemaContext;
+ }
+
+ @Override
+ public Optional<NormalizedNode<?, ?>> readNode(final InstanceIdentifier path) {
+ return NormalizedNodeUtils.findNode(rootNode.getData(), path);
+ }
+
+ @Override
+ public InMemoryDataTreeModification newModification() {
+ return new InMemoryDataTreeModification(this, applyOper);
+ }
+
+ @Override
+ public String toString() {
+ return rootNode.getSubtreeVersion().toString();
+ }
+
+}
\ No newline at end of file
--- /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.yangtools.yang.data.impl.schema.tree;
+
+import com.google.common.base.Optional;
+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.tree.StoreTreeNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.TreeNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.Version;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException;
+
+/**
+ *
+ * Operation responsible for applying {@link ModifiedNode} 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(ModifiedNode, Optional)} if applicable.
+ *
+ *
+ * Hierarchical composite operation which is responsible for applying
+ * modification on particular subtree and creating updated subtree
+ *
+ *
+ */
+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
+ * @param version New subtree version of parent node
+ * @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 ModifiedNode}
+ * resulted in deletion of this node.
+ */
+ Optional<TreeNode> apply(ModifiedNode modification, Optional<TreeNode> storeMeta, Version version);
+
+ /**
+ *
+ * Performs structural verification of NodeModification, such as writen values / types
+ * uses right structural elements.
+ *
+ * @param modification to be verified.
+ * @throws IllegalArgumentException If provided NodeModification does not adhere to the structure.
+ */
+ void verifyStructure(ModifiedNode modification) throws IllegalArgumentException;
+
+ /**
+ * 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
+ Optional<ModificationApplyOperation> getChild(PathArgument child);
+
+ /**
+ *
+ * Checks if provided node modification could be applied to current metadata node.
+ *
+ * @param modification Modification
+ * @param current Metadata Node to which modification should be applied
+ * @return true if modification is applicable
+ * false if modification is no applicable
+ * @throws DataValidationFailedException
+ */
+ void checkApplicable(InstanceIdentifier path, NodeModification modification, Optional<TreeNode> current) throws DataValidationFailedException;
+}
--- /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.yangtools.yang.data.impl.schema.tree;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import org.opendaylight.yangtools.concepts.Identifiable;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.StoreTreeNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.TreeNode;
+
+import javax.annotation.concurrent.GuardedBy;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Node Modification Node and Tree
+ *
+ * Tree which structurally resembles data tree and captures client modifications
+ * to the data store tree.
+ *
+ * This tree is lazily created and populated via {@link #modifyChild(PathArgument)}
+ * and {@link StoreMetadataNode} which represents original state {@link #getOriginal()}.
+ */
+final class ModifiedNode implements StoreTreeNode<ModifiedNode>, Identifiable<PathArgument>, NodeModification {
+
+ public static final Predicate<ModifiedNode> IS_TERMINAL_PREDICATE = new Predicate<ModifiedNode>() {
+ @Override
+ public boolean apply(final ModifiedNode input) {
+ switch (input.getType()) {
+ case DELETE:
+ case MERGE:
+ case WRITE:
+ return true;
+ case SUBTREE_MODIFIED:
+ case UNMODIFIED:
+ return false;
+ }
+
+ throw new IllegalArgumentException(String.format("Unhandled modification type %s", input.getType()));
+ }
+ };
+
+ private final Map<PathArgument, ModifiedNode> children = new LinkedHashMap<>();
+ private final Optional<TreeNode> original;
+ private final PathArgument identifier;
+ private ModificationType modificationType = ModificationType.UNMODIFIED;
+ private Optional<TreeNode> snapshotCache;
+ private NormalizedNode<?, ?> value;
+
+ private ModifiedNode(final PathArgument identifier, final Optional<TreeNode> original) {
+ this.identifier = identifier;
+ this.original = original;
+ }
+
+ /**
+ *
+ *
+ * @return
+ */
+ public NormalizedNode<?, ?> getWrittenValue() {
+ return value;
+ }
+
+ @Override
+ public PathArgument getIdentifier() {
+ return identifier;
+ }
+
+ /**
+ *
+ * Returns original store metadata
+ * @return original store metadata
+ */
+ @Override
+ public Optional<TreeNode> getOriginal() {
+ return original;
+ }
+
+ /**
+ * Returns modification type
+ *
+ * @return modification type
+ */
+ @Override
+ public ModificationType getType() {
+ return modificationType;
+ }
+
+ /**
+ *
+ * Returns child modification if child was modified
+ *
+ * @return Child modification if direct child or it's subtree
+ * was modified.
+ *
+ */
+ @Override
+ public Optional<ModifiedNode> getChild(final PathArgument child) {
+ return Optional.<ModifiedNode> fromNullable(children.get(child));
+ }
+
+ /**
+ *
+ * Returns child modification if child was modified, creates {@link org.opendaylight.controller.md.sal.dom.store.impl.tree.data.ModifiedNode}
+ * for child otherwise.
+ *
+ * If this node's {@link ModificationType} is {@link ModificationType#UNMODIFIED}
+ * changes modification type to {@link ModificationType#SUBTREE_MODIFIED}
+ *
+ * @param child
+ * @return {@link org.opendaylight.controller.md.sal.dom.store.impl.tree.data.ModifiedNode} for specified child, with {@link #getOriginal()}
+ * containing child metadata if child was present in original data.
+ */
+ public ModifiedNode modifyChild(final PathArgument child) {
+ clearSnapshot();
+ if (modificationType == ModificationType.UNMODIFIED) {
+ updateModificationType(ModificationType.SUBTREE_MODIFIED);
+ }
+ final ModifiedNode potential = children.get(child);
+ if (potential != null) {
+ return potential;
+ }
+
+ final Optional<TreeNode> currentMetadata;
+ if (original.isPresent()) {
+ final TreeNode orig = original.get();
+ currentMetadata = orig.getChild(child);
+ } else {
+ currentMetadata = Optional.absent();
+ }
+
+ ModifiedNode newlyCreated = new ModifiedNode(child, currentMetadata);
+ children.put(child, newlyCreated);
+ return newlyCreated;
+ }
+
+ /**
+ *
+ * Returns all recorded direct child modification
+ *
+ * @return all recorded direct child modifications
+ */
+ @Override
+ public Iterable<ModifiedNode> getChildren() {
+ return children.values();
+ }
+
+ /**
+ *
+ * Records a delete for associated node.
+ *
+ */
+ public void delete() {
+ clearSnapshot();
+ updateModificationType(ModificationType.DELETE);
+ children.clear();
+ this.value = null;
+ }
+
+ /**
+ *
+ * Records a write for associated node.
+ *
+ * @param value
+ */
+ public void write(final NormalizedNode<?, ?> value) {
+ clearSnapshot();
+ updateModificationType(ModificationType.WRITE);
+ children.clear();
+ this.value = value;
+ }
+
+ public void merge(final NormalizedNode<?, ?> data) {
+ clearSnapshot();
+ updateModificationType(ModificationType.MERGE);
+ // FIXME: Probably merge with previous value.
+ this.value = data;
+ }
+
+ void seal() {
+ clearSnapshot();
+ for (ModifiedNode child : children.values()) {
+ child.seal();
+ }
+ }
+
+ private void clearSnapshot() {
+ snapshotCache = null;
+ }
+
+ public Optional<TreeNode> storeSnapshot(final Optional<TreeNode> snapshot) {
+ snapshotCache = snapshot;
+ return snapshot;
+ }
+
+ public Optional<Optional<TreeNode>> getSnapshotCache() {
+ return Optional.fromNullable(snapshotCache);
+ }
+
+ @GuardedBy("this")
+ private void updateModificationType(final ModificationType type) {
+ modificationType = type;
+ clearSnapshot();
+ }
+
+ @Override
+ public String toString() {
+ return "NodeModification [identifier=" + identifier + ", modificationType="
+ + modificationType + ", childModification=" + children + "]";
+ }
+
+ public static ModifiedNode createUnmodified(final TreeNode metadataTree) {
+ return new ModifiedNode(metadataTree.getIdentifier(), Optional.of(metadataTree));
+ }
+}
--- /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.yangtools.yang.data.impl.schema.tree;
+
+
+import com.google.common.base.Optional;
+import org.opendaylight.yangtools.concepts.Identifiable;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.TreeNode;
+
+/**
+ * Internal interface representing a modification action of a particular node.
+ * It is used by the validation code to allow for a read-only view of the
+ * modification tree as we should never modify that during validation.
+ */
+interface NodeModification extends Identifiable<PathArgument> {
+ /**
+ * Get the type of modification.
+ *
+ * @return Modification type.
+ */
+ ModificationType getType();
+
+ /**
+ * Get the original tree node to which the modification is to be applied.
+ *
+ * @return The original node, or {@link Optional#absent()} if the node is
+ * a new node.
+ */
+ Optional<TreeNode> getOriginal();
+
+ /**
+ * Get a read-only view of children nodes.
+ *
+ * @return Iterable of all children nodes.
+ */
+ Iterable<? extends NodeModification> getChildren();
+}
--- /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.yangtools.yang.data.impl.schema.tree;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+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.api.schema.tree.DataTreeCandidateNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
+
+import java.util.Collections;
+
+/**
+ * Internal utility class for an empty candidate. We instantiate this class
+ * for empty modifications, saving memory and processing speed. Instances
+ * of this class are explicitly recognized and processing of them is skipped.
+ */
+final class NoopDataTreeCandidate extends AbstractDataTreeCandidate {
+ private static final DataTreeCandidateNode ROOT = new DataTreeCandidateNode() {
+ @Override
+ public ModificationType getModificationType() {
+ return ModificationType.UNMODIFIED;
+ }
+
+ @Override
+ public Iterable<DataTreeCandidateNode> getChildNodes() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public PathArgument getIdentifier() {
+ throw new IllegalStateException("Attempted to read identifier of the no-operation change");
+ }
+
+ @Override
+ public Optional<NormalizedNode<?, ?>> getDataAfter() {
+ return Optional.absent();
+ }
+
+ @Override
+ public Optional<NormalizedNode<?, ?>> getDataBefore() {
+ return Optional.absent();
+ }
+ };
+
+ protected NoopDataTreeCandidate(final InstanceIdentifier rootPath, final ModifiedNode modificationRoot) {
+ super(rootPath);
+ Preconditions.checkArgument(modificationRoot.getType() == ModificationType.UNMODIFIED);
+ }
+
+ @Override
+ public DataTreeCandidateNode getRootNode() {
+ return ROOT;
+ }
+}
--- /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.yangtools.yang.data.impl.schema.tree;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+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.LeafSetNode;
+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.NormalizedNodeContainer;
+import org.opendaylight.yangtools.yang.data.api.schema.OrderedLeafSetNode;
+import org.opendaylight.yangtools.yang.data.api.schema.OrderedMapNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException;
+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.DataContainerNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.NormalizedNodeContainerBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableChoiceNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableLeafSetNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableMapNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableOrderedLeafSetNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableOrderedMapNodeBuilder;
+import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
+import org.opendaylight.yangtools.yang.model.api.ChoiceNode;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
+
+import java.util.Map;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+abstract class NormalizedNodeContainerModificationStrategy extends SchemaAwareApplyOperation {
+
+ private final Class<? extends NormalizedNode<?, ?>> nodeClass;
+
+ protected NormalizedNodeContainerModificationStrategy(final Class<? extends NormalizedNode<?, ?>> nodeClass) {
+ this.nodeClass = nodeClass;
+ }
+
+ @Override
+ public void verifyStructure(final ModifiedNode modification) throws IllegalArgumentException {
+ if (modification.getType() == ModificationType.WRITE) {
+
+ }
+ for (ModifiedNode childModification : modification.getChildren()) {
+ resolveChildOperation(childModification.getIdentifier()).verifyStructure(childModification);
+ }
+ }
+
+ @Override
+ protected void checkWriteApplicable(final InstanceIdentifier path, final NodeModification modification,
+ final Optional<TreeNode> current) throws DataValidationFailedException {
+ // FIXME: Implement proper write check for replacement of node container
+ // prerequisite is to have transaction chain available for clients
+ // otherwise this will break chained writes to same node.
+ }
+
+ @SuppressWarnings("rawtypes")
+ @Override
+ protected void verifyWrittenStructure(final NormalizedNode<?, ?> writtenValue) {
+ checkArgument(nodeClass.isInstance(writtenValue), "Node should must be of type %s", nodeClass);
+ checkArgument(writtenValue instanceof NormalizedNodeContainer);
+
+ NormalizedNodeContainer container = (NormalizedNodeContainer) writtenValue;
+ for (Object child : container.getValue()) {
+ checkArgument(child instanceof NormalizedNode);
+
+ /*
+ * FIXME: fail-fast semantics:
+ *
+ * We can validate the data structure here, aborting the commit
+ * before it ever progresses to being committed.
+ */
+ }
+ }
+
+ @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;
+ }
+
+ /*
+ * 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.
+ */
+ final MutableTreeNode mutable = newValueMeta.mutable();
+ mutable.setSubtreeVersion(version);
+
+ @SuppressWarnings("rawtypes")
+ final NormalizedNodeContainerBuilder dataBuilder = createBuilder(newValue);
+
+ return mutateChildren(mutable, dataBuilder, version, modification.getChildren());
+ }
+
+ @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
+ protected TreeNode applyMerge(final ModifiedNode modification, final TreeNode currentMeta,
+ final Version version) {
+ // For Node Containers - merge is same as subtree change - we only replace children.
+ return applySubtreeChange(modification, currentMeta, version);
+ }
+
+ @Override
+ public TreeNode applySubtreeChange(final ModifiedNode modification,
+ final TreeNode currentMeta, final Version version) {
+ final MutableTreeNode newMeta = currentMeta.mutable();
+ newMeta.setSubtreeVersion(version);
+
+ @SuppressWarnings("rawtypes")
+ NormalizedNodeContainerBuilder dataBuilder = createBuilder(currentMeta.getData());
+
+ return mutateChildren(newMeta, dataBuilder, version, modification.getChildren());
+ }
+
+ @Override
+ protected void checkSubtreeModificationApplicable(final InstanceIdentifier path, final NodeModification modification,
+ final Optional<TreeNode> current) throws DataValidationFailedException {
+ SchemaAwareApplyOperation.checkConflicting(path, current.isPresent(), "Node was deleted by other transaction.");
+ checkChildPreconditions(path, modification, current);
+ }
+
+ private void checkChildPreconditions(final InstanceIdentifier path, final NodeModification modification, final Optional<TreeNode> current) throws DataValidationFailedException {
+ final TreeNode currentMeta = current.get();
+ for (NodeModification childMod : modification.getChildren()) {
+ final PathArgument childId = childMod.getIdentifier();
+ final Optional<TreeNode> childMeta = currentMeta.getChild(childId);
+
+ InstanceIdentifier childPath = path.node(childId);
+ resolveChildOperation(childId).checkApplicable(childPath, childMod, childMeta);
+ }
+ }
+
+ @Override
+ protected void checkMergeApplicable(final InstanceIdentifier path, final NodeModification modification,
+ final Optional<TreeNode> current) throws DataValidationFailedException {
+ if(current.isPresent()) {
+ checkChildPreconditions(path, modification,current);
+ }
+ }
+
+ @SuppressWarnings("rawtypes")
+ protected abstract NormalizedNodeContainerBuilder createBuilder(NormalizedNode<?, ?> original);
+
+ public static class ChoiceModificationStrategy extends NormalizedNodeContainerModificationStrategy {
+
+ private final Map<PathArgument, ModificationApplyOperation> childNodes;
+
+ public ChoiceModificationStrategy(final ChoiceNode schemaNode) {
+ super(org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode.class);
+ ImmutableMap.Builder<PathArgument, ModificationApplyOperation> child = ImmutableMap.builder();
+
+ for (ChoiceCaseNode caze : schemaNode.getCases()) {
+ for (DataSchemaNode cazeChild : caze.getChildNodes()) {
+ SchemaAwareApplyOperation childNode = SchemaAwareApplyOperation.from(cazeChild);
+ child.put(new NodeIdentifier(cazeChild.getQName()), childNode);
+ }
+ }
+ childNodes = child.build();
+ }
+
+ @Override
+ public Optional<ModificationApplyOperation> getChild(final PathArgument child) {
+ return Optional.fromNullable(childNodes.get(child));
+ }
+
+ @Override
+ @SuppressWarnings("rawtypes")
+ protected DataContainerNodeBuilder createBuilder(final NormalizedNode<?, ?> original) {
+ checkArgument(original instanceof org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode);
+ return ImmutableChoiceNodeBuilder.create((org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode) original);
+ }
+ }
+
+ public static class OrderedLeafSetModificationStrategy extends NormalizedNodeContainerModificationStrategy {
+
+ private final Optional<ModificationApplyOperation> entryStrategy;
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ protected OrderedLeafSetModificationStrategy(final LeafListSchemaNode schema) {
+ super((Class) LeafSetNode.class);
+ entryStrategy = Optional.<ModificationApplyOperation> of(new ValueNodeModificationStrategy.LeafSetEntryModificationStrategy(schema));
+ }
+
+ @SuppressWarnings("rawtypes")
+ @Override
+ protected NormalizedNodeContainerBuilder createBuilder(final NormalizedNode<?, ?> original) {
+ checkArgument(original instanceof OrderedLeafSetNode<?>);
+ return ImmutableOrderedLeafSetNodeBuilder.create((OrderedLeafSetNode<?>) original);
+ }
+
+ @Override
+ public Optional<ModificationApplyOperation> getChild(final PathArgument identifier) {
+ if (identifier instanceof NodeWithValue) {
+ return entryStrategy;
+ }
+ return Optional.absent();
+ }
+ }
+
+ public static class OrderedMapModificationStrategy extends NormalizedNodeContainerModificationStrategy {
+
+ private final Optional<ModificationApplyOperation> entryStrategy;
+
+ protected OrderedMapModificationStrategy(final ListSchemaNode schema) {
+ super(OrderedMapNode.class);
+ entryStrategy = Optional.<ModificationApplyOperation> of(new DataNodeContainerModificationStrategy.ListEntryModificationStrategy(schema));
+ }
+
+ @SuppressWarnings("rawtypes")
+ @Override
+ protected NormalizedNodeContainerBuilder createBuilder(final NormalizedNode<?, ?> original) {
+ checkArgument(original instanceof OrderedMapNode);
+ return ImmutableOrderedMapNodeBuilder.create((OrderedMapNode) original);
+ }
+
+ @Override
+ public Optional<ModificationApplyOperation> getChild(final PathArgument identifier) {
+ if (identifier instanceof NodeIdentifierWithPredicates) {
+ return entryStrategy;
+ }
+ return Optional.absent();
+ }
+
+ @Override
+ public String toString() {
+ return "OrderedMapModificationStrategy [entry=" + entryStrategy + "]";
+ }
+ }
+
+ public static class UnorderedLeafSetModificationStrategy extends NormalizedNodeContainerModificationStrategy {
+
+ private final Optional<ModificationApplyOperation> entryStrategy;
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ protected UnorderedLeafSetModificationStrategy(final LeafListSchemaNode schema) {
+ super((Class) LeafSetNode.class);
+ entryStrategy = Optional.<ModificationApplyOperation> of(new ValueNodeModificationStrategy.LeafSetEntryModificationStrategy(schema));
+ }
+
+ @SuppressWarnings("rawtypes")
+ @Override
+ protected NormalizedNodeContainerBuilder createBuilder(final NormalizedNode<?, ?> original) {
+ checkArgument(original instanceof LeafSetNode<?>);
+ return ImmutableLeafSetNodeBuilder.create((LeafSetNode<?>) original);
+ }
+
+ @Override
+ public Optional<ModificationApplyOperation> getChild(final PathArgument identifier) {
+ if (identifier instanceof NodeWithValue) {
+ return entryStrategy;
+ }
+ return Optional.absent();
+ }
+ }
+
+ public static class UnorderedMapModificationStrategy extends NormalizedNodeContainerModificationStrategy {
+
+ private final Optional<ModificationApplyOperation> entryStrategy;
+
+ protected UnorderedMapModificationStrategy(final ListSchemaNode schema) {
+ super(MapNode.class);
+ entryStrategy = Optional.<ModificationApplyOperation> of(new DataNodeContainerModificationStrategy.ListEntryModificationStrategy(schema));
+ }
+
+ @SuppressWarnings("rawtypes")
+ @Override
+ protected NormalizedNodeContainerBuilder createBuilder(final NormalizedNode<?, ?> original) {
+ checkArgument(original instanceof MapNode);
+ return ImmutableMapNodeBuilder.create((MapNode) original);
+ }
+
+ @Override
+ public Optional<ModificationApplyOperation> getChild(final PathArgument identifier) {
+ if (identifier instanceof NodeIdentifierWithPredicates) {
+ return entryStrategy;
+ }
+ return Optional.absent();
+ }
+
+ @Override
+ public String toString() {
+ return "UnorderedMapModificationStrategy [entry=" + entryStrategy + "]";
+ }
+ }
+}
--- /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.yangtools.yang.data.impl.schema.tree;
+
+
+import com.google.common.base.Optional;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.TreeNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.Version;
+
+final class OperationWithModification {
+
+ private final ModifiedNode modification;
+
+ private final ModificationApplyOperation applyOperation;
+
+ private OperationWithModification(final ModificationApplyOperation op, final ModifiedNode mod) {
+ this.modification = mod;
+ this.applyOperation = op;
+ }
+
+ public OperationWithModification write(final NormalizedNode<?, ?> value) {
+ modification.write(value);
+ applyOperation.verifyStructure(modification);
+ return this;
+ }
+
+ public OperationWithModification delete() {
+ modification.delete();
+ return this;
+ }
+
+ public ModifiedNode getModification() {
+ return modification;
+ }
+
+ public ModificationApplyOperation getApplyOperation() {
+ return applyOperation;
+ }
+
+ public Optional<TreeNode> apply(final Optional<TreeNode> data, final Version version) {
+ return applyOperation.apply(modification, data, version);
+ }
+
+ public static OperationWithModification from(final ModificationApplyOperation operation,
+ final ModifiedNode modification) {
+ return new OperationWithModification(operation, modification);
+
+ }
+
+ public void merge(final NormalizedNode<?, ?> data) {
+ modification.merge(data);
+ applyOperation.verifyStructure(modification);
+
+ }
+
+ public OperationWithModification forChild(final PathArgument childId) {
+ ModifiedNode childMod = modification.modifyChild(childId);
+ Optional<ModificationApplyOperation> childOp = applyOperation.getChild(childId);
+ return from(childOp.get(),childMod);
+ }
+}
--- /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.yangtools.yang.data.impl.schema.tree;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.AugmentationIdentifier;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+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.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.model.api.AugmentationSchema;
+import org.opendaylight.yangtools.yang.model.api.AugmentationTarget;
+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 org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+abstract class SchemaAwareApplyOperation implements ModificationApplyOperation {
+ private static final Logger LOG = LoggerFactory.getLogger(SchemaAwareApplyOperation.class);
+
+ public static SchemaAwareApplyOperation from(final DataSchemaNode schemaNode) {
+ if (schemaNode instanceof ContainerSchemaNode) {
+ return new DataNodeContainerModificationStrategy.ContainerModificationStrategy((ContainerSchemaNode) schemaNode);
+ } else if (schemaNode instanceof ListSchemaNode) {
+ return fromListSchemaNode((ListSchemaNode) schemaNode);
+ } else if (schemaNode instanceof ChoiceNode) {
+ return new NormalizedNodeContainerModificationStrategy.ChoiceModificationStrategy((ChoiceNode) schemaNode);
+ } else if (schemaNode instanceof LeafListSchemaNode) {
+ return fromLeafListSchemaNode((LeafListSchemaNode) schemaNode);
+ } else if (schemaNode instanceof LeafSchemaNode) {
+ return new ValueNodeModificationStrategy.LeafModificationStrategy((LeafSchemaNode) schemaNode);
+ }
+ throw new IllegalArgumentException("Not supported schema node type for " + schemaNode.getClass());
+ }
+
+ public static SchemaAwareApplyOperation from(final DataNodeContainer resolvedTree,
+ final AugmentationTarget augSchemas, final AugmentationIdentifier identifier) {
+ AugmentationSchema augSchema = null;
+
+ allAugments:
+ for (AugmentationSchema potential : augSchemas.getAvailableAugmentations()) {
+ for (DataSchemaNode child : potential.getChildNodes()) {
+ if (identifier.getPossibleChildNames().contains(child.getQName())) {
+ augSchema = potential;
+ break allAugments;
+ }
+ }
+ }
+
+ if (augSchema != null) {
+ return new DataNodeContainerModificationStrategy.AugmentationModificationStrategy(augSchema, resolvedTree);
+ }
+ return null;
+ }
+
+ public static boolean checkConflicting(final InstanceIdentifier path, final boolean condition, final String message) throws ConflictingModificationAppliedException {
+ if(!condition) {
+ throw new ConflictingModificationAppliedException(path, message);
+ }
+ return condition;
+ }
+
+ private static SchemaAwareApplyOperation fromListSchemaNode(final ListSchemaNode schemaNode) {
+ List<QName> keyDefinition = schemaNode.getKeyDefinition();
+ if (keyDefinition == null || keyDefinition.isEmpty()) {
+ return new UnkeyedListModificationStrategy(schemaNode);
+ }
+ if (schemaNode.isUserOrdered()) {
+ return new NormalizedNodeContainerModificationStrategy.OrderedMapModificationStrategy(schemaNode);
+ }
+
+ return new NormalizedNodeContainerModificationStrategy.UnorderedMapModificationStrategy(schemaNode);
+ }
+
+ private static SchemaAwareApplyOperation fromLeafListSchemaNode(final LeafListSchemaNode schemaNode) {
+ if(schemaNode.isUserOrdered()) {
+ return new NormalizedNodeContainerModificationStrategy.OrderedLeafSetModificationStrategy(schemaNode);
+ } else {
+ return new NormalizedNodeContainerModificationStrategy.UnorderedLeafSetModificationStrategy(schemaNode);
+ }
+ }
+
+ private static final void checkNotConflicting(final InstanceIdentifier path, final TreeNode original, final TreeNode current) throws ConflictingModificationAppliedException {
+ checkConflicting(path, original.getVersion().equals(current.getVersion()),
+ "Node was replaced by other transaction.");
+ checkConflicting(path, original.getSubtreeVersion().equals(current.getSubtreeVersion()),
+ "Node children was modified by other transaction");
+ }
+
+ protected final ModificationApplyOperation resolveChildOperation(final PathArgument child) {
+ Optional<ModificationApplyOperation> potential = getChild(child);
+ Preconditions.checkArgument(potential.isPresent(), "Operation for child %s is not defined.", child);
+ return potential.get();
+ }
+
+ @Override
+ public void verifyStructure(final ModifiedNode modification) throws IllegalArgumentException {
+ if (modification.getType() == ModificationType.WRITE) {
+ verifyWrittenStructure(modification.getWrittenValue());
+ }
+ }
+
+ @Override
+ public final void checkApplicable(final InstanceIdentifier path,final NodeModification modification, final Optional<TreeNode> current) throws DataValidationFailedException {
+ switch (modification.getType()) {
+ case DELETE:
+ checkDeleteApplicable(modification, current);
+ case SUBTREE_MODIFIED:
+ checkSubtreeModificationApplicable(path, modification, current);
+ return;
+ case WRITE:
+ checkWriteApplicable(path, modification, current);
+ return;
+ case MERGE:
+ checkMergeApplicable(path, modification, current);
+ return;
+ case UNMODIFIED:
+ return;
+ default:
+ throw new UnsupportedOperationException("Suplied modification type "+ modification.getType()+ "is not supported.");
+ }
+
+ }
+
+ protected void checkMergeApplicable(final InstanceIdentifier path, final NodeModification modification, final Optional<TreeNode> current) throws DataValidationFailedException {
+ Optional<TreeNode> original = modification.getOriginal();
+ if (original.isPresent() && current.isPresent()) {
+ /*
+ * We need to do conflict detection only and only if the value of leaf changed
+ * before two transactions. If value of leaf is unchanged between two transactions
+ * it should not cause transaction to fail, since result of this merge
+ * leads to same data.
+ */
+ if(!original.get().getData().equals(current.get().getData())) {
+ checkNotConflicting(path, original.get(), current.get());
+ }
+ }
+ }
+
+ protected void checkWriteApplicable(final InstanceIdentifier 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());
+ } else if(original.isPresent()) {
+ throw new ConflictingModificationAppliedException(path,"Node was deleted by other transaction.");
+ }
+ }
+
+ private void checkDeleteApplicable(final NodeModification modification, final Optional<TreeNode> current) {
+ // Delete is always applicable, we do not expose it to subclasses
+ if (current.isPresent()) {
+ LOG.trace("Delete operation turned to no-op on missing node {}", modification);
+ }
+ }
+
+ @Override
+ public final Optional<TreeNode> apply(final ModifiedNode modification,
+ final Optional<TreeNode> currentMeta, final Version version) {
+
+ switch (modification.getType()) {
+ case DELETE:
+ return modification.storeSnapshot(Optional.<TreeNode> absent());
+ case SUBTREE_MODIFIED:
+ Preconditions.checkArgument(currentMeta.isPresent(), "Metadata not available for modification",
+ modification);
+ return modification.storeSnapshot(Optional.of(applySubtreeChange(modification, currentMeta.get(),
+ version)));
+ case MERGE:
+ if(currentMeta.isPresent()) {
+ return modification.storeSnapshot(Optional.of(applyMerge(modification,currentMeta.get(), version)));
+ } // Fallback to write is intentional - if node is not preexisting merge is same as write
+ case WRITE:
+ return modification.storeSnapshot(Optional.of(applyWrite(modification, currentMeta, version)));
+ case UNMODIFIED:
+ return currentMeta;
+ default:
+ throw new IllegalArgumentException("Provided modification type is not supported.");
+ }
+ }
+
+ protected abstract TreeNode applyMerge(ModifiedNode modification,
+ TreeNode currentMeta, Version version);
+
+ protected abstract TreeNode applyWrite(ModifiedNode modification,
+ Optional<TreeNode> currentMeta, Version version);
+
+ protected abstract TreeNode applySubtreeChange(ModifiedNode modification,
+ TreeNode currentMeta, Version version);
+
+ /**
+ *
+ * Checks is supplied {@link NodeModification} is applicable for Subtree Modification.
+ *
+ * @param path Path to current node
+ * @param modification Node modification which should be applied.
+ * @param current Current state of data tree
+ * @throws ConflictingModificationAppliedException If subtree was changed in conflicting way
+ * @throws IncorrectDataStructureException If subtree modification is not applicable (e.g. leaf node).
+ */
+ protected abstract void checkSubtreeModificationApplicable(InstanceIdentifier path, final NodeModification modification,
+ final Optional<TreeNode> current) throws DataValidationFailedException;
+
+ protected abstract void verifyWrittenStructure(NormalizedNode<?, ?> writtenValue);
+
+ public static class UnkeyedListModificationStrategy extends SchemaAwareApplyOperation {
+
+ private final Optional<ModificationApplyOperation> entryStrategy;
+
+ protected UnkeyedListModificationStrategy(final ListSchemaNode schema) {
+ entryStrategy = Optional.<ModificationApplyOperation> of(new DataNodeContainerModificationStrategy.UnkeyedListItemModificationStrategy(schema));
+ }
+
+ @Override
+ protected TreeNode applyMerge(final ModifiedNode modification, final TreeNode currentMeta,
+ final Version version) {
+ return applyWrite(modification, Optional.of(currentMeta), version);
+ }
+
+ @Override
+ protected TreeNode applySubtreeChange(final ModifiedNode modification,
+ final TreeNode currentMeta, final Version version) {
+ throw new UnsupportedOperationException("UnkeyedList does not support subtree change.");
+ }
+
+ @Override
+ protected TreeNode applyWrite(final ModifiedNode modification,
+ final Optional<TreeNode> currentMeta, final Version version) {
+ return TreeNodeFactory.createTreeNode(modification.getWrittenValue(), version);
+ }
+
+ @Override
+ public Optional<ModificationApplyOperation> getChild(final PathArgument child) {
+ if (child instanceof NodeIdentifier) {
+ return entryStrategy;
+ }
+ return Optional.absent();
+ }
+
+ @Override
+ protected void verifyWrittenStructure(final NormalizedNode<?, ?> writtenValue) {
+
+ }
+
+ @Override
+ protected void checkSubtreeModificationApplicable(final InstanceIdentifier path, final NodeModification modification,
+ final Optional<TreeNode> current) throws IncorrectDataStructureException {
+ throw new IncorrectDataStructureException(path, "Subtree modification is not allowed.");
+ }
+ }
+}
--- /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.yangtools.yang.data.impl.schema.tree;
+
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.AugmentationIdentifier;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
+
+import com.google.common.base.Strings;
+
+/**
+ * Data store tree manipulation utilities.
+ */
+public final class StoreUtils {
+ private static final int STRINGTREE_INDENT = 4;
+
+ private StoreUtils() {
+ throw new UnsupportedOperationException("Utility class should not be instantiated");
+ }
+
+ /**
+ * Convert a data subtree under a node into a human-readable string format.
+ *
+ * @param node Data subtree root
+ * @return String containing a human-readable form of the subtree.
+ */
+ public static String toStringTree(final NormalizedNode<?, ?> node) {
+ final StringBuilder builder = new StringBuilder();
+ toStringTree(builder, node, 0);
+ return builder.toString();
+ }
+
+ private static void toStringTree(final StringBuilder builder, final NormalizedNode<?, ?> node, final int offset) {
+ final String prefix = Strings.repeat(" ", offset);
+
+ builder.append(prefix).append(toStringTree(node.getIdentifier()));
+ if (node instanceof NormalizedNodeContainer<?, ?, ?>) {
+ final NormalizedNodeContainer<?, ?, ?> container = (NormalizedNodeContainer<?, ?, ?>) node;
+
+ builder.append(" {\n");
+ for (NormalizedNode<?, ?> child : container.getValue()) {
+ toStringTree(builder, child, offset + STRINGTREE_INDENT);
+ }
+
+ builder.append(prefix).append('}');
+ } else {
+ builder.append(' ').append(node.getValue());
+ }
+ builder.append('\n');
+ }
+
+ private static String toStringTree(final PathArgument identifier) {
+ if (identifier instanceof NodeIdentifierWithPredicates) {
+ StringBuilder builder = new StringBuilder();
+ builder.append(identifier.getNodeType().getLocalName());
+ builder.append(((NodeIdentifierWithPredicates) identifier).getKeyValues().values());
+ return builder.toString();
+ } else if (identifier instanceof AugmentationIdentifier) {
+ return "augmentation";
+ }
+ return identifier.getNodeType().getLocalName();
+ }
+}
--- /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.yangtools.yang.data.impl.schema.tree;
+
+import java.util.AbstractMap.SimpleEntry;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.StoreTreeNode;
+
+/**
+ * A set of utility methods for interacting with {@link org.opendaylight.controller.md.sal.dom.store.impl.tree.spi.TreeNode} objects.
+ */
+public final class TreeNodeUtils {
+ private TreeNodeUtils() {
+ throw new UnsupportedOperationException("Utility class should not be instantiated");
+ }
+
+ /**
+ * Finds a node in tree
+ *
+ * @param tree Data Tree
+ * @param path Path to the node
+ * @return Optional with node if the node is present in tree, {@link Optional#absent()} otherwise.
+ */
+ public static <T extends StoreTreeNode<T>> Optional<T> findNode(final T tree, final InstanceIdentifier path) {
+ Optional<T> current = Optional.<T> of(tree);
+ Iterator<PathArgument> pathIter = path.getPath().iterator();
+ while (current.isPresent() && pathIter.hasNext()) {
+ current = current.get().getChild(pathIter.next());
+ }
+ return current;
+ }
+
+ public static <T extends StoreTreeNode<T>> T findNodeChecked(final T tree, final InstanceIdentifier path) {
+ T current = tree;
+ List<PathArgument> nested = new ArrayList<>(path.getPath().size());
+ for(PathArgument pathArg : path.getPath()) {
+ Optional<T> potential = current.getChild(pathArg);
+ nested.add(pathArg);
+ Preconditions.checkArgument(potential.isPresent(),"Child %s is not present in tree.",nested);
+ current = potential.get();
+ }
+ return current;
+ }
+
+ /**
+ * Finds a node or closest parent in the tree
+ *
+ * @param tree Data Tree
+ * @param path Path to the node
+ * @return Map.Entry Entry with key which is path to closest parent and value is parent node.
+ *
+ */
+ public static <T extends StoreTreeNode<T>> Map.Entry<InstanceIdentifier, T> findClosest(final T tree, final InstanceIdentifier path) {
+ return findClosestsOrFirstMatch(tree, path, Predicates.<T>alwaysFalse());
+ }
+
+ public static <T extends StoreTreeNode<T>> Map.Entry<InstanceIdentifier, T> findClosestsOrFirstMatch(final T tree, final InstanceIdentifier path, final Predicate<T> predicate) {
+ Optional<T> parent = Optional.<T>of(tree);
+ Optional<T> current = Optional.<T> of(tree);
+
+ int nesting = 0;
+ Iterator<PathArgument> pathIter = path.getPath().iterator();
+ while (current.isPresent() && pathIter.hasNext() && !predicate.apply(current.get())) {
+ parent = current;
+ current = current.get().getChild(pathIter.next());
+ nesting++;
+ }
+ if(current.isPresent()) {
+ final InstanceIdentifier currentPath = new InstanceIdentifier(path.getPath().subList(0, nesting));
+ return new SimpleEntry<InstanceIdentifier,T>(currentPath,current.get());
+ }
+
+ /*
+ * Subtracting 1 from nesting level at this point is safe, because we
+ * cannot reach here with nesting == 0: that would mean the above check
+ * for current.isPresent() failed, which it cannot, as current is always
+ * present. At any rate we check state just to be on the safe side.
+ */
+ Preconditions.checkState(nesting > 0);
+ final InstanceIdentifier parentPath = new InstanceIdentifier(path.getPath().subList(0, nesting - 1));
+
+ return new SimpleEntry<InstanceIdentifier,T>(parentPath,parent.get());
+ }
+
+ public static <T extends StoreTreeNode<T>> Optional<T> getChild(final Optional<T> parent,final PathArgument child) {
+ if(parent.isPresent()) {
+ return parent.get().getChild(child);
+ }
+ return Optional.absent();
+ }
+
+}
--- /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.yangtools.yang.data.impl.schema.tree;
+
+import com.google.common.base.Optional;
+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.LeafNode;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.IncorrectDataStructureException;
+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.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+abstract class ValueNodeModificationStrategy<T extends DataSchemaNode> extends SchemaAwareApplyOperation {
+
+ private final T schema;
+ private final Class<? extends NormalizedNode<?, ?>> nodeClass;
+
+ protected ValueNodeModificationStrategy(final T schema, final Class<? extends NormalizedNode<?, ?>> nodeClass) {
+ super();
+ this.schema = schema;
+ this.nodeClass = nodeClass;
+ }
+
+ @Override
+ protected void verifyWrittenStructure(final NormalizedNode<?, ?> writtenValue) {
+ checkArgument(nodeClass.isInstance(writtenValue), "Node should must be of type %s", nodeClass);
+ }
+
+ @Override
+ public Optional<ModificationApplyOperation> getChild(final PathArgument child) {
+ throw new UnsupportedOperationException("Node " + schema.getPath()
+ + "is leaf type node. Child nodes not allowed");
+ }
+
+ @Override
+ protected TreeNode applySubtreeChange(final ModifiedNode modification,
+ final TreeNode currentMeta, final Version version) {
+ throw new UnsupportedOperationException("Node " + schema.getPath()
+ + "is leaf type node. Subtree change is not allowed.");
+ }
+
+ @Override
+ protected TreeNode applyMerge(final ModifiedNode modification, final TreeNode currentMeta,
+ final Version version) {
+ // Just overwrite whatever was there
+ return applyWrite(modification, null, version);
+ }
+
+ @Override
+ protected TreeNode applyWrite(final ModifiedNode modification,
+ final Optional<TreeNode> currentMeta, final Version version) {
+ return TreeNodeFactory.createTreeNode(modification.getWrittenValue(), version);
+ }
+
+ @Override
+ protected void checkSubtreeModificationApplicable(final InstanceIdentifier path, final NodeModification modification,
+ final Optional<TreeNode> current) throws IncorrectDataStructureException {
+ throw new IncorrectDataStructureException(path, "Subtree modification is not allowed.");
+ }
+
+ public static class LeafSetEntryModificationStrategy extends ValueNodeModificationStrategy<LeafListSchemaNode> {
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ protected LeafSetEntryModificationStrategy(final LeafListSchemaNode schema) {
+ super(schema, (Class) LeafSetEntryNode.class);
+ }
+ }
+
+ public static class LeafModificationStrategy extends ValueNodeModificationStrategy<LeafSchemaNode> {
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ protected LeafModificationStrategy(final LeafSchemaNode schema) {
+ super(schema, (Class) LeafNode.class);
+ }
+ }
+}
\ No newline at end of file
--- /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.yangtools.yang.data.impl.schema.tree;
+
+import com.google.common.base.Optional;
+import org.junit.Before;
+import org.junit.Test;
+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.api.schema.tree.DataTree;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModification;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.TreeNodeFactory;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.Version;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+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.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;
+
+
+
+
+
+
+/**
+ *
+ * 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(TestModel.OUTER_LIST_PATH)
+ .nodeWithKey(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, ONE_ID) //
+ .build();
+
+ private static final InstanceIdentifier OUTER_LIST_2_PATH = InstanceIdentifier.builder(TestModel.OUTER_LIST_PATH)
+ .nodeWithKey(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, TWO_ID) //
+ .build();
+
+ private static final InstanceIdentifier TWO_TWO_PATH = InstanceIdentifier.builder(OUTER_LIST_2_PATH)
+ .node(TestModel.INNER_LIST_QNAME) //
+ .nodeWithKey(TestModel.INNER_LIST_QNAME, TestModel.NAME_QNAME, TWO_TWO_NAME) //
+ .build();
+
+ private static final InstanceIdentifier TWO_TWO_VALUE_PATH = InstanceIdentifier.builder(TWO_TWO_PATH)
+ .node(TestModel.VALUE_QNAME) //
+ .build();
+
+ private static final MapEntryNode BAR_NODE = mapEntryBuilder(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, TWO_ID) //
+ .withChild(mapNodeBuilder(TestModel.INNER_LIST_QNAME) //
+ .withChild(mapEntry(TestModel.INNER_LIST_QNAME, TestModel.NAME_QNAME, TWO_ONE_NAME)) //
+ .withChild(mapEntry(TestModel.INNER_LIST_QNAME,TestModel.NAME_QNAME, TWO_TWO_NAME)) //
+ .build()) //
+ .build();
+
+ private SchemaContext schemaContext;
+ private ModificationApplyOperation applyOper;
+
+ @Before
+ public void prepare() {
+ schemaContext = TestModel.createTestContext();
+ assertNotNull("Schema context must not be null.", schemaContext);
+ applyOper = SchemaAwareApplyOperation.from(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(TestModel.TEST_QNAME))
+ .withChild(
+ mapNodeBuilder(TestModel.OUTER_LIST_QNAME)
+ .withChild(mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, ONE_ID))
+ .withChild(BAR_NODE).build()).build();
+ }
+
+ @Test
+ public void basicReadWrites() {
+ DataTreeModification modificationTree = new InMemoryDataTreeModification(new InMemoryDataTreeSnapshot(schemaContext,
+ TreeNodeFactory.createTreeNode(createDocumentOne(), Version.initial()), applyOper),
+ new SchemaAwareApplyOperationRoot(schemaContext));
+ Optional<NormalizedNode<?, ?>> originalBarNode = modificationTree.readNode(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(TestModel.VALUE_QNAME, "test"));
+
+ // reads node to /outer-list/1/inner_list/two/value
+ // and checks if node is already present
+ Optional<NormalizedNode<?, ?>> barTwoCModified = modificationTree.readNode(TWO_TWO_VALUE_PATH);
+ assertTrue(barTwoCModified.isPresent());
+ assertEquals(ImmutableNodes.leafNode(TestModel.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.readNode(TWO_TWO_VALUE_PATH);
+ assertFalse(barTwoCAfterDelete.isPresent());
+ }
+
+
+ public DataTreeModification createEmptyModificationTree() {
+ /**
+ * Creates empty Snapshot with associated schema context.
+ */
+ DataTree t = InMemoryDataTreeFactory.getInstance().create();
+ t.setSchemaContext(schemaContext);
+
+ /**
+ *
+ * Creates Mutable Data Tree based on provided snapshot and schema
+ * context.
+ *
+ */
+ return t.takeSnapshot().newModification();
+ }
+
+ @Test
+ public void createFromEmptyState() {
+
+ DataTreeModification modificationTree = createEmptyModificationTree();
+ /**
+ * Writes empty container node to /test
+ *
+ */
+ modificationTree.write(TestModel.TEST_PATH, ImmutableNodes.containerNode(TestModel.TEST_QNAME));
+
+ /**
+ * Writes empty list node to /test/outer-list
+ */
+ modificationTree.write(TestModel.OUTER_LIST_PATH, ImmutableNodes.mapNodeBuilder(TestModel.OUTER_LIST_QNAME).build());
+
+ /**
+ * Reads list node from /test/outer-list
+ */
+ Optional<NormalizedNode<?, ?>> potentialOuterList = modificationTree.readNode(TestModel.OUTER_LIST_PATH);
+ assertTrue(potentialOuterList.isPresent());
+
+ /**
+ * Reads container node from /test and verifies that it contains test
+ * node
+ */
+ Optional<NormalizedNode<?, ?>> potentialTest = modificationTree.readNode(TestModel.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(TestModel.OUTER_LIST_QNAME)), MapNode.class);
+
+ }
+
+ @Test
+ public void writeSubtreeReadChildren() {
+ DataTreeModification modificationTree = createEmptyModificationTree();
+ modificationTree.write(TestModel.TEST_PATH, createTestContainer());
+ Optional<NormalizedNode<?, ?>> potential = modificationTree.readNode(TWO_TWO_PATH);
+ assertPresentAndType(potential, MapEntryNode.class);
+ }
+
+ @Test
+ public void writeSubtreeDeleteChildren() {
+ DataTreeModification modificationTree = createEmptyModificationTree();
+ modificationTree.write(TestModel.TEST_PATH, createTestContainer());
+
+ // We verify data are present
+ Optional<NormalizedNode<?, ?>> potentialBeforeDelete = modificationTree.readNode(TWO_TWO_PATH);
+ assertPresentAndType(potentialBeforeDelete, MapEntryNode.class);
+
+ modificationTree.delete(TWO_TWO_PATH);
+ Optional<NormalizedNode<?, ?>> potentialAfterDelete = modificationTree.readNode(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.yangtools.yang.data.impl.schema.tree;
+
+
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+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.impl.ImmutableContainerNodeBuilder;
+import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+public class SchemaAwareApplyOperationRoot extends DataNodeContainerModificationStrategy<ContainerSchemaNode> {
+ private final SchemaContext context;
+
+ public SchemaAwareApplyOperationRoot(final SchemaContext context) {
+ super(context,ContainerNode.class);
+ this.context = context;
+ }
+
+ public SchemaContext getContext() {
+ return context;
+ }
+
+ @Override
+ public String toString() {
+ return "SchemaAwareApplyOperationRoot [context=" + context + "]";
+ }
+
+ @Override
+ @SuppressWarnings("rawtypes")
+ protected DataContainerNodeBuilder createBuilder(NormalizedNode<?, ?> original) {
+ return ImmutableContainerNodeBuilder.create((ContainerNode) original);
+ }
+}
--- /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.yangtools.yang.data.impl.schema.tree;
+
+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;
+
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.Set;
+
+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 QName TWO_QNAME = QName.create(TEST_QNAME, "two");
+ public static final QName THREE_QNAME = QName.create(TEST_QNAME, "three");
+
+
+ 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