}
@Override
- protected TreeNode applyWrite(final ModifiedNode modification,
+ protected TreeNode applyWrite(final ModifiedNode modification, final NormalizedNode<?, ?> newValue,
final Optional<TreeNode> currentMeta, final Version version) {
- final NormalizedNode<?, ?> newValue = modification.getWrittenValue();
final TreeNode newValueMeta = TreeNodeFactory.createTreeNode(newValue, version);
if (modification.getChildren().isEmpty()) {
protected final TreeNode applyMerge(final ModifiedNode modification, final TreeNode currentMeta,
final Version version) {
// Just overwrite whatever was there, but be sure to run validation
- verifyStructure(modification.getWrittenValue(), true);
+ final NormalizedNode<?, ?> newValue = modification.getWrittenValue();
+ verifyStructure(newValue, true);
modification.resolveModificationType(ModificationType.WRITE);
- return applyWrite(modification, null, version);
+ return applyWrite(modification, newValue, null, version);
}
@Override
- protected final TreeNode applyWrite(final ModifiedNode modification,
+ protected final TreeNode applyWrite(final ModifiedNode modification, final NormalizedNode<?, ?> newValue,
final Optional<TreeNode> currentMeta, final Version version) {
- return TreeNodeFactory.createTreeNode(modification.getWrittenValue(), version);
+ return TreeNodeFactory.createTreeNode(newValue, version);
}
@Override
}
@Override
- protected TreeNode applyWrite(final ModifiedNode modification, final Optional<TreeNode> currentMeta,
- final Version version) {
- final TreeNode ret = super.applyWrite(modification, currentMeta, version);
+ protected TreeNode applyWrite(final ModifiedNode modification, final NormalizedNode<?, ?> newValue,
+ final Optional<TreeNode> currentMeta, final Version version) {
+ final TreeNode ret = super.applyWrite(modification, newValue, currentMeta, version);
enforceCases(ret);
return ret;
}
}
@Override
- protected TreeNode applyWrite(final ModifiedNode modification, final Optional<TreeNode> currentMeta,
- final Version version) {
- final TreeNode ret = super.applyWrite(modification, currentMeta, version);
+ protected TreeNode applyWrite(final ModifiedNode modification, final NormalizedNode<?, ?> newValue,
+ final Optional<TreeNode> currentMeta, final Version version) {
+ final TreeNode ret = super.applyWrite(modification, newValue, currentMeta, version);
enforcer.enforceOnTreeNode(ret);
return ret;
}
}
@Override
- protected TreeNode applyWrite(final ModifiedNode modification, final Optional<TreeNode> currentMeta,
- final Version version) {
+ protected TreeNode applyWrite(final ModifiedNode modification, final NormalizedNode<?, ?> newValue,
+ final Optional<TreeNode> currentMeta, final Version version) {
final TreeNode validated = modification.getValidatedNode(this, currentMeta);
if (validated != null) {
return validated;
}
// FIXME: the result moved, make sure we enforce again
- return delegate.applyWrite(modification, currentMeta, version);
+ return delegate.applyWrite(modification, newValue, currentMeta, version);
}
@Override
*/
package org.opendaylight.yangtools.yang.data.impl.schema.tree;
+import static com.google.common.base.Verify.verifyNotNull;
import static java.util.Objects.requireNonNull;
import java.util.Collection;
*
* @return Currently-written value
*/
+ @Nonnull
NormalizedNode<?, ?> getWrittenValue() {
- return value;
+ return verifyNotNull(value);
}
/**
}
@Override
- protected TreeNode applyWrite(final ModifiedNode modification, final Optional<TreeNode> currentMeta,
- final Version version) {
- final TreeNode ret = super.applyWrite(modification, currentMeta, version);
+ protected TreeNode applyWrite(final ModifiedNode modification, final NormalizedNode<?, ?> newValue,
+ final Optional<TreeNode> currentMeta, final Version version) {
+ final TreeNode ret = super.applyWrite(modification, newValue, currentMeta, version);
enforcer.enforceOnTreeNode(ret);
return ret;
}
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
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.tree.ConflictingModificationAppliedException;
import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeConfiguration;
import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException;
// structure is usually verified when the transaction is sealed. To preserve correctness, we have
// to run that validation here.
modification.resolveModificationType(ModificationType.WRITE);
- result = applyWrite(modification, currentMeta, version);
+ result = applyWrite(modification, modification.getWrittenValue(), currentMeta, version);
verifyStructure(result.getData(), true);
} else {
result = applyMerge(modification, currentMeta.get(), version);
return modification.setSnapshot(Optional.of(result));
case WRITE:
modification.resolveModificationType(ModificationType.WRITE);
- return modification.setSnapshot(Optional.of(applyWrite(modification, currentMeta, version)));
+ return modification.setSnapshot(Optional.of(applyWrite(modification, modification.getWrittenValue(),
+ currentMeta, version)));
case NONE:
modification.resolveModificationType(ModificationType.UNMODIFIED);
return currentMeta;
*/
protected abstract TreeNode applyMerge(ModifiedNode modification, TreeNode currentMeta, Version version);
- protected abstract TreeNode applyWrite(ModifiedNode modification, Optional<TreeNode> currentMeta, Version version);
+ protected abstract TreeNode applyWrite(ModifiedNode modification, NormalizedNode<?, ?> newValue,
+ Optional<TreeNode> currentMeta, Version version);
/**
* Apply a nested operation. Since there may not actually be a nested operation
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.NormalizedNodeContainer;
+import org.opendaylight.yangtools.yang.data.api.schema.OrderedNodeContainer;
import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeConfiguration;
import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException;
import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
@Override
Optional<TreeNode> apply(final ModifiedNode modification, final Optional<TreeNode> storeMeta,
final Version version) {
- final Optional<TreeNode> ret;
- if (modification.getOperation() == LogicalOperation.TOUCH && !storeMeta.isPresent()) {
- // Container is not present, let's take care of the 'magically appear' part of our job
- ret = delegate.apply(modification, fakeMeta(version), version);
-
- // Fake container got removed: that is a no-op
- if (!ret.isPresent()) {
- modification.resolveModificationType(ModificationType.UNMODIFIED);
- return ret;
- }
-
- // If the delegate indicated SUBTREE_MODIFIED, account for the fake and report APPEARED
- if (modification.getModificationType() == ModificationType.SUBTREE_MODIFIED) {
- modification.resolveModificationType(ModificationType.APPEARED);
+ // The only way a tree node can disappear is through delete (which we handle here explicitly) or through
+ // actions of disappearResult(). It is therefore safe to perform Optional.get() on the results of
+ // delegate.apply()
+ final TreeNode ret;
+ if (modification.getOperation() == LogicalOperation.DELETE) {
+ if (modification.getChildren().isEmpty()) {
+ return delegate.apply(modification, storeMeta, version);
}
+ // Delete with children, implies it really is an empty write
+ ret = delegate.applyWrite(modification, emptyNode, storeMeta, version);
+ } else if (modification.getOperation() == LogicalOperation.TOUCH && !storeMeta.isPresent()) {
+ ret = applyTouch(modification, storeMeta, version);
} else {
- // Container is present, run normal apply operation
- ret = delegate.apply(modification, storeMeta, version);
-
- // Container was explicitly deleted, no magic required
- if (!ret.isPresent()) {
- return ret;
- }
- }
-
- /*
- * At this point ret is guaranteed to be present. We need to take care of the 'magically disappear' part of
- * our job. Check if there are any child nodes left. If there are none, remove this container and turn the
- * modification into a DISAPPEARED.
- */
- if (((NormalizedNodeContainer<?, ?, ?>) ret.get().getData()).getValue().isEmpty()) {
- modification.resolveModificationType(ModificationType.DISAPPEARED);
- return Optional.empty();
+ // No special handling required here, run normal apply operation
+ ret = delegate.apply(modification, storeMeta, version).get();
}
- return ret;
+ return disappearResult(modification, ret, storeMeta);
}
@Override
public Optional<ModificationApplyOperation> getChild(final PathArgument child) {
return delegate.getChild(child);
}
+
+ private TreeNode applyTouch(final ModifiedNode modification, final Optional<TreeNode> storeMeta,
+ final Version version) {
+ // Container is not present, let's take care of the 'magically appear' part of our job
+ final Optional<TreeNode> ret = delegate.apply(modification, fakeMeta(version), version);
+
+ // If the delegate indicated SUBTREE_MODIFIED, account for the fake and report APPEARED
+ if (modification.getModificationType() == ModificationType.SUBTREE_MODIFIED) {
+ modification.resolveModificationType(ModificationType.APPEARED);
+ }
+ return ret.get();
+ }
+
+ private static Optional<TreeNode> disappearResult(final ModifiedNode modification, final TreeNode result,
+ final Optional<TreeNode> storeMeta) {
+ // Check if the result is in fact empty before pulling any tricks
+ if (!isEmpty(result)) {
+ return Optional.of(result);
+ }
+
+ // We are pulling the 'disappear' trick, but what we report can be three different things
+ final ModificationType finalType;
+ if (!storeMeta.isPresent()) {
+ // ... there was nothing in the datastore, no change
+ finalType = ModificationType.UNMODIFIED;
+ } else if (modification.getModificationType() == ModificationType.WRITE) {
+ // ... this was an empty write, possibly originally a delete
+ finalType = ModificationType.DELETE;
+ } else {
+ // ... it really disappeared
+ finalType = ModificationType.DISAPPEARED;
+ }
+ modification.resolveModificationType(finalType);
+ return Optional.empty();
+ }
+
+ private static boolean isEmpty(final TreeNode treeNode) {
+ final NormalizedNode<?, ?> data = treeNode.getData();
+ if (data instanceof NormalizedNodeContainer) {
+ return ((NormalizedNodeContainer<?, ?, ?>) data).getValue().isEmpty();
+ }
+ if (data instanceof OrderedNodeContainer) {
+ return ((OrderedNodeContainer<?>) data).getSize() == 0;
+ }
+ throw new IllegalStateException("Unhandled data " + data);
+ }
}
}
@Override
- protected TreeNode applyWrite(final ModifiedNode modification,
+ protected TreeNode applyWrite(final ModifiedNode modification, final NormalizedNode<?, ?> newValue,
final Optional<TreeNode> currentMeta, final Version version) {
- final NormalizedNode<?, ?> newValue = modification.getWrittenValue();
final TreeNode newValueMeta = TreeNodeFactory.createTreeNode(newValue, version);
if (modification.getChildren().isEmpty()) {
import org.junit.Before;
import org.junit.Test;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.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.test.util.YangParserTestUtils;
public class Bug2690Test {
+ private static final YangInstanceIdentifier NAME_PATH = YangInstanceIdentifier.of(TestModel.NON_PRESENCE_QNAME)
+ .node(TestModel.NAME_QNAME);
+
private DataTree inMemoryDataTree;
@Before
final MapEntryNode fooEntryNode = ImmutableNodes.mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 1);
final MapEntryNode barEntryNode = ImmutableNodes.mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 2);
final MapNode mapNode1 = ImmutableNodes.mapNodeBuilder()
- .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TestModel.OUTER_LIST_QNAME))
+ .withNodeIdentifier(new NodeIdentifier(TestModel.OUTER_LIST_QNAME))
.withChild(fooEntryNode).build();
final MapNode mapNode2 = ImmutableNodes.mapNodeBuilder()
- .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TestModel.OUTER_LIST_QNAME))
+ .withNodeIdentifier(new NodeIdentifier(TestModel.OUTER_LIST_QNAME))
.withChild(barEntryNode).build();
final ContainerNode cont1 = Builders.containerBuilder()
- .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TestModel.TEST_QNAME))
+ .withNodeIdentifier(new NodeIdentifier(TestModel.TEST_QNAME))
.withChild(mapNode1).build();
final ContainerNode cont2 = Builders.containerBuilder()
- .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TestModel.TEST_QNAME))
+ .withNodeIdentifier(new NodeIdentifier(TestModel.TEST_QNAME))
.withChild(mapNode2).build();
final DataTreeModification modificationTree = inMemoryDataTree.takeSnapshot().newModification();
modificationTree.write(TestModel.TEST_PATH, cont1);
modificationTree.merge(TestModel.TEST_PATH, cont2);
- modificationTree.ready();
-
- inMemoryDataTree.validate(modificationTree);
- final DataTreeCandidate prepare = inMemoryDataTree.prepare(modificationTree);
- inMemoryDataTree.commit(prepare);
+ commit(modificationTree);
final DataTreeSnapshot snapshotAfterTx = inMemoryDataTree.takeSnapshot();
final DataTreeModification modificationAfterTx = snapshotAfterTx.newModification();
assertTrue(readNode.isPresent());
assertEquals(2, ((NormalizedNodeContainer<?,?,?>)readNode.get()).getValue().size());
}
+
+ @Test
+ public void testDeleteStructuralAndWriteChild() throws DataValidationFailedException {
+ final DataTreeModification modificationTree = setupTestDeleteStructuralAndWriteChild();
+ verifyTestDeleteStructuralAndWriteChild(modificationTree);
+ }
+
+ @Test
+ public void testDeleteStructuralAndWriteChildWithCommit() throws DataValidationFailedException {
+ final DataTreeModification modificationTree = setupTestDeleteStructuralAndWriteChild();
+ commit(modificationTree);
+ verifyTestDeleteStructuralAndWriteChild(inMemoryDataTree.takeSnapshot());
+ }
+
+ private DataTreeModification setupTestDeleteStructuralAndWriteChild() {
+ final DataTreeModification modificationTree = inMemoryDataTree.takeSnapshot().newModification();
+ modificationTree.delete(YangInstanceIdentifier.of(TestModel.NON_PRESENCE_QNAME));
+ modificationTree.write(NAME_PATH, Builders.leafBuilder()
+ .withNodeIdentifier(new NodeIdentifier(TestModel.NAME_QNAME)).withValue("abc").build());
+ return modificationTree;
+ }
+
+ private static void verifyTestDeleteStructuralAndWriteChild(final DataTreeSnapshot snapshot) {
+ final Optional<NormalizedNode<?, ?>> readNode = snapshot.readNode(NAME_PATH);
+ assertTrue(readNode.isPresent());
+ }
+
+ private void commit(final DataTreeModification modificationTree) throws DataValidationFailedException {
+ modificationTree.ready();
+
+ inMemoryDataTree.validate(modificationTree);
+ final DataTreeCandidate prepare = inMemoryDataTree.prepare(modificationTree);
+ inMemoryDataTree.commit(prepare);
+ }
}
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 QName NON_PRESENCE_QNAME = QName.create(TEST_QNAME, "non-presence");
+ public static final QName DEEP_CHOICE_QNAME = QName.create(TEST_QNAME, "deep-choice");
+ public static final QName A_LIST_QNAME = QName.create(TEST_QNAME, "a-list");
+ public static final QName A_NAME_QNAME = QName.create(TEST_QNAME, "a-name");
+
public static final YangInstanceIdentifier TEST_PATH = YangInstanceIdentifier.of(TEST_QNAME);
public static final YangInstanceIdentifier OUTER_LIST_PATH = YangInstanceIdentifier.builder(TEST_PATH)
.node(OUTER_LIST_QNAME).build();
description "Initial revision.";
}
+ container non-presence {
+ description "Deep structure of (structural) nodes to test structural ";
+
+ leaf name {
+ type string;
+ }
+
+ choice deep-choice {
+ list a-list {
+ key "a-name";
+
+ leaf a-name {
+ type string;
+ }
+ }
+
+ container b-container {
+ leaf b-name {
+ type string;
+ }
+ }
+ }
+ }
+
container test {
presence true;
choice choice1 {