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