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