X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;f=yang%2Fyang-data-impl%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fyangtools%2Fyang%2Fdata%2Fimpl%2Fschema%2Ftree%2FInMemoryDataTreeModification.java;h=cf9b2c3377c92f46daf21b8361bccd345e603335;hb=fce70654f7d387a8454c8441459ffff03ad0f1a2;hp=3c903e7831b33f6cb459a4404b2a3be0f15078d7;hpb=949b5d427994f87a8000f5b2099d2ceff97dfd85;p=yangtools.git diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTreeModification.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTreeModification.java index 3c903e7831..cf9b2c3377 100644 --- a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTreeModification.java +++ b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTreeModification.java @@ -9,35 +9,51 @@ package org.opendaylight.yangtools.yang.data.impl.schema.tree; import com.google.common.base.Optional; import com.google.common.base.Preconditions; - +import java.util.Collection; import java.util.Map.Entry; - -import javax.annotation.concurrent.GuardedBy; - -import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier; -import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import org.opendaylight.yangtools.yang.common.QName; +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.NormalizedNodes; +import org.opendaylight.yangtools.yang.data.api.schema.tree.CursorAwareDataTreeModification; 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.DataTreeModificationCursor; +import org.opendaylight.yangtools.yang.data.api.schema.tree.StoreTreeNodes; 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.data.api.schema.tree.spi.Version; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -final class InMemoryDataTreeModification implements DataTreeModification { +final class InMemoryDataTreeModification extends AbstractCursorAware implements CursorAwareDataTreeModification { + private static final AtomicIntegerFieldUpdater SEALED_UPDATER = + AtomicIntegerFieldUpdater.newUpdater(InMemoryDataTreeModification.class, "sealed"); private static final Logger LOG = LoggerFactory.getLogger(InMemoryDataTreeModification.class); - private final ModificationApplyOperation strategyTree; + + private final RootModificationApplyOperation strategyTree; private final InMemoryDataTreeSnapshot snapshot; private final ModifiedNode rootNode; + private final Version version; - @GuardedBy("this") - private boolean sealed = false; + private volatile int sealed = 0; - InMemoryDataTreeModification(final InMemoryDataTreeSnapshot snapshot, final ModificationApplyOperation resolver) { + InMemoryDataTreeModification(final InMemoryDataTreeSnapshot snapshot, final RootModificationApplyOperation resolver) { this.snapshot = Preconditions.checkNotNull(snapshot); - this.strategyTree = Preconditions.checkNotNull(resolver); - this.rootNode = ModifiedNode.createUnmodified(snapshot.getRootNode()); + this.strategyTree = Preconditions.checkNotNull(resolver).snapshot(); + this.rootNode = ModifiedNode.createUnmodified(snapshot.getRootNode(), strategyTree.getChildPolicy()); + + /* + * We could allocate version beforehand, since Version contract + * states two allocated version must be always different. + * + * Preallocating version simplifies scenarios such as + * chaining of modifications, since version for particular + * node in modification and in data tree (if successfully + * committed) will be same and will not change. + */ + this.version = snapshot.getRootNode().getSubtreeVersion().next(); } ModifiedNode getRootModification() { @@ -49,97 +65,108 @@ final class InMemoryDataTreeModification implements DataTreeModification { } @Override - public synchronized void write(final InstanceIdentifier path, final NormalizedNode value) { + public void write(final YangInstanceIdentifier path, final NormalizedNode data) { checkSealed(); - resolveModificationFor(path).write(value); + checkIdentifierReferencesData(path, data); + resolveModificationFor(path).write(data); } @Override - public synchronized void merge(final InstanceIdentifier path, final NormalizedNode data) { + public void merge(final YangInstanceIdentifier 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> dataContainer = (NormalizedNodeContainer) data; - for(NormalizedNode child : dataContainer.getValue()) { - PathArgument childId = child.getIdentifier(); - mergeImpl(op.forChild(childId), child); - } - } - op.merge(data); + checkIdentifierReferencesData(path, data); + resolveModificationFor(path).merge(data, version); } @Override - public synchronized void delete(final InstanceIdentifier path) { + public void delete(final YangInstanceIdentifier path) { checkSealed(); + resolveModificationFor(path).delete(); } @Override - public synchronized Optional> readNode(final InstanceIdentifier path) { + public Optional> readNode(final YangInstanceIdentifier 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 entry = TreeNodeUtils.findClosestsOrFirstMatch(rootNode, path, ModifiedNode.IS_TERMINAL_PREDICATE); - final InstanceIdentifier key = entry.getKey(); + final Entry entry = StoreTreeNodes.findClosestsOrFirstMatch(rootNode, + path, ModifiedNode.IS_TERMINAL_PREDICATE); + final YangInstanceIdentifier key = entry.getKey(); final ModifiedNode mod = entry.getValue(); final Optional result = resolveSnapshot(key, mod); if (result.isPresent()) { - NormalizedNode data = result.get().getData(); - return NormalizedNodeUtils.findNode(key, data, path); + final NormalizedNode data = result.get().getData(); + return NormalizedNodes.findNode(key, data, path); } else { return Optional.absent(); } } - private Optional resolveSnapshot(final InstanceIdentifier path, - final ModifiedNode modification) { - final Optional> potentialSnapshot = modification.getSnapshotCache(); - if(potentialSnapshot.isPresent()) { - return potentialSnapshot.get(); + private Optional resolveSnapshot(final YangInstanceIdentifier path, final ModifiedNode modification) { + final Optional potentialSnapshot = modification.getSnapshot(); + if (potentialSnapshot != null) { + return potentialSnapshot; } 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); + return resolveModificationStrategy(path).apply(modification, modification.getOriginal(), version); + } catch (final Exception e) { + LOG.error("Could not create snapshot for {}:{}", path, modification, e); throw e; } } - private ModificationApplyOperation resolveModificationStrategy(final InstanceIdentifier path) { + void upgradeIfPossible() { + if (rootNode.getOperation() == LogicalOperation.NONE) { + strategyTree.upgradeIfPossible(); + } + } + + private ModificationApplyOperation resolveModificationStrategy(final YangInstanceIdentifier path) { LOG.trace("Resolving modification apply strategy for {}", path); - return TreeNodeUtils.findNodeChecked(strategyTree, path); + + upgradeIfPossible(); + return StoreTreeNodes.findNodeChecked(strategyTree, path); } - private OperationWithModification resolveModificationFor(final InstanceIdentifier path) { + private OperationWithModification resolveModificationFor(final YangInstanceIdentifier path) { + upgradeIfPossible(); + + /* + * Walk the strategy and modification trees in-sync, creating modification nodes as needed. + * + * If the user has provided wrong input, we may end up with a bunch of TOUCH nodes present + * ending with an empty one, as we will throw the exception below. This fact could end up + * being a problem, as we'd have bunch of phantom operations. + * + * That is fine, as we will prune any empty TOUCH nodes in the last phase of the ready + * process. + */ + ModificationApplyOperation operation = strategyTree; ModifiedNode modification = rootNode; - // We ensure strategy is present. - ModificationApplyOperation operation = resolveModificationStrategy(path); - for (PathArgument pathArg : path.getPathArguments()) { - modification = modification.modifyChild(pathArg); + + int i = 1; + for(final PathArgument pathArg : path.getPathArguments()) { + final Optional potential = operation.getChild(pathArg); + if (!potential.isPresent()) { + throw new SchemaValidationFailedException(String.format("Child %s is not present in schema tree.", + path.getAncestor(i))); + } + operation = potential.get(); + ++i; + + modification = modification.modifyChild(pathArg, operation, version); } - 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(); + return OperationWithModification.from(operation, modification); } - @GuardedBy("this") private void checkSealed() { - Preconditions.checkState(!sealed, "Data Tree is sealed. No further modifications allowed."); + Preconditions.checkState(sealed == 0, "Data Tree is sealed. No further modifications allowed."); } @Override @@ -148,30 +175,109 @@ final class InMemoryDataTreeModification implements DataTreeModification { } @Override - public synchronized DataTreeModification newModification() { - Preconditions.checkState(sealed, "Attempted to chain on an unsealed modification"); + public DataTreeModification newModification() { + Preconditions.checkState(sealed == 1, "Attempted to chain on an unsealed modification"); - if(rootNode.getType() == ModificationType.UNMODIFIED) { + if (rootNode.getOperation() == LogicalOperation.NONE) { + // Simple fast case: just use the underlying modification 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. + * We will use preallocated version, this means returned snapshot will + * have same version each time this method is called. */ - TreeNode originalSnapshotRoot = snapshot.getRootNode(); - Optional tempRoot = strategyTree.apply(rootNode, Optional.of(originalSnapshotRoot), originalSnapshotRoot.getSubtreeVersion().next()); + final TreeNode originalSnapshotRoot = snapshot.getRootNode(); + final Optional tempRoot = strategyTree.apply(rootNode, Optional.of(originalSnapshotRoot), version); + Preconditions.checkState(tempRoot.isPresent(), "Data tree root is not present, possibly removed by previous modification"); - InMemoryDataTreeSnapshot tempTree = new InMemoryDataTreeSnapshot(snapshot.getSchemaContext(), tempRoot.get(), strategyTree); + final InMemoryDataTreeSnapshot tempTree = new InMemoryDataTreeSnapshot(snapshot.getSchemaContext(), tempRoot.get(), strategyTree); return tempTree.newModification(); } + + Version getVersion() { + return version; + } + + boolean isSealed() { + return sealed == 1; + } + + private static void applyChildren(final DataTreeModificationCursor cursor, final ModifiedNode node) { + final Collection children = node.getChildren(); + if (!children.isEmpty()) { + cursor.enter(node.getIdentifier()); + for (final ModifiedNode child : children) { + applyNode(cursor, child); + } + cursor.exit(); + } + } + + private static void applyNode(final DataTreeModificationCursor cursor, final ModifiedNode node) { + switch (node.getOperation()) { + case NONE: + break; + case DELETE: + cursor.delete(node.getIdentifier()); + break; + case MERGE: + cursor.merge(node.getIdentifier(), node.getWrittenValue()); + applyChildren(cursor, node); + break; + case TOUCH: + // TODO: we could improve efficiency of cursor use if we could understand + // nested TOUCH operations. One way of achieving that would be a proxy + // cursor, which would keep track of consecutive enter and exit calls + // and coalesce them. + applyChildren(cursor, node); + break; + case WRITE: + cursor.write(node.getIdentifier(), node.getWrittenValue()); + applyChildren(cursor, node); + break; + default: + throw new IllegalArgumentException("Unhandled node operation " + node.getOperation()); + } + } + + @Override + public void applyToCursor(final DataTreeModificationCursor cursor) { + for (final ModifiedNode child : rootNode.getChildren()) { + applyNode(cursor, child); + } + } + + static void checkIdentifierReferencesData(final PathArgument arg, final NormalizedNode data) { + Preconditions.checkArgument(arg.equals(data.getIdentifier()), + "Instance identifier references %s but data identifier is %s", arg, data.getIdentifier()); + } + + private static void checkIdentifierReferencesData(final YangInstanceIdentifier path, final NormalizedNode data) { + if (!path.isEmpty()) { + final PathArgument lastArg = path.getLastPathArgument(); + Preconditions.checkArgument(lastArg != null, "Instance identifier %s has invalid null path argument", path); + checkIdentifierReferencesData(lastArg, data); + } else { + final QName type = data.getNodeType(); + Preconditions.checkArgument(SchemaContext.NAME.equals(type), "Incorrect name %s of root node", type); + } + } + + @Override + public DataTreeModificationCursor createCursor(final YangInstanceIdentifier path) { + final OperationWithModification op = resolveModificationFor(path); + return openCursor(new InMemoryDataTreeModificationCursor(this, path, op)); + } + + @Override + public void ready() { + final boolean wasRunning = SEALED_UPDATER.compareAndSet(this, 0, 1); + Preconditions.checkState(wasRunning, "Attempted to seal an already-sealed Data Tree."); + + AbstractReadyIterator current = AbstractReadyIterator.create(rootNode, strategyTree); + do { + current = current.process(version); + } while (current != null); + } }