Merge "Introduced skeletons of Contributor and User guide."
[yangtools.git] / yang / yang-data-impl / src / main / java / org / opendaylight / yangtools / yang / data / impl / schema / tree / SchemaAwareApplyOperation.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.Optional;
11 import com.google.common.base.Preconditions;
12 import java.util.List;
13 import org.opendaylight.yangtools.yang.common.QName;
14 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
15 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
16 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
17 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
18 import org.opendaylight.yangtools.yang.data.api.schema.tree.ConflictingModificationAppliedException;
19 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException;
20 import org.opendaylight.yangtools.yang.data.api.schema.tree.IncorrectDataStructureException;
21 import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.TreeNode;
22 import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.Version;
23 import org.opendaylight.yangtools.yang.model.api.AugmentationSchema;
24 import org.opendaylight.yangtools.yang.model.api.AugmentationTarget;
25 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
26 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
27 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
28 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
29 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
30 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
31 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
34
35 abstract class SchemaAwareApplyOperation implements ModificationApplyOperation {
36     private static final Logger LOG = LoggerFactory.getLogger(SchemaAwareApplyOperation.class);
37
38     public static SchemaAwareApplyOperation from(final DataSchemaNode schemaNode) {
39         if (schemaNode instanceof ContainerSchemaNode) {
40             return new DataNodeContainerModificationStrategy.ContainerModificationStrategy((ContainerSchemaNode) schemaNode);
41         } else if (schemaNode instanceof ListSchemaNode) {
42             return fromListSchemaNode((ListSchemaNode) schemaNode);
43         } else if (schemaNode instanceof ChoiceSchemaNode) {
44             return new NormalizedNodeContainerModificationStrategy.ChoiceModificationStrategy((ChoiceSchemaNode) schemaNode);
45         } else if (schemaNode instanceof LeafListSchemaNode) {
46             return fromLeafListSchemaNode((LeafListSchemaNode) schemaNode);
47         } else if (schemaNode instanceof LeafSchemaNode) {
48             return new ValueNodeModificationStrategy.LeafModificationStrategy((LeafSchemaNode) schemaNode);
49         }
50         throw new IllegalArgumentException("Not supported schema node type for " + schemaNode.getClass());
51     }
52
53     public static SchemaAwareApplyOperation from(final DataNodeContainer resolvedTree,
54             final AugmentationTarget augSchemas, final AugmentationIdentifier identifier) {
55         AugmentationSchema augSchema = null;
56
57         allAugments:
58             for (AugmentationSchema potential : augSchemas.getAvailableAugmentations()) {
59                 for (DataSchemaNode child : potential.getChildNodes()) {
60                     if (identifier.getPossibleChildNames().contains(child.getQName())) {
61                         augSchema = potential;
62                         break allAugments;
63                     }
64                 }
65             }
66
67         if (augSchema != null) {
68             return new DataNodeContainerModificationStrategy.AugmentationModificationStrategy(augSchema, resolvedTree);
69         }
70         return null;
71     }
72
73     public static boolean checkConflicting(final YangInstanceIdentifier path, final boolean condition, final String message) throws ConflictingModificationAppliedException {
74         if(!condition) {
75             throw new ConflictingModificationAppliedException(path, message);
76         }
77         return condition;
78     }
79
80     private static SchemaAwareApplyOperation fromListSchemaNode(final ListSchemaNode schemaNode) {
81         List<QName> keyDefinition = schemaNode.getKeyDefinition();
82         if (keyDefinition == null || keyDefinition.isEmpty()) {
83             return new UnkeyedListModificationStrategy(schemaNode);
84         }
85         if (schemaNode.isUserOrdered()) {
86             return new NormalizedNodeContainerModificationStrategy.OrderedMapModificationStrategy(schemaNode);
87         }
88
89         return new NormalizedNodeContainerModificationStrategy.UnorderedMapModificationStrategy(schemaNode);
90     }
91
92     private static SchemaAwareApplyOperation fromLeafListSchemaNode(final LeafListSchemaNode schemaNode) {
93         if(schemaNode.isUserOrdered()) {
94             return new NormalizedNodeContainerModificationStrategy.OrderedLeafSetModificationStrategy(schemaNode);
95         } else {
96             return new NormalizedNodeContainerModificationStrategy.UnorderedLeafSetModificationStrategy(schemaNode);
97         }
98     }
99
100     private static final void checkNotConflicting(final YangInstanceIdentifier path, final TreeNode original, final TreeNode current) throws ConflictingModificationAppliedException {
101         checkConflicting(path, original.getVersion().equals(current.getVersion()),
102                 "Node was replaced by other transaction.");
103         checkConflicting(path, original.getSubtreeVersion().equals(current.getSubtreeVersion()),
104                 "Node children was modified by other transaction");
105     }
106
107     protected final ModificationApplyOperation resolveChildOperation(final PathArgument child) {
108         Optional<ModificationApplyOperation> potential = getChild(child);
109         Preconditions.checkArgument(potential.isPresent(), "Operation for child %s is not defined.", child);
110         return potential.get();
111     }
112
113     @Override
114     public void verifyStructure(final ModifiedNode modification) throws IllegalArgumentException {
115         if (modification.getOperation() == LogicalOperation.WRITE) {
116             verifyWrittenStructure(modification.getWrittenValue());
117         }
118     }
119
120     @Override
121     public final void checkApplicable(final YangInstanceIdentifier path,final NodeModification modification, final Optional<TreeNode> current) throws DataValidationFailedException {
122         switch (modification.getOperation()) {
123         case DELETE:
124             checkDeleteApplicable(modification, current);
125         case TOUCH:
126             checkSubtreeModificationApplicable(path, modification, current);
127             return;
128         case WRITE:
129             checkWriteApplicable(path, modification, current);
130             return;
131         case MERGE:
132             checkMergeApplicable(path, modification, current);
133             return;
134         case NONE:
135             return;
136         default:
137             throw new UnsupportedOperationException("Suplied modification type "+ modification.getOperation()+ "is not supported.");
138         }
139
140     }
141
142     protected void checkMergeApplicable(final YangInstanceIdentifier path, final NodeModification modification, final Optional<TreeNode> current) throws DataValidationFailedException {
143         Optional<TreeNode> original = modification.getOriginal();
144         if (original.isPresent() && current.isPresent()) {
145             /*
146              * We need to do conflict detection only and only if the value of leaf changed
147              * before two transactions. If value of leaf is unchanged between two transactions
148              * it should not cause transaction to fail, since result of this merge
149              * leads to same data.
150              */
151             if(!original.get().getData().equals(current.get().getData())) {
152                 checkNotConflicting(path, original.get(), current.get());
153             }
154         }
155     }
156
157     /**
158      * Checks if write operation can be applied to current TreeNode.
159      * The operation checks if original tree node to which the modification is going to be applied exists and if
160      * current node in TreeNode structure exists.
161      *
162      * @param path Path from current node in TreeNode
163      * @param modification modification to apply
164      * @param current current node in TreeNode for modification to apply
165      * @throws DataValidationFailedException
166      */
167     protected void checkWriteApplicable(final YangInstanceIdentifier path, final NodeModification modification,
168         final Optional<TreeNode> current) throws DataValidationFailedException {
169         Optional<TreeNode> original = modification.getOriginal();
170         if (original.isPresent() && current.isPresent()) {
171             checkNotConflicting(path, original.get(), current.get());
172         } else if(original.isPresent()) {
173             throw new ConflictingModificationAppliedException(path,"Node was deleted by other transaction.");
174         } else if(current.isPresent()) {
175             throw new ConflictingModificationAppliedException(path,"Node was created by other transaction.");
176         }
177     }
178
179     private void checkDeleteApplicable(final NodeModification modification, final Optional<TreeNode> current) {
180         // Delete is always applicable, we do not expose it to subclasses
181         if (current.isPresent()) {
182             LOG.trace("Delete operation turned to no-op on missing node {}", modification);
183         }
184     }
185
186     boolean isOrdered() {
187         return false;
188     }
189
190     @Override
191     public final Optional<TreeNode> apply(final ModifiedNode modification,
192             final Optional<TreeNode> currentMeta, final Version version) {
193
194         switch (modification.getOperation()) {
195         case DELETE:
196             return modification.setSnapshot(Optional.<TreeNode> absent());
197         case TOUCH:
198             Preconditions.checkArgument(currentMeta.isPresent(), "Metadata not available for modification",
199                     modification);
200             return modification.setSnapshot(Optional.of(applySubtreeChange(modification, currentMeta.get(),
201                     version)));
202         case MERGE:
203             final TreeNode result;
204
205             // This is a slight optimization: a merge on a non-existing node equals to a write
206             if (currentMeta.isPresent()) {
207                 result = applyMerge(modification,currentMeta.get(), version);
208             } else {
209                 result = applyWrite(modification, currentMeta, version);
210             }
211
212             return modification.setSnapshot(Optional.of(result));
213         case WRITE:
214             return modification.setSnapshot(Optional.of(applyWrite(modification, currentMeta, version)));
215         case NONE:
216             return currentMeta;
217         default:
218             throw new IllegalArgumentException("Provided modification type is not supported.");
219         }
220     }
221
222     protected abstract TreeNode applyMerge(ModifiedNode modification,
223             TreeNode currentMeta, Version version);
224
225     protected abstract TreeNode applyWrite(ModifiedNode modification,
226             Optional<TreeNode> currentMeta, Version version);
227
228     protected abstract TreeNode applySubtreeChange(ModifiedNode modification,
229             TreeNode currentMeta, Version version);
230
231     /**
232      *
233      * Checks is supplied {@link NodeModification} is applicable for Subtree Modification.
234      *
235      * @param path Path to current node
236      * @param modification Node modification which should be applied.
237      * @param current Current state of data tree
238      * @throws ConflictingModificationAppliedException If subtree was changed in conflicting way
239      * @throws IncorrectDataStructureException If subtree modification is not applicable (e.g. leaf node).
240      */
241     protected abstract void checkSubtreeModificationApplicable(YangInstanceIdentifier path, final NodeModification modification,
242             final Optional<TreeNode> current) throws DataValidationFailedException;
243
244     protected abstract void verifyWrittenStructure(NormalizedNode<?, ?> writtenValue);
245 }