2 * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.yangtools.yang.data.tree.impl;
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static java.util.Objects.requireNonNull;
13 import com.google.common.base.MoreObjects.ToStringHelper;
14 import com.google.common.base.VerifyException;
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.schema.DistinctNodeContainer;
20 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
21 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
22 import org.opendaylight.yangtools.yang.data.api.schema.builder.NormalizedNodeContainerBuilder;
23 import org.opendaylight.yangtools.yang.data.tree.api.ConflictingModificationAppliedException;
24 import org.opendaylight.yangtools.yang.data.tree.api.DataTreeConfiguration;
25 import org.opendaylight.yangtools.yang.data.tree.api.DataValidationFailedException;
26 import org.opendaylight.yangtools.yang.data.tree.api.ModificationType;
27 import org.opendaylight.yangtools.yang.data.tree.api.ModifiedNodeDoesNotExistException;
28 import org.opendaylight.yangtools.yang.data.tree.api.SchemaValidationFailedException;
29 import org.opendaylight.yangtools.yang.data.tree.api.TreeType;
30 import org.opendaylight.yangtools.yang.data.tree.impl.node.MutableTreeNode;
31 import org.opendaylight.yangtools.yang.data.tree.impl.node.TreeNode;
32 import org.opendaylight.yangtools.yang.data.tree.impl.node.Version;
33 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
35 abstract sealed class AbstractNodeContainerModificationStrategy<T extends DataSchemaNode>
36 extends SchemaAwareApplyOperation<T> {
37 abstract static sealed class Invisible<T extends DataSchemaNode>
38 extends AbstractNodeContainerModificationStrategy<T>
39 permits LeafSetModificationStrategy, MapModificationStrategy {
40 private final @NonNull SchemaAwareApplyOperation<T> entryStrategy;
42 Invisible(final NormalizedNodeContainerSupport<?, ?> support, final DataTreeConfiguration treeConfig,
43 final SchemaAwareApplyOperation<T> entryStrategy) {
44 super(support, treeConfig);
45 this.entryStrategy = requireNonNull(entryStrategy);
50 return entryStrategy.getSchema();
53 final @NonNull ModificationApplyOperation entryStrategy() {
58 ToStringHelper addToStringAttributes(final ToStringHelper helper) {
59 return super.addToStringAttributes(helper).add("entry", entryStrategy);
63 abstract static sealed class Visible<T extends DataSchemaNode> extends AbstractNodeContainerModificationStrategy<T>
64 permits ChoiceModificationStrategy, DataNodeContainerModificationStrategy {
65 private final @NonNull T schema;
67 Visible(final NormalizedNodeContainerSupport<?, ?> support, final DataTreeConfiguration treeConfig,
69 super(support, treeConfig);
70 this.schema = requireNonNull(schema);
79 ToStringHelper addToStringAttributes(final ToStringHelper helper) {
80 return super.addToStringAttributes(helper).add("schema", schema);
85 * Fake TreeNode version used in
86 * {@link #checkTouchApplicable(ModificationPath, NodeModification, Optional, Version)}
87 * It is okay to use a global constant, as the delegate will ignore it anyway.
89 private static final Version FAKE_VERSION = Version.initial();
91 private final NormalizedNodeContainerSupport<?, ?> support;
92 private final boolean verifyChildrenStructure;
94 AbstractNodeContainerModificationStrategy(final NormalizedNodeContainerSupport<?, ?> support,
95 final DataTreeConfiguration treeConfig) {
96 this.support = requireNonNull(support);
97 verifyChildrenStructure = treeConfig.getTreeType() == TreeType.CONFIGURATION;
101 protected final ChildTrackingPolicy getChildPolicy() {
102 return support.childPolicy;
106 final void verifyValue(final NormalizedNode writtenValue) {
107 final Class<?> nodeClass = support.requiredClass;
108 checkArgument(nodeClass.isInstance(writtenValue), "Node %s is not of type %s", writtenValue, nodeClass);
109 checkArgument(writtenValue instanceof NormalizedNodeContainer);
113 final void verifyValueChildren(final NormalizedNode writtenValue) {
114 final var container = (DistinctNodeContainer<?, ?>) writtenValue;
115 if (verifyChildrenStructure) {
116 for (var child : container.body()) {
117 final var childOp = childByArg(child.name());
118 if (childOp == null) {
119 throw new SchemaValidationFailedException(String.format(
120 "Node %s is not a valid child of %s according to the schema.",
121 child.name(), container.name()));
123 childOp.fullVerifyStructure(child);
126 optionalVerifyValueChildren(container);
128 mandatoryVerifyValueChildren(container);
132 * Perform additional verification on written value's child structure, like presence of mandatory children and
133 * exclusion. The default implementation does nothing and is not invoked for non-CONFIG data trees.
135 * @param writtenValue Effective written value
137 void optionalVerifyValueChildren(final DistinctNodeContainer<?, ?> writtenValue) {
142 * Perform additional verification on written value's child structure, like presence of mandatory children.
143 * The default implementation does nothing.
145 * @param writtenValue Effective written value
147 void mandatoryVerifyValueChildren(final DistinctNodeContainer<?, ?> writtenValue) {
152 protected final void recursivelyVerifyStructure(final NormalizedNode value) {
153 final var container = (NormalizedNodeContainer<?>) value;
154 for (var child : container.body()) {
155 final var childOp = childByArg(child.name());
156 if (childOp == null) {
157 throw new SchemaValidationFailedException(
158 String.format("Node %s is not a valid child of %s according to the schema.",
159 child.name(), container.name()));
162 childOp.recursivelyVerifyStructure(child);
167 protected TreeNode applyWrite(final ModifiedNode modification, final NormalizedNode newValue,
168 final TreeNode currentMeta, final Version version) {
169 final var newValueMeta = TreeNode.of(newValue, version);
170 if (modification.isEmpty()) {
175 * This is where things get interesting. The user has performed a write and then she applied some more
176 * modifications to it. So we need to make sense of that and apply the operations on top of the written value.
178 * We could have done it during the write, but this operation is potentially expensive, so we have left it out
181 * As it turns out, once we materialize the written data, we can share the code path with the subtree change. So
182 * let's create an unsealed TreeNode and run the common parts on it -- which end with the node being sealed.
184 * FIXME: this code needs to be moved out from the prepare() path and into the read() and seal() paths. Merging
185 * of writes needs to be charged to the code which originated this, not to the code which is attempting
186 * to make it visible.
188 final var mutable = newValueMeta.mutable();
189 mutable.setSubtreeVersion(version);
191 final var result = mutateChildren(mutable, support.createBuilder(newValue), version,
192 modification.getChildren());
194 // We are good to go except one detail: this is a single logical write, but
195 // we have a result TreeNode which has been forced to materialized, e.g. it
196 // is larger than it needs to be. Create a new TreeNode to host the data.
197 return TreeNode.of(result.getData(), version);
201 * Applies write/remove diff operation for each modification child in modification subtree.
202 * Operation also sets the Data tree references for each Tree Node (Index Node) in meta (MutableTreeNode) structure.
204 * @param meta MutableTreeNode (IndexTreeNode)
205 * @param data DataBuilder
206 * @param nodeVersion Version of TreeNode
207 * @param modifications modification operations to apply
208 * @return Sealed immutable copy of TreeNode structure with all Data Node references set.
210 @SuppressWarnings({ "rawtypes", "unchecked" })
211 private TreeNode mutateChildren(final MutableTreeNode meta, final NormalizedNodeContainerBuilder data,
212 final Version nodeVersion, final Iterable<ModifiedNode> modifications) {
213 for (var mod : modifications) {
214 final var id = mod.getIdentifier();
215 final var result = resolveChildOperation(id).apply(mod, meta.childByArg(id), nodeVersion);
216 if (result.isPresent()) {
217 final var tn = result.orElseThrow();
219 data.addChild(tn.getData());
221 meta.removeChild(id);
222 data.removeChild(id);
226 meta.setData(data.build());
231 protected TreeNode applyMerge(final ModifiedNode modification, final TreeNode currentMeta, final Version version) {
233 * The node which we are merging exists. We now need to expand any child operations implied by the value. Once
234 * we do that, ModifiedNode children will look like this node were a TOUCH and we will let applyTouch() do the
235 * heavy lifting of applying the children recursively (either through here or through applyWrite().
237 final var value = modification.getWrittenValue();
238 if (!(value instanceof DistinctNodeContainer<?, ?> containerValue)) {
239 throw new VerifyException("Attempted to merge non-container " + value);
242 for (var c : containerValue.body()) {
243 final var id = c.name();
244 modification.modifyChild(id, resolveChildOperation(id), version);
246 return applyTouch(modification, currentMeta, version);
249 private void mergeChildrenIntoModification(final ModifiedNode modification,
250 final Collection<? extends NormalizedNode> children, final Version version) {
251 for (final NormalizedNode c : children) {
252 final ModificationApplyOperation childOp = resolveChildOperation(c.name());
253 final ModifiedNode childNode = modification.modifyChild(c.name(), childOp, version);
254 childOp.mergeIntoModifiedNode(childNode, c, version);
259 final void mergeIntoModifiedNode(final ModifiedNode modification, final NormalizedNode value,
260 final Version version) {
261 final var valueChildren = ((DistinctNodeContainer<?, ?>) value).body();
262 switch (modification.getOperation()) {
264 // Fresh node, just record a MERGE with a value
265 recursivelyVerifyStructure(value);
266 modification.updateValue(LogicalOperation.MERGE, value);
270 mergeChildrenIntoModification(modification, valueChildren, version);
271 // We record empty merge value, since real children merges are already expanded. This is needed to
272 // satisfy non-null for merge original merge value can not be used since it mean different order of
273 // operation - parent changes are always resolved before children ones, and having node in TOUCH means
274 // children was modified before.
275 modification.updateValue(LogicalOperation.MERGE, support.createEmptyValue(value));
278 // Merging into an existing node. Merge data children modifications (maybe recursively) and mark
279 // as MERGE, invalidating cached snapshot
280 mergeChildrenIntoModification(modification, valueChildren, version);
281 modification.updateOperationType(LogicalOperation.MERGE);
284 // Delete performs a data dependency check on existence of the node. Performing a merge on DELETE means
285 // we are really performing a write. One thing that ruins that are any child modifications. If there
286 // are any, we will perform a read() to get the current state of affairs, turn this into into a WRITE
287 // and then append any child entries.
288 if (!modification.isEmpty()) {
289 // Version does not matter here as we'll throw it out
290 final var current = apply(modification, modification.original(), Version.initial());
291 if (current.isPresent()) {
292 modification.updateValue(LogicalOperation.WRITE, current.orElseThrow().getData());
293 mergeChildrenIntoModification(modification, valueChildren, version);
298 modification.updateValue(LogicalOperation.WRITE, value);
301 // We are augmenting a previous write. We'll just walk value's children, get the corresponding
302 // ModifiedNode and run recursively on it
303 mergeChildrenIntoModification(modification, valueChildren, version);
304 modification.updateOperationType(LogicalOperation.WRITE);
307 throw new IllegalArgumentException("Unsupported operation " + modification.getOperation());
312 protected TreeNode applyTouch(final ModifiedNode modification, final TreeNode currentMeta, final Version version) {
314 * The user may have issued an empty merge operation. In this case we:
315 * - do not perform a data tree mutation
316 * - do not pass GO, and
317 * - do not collect useless garbage.
318 * It also means the ModificationType is UNMODIFIED.
320 if (!modification.isEmpty()) {
321 final var dataBuilder = support.createBuilder(currentMeta.getData());
322 final var newMeta = currentMeta.mutable();
323 newMeta.setSubtreeVersion(version);
324 final var children = modification.getChildren();
325 final var ret = mutateChildren(newMeta, dataBuilder, version, children);
328 * It is possible that the only modifications under this node were empty merges, which were turned into
329 * UNMODIFIED. If that is the case, we can turn this operation into UNMODIFIED, too, potentially cascading
330 * it up to root. This has the benefit of speeding up any users, who can skip processing child nodes.
332 * In order to do that, though, we have to check all child operations are UNMODIFIED.
334 * Let's do precisely that, stopping as soon we find a different result.
336 for (var child : children) {
337 if (child.getModificationType() != ModificationType.UNMODIFIED) {
338 modification.resolveModificationType(ModificationType.SUBTREE_MODIFIED);
344 // The merge operation did not have any children, or all of them turned out to be UNMODIFIED, hence do not
345 // replace the metadata node.
346 modification.resolveModificationType(ModificationType.UNMODIFIED);
351 protected final void checkTouchApplicable(final ModificationPath path, final NodeModification modification,
352 final TreeNode currentMeta, final Version version) throws DataValidationFailedException {
353 final TreeNode currentNode;
354 if (currentMeta == null) {
355 currentNode = defaultTreeNode();
356 if (currentNode == null) {
357 if (modification.original() == null) {
358 final var id = path.toInstanceIdentifier();
359 throw new ModifiedNodeDoesNotExistException(id,
360 "Node " + id + " does not exist. Cannot apply modification to its children.");
363 throw new ConflictingModificationAppliedException(path.toInstanceIdentifier(),
364 "Node was deleted by other transaction.");
367 currentNode = currentMeta;
370 checkChildPreconditions(path, modification, currentNode, version);
374 * Return the default tree node. Default implementation does nothing, but can be overridden to call
375 * {@link #defaultTreeNode(NormalizedNode)}.
377 * @return Default empty tree node, or null if no default is available
379 @Nullable TreeNode defaultTreeNode() {
380 // Defaults to no recovery
384 static final TreeNode defaultTreeNode(final NormalizedNode emptyNode) {
385 return TreeNode.of(emptyNode, FAKE_VERSION);
389 protected final void checkMergeApplicable(final ModificationPath path, final NodeModification modification,
390 final TreeNode currentMeta, final Version version) throws DataValidationFailedException {
391 if (currentMeta != null) {
392 checkChildPreconditions(path, modification, currentMeta, version);
397 * Recursively check child preconditions.
399 * @param path current node path
400 * @param modification current modification
401 * @param currentMeta Current data tree node.
403 private void checkChildPreconditions(final ModificationPath path, final NodeModification modification,
404 final @NonNull TreeNode currentMeta, final Version version) throws DataValidationFailedException {
405 for (var childMod : modification.getChildren()) {
406 final var childId = childMod.getIdentifier();
407 final var childMeta = currentMeta.childByArg(childId);
411 resolveChildOperation(childId).checkApplicable(path, childMod, childMeta, version);
419 ToStringHelper addToStringAttributes(final ToStringHelper helper) {
420 return helper.add("support", support).add("verifyChildren", verifyChildrenStructure);