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