fix bug 431
[controller.git] / opendaylight / md-sal / sal-dom-broker / src / main / java / org / opendaylight / controller / sal / dom / broker / impl / SchemaAwareDataStoreAdapter.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.sal.dom.broker.impl;
9
10 import static com.google.common.base.Preconditions.checkState;
11
12 import java.io.Console;
13 import java.util.ArrayList;
14 import java.util.Comparator;
15 import java.util.HashMap;
16 import java.util.Iterator;
17 import java.util.List;
18 import java.util.Map;
19 import java.util.Map.Entry;
20 import java.util.Set;
21 import java.util.concurrent.Future;
22
23 import javax.activation.UnsupportedDataTypeException;
24
25 import org.opendaylight.controller.md.sal.common.api.TransactionStatus;
26 import org.opendaylight.controller.md.sal.common.api.data.DataModification;
27 import org.opendaylight.controller.md.sal.common.api.data.DataReader;
28 import org.opendaylight.controller.md.sal.common.impl.AbstractDataModification;
29 import org.opendaylight.controller.md.sal.common.impl.util.AbstractLockableDelegator;
30 import org.opendaylight.controller.sal.core.api.data.DataStore;
31 import org.opendaylight.controller.sal.dom.broker.util.YangDataOperations;
32 import org.opendaylight.controller.sal.dom.broker.util.YangSchemaUtils;
33 import org.opendaylight.yangtools.yang.common.QName;
34 import org.opendaylight.yangtools.yang.common.RpcResult;
35 import org.opendaylight.yangtools.yang.data.api.CompositeNode;
36 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
37 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.InstanceIdentifierBuilder;
38 import org.opendaylight.yangtools.yang.data.api.Node;
39 import org.opendaylight.yangtools.yang.data.api.SimpleNode;
40 import org.opendaylight.yangtools.yang.data.impl.CompositeNodeTOImpl;
41 import org.opendaylight.yangtools.yang.model.api.ConstraintDefinition;
42 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
43 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
44 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
45 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
46 import org.opendaylight.yangtools.yang.model.api.SchemaContextListener;
47 import org.opendaylight.yangtools.yang.model.api.SchemaServiceListener;
48 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
49 import org.opendaylight.yangtools.yang.model.api.UnknownSchemaNode;
50 import org.slf4j.Logger;
51 import org.slf4j.LoggerFactory;
52
53 import com.google.common.base.Optional;
54 import com.google.common.base.Predicate;
55 import com.google.common.collect.FluentIterable;
56 import com.google.common.collect.ImmutableSet;
57
58 public class SchemaAwareDataStoreAdapter extends AbstractLockableDelegator<DataStore> implements //
59         DataStore, //
60         SchemaServiceListener, //
61         SchemaContextListener, //
62         AutoCloseable {
63
64     private final static Logger LOG = LoggerFactory.getLogger(SchemaAwareDataStoreAdapter.class);
65
66     private SchemaContext schema = null;
67     private boolean validationEnabled = false;
68     private final DataReader<InstanceIdentifier, CompositeNode> reader = new MergeFirstLevelReader();
69
70     @Override
71     public boolean containsConfigurationPath(InstanceIdentifier path) {
72         try {
73             getDelegateReadLock().lock();
74             return getDelegate().containsConfigurationPath(path);
75
76         } finally {
77             getDelegateReadLock().unlock();
78         }
79     }
80
81     @Override
82     public boolean containsOperationalPath(InstanceIdentifier path) {
83         try {
84             getDelegateReadLock().lock();
85             return getDelegate().containsOperationalPath(path);
86
87         } finally {
88             getDelegateReadLock().unlock();
89         }
90     }
91
92     @Override
93     public Iterable<InstanceIdentifier> getStoredConfigurationPaths() {
94         try {
95             getDelegateReadLock().lock();
96             return getDelegate().getStoredConfigurationPaths();
97
98         } finally {
99             getDelegateReadLock().unlock();
100         }
101     }
102
103     @Override
104     public Iterable<InstanceIdentifier> getStoredOperationalPaths() {
105         try {
106             getDelegateReadLock().lock();
107             return getDelegate().getStoredOperationalPaths();
108
109         } finally {
110             getDelegateReadLock().unlock();
111         }
112     }
113
114     @Override
115     public CompositeNode readConfigurationData(InstanceIdentifier path) {
116         return reader.readConfigurationData(path);
117     }
118
119     @Override
120     public CompositeNode readOperationalData(InstanceIdentifier path) {
121         return reader.readOperationalData(path);
122     }
123
124     @Override
125     public org.opendaylight.controller.md.sal.common.api.data.DataCommitHandler.DataCommitTransaction<InstanceIdentifier, CompositeNode> requestCommit(
126             DataModification<InstanceIdentifier, CompositeNode> modification) {
127         validateAgainstSchema(modification);
128         NormalizedDataModification cleanedUp = prepareMergedTransaction(modification);
129         cleanedUp.status = TransactionStatus.SUBMITED;
130         return retrieveDelegate().requestCommit(cleanedUp);
131     }
132
133     public boolean isValidationEnabled() {
134         return validationEnabled;
135     }
136
137     public void setValidationEnabled(boolean validationEnabled) {
138         this.validationEnabled = validationEnabled;
139     }
140
141     private void validateAgainstSchema(DataModification<InstanceIdentifier, CompositeNode> modification) {
142         if (!validationEnabled) {
143             return;
144         }
145
146         if (schema == null) {
147             LOG.warn("Validation not performed for {}. Reason: YANG Schema not present.", modification.getIdentifier());
148             return;
149         }
150     }
151
152     @Override
153     protected void onDelegateChanged(DataStore oldDelegate, DataStore newDelegate) {
154         // NOOP
155     }
156
157     @Override
158     public void onGlobalContextUpdated(SchemaContext context) {
159         this.schema = context;
160     }
161
162     @Override
163     public void close() throws Exception {
164         this.schema = null;
165     }
166
167     protected CompositeNode mergeData(InstanceIdentifier path, CompositeNode stored, CompositeNode modified,
168             boolean config) {
169         // long startTime = System.nanoTime();
170         try {
171             DataSchemaNode node = schemaNodeFor(path);
172             return YangDataOperations.merge(node, stored, modified, config);
173         } finally {
174             // System.out.println("Merge time: " + ((System.nanoTime() -
175             // startTime) / 1000.0d));
176         }
177     }
178
179     private DataSchemaNode schemaNodeFor(InstanceIdentifier path) {
180         checkState(schema != null, "YANG Schema is not available");
181         return YangSchemaUtils.getSchemaNode(schema, path);
182     }
183
184     private NormalizedDataModification prepareMergedTransaction(
185             DataModification<InstanceIdentifier, CompositeNode> original) {
186         NormalizedDataModification normalized = new NormalizedDataModification(original);
187         for (Entry<InstanceIdentifier, CompositeNode> entry : original.getUpdatedConfigurationData().entrySet()) {
188             normalized.putDeepConfigurationData(entry.getKey(), entry.getValue());
189         }
190         for (Entry<InstanceIdentifier, CompositeNode> entry : original.getUpdatedOperationalData().entrySet()) {
191             normalized.putDeepOperationalData(entry.getKey(), entry.getValue());
192         }
193         for (InstanceIdentifier entry : original.getRemovedConfigurationData()) {
194             normalized.deepRemoveConfigurationData(entry);
195         }
196         for (InstanceIdentifier entry : original.getRemovedOperationalData()) {
197             normalized.deepRemoveOperationalData(entry);
198         }
199         return normalized;
200     }
201
202     private Iterable<InstanceIdentifier> getConfigurationSubpaths(InstanceIdentifier entry) {
203         // FIXME: This should be replaced by index
204         Iterable<InstanceIdentifier> paths = getStoredConfigurationPaths();
205
206         return getChildrenPaths(entry, paths);
207
208     }
209
210     public Iterable<InstanceIdentifier> getOperationalSubpaths(InstanceIdentifier entry) {
211         // FIXME: This should be indexed
212         Iterable<InstanceIdentifier> paths = getStoredOperationalPaths();
213
214         return getChildrenPaths(entry, paths);
215     }
216
217     private static final Iterable<InstanceIdentifier> getChildrenPaths(InstanceIdentifier entry,
218             Iterable<InstanceIdentifier> paths) {
219         ImmutableSet.Builder<InstanceIdentifier> children = ImmutableSet.builder();
220         for (InstanceIdentifier potential : paths) {
221             if (entry.contains(potential)) {
222                 children.add(entry);
223             }
224         }
225         return children.build();
226     }
227
228     private final Comparator<Entry<InstanceIdentifier, CompositeNode>> preparationComparator = new Comparator<Entry<InstanceIdentifier, CompositeNode>>() {
229         @Override
230         public int compare(Entry<InstanceIdentifier, CompositeNode> o1, Entry<InstanceIdentifier, CompositeNode> o2) {
231             InstanceIdentifier o1Key = o1.getKey();
232             InstanceIdentifier o2Key = o2.getKey();
233             return Integer.compare(o1Key.getPath().size(), o2Key.getPath().size());
234         }
235     };
236
237     private class MergeFirstLevelReader implements DataReader<InstanceIdentifier, CompositeNode> {
238
239         @Override
240         public CompositeNode readConfigurationData(final InstanceIdentifier path) {
241             getDelegateReadLock().lock();
242             try {
243                 if (path.getPath().isEmpty()) {
244                     return null;
245                 }
246                 QName qname = null;
247                 CompositeNode original = getDelegate().readConfigurationData(path);
248                 ArrayList<Node<?>> childNodes = new ArrayList<Node<?>>();
249                 if (original != null) {
250                     childNodes.addAll(original.getChildren());
251                     qname = original.getNodeType();
252                 } else {
253                     qname = path.getPath().get(path.getPath().size() - 1).getNodeType();
254                 }
255
256                 FluentIterable<InstanceIdentifier> directChildren = FluentIterable.from(getStoredConfigurationPaths())
257                         .filter(new Predicate<InstanceIdentifier>() {
258                             @Override
259                             public boolean apply(InstanceIdentifier input) {
260                                 if (path.contains(input)) {
261                                     int nesting = input.getPath().size() - path.getPath().size();
262                                     if (nesting == 1) {
263                                         return true;
264                                     }
265                                 }
266                                 return false;
267                             }
268                         });
269                 for (InstanceIdentifier instanceIdentifier : directChildren) {
270                     childNodes.add(getDelegate().readConfigurationData(instanceIdentifier));
271                 }
272                 if (original == null && childNodes.isEmpty()) {
273                     return null;
274                 }
275
276                 return new CompositeNodeTOImpl(qname, null, childNodes);
277             } finally {
278                 getDelegateReadLock().unlock();
279             }
280         }
281
282         @Override
283         public CompositeNode readOperationalData(final InstanceIdentifier path) {
284             getDelegateReadLock().lock();
285             try {
286                 if (path.getPath().isEmpty()) {
287                     return null;
288                 }
289                 QName qname = null;
290                 CompositeNode original = getDelegate().readOperationalData(path);
291                 ArrayList<Node<?>> childNodes = new ArrayList<Node<?>>();
292                 if (original != null) {
293                     childNodes.addAll(original.getChildren());
294                     qname = original.getNodeType();
295                 } else {
296                     qname = path.getPath().get(path.getPath().size() - 1).getNodeType();
297                 }
298
299                 FluentIterable<InstanceIdentifier> directChildren = FluentIterable.from(getStoredOperationalPaths())
300                         .filter(new Predicate<InstanceIdentifier>() {
301                             @Override
302                             public boolean apply(InstanceIdentifier input) {
303                                 if (path.contains(input)) {
304                                     int nesting = input.getPath().size() - path.getPath().size();
305                                     if (nesting == 1) {
306                                         return true;
307                                     }
308                                 }
309                                 return false;
310                             }
311                         });
312
313                 for (InstanceIdentifier instanceIdentifier : directChildren) {
314                     childNodes.add(getDelegate().readOperationalData(instanceIdentifier));
315                 }
316                 if (original == null && childNodes.isEmpty()) {
317                     return null;
318                 }
319
320                 return new CompositeNodeTOImpl(qname, null, childNodes);
321             } finally {
322                 getDelegateReadLock().unlock();
323             }
324         }
325     }
326
327     private class NormalizedDataModification extends AbstractDataModification<InstanceIdentifier, CompositeNode> {
328
329         private final String CONFIGURATIONAL_DATA_STORE_MARKER = "configurational";
330         private final String OPERATIONAL_DATA_STORE_MARKER = "operational";
331         private final Object identifier;
332         private TransactionStatus status;
333
334         public NormalizedDataModification(DataModification<InstanceIdentifier, CompositeNode> original) {
335             super(getDelegate());
336             identifier = original;
337             status = TransactionStatus.NEW;
338         }
339
340         /**
341          *
342          * Ensures all subpaths are removed - this currently does slow lookup in
343          * all keys.
344          *
345          * @param entry
346          */
347         public void deepRemoveOperationalData(InstanceIdentifier entry) {
348             Iterable<InstanceIdentifier> paths = getOperationalSubpaths(entry);
349             removeOperationalData(entry);
350             for (InstanceIdentifier potential : paths) {
351                 removeOperationalData(potential);
352             }
353         }
354
355         public void deepRemoveConfigurationData(InstanceIdentifier entry) {
356             Iterable<InstanceIdentifier> paths = getConfigurationSubpaths(entry);
357             removeConfigurationData(entry);
358             for (InstanceIdentifier potential : paths) {
359                 removeConfigurationData(potential);
360             }
361         }
362
363         public void putDeepConfigurationData(InstanceIdentifier entryKey, CompositeNode entryData) {
364             this.putCompositeNodeData(entryKey, entryData, CONFIGURATIONAL_DATA_STORE_MARKER);
365         }
366         
367         public void putDeepOperationalData(InstanceIdentifier entryKey, CompositeNode entryData) {
368             this.putCompositeNodeData(entryKey, entryData, OPERATIONAL_DATA_STORE_MARKER);
369         }
370
371         @Override
372         public Object getIdentifier() {
373             return this.identifier;
374         }
375
376         @Override
377         public TransactionStatus getStatus() {
378             return status;
379         }
380
381         @Override
382         public Future<RpcResult<TransactionStatus>> commit() {
383             throw new UnsupportedOperationException("Commit should not be invoked on this");
384         }
385
386         @Override
387         protected CompositeNode mergeConfigurationData(InstanceIdentifier path, CompositeNode stored,
388                 CompositeNode modified) {
389             return mergeData(path, stored, modified, true);
390         }
391
392         @Override
393         protected CompositeNode mergeOperationalData(InstanceIdentifier path, CompositeNode stored,
394                 CompositeNode modified) {
395             return mergeData(path, stored, modified, false);
396         }
397
398         private void putData(InstanceIdentifier entryKey, CompositeNode entryData, String dataStoreIdentifier) {
399             if (dataStoreIdentifier != null && entryKey != null && entryData != null) {
400                 switch (dataStoreIdentifier) {
401                 case (CONFIGURATIONAL_DATA_STORE_MARKER):
402                     this.putConfigurationData(entryKey, entryData);
403                     break;
404                 case (OPERATIONAL_DATA_STORE_MARKER):
405                     this.putOperationalData(entryKey, entryData);
406                     break;
407
408                 default :
409                     LOG.error(dataStoreIdentifier + " is NOT valid DataStore switch marker");
410                     throw new RuntimeException(dataStoreIdentifier + " is NOT valid DataStore switch marker");
411                 }
412             }
413         }
414
415         private void putCompositeNodeData(InstanceIdentifier entryKey, CompositeNode entryData, String dataStoreIdentifier) {
416             this.putData(entryKey, entryData, dataStoreIdentifier);
417             
418             for (Node<?> child : entryData.getChildren()) {
419                 InstanceIdentifier subEntryId = InstanceIdentifier.builder(entryKey).node(child.getNodeType()).toInstance();
420                 if (child instanceof CompositeNode) {
421                     DataSchemaNode subSchema = schemaNodeFor(subEntryId);
422                     CompositeNode compNode = (CompositeNode) child;
423                     InstanceIdentifier instanceId = null;
424
425                     if (subSchema instanceof ListSchemaNode) {
426                         ListSchemaNode listSubSchema = (ListSchemaNode) subSchema;
427                         Map<QName, Object> mapOfSubValues = this.getValuesFromListSchema(listSubSchema, (CompositeNode) child);
428                         if (mapOfSubValues != null) {
429                             instanceId = InstanceIdentifier.builder(entryKey).nodeWithKey(listSubSchema.getQName(), mapOfSubValues).toInstance();
430                         }
431                     } 
432                     else if (subSchema instanceof ContainerSchemaNode) {
433                         ContainerSchemaNode containerSchema = (ContainerSchemaNode) subSchema;
434                         instanceId = InstanceIdentifier.builder(entryKey).node(subSchema.getQName()).toInstance();
435                     }
436                     if (instanceId != null) {
437                         this.putCompositeNodeData(instanceId, compNode, dataStoreIdentifier);
438                     }
439                 }
440             }
441         }
442
443         private Map<QName, Object> getValuesFromListSchema (ListSchemaNode listSchema, CompositeNode entryData) {
444             List<QName> keyDef = listSchema.getKeyDefinition();
445             if (keyDef != null && ! keyDef.isEmpty()) {
446                 Map<QName, Object> map = new HashMap<QName, Object>();
447                 for (QName key : keyDef) {
448                     List<Node<?>> data = entryData.get(key);
449                     if (data != null && ! data.isEmpty()) {
450                         for (Node<?> nodeData : data) {
451                             if (nodeData instanceof SimpleNode<?>) {
452                                 map.put(key, data.get(0).getValue());
453                             }
454                         }
455                     }
456                 }
457                 return map;
458             }
459             return null;
460         }
461     }
462 }