3c5879228f831efbc127142a4f7bb38c1ba7d878
[yangtools.git] / data / yang-data-tree-ri / src / main / java / org / opendaylight / yangtools / yang / data / tree / impl / AbstractNodeContainerModificationStrategy.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.tree.impl;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static java.util.Objects.requireNonNull;
12
13 import com.google.common.base.MoreObjects.ToStringHelper;
14 import com.google.common.base.Verify;
15 import java.util.Collection;
16 import java.util.Optional;
17 import org.eclipse.jdt.annotation.NonNull;
18 import org.eclipse.jdt.annotation.Nullable;
19 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
20 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
21 import org.opendaylight.yangtools.yang.data.api.schema.DistinctNodeContainer;
22 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
23 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
24 import org.opendaylight.yangtools.yang.data.api.schema.builder.NormalizedNodeContainerBuilder;
25 import org.opendaylight.yangtools.yang.data.tree.api.ConflictingModificationAppliedException;
26 import org.opendaylight.yangtools.yang.data.tree.api.DataTreeConfiguration;
27 import org.opendaylight.yangtools.yang.data.tree.api.DataValidationFailedException;
28 import org.opendaylight.yangtools.yang.data.tree.api.ModificationType;
29 import org.opendaylight.yangtools.yang.data.tree.api.ModifiedNodeDoesNotExistException;
30 import org.opendaylight.yangtools.yang.data.tree.api.SchemaValidationFailedException;
31 import org.opendaylight.yangtools.yang.data.tree.api.TreeType;
32 import org.opendaylight.yangtools.yang.data.tree.impl.node.MutableTreeNode;
33 import org.opendaylight.yangtools.yang.data.tree.impl.node.TreeNode;
34 import org.opendaylight.yangtools.yang.data.tree.impl.node.Version;
35 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
36
37 abstract sealed class AbstractNodeContainerModificationStrategy<T extends DataSchemaNode>
38         extends SchemaAwareApplyOperation<T> {
39     abstract static sealed class Invisible<T extends DataSchemaNode>
40             extends AbstractNodeContainerModificationStrategy<T>
41             permits LeafSetModificationStrategy, MapModificationStrategy {
42         private final @NonNull SchemaAwareApplyOperation<T> entryStrategy;
43
44         Invisible(final NormalizedNodeContainerSupport<?, ?> support, final DataTreeConfiguration treeConfig,
45                 final SchemaAwareApplyOperation<T> entryStrategy) {
46             super(support, treeConfig);
47             this.entryStrategy = requireNonNull(entryStrategy);
48         }
49
50         @Override
51         final T getSchema() {
52             return entryStrategy.getSchema();
53         }
54
55         final @NonNull ModificationApplyOperation entryStrategy() {
56             return entryStrategy;
57         }
58
59         @Override
60         ToStringHelper addToStringAttributes(final ToStringHelper helper) {
61             return super.addToStringAttributes(helper).add("entry", entryStrategy);
62         }
63     }
64
65     abstract static sealed class Visible<T extends DataSchemaNode> extends AbstractNodeContainerModificationStrategy<T>
66             permits ChoiceModificationStrategy, DataNodeContainerModificationStrategy {
67         private final @NonNull T schema;
68
69         Visible(final NormalizedNodeContainerSupport<?, ?> support, final DataTreeConfiguration treeConfig,
70                 final T schema) {
71             super(support, treeConfig);
72             this.schema = requireNonNull(schema);
73         }
74
75         @Override
76         final T getSchema() {
77             return schema;
78         }
79
80         @Override
81         ToStringHelper addToStringAttributes(final ToStringHelper helper) {
82             return super.addToStringAttributes(helper).add("schema", schema);
83         }
84     }
85
86     /**
87      * Fake TreeNode version used in
88      * {@link #checkTouchApplicable(ModificationPath, NodeModification, Optional, Version)}
89      * It is okay to use a global constant, as the delegate will ignore it anyway.
90      */
91     private static final Version FAKE_VERSION = Version.initial();
92
93     private final NormalizedNodeContainerSupport<?, ?> support;
94     private final boolean verifyChildrenStructure;
95
96     AbstractNodeContainerModificationStrategy(final NormalizedNodeContainerSupport<?, ?> support,
97             final DataTreeConfiguration treeConfig) {
98         this.support = requireNonNull(support);
99         verifyChildrenStructure = treeConfig.getTreeType() == TreeType.CONFIGURATION;
100     }
101
102     @Override
103     protected final ChildTrackingPolicy getChildPolicy() {
104         return support.childPolicy;
105     }
106
107     @Override
108     final void verifyValue(final NormalizedNode writtenValue) {
109         final Class<?> nodeClass = support.requiredClass;
110         checkArgument(nodeClass.isInstance(writtenValue), "Node %s is not of type %s", writtenValue, nodeClass);
111         checkArgument(writtenValue instanceof NormalizedNodeContainer);
112     }
113
114     @Override
115     final void verifyValueChildren(final NormalizedNode writtenValue) {
116         final var container = (DistinctNodeContainer<?, ?>) writtenValue;
117         if (verifyChildrenStructure) {
118             for (var child : container.body()) {
119                 final var childOp = childByArg(child.name());
120                 if (childOp == null) {
121                     throw new SchemaValidationFailedException(String.format(
122                         "Node %s is not a valid child of %s according to the schema.",
123                         child.name(), container.name()));
124                 }
125                 childOp.fullVerifyStructure(child);
126             }
127
128             optionalVerifyValueChildren(container);
129         }
130         mandatoryVerifyValueChildren(container);
131     }
132
133     /**
134      * Perform additional verification on written value's child structure, like presence of mandatory children and
135      * exclusion. The default implementation does nothing and is not invoked for non-CONFIG data trees.
136      *
137      * @param writtenValue Effective written value
138      */
139     void optionalVerifyValueChildren(final DistinctNodeContainer<?, ?> writtenValue) {
140         // Defaults to no-op
141     }
142
143     /**
144      * Perform additional verification on written value's child structure, like presence of mandatory children.
145      * The default implementation does nothing.
146      *
147      * @param writtenValue Effective written value
148      */
149     void mandatoryVerifyValueChildren(final DistinctNodeContainer<?, ?> writtenValue) {
150         // Defaults to no-op
151     }
152
153     @Override
154     protected final void recursivelyVerifyStructure(final NormalizedNode value) {
155         final var container = (NormalizedNodeContainer<?>) value;
156         for (var child : container.body()) {
157             final var childOp = childByArg(child.name());
158             if (childOp == null) {
159                 throw new SchemaValidationFailedException(
160                     String.format("Node %s is not a valid child of %s according to the schema.",
161                         child.name(), container.name()));
162             }
163
164             childOp.recursivelyVerifyStructure(child);
165         }
166     }
167
168     @Override
169     protected TreeNode applyWrite(final ModifiedNode modification, final NormalizedNode newValue,
170             final Optional<? extends TreeNode> currentMeta, final Version version) {
171         final var newValueMeta = TreeNode.of(newValue, version);
172         if (modification.isEmpty()) {
173             return newValueMeta;
174         }
175
176         /*
177          * This is where things get interesting. The user has performed a write and then she applied some more
178          * modifications to it. So we need to make sense of that and apply the operations on top of the written value.
179          *
180          * We could have done it during the write, but this operation is potentially expensive, so we have left it out
181          * of the fast path.
182          *
183          * As it turns out, once we materialize the written data, we can share the code path with the subtree change. So
184          * let's create an unsealed TreeNode and run the common parts on it -- which end with the node being sealed.
185          *
186          * FIXME: this code needs to be moved out from the prepare() path and into the read() and seal() paths. Merging
187          *        of writes needs to be charged to the code which originated this, not to the code which is attempting
188          *        to make it visible.
189          */
190         final var mutable = newValueMeta.mutable();
191         mutable.setSubtreeVersion(version);
192
193         final var result = mutateChildren(mutable, support.createBuilder(newValue), version,
194             modification.getChildren());
195
196         // We are good to go except one detail: this is a single logical write, but
197         // we have a result TreeNode which has been forced to materialized, e.g. it
198         // is larger than it needs to be. Create a new TreeNode to host the data.
199         return TreeNode.of(result.getData(), version);
200     }
201
202     /**
203      * Applies write/remove diff operation for each modification child in modification subtree.
204      * Operation also sets the Data tree references for each Tree Node (Index Node) in meta (MutableTreeNode) structure.
205      *
206      * @param meta MutableTreeNode (IndexTreeNode)
207      * @param data DataBuilder
208      * @param nodeVersion Version of TreeNode
209      * @param modifications modification operations to apply
210      * @return Sealed immutable copy of TreeNode structure with all Data Node references set.
211      */
212     @SuppressWarnings({ "rawtypes", "unchecked" })
213     private TreeNode mutateChildren(final MutableTreeNode meta, final NormalizedNodeContainerBuilder data,
214             final Version nodeVersion, final Iterable<ModifiedNode> modifications) {
215
216         for (final ModifiedNode mod : modifications) {
217             final PathArgument id = mod.getIdentifier();
218             final Optional<? extends TreeNode> cm = meta.findChildByArg(id);
219
220             final Optional<? extends TreeNode> result = resolveChildOperation(id).apply(mod, cm, nodeVersion);
221             if (result.isPresent()) {
222                 final TreeNode tn = result.orElseThrow();
223                 meta.putChild(tn);
224                 data.addChild(tn.getData());
225             } else {
226                 meta.removeChild(id);
227                 data.removeChild(id);
228             }
229         }
230
231         meta.setData(data.build());
232         return meta.seal();
233     }
234
235     @Override
236     protected TreeNode applyMerge(final ModifiedNode modification, final TreeNode currentMeta, final Version version) {
237         /*
238          * The node which we are merging exists. We now need to expand any child operations implied by the value. Once
239          * we do that, ModifiedNode children will look like this node were a TOUCH and we will let applyTouch() do the
240          * heavy lifting of applying the children recursively (either through here or through applyWrite().
241          */
242         final NormalizedNode value = modification.getWrittenValue();
243
244         Verify.verify(value instanceof DistinctNodeContainer, "Attempted to merge non-container %s", value);
245         for (var c : ((DistinctNodeContainer<?, ?>) value).body()) {
246             final var id = c.name();
247             modification.modifyChild(id, resolveChildOperation(id), version);
248         }
249         return applyTouch(modification, currentMeta, version);
250     }
251
252     private void mergeChildrenIntoModification(final ModifiedNode modification,
253             final Collection<? extends NormalizedNode> children, final Version version) {
254         for (final NormalizedNode c : children) {
255             final ModificationApplyOperation childOp = resolveChildOperation(c.name());
256             final ModifiedNode childNode = modification.modifyChild(c.name(), childOp, version);
257             childOp.mergeIntoModifiedNode(childNode, c, version);
258         }
259     }
260
261     @Override
262     final void mergeIntoModifiedNode(final ModifiedNode modification, final NormalizedNode value,
263             final Version version) {
264         final var valueChildren = ((DistinctNodeContainer<?, ?>) value).body();
265         switch (modification.getOperation()) {
266             case NONE:
267                 // Fresh node, just record a MERGE with a value
268                 recursivelyVerifyStructure(value);
269                 modification.updateValue(LogicalOperation.MERGE, value);
270                 return;
271             case TOUCH:
272
273                 mergeChildrenIntoModification(modification, valueChildren, version);
274                 // We record empty merge value, since real children merges are already expanded. This is needed to
275                 // satisfy non-null for merge original merge value can not be used since it mean different order of
276                 // operation - parent changes are always resolved before children ones, and having node in TOUCH means
277                 // children was modified before.
278                 modification.updateValue(LogicalOperation.MERGE, support.createEmptyValue(value));
279                 return;
280             case MERGE:
281                 // Merging into an existing node. Merge data children modifications (maybe recursively) and mark
282                 // as MERGE, invalidating cached snapshot
283                 mergeChildrenIntoModification(modification, valueChildren, version);
284                 modification.updateOperationType(LogicalOperation.MERGE);
285                 return;
286             case DELETE:
287                 // Delete performs a data dependency check on existence of the node. Performing a merge on DELETE means
288                 // we are really performing a write. One thing that ruins that are any child modifications. If there
289                 // are any, we will perform a read() to get the current state of affairs, turn this into into a WRITE
290                 // and then append any child entries.
291                 if (!modification.isEmpty()) {
292                     // Version does not matter here as we'll throw it out
293                     final var current = apply(modification, modification.getOriginal(), Version.initial());
294                     if (current.isPresent()) {
295                         modification.updateValue(LogicalOperation.WRITE, current.orElseThrow().getData());
296                         mergeChildrenIntoModification(modification, valueChildren, version);
297                         return;
298                     }
299                 }
300
301                 modification.updateValue(LogicalOperation.WRITE, value);
302                 return;
303             case WRITE:
304                 // We are augmenting a previous write. We'll just walk value's children, get the corresponding
305                 // ModifiedNode and run recursively on it
306                 mergeChildrenIntoModification(modification, valueChildren, version);
307                 modification.updateOperationType(LogicalOperation.WRITE);
308                 return;
309             default:
310                 throw new IllegalArgumentException("Unsupported operation " + modification.getOperation());
311         }
312     }
313
314     @Override
315     protected TreeNode applyTouch(final ModifiedNode modification, final TreeNode currentMeta, final Version version) {
316         /*
317          * The user may have issued an empty merge operation. In this case we:
318          * - do not perform a data tree mutation
319          * - do not pass GO, and
320          * - do not collect useless garbage.
321          * It also means the ModificationType is UNMODIFIED.
322          */
323         if (!modification.isEmpty()) {
324             final var dataBuilder = support.createBuilder(currentMeta.getData());
325             final var newMeta = currentMeta.mutable();
326             newMeta.setSubtreeVersion(version);
327             final var children = modification.getChildren();
328             final var ret = mutateChildren(newMeta, dataBuilder, version, children);
329
330             /*
331              * It is possible that the only modifications under this node were empty merges, which were turned into
332              * UNMODIFIED. If that is the case, we can turn this operation into UNMODIFIED, too, potentially cascading
333              * it up to root. This has the benefit of speeding up any users, who can skip processing child nodes.
334              *
335              * In order to do that, though, we have to check all child operations are UNMODIFIED.
336              *
337              * Let's do precisely that, stopping as soon we find a different result.
338              */
339             for (var child : children) {
340                 if (child.getModificationType() != ModificationType.UNMODIFIED) {
341                     modification.resolveModificationType(ModificationType.SUBTREE_MODIFIED);
342                     return ret;
343                 }
344             }
345         }
346
347         // The merge operation did not have any children, or all of them turned out to be UNMODIFIED, hence do not
348         // replace the metadata node.
349         modification.resolveModificationType(ModificationType.UNMODIFIED);
350         return currentMeta;
351     }
352
353     @Override
354     protected final void checkTouchApplicable(final ModificationPath path, final NodeModification modification,
355             final Optional<? extends TreeNode> current, final Version version) throws DataValidationFailedException {
356         final TreeNode currentNode;
357         if (current.isEmpty()) {
358             currentNode = defaultTreeNode();
359             if (currentNode == null) {
360                 if (modification.getOriginal().isEmpty()) {
361                     final YangInstanceIdentifier id = path.toInstanceIdentifier();
362                     throw new ModifiedNodeDoesNotExistException(id,
363                         "Node " + id + " does not exist. Cannot apply modification to its children.");
364                 }
365
366                 throw new ConflictingModificationAppliedException(path.toInstanceIdentifier(),
367                     "Node was deleted by other transaction.");
368             }
369         } else {
370             currentNode = current.orElseThrow();
371         }
372
373         checkChildPreconditions(path, modification, currentNode, version);
374     }
375
376     /**
377      * Return the default tree node. Default implementation does nothing, but can be overridden to call
378      * {@link #defaultTreeNode(NormalizedNode)}.
379      *
380      * @return Default empty tree node, or null if no default is available
381      */
382     @Nullable TreeNode defaultTreeNode() {
383         // Defaults to no recovery
384         return null;
385     }
386
387     static final TreeNode defaultTreeNode(final NormalizedNode emptyNode) {
388         return TreeNode.of(emptyNode, FAKE_VERSION);
389     }
390
391     @Override
392     protected final void checkMergeApplicable(final ModificationPath path, final NodeModification modification,
393             final Optional<? extends TreeNode> current, final Version version) throws DataValidationFailedException {
394         if (current.isPresent()) {
395             checkChildPreconditions(path, modification, current.orElseThrow(), version);
396         }
397     }
398
399     /**
400      * Recursively check child preconditions.
401      *
402      * @param path current node path
403      * @param modification current modification
404      * @param current Current data tree node.
405      */
406     private void checkChildPreconditions(final ModificationPath path, final NodeModification modification,
407             final TreeNode current, final Version version) throws DataValidationFailedException {
408         for (final NodeModification childMod : modification.getChildren()) {
409             final PathArgument childId = childMod.getIdentifier();
410             final Optional<? extends TreeNode> childMeta = current.findChildByArg(childId);
411
412             path.push(childId);
413             try {
414                 resolveChildOperation(childId).checkApplicable(path, childMod, childMeta, version);
415             } finally {
416                 path.pop();
417             }
418         }
419     }
420
421     @Override
422     ToStringHelper addToStringAttributes(final ToStringHelper helper) {
423         return helper.add("support", support).add("verifyChildren", verifyChildrenStructure);
424     }
425 }