Expose DataTreeSnapshot's SchemaContext
[yangtools.git] / yang / yang-data-impl / src / main / java / org / opendaylight / yangtools / yang / data / impl / schema / tree / InMemoryDataTreeModification.java
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.yangtools.yang.data.impl.schema.tree;
9
10 import com.google.common.base.Preconditions;
11 import java.util.Collection;
12 import java.util.Map.Entry;
13 import java.util.Optional;
14 import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
15 import javax.annotation.Nonnull;
16 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
17 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
18 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
19 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes;
20 import org.opendaylight.yangtools.yang.data.api.schema.tree.CursorAwareDataTreeModification;
21 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModificationCursor;
22 import org.opendaylight.yangtools.yang.data.api.schema.tree.StoreTreeNodes;
23 import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.TreeNode;
24 import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.Version;
25 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
26 import org.slf4j.Logger;
27 import org.slf4j.LoggerFactory;
28
29 final class InMemoryDataTreeModification extends AbstractCursorAware implements CursorAwareDataTreeModification {
30     private static final AtomicIntegerFieldUpdater<InMemoryDataTreeModification> SEALED_UPDATER =
31             AtomicIntegerFieldUpdater.newUpdater(InMemoryDataTreeModification.class, "sealed");
32     private static final Logger LOG = LoggerFactory.getLogger(InMemoryDataTreeModification.class);
33
34     private final RootModificationApplyOperation strategyTree;
35     private final InMemoryDataTreeSnapshot snapshot;
36     private final ModifiedNode rootNode;
37     private final Version version;
38
39     private volatile int sealed = 0;
40
41     InMemoryDataTreeModification(final InMemoryDataTreeSnapshot snapshot,
42             final RootModificationApplyOperation resolver) {
43         this.snapshot = Preconditions.checkNotNull(snapshot);
44         this.strategyTree = Preconditions.checkNotNull(resolver).snapshot();
45         this.rootNode = ModifiedNode.createUnmodified(snapshot.getRootNode(), strategyTree.getChildPolicy());
46
47         /*
48          * We could allocate version beforehand, since Version contract
49          * states two allocated version must be always different.
50          *
51          * Preallocating version simplifies scenarios such as
52          * chaining of modifications, since version for particular
53          * node in modification and in data tree (if successfully
54          * committed) will be same and will not change.
55          */
56         this.version = snapshot.getRootNode().getSubtreeVersion().next();
57     }
58
59     ModifiedNode getRootModification() {
60         return rootNode;
61     }
62
63     ModificationApplyOperation getStrategy() {
64         return strategyTree;
65     }
66
67     @Override
68     public SchemaContext getSchemaContext() {
69         return snapshot.getSchemaContext();
70     }
71
72     @Override
73     public void write(final YangInstanceIdentifier path, final NormalizedNode<?, ?> data) {
74         checkSealed();
75         checkIdentifierReferencesData(path, data);
76         resolveModificationFor(path).write(data);
77     }
78
79     @Override
80     public void merge(final YangInstanceIdentifier path, final NormalizedNode<?, ?> data) {
81         checkSealed();
82         checkIdentifierReferencesData(path, data);
83         resolveModificationFor(path).merge(data, version);
84     }
85
86     @Override
87     public void delete(final YangInstanceIdentifier path) {
88         checkSealed();
89
90         resolveModificationFor(path).delete();
91     }
92
93     @Override
94     public Optional<NormalizedNode<?, ?>> readNode(final YangInstanceIdentifier path) {
95         /*
96          * Walk the tree from the top, looking for the first node between root and
97          * the requested path which has been modified. If no such node exists,
98          * we use the node itself.
99          */
100         final Entry<YangInstanceIdentifier, ModifiedNode> entry = StoreTreeNodes.findClosestsOrFirstMatch(rootNode,
101             path, ModifiedNode.IS_TERMINAL_PREDICATE);
102         final YangInstanceIdentifier key = entry.getKey();
103         final ModifiedNode mod = entry.getValue();
104
105         final Optional<TreeNode> result = resolveSnapshot(key, mod);
106         if (result.isPresent()) {
107             final NormalizedNode<?, ?> data = result.get().getData();
108             return NormalizedNodes.findNode(key, data, path);
109         }
110
111         return Optional.empty();
112     }
113
114     @SuppressWarnings("checkstyle:illegalCatch")
115     private Optional<TreeNode> resolveSnapshot(final YangInstanceIdentifier path, final ModifiedNode modification) {
116         final Optional<TreeNode> potentialSnapshot = modification.getSnapshot();
117         if (potentialSnapshot != null) {
118             return potentialSnapshot;
119         }
120
121         try {
122             return resolveModificationStrategy(path).apply(modification, modification.getOriginal(), version);
123         } catch (final Exception e) {
124             LOG.error("Could not create snapshot for {}:{}", path, modification, e);
125             throw e;
126         }
127     }
128
129     void upgradeIfPossible() {
130         if (rootNode.getOperation() == LogicalOperation.NONE) {
131             strategyTree.upgradeIfPossible();
132         }
133     }
134
135     private ModificationApplyOperation resolveModificationStrategy(final YangInstanceIdentifier path) {
136         LOG.trace("Resolving modification apply strategy for {}", path);
137
138         upgradeIfPossible();
139         return StoreTreeNodes.findNodeChecked(strategyTree, path);
140     }
141
142     private OperationWithModification resolveModificationFor(final YangInstanceIdentifier path) {
143         upgradeIfPossible();
144
145         /*
146          * Walk the strategy and modification trees in-sync, creating modification nodes as needed.
147          *
148          * If the user has provided wrong input, we may end up with a bunch of TOUCH nodes present
149          * ending with an empty one, as we will throw the exception below. This fact could end up
150          * being a problem, as we'd have bunch of phantom operations.
151          *
152          * That is fine, as we will prune any empty TOUCH nodes in the last phase of the ready
153          * process.
154          */
155         ModificationApplyOperation operation = strategyTree;
156         ModifiedNode modification = rootNode;
157
158         int depth = 1;
159         for (final PathArgument pathArg : path.getPathArguments()) {
160             final Optional<ModificationApplyOperation> potential = operation.getChild(pathArg);
161             if (!potential.isPresent()) {
162                 throw new SchemaValidationFailedException(String.format("Child %s is not present in schema tree.",
163                         path.getAncestor(depth)));
164             }
165             operation = potential.get();
166             ++depth;
167
168             modification = modification.modifyChild(pathArg, operation, version);
169         }
170
171         return OperationWithModification.from(operation, modification);
172     }
173
174     private void checkSealed() {
175         Preconditions.checkState(sealed == 0, "Data Tree is sealed. No further modifications allowed.");
176     }
177
178     @Override
179     public String toString() {
180         return "MutableDataTree [modification=" + rootNode + "]";
181     }
182
183     @Override
184     public InMemoryDataTreeModification newModification() {
185         Preconditions.checkState(sealed == 1, "Attempted to chain on an unsealed modification");
186
187         if (rootNode.getOperation() == LogicalOperation.NONE) {
188             // Simple fast case: just use the underlying modification
189             return snapshot.newModification();
190         }
191
192         /*
193          * We will use preallocated version, this means returned snapshot will
194          * have same version each time this method is called.
195          */
196         final TreeNode originalSnapshotRoot = snapshot.getRootNode();
197         final Optional<TreeNode> tempRoot = strategyTree.apply(rootNode, Optional.of(originalSnapshotRoot), version);
198         Preconditions.checkState(tempRoot.isPresent(),
199             "Data tree root is not present, possibly removed by previous modification");
200
201         final InMemoryDataTreeSnapshot tempTree = new InMemoryDataTreeSnapshot(snapshot.getSchemaContext(),
202             tempRoot.get(), strategyTree);
203         return tempTree.newModification();
204     }
205
206     Version getVersion() {
207         return version;
208     }
209
210     boolean isSealed() {
211         return sealed == 1;
212     }
213
214     private static void applyChildren(final DataTreeModificationCursor cursor, final ModifiedNode node) {
215         final Collection<ModifiedNode> children = node.getChildren();
216         if (!children.isEmpty()) {
217             cursor.enter(node.getIdentifier());
218             for (final ModifiedNode child : children) {
219                 applyNode(cursor, child);
220             }
221             cursor.exit();
222         }
223     }
224
225     private static void applyNode(final DataTreeModificationCursor cursor, final ModifiedNode node) {
226         switch (node.getOperation()) {
227             case NONE:
228                 break;
229             case DELETE:
230                 cursor.delete(node.getIdentifier());
231                 break;
232             case MERGE:
233                 cursor.merge(node.getIdentifier(), node.getWrittenValue());
234                 applyChildren(cursor, node);
235                 break;
236             case TOUCH:
237                 // TODO: we could improve efficiency of cursor use if we could understand
238                 //       nested TOUCH operations. One way of achieving that would be a proxy
239                 //       cursor, which would keep track of consecutive enter and exit calls
240                 //       and coalesce them.
241                 applyChildren(cursor, node);
242                 break;
243             case WRITE:
244                 cursor.write(node.getIdentifier(), node.getWrittenValue());
245                 applyChildren(cursor, node);
246                 break;
247             default:
248                 throw new IllegalArgumentException("Unhandled node operation " + node.getOperation());
249         }
250     }
251
252     @Override
253     public void applyToCursor(@Nonnull final DataTreeModificationCursor cursor) {
254         for (final ModifiedNode child : rootNode.getChildren()) {
255             applyNode(cursor, child);
256         }
257     }
258
259     static void checkIdentifierReferencesData(final PathArgument arg, final NormalizedNode<?, ?> data) {
260         Preconditions.checkArgument(arg.equals(data.getIdentifier()),
261             "Instance identifier references %s but data identifier is %s", arg, data.getIdentifier());
262     }
263
264     private void checkIdentifierReferencesData(final YangInstanceIdentifier path,
265             final NormalizedNode<?, ?> data) {
266         final PathArgument arg;
267
268         if (!path.isEmpty()) {
269             arg = path.getLastPathArgument();
270             Preconditions.checkArgument(arg != null, "Instance identifier %s has invalid null path argument", path);
271         } else {
272             arg = rootNode.getIdentifier();
273         }
274
275         checkIdentifierReferencesData(arg, data);
276     }
277
278     @Override
279     public DataTreeModificationCursor createCursor(@Nonnull final YangInstanceIdentifier path) {
280         final OperationWithModification op = resolveModificationFor(path);
281         return openCursor(new InMemoryDataTreeModificationCursor(this, path, op));
282     }
283
284     @Override
285     public void ready() {
286         final boolean wasRunning = SEALED_UPDATER.compareAndSet(this, 0, 1);
287         Preconditions.checkState(wasRunning, "Attempted to seal an already-sealed Data Tree.");
288
289         AbstractReadyIterator current = AbstractReadyIterator.create(rootNode, strategyTree);
290         do {
291             current = current.process(version);
292         } while (current != null);
293     }
294 }