Merge "Fix issue where NodeConnector ADDED events were propagated as NodeConnector...
[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.util.ArrayList;
13 import java.util.Comparator;
14 import java.util.Map.Entry;
15 import java.util.concurrent.Future;
16
17 import org.opendaylight.controller.md.sal.common.api.TransactionStatus;
18 import org.opendaylight.controller.md.sal.common.api.data.DataModification;
19 import org.opendaylight.controller.md.sal.common.api.data.DataReader;
20 import org.opendaylight.controller.md.sal.common.impl.AbstractDataModification;
21 import org.opendaylight.controller.md.sal.common.impl.util.AbstractLockableDelegator;
22 import org.opendaylight.controller.sal.core.api.data.DataStore;
23 import org.opendaylight.controller.sal.dom.broker.util.YangDataOperations;
24 import org.opendaylight.controller.sal.dom.broker.util.YangSchemaUtils;
25 import org.opendaylight.yangtools.yang.common.QName;
26 import org.opendaylight.yangtools.yang.common.RpcResult;
27 import org.opendaylight.yangtools.yang.data.api.CompositeNode;
28 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
29 import org.opendaylight.yangtools.yang.data.api.Node;
30 import org.opendaylight.yangtools.yang.data.impl.CompositeNodeTOImpl;
31 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
32 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
33 import org.opendaylight.yangtools.yang.model.api.SchemaContextListener;
34 import org.opendaylight.yangtools.yang.model.api.SchemaServiceListener;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
37
38 import com.google.common.base.Predicate;
39 import com.google.common.collect.FluentIterable;
40 import com.google.common.collect.ImmutableSet;
41
42 public class SchemaAwareDataStoreAdapter extends AbstractLockableDelegator<DataStore> implements //
43         DataStore, //
44         SchemaServiceListener, //
45         SchemaContextListener, //
46         AutoCloseable {
47
48     private final static Logger LOG = LoggerFactory.getLogger(SchemaAwareDataStoreAdapter.class);
49
50     private SchemaContext schema = null;
51     private boolean validationEnabled = false;
52     private final DataReader<InstanceIdentifier, CompositeNode> reader = new MergeFirstLevelReader();
53
54     @Override
55     public boolean containsConfigurationPath(InstanceIdentifier path) {
56         try {
57             getDelegateReadLock().lock();
58             return getDelegate().containsConfigurationPath(path);
59
60         } finally {
61             getDelegateReadLock().unlock();
62         }
63     }
64
65     @Override
66     public boolean containsOperationalPath(InstanceIdentifier path) {
67         try {
68             getDelegateReadLock().lock();
69             return getDelegate().containsOperationalPath(path);
70
71         } finally {
72             getDelegateReadLock().unlock();
73         }
74     }
75
76     @Override
77     public Iterable<InstanceIdentifier> getStoredConfigurationPaths() {
78         try {
79             getDelegateReadLock().lock();
80             return getDelegate().getStoredConfigurationPaths();
81
82         } finally {
83             getDelegateReadLock().unlock();
84         }
85     }
86
87     @Override
88     public Iterable<InstanceIdentifier> getStoredOperationalPaths() {
89         try {
90             getDelegateReadLock().lock();
91             return getDelegate().getStoredOperationalPaths();
92
93         } finally {
94             getDelegateReadLock().unlock();
95         }
96     }
97
98     @Override
99     public CompositeNode readConfigurationData(InstanceIdentifier path) {
100         return reader.readConfigurationData(path);
101     }
102
103     @Override
104     public CompositeNode readOperationalData(InstanceIdentifier path) {
105         return reader.readOperationalData(path);
106     }
107
108     @Override
109     public org.opendaylight.controller.md.sal.common.api.data.DataCommitHandler.DataCommitTransaction<InstanceIdentifier, CompositeNode> requestCommit(
110             DataModification<InstanceIdentifier, CompositeNode> modification) {
111         validateAgainstSchema(modification);
112         NormalizedDataModification cleanedUp = prepareMergedTransaction(modification);
113         cleanedUp.status = TransactionStatus.SUBMITED;
114         return retrieveDelegate().requestCommit(cleanedUp);
115     }
116
117     public boolean isValidationEnabled() {
118         return validationEnabled;
119     }
120
121     public void setValidationEnabled(boolean validationEnabled) {
122         this.validationEnabled = validationEnabled;
123     }
124
125     private void validateAgainstSchema(DataModification<InstanceIdentifier, CompositeNode> modification) {
126         if (!validationEnabled) {
127             return;
128         }
129
130         if (schema == null) {
131             LOG.warn("Validation not performed for {}. Reason: YANG Schema not present.", modification.getIdentifier());
132             return;
133         }
134     }
135
136     @Override
137     protected void onDelegateChanged(DataStore oldDelegate, DataStore newDelegate) {
138         // NOOP
139     }
140
141     @Override
142     public void onGlobalContextUpdated(SchemaContext context) {
143         this.schema = context;
144     }
145
146     @Override
147     public void close() throws Exception {
148         this.schema = null;
149     }
150
151     protected CompositeNode mergeData(InstanceIdentifier path, CompositeNode stored, CompositeNode modified,
152             boolean config) {
153         long startTime = System.nanoTime();
154         try {
155             DataSchemaNode node = schemaNodeFor(path);
156             return YangDataOperations.merge(node, stored, modified, config);
157         } finally {
158             // System.out.println("Merge time: " + ((System.nanoTime() -
159             // startTime) / 1000.0d));
160         }
161     }
162
163     private DataSchemaNode schemaNodeFor(InstanceIdentifier path) {
164         checkState(schema != null, "YANG Schema is not available");
165         return YangSchemaUtils.getSchemaNode(schema, path);
166     }
167
168     private NormalizedDataModification prepareMergedTransaction(
169             DataModification<InstanceIdentifier, CompositeNode> original) {
170         NormalizedDataModification normalized = new NormalizedDataModification(original);
171         for (Entry<InstanceIdentifier, CompositeNode> entry : original.getUpdatedConfigurationData().entrySet()) {
172             normalized.putConfigurationData(entry.getKey(), entry.getValue());
173         }
174         for (Entry<InstanceIdentifier, CompositeNode> entry : original.getUpdatedOperationalData().entrySet()) {
175             normalized.putOperationalData(entry.getKey(), entry.getValue());
176         }
177         for (InstanceIdentifier entry : original.getRemovedConfigurationData()) {
178             normalized.deepRemoveConfigurationData(entry);
179         }
180         for (InstanceIdentifier entry : original.getRemovedOperationalData()) {
181             normalized.deepRemoveOperationalData(entry);
182         }
183         return normalized;
184     }
185
186     private Iterable<InstanceIdentifier> getConfigurationSubpaths(InstanceIdentifier entry) {
187         // FIXME: This should be replaced by index
188         Iterable<InstanceIdentifier> paths = getStoredConfigurationPaths();
189
190         return getChildrenPaths(entry, paths);
191
192     }
193
194     public Iterable<InstanceIdentifier> getOperationalSubpaths(InstanceIdentifier entry) {
195         // FIXME: This should be indexed
196         Iterable<InstanceIdentifier> paths = getStoredOperationalPaths();
197
198         return getChildrenPaths(entry, paths);
199     }
200
201     private static final Iterable<InstanceIdentifier> getChildrenPaths(InstanceIdentifier entry,
202             Iterable<InstanceIdentifier> paths) {
203         ImmutableSet.Builder<InstanceIdentifier> children = ImmutableSet.builder();
204         for (InstanceIdentifier potential : paths) {
205             if (entry.contains(potential)) {
206                 children.add(entry);
207             }
208         }
209         return children.build();
210     }
211
212     private final Comparator<Entry<InstanceIdentifier, CompositeNode>> preparationComparator = new Comparator<Entry<InstanceIdentifier, CompositeNode>>() {
213         @Override
214         public int compare(Entry<InstanceIdentifier, CompositeNode> o1, Entry<InstanceIdentifier, CompositeNode> o2) {
215             InstanceIdentifier o1Key = o1.getKey();
216             InstanceIdentifier o2Key = o2.getKey();
217             return Integer.compare(o1Key.getPath().size(), o2Key.getPath().size());
218         }
219     };
220
221     private class MergeFirstLevelReader implements DataReader<InstanceIdentifier, CompositeNode> {
222
223         @Override
224         public CompositeNode readConfigurationData(final InstanceIdentifier path) {
225             getDelegateReadLock().lock();
226             try {
227                 if (path.getPath().isEmpty()) {
228                     return null;
229                 }
230                 QName qname = null;
231                 CompositeNode original = getDelegate().readConfigurationData(path);
232                 ArrayList<Node<?>> childNodes = new ArrayList<Node<?>>();
233                 if (original != null) {
234                     childNodes.addAll(original.getChildren());
235                     qname = original.getNodeType();
236                 } else {
237                     qname = path.getPath().get(path.getPath().size() - 1).getNodeType();
238                 }
239
240                 FluentIterable<InstanceIdentifier> directChildren = FluentIterable.from(getStoredConfigurationPaths())
241                         .filter(new Predicate<InstanceIdentifier>() {
242                             @Override
243                             public boolean apply(InstanceIdentifier input) {
244                                 if (path.contains(input)) {
245                                     int nesting = input.getPath().size() - path.getPath().size();
246                                     if (nesting == 1) {
247                                         return true;
248                                     }
249                                 }
250                                 return false;
251                             }
252                         });
253                 for (InstanceIdentifier instanceIdentifier : directChildren) {
254                     childNodes.add(getDelegate().readConfigurationData(instanceIdentifier));
255                 }
256                 if (original == null && childNodes.isEmpty()) {
257                     return null;
258                 }
259
260                 return new CompositeNodeTOImpl(qname, null, childNodes);
261             } finally {
262                 getDelegateReadLock().unlock();
263             }
264         }
265
266         @Override
267         public CompositeNode readOperationalData(final InstanceIdentifier path) {
268             getDelegateReadLock().lock();
269             try {
270                 if (path.getPath().isEmpty()) {
271                     return null;
272                 }
273                 QName qname = null;
274                 CompositeNode original = getDelegate().readOperationalData(path);
275                 ArrayList<Node<?>> childNodes = new ArrayList<Node<?>>();
276                 if (original != null) {
277                     childNodes.addAll(original.getChildren());
278                     qname = original.getNodeType();
279                 } else {
280                     qname = path.getPath().get(path.getPath().size() - 1).getNodeType();
281                 }
282
283                 FluentIterable<InstanceIdentifier> directChildren = FluentIterable.from(getStoredOperationalPaths())
284                         .filter(new Predicate<InstanceIdentifier>() {
285                             @Override
286                             public boolean apply(InstanceIdentifier input) {
287                                 if (path.contains(input)) {
288                                     int nesting = input.getPath().size() - path.getPath().size();
289                                     if (nesting == 1) {
290                                         return true;
291                                     }
292                                 }
293                                 return false;
294                             }
295                         });
296
297                 for (InstanceIdentifier instanceIdentifier : directChildren) {
298                     childNodes.add(getDelegate().readOperationalData(instanceIdentifier));
299                 }
300                 if (original == null && childNodes.isEmpty()) {
301                     return null;
302                 }
303
304                 return new CompositeNodeTOImpl(qname, null, childNodes);
305             } finally {
306                 getDelegateReadLock().unlock();
307             }
308         }
309     }
310
311     private class NormalizedDataModification extends AbstractDataModification<InstanceIdentifier, CompositeNode> {
312
313         private final Object identifier;
314         private TransactionStatus status;
315
316         public NormalizedDataModification(DataModification<InstanceIdentifier, CompositeNode> original) {
317             super(getDelegate());
318             identifier = original;
319             status = TransactionStatus.NEW;
320         }
321
322         /**
323          *
324          * Ensures all subpaths are removed - this currently does slow lookup in
325          * all keys.
326          *
327          * @param entry
328          */
329         public void deepRemoveOperationalData(InstanceIdentifier entry) {
330             Iterable<InstanceIdentifier> paths = getOperationalSubpaths(entry);
331             removeOperationalData(entry);
332             for (InstanceIdentifier potential : paths) {
333                 removeOperationalData(potential);
334             }
335         }
336
337         public void deepRemoveConfigurationData(InstanceIdentifier entry) {
338             Iterable<InstanceIdentifier> paths = getConfigurationSubpaths(entry);
339             removeConfigurationData(entry);
340             for (InstanceIdentifier potential : paths) {
341                 removeConfigurationData(potential);
342             }
343         }
344
345         @Override
346         public Object getIdentifier() {
347             return this.identifier;
348         }
349
350         @Override
351         public TransactionStatus getStatus() {
352             return status;
353         }
354
355         @Override
356         public Future<RpcResult<TransactionStatus>> commit() {
357             throw new UnsupportedOperationException("Commit should not be invoked on this");
358         }
359
360         @Override
361         protected CompositeNode mergeConfigurationData(InstanceIdentifier path, CompositeNode stored,
362                 CompositeNode modified) {
363             return mergeData(path, stored, modified, true);
364         }
365
366         @Override
367         protected CompositeNode mergeOperationalData(InstanceIdentifier path, CompositeNode stored,
368                 CompositeNode modified) {
369             return mergeData(path, stored, modified, false);
370         }
371     }
372
373 }