Merge "Add missing copyright text"
[controller.git] / opendaylight / md-sal / sal-rest-connector / src / main / java / org / opendaylight / controller / sal / restconf / impl / BrokerFacade.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.restconf.impl;
9
10 import static org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType.CONFIGURATION;
11 import static org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType.OPERATIONAL;
12
13 import com.google.common.base.Optional;
14 import com.google.common.base.Preconditions;
15 import com.google.common.util.concurrent.CheckedFuture;
16 import com.google.common.util.concurrent.ListenableFuture;
17 import java.util.ArrayList;
18 import java.util.Iterator;
19 import java.util.List;
20 import java.util.concurrent.ExecutionException;
21 import javax.ws.rs.core.Response.Status;
22 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
23 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
24 import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
25 import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
26 import org.opendaylight.controller.md.sal.dom.api.DOMDataChangeListener;
27 import org.opendaylight.controller.md.sal.dom.api.DOMDataReadTransaction;
28 import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
29 import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
30 import org.opendaylight.controller.md.sal.dom.api.DOMMountPoint;
31 import org.opendaylight.controller.md.sal.dom.api.DOMRpcException;
32 import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult;
33 import org.opendaylight.controller.md.sal.dom.api.DOMRpcService;
34 import org.opendaylight.controller.sal.core.api.Broker.ConsumerSession;
35 import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorTag;
36 import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorType;
37 import org.opendaylight.controller.sal.streams.listeners.ListenerAdapter;
38 import org.opendaylight.yangtools.concepts.ListenerRegistration;
39 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
40 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
41 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
42 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
43 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
44 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
45 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
46 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
47 import org.slf4j.Logger;
48 import org.slf4j.LoggerFactory;
49
50 public class BrokerFacade {
51     private final static Logger LOG = LoggerFactory.getLogger(BrokerFacade.class);
52
53     private final static BrokerFacade INSTANCE = new BrokerFacade();
54     private volatile DOMRpcService rpcService;
55     private volatile ConsumerSession context;
56     private DOMDataBroker domDataBroker;
57
58     private BrokerFacade() {
59     }
60
61     public void setRpcService(final DOMRpcService router) {
62         rpcService = router;
63     }
64
65     public void setContext(final ConsumerSession context) {
66         this.context = context;
67     }
68
69     public static BrokerFacade getInstance() {
70         return BrokerFacade.INSTANCE;
71     }
72
73     private void checkPreconditions() {
74         if (context == null || domDataBroker == null) {
75             throw new RestconfDocumentedException(Status.SERVICE_UNAVAILABLE);
76         }
77     }
78
79     // READ configuration
80     public NormalizedNode<?, ?> readConfigurationData(final YangInstanceIdentifier path) {
81         checkPreconditions();
82         return readDataViaTransaction(domDataBroker.newReadOnlyTransaction(), CONFIGURATION, path);
83     }
84
85     public NormalizedNode<?, ?> readConfigurationData(final DOMMountPoint mountPoint, final YangInstanceIdentifier path) {
86         final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
87         if (domDataBrokerService.isPresent()) {
88             return readDataViaTransaction(domDataBrokerService.get().newReadOnlyTransaction(), CONFIGURATION, path);
89         }
90         throw new RestconfDocumentedException("DOM data broker service isn't available for mount point.");
91     }
92
93     // READ operational
94     public NormalizedNode<?, ?> readOperationalData(final YangInstanceIdentifier path) {
95         checkPreconditions();
96         return readDataViaTransaction(domDataBroker.newReadOnlyTransaction(), OPERATIONAL, path);
97     }
98
99     public NormalizedNode<?, ?> readOperationalData(final DOMMountPoint mountPoint, final YangInstanceIdentifier path) {
100         final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
101         if (domDataBrokerService.isPresent()) {
102             return readDataViaTransaction(domDataBrokerService.get().newReadOnlyTransaction(), OPERATIONAL, path);
103         }
104         throw new RestconfDocumentedException("DOM data broker service isn't available for mount point.");
105     }
106
107     // PUT configuration
108     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataPut(
109             final SchemaContext globalSchema, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload) {
110         checkPreconditions();
111         return putDataViaTransaction(domDataBroker.newReadWriteTransaction(), CONFIGURATION, path, payload, globalSchema);
112     }
113
114     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataPut(
115             final DOMMountPoint mountPoint, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload) {
116         final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
117         if (domDataBrokerService.isPresent()) {
118             return putDataViaTransaction(domDataBrokerService.get().newReadWriteTransaction(), CONFIGURATION, path,
119                     payload, mountPoint.getSchemaContext());
120         }
121         throw new RestconfDocumentedException("DOM data broker service isn't available for mount point.");
122     }
123
124     // POST configuration
125     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataPost(
126             final SchemaContext globalSchema, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload) {
127         checkPreconditions();
128         return postDataViaTransaction(domDataBroker.newReadWriteTransaction(), CONFIGURATION, path, payload, globalSchema);
129     }
130
131     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataPost(
132             final DOMMountPoint mountPoint, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload) {
133         final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
134         if (domDataBrokerService.isPresent()) {
135             return postDataViaTransaction(domDataBrokerService.get().newReadWriteTransaction(), CONFIGURATION, path,
136                     payload, mountPoint.getSchemaContext());
137         }
138         throw new RestconfDocumentedException("DOM data broker service isn't available for mount point.");
139     }
140
141     // DELETE configuration
142     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataDelete(
143             final YangInstanceIdentifier path) {
144         checkPreconditions();
145         return deleteDataViaTransaction(domDataBroker.newWriteOnlyTransaction(), CONFIGURATION, path);
146     }
147
148     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataDelete(
149             final DOMMountPoint mountPoint, final YangInstanceIdentifier path) {
150         final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
151         if (domDataBrokerService.isPresent()) {
152             return deleteDataViaTransaction(domDataBrokerService.get().newWriteOnlyTransaction(), CONFIGURATION, path);
153         }
154         throw new RestconfDocumentedException("DOM data broker service isn't available for mount point.");
155     }
156
157     // RPC
158     public CheckedFuture<DOMRpcResult, DOMRpcException> invokeRpc(final SchemaPath type, final NormalizedNode<?, ?> input) {
159         checkPreconditions();
160         if (rpcService == null) {
161             throw new RestconfDocumentedException(Status.SERVICE_UNAVAILABLE);
162         }
163         return rpcService.invokeRpc(type, input);
164     }
165
166     public void registerToListenDataChanges(final LogicalDatastoreType datastore, final DataChangeScope scope,
167             final ListenerAdapter listener) {
168         checkPreconditions();
169
170         if (listener.isListening()) {
171             return;
172         }
173
174         final YangInstanceIdentifier path = listener.getPath();
175         final ListenerRegistration<DOMDataChangeListener> registration = domDataBroker.registerDataChangeListener(
176                 datastore, path, listener, scope);
177
178         listener.setRegistration(registration);
179     }
180
181     private NormalizedNode<?, ?> readDataViaTransaction(final DOMDataReadTransaction transaction,
182             final LogicalDatastoreType datastore, final YangInstanceIdentifier path) {
183         LOG.trace("Read " + datastore.name() + " via Restconf: {}", path);
184         final ListenableFuture<Optional<NormalizedNode<?, ?>>> listenableFuture = transaction.read(datastore, path);
185         if (listenableFuture != null) {
186             Optional<NormalizedNode<?, ?>> optional;
187             try {
188                 LOG.debug("Reading result data from transaction.");
189                 optional = listenableFuture.get();
190             } catch (InterruptedException | ExecutionException e) {
191                 throw new RestconfDocumentedException("Problem to get data from transaction.", e.getCause());
192
193             }
194             if (optional != null) {
195                 if (optional.isPresent()) {
196                     return optional.get();
197                 }
198             }
199         }
200         return null;
201     }
202
203     private CheckedFuture<Void, TransactionCommitFailedException> postDataViaTransaction(
204             final DOMDataReadWriteTransaction rWTransaction, final LogicalDatastoreType datastore,
205             final YangInstanceIdentifier parentPath, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext) {
206         // FIXME: This is doing correct post for container and list children
207         //        not sure if this will work for choice case
208         if(payload instanceof MapNode) {
209             final YangInstanceIdentifier mapPath = parentPath.node(payload.getIdentifier());
210             final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, mapPath);
211             rWTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
212             ensureParentsByMerge(datastore, mapPath, rWTransaction, schemaContext);
213             for(final MapEntryNode child : ((MapNode) payload).getValue()) {
214                 final YangInstanceIdentifier childPath = mapPath.node(child.getIdentifier());
215                 checkItemDoesNotExists(rWTransaction, datastore, childPath);
216                 rWTransaction.put(datastore, childPath, child);
217             }
218         } else {
219             final YangInstanceIdentifier path;
220             if(payload instanceof MapEntryNode) {
221                 path = parentPath.node(payload.getNodeType()).node(payload.getIdentifier());
222             } else {
223                 path = parentPath.node(payload.getIdentifier());
224             }
225             checkItemDoesNotExists(rWTransaction,datastore, path);
226             ensureParentsByMerge(datastore, path, rWTransaction, schemaContext);
227             rWTransaction.put(datastore, path, payload);
228         }
229         return rWTransaction.submit();
230     }
231
232     private void checkItemDoesNotExists(final DOMDataReadWriteTransaction rWTransaction,final LogicalDatastoreType store, final YangInstanceIdentifier path) {
233         final ListenableFuture<Boolean> futureDatastoreData = rWTransaction.exists(store, path);
234         try {
235             if (futureDatastoreData.get()) {
236                 final String errMsg = "Post Configuration via Restconf was not executed because data already exists";
237                 LOG.debug(errMsg + ":{}", path);
238                 rWTransaction.cancel();
239                 throw new RestconfDocumentedException("Data already exists for path: " + path, ErrorType.PROTOCOL,
240                         ErrorTag.DATA_EXISTS);
241             }
242         } catch (InterruptedException | ExecutionException e) {
243             LOG.trace("It wasn't possible to get data loaded from datastore at path " + path);
244         }
245
246     }
247
248     private CheckedFuture<Void, TransactionCommitFailedException> putDataViaTransaction(
249             final DOMDataReadWriteTransaction writeTransaction, final LogicalDatastoreType datastore,
250             final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext) {
251         LOG.trace("Put " + datastore.name() + " via Restconf: {}", path);
252         ensureParentsByMerge(datastore, path, writeTransaction, schemaContext);
253         writeTransaction.put(datastore, path, payload);
254         return writeTransaction.submit();
255     }
256
257     private CheckedFuture<Void, TransactionCommitFailedException> deleteDataViaTransaction(
258             final DOMDataWriteTransaction writeTransaction, final LogicalDatastoreType datastore,
259             final YangInstanceIdentifier path) {
260         LOG.trace("Delete " + datastore.name() + " via Restconf: {}", path);
261         writeTransaction.delete(datastore, path);
262         return writeTransaction.submit();
263     }
264
265     public void setDomDataBroker(final DOMDataBroker domDataBroker) {
266         this.domDataBroker = domDataBroker;
267     }
268
269     private void ensureParentsByMerge(final LogicalDatastoreType store,
270                                       final YangInstanceIdentifier normalizedPath, final DOMDataReadWriteTransaction rwTx, final SchemaContext schemaContext) {
271         final List<PathArgument> normalizedPathWithoutChildArgs = new ArrayList<>();
272         YangInstanceIdentifier rootNormalizedPath = null;
273
274         final Iterator<PathArgument> it = normalizedPath.getPathArguments().iterator();
275
276         while(it.hasNext()) {
277             final PathArgument pathArgument = it.next();
278             if(rootNormalizedPath == null) {
279                 rootNormalizedPath = YangInstanceIdentifier.create(pathArgument);
280             }
281
282             // Skip last element, its not a parent
283             if(it.hasNext()) {
284                 normalizedPathWithoutChildArgs.add(pathArgument);
285             }
286         }
287
288         // No parent structure involved, no need to ensure parents
289         if(normalizedPathWithoutChildArgs.isEmpty()) {
290             return;
291         }
292
293         Preconditions.checkArgument(rootNormalizedPath != null, "Empty path received");
294
295         final NormalizedNode<?, ?> parentStructure =
296                 ImmutableNodes.fromInstanceId(schemaContext, YangInstanceIdentifier.create(normalizedPathWithoutChildArgs));
297         rwTx.merge(store, rootNormalizedPath, parentStructure);
298     }
299 }