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 com.google.common.base.Verify.verifyNotNull;
13 import org.eclipse.jdt.annotation.NonNull;
14 import org.eclipse.jdt.annotation.Nullable;
15 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
16 import org.opendaylight.yangtools.yang.data.api.schema.AnydataNode;
17 import org.opendaylight.yangtools.yang.data.api.schema.AnyxmlNode;
18 import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
19 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
20 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode.BuilderFactory;
21 import org.opendaylight.yangtools.yang.data.spi.node.ImmutableNodes;
22 import org.opendaylight.yangtools.yang.data.spi.node.MandatoryLeafEnforcer;
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.TreeType;
28 import org.opendaylight.yangtools.yang.data.tree.impl.node.TreeNode;
29 import org.opendaylight.yangtools.yang.data.tree.impl.node.Version;
30 import org.opendaylight.yangtools.yang.model.api.AnydataSchemaNode;
31 import org.opendaylight.yangtools.yang.model.api.AnyxmlSchemaNode;
32 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
33 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
34 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
35 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
36 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
37 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
38 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
39 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
43 abstract sealed class SchemaAwareApplyOperation<T extends DataSchemaNode> extends ModificationApplyOperation
44 permits AbstractNodeContainerModificationStrategy, ListModificationStrategy, ValueNodeModificationStrategy {
45 private static final Logger LOG = LoggerFactory.getLogger(SchemaAwareApplyOperation.class);
46 static final @NonNull BuilderFactory BUILDER_FACTORY = ImmutableNodes.builderFactory();
48 static ModificationApplyOperation from(final DataSchemaNode schemaNode,
49 final DataTreeConfiguration treeConfig) throws ExcludedDataSchemaNodeException {
50 if (!belongsToTree(treeConfig.getTreeType(), schemaNode)) {
51 throw new ExcludedDataSchemaNodeException(schemaNode + " does not belong to configuration tree");
53 if (schemaNode instanceof ContainerSchemaNode container) {
54 return ContainerModificationStrategy.of(container, treeConfig);
55 } else if (schemaNode instanceof ListSchemaNode list) {
56 return fromListSchemaNode(list, treeConfig);
57 } else if (schemaNode instanceof ChoiceSchemaNode choice) {
58 return new ChoiceModificationStrategy(choice, treeConfig);
59 } else if (schemaNode instanceof LeafListSchemaNode leafList) {
60 return MinMaxElementsValidation.from(new LeafSetModificationStrategy(leafList, treeConfig));
61 } else if (schemaNode instanceof LeafSchemaNode leaf) {
62 return new ValueNodeModificationStrategy<>(LeafNode.class, leaf);
63 } else if (schemaNode instanceof AnydataSchemaNode anydata) {
64 return new ValueNodeModificationStrategy<>(AnydataNode.class, anydata);
65 } else if (schemaNode instanceof AnyxmlSchemaNode anyxml) {
66 return new ValueNodeModificationStrategy<>(AnyxmlNode.class, anyxml);
67 } else if (schemaNode instanceof SchemaContext context) {
68 return new ContainerModificationStrategy.Structural(context, treeConfig);
70 throw new IllegalStateException("Unsupported schema " + schemaNode);
74 static void checkConflicting(final ModificationPath path, final boolean condition, final String message)
75 throws ConflictingModificationAppliedException {
77 throw new ConflictingModificationAppliedException(path.toInstanceIdentifier(), message);
81 private static ModificationApplyOperation fromListSchemaNode(final ListSchemaNode schemaNode,
82 final DataTreeConfiguration treeConfig) {
83 final var keyDefinition = schemaNode.getKeyDefinition();
84 final var strategy = keyDefinition == null || keyDefinition.isEmpty()
85 ? new ListModificationStrategy(schemaNode, treeConfig)
86 : MapModificationStrategy.of(schemaNode, treeConfig);
87 return UniqueValidation.of(schemaNode, treeConfig, MinMaxElementsValidation.from(strategy));
90 protected static final void checkNotConflicting(final ModificationPath path, final @NonNull TreeNode original,
91 final @NonNull TreeNode current) throws ConflictingModificationAppliedException {
92 checkConflicting(path, original.getVersion().equals(current.getVersion()),
93 "Node was replaced by other transaction.");
94 checkConflicting(path, original.getSubtreeVersion().equals(current.getSubtreeVersion()),
95 "Node children was modified by other transaction");
98 protected final @NonNull ModificationApplyOperation resolveChildOperation(final PathArgument child) {
99 final ModificationApplyOperation potential = childByArg(child);
100 checkArgument(potential != null, "Operation for child %s is not defined.", child);
105 final void checkApplicable(final ModificationPath path, final NodeModification modification,
106 final TreeNode currentMeta, final Version version) throws DataValidationFailedException {
107 switch (modification.getOperation()) {
108 case DELETE -> checkDeleteApplicable(modification, currentMeta);
109 case TOUCH -> checkTouchApplicable(path, modification, currentMeta, version);
110 case WRITE -> checkWriteApplicable(path, modification, currentMeta, version);
111 case MERGE -> checkMergeApplicable(path, modification, currentMeta, version);
115 default -> throw new UnsupportedOperationException(
116 "Suplied modification type " + modification.getOperation() + " is not supported.");
121 final void quickVerifyStructure(final NormalizedNode writtenValue) {
122 verifyValue(writtenValue);
126 final void fullVerifyStructure(final NormalizedNode writtenValue) {
127 verifyValue(writtenValue);
128 verifyValueChildren(writtenValue);
132 * Verify the a written value, without performing deeper tree validation.
134 * @param writtenValue Written value
136 abstract void verifyValue(NormalizedNode writtenValue);
139 * Verify the children implied by a written value after the value itself has been verified by
140 * {@link #verifyValue(NormalizedNode)}. Default implementation does nothing.
142 * @param writtenValue Written value
144 void verifyValueChildren(final NormalizedNode writtenValue) {
148 protected void checkMergeApplicable(final ModificationPath path, final NodeModification modification,
149 final TreeNode currentMeta, final Version version) throws DataValidationFailedException {
150 final var orig = modification.original();
151 if (orig != null && currentMeta != null) {
153 * We need to do conflict detection only and only if the value of leaf changed before two transactions. If
154 * value of leaf is unchanged between two transactions it should not cause transaction to fail, since result
155 * of this merge leads to same data.
157 if (!orig.getData().equals(currentMeta.getData())) {
158 checkNotConflicting(path, orig, currentMeta);
164 * Checks if write operation can be applied to current TreeNode.
165 * The operation checks if original tree node to which the modification is going to be applied exists and if
166 * current node in TreeNode structure exists.
168 * @param path Path from current node in TreeNode
169 * @param modification modification to apply
170 * @param currentMeta current node in TreeNode for modification to apply
171 * @throws DataValidationFailedException when a data dependency conflict is detected
173 private static void checkWriteApplicable(final ModificationPath path, final NodeModification modification,
174 final TreeNode currentMeta, final Version version) throws DataValidationFailedException {
175 final var original = modification.original();
176 if (original != null && currentMeta != null) {
177 checkNotConflicting(path, original, currentMeta);
179 checkConflicting(path, original == null, "Node was deleted by other transaction.");
180 checkConflicting(path, currentMeta == null, "Node was created by other transaction.");
184 private static void checkDeleteApplicable(final NodeModification modification,
185 final @Nullable TreeNode currentMeta) {
186 // Delete is always applicable, we do not expose it to subclasses
187 if (currentMeta == null) {
188 LOG.trace("Delete operation turned to no-op on missing node {}", modification);
193 TreeNode apply(final ModifiedNode modification, final TreeNode currentMeta, final Version version) {
194 return switch (modification.getOperation()) {
196 // Deletion of a non-existing node is a no-op, report it as such
197 modification.resolveModificationType(currentMeta != null ? ModificationType.DELETE
198 : ModificationType.UNMODIFIED);
199 yield modification.setSnapshot(null);
202 if (currentMeta == null) {
203 throw new IllegalArgumentException("Metadata not available for modification " + modification);
205 yield modification.setSnapshot(applyTouch(modification, currentMeta, version));
208 final TreeNode result;
210 if (currentMeta == null) {
211 // This is a slight optimization: a merge on a non-existing node equals to a write. Written data
212 // structure is usually verified when the transaction is sealed. To preserve correctness, we have
213 // to run that validation here.
214 modification.resolveModificationType(ModificationType.WRITE);
215 result = applyWrite(modification, modification.getWrittenValue(), null, version);
216 fullVerifyStructure(result.getData());
218 result = applyMerge(modification, currentMeta, version);
221 yield modification.setSnapshot(result);
224 modification.resolveModificationType(ModificationType.WRITE);
225 yield modification.setSnapshot(applyWrite(modification, verifyNotNull(modification.getWrittenValue()),
226 currentMeta, version));
229 modification.resolveModificationType(ModificationType.UNMODIFIED);
236 * Apply a merge operation. Since the result of merge differs based on the data type
237 * being modified, implementations of this method are responsible for calling
238 * {@link ModifiedNode#resolveModificationType(ModificationType)} as appropriate.
240 * @param modification Modified node
241 * @param currentMeta Store Metadata Node on which NodeModification should be applied
242 * @param version New subtree version of parent node
243 * @return A sealed TreeNode representing applied operation.
245 protected abstract @NonNull TreeNode applyMerge(ModifiedNode modification, @NonNull TreeNode currentMeta,
248 protected abstract @NonNull TreeNode applyWrite(ModifiedNode modification, NormalizedNode newValue,
249 @Nullable TreeNode currentMeta, Version version);
252 * Apply a nested operation. Since there may not actually be a nested operation
253 * to be applied, implementations of this method are responsible for calling
254 * {@link ModifiedNode#resolveModificationType(ModificationType)} as appropriate.
256 * @param modification Modified node
257 * @param currentMeta Store Metadata Node on which NodeModification should be applied
258 * @param version New subtree version of parent node
259 * @return A sealed TreeNode representing applied operation.
261 protected abstract @NonNull TreeNode applyTouch(ModifiedNode modification, @NonNull TreeNode currentMeta,
265 * Checks is supplied {@link NodeModification} is applicable for Subtree Modification.
267 * @param path Path to current node
268 * @param modification Node modification which should be applied.
269 * @param currentMeta Current state of data tree
270 * @throws ConflictingModificationAppliedException If subtree was changed in conflicting way
271 * @throws org.opendaylight.yangtools.yang.data.tree.api.IncorrectDataStructureException If subtree
272 * modification is not applicable (e.g. leaf node).
274 protected abstract void checkTouchApplicable(ModificationPath path, NodeModification modification,
275 @Nullable TreeNode currentMeta, Version version) throws DataValidationFailedException;
278 * Return the {@link DataSchemaNode}-subclass schema associated with this operation.
280 * @return A model node
282 abstract @NonNull T getSchema();
285 * Checks if supplied schema node belong to specified Data Tree type. All nodes belong to the operational tree,
286 * nodes in configuration tree are marked as such.
288 * @param treeType Tree Type
289 * @param node Schema node
290 * @return {@code true} if the node matches the tree type, {@code false} otherwise.
292 static final boolean belongsToTree(final TreeType treeType, final DataSchemaNode node) {
293 return treeType == TreeType.OPERATIONAL || node.effectiveConfig().orElse(Boolean.TRUE);
296 static final @Nullable MandatoryLeafEnforcer enforcerFor(final DataSchemaNode schema,
297 final DataTreeConfiguration treeConfig) {
298 if (treeConfig.isMandatoryNodesValidationEnabled() && schema instanceof DataNodeContainer container) {
299 final var includeConfigFalse = treeConfig.getTreeType() == TreeType.OPERATIONAL;
300 if (includeConfigFalse || schema.effectiveConfig().orElse(Boolean.TRUE)) {
301 return MandatoryLeafEnforcer.forContainer(container, includeConfigFalse);