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