import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
+import com.google.common.base.Verify;
import java.util.Collection;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
import org.opendaylight.yangtools.yang.data.api.schema.tree.ConflictingModificationAppliedException;
}
}
+ protected void recursivelyVerifyStructure(NormalizedNode<?, ?> value) {
+ final NormalizedNodeContainer container = (NormalizedNodeContainer) value;
+ for (final Object child : container.getValue()) {
+ checkArgument(child instanceof NormalizedNode);
+ final NormalizedNode<?, ?> castedChild = (NormalizedNode<?, ?>) child;
+ final Optional<ModificationApplyOperation> childOp = getChild(castedChild.getIdentifier());
+ if (childOp.isPresent()) {
+ childOp.get().recursivelyVerifyStructure(castedChild);
+ } else {
+ throw new SchemaValidationFailedException(
+ String.format("Child %s is not valid child according to schema.", castedChild.getIdentifier()));
+ }
+ }
+ }
+
@Override
protected TreeNode applyWrite(final ModifiedNode modification,
final Optional<TreeNode> currentMeta, final Version version) {
}
@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.
+ protected TreeNode applyMerge(final ModifiedNode modification, final TreeNode currentMeta, final Version version) {
+ /*
+ * The node which we are merging exists. We now need to expand any child operations implied by the value. Once
+ * we do that, ModifiedNode children will look like this node were a TOUCH and we will let applyTouch() do the
+ * heavy lifting of applying the children recursively (either through here or through applyWrite().
+ */
+ final NormalizedNode<?, ?> value = modification.getWrittenValue();
+
+ Verify.verify(value instanceof NormalizedNodeContainer, "Attempted to merge non-container %s", value);
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ final Collection<NormalizedNode<?, ?>> children = ((NormalizedNodeContainer) value).getValue();
+ for (NormalizedNode<?, ?> c : children) {
+ final PathArgument id = c.getIdentifier();
+ modification.modifyChild(id, resolveChildOperation(id).getChildPolicy(), version);
+ }
return applyTouch(modification, currentMeta, version);
}
+ private void mergeChildrenIntoModification(final ModifiedNode modification,
+ final Collection<NormalizedNode<?, ?>> children, final Version version) {
+ for (NormalizedNode<?, ?> c : children) {
+ final ModificationApplyOperation childOp = resolveChildOperation(c.getIdentifier());
+ final ModifiedNode childNode = modification.modifyChild(c.getIdentifier(), childOp.getChildPolicy(), version);
+ childOp.mergeIntoModifiedNode(childNode, c, version);
+ }
+ }
+
+ @Override
+ final void mergeIntoModifiedNode(final ModifiedNode modification, final NormalizedNode<?, ?> value,
+ final Version version) {
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ final Collection<NormalizedNode<?, ?>> children = ((NormalizedNodeContainer)value).getValue();
+
+ switch (modification.getOperation()) {
+ case NONE:
+ // Fresh node, just record a MERGE with a value
+ recursivelyVerifyStructure(value);
+ modification.updateValue(LogicalOperation.MERGE, value);
+ return;
+ case TOUCH:
+
+ mergeChildrenIntoModification(modification, children, version);
+ // We record empty merge value, since real children merges
+ // are already expanded. This is needed to satisfy non-null for merge
+ // original merge value can not be used since it mean different
+ // order of operation - parent changes are always resolved before
+ // children ones, and having node in TOUCH means children was modified
+ // before.
+ modification.updateValue(LogicalOperation.MERGE, createEmptyValue(value));
+ return;
+ case MERGE:
+ // Merging into an existing node. Merge data children modifications (maybe recursively) and mark as MERGE,
+ // invalidating cached snapshot
+ mergeChildrenIntoModification(modification, children, version);
+ modification.updateOperationType(LogicalOperation.MERGE);
+ return;
+ case DELETE:
+ // Delete performs a data dependency check on existence of the node. Performing a merge on DELETE means we
+ // are really performing a write. One thing that ruins that are any child modifications. If there are any,
+ // we will perform a read() to get the current state of affairs, turn this into into a WRITE and then
+ // append any child entries.
+ if (!modification.getChildren().isEmpty()) {
+ // Version does not matter here as we'll throw it out
+ final Optional<TreeNode> current = apply(modification, modification.getOriginal(), Version.initial());
+ if (current.isPresent()) {
+ modification.updateValue(LogicalOperation.WRITE, current.get().getData());
+ mergeChildrenIntoModification(modification, children, version);
+ return;
+ }
+ }
+
+ modification.updateValue(LogicalOperation.WRITE, value);
+ return;
+ case WRITE:
+ // We are augmenting a previous write. We'll just walk value's children, get the corresponding ModifiedNode
+ // and run recursively on it
+ mergeChildrenIntoModification(modification, children, version);
+ modification.updateOperationType(LogicalOperation.WRITE);
+ return;
+ }
+
+ throw new IllegalArgumentException("Unsupported operation " + modification.getOperation());
+ }
+
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ private NormalizedNode<?, ?> createEmptyValue(NormalizedNode<?, ?> value,
+ Collection<NormalizedNode<?, ?>> children) {
+ NormalizedNodeContainerBuilder builder = createBuilder(value);
+ for (NormalizedNode<?, ?> child : children) {
+ builder.removeChild(child.getIdentifier());
+ }
+ return builder.build();
+ }
+
@Override
protected TreeNode applyTouch(final ModifiedNode modification, final TreeNode currentMeta, final Version version) {
/*
@SuppressWarnings("rawtypes")
protected abstract NormalizedNodeContainerBuilder createBuilder(NormalizedNode<?, ?> original);
+
+ protected abstract NormalizedNode<?, ?> createEmptyValue(NormalizedNode<?, ?> original);
}
final Optional<TreeNode> current) throws IncorrectDataStructureException {
throw new IncorrectDataStructureException(path, "Subtree modification is not allowed.");
}
-}
\ No newline at end of file
+
+ @Override
+ void mergeIntoModifiedNode(final ModifiedNode node, final NormalizedNode<?, ?> value, final Version version) {
+
+ switch (node.getOperation()) {
+ // Delete performs a data dependency check on existence of the node. Performing a merge
+ // on DELETE means we
+ // are really performing a write.
+ case DELETE:
+ case WRITE:
+ node.write(value);
+ break;
+ default:
+ node.updateValue(LogicalOperation.MERGE, value);
+ }
+ }
+
+ @Override
+ void recursivelyVerifyStructure(NormalizedNode<?, ?> value) {
+ verifyStructure(value, false);
+ }
+}
}
@Override
- void checkApplicable(final YangInstanceIdentifier path,final NodeModification modification, final Optional<TreeNode> storeMetadata) {
+ void checkApplicable(final YangInstanceIdentifier path,final NodeModification modification,
+ final Optional<TreeNode> storeMetadata) {
throw new IllegalStateException("Schema Context is not available.");
}
ChildTrackingPolicy getChildPolicy() {
throw new IllegalStateException("Schema Context is not available.");
}
+
+ @Override
+ void mergeIntoModifiedNode(final ModifiedNode node, final NormalizedNode<?, ?> value, final Version version) {
+ throw new IllegalStateException("Schema Context is not available.");
+ }
+
+ @Override
+ void recursivelyVerifyStructure(NormalizedNode<?, ?> value) {
+ throw new IllegalStateException("Schema Context is not available.");
+ }
}
\ No newline at end of file
return ImmutableAugmentationNodeBuilder.create((AugmentationNode) original);
}
+ @Override
+ protected NormalizedNode<?, ?> createEmptyValue(NormalizedNode<?, ?> original) {
+ checkArgument(original instanceof AugmentationNode);
+ return ImmutableAugmentationNodeBuilder.create()
+ .withNodeIdentifier(((AugmentationNode) original).getIdentifier()).build();
+ }
+
private static AugmentationSchema createAugmentProxy(final AugmentationSchema schema, final DataNodeContainer resolved) {
final Set<DataSchemaNode> realChildSchemas = new HashSet<>();
for(final DataSchemaNode augChild : schema.getChildNodes()) {
enforceCases(ret);
return ret;
}
+
+ @Override
+ protected NormalizedNode<?, ?> createEmptyValue(NormalizedNode<?, ?> original) {
+ checkArgument(original instanceof ChoiceNode);
+ return ImmutableChoiceNodeBuilder.create().withNodeIdentifier(((ChoiceNode) original).getIdentifier()).build();
+ }
}
+
package org.opendaylight.yangtools.yang.data.impl.schema.tree;
import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.common.base.Preconditions;
import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
import org.opendaylight.yangtools.yang.data.api.schema.tree.TreeType;
checkArgument(original instanceof ContainerNode);
return ImmutableContainerNodeBuilder.create((ContainerNode) original);
}
+
+ @Override
+ protected NormalizedNode<?, ?> createEmptyValue(NormalizedNode<?, ?> original) {
+ Preconditions.checkArgument(original instanceof ContainerNode);
+ return ImmutableContainerNodeBuilder.create().withNodeIdentifier(((ContainerNode) original).getIdentifier())
+ .build();
+ }
}
checkArgument(original instanceof MapEntryNode);
return ImmutableMapEntryNodeBuilder.create((MapEntryNode) original);
}
+
+ @Override
+ protected NormalizedNode<?, ?> createEmptyValue(NormalizedNode<?, ?> original) {
+ checkArgument(original instanceof MapEntryNode);
+ return ImmutableMapEntryNodeBuilder.create().withNodeIdentifier(((MapEntryNode) original).getIdentifier())
+ .build();
+ }
}
\ No newline at end of file
for (final ModifiedNode modChild : modification.getChildren()) {
switch (modChild.getOperation()) {
case WRITE:
- if (!modChild.getOriginal().isPresent()) {
+ if (!checkOriginalPresent(modChild)) {
result++;
}
break;
case MERGE:
- if (!current.isPresent()) {
+ if (!checkOriginalPresent(modChild)) {
result++;
}
break;
case DELETE:
- if (modChild.getOriginal().isPresent()) {
+ if (checkOriginalPresent(modChild)) {
result--;
}
break;
return result;
}
+ private static boolean checkOriginalPresent(ModifiedNode child) {
+ return child.getOriginal().isPresent();
+ }
+
@Override
protected void checkTouchApplicable(final YangInstanceIdentifier path, final NodeModification modification,
final Optional<TreeNode> current) throws DataValidationFailedException {
protected ChildTrackingPolicy getChildPolicy() {
return delegate.getChildPolicy();
}
+
+ @Override
+ void mergeIntoModifiedNode(final ModifiedNode node, final NormalizedNode<?, ?> value, final Version version) {
+ delegate.mergeIntoModifiedNode(node, value, version);
+ }
+
+ @Override
+ void recursivelyVerifyStructure(NormalizedNode<?, ?> value) {
+ delegate.recursivelyVerifyStructure(value);
+ }
}
*/
abstract ChildTrackingPolicy getChildPolicy();
+ /**
+ * Stage a merge operation into a {@link ModifiedNode} such that it will be processed correctly by
+ * {@link #apply(ModifiedNode, Optional, Version)}. This method is the context which is introducing this operation,
+ * and so any overheads are charged to whoever is in control of the access pattern.
+ *
+ * @param modification Original modification node
+ * @param value Value which should be merge into the modification
+ * @param version Data version as carried in the containing {@link InMemoryDataTreeModification}
+ */
+ abstract void mergeIntoModifiedNode(ModifiedNode modification, NormalizedNode<?, ?> value, Version version);
+
/**
* Returns a suboperation for specified tree node
*
*/
@Override
public abstract Optional<ModificationApplyOperation> getChild(PathArgument child);
+
+ abstract void recursivelyVerifyStructure(NormalizedNode<?, ?> value);
}
import javax.annotation.concurrent.NotThreadSafe;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
-import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
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;
/**
* Node Modification Node and Tree
*
- * Tree which structurally resembles data tree and captures client modifications
- * to the data store 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 TreeNode} which represents original
+ * state as tracked by {@link #getOriginal()}.
*
- * This tree is lazily created and populated via {@link #modifyChild(PathArgument)}
- * and {@link TreeNode} which represents original state as tracked by {@link #getOriginal()}.
+ * The contract is that the state information exposed here preserves the temporal ordering of whatever modifications
+ * were executed. A child's effects pertain to data node as modified by its ancestors. This means that in order to
+ * reconstruct the effective data node presentation, it is sufficient to perform a depth-first pre-order traversal of
+ * the tree.
*/
@NotThreadSafe
final class ModifiedNode extends NodeModification implements StoreTreeNode<ModifiedNode> {
this.children = childPolicy.createMap();
}
- /**
- * Return the value which was written to this node.
- *
- * @return Currently-written value
- */
- public NormalizedNode<?, ?> getWrittenValue() {
- return value;
- }
-
@Override
public PathArgument getIdentifier() {
return identifier;
}
+ @Override
+ LogicalOperation getOperation() {
+ return operation;
+ }
+
@Override
Optional<TreeNode> getOriginal() {
return original;
}
- @Override
- LogicalOperation getOperation() {
- return operation;
+ /**
+ * Return the value which was written to this node. The returned object is only valid for
+ * {@link LogicalOperation#MERGE} and {@link LogicalOperation#WRITE}.
+ * operations. It should only be consulted when this modification is going to end up being
+ * {@link ModificationType#WRITE}.
+ *
+ * @return Currently-written value
+ */
+ NormalizedNode<?, ?> getWrittenValue() {
+ return value;
}
/**
return Optional.absent();
case NONE:
case TOUCH:
- return metadataFromSnapshot(child);
case MERGE:
- // MERGE is half-way between TOUCH and WRITE. If the child exists in data, it behaves as a WRITE, otherwise
- // it behaves as a TOUCH
- if (NormalizedNodes.findNode(value, child).isPresent()) {
- return metadataFromData(child, modVersion);
- } else {
- return metadataFromSnapshot(child);
- }
+ return metadataFromSnapshot(child);
case WRITE:
// WRITE implies presence based on written data
return metadataFromData(child, modVersion);
final ModifiedNode newlyCreated = new ModifiedNode(child, currentMetadata, childPolicy);
+ if (operation == LogicalOperation.MERGE && value != null) {
+ /*
+ * We are attempting to modify a previously-unmodified part of a MERGE node. If the
+ * value contains this component, we need to materialize it as a MERGE modification.
+ */
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ final Optional<NormalizedNode<?, ?>> childData = ((NormalizedNodeContainer)value).getChild(child);
+ if (childData.isPresent()) {
+ newlyCreated.updateValue(LogicalOperation.MERGE, childData.get());
+ }
+ }
+
children.put(child, newlyCreated);
return newlyCreated;
}
* @param value
*/
void write(final NormalizedNode<?, ?> value) {
- pushWrite(value);
+ updateValue(LogicalOperation.WRITE, value);
children.clear();
}
- // Promote the node to write, but do not lose children
- void pushWrite(final NormalizedNode<?, ?> value) {
- clearSnapshot();
- updateOperationType(LogicalOperation.WRITE);
- this.value = value;
- }
-
- void merge(final NormalizedNode<?, ?> value) {
- clearSnapshot();
- updateOperationType(LogicalOperation.MERGE);
-
- /*
- * Blind overwrite of any previous data is okay, no matter whether the node
- * is simple or complex type.
- *
- * If this is a simple or complex type with unkeyed children, this merge will
- * be turned into a write operation, overwriting whatever was there before.
- *
- * If this is a container with keyed children, there are two possibilities:
- * - if it existed before, this value will never be consulted and the children
- * will get explicitly merged onto the original data.
- * - if it did not exist before, this value will be used as a seed write and
- * children will be merged into it.
- * In either case we rely on OperationWithModification to manipulate the children
- * before calling this method, so unlike a write we do not want to clear them.
- */
- this.value = value;
- }
-
/**
* Seal the modification node and prune any children which has not been modified.
*
return snapshot;
}
- private void updateOperationType(final LogicalOperation type) {
+ void updateOperationType(final LogicalOperation type) {
operation = type;
modType = null;
modType = type;
}
+ /**
+ * Update this node's value and operation type without disturbing any of its child modifications.
+ *
+ * @param type New operation type
+ * @param value New node value
+ */
+ void updateValue(final LogicalOperation type, final NormalizedNode<?, ?> value) {
+ this.value = Preconditions.checkNotNull(value);
+ updateOperationType(type);
+ }
+
/**
* Return the physical modification done to data. May return null if the
* operation has not been applied to the underlying tree. This is different
return modType;
}
- /**
- * Create a node which will reflect the state of this node, except it will behave as newly-written
- * value. This is useful only for merge validation.
- *
- * @param value Value associated with the node
- * @return An isolated node. This node should never reach a datatree.
- */
- ModifiedNode asNewlyWritten(final NormalizedNode<?, ?> value) {
- /*
- * We are instantiating an "equivalent" of this node. Currently the only callsite does not care
- * about the actual iteration order, so we do not have to specify the same tracking policy as
- * we were instantiated with. Since this is the only time we need to know that policy (it affects
- * only things in constructor), we do not want to retain it (saves some memory on per-instance
- * basis).
- *
- * We could reconstruct it using two instanceof checks (to undo what the constructor has done),
- * which would give perfect results. The memory saving would be at most 32 bytes of a short-lived
- * object, so let's not bother with that.
- */
- final ModifiedNode ret = new ModifiedNode(getIdentifier(), Optional.<TreeNode>absent(), ChildTrackingPolicy.UNORDERED);
- ret.write(value);
- return ret;
- }
-
public static ModifiedNode createUnmodified(final TreeNode metadataTree, final ChildTrackingPolicy childPolicy) {
return new ModifiedNode(metadataTree.getIdentifier(), Optional.of(metadataTree), childPolicy);
}
import com.google.common.base.Preconditions;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
-import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.TreeNode;
import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.Version;
applyOperation.verifyStructure(value, false);
}
- private void recursiveMerge(final NormalizedNode<?,?> data, final Version version) {
- if (data instanceof NormalizedNodeContainer) {
- @SuppressWarnings({ "rawtypes", "unchecked" })
- final NormalizedNodeContainer<?,?, NormalizedNode<PathArgument, ?>> dataContainer =
- (NormalizedNodeContainer) data;
-
- /*
- * If there was write before on this node and it is of NormalizedNodeContainer type merge would overwrite
- * our changes. So we create write modifications from data children to retain children created by previous
- * write operation. These writes will then be pushed down in the tree while there are merge modifications
- * on these children
- */
- if (modification.getOperation() == LogicalOperation.WRITE) {
- @SuppressWarnings({ "rawtypes", "unchecked" })
- final NormalizedNodeContainer<?,?, NormalizedNode<PathArgument, ?>> oldDataContainer =
- (NormalizedNodeContainer) modification.getWrittenValue();
- for (final NormalizedNode<PathArgument, ?> c : oldDataContainer.getValue()) {
- final PathArgument childId = c.getIdentifier();
-
- // Acquire the child operation type if available, fall back to NONE
- final Optional<ModifiedNode> maybeChild = modification.getChild(childId);
- if (maybeChild.isPresent()) {
- final ModifiedNode child = maybeChild.get();
- final LogicalOperation op = child.getOperation();
- if (op == LogicalOperation.TOUCH || op == LogicalOperation.NONE) {
- child.pushWrite(c);
- }
- } else {
- // Not present, issue a write
- forChild(childId, version).write(c);
- }
- }
- }
- for (final NormalizedNode<PathArgument, ?> child : dataContainer.getValue()) {
- final PathArgument childId = child.getIdentifier();
- forChild(childId, version).recursiveMerge(child, version);
- }
- }
-
- modification.merge(data);
- }
-
void merge(final NormalizedNode<?, ?> data, final Version version) {
/*
* A merge operation will end up overwriting parts of the tree, retaining others. We want to
* written. In order to do that, we first pretend the data was written, run verification and
* then perform the merge -- with the explicit assumption that adding the newly-validated
* data with the previously-validated data will not result in invalid data.
- *
- * FIXME: Should be this moved to recursive merge and run for each node?
*/
- applyOperation.verifyStructure(data, false);
- recursiveMerge(data, version);
+ applyOperation.verifyStructure(data, true);
+ applyOperation.mergeIntoModifiedNode(modification, data, version);
}
void delete() {
final ModifiedNode modification) {
return new OperationWithModification(operation, modification);
}
-
- private OperationWithModification forChild(final PathArgument childId, final Version version) {
- final Optional<ModificationApplyOperation> maybeChildOp = applyOperation.getChild(childId);
- Preconditions.checkArgument(maybeChildOp.isPresent(),
- "Attempted to apply operation to non-existent child %s", childId);
-
- final ModificationApplyOperation childOp = maybeChildOp.get();
- final ModifiedNode childMod = modification.modifyChild(childId, childOp.getChildPolicy(), version);
-
- return from(childOp, childMod);
- }
}
return ImmutableOrderedLeafSetNodeBuilder.create((OrderedLeafSetNode<?>) original);
}
+ @Override
+ protected NormalizedNode<?, ?> createEmptyValue(NormalizedNode<?, ?> original) {
+ checkArgument(original instanceof OrderedLeafSetNode<?>);
+ return ImmutableOrderedLeafSetNodeBuilder.create()
+ .withNodeIdentifier(((OrderedLeafSetNode<?>) original).getIdentifier()).build();
+ }
+
@Override
public Optional<ModificationApplyOperation> getChild(final YangInstanceIdentifier.PathArgument identifier) {
if (identifier instanceof YangInstanceIdentifier.NodeWithValue) {
return ImmutableOrderedMapNodeBuilder.create((OrderedMapNode) original);
}
+ @Override
+ protected NormalizedNode<?, ?> createEmptyValue(NormalizedNode<?, ?> original) {
+ checkArgument(original instanceof OrderedMapNode);
+ return ImmutableOrderedMapNodeBuilder.create().withNodeIdentifier(((OrderedMapNode) original).getIdentifier())
+ .build();
+ }
+
@Override
public Optional<ModificationApplyOperation> getChild(final YangInstanceIdentifier.PathArgument identifier) {
if (identifier instanceof YangInstanceIdentifier.NodeIdentifierWithPredicates) {
getDelegate().verifyStructure(modification, verifyChildren);
}
+ @Override
+ void recursivelyVerifyStructure(NormalizedNode<?, ?> value) {
+ getDelegate().recursivelyVerifyStructure(value);
+ }
+
@Override
final ChildTrackingPolicy getChildPolicy() {
return getDelegate().getChildPolicy();
}
+ @Override
+ final void mergeIntoModifiedNode(final ModifiedNode node, final NormalizedNode<?, ?> value, final Version version) {
+ getDelegate().mergeIntoModifiedNode(node, value, version);
+ }
+
/**
* Return the underlying delegate.
*
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
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.TreeType;
import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.TreeNode;
checkNotConflicting(path, original.get(), current.get());
} else if(original.isPresent()) {
throw new ConflictingModificationAppliedException(path,"Node was deleted by other transaction.");
- } else if(current.isPresent()) {
- throw new ConflictingModificationAppliedException(path,"Node was created by other transaction.");
+ } else if (current.isPresent()) {
+ throw new ConflictingModificationAppliedException(path, "Node was created by other transaction.");
}
}
// This is a slight optimization: a merge on a non-existing node equals to a write
if (currentMeta.isPresent()) {
- result = applyMerge(modification,currentMeta.get(), version);
+ result = applyMerge(modification, currentMeta.get(), version);
} else {
modification.resolveModificationType(ModificationType.WRITE);
result = applyWrite(modification, currentMeta, version);
delegate.verifyStructure(modification, verifyChildren);
}
+ @Override
+ void recursivelyVerifyStructure(NormalizedNode<?, ?> value) {
+ delegate.recursivelyVerifyStructure(value);
+ }
+
@Override
ChildTrackingPolicy getChildPolicy() {
return delegate.getChildPolicy();
}
+ @Override
+ void mergeIntoModifiedNode(ModifiedNode modification, NormalizedNode<?, ?> value, Version version) {
+ delegate.mergeIntoModifiedNode(modification, value, version);
+ }
+
@Override
public Optional<ModificationApplyOperation> getChild(final PathArgument child) {
return delegate.getChild(child);
checkArgument(original instanceof UnkeyedListEntryNode);
return ImmutableUnkeyedListEntryNodeBuilder.create((UnkeyedListEntryNode) original);
}
+
+ @Override
+ protected NormalizedNode<?, ?> createEmptyValue(NormalizedNode<?, ?> original) {
+ checkArgument(original instanceof UnkeyedListEntryNode);
+ return ImmutableUnkeyedListEntryNodeBuilder.create()
+ .withNodeIdentifier(((UnkeyedListEntryNode) original).getIdentifier()).build();
+ }
}
\ No newline at end of file
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListEntryNode;
import org.opendaylight.yangtools.yang.data.api.schema.tree.IncorrectDataStructureException;
-import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
import org.opendaylight.yangtools.yang.data.api.schema.tree.TreeType;
import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.MutableTreeNode;
import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.TreeNode;
}
@Override
- protected TreeNode applyMerge(final ModifiedNode modification, final TreeNode currentMeta,
- final Version version) {
- modification.resolveModificationType(ModificationType.WRITE);
- return applyWrite(modification, Optional.of(currentMeta), version);
+ protected TreeNode applyMerge(final ModifiedNode modification, final TreeNode currentMeta, final Version version) {
+ throw new IllegalStateException("Invalid merge into unkeyed list");
}
@Override
}
+ @Override
+ void recursivelyVerifyStructure(NormalizedNode<?, ?> value) {
+ // NOOP
+ }
+
@Override
protected void checkTouchApplicable(final YangInstanceIdentifier path, final NodeModification modification,
final Optional<TreeNode> current) throws IncorrectDataStructureException {
throw new IncorrectDataStructureException(path, "Subtree modification is not allowed.");
}
+
+ @Override
+ void mergeIntoModifiedNode(final ModifiedNode node, final NormalizedNode<?, ?> value, final Version version) {
+ // Unkeyed lists are always replaced
+ node.write(value);
+ }
}
return ImmutableLeafSetNodeBuilder.create((LeafSetNode<?>) original);
}
+ @Override
+ protected NormalizedNode<?, ?> createEmptyValue(NormalizedNode<?, ?> original) {
+ checkArgument(original instanceof LeafSetNode<?>);
+ return ImmutableLeafSetNodeBuilder.create().withNodeIdentifier(((LeafSetNode<?>) original).getIdentifier())
+ .build();
+ }
+
@Override
public Optional<ModificationApplyOperation> getChild(final YangInstanceIdentifier.PathArgument identifier) {
if (identifier instanceof YangInstanceIdentifier.NodeWithValue) {
return ImmutableMapNodeBuilder.create((MapNode) original);
}
+ @Override
+ protected NormalizedNode<?, ?> createEmptyValue(final NormalizedNode<?, ?> original) {
+ checkArgument(original instanceof MapNode);
+ return ImmutableMapNodeBuilder.create().withNodeIdentifier(((MapNode) original).getIdentifier()).build();
+ }
+
@Override
public Optional<ModificationApplyOperation> getChild(final YangInstanceIdentifier.PathArgument identifier) {
if (identifier instanceof YangInstanceIdentifier.NodeIdentifierWithPredicates) {
--- /dev/null
+/*
+ * Copyright (c) 2015 Pantheon Technologies s.r.o. 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.collect.ImmutableMap;
+import com.google.common.collect.ImmutableMap.Builder;
+import java.io.File;
+import java.net.URI;
+import org.junit.Before;
+import org.junit.Test;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.yang.common.SimpleDateFormatUtil;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
+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.tree.DataTreeModification;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.TipProducingDataTree;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.TreeType;
+import org.opendaylight.yangtools.yang.data.impl.RetestUtils;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.CollectionNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeAttrBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+public class Bug4295Test {
+
+ private TipProducingDataTree inMemoryDataTree;
+ private SchemaContext context;
+ private QName root;
+ private QName subRoot;
+ private QName outerList;
+ private QName innerList;
+ private QName oId;
+ private QName iId;
+ private QName oLeaf;
+ private QName iLeaf;
+ private QNameModule foo;
+
+ @Before
+ public void init() throws Exception {
+ final File resourceFile = new File(Bug4295Test.class.getResource("/bug-4295/foo.yang")
+ .toURI());
+ context = RetestUtils.parseYangSources(resourceFile);
+ foo = QNameModule.create(new URI("foo"), SimpleDateFormatUtil.getRevisionFormat().parse("1970-01-01"));
+ root = QName.create(foo, "root");
+ subRoot = QName.create(foo, "sub-root");
+ outerList = QName.create(foo, "outer-list");
+ innerList = QName.create(foo, "inner-list");
+ oId = QName.create(foo, "o-id");
+ iId = QName.create(foo, "i-id");
+ oLeaf = QName.create(foo, "o");
+ iLeaf = QName.create(foo, "i");
+ inMemoryDataTree = InMemoryDataTreeFactory.getInstance().create(TreeType.OPERATIONAL);
+ inMemoryDataTree.setSchemaContext(context);
+ }
+
+ @Test
+ public void test() throws DataValidationFailedException {
+ firstModification();
+ secondModification(1);
+ secondModification(2);
+ secondModification(3);
+ }
+
+
+ private void firstModification() throws DataValidationFailedException {
+ /* MERGE */
+ MapNode outerListNode = ImmutableNodes.mapNodeBuilder().withNodeIdentifier(NodeIdentifier.create(outerList))
+ .withChild(createOuterListEntry("1", "o-1"))
+ .withChild(createOuterListEntry("2", "o-2"))
+ .withChild(createOuterListEntry("3", "o-3"))
+ .build();
+ ContainerNode rootContainerNode = createRootContainerBuilder()
+ .withChild(
+ createSubRootContainerBuilder()
+ .withChild(outerListNode)
+ .build())
+ .build();
+ YangInstanceIdentifier path = YangInstanceIdentifier.of(root);
+ DataTreeModification modification = inMemoryDataTree.takeSnapshot().newModification();
+ modification.merge(path, rootContainerNode);
+
+ /* WRITE INNER LIST WITH ENTRIES*/
+ MapNode innerListNode = createInnerListBuilder()
+ .withChild(createInnerListEntry("a", "i-a"))
+ .withChild(createInnerListEntry("b", "i-b"))
+ .build();
+ path = YangInstanceIdentifier.of(root).node(subRoot).node(outerList).node(createOuterListEntryPath("2")).node(innerList);
+ modification.write(path, innerListNode);
+
+ /* COMMIT */
+ modification.ready();
+ inMemoryDataTree.validate(modification);
+ inMemoryDataTree.commit(inMemoryDataTree.prepare(modification));
+ }
+
+ private void secondModification(int testScenarioNumber) throws DataValidationFailedException {
+ /* MERGE */
+ MapNode outerListNode = ImmutableNodes.mapNodeBuilder().withNodeIdentifier(NodeIdentifier.create(outerList))
+ .withChild(createOuterListEntry("3", "o-3"))
+ .withChild(createOuterListEntry("4", "o-4"))
+ .withChild(createOuterListEntry("5", "o-5"))
+ .build();
+
+ ContainerNode rootContainerNode = createRootContainerBuilder()
+ .withChild(
+ createSubRootContainerBuilder()
+ .withChild(outerListNode)
+ .build())
+ .build();
+
+ YangInstanceIdentifier path = YangInstanceIdentifier.of(root);
+ DataTreeModification modification = inMemoryDataTree.takeSnapshot().newModification();
+ modification.merge(path, rootContainerNode);
+
+ if (testScenarioNumber == 1) {
+ /* WRITE EMPTY INNER LIST */
+ writeEmptyInnerList(modification, "2");
+ } else if (testScenarioNumber == 2) {
+ /* WRITE INNER LIST ENTRY */
+ MapEntryNode innerListEntryA = createInnerListEntry("a", "i-a-2");
+ path = YangInstanceIdentifier.of(root).node(subRoot).node(outerList).node(createOuterListEntryPath("2"))
+ .node(innerList).node(createInnerListEntryPath("a"));
+ modification.write(path, innerListEntryA);
+ } else if (testScenarioNumber == 3) {
+ /* WRITE INNER LIST WITH ENTRIES */
+ MapNode innerListNode = createInnerListBuilder().withChild(createInnerListEntry("a", "i-a-3"))
+ .withChild(createInnerListEntry("c", "i-c")).build();
+ path = YangInstanceIdentifier.of(root).node(subRoot).node(outerList).node(createOuterListEntryPath("2"))
+ .node(innerList);
+ modification.write(path, innerListNode);
+ }
+
+ /* COMMIT */
+ modification.ready();
+ inMemoryDataTree.validate(modification);
+ inMemoryDataTree.commit(inMemoryDataTree.prepare(modification));
+ }
+
+ private void writeEmptyInnerList(DataTreeModification modification, String outerListEntryKey) {
+ YangInstanceIdentifier path = YangInstanceIdentifier.of(root).node(subRoot).node(outerList)
+ .node(createOuterListEntryPath(outerListEntryKey)).node(innerList);
+ modification.write(path, createInnerListBuilder().build());
+ }
+
+ private DataContainerNodeAttrBuilder<NodeIdentifier, ContainerNode> createRootContainerBuilder() {
+ return ImmutableContainerNodeBuilder.create().withNodeIdentifier(
+ new YangInstanceIdentifier.NodeIdentifier(root));
+ }
+
+ private DataContainerNodeAttrBuilder<NodeIdentifier, ContainerNode> createSubRootContainerBuilder() {
+ return ImmutableContainerNodeBuilder.create().withNodeIdentifier(
+ new YangInstanceIdentifier.NodeIdentifier(subRoot));
+ }
+
+ private CollectionNodeBuilder<MapEntryNode, MapNode> createInnerListBuilder() {
+ return ImmutableNodes.mapNodeBuilder().withNodeIdentifier(NodeIdentifier.create(innerList));
+ }
+
+ private NodeIdentifierWithPredicates createInnerListEntryPath(String keyValue) {
+ Builder<QName, Object> builder = ImmutableMap.builder();
+ ImmutableMap<QName, Object> keys = builder.put(iId, keyValue).build();
+ return new YangInstanceIdentifier.NodeIdentifierWithPredicates(innerList, keys);
+ }
+
+ private NodeIdentifierWithPredicates createOuterListEntryPath(String keyValue) {
+ Builder<QName, Object> builder = ImmutableMap.builder();
+ ImmutableMap<QName, Object> keys = builder.put(oId, keyValue).build();
+ return new YangInstanceIdentifier.NodeIdentifierWithPredicates(outerList, keys);
+ }
+
+ private MapEntryNode createOuterListEntry(String keyValue, String leafValue) {
+ return ImmutableNodes.mapEntryBuilder(outerList, oId, keyValue)
+ .withChild(ImmutableNodes.leafNode(oLeaf, leafValue)).build();
+ }
+
+ private MapEntryNode createInnerListEntry(String keyValue, String leafValue) {
+ return ImmutableNodes.mapEntryBuilder(innerList, iId, keyValue)
+ .withChild(ImmutableNodes.leafNode(iLeaf, leafValue)).build();
+ }
+}
\ No newline at end of file
.node(mapEntryPath2).build();
key.clear();
- key.put(MIN_MAX_KEY_LEAF_QNAME, "bar");
+ key.put(MIN_MAX_KEY_LEAF_QNAME, "baz");
mapEntryPath2 = new YangInstanceIdentifier.NodeIdentifierWithPredicates(MIN_MAX_LIST_QNAME, key);
--- /dev/null
+module foo {
+ namespace "foo";
+ prefix foo;
+
+ container root {
+ container sub-root {
+ list outer-list {
+ key "o-id";
+ leaf o-id {
+ type string;
+ }
+ list inner-list {
+ key "i-id";
+ leaf i-id {
+ type string;
+ }
+ leaf i {
+ type string;
+ }
+ }
+ leaf o {
+ type string;
+ }
+ }
+ }
+ }
+}