BUG 2412 - restconf @POST invokeRpc with nopayload method
[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 import com.google.common.base.Optional;
13 import com.google.common.util.concurrent.CheckedFuture;
14 import com.google.common.util.concurrent.ListenableFuture;
15 import java.util.ArrayList;
16 import java.util.Iterator;
17 import java.util.List;
18 import java.util.concurrent.ExecutionException;
19 import javax.ws.rs.core.Response.Status;
20 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
21 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
22 import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
23 import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
24 import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizationException;
25 import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizationOperation;
26 import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizer;
27 import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
28 import org.opendaylight.controller.md.sal.dom.api.DOMDataChangeListener;
29 import org.opendaylight.controller.md.sal.dom.api.DOMDataReadTransaction;
30 import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
31 import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
32 import org.opendaylight.controller.md.sal.dom.api.DOMMountPoint;
33 import org.opendaylight.controller.md.sal.dom.api.DOMRpcException;
34 import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult;
35 import org.opendaylight.controller.md.sal.dom.api.DOMRpcService;
36 import org.opendaylight.controller.sal.core.api.Broker.ConsumerSession;
37 import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorTag;
38 import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorType;
39 import org.opendaylight.controller.sal.streams.listeners.ListenerAdapter;
40 import org.opendaylight.yangtools.concepts.ListenerRegistration;
41 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
42 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
43 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
44 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
47
48 public class BrokerFacade {
49     private final static Logger LOG = LoggerFactory.getLogger(BrokerFacade.class);
50
51     private final static BrokerFacade INSTANCE = new BrokerFacade();
52     private volatile DOMRpcService rpcService;
53     private volatile ConsumerSession context;
54     private DOMDataBroker domDataBroker;
55
56     private BrokerFacade() {
57     }
58
59     public void setRpcService(final DOMRpcService router) {
60         rpcService = router;
61     }
62
63     public void setContext(final ConsumerSession context) {
64         this.context = context;
65     }
66
67     public static BrokerFacade getInstance() {
68         return BrokerFacade.INSTANCE;
69     }
70
71     private void checkPreconditions() {
72         if (context == null || domDataBroker == null) {
73             throw new RestconfDocumentedException(Status.SERVICE_UNAVAILABLE);
74         }
75     }
76
77     // READ configuration
78     public NormalizedNode<?, ?> readConfigurationData(final YangInstanceIdentifier path) {
79         checkPreconditions();
80         return readDataViaTransaction(domDataBroker.newReadOnlyTransaction(), CONFIGURATION, path);
81     }
82
83     public NormalizedNode<?, ?> readConfigurationData(final DOMMountPoint mountPoint, final YangInstanceIdentifier path) {
84         final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
85         if (domDataBrokerService.isPresent()) {
86             return readDataViaTransaction(domDataBrokerService.get().newReadOnlyTransaction(), CONFIGURATION, path);
87         }
88         throw new RestconfDocumentedException("DOM data broker service isn't available for mount point.");
89     }
90
91     // READ operational
92     public NormalizedNode<?, ?> readOperationalData(final YangInstanceIdentifier path) {
93         checkPreconditions();
94         return readDataViaTransaction(domDataBroker.newReadOnlyTransaction(), OPERATIONAL, path);
95     }
96
97     public NormalizedNode<?, ?> readOperationalData(final DOMMountPoint mountPoint, final YangInstanceIdentifier path) {
98         final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
99         if (domDataBrokerService.isPresent()) {
100             return readDataViaTransaction(domDataBrokerService.get().newReadOnlyTransaction(), OPERATIONAL, path);
101         }
102         throw new RestconfDocumentedException("DOM data broker service isn't available for mount point.");
103     }
104
105     // PUT configuration
106     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataPut(
107             final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload) {
108         checkPreconditions();
109         final DataNormalizationOperation<?> rootOp = ControllerContext.getInstance().getRootOperation();
110         return putDataViaTransaction(domDataBroker.newReadWriteTransaction(), CONFIGURATION, path, payload, rootOp);
111     }
112
113     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataPut(
114             final DOMMountPoint mountPoint, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload) {
115         final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
116         if (domDataBrokerService.isPresent()) {
117             final DataNormalizationOperation<?> rootOp = new DataNormalizer(mountPoint.getSchemaContext()).getRootOperation();
118             return putDataViaTransaction(domDataBrokerService.get().newReadWriteTransaction(), CONFIGURATION, path,
119                     payload, rootOp);
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 YangInstanceIdentifier path, final NormalizedNode<?, ?> payload) {
127         checkPreconditions();
128         final DataNormalizationOperation<?> rootOp = ControllerContext.getInstance().getRootOperation();
129         return postDataViaTransaction(domDataBroker.newReadWriteTransaction(), CONFIGURATION, path, payload, rootOp);
130     }
131
132     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataPost(
133             final DOMMountPoint mountPoint, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload) {
134         final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
135         if (domDataBrokerService.isPresent()) {
136             final DataNormalizationOperation<?> rootOp = new DataNormalizer(mountPoint.getSchemaContext()).getRootOperation();
137             return postDataViaTransaction(domDataBrokerService.get().newReadWriteTransaction(), CONFIGURATION, path,
138                     payload, rootOp);
139         }
140         throw new RestconfDocumentedException("DOM data broker service isn't available for mount point.");
141     }
142
143     // DELETE configuration
144     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataDelete(
145             final YangInstanceIdentifier path) {
146         checkPreconditions();
147         return deleteDataViaTransaction(domDataBroker.newWriteOnlyTransaction(), CONFIGURATION, path);
148     }
149
150     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataDelete(
151             final DOMMountPoint mountPoint, final YangInstanceIdentifier path) {
152         final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
153         if (domDataBrokerService.isPresent()) {
154             return deleteDataViaTransaction(domDataBrokerService.get().newWriteOnlyTransaction(), CONFIGURATION, path);
155         }
156         throw new RestconfDocumentedException("DOM data broker service isn't available for mount point.");
157     }
158
159     // RPC
160     public CheckedFuture<DOMRpcResult, DOMRpcException> invokeRpc(final SchemaPath type, final NormalizedNode<?, ?> input) {
161         checkPreconditions();
162         if (rpcService == null) {
163             throw new RestconfDocumentedException(Status.SERVICE_UNAVAILABLE);
164         }
165         return rpcService.invokeRpc(type, input);
166     }
167
168     public void registerToListenDataChanges(final LogicalDatastoreType datastore, final DataChangeScope scope,
169             final ListenerAdapter listener) {
170         checkPreconditions();
171
172         if (listener.isListening()) {
173             return;
174         }
175
176         final YangInstanceIdentifier path = listener.getPath();
177         final ListenerRegistration<DOMDataChangeListener> registration = domDataBroker.registerDataChangeListener(
178                 datastore, path, listener, scope);
179
180         listener.setRegistration(registration);
181     }
182
183     private NormalizedNode<?, ?> readDataViaTransaction(final DOMDataReadTransaction transaction,
184             final LogicalDatastoreType datastore, final YangInstanceIdentifier path) {
185         LOG.trace("Read " + datastore.name() + " via Restconf: {}", path);
186         final ListenableFuture<Optional<NormalizedNode<?, ?>>> listenableFuture = transaction.read(datastore, path);
187         if (listenableFuture != null) {
188             Optional<NormalizedNode<?, ?>> optional;
189             try {
190                 LOG.debug("Reading result data from transaction.");
191                 optional = listenableFuture.get();
192             } catch (InterruptedException | ExecutionException e) {
193                 throw new RestconfDocumentedException("Problem to get data from transaction.", e.getCause());
194
195             }
196             if (optional != null) {
197                 if (optional.isPresent()) {
198                     return optional.get();
199                 }
200             }
201         }
202         return null;
203     }
204
205     private CheckedFuture<Void, TransactionCommitFailedException> postDataViaTransaction(
206             final DOMDataReadWriteTransaction rWTransaction, final LogicalDatastoreType datastore,
207             final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final DataNormalizationOperation<?> root) {
208         final ListenableFuture<Optional<NormalizedNode<?, ?>>> futureDatastoreData = rWTransaction.read(datastore, path);
209         try {
210             final Optional<NormalizedNode<?, ?>> optionalDatastoreData = futureDatastoreData.get();
211             if (optionalDatastoreData.isPresent() && payload.equals(optionalDatastoreData.get())) {
212                 final String errMsg = "Post Configuration via Restconf was not executed because data already exists";
213                 LOG.trace(errMsg + ":{}", path);
214                 rWTransaction.cancel();
215                 throw new RestconfDocumentedException("Data already exists for path: " + path, ErrorType.PROTOCOL,
216                         ErrorTag.DATA_EXISTS);
217             }
218         } catch (InterruptedException | ExecutionException e) {
219             LOG.trace("It wasn't possible to get data loaded from datastore at path " + path);
220         }
221
222         ensureParentsByMerge(datastore, path, rWTransaction, root);
223         rWTransaction.merge(datastore, path, payload);
224         LOG.trace("Post " + datastore.name() + " via Restconf: {}", path);
225         return rWTransaction.submit();
226     }
227
228     private CheckedFuture<Void, TransactionCommitFailedException> putDataViaTransaction(
229             final DOMDataReadWriteTransaction writeTransaction, final LogicalDatastoreType datastore,
230             final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final DataNormalizationOperation<?> root) {
231         LOG.trace("Put " + datastore.name() + " via Restconf: {}", path);
232         ensureParentsByMerge(datastore, path, writeTransaction, root);
233         writeTransaction.put(datastore, path, payload);
234         return writeTransaction.submit();
235     }
236
237     private CheckedFuture<Void, TransactionCommitFailedException> deleteDataViaTransaction(
238             final DOMDataWriteTransaction writeTransaction, final LogicalDatastoreType datastore,
239             final YangInstanceIdentifier path) {
240         LOG.trace("Delete " + datastore.name() + " via Restconf: {}", path);
241         writeTransaction.delete(datastore, path);
242         return writeTransaction.submit();
243     }
244
245     public void setDomDataBroker(final DOMDataBroker domDataBroker) {
246         this.domDataBroker = domDataBroker;
247     }
248
249     private final void ensureParentsByMerge(final LogicalDatastoreType store,
250             final YangInstanceIdentifier normalizedPath, final DOMDataReadWriteTransaction rwTx,
251             final DataNormalizationOperation<?> root) {
252         final List<PathArgument> currentArguments = new ArrayList<>();
253         final Iterator<PathArgument> iterator = normalizedPath.getPathArguments().iterator();
254         DataNormalizationOperation<?> currentOp = root;
255         while (iterator.hasNext()) {
256             final PathArgument currentArg = iterator.next();
257             try {
258                 currentOp = currentOp.getChild(currentArg);
259             } catch (final DataNormalizationException e) {
260                 rwTx.cancel();
261                 throw new IllegalArgumentException(
262                         String.format("Invalid child encountered in path %s", normalizedPath), e);
263             }
264             currentArguments.add(currentArg);
265             final YangInstanceIdentifier currentPath = YangInstanceIdentifier.create(currentArguments);
266
267             final Boolean exists;
268
269             try {
270
271                 final CheckedFuture<Boolean, ReadFailedException> future = rwTx.exists(store, currentPath);
272                 exists = future.checkedGet();
273             } catch (final ReadFailedException e) {
274                 LOG.error("Failed to read pre-existing data from store {} path {}", store, currentPath, e);
275                 rwTx.cancel();
276                 throw new IllegalStateException("Failed to read pre-existing data", e);
277             }
278
279             if (!exists && iterator.hasNext()) {
280                 rwTx.merge(store, currentPath, currentOp.createDefault(currentArg));
281             }
282         }
283     }
284 }