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%2FInMemoryDataTree.java;h=1078f74b17187f5b71e2ab6737091f1d2ffa84fc;hb=37380a5c65e213bc5f34b521d8f8e7d315df7465;hp=668f7512c33f05e5a60749cbc540470b33465c0d;hpb=73276d20a41d17977e730e5bec623a35d3d7869f;p=yangtools.git diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTree.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTree.java index 668f7512c3..1078f74b17 100644 --- a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTree.java +++ b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTree.java @@ -7,133 +7,195 @@ */ package org.opendaylight.yangtools.yang.data.impl.schema.tree; -import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; - -import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier; +import static java.util.Objects.requireNonNull; + +import com.google.common.base.MoreObjects; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import java.util.Optional; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes; 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.DataTreeConfiguration; import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.TreeNode; -import org.opendaylight.yangtools.yang.data.impl.schema.tree.RootModificationApplyOperation.LatestOperationHolder; -import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.opendaylight.yangtools.yang.data.util.DataSchemaContextNode; +import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree; +import org.opendaylight.yangtools.yang.model.api.ContainerLike; +import org.opendaylight.yangtools.yang.model.api.DataNodeContainer; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; +import org.opendaylight.yangtools.yang.model.api.ListSchemaNode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.base.Optional; -import com.google.common.base.Preconditions; - /** * Read-only snapshot of the data tree. */ -final class InMemoryDataTree implements DataTree { +final class InMemoryDataTree extends AbstractDataTreeTip implements DataTree { + private static final VarHandle STATE; + + static { + try { + STATE = MethodHandles.lookup().findVarHandle(InMemoryDataTree.class, "state", DataTreeState.class); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new ExceptionInInitializerError(e); + } + } + 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 final LatestOperationHolder operationHolder = new LatestOperationHolder(); - private SchemaContext currentSchemaContext; - private TreeNode rootNode; + private final DataTreeConfiguration treeConfig; + private final boolean maskMandatory; - public InMemoryDataTree(final TreeNode rootNode, final SchemaContext schemaContext) { - this.rootNode = Preconditions.checkNotNull(rootNode); + /** + * Current data store state generation. All accesses need to go through {@link #STATE} + */ + @SuppressWarnings("unused") + private volatile DataTreeState state; + InMemoryDataTree(final TreeNode rootNode, final DataTreeConfiguration treeConfig, + final EffectiveModelContext schemaContext) { + this.treeConfig = requireNonNull(treeConfig, "treeConfig"); + maskMandatory = true; + state = DataTreeState.createInitial(rootNode); if (schemaContext != null) { - // Also sets applyOper - setSchemaContext(schemaContext); + setEffectiveModelContext(schemaContext); } } - @Override - public synchronized void setSchemaContext(final SchemaContext newSchemaContext) { - Preconditions.checkNotNull(newSchemaContext); - - LOG.info("Attempting to install schema contexts"); - LOG.debug("Following schema contexts will be attempted {}",newSchemaContext); + InMemoryDataTree(final TreeNode rootNode, final DataTreeConfiguration treeConfig, + final EffectiveModelContext schemaContext, final DataSchemaNode rootSchemaNode, + final boolean maskMandatory) { + this.treeConfig = requireNonNull(treeConfig, "treeConfig"); + this.maskMandatory = maskMandatory; - /* - * 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); + state = DataTreeState.createInitial(rootNode).withSchemaContext(schemaContext, getOperation(rootSchemaNode)); + } - // Ready to change the context now, make sure no operations are running - rwLock.writeLock().lock(); - try { - this.operationHolder.setCurrent(newApplyOper); - this.currentSchemaContext = newSchemaContext; - } finally { - rwLock.writeLock().unlock(); + private ModificationApplyOperation getOperation(final DataSchemaNode rootSchemaNode) { + if (rootSchemaNode instanceof ContainerLike && maskMandatory) { + return new ContainerModificationStrategy((ContainerLike) rootSchemaNode, treeConfig); + } + if (rootSchemaNode instanceof ListSchemaNode) { + final PathArgument arg = treeConfig.getRootPath().getLastPathArgument(); + if (arg instanceof NodeIdentifierWithPredicates) { + return maskMandatory ? new MapEntryModificationStrategy((ListSchemaNode) rootSchemaNode, treeConfig) + : MapEntryModificationStrategy.of((ListSchemaNode) rootSchemaNode, treeConfig); + } } - } - @Override - public InMemoryDataTreeSnapshot takeSnapshot() { - rwLock.readLock().lock(); try { - return new InMemoryDataTreeSnapshot(currentSchemaContext, rootNode, operationHolder.newSnapshot()); - } finally { - rwLock.readLock().unlock(); + return SchemaAwareApplyOperation.from(rootSchemaNode, treeConfig); + } catch (ExcludedDataSchemaNodeException e) { + throw new IllegalArgumentException("Root node does not belong current data tree", e); } } @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.of(rootNode)); + public void setEffectiveModelContext(final EffectiveModelContext newModelContext) { + internalSetSchemaContext(newModelContext); } - @Override - public synchronized DataTreeCandidate prepare(final DataTreeModification modification) { - Preconditions.checkArgument(modification instanceof InMemoryDataTreeModification, "Invalid modification class %s", modification.getClass()); + /* + * This method is synchronized to guard against user attempting to install + * multiple contexts. Otherwise it runs in a lock-free manner. + */ + private synchronized void internalSetSchemaContext(final EffectiveModelContext newSchemaContext) { + requireNonNull(newSchemaContext); - final InMemoryDataTreeModification m = (InMemoryDataTreeModification)modification; - final ModifiedNode root = m.getRootModification(); + LOG.debug("Following schema contexts will be attempted {}", newSchemaContext); - if (root.getType() == ModificationType.UNMODIFIED) { - return new NoopDataTreeCandidate(PUBLIC_ROOT_PATH, root); + final DataSchemaContextTree contextTree = DataSchemaContextTree.from(newSchemaContext); + final Optional> rootContextNode = contextTree.findChild(getRootPath()); + if (!rootContextNode.isPresent()) { + LOG.warn("Could not find root {} in new schema context, not upgrading", getRootPath()); + return; } - rwLock.writeLock().lock(); - try { - final Optional newRoot = m.getStrategy().apply(m.getRootModification(), - Optional.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(); + final DataSchemaNode rootSchemaNode = rootContextNode.get().getDataSchemaNode(); + if (!(rootSchemaNode instanceof DataNodeContainer)) { + LOG.warn("Root {} resolves to non-container type {}, not upgrading", getRootPath(), rootSchemaNode); + return; } + + final ModificationApplyOperation rootNode = getOperation(rootSchemaNode); + DataTreeState currentState; + DataTreeState newState; + do { + currentState = currentState(); + newState = currentState.withSchemaContext(newSchemaContext, rootNode); + // TODO: can we lower this to compareAndSwapRelease? + } while (!STATE.compareAndSet(this, currentState, newState)); } @Override - public synchronized void commit(final DataTreeCandidate candidate) { + public InMemoryDataTreeSnapshot takeSnapshot() { + return currentState().newSnapshot(); + } + + @Override + public void commit(final DataTreeCandidate candidate) { if (candidate instanceof NoopDataTreeCandidate) { return; } + if (!(candidate instanceof InMemoryDataTreeCandidate)) { + throw new IllegalArgumentException("Invalid candidate class " + candidate.getClass()); + } - 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())); + LOG.trace("Data Tree is {}", NormalizedNodes.toStringTree(c.getTipRoot().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(); - } + final TreeNode newRoot = c.getTipRoot(); + DataTreeState currentState; + DataTreeState newState; + do { + currentState = currentState(); + final TreeNode currentRoot = currentState.getRoot(); + LOG.debug("Updating datastore from {} to {}", currentRoot, newRoot); + + final TreeNode oldRoot = c.getBeforeRoot(); + if (oldRoot != currentRoot) { + final String oldStr = simpleToString(oldRoot); + final String currentStr = simpleToString(currentRoot); + throw new IllegalStateException("Store tree " + currentStr + " and candidate base " + oldStr + + " differ."); + } + + newState = currentState.withRoot(newRoot); + LOG.trace("Updated state from {} to {}", currentState, newState); + // TODO: can we lower this to compareAndSwapRelease? + } while (!STATE.compareAndSet(this, currentState, newState)); + } + + private static String simpleToString(final Object obj) { + return obj.getClass().getName() + "@" + Integer.toHexString(obj.hashCode()); + } + + private DataTreeState currentState() { + return (DataTreeState) STATE.getAcquire(this); + } + + @Override + public YangInstanceIdentifier getRootPath() { + return treeConfig.getRootPath(); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("object", super.toString()) + .add("config", treeConfig) + .add("state", currentState()) + .toString(); + } + + @Override + protected TreeNode getTipRoot() { + return currentState().getRoot(); } }