Bug 499: Initial draft of in-memory datastore and data broker
[controller.git] / opendaylight / md-sal / sal-dom-broker / src / main / java / org / opendaylight / controller / md / sal / dom / store / impl / SchemaAwareApplyOperation.java
1 package org.opendaylight.controller.md.sal.dom.store.impl;
2
3 import static com.google.common.base.Preconditions.checkArgument;
4
5 import java.util.Set;
6
7 import org.opendaylight.controller.md.sal.dom.store.impl.tree.ModificationType;
8 import org.opendaylight.controller.md.sal.dom.store.impl.tree.NodeModification;
9 import org.opendaylight.controller.md.sal.dom.store.impl.tree.StoreMetadataNode;
10 import org.opendaylight.controller.md.sal.dom.store.impl.tree.StoreNodeCompositeBuilder;
11 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifier;
12 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifierWithPredicates;
13 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeWithValue;
14 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
15 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
16 import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
17 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
18 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
19 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
20 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
21 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
22 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
23 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeBuilder;
24 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.NormalizedNodeContainerBuilder;
25 import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder;
26 import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableLeafSetNodeBuilder;
27 import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableMapEntryNodeBuilder;
28 import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableMapNodeBuilder;
29 import org.opendaylight.yangtools.yang.model.api.ChoiceNode;
30 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
31 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
32 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
33 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
34 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
35 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
36
37 import com.google.common.base.Function;
38 import com.google.common.base.Optional;
39 import com.google.common.cache.Cache;
40 import com.google.common.cache.CacheBuilder;
41 import com.google.common.cache.CacheLoader;
42 import com.google.common.collect.ImmutableSet;
43 import com.google.common.collect.ImmutableSet.Builder;
44 import com.google.common.primitives.UnsignedLong;
45
46 public abstract class SchemaAwareApplyOperation implements ModificationApplyOperation {
47
48     public static SchemaAwareApplyOperation from(final DataSchemaNode schemaNode) {
49         if (schemaNode instanceof ContainerSchemaNode) {
50             return new ContainerModificationStrategy((ContainerSchemaNode) schemaNode);
51         } else if (schemaNode instanceof ListSchemaNode) {
52             return new ListMapModificationStrategy((ListSchemaNode) schemaNode);
53         } else if (schemaNode instanceof ChoiceNode) {
54             return new ChoiceModificationStrategy((ChoiceNode) schemaNode);
55         } else if (schemaNode instanceof LeafListSchemaNode) {
56             return new LeafSetEntryModificationStrategy((LeafListSchemaNode) schemaNode);
57         } else if (schemaNode instanceof LeafSchemaNode) {
58             return new LeafModificationStrategy((LeafSchemaNode) schemaNode);
59         }
60         throw new IllegalArgumentException("Not supported schema node type for " + schemaNode.getClass());
61     }
62
63     @Override
64     public Optional<ModificationApplyOperation> getChild(final PathArgument child) {
65         throw new IllegalArgumentException();
66     }
67
68     protected final ModificationApplyOperation resolveChildOperation(final PathArgument child) {
69         Optional<ModificationApplyOperation> potential = getChild(child);
70         checkArgument(potential.isPresent(), "Operation for child %s is not defined.", child);
71         return potential.get();
72     }
73
74     @Override
75     public void verifyStructure(final NodeModification modification) throws IllegalArgumentException {
76         if (modification.getModificationType() == ModificationType.WRITE) {
77             verifyWritenStructure(modification.getWritenValue());
78         }
79     }
80
81     protected abstract void verifyWritenStructure(NormalizedNode<?, ?> writenValue);
82
83     @Override
84     public boolean isApplicable(final NodeModification modification, final Optional<StoreMetadataNode> current) {
85         switch (modification.getModificationType()) {
86         case DELETE:
87             return isDeleteApplicable(modification, current);
88         case SUBTREE_MODIFIED:
89             return isSubtreeModificationApplicable(modification, current);
90         case WRITE:
91             return isWriteApplicable(modification, current);
92         case UNMODIFIED:
93             return true;
94         default:
95             return false;
96         }
97     }
98
99     protected boolean isWriteApplicable(final NodeModification modification, final Optional<StoreMetadataNode> current) {
100         Optional<StoreMetadataNode> original = modification.getOriginal();
101         if (original.isPresent() && current.isPresent()) {
102             return isNotConflicting(original.get(), current.get());
103         } else if (current.isPresent()) {
104             return false;
105         }
106         return true;
107
108     }
109
110     protected final boolean isNotConflicting(final StoreMetadataNode original, final StoreMetadataNode current) {
111         return original.getNodeVersion().equals(current.getNodeVersion())
112                 && original.getSubtreeVersion().equals(current.getSubtreeVersion());
113     }
114
115     protected abstract boolean isSubtreeModificationApplicable(final NodeModification modification,
116             final Optional<StoreMetadataNode> current);
117
118     private boolean isDeleteApplicable(final NodeModification modification, final Optional<StoreMetadataNode> current) {
119         // FiXME: Add delete conflict detection.
120         return true;
121     }
122
123     @Override
124     public final Optional<StoreMetadataNode> apply(final NodeModification modification,
125             final Optional<StoreMetadataNode> currentMeta, final UnsignedLong subtreeVersion) {
126         switch (modification.getModificationType()) {
127         case DELETE:
128             return Optional.absent();
129         case SUBTREE_MODIFIED:
130             return Optional.of(applySubtreeChange(modification, currentMeta.get(), subtreeVersion));
131         case WRITE:
132             return Optional.of(applyWrite(modification, currentMeta, subtreeVersion));
133         case UNMODIFIED:
134             return currentMeta;
135         default:
136             throw new IllegalArgumentException("Provided modification type is not supported.");
137         }
138     }
139
140     protected abstract StoreMetadataNode applyWrite(NodeModification modification,
141             Optional<StoreMetadataNode> currentMeta, UnsignedLong subtreeVersion);
142
143     protected abstract StoreMetadataNode applySubtreeChange(NodeModification modification,
144             StoreMetadataNode currentMeta, UnsignedLong subtreeVersion);
145
146     public static abstract class ValueNodeModificationStrategy<T extends DataSchemaNode> extends
147             SchemaAwareApplyOperation {
148
149         private final T schema;
150         private final Class<? extends NormalizedNode<?, ?>> nodeClass;
151
152         protected ValueNodeModificationStrategy(final T schema, final Class<? extends NormalizedNode<?, ?>> nodeClass) {
153             super();
154             this.schema = schema;
155             this.nodeClass = nodeClass;
156         }
157
158         @Override
159         protected void verifyWritenStructure(final NormalizedNode<?, ?> writenValue) {
160             checkArgument(nodeClass.isInstance(writenValue), "Node should must be of type %s", nodeClass);
161         }
162
163         @Override
164         public Optional<ModificationApplyOperation> getChild(final PathArgument child) {
165             throw new UnsupportedOperationException("Node " + schema.getPath()
166                     + "is leaf type node. Child nodes not allowed");
167         }
168
169         @Override
170         protected StoreMetadataNode applySubtreeChange(final NodeModification modification,
171                 final StoreMetadataNode currentMeta, final UnsignedLong subtreeVersion) {
172             throw new UnsupportedOperationException("Node " + schema.getPath()
173                     + "is leaf type node. Subtree change is not allowed.");
174         }
175
176         @Override
177         protected StoreMetadataNode applyWrite(final NodeModification modification,
178                 final Optional<StoreMetadataNode> currentMeta, final UnsignedLong subtreeVersion) {
179             UnsignedLong nodeVersion = subtreeVersion;
180             if (currentMeta.isPresent()) {
181                 nodeVersion = StoreUtils.increase(currentMeta.get().getNodeVersion());
182             }
183
184             return StoreMetadataNode.builder().setNodeVersion(nodeVersion).setSubtreeVersion(subtreeVersion)
185                     .setData(modification.getWritenValue()).build();
186         }
187
188         @Override
189         protected boolean isSubtreeModificationApplicable(final NodeModification modification,
190                 final Optional<StoreMetadataNode> current) {
191             return false;
192         }
193
194     }
195
196     public static class LeafSetEntryModificationStrategy extends ValueNodeModificationStrategy<LeafListSchemaNode> {
197
198         @SuppressWarnings({ "unchecked", "rawtypes" })
199         protected LeafSetEntryModificationStrategy(final LeafListSchemaNode schema) {
200             super(schema, (Class) LeafSetEntryNode.class);
201         }
202     }
203
204     public static class LeafModificationStrategy extends ValueNodeModificationStrategy<LeafSchemaNode> {
205
206         @SuppressWarnings({ "unchecked", "rawtypes" })
207         protected LeafModificationStrategy(final LeafSchemaNode schema) {
208             super(schema, (Class) LeafNode.class);
209         }
210     }
211
212     public static abstract class NormalizedNodeContainerModificationStrategy extends SchemaAwareApplyOperation {
213
214         private final Class<? extends NormalizedNode<?, ?>> nodeClass;
215
216         protected NormalizedNodeContainerModificationStrategy(final Class<? extends NormalizedNode<?, ?>> nodeClass) {
217             this.nodeClass = nodeClass;
218         }
219
220
221         @Override
222         public void verifyStructure(final NodeModification modification) throws IllegalArgumentException {
223             if(modification.getModificationType() == ModificationType.WRITE) {
224
225             }
226             for(NodeModification childModification : modification.getModifications()) {
227                 resolveChildOperation(childModification.getIdentifier()).verifyStructure(childModification);
228             }
229         }
230
231         @SuppressWarnings("rawtypes")
232         @Override
233         protected void verifyWritenStructure(final NormalizedNode<?, ?> writenValue) {
234             checkArgument(nodeClass.isInstance(writenValue), "Node should must be of type %s", nodeClass);
235             checkArgument(writenValue instanceof NormalizedNodeContainer);
236             NormalizedNodeContainer writenCont = (NormalizedNodeContainer) writenValue;
237             for(Object child : writenCont.getValue()) {
238                 checkArgument(child instanceof NormalizedNode);
239                 NormalizedNode childNode = (NormalizedNode) child;
240             }
241         }
242
243         @Override
244         protected StoreMetadataNode applyWrite(final NodeModification modification,
245                 final Optional<StoreMetadataNode> currentMeta, final UnsignedLong subtreeVersion) {
246             //
247             NormalizedNode<?, ?> newValue = modification.getWritenValue();
248
249             UnsignedLong nodeVersion = subtreeVersion;
250             if (currentMeta.isPresent()) {
251                 nodeVersion = StoreUtils.increase(currentMeta.get().getNodeVersion());
252             }
253             StoreMetadataNode newValueMeta = StoreMetadataNode.createRecursivelly(newValue, nodeVersion, nodeVersion);
254
255             if (!modification.hasAdditionalModifications()) {
256                 return newValueMeta;
257             }
258             @SuppressWarnings("rawtypes")
259             NormalizedNodeContainerBuilder dataBuilder = createBuilder(modification.getIdentifier());
260             StoreNodeCompositeBuilder builder = StoreNodeCompositeBuilder.from(dataBuilder) //
261                     .setNodeVersion(nodeVersion) //
262                     .setSubtreeVersion(subtreeVersion);
263
264             Set<PathArgument> processedPreexisting = applyPreexistingChildren(modification, newValueMeta.getChildren(),
265                     builder, nodeVersion);
266             applyNewChildren(modification, processedPreexisting, builder, nodeVersion);
267
268             return builder.build();
269
270         }
271
272         @Override
273         public StoreMetadataNode applySubtreeChange(final NodeModification modification,
274                 final StoreMetadataNode currentMeta, final UnsignedLong subtreeVersion) {
275
276             UnsignedLong updatedSubtreeVersion = StoreUtils.increase(currentMeta.getSubtreeVersion());
277             @SuppressWarnings("rawtypes")
278             NormalizedNodeContainerBuilder dataBuilder = createBuilder(modification.getIdentifier());
279             StoreNodeCompositeBuilder builder = StoreNodeCompositeBuilder.from(dataBuilder)
280                     //
281                     .setIdentifier(modification.getIdentifier()).setNodeVersion(currentMeta.getNodeVersion())
282                     .setSubtreeVersion(updatedSubtreeVersion);
283             // We process preexisting nodes
284             Set<PathArgument> processedPreexisting = applyPreexistingChildren(modification, currentMeta.getChildren(),
285                     builder, updatedSubtreeVersion);
286             applyNewChildren(modification, processedPreexisting, builder, updatedSubtreeVersion);
287             return builder.build();
288         }
289
290         private void applyNewChildren(final NodeModification modification, final Set<PathArgument> ignore,
291                 final StoreNodeCompositeBuilder builder, final UnsignedLong subtreeVersion) {
292             for (NodeModification childModification : modification.getModifications()) {
293                 PathArgument childIdentifier = childModification.getIdentifier();
294                 // We skip allready processed modifications
295                 if (ignore.contains(childIdentifier)) {
296                     continue;
297                 }
298
299                 builder.addIfPresent(resolveChildOperation(childIdentifier) //
300                         .apply(childModification, Optional.<StoreMetadataNode> absent(), subtreeVersion));
301             }
302         }
303
304         private Set<PathArgument> applyPreexistingChildren(final NodeModification modification,
305                 final Iterable<StoreMetadataNode> children, final StoreNodeCompositeBuilder nodeBuilder,
306                 final UnsignedLong subtreeVersion) {
307             Builder<PathArgument> processedModifications = ImmutableSet.<PathArgument> builder();
308             for (StoreMetadataNode childMeta : children) {
309                 PathArgument childIdentifier = childMeta.getIdentifier();
310                 // We retrieve Child modification metadata
311                 Optional<NodeModification> childModification = modification.getChild(childIdentifier);
312                 // Node is modified
313                 if (childModification.isPresent()) {
314                     processedModifications.add(childIdentifier);
315                     Optional<StoreMetadataNode> result = resolveChildOperation(childIdentifier) //
316                             .apply(childModification.get(), Optional.of(childMeta), subtreeVersion);
317                     nodeBuilder.addIfPresent(result);
318                 } else {
319                     // Child is unmodified - reuse existing metadata and data
320                     // snapshot
321                     nodeBuilder.add(childMeta);
322                 }
323             }
324             return processedModifications.build();
325         }
326
327         @Override
328         protected boolean isSubtreeModificationApplicable(final NodeModification modification,
329                 final Optional<StoreMetadataNode> current) {
330             if (false == current.isPresent()) {
331                 return false;
332             }
333             boolean result = true;
334             StoreMetadataNode currentMeta = current.get();
335             for (NodeModification childMod : modification.getModifications()) {
336                 PathArgument childId = childMod.getIdentifier();
337                 Optional<StoreMetadataNode> childMeta = currentMeta.getChild(childId);
338                 result &= resolveChildOperation(childId).isApplicable(childMod, childMeta);
339             }
340             return result;
341         }
342
343         @SuppressWarnings("rawtypes")
344         protected abstract NormalizedNodeContainerBuilder createBuilder(PathArgument identifier);
345     }
346
347     public static abstract class DataNodeContainerModificationStrategy<T extends DataNodeContainer> extends
348             NormalizedNodeContainerModificationStrategy {
349
350         private final T schema;
351         private final Cache<PathArgument, ModificationApplyOperation> childCache = CacheBuilder.newBuilder()
352                 .build(CacheLoader.from(new Function<PathArgument, ModificationApplyOperation>() {
353
354                 @Override
355                 public ModificationApplyOperation apply(final PathArgument identifier) {
356                     DataSchemaNode child = schema.getDataChildByName(identifier.getNodeType());
357                     if (child == null || child.isAugmenting()) {
358                         return null;
359                     }
360                     return from(child);
361                 }
362                 }));
363
364         protected DataNodeContainerModificationStrategy(final T schema,
365                 final Class<? extends NormalizedNode<?, ?>> nodeClass) {
366             super(nodeClass);
367             this.schema = schema;
368         }
369
370         protected T getSchema() {
371             return schema;
372         }
373
374         @Override
375         public Optional<ModificationApplyOperation> getChild(final PathArgument identifier) {
376             DataSchemaNode child = schema.getDataChildByName(identifier.getNodeType());
377             if (child == null || child.isAugmenting()) {
378                 return Optional.absent();
379             }
380             return Optional.<ModificationApplyOperation> of(from(child));
381         }
382
383         @Override
384         @SuppressWarnings("rawtypes")
385         protected abstract DataContainerNodeBuilder createBuilder(PathArgument identifier);
386
387         @Override
388         public String toString() {
389             return getClass().getSimpleName() + " [" + schema + "]";
390         }
391
392     }
393
394     public static class ContainerModificationStrategy extends
395             DataNodeContainerModificationStrategy<ContainerSchemaNode> {
396
397         public ContainerModificationStrategy(final ContainerSchemaNode schemaNode) {
398             super(schemaNode, ContainerNode.class);
399         }
400
401         @Override
402         @SuppressWarnings("rawtypes")
403         protected DataContainerNodeBuilder createBuilder(final PathArgument identifier) {
404             // TODO Auto-generated method stub
405             checkArgument(identifier instanceof NodeIdentifier);
406             return ImmutableContainerNodeBuilder.create().withNodeIdentifier((NodeIdentifier) identifier);
407         }
408
409     }
410
411     public static class ChoiceModificationStrategy extends NormalizedNodeContainerModificationStrategy {
412
413         private final ChoiceNode schema;
414
415         public ChoiceModificationStrategy(final ChoiceNode schemaNode) {
416             super(org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode.class);
417             this.schema = schemaNode;
418         }
419
420         @Override
421         @SuppressWarnings("rawtypes")
422         protected DataContainerNodeBuilder createBuilder(final PathArgument identifier) {
423             checkArgument(identifier instanceof NodeIdentifier);
424             return ImmutableContainerNodeBuilder.create().withNodeIdentifier((NodeIdentifier) identifier);
425         }
426
427     }
428
429     public static class ListEntryModificationStrategy extends DataNodeContainerModificationStrategy<ListSchemaNode> {
430
431         protected ListEntryModificationStrategy(final ListSchemaNode schema) {
432             super(schema, MapEntryNode.class);
433         }
434
435         @Override
436         @SuppressWarnings("rawtypes")
437         protected final DataContainerNodeBuilder createBuilder(final PathArgument identifier) {
438             return ImmutableMapEntryNodeBuilder.create().withNodeIdentifier((NodeIdentifierWithPredicates) identifier);
439         }
440
441     }
442
443     public static class LeafSetModificationStrategy extends NormalizedNodeContainerModificationStrategy {
444
445         private final Optional<ModificationApplyOperation> entryStrategy;
446
447         @SuppressWarnings({ "unchecked", "rawtypes" })
448         protected LeafSetModificationStrategy(final LeafListSchemaNode schema) {
449             super((Class) LeafSetNode.class);
450             entryStrategy = Optional.<ModificationApplyOperation> of(new LeafSetEntryModificationStrategy(schema));
451         }
452
453         @SuppressWarnings("rawtypes")
454         @Override
455         protected NormalizedNodeContainerBuilder createBuilder(final PathArgument identifier) {
456             return ImmutableLeafSetNodeBuilder.create().withNodeIdentifier((NodeIdentifier) identifier);
457         }
458
459         @Override
460         public Optional<ModificationApplyOperation> getChild(final PathArgument identifier) {
461             if (identifier instanceof NodeWithValue) {
462                 return entryStrategy;
463             }
464             return Optional.absent();
465         }
466
467     }
468
469     public static class ListMapModificationStrategy extends NormalizedNodeContainerModificationStrategy {
470
471         private final Optional<ModificationApplyOperation> entryStrategy;
472
473         protected ListMapModificationStrategy(final ListSchemaNode schema) {
474             super(MapNode.class);
475             entryStrategy = Optional.<ModificationApplyOperation> of(new ListEntryModificationStrategy(schema));
476         }
477
478         @SuppressWarnings("rawtypes")
479         @Override
480         protected NormalizedNodeContainerBuilder createBuilder(final PathArgument identifier) {
481             return ImmutableMapNodeBuilder.create().withNodeIdentifier((NodeIdentifier) identifier);
482         }
483
484         @Override
485         public Optional<ModificationApplyOperation> getChild(final PathArgument identifier) {
486             if (identifier instanceof NodeIdentifierWithPredicates) {
487                 return entryStrategy;
488             }
489             return Optional.absent();
490         }
491
492         @Override
493         public String toString() {
494             return "ListMapModificationStrategy [entry=" + entryStrategy + "]";
495         }
496     }
497
498     public void verifyIdentifier(final PathArgument identifier) {
499
500     }
501
502 }