*/
package org.opendaylight.controller.md.sal.dom.store.impl.tree.data;
-import static com.google.common.base.Preconditions.checkState;
-
import java.util.Map.Entry;
-import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
-import org.opendaylight.controller.md.sal.dom.store.impl.OperationWithModification;
+import javax.annotation.concurrent.GuardedBy;
+
import org.opendaylight.controller.md.sal.dom.store.impl.tree.DataTreeModification;
-import org.opendaylight.controller.md.sal.dom.store.impl.tree.ModificationApplyOperation;
-import org.opendaylight.controller.md.sal.dom.store.impl.tree.StoreUtils;
+import org.opendaylight.controller.md.sal.dom.store.impl.tree.ModificationType;
import org.opendaylight.controller.md.sal.dom.store.impl.tree.TreeNodeUtils;
+import org.opendaylight.controller.md.sal.dom.store.impl.tree.spi.TreeNode;
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;
final class InMemoryDataTreeModification implements DataTreeModification {
private static final Logger LOG = LoggerFactory.getLogger(InMemoryDataTreeModification.class);
-
- /*
- * FIXME: the thread safety of concurrent write/delete/read/seal operations
- * needs to be evaluated.
- */
- private static final AtomicIntegerFieldUpdater<InMemoryDataTreeModification> SEALED_UPDATER =
- AtomicIntegerFieldUpdater.newUpdater(InMemoryDataTreeModification.class, "sealed");
- private volatile int sealed = 0;
-
private final ModificationApplyOperation strategyTree;
private final InMemoryDataTreeSnapshot snapshot;
- private final NodeModification rootNode;
+ 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 = NodeModification.createUnmodified(snapshot.getRootNode());
+ this.rootNode = ModifiedNode.createUnmodified(snapshot.getRootNode());
}
- NodeModification getRootModification() {
+ ModifiedNode getRootModification() {
return rootNode;
}
- ModificationApplyOperation getStrategy() {
- return strategyTree;
- }
+ ModificationApplyOperation getStrategy() {
+ return strategyTree;
+ }
@Override
- public void write(final InstanceIdentifier path, final NormalizedNode<?, ?> value) {
+ public synchronized void write(final InstanceIdentifier path, final NormalizedNode<?, ?> value) {
checkSealed();
resolveModificationFor(path).write(value);
}
@Override
- public void merge(final InstanceIdentifier path, final NormalizedNode<?, ?> data) {
+ public synchronized void merge(final InstanceIdentifier path, final NormalizedNode<?, ?> data) {
checkSealed();
mergeImpl(resolveModificationFor(path),data);
}
}
@Override
- public void delete(final InstanceIdentifier path) {
+ public synchronized void delete(final InstanceIdentifier path) {
checkSealed();
resolveModificationFor(path).delete();
}
@Override
- public Optional<NormalizedNode<?, ?>> readNode(final InstanceIdentifier path) {
- Entry<InstanceIdentifier, NodeModification> modification = TreeNodeUtils.findClosestsOrFirstMatch(rootNode, path, NodeModification.IS_TERMINAL_PREDICATE);
-
- Optional<StoreMetadataNode> result = resolveSnapshot(modification);
+ 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(modification.getKey(), data, path);
+ return NormalizedNodeUtils.findNode(key, data, path);
+ } else {
+ return Optional.absent();
}
- return Optional.absent();
}
- private Optional<StoreMetadataNode> resolveSnapshot(
- final Entry<InstanceIdentifier, NodeModification> keyModification) {
- InstanceIdentifier path = keyModification.getKey();
- NodeModification modification = keyModification.getValue();
- return resolveSnapshot(path, modification);
- }
+ private Optional<TreeNode> resolveSnapshot(final InstanceIdentifier path,
+ final ModifiedNode modification) {
+ final Optional<Optional<TreeNode>> potentialSnapshot = modification.getSnapshotCache();
+ if(potentialSnapshot.isPresent()) {
+ return potentialSnapshot.get();
+ }
- private Optional<StoreMetadataNode> resolveSnapshot(final InstanceIdentifier path,
- final NodeModification modification) {
try {
- Optional<Optional<StoreMetadataNode>> potentialSnapshot = modification.getSnapshotCache();
- if(potentialSnapshot.isPresent()) {
- return potentialSnapshot.get();
- }
return resolveModificationStrategy(path).apply(modification, modification.getOriginal(),
- StoreUtils.increase(snapshot.getRootNode().getSubtreeVersion()));
+ snapshot.getRootNode().getSubtreeVersion().next());
} catch (Exception e) {
LOG.error("Could not create snapshot for {}:{}", path,modification,e);
throw e;
}
private OperationWithModification resolveModificationFor(final InstanceIdentifier path) {
- NodeModification modification = rootNode;
+ ModifiedNode modification = rootNode;
// We ensure strategy is present.
ModificationApplyOperation operation = resolveModificationStrategy(path);
for (PathArgument pathArg : path.getPath()) {
}
@Override
- public void seal() {
- final boolean success = SEALED_UPDATER.compareAndSet(this, 0, 1);
- Preconditions.checkState(success, "Attempted to seal an already-sealed Data Tree.");
+ public synchronized void ready() {
+ Preconditions.checkState(!sealed, "Attempted to seal an already-sealed Data Tree.");
+ sealed = true;
rootNode.seal();
}
+ @GuardedBy("this")
private void checkSealed() {
- checkState(sealed == 0, "Data Tree is sealed. No further modifications allowed.");
+ Preconditions.checkState(!sealed, "Data Tree is sealed. No further modifications allowed.");
}
@Override
return "MutableDataTree [modification=" + rootNode + "]";
}
- @Override
- public DataTreeModification newModification(ModificationApplyOperation applyOper) {
- // FIXME: transaction chaining
- throw new UnsupportedOperationException("Implement this as part of transaction chaining");
- }
+ @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();
+ }
}