2 * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.netconf.sal.restconf.impl;
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;
13 import com.google.common.base.Optional;
14 import com.google.common.base.Preconditions;
15 import com.google.common.collect.ImmutableList;
16 import com.google.common.collect.Lists;
17 import com.google.common.util.concurrent.CheckedFuture;
18 import com.google.common.util.concurrent.FutureCallback;
19 import com.google.common.util.concurrent.Futures;
20 import com.google.common.util.concurrent.ListenableFuture;
21 import java.util.ArrayList;
22 import java.util.List;
23 import java.util.concurrent.CountDownLatch;
24 import java.util.concurrent.ExecutionException;
25 import javax.annotation.Nullable;
26 import javax.ws.rs.core.Response.Status;
27 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
28 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
29 import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
30 import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
31 import org.opendaylight.controller.md.sal.dom.api.DOMDataChangeListener;
32 import org.opendaylight.controller.md.sal.dom.api.DOMDataReadTransaction;
33 import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
34 import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
35 import org.opendaylight.controller.md.sal.dom.api.DOMMountPoint;
36 import org.opendaylight.controller.md.sal.dom.api.DOMNotificationListener;
37 import org.opendaylight.controller.md.sal.dom.api.DOMNotificationService;
38 import org.opendaylight.controller.md.sal.dom.api.DOMRpcException;
39 import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult;
40 import org.opendaylight.controller.md.sal.dom.api.DOMRpcService;
41 import org.opendaylight.controller.sal.core.api.Broker.ConsumerSession;
42 import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorTag;
43 import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorType;
44 import org.opendaylight.netconf.sal.streams.listeners.ListenerAdapter;
45 import org.opendaylight.netconf.sal.streams.listeners.NotificationListenerAdapter;
46 import org.opendaylight.restconf.restful.utils.TransactionUtil;
47 import org.opendaylight.yangtools.concepts.ListenerRegistration;
48 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
49 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
50 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
51 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
52 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
53 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
54 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
55 import org.slf4j.Logger;
56 import org.slf4j.LoggerFactory;
58 public class BrokerFacade {
59 private final static Logger LOG = LoggerFactory.getLogger(BrokerFacade.class);
61 private final static BrokerFacade INSTANCE = new BrokerFacade();
62 private volatile DOMRpcService rpcService;
63 private volatile ConsumerSession context;
64 private DOMDataBroker domDataBroker;
65 private DOMNotificationService domNotification;
67 private BrokerFacade() {}
69 public void setRpcService(final DOMRpcService router) {
70 this.rpcService = router;
73 public void setDomNotificationService(final DOMNotificationService domNotification) {
74 this.domNotification = domNotification;
77 public void setContext(final ConsumerSession context) {
78 this.context = context;
81 public static BrokerFacade getInstance() {
82 return BrokerFacade.INSTANCE;
85 private void checkPreconditions() {
86 if ((this.context == null) || (this.domDataBroker == null)) {
87 throw new RestconfDocumentedException(Status.SERVICE_UNAVAILABLE);
92 public NormalizedNode<?, ?> readConfigurationData(final YangInstanceIdentifier path) {
94 return readDataViaTransaction(this.domDataBroker.newReadOnlyTransaction(), CONFIGURATION, path);
97 public NormalizedNode<?, ?> readConfigurationData(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(), CONFIGURATION, path);
102 final String errMsg = "DOM data broker service isn't available for mount point " + path;
104 throw new RestconfDocumentedException(errMsg);
108 public NormalizedNode<?, ?> readOperationalData(final YangInstanceIdentifier path) {
109 checkPreconditions();
110 return readDataViaTransaction(this.domDataBroker.newReadOnlyTransaction(), OPERATIONAL, path);
113 public NormalizedNode<?, ?> readOperationalData(final DOMMountPoint mountPoint, final YangInstanceIdentifier path) {
114 final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
115 if (domDataBrokerService.isPresent()) {
116 return readDataViaTransaction(domDataBrokerService.get().newReadOnlyTransaction(), OPERATIONAL, path);
118 final String errMsg = "DOM data broker service isn't available for mount point " + path;
120 throw new RestconfDocumentedException(errMsg);
124 * <b>PUT configuration data</b>
126 * Prepare result(status) for PUT operation and PUT data via transaction.
127 * Return wrapped status and future from PUT.
129 * @param globalSchema
130 * - used by merge parents (if contains list)
135 * @return wrapper of status and future of PUT
137 public PutResult commitConfigurationDataPut(
138 final SchemaContext globalSchema, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload) {
139 Preconditions.checkNotNull(globalSchema);
140 Preconditions.checkNotNull(path);
141 Preconditions.checkNotNull(payload);
143 checkPreconditions();
145 final DOMDataReadWriteTransaction newReadWriteTransaction = this.domDataBroker.newReadWriteTransaction();
146 final Status status = readDataViaTransaction(newReadWriteTransaction, CONFIGURATION, path) != null ? Status.OK
148 final CheckedFuture<Void, TransactionCommitFailedException> future = putDataViaTransaction(
149 newReadWriteTransaction, CONFIGURATION, path, payload, globalSchema);
150 return new PutResult(status, future);
154 * <b>PUT configuration data (Mount point)</b>
156 * Prepare result(status) for PUT operation and PUT data via transaction.
157 * Return wrapped status and future from PUT.
160 * - mount point for getting transaction for operation and schema
161 * context for merging parents(if contains list)
166 * @return wrapper of status and future of PUT
168 public PutResult commitMountPointDataPut(
169 final DOMMountPoint mountPoint, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload) {
170 Preconditions.checkNotNull(mountPoint);
171 Preconditions.checkNotNull(path);
172 Preconditions.checkNotNull(payload);
174 final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
175 if (domDataBrokerService.isPresent()) {
176 final DOMDataReadWriteTransaction newReadWriteTransaction = domDataBrokerService.get().newReadWriteTransaction();
177 final Status status = readDataViaTransaction(newReadWriteTransaction, CONFIGURATION, path) != null
178 ? Status.OK : Status.CREATED;
179 final CheckedFuture<Void, TransactionCommitFailedException> future = putDataViaTransaction(
180 newReadWriteTransaction, CONFIGURATION, path,
181 payload, mountPoint.getSchemaContext());
182 return new PutResult(status, future);
184 final String errMsg = "DOM data broker service isn't available for mount point " + path;
186 throw new RestconfDocumentedException(errMsg);
189 public PATCHStatusContext patchConfigurationDataWithinTransaction(final PATCHContext context,
190 final SchemaContext globalSchema)
191 throws InterruptedException {
192 final DOMDataReadWriteTransaction patchTransaction = this.domDataBroker.newReadWriteTransaction();
193 final List<PATCHStatusEntity> editCollection = new ArrayList<>();
195 List<RestconfError> editErrors;
196 int errorCounter = 0;
198 for (final PATCHEntity patchEntity : context.getData()) {
199 final PATCHEditOperation operation = PATCHEditOperation.valueOf(patchEntity.getOperation().toUpperCase());
203 if (errorCounter == 0) {
205 postDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity.getTargetNode(),
206 patchEntity.getNode(), globalSchema);
207 editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
208 } catch (final RestconfDocumentedException e) {
209 editErrors = new ArrayList<>();
210 editErrors.addAll(e.getErrors());
211 editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), false, editErrors));
217 if (errorCounter == 0) {
219 putDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity
220 .getTargetNode(), patchEntity.getNode(), globalSchema);
221 editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
222 } catch (final RestconfDocumentedException e) {
223 editErrors = new ArrayList<>();
224 editErrors.addAll(e.getErrors());
225 editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), false, editErrors));
231 if (errorCounter == 0) {
233 deleteDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity
235 editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
236 } catch (final RestconfDocumentedException e) {
237 editErrors = new ArrayList<>();
238 editErrors.addAll(e.getErrors());
239 editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), false, editErrors));
245 if (errorCounter == 0) {
247 deleteDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity
249 editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
250 } catch (final RestconfDocumentedException e) {
251 LOG.error("Error removing {} by {} operation", patchEntity.getTargetNode().toString(),
252 patchEntity.getEditId(), e);
257 if (errorCounter == 0) {
259 mergeDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity.getTargetNode(),
260 patchEntity.getNode(), globalSchema);
261 editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
262 } catch (final RestconfDocumentedException e) {
263 editErrors = new ArrayList<>();
264 editErrors.addAll(e.getErrors());
265 editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), false, editErrors));
273 // if errors then cancel transaction and return error status
274 if (errorCounter != 0) {
275 patchTransaction.cancel();
276 return new PATCHStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection), false, null);
279 // if no errors commit transaction
280 final CountDownLatch waiter = new CountDownLatch(1);
281 final CheckedFuture<Void, TransactionCommitFailedException> future = patchTransaction.submit();
282 final PATCHStatusContextHelper status = new PATCHStatusContextHelper();
284 Futures.addCallback(future, new FutureCallback<Void>() {
286 public void onSuccess(@Nullable final Void result) {
287 status.setStatus(new PATCHStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection),
293 public void onFailure(final Throwable t) {
294 // if commit failed it is global error
295 status.setStatus(new PATCHStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection),
296 false, Lists.newArrayList(
297 new RestconfError(ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED, t.getMessage()))));
303 return status.getStatus();
306 // POST configuration
307 public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataPost(
308 final SchemaContext globalSchema, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload) {
309 checkPreconditions();
310 return postDataViaTransaction(this.domDataBroker.newReadWriteTransaction(), CONFIGURATION, path, payload, globalSchema);
313 public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataPost(
314 final DOMMountPoint mountPoint, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload) {
315 final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
316 if (domDataBrokerService.isPresent()) {
317 return postDataViaTransaction(domDataBrokerService.get().newReadWriteTransaction(), CONFIGURATION, path,
318 payload, mountPoint.getSchemaContext());
320 final String errMsg = "DOM data broker service isn't available for mount point " + path;
322 throw new RestconfDocumentedException(errMsg);
325 // DELETE configuration
326 public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataDelete(
327 final YangInstanceIdentifier path) {
328 checkPreconditions();
329 return deleteDataViaTransaction(this.domDataBroker.newWriteOnlyTransaction(), CONFIGURATION, path);
332 public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataDelete(
333 final DOMMountPoint mountPoint, final YangInstanceIdentifier path) {
334 final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
335 if (domDataBrokerService.isPresent()) {
336 return deleteDataViaTransaction(domDataBrokerService.get().newWriteOnlyTransaction(), CONFIGURATION, path);
338 final String errMsg = "DOM data broker service isn't available for mount point " + path;
340 throw new RestconfDocumentedException(errMsg);
344 public CheckedFuture<DOMRpcResult, DOMRpcException> invokeRpc(final SchemaPath type, final NormalizedNode<?, ?> input) {
345 checkPreconditions();
346 if (this.rpcService == null) {
347 throw new RestconfDocumentedException(Status.SERVICE_UNAVAILABLE);
349 LOG.trace("Invoke RPC {} with input: {}", type, input);
350 return this.rpcService.invokeRpc(type, input);
353 public void registerToListenDataChanges(final LogicalDatastoreType datastore, final DataChangeScope scope,
354 final ListenerAdapter listener) {
355 checkPreconditions();
357 if (listener.isListening()) {
361 final YangInstanceIdentifier path = listener.getPath();
362 final ListenerRegistration<DOMDataChangeListener> registration = this.domDataBroker.registerDataChangeListener(
363 datastore, path, listener, scope);
365 listener.setRegistration(registration);
368 private NormalizedNode<?, ?> readDataViaTransaction(final DOMDataReadTransaction transaction,
369 final LogicalDatastoreType datastore, final YangInstanceIdentifier path) {
370 LOG.trace("Read {} via Restconf: {}", datastore.name(), path);
371 final ListenableFuture<Optional<NormalizedNode<?, ?>>> listenableFuture = transaction.read(datastore, path);
372 final ReadDataResult readData = new ReadDataResult();
373 final CountDownLatch responseWaiter = new CountDownLatch(1);
375 Futures.addCallback(listenableFuture, new FutureCallback<Optional<NormalizedNode<?, ?>>>() {
378 public void onSuccess(final Optional<NormalizedNode<?, ?>> result) {
379 handlingCallback(null, datastore, path, result, readData);
380 responseWaiter.countDown();
384 public void onFailure(final Throwable t) {
385 handlingCallback(t, datastore, path, null, null);
386 responseWaiter.countDown();
391 responseWaiter.await();
392 } catch (final InterruptedException e) {
393 final String msg = "Problem while waiting for response";
395 throw new RestconfDocumentedException(msg, e);
397 return readData.getResult();
400 protected static void handlingCallback(final Throwable t, final LogicalDatastoreType datastore,
401 final YangInstanceIdentifier path, final Optional<NormalizedNode<?, ?>> result,
402 final ReadDataResult readData) {
404 LOG.warn("Exception by reading {} via Restconf: {}", datastore.name(), path, t);
405 throw new RestconfDocumentedException("Problem to get data from transaction.", t);
407 LOG.debug("Reading result data from transaction.");
408 if (result != null) {
409 if (result.isPresent()) {
410 readData.setResult(result.get());
416 private CheckedFuture<Void, TransactionCommitFailedException> postDataViaTransaction(
417 final DOMDataReadWriteTransaction rWTransaction, final LogicalDatastoreType datastore,
418 final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext) {
419 // FIXME: This is doing correct post for container and list children
420 // not sure if this will work for choice case
421 if(payload instanceof MapNode) {
422 LOG.trace("POST {} via Restconf: {} with payload {}", datastore.name(), path, payload);
423 final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
424 rWTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
425 TransactionUtil.ensureParentsByMerge(path, schemaContext, rWTransaction);
426 for(final MapEntryNode child : ((MapNode) payload).getValue()) {
427 final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
428 checkItemDoesNotExists(rWTransaction, datastore, childPath);
429 rWTransaction.put(datastore, childPath, child);
432 checkItemDoesNotExists(rWTransaction,datastore, path);
433 TransactionUtil.ensureParentsByMerge(path, schemaContext, rWTransaction);
434 rWTransaction.put(datastore, path, payload);
436 return rWTransaction.submit();
439 private void postDataWithinTransaction(
440 final DOMDataReadWriteTransaction rWTransaction, final LogicalDatastoreType datastore,
441 final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext) {
442 // FIXME: This is doing correct post for container and list children
443 // not sure if this will work for choice case
444 if(payload instanceof MapNode) {
445 LOG.trace("POST {} within Restconf PATCH: {} with payload {}", datastore.name(), path, payload);
446 final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
447 rWTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
448 TransactionUtil.ensureParentsByMerge(path, schemaContext, rWTransaction);
449 for(final MapEntryNode child : ((MapNode) payload).getValue()) {
450 final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
451 checkItemDoesNotExists(rWTransaction, datastore, childPath);
452 rWTransaction.put(datastore, childPath, child);
455 checkItemDoesNotExists(rWTransaction,datastore, path);
456 TransactionUtil.ensureParentsByMerge(path, schemaContext, rWTransaction);
457 rWTransaction.put(datastore, path, payload);
461 private void checkItemDoesNotExists(final DOMDataReadWriteTransaction rWTransaction,final LogicalDatastoreType store, final YangInstanceIdentifier path) {
462 final ListenableFuture<Boolean> futureDatastoreData = rWTransaction.exists(store, path);
464 if (futureDatastoreData.get()) {
465 final String errMsg = "Post Configuration via Restconf was not executed because data already exists";
466 LOG.trace("{}:{}", errMsg, path);
467 rWTransaction.cancel();
468 throw new RestconfDocumentedException("Data already exists for path: " + path, ErrorType.PROTOCOL,
469 ErrorTag.DATA_EXISTS);
471 } catch (InterruptedException | ExecutionException e) {
472 LOG.warn("It wasn't possible to get data loaded from datastore at path {}", path, e);
477 private CheckedFuture<Void, TransactionCommitFailedException> putDataViaTransaction(
478 final DOMDataReadWriteTransaction writeTransaction, final LogicalDatastoreType datastore,
479 final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext) {
480 LOG.trace("Put {} via Restconf: {} with payload {}", datastore.name(), path, payload);
481 TransactionUtil.ensureParentsByMerge(path, schemaContext, writeTransaction);
482 writeTransaction.put(datastore, path, payload);
483 return writeTransaction.submit();
486 private void putDataWithinTransaction(
487 final DOMDataReadWriteTransaction writeTransaction, final LogicalDatastoreType datastore,
488 final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext) {
489 LOG.trace("Put {} within Restconf PATCH: {} with payload {}", datastore.name(), path, payload);
490 TransactionUtil.ensureParentsByMerge(path, schemaContext, writeTransaction);
491 writeTransaction.put(datastore, path, payload);
494 private CheckedFuture<Void, TransactionCommitFailedException> deleteDataViaTransaction(
495 final DOMDataWriteTransaction writeTransaction, final LogicalDatastoreType datastore,
496 final YangInstanceIdentifier path) {
497 LOG.trace("Delete {} via Restconf: {}", datastore.name(), path);
498 writeTransaction.delete(datastore, path);
499 return writeTransaction.submit();
502 private void deleteDataWithinTransaction(
503 final DOMDataWriteTransaction writeTransaction, final LogicalDatastoreType datastore,
504 final YangInstanceIdentifier path) {
505 LOG.trace("Delete {} within Restconf PATCH: {}", datastore.name(), path);
506 writeTransaction.delete(datastore, path);
509 private void mergeDataWithinTransaction(
510 final DOMDataReadWriteTransaction writeTransaction, final LogicalDatastoreType datastore,
511 final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext) {
512 LOG.trace("Merge {} within Restconf PATCH: {} with payload {}", datastore.name(), path, payload);
513 TransactionUtil.ensureParentsByMerge(path, schemaContext, writeTransaction);
515 // merging is necessary only for lists otherwise we can call put method
516 if (payload instanceof MapNode) {
517 writeTransaction.merge(datastore, path, payload);
519 writeTransaction.put(datastore, path, payload);
523 public void setDomDataBroker(final DOMDataBroker domDataBroker) {
524 this.domDataBroker = domDataBroker;
527 private class ReadDataResult {
528 NormalizedNode<?, ?> result = null;
530 NormalizedNode<?, ?> getResult() {
534 void setResult(final NormalizedNode<?, ?> result) {
535 this.result = result;
539 public void registerToListenNotification(final NotificationListenerAdapter listener) {
540 checkPreconditions();
542 if (listener.isListening()) {
546 final SchemaPath path = listener.getSchemaPath();
547 final ListenerRegistration<DOMNotificationListener> registration = this.domNotification
548 .registerNotificationListener(listener, path);
550 listener.setRegistration(registration);
553 private final class PATCHStatusContextHelper {
554 PATCHStatusContext status;
556 public PATCHStatusContext getStatus() {
560 public void setStatus(PATCHStatusContext status) {
561 this.status = status;