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