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 return switch (schemaNode) {
54 case AnydataSchemaNode anydata -> new ValueNodeModificationStrategy<>(AnydataNode.class, anydata);
55 case AnyxmlSchemaNode anyxml -> new ValueNodeModificationStrategy<>(AnyxmlNode.class, anyxml);
56 case ChoiceSchemaNode choice -> new ChoiceModificationStrategy(choice, treeConfig);
57 case ContainerSchemaNode container -> ContainerModificationStrategy.of(container, treeConfig);
58 case LeafSchemaNode leaf -> new ValueNodeModificationStrategy<>(LeafNode.class, leaf);
59 case LeafListSchemaNode leafList ->
60 MinMaxElementsValidation.from(new LeafSetModificationStrategy(leafList, treeConfig));
61 case ListSchemaNode list -> fromListSchemaNode(list, treeConfig);
62 case SchemaContext context -> new ContainerModificationStrategy.Structural(context, treeConfig);
63 default -> throw new IllegalStateException("Unsupported schema " + schemaNode);
67 static void checkConflicting(final ModificationPath path, final boolean condition, final String message)
68 throws ConflictingModificationAppliedException {
70 throw new ConflictingModificationAppliedException(path.toInstanceIdentifier(), message);
74 private static ModificationApplyOperation fromListSchemaNode(final ListSchemaNode schemaNode,
75 final DataTreeConfiguration treeConfig) {
76 final var keyDefinition = schemaNode.getKeyDefinition();
77 final var strategy = keyDefinition == null || keyDefinition.isEmpty()
78 ? new ListModificationStrategy(schemaNode, treeConfig)
79 : MapModificationStrategy.of(schemaNode, treeConfig);
80 return UniqueValidation.of(schemaNode, treeConfig, MinMaxElementsValidation.from(strategy));
83 protected static final void checkNotConflicting(final ModificationPath path, final @NonNull TreeNode original,
84 final @NonNull TreeNode current) throws ConflictingModificationAppliedException {
85 checkConflicting(path, original.version().equals(current.version()),
86 "Node was replaced by other transaction.");
87 checkConflicting(path, original.subtreeVersion().equals(current.subtreeVersion()),
88 "Node children was modified by other transaction");
91 protected final @NonNull ModificationApplyOperation resolveChildOperation(final PathArgument child) {
92 final ModificationApplyOperation potential = childByArg(child);
93 checkArgument(potential != null, "Operation for child %s is not defined.", child);
98 final void checkApplicable(final ModificationPath path, final NodeModification modification,
99 final TreeNode currentMeta, final Version version) throws DataValidationFailedException {
100 switch (modification.getOperation()) {
101 case DELETE -> checkDeleteApplicable(modification, currentMeta);
102 case TOUCH -> checkTouchApplicable(path, modification, currentMeta, version);
103 case WRITE -> checkWriteApplicable(path, modification, currentMeta, version);
104 case MERGE -> checkMergeApplicable(path, modification, currentMeta, version);
108 default -> throw new UnsupportedOperationException(
109 "Suplied modification type " + modification.getOperation() + " is not supported.");
114 final void quickVerifyStructure(final NormalizedNode writtenValue) {
115 verifyValue(writtenValue);
119 final void fullVerifyStructure(final NormalizedNode writtenValue) {
120 verifyValue(writtenValue);
121 verifyValueChildren(writtenValue);
125 * Verify the a written value, without performing deeper tree validation.
127 * @param writtenValue Written value
129 abstract void verifyValue(NormalizedNode writtenValue);
132 * Verify the children implied by a written value after the value itself has been verified by
133 * {@link #verifyValue(NormalizedNode)}. Default implementation does nothing.
135 * @param writtenValue Written value
137 void verifyValueChildren(final NormalizedNode writtenValue) {
141 protected void checkMergeApplicable(final ModificationPath path, final NodeModification modification,
142 final TreeNode currentMeta, final Version version) throws DataValidationFailedException {
143 final var orig = modification.original();
144 if (orig != null && currentMeta != null) {
146 * We need to do conflict detection only and only if the value of leaf changed before two transactions. If
147 * value of leaf is unchanged between two transactions it should not cause transaction to fail, since result
148 * of this merge leads to same data.
150 if (!orig.data().equals(currentMeta.data())) {
151 checkNotConflicting(path, orig, currentMeta);
157 * Checks if write operation can be applied to current TreeNode.
158 * The operation checks if original tree node to which the modification is going to be applied exists and if
159 * current node in TreeNode structure exists.
161 * @param path Path from current node in TreeNode
162 * @param modification modification to apply
163 * @param currentMeta current node in TreeNode for modification to apply
164 * @throws DataValidationFailedException when a data dependency conflict is detected
166 private static void checkWriteApplicable(final ModificationPath path, final NodeModification modification,
167 final TreeNode currentMeta, final Version version) throws DataValidationFailedException {
168 final var original = modification.original();
169 if (original != null && currentMeta != null) {
170 checkNotConflicting(path, original, currentMeta);
172 checkConflicting(path, original == null, "Node was deleted by other transaction.");
173 checkConflicting(path, currentMeta == null, "Node was created by other transaction.");
177 private static void checkDeleteApplicable(final NodeModification modification,
178 final @Nullable TreeNode currentMeta) {
179 // Delete is always applicable, we do not expose it to subclasses
180 if (currentMeta == null) {
181 LOG.trace("Delete operation turned to no-op on missing node {}", modification);
186 TreeNode apply(final ModifiedNode modification, final TreeNode currentMeta, final Version version) {
187 return switch (modification.getOperation()) {
189 // Deletion of a non-existing node is a no-op, report it as such
190 modification.resolveModificationType(currentMeta != null ? ModificationType.DELETE
191 : ModificationType.UNMODIFIED);
192 yield modification.setSnapshot(null);
195 if (currentMeta == null) {
196 throw new IllegalArgumentException("Metadata not available for modification " + modification);
198 yield modification.setSnapshot(applyTouch(modification, currentMeta, version));
201 final TreeNode result;
203 if (currentMeta == null) {
204 // This is a slight optimization: a merge on a non-existing node equals to a write. Written data
205 // structure is usually verified when the transaction is sealed. To preserve correctness, we have
206 // to run that validation here.
207 modification.resolveModificationType(ModificationType.WRITE);
208 result = applyWrite(modification, modification.getWrittenValue(), null, version);
209 fullVerifyStructure(result.data());
211 result = applyMerge(modification, currentMeta, version);
214 yield modification.setSnapshot(result);
217 modification.resolveModificationType(ModificationType.WRITE);
218 yield modification.setSnapshot(applyWrite(modification, verifyNotNull(modification.getWrittenValue()),
219 currentMeta, version));
222 modification.resolveModificationType(ModificationType.UNMODIFIED);
229 * Apply a merge operation. Since the result of merge differs based on the data type
230 * being modified, implementations of this method are responsible for calling
231 * {@link ModifiedNode#resolveModificationType(ModificationType)} as appropriate.
233 * @param modification Modified node
234 * @param currentMeta Store Metadata Node on which NodeModification should be applied
235 * @param version New subtree version of parent node
236 * @return A sealed TreeNode representing applied operation.
238 protected abstract @NonNull TreeNode applyMerge(ModifiedNode modification, @NonNull TreeNode currentMeta,
241 protected abstract @NonNull TreeNode applyWrite(ModifiedNode modification, NormalizedNode newValue,
242 @Nullable TreeNode currentMeta, Version version);
245 * Apply a nested operation. Since there may not actually be a nested operation
246 * to be applied, implementations of this method are responsible for calling
247 * {@link ModifiedNode#resolveModificationType(ModificationType)} as appropriate.
249 * @param modification Modified node
250 * @param currentMeta Store Metadata Node on which NodeModification should be applied
251 * @param version New subtree version of parent node
252 * @return A sealed TreeNode representing applied operation.
254 protected abstract @NonNull TreeNode applyTouch(ModifiedNode modification, @NonNull TreeNode currentMeta,
258 * Checks is supplied {@link NodeModification} is applicable for Subtree Modification.
260 * @param path Path to current node
261 * @param modification Node modification which should be applied.
262 * @param currentMeta Current state of data tree
263 * @throws ConflictingModificationAppliedException If subtree was changed in conflicting way
264 * @throws org.opendaylight.yangtools.yang.data.tree.api.IncorrectDataStructureException If subtree
265 * modification is not applicable (e.g. leaf node).
267 protected abstract void checkTouchApplicable(ModificationPath path, NodeModification modification,
268 @Nullable TreeNode currentMeta, Version version) throws DataValidationFailedException;
271 * Return the {@link DataSchemaNode}-subclass schema associated with this operation.
273 * @return A model node
275 abstract @NonNull T getSchema();
278 * Checks if supplied schema node belong to specified Data Tree type. All nodes belong to the operational tree,
279 * nodes in configuration tree are marked as such.
281 * @param treeType Tree Type
282 * @param node Schema node
283 * @return {@code true} if the node matches the tree type, {@code false} otherwise.
285 static final boolean belongsToTree(final TreeType treeType, final DataSchemaNode node) {
286 return treeType == TreeType.OPERATIONAL || node.effectiveConfig().orElse(Boolean.TRUE);
289 static final @Nullable MandatoryLeafEnforcer enforcerFor(final DataSchemaNode schema,
290 final DataTreeConfiguration treeConfig) {
291 if (treeConfig.isMandatoryNodesValidationEnabled() && schema instanceof DataNodeContainer container) {
292 final var includeConfigFalse = treeConfig.getTreeType() == TreeType.OPERATIONAL;
293 if (includeConfigFalse || schema.effectiveConfig().orElse(Boolean.TRUE)) {
294 return MandatoryLeafEnforcer.forContainer(container, includeConfigFalse);