BUG-868: remove InstanceIdentifier.getPath() users
[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 com.google.common.base.Predicate;
13 import com.google.common.collect.FluentIterable;
14 import com.google.common.collect.ImmutableSet;
15 import com.google.common.collect.Iterables;
16
17 import java.util.ArrayList;
18 import java.util.Comparator;
19 import java.util.HashMap;
20 import java.util.List;
21 import java.util.Map;
22 import java.util.Map.Entry;
23 import java.util.concurrent.Future;
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.Node;
38 import org.opendaylight.yangtools.yang.data.api.SimpleNode;
39 import org.opendaylight.yangtools.yang.data.impl.CompositeNodeTOImpl;
40 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
41 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
42 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
43 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
44 import org.opendaylight.yangtools.yang.model.api.SchemaContextListener;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
47
48 public class SchemaAwareDataStoreAdapter extends AbstractLockableDelegator<DataStore> implements //
49 DataStore, //
50 SchemaContextListener, //
51 AutoCloseable {
52
53     private final static Logger LOG = LoggerFactory.getLogger(SchemaAwareDataStoreAdapter.class);
54
55     private SchemaContext schema = null;
56     private boolean validationEnabled = false;
57     private final DataReader<InstanceIdentifier, CompositeNode> reader = new MergeFirstLevelReader();
58
59     @Override
60     public boolean containsConfigurationPath(final InstanceIdentifier path) {
61         try {
62             getDelegateReadLock().lock();
63             return getDelegate().containsConfigurationPath(path);
64
65         } finally {
66             getDelegateReadLock().unlock();
67         }
68     }
69
70     @Override
71     public boolean containsOperationalPath(final InstanceIdentifier path) {
72         try {
73             getDelegateReadLock().lock();
74             return getDelegate().containsOperationalPath(path);
75
76         } finally {
77             getDelegateReadLock().unlock();
78         }
79     }
80
81     @Override
82     public Iterable<InstanceIdentifier> getStoredConfigurationPaths() {
83         try {
84             getDelegateReadLock().lock();
85             return getDelegate().getStoredConfigurationPaths();
86
87         } finally {
88             getDelegateReadLock().unlock();
89         }
90     }
91
92     @Override
93     public Iterable<InstanceIdentifier> getStoredOperationalPaths() {
94         try {
95             getDelegateReadLock().lock();
96             return getDelegate().getStoredOperationalPaths();
97
98         } finally {
99             getDelegateReadLock().unlock();
100         }
101     }
102
103     @Override
104     public CompositeNode readConfigurationData(final InstanceIdentifier path) {
105         return reader.readConfigurationData(path);
106     }
107
108     @Override
109     public CompositeNode readOperationalData(final InstanceIdentifier path) {
110         return reader.readOperationalData(path);
111     }
112
113     @Override
114     public org.opendaylight.controller.md.sal.common.api.data.DataCommitHandler.DataCommitTransaction<InstanceIdentifier, CompositeNode> requestCommit(
115             final DataModification<InstanceIdentifier, CompositeNode> modification) {
116         validateAgainstSchema(modification);
117         NormalizedDataModification cleanedUp = prepareMergedTransaction(modification);
118         cleanedUp.status = TransactionStatus.SUBMITED;
119         return retrieveDelegate().requestCommit(cleanedUp);
120     }
121
122     public boolean isValidationEnabled() {
123         return validationEnabled;
124     }
125
126     public void setValidationEnabled(final boolean validationEnabled) {
127         this.validationEnabled = validationEnabled;
128     }
129
130     private void validateAgainstSchema(final DataModification<InstanceIdentifier, CompositeNode> modification) {
131         if (!validationEnabled) {
132             return;
133         }
134
135         if (schema == null) {
136             LOG.warn("Validation not performed for {}. Reason: YANG Schema not present.", modification.getIdentifier());
137             return;
138         }
139     }
140
141     @Override
142     protected void onDelegateChanged(final DataStore oldDelegate, final DataStore newDelegate) {
143         // NOOP
144     }
145
146     @Override
147     public void onGlobalContextUpdated(final SchemaContext context) {
148         this.schema = context;
149     }
150
151     @Override
152     public void close() throws Exception {
153         this.schema = null;
154     }
155
156     protected CompositeNode mergeData(final InstanceIdentifier path, final CompositeNode stored, final CompositeNode modified,
157             final boolean config) {
158         // long startTime = System.nanoTime();
159         try {
160             DataSchemaNode node = schemaNodeFor(path);
161             return YangDataOperations.merge(node, stored, modified, config);
162         } finally {
163             // System.out.println("Merge time: " + ((System.nanoTime() -
164             // startTime) / 1000.0d));
165         }
166     }
167
168     private DataSchemaNode schemaNodeFor(final InstanceIdentifier path) {
169         checkState(schema != null, "YANG Schema is not available");
170         return YangSchemaUtils.getSchemaNode(schema, path);
171     }
172
173     private NormalizedDataModification prepareMergedTransaction(
174             final DataModification<InstanceIdentifier, CompositeNode> original) {
175         NormalizedDataModification normalized = new NormalizedDataModification(original);
176         LOG.trace("Transaction: {} Removed Configuration {}, Removed Operational {}", original.getIdentifier(),
177                 original.getRemovedConfigurationData(), original.getRemovedConfigurationData());
178         LOG.trace("Transaction: {} Created Configuration {}, Created Operational {}", original.getIdentifier(),
179                 original.getCreatedConfigurationData().entrySet(), original.getCreatedOperationalData().entrySet());
180         LOG.trace("Transaction: {} Updated Configuration {}, Updated Operational {}", original.getIdentifier(),
181                 original.getUpdatedConfigurationData().entrySet(), original.getUpdatedOperationalData().entrySet());
182
183         for (InstanceIdentifier entry : original.getRemovedConfigurationData()) {
184             normalized.deepRemoveConfigurationData(entry);
185         }
186         for (InstanceIdentifier entry : original.getRemovedOperationalData()) {
187             normalized.deepRemoveOperationalData(entry);
188         }
189         for (Entry<InstanceIdentifier, CompositeNode> entry : original.getUpdatedConfigurationData().entrySet()) {
190             normalized.putDeepConfigurationData(entry.getKey(), entry.getValue());
191         }
192         for (Entry<InstanceIdentifier, CompositeNode> entry : original.getUpdatedOperationalData().entrySet()) {
193             normalized.putDeepOperationalData(entry.getKey(), entry.getValue());
194         }
195         return normalized;
196     }
197
198     private Iterable<InstanceIdentifier> getConfigurationSubpaths(final 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(final 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(final InstanceIdentifier entry,
214             final 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(final Entry<InstanceIdentifier, CompositeNode> o1, final 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 (Iterables.isEmpty(path.getPathArguments())) {
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.getValue());
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(final 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 (Iterables.isEmpty(path.getPathArguments())) {
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.getValue());
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(final 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(final 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(final 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(final 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(final InstanceIdentifier entryKey, final CompositeNode entryData) {
360             this.putCompositeNodeData(entryKey, entryData, CONFIGURATIONAL_DATA_STORE_MARKER);
361         }
362
363         public void putDeepOperationalData(final InstanceIdentifier entryKey, final 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(final InstanceIdentifier path, final CompositeNode stored,
384                 final CompositeNode modified) {
385             return mergeData(path, stored, modified, true);
386         }
387
388         @Override
389         protected CompositeNode mergeOperationalData(final InstanceIdentifier path, final CompositeNode stored,
390                 final CompositeNode modified) {
391             return mergeData(path, stored, modified, false);
392         }
393
394         private void putData(final InstanceIdentifier entryKey, final CompositeNode entryData, final 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(final InstanceIdentifier entryKey, final CompositeNode entryData,
412                 final String dataStoreIdentifier) {
413             this.putData(entryKey, entryData, dataStoreIdentifier);
414
415             for (Node<?> child : entryData.getValue()) {
416                 InstanceIdentifier subEntryId = InstanceIdentifier.builder(entryKey).node(child.getNodeType())
417                         .toInstance();
418                 if (child instanceof CompositeNode) {
419                     DataSchemaNode subSchema = schemaNodeFor(subEntryId);
420                     CompositeNode compNode = (CompositeNode) child;
421                     InstanceIdentifier instanceId = null;
422
423                     if (subSchema instanceof ListSchemaNode) {
424                         ListSchemaNode listSubSchema = (ListSchemaNode) subSchema;
425                         Map<QName, Object> mapOfSubValues = this.getValuesFromListSchema(listSubSchema,
426                                 (CompositeNode) child);
427                         if (mapOfSubValues != null) {
428                             instanceId = InstanceIdentifier.builder(entryKey)
429                                     .nodeWithKey(listSubSchema.getQName(), mapOfSubValues).toInstance();
430                         }
431                     } else if (subSchema instanceof ContainerSchemaNode) {
432                         ContainerSchemaNode containerSchema = (ContainerSchemaNode) subSchema;
433                         instanceId = InstanceIdentifier.builder(entryKey).node(subSchema.getQName()).toInstance();
434                     }
435                     if (instanceId != null) {
436                         this.putCompositeNodeData(instanceId, compNode, dataStoreIdentifier);
437                     }
438                 }
439             }
440         }
441
442         private Map<QName, Object> getValuesFromListSchema(final ListSchemaNode listSchema, final CompositeNode entryData) {
443             List<QName> keyDef = listSchema.getKeyDefinition();
444             if (keyDef != null && !keyDef.isEmpty()) {
445                 Map<QName, Object> map = new HashMap<QName, Object>();
446                 for (QName key : keyDef) {
447                     List<Node<?>> data = entryData.get(key);
448                     if (data != null && !data.isEmpty()) {
449                         for (Node<?> nodeData : data) {
450                             if (nodeData instanceof SimpleNode<?>) {
451                                 map.put(key, data.get(0).getValue());
452                             }
453                         }
454                     }
455                 }
456                 return map;
457             }
458             return null;
459         }
460     }
461 }