Bug 6037 - Check if delete request was successful
[netconf.git] / restconf / sal-rest-connector / src / main / java / org / opendaylight / netconf / 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.netconf.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.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 javax.annotation.Nullable;
25 import javax.ws.rs.core.Response.Status;
26 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
27 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
28 import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
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;
57
58 public class BrokerFacade {
59     private final static Logger LOG = LoggerFactory.getLogger(BrokerFacade.class);
60
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;
66
67     private BrokerFacade() {}
68
69     public void setRpcService(final DOMRpcService router) {
70         this.rpcService = router;
71     }
72
73     public void setDomNotificationService(final DOMNotificationService domNotification) {
74         this.domNotification = domNotification;
75     }
76
77     public void setContext(final ConsumerSession context) {
78         this.context = context;
79     }
80
81     public static BrokerFacade getInstance() {
82         return BrokerFacade.INSTANCE;
83     }
84
85     private void checkPreconditions() {
86         if ((this.context == null) || (this.domDataBroker == null)) {
87             throw new RestconfDocumentedException(Status.SERVICE_UNAVAILABLE);
88         }
89     }
90
91     // READ configuration
92     public NormalizedNode<?, ?> readConfigurationData(final YangInstanceIdentifier path) {
93         checkPreconditions();
94         return readDataViaTransaction(this.domDataBroker.newReadOnlyTransaction(), CONFIGURATION, path);
95     }
96
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);
101         }
102         final String errMsg = "DOM data broker service isn't available for mount point " + path;
103         LOG.warn(errMsg);
104         throw new RestconfDocumentedException(errMsg);
105     }
106
107     // READ operational
108     public NormalizedNode<?, ?> readOperationalData(final YangInstanceIdentifier path) {
109         checkPreconditions();
110         return readDataViaTransaction(this.domDataBroker.newReadOnlyTransaction(), OPERATIONAL, path);
111     }
112
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);
117         }
118         final String errMsg = "DOM data broker service isn't available for mount point " + path;
119         LOG.warn(errMsg);
120         throw new RestconfDocumentedException(errMsg);
121     }
122
123     /**
124      * <b>PUT configuration data</b>
125      *
126      * Prepare result(status) for PUT operation and PUT data via transaction.
127      * Return wrapped status and future from PUT.
128      *
129      * @param globalSchema
130      *            - used by merge parents (if contains list)
131      * @param path
132      *            - path of node
133      * @param payload
134      *            - input data
135      * @return wrapper of status and future of PUT
136      */
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);
142
143         checkPreconditions();
144
145         final DOMDataReadWriteTransaction newReadWriteTransaction = this.domDataBroker.newReadWriteTransaction();
146         final Status status = readDataViaTransaction(newReadWriteTransaction, CONFIGURATION, path) != null ? Status.OK
147                 : Status.CREATED;
148         final CheckedFuture<Void, TransactionCommitFailedException> future = putDataViaTransaction(
149                 newReadWriteTransaction, CONFIGURATION, path, payload, globalSchema);
150         return new PutResult(status, future);
151     }
152
153     /**
154      * <b>PUT configuration data (Mount point)</b>
155      *
156      * Prepare result(status) for PUT operation and PUT data via transaction.
157      * Return wrapped status and future from PUT.
158      *
159      * @param mountPoint
160      *            - mount point for getting transaction for operation and schema
161      *            context for merging parents(if contains list)
162      * @param path
163      *            - path of node
164      * @param payload
165      *            - input data
166      * @return wrapper of status and future of PUT
167      */
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);
173
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);
183         }
184         final String errMsg = "DOM data broker service isn't available for mount point " + path;
185         LOG.warn(errMsg);
186         throw new RestconfDocumentedException(errMsg);
187     }
188
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<>();
194
195         List<RestconfError> editErrors;
196         int errorCounter = 0;
197
198         for (final PATCHEntity patchEntity : context.getData()) {
199             final PATCHEditOperation operation = PATCHEditOperation.valueOf(patchEntity.getOperation().toUpperCase());
200
201             switch (operation) {
202                 case CREATE:
203                     if (errorCounter == 0) {
204                         try {
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));
212                             errorCounter++;
213                         }
214                     }
215                     break;
216                 case REPLACE:
217                     if (errorCounter == 0) {
218                         try {
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));
226                             errorCounter++;
227                         }
228                     }
229                     break;
230                 case DELETE:
231                     if (errorCounter == 0) {
232                         try {
233                             deleteDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity
234                                     .getTargetNode());
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));
240                             errorCounter++;
241                         }
242                     }
243                     break;
244                 case REMOVE:
245                     if (errorCounter == 0) {
246                         try {
247                             deleteDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity
248                                     .getTargetNode());
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);
253                         }
254                     }
255                     break;
256                 case MERGE:
257                     if (errorCounter == 0) {
258                         try {
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));
266                             errorCounter++;
267                         }
268                     }
269                     break;
270             }
271         }
272
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);
277         }
278
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();
283
284         Futures.addCallback(future, new FutureCallback<Void>() {
285             @Override
286             public void onSuccess(@Nullable final Void result) {
287                 status.setStatus(new PATCHStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection),
288                         true, null));
289                 waiter.countDown();
290             }
291
292             @Override
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()))));
298                 waiter.countDown();
299             }
300         });
301
302         waiter.await();
303         return status.getStatus();
304     }
305
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);
311     }
312
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());
319         }
320         final String errMsg = "DOM data broker service isn't available for mount point " + path;
321         LOG.warn(errMsg);
322         throw new RestconfDocumentedException(errMsg);
323     }
324
325     // DELETE configuration
326     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataDelete(
327             final YangInstanceIdentifier path) {
328         checkPreconditions();
329         return deleteDataViaTransaction(this.domDataBroker.newReadWriteTransaction(), CONFIGURATION, path);
330     }
331
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().newReadWriteTransaction(), CONFIGURATION, path);
337         }
338         final String errMsg = "DOM data broker service isn't available for mount point " + path;
339         LOG.warn(errMsg);
340         throw new RestconfDocumentedException(errMsg);
341     }
342
343     // RPC
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);
348         }
349         LOG.trace("Invoke RPC {} with input: {}", type, input);
350         return this.rpcService.invokeRpc(type, input);
351     }
352
353     public void registerToListenDataChanges(final LogicalDatastoreType datastore, final DataChangeScope scope,
354             final ListenerAdapter listener) {
355         checkPreconditions();
356
357         if (listener.isListening()) {
358             return;
359         }
360
361         final YangInstanceIdentifier path = listener.getPath();
362         final ListenerRegistration<DOMDataChangeListener> registration = this.domDataBroker.registerDataChangeListener(
363                 datastore, path, listener, scope);
364
365         listener.setRegistration(registration);
366     }
367
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<NormalizedNode<?, ?>> readData = new ReadDataResult<>();
373         final CountDownLatch responseWaiter = new CountDownLatch(1);
374
375         Futures.addCallback(listenableFuture, new FutureCallback<Optional<NormalizedNode<?, ?>>>() {
376
377             @Override
378             public void onSuccess(final Optional<NormalizedNode<?, ?>> result) {
379                 responseWaiter.countDown();
380                 handlingCallback(null, datastore, path, result, readData);
381             }
382
383             @Override
384             public void onFailure(final Throwable t) {
385                 responseWaiter.countDown();
386                 handlingCallback(t, datastore, path, null, null);
387             }
388         });
389
390         try {
391             responseWaiter.await();
392         } catch (final InterruptedException e) {
393             final String msg = "Problem while waiting for response";
394             LOG.warn(msg);
395             throw new RestconfDocumentedException(msg, e);
396         }
397         return readData.getResult();
398     }
399
400     private CheckedFuture<Void, TransactionCommitFailedException> postDataViaTransaction(
401             final DOMDataReadWriteTransaction rWTransaction, final LogicalDatastoreType datastore,
402             final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext) {
403         // FIXME: This is doing correct post for container and list children
404         //        not sure if this will work for choice case
405         if(payload instanceof MapNode) {
406             LOG.trace("POST {} via Restconf: {} with payload {}", datastore.name(), path, payload);
407             final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
408             rWTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
409             TransactionUtil.ensureParentsByMerge(path, schemaContext, rWTransaction);
410             for(final MapEntryNode child : ((MapNode) payload).getValue()) {
411                 final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
412                 checkItemDoesNotExists(rWTransaction, datastore, childPath);
413                 rWTransaction.put(datastore, childPath, child);
414             }
415         } else {
416             checkItemDoesNotExists(rWTransaction,datastore, path);
417             TransactionUtil.ensureParentsByMerge(path, schemaContext, rWTransaction);
418             rWTransaction.put(datastore, path, payload);
419         }
420         return rWTransaction.submit();
421     }
422
423     private void postDataWithinTransaction(
424             final DOMDataReadWriteTransaction rWTransaction, final LogicalDatastoreType datastore,
425             final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext) {
426         // FIXME: This is doing correct post for container and list children
427         //        not sure if this will work for choice case
428         if(payload instanceof MapNode) {
429             LOG.trace("POST {} within Restconf PATCH: {} with payload {}", datastore.name(), path, payload);
430             final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
431             rWTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
432             TransactionUtil.ensureParentsByMerge(path, schemaContext, rWTransaction);
433             for(final MapEntryNode child : ((MapNode) payload).getValue()) {
434                 final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
435                 checkItemDoesNotExists(rWTransaction, datastore, childPath);
436                 rWTransaction.put(datastore, childPath, child);
437             }
438         } else {
439             checkItemDoesNotExists(rWTransaction,datastore, path);
440             TransactionUtil.ensureParentsByMerge(path, schemaContext, rWTransaction);
441             rWTransaction.put(datastore, path, payload);
442         }
443     }
444
445     /**
446      * Check if item already exists. Throws error if it does NOT already exist.
447      * @param rWTransaction Current transaction
448      * @param store Used datastore
449      * @param path Path to item to verify its existence
450      */
451     private void checkItemExists(final DOMDataReadWriteTransaction rWTransaction,
452                                  final LogicalDatastoreType store, final YangInstanceIdentifier path) {
453         final CountDownLatch responseWaiter = new CountDownLatch(1);
454         final ReadDataResult<Boolean> readData = new ReadDataResult<>();
455         final CheckedFuture<Boolean, ReadFailedException> future = rWTransaction.exists(store, path);
456
457         Futures.addCallback(future, new FutureCallback<Boolean>() {
458             @Override
459             public void onSuccess(@Nullable final Boolean result) {
460                 responseWaiter.countDown();
461                 handlingCallback(null, store, path, Optional.of(result), readData);
462             }
463
464             @Override
465             public void onFailure(final Throwable t) {
466                 responseWaiter.countDown();
467                 handlingCallback(t, store, path, null, null);
468             }
469         });
470
471         try {
472             responseWaiter.await();
473         } catch (final InterruptedException e) {
474             final String msg = "Problem while waiting for response";
475             LOG.warn(msg);
476             throw new RestconfDocumentedException(msg, e);
477         }
478
479         if (!readData.getResult()) {
480             final String errMsg = "Operation via Restconf was not executed because data does not exist";
481             LOG.trace("{}:{}", errMsg, path);
482             rWTransaction.cancel();
483             throw new RestconfDocumentedException("Data does not exist for path: " + path, ErrorType.PROTOCOL,
484                     ErrorTag.DATA_MISSING);
485         }
486     }
487
488     /**
489      * Check if item does NOT already exist. Throws error if it already exists.
490      * @param rWTransaction Current transaction
491      * @param store Used datastore
492      * @param path Path to item to verify its existence
493      */
494     private void checkItemDoesNotExists(final DOMDataReadWriteTransaction rWTransaction,
495                                         final LogicalDatastoreType store, final YangInstanceIdentifier path) {
496         final CountDownLatch responseWaiter = new CountDownLatch(1);
497         final ReadDataResult<Boolean> readData = new ReadDataResult<>();
498         final CheckedFuture<Boolean, ReadFailedException> future = rWTransaction.exists(store, path);
499
500         Futures.addCallback(future, new FutureCallback<Boolean>() {
501             @Override
502             public void onSuccess(@Nullable final Boolean result) {
503                 responseWaiter.countDown();
504                 handlingCallback(null, store, path, Optional.of(result), readData);
505             }
506
507             @Override
508             public void onFailure(final Throwable t) {
509                 responseWaiter.countDown();
510                 handlingCallback(t, store, path, null, null);
511             }
512         });
513
514         try {
515             responseWaiter.await();
516         } catch (final InterruptedException e) {
517             final String msg = "Problem while waiting for response";
518             LOG.warn(msg);
519             throw new RestconfDocumentedException(msg, e);
520         }
521
522         if (readData.getResult()) {
523             final String errMsg = "Operation via Restconf was not executed because data already exists";
524             LOG.trace("{}:{}", errMsg, path);
525             rWTransaction.cancel();
526             throw new RestconfDocumentedException("Data already exists for path: " + path, ErrorType.PROTOCOL,
527                     ErrorTag.DATA_EXISTS);
528         }
529     }
530
531     private CheckedFuture<Void, TransactionCommitFailedException> putDataViaTransaction(
532             final DOMDataReadWriteTransaction writeTransaction, final LogicalDatastoreType datastore,
533             final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext) {
534         LOG.trace("Put {} via Restconf: {} with payload {}", datastore.name(), path, payload);
535         TransactionUtil.ensureParentsByMerge(path, schemaContext, writeTransaction);
536         writeTransaction.put(datastore, path, payload);
537         return writeTransaction.submit();
538     }
539
540     private void putDataWithinTransaction(
541             final DOMDataReadWriteTransaction writeTransaction, final LogicalDatastoreType datastore,
542             final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext) {
543         LOG.trace("Put {} within Restconf PATCH: {} with payload {}", datastore.name(), path, payload);
544         TransactionUtil.ensureParentsByMerge(path, schemaContext, writeTransaction);
545         writeTransaction.put(datastore, path, payload);
546     }
547
548     private CheckedFuture<Void, TransactionCommitFailedException> deleteDataViaTransaction(
549             final DOMDataReadWriteTransaction readWriteTransaction, final LogicalDatastoreType datastore,
550             final YangInstanceIdentifier path) {
551         LOG.trace("Delete {} via Restconf: {}", datastore.name(), path);
552         checkItemExists(readWriteTransaction, datastore, path);
553         readWriteTransaction.delete(datastore, path);
554         return readWriteTransaction.submit();
555     }
556
557     private void deleteDataWithinTransaction(
558             final DOMDataWriteTransaction writeTransaction, final LogicalDatastoreType datastore,
559             final YangInstanceIdentifier path) {
560         LOG.trace("Delete {} within Restconf PATCH: {}", datastore.name(), path);
561         writeTransaction.delete(datastore, path);
562     }
563
564     private void mergeDataWithinTransaction(
565             final DOMDataReadWriteTransaction writeTransaction, final LogicalDatastoreType datastore,
566             final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext) {
567         LOG.trace("Merge {} within Restconf PATCH: {} with payload {}", datastore.name(), path, payload);
568         TransactionUtil.ensureParentsByMerge(path, schemaContext, writeTransaction);
569
570         // merging is necessary only for lists otherwise we can call put method
571         if (payload instanceof MapNode) {
572             writeTransaction.merge(datastore, path, payload);
573         } else {
574             writeTransaction.put(datastore, path, payload);
575         }
576     }
577
578     public void setDomDataBroker(final DOMDataBroker domDataBroker) {
579         this.domDataBroker = domDataBroker;
580     }
581
582     /**
583      * Helper class for result of transaction commit callback.
584      * @param <T> Type of result
585      */
586     private final class ReadDataResult<T> {
587         T result = null;
588
589         T getResult() {
590             return this.result;
591         }
592
593         void setResult(final T result) {
594             this.result = result;
595         }
596     }
597
598     /**
599      * Set result from transaction commit callback.
600      * @param t Throwable if transaction commit failed
601      * @param datastore Datastore from which data are read
602      * @param path Path from which data are read
603      * @param result Result of read from {@code datastore}
604      * @param readData Result value which will be set
605      * @param <X> Result type
606      */
607     protected final static <X> void handlingCallback(final Throwable t, final LogicalDatastoreType datastore,
608                                                      final YangInstanceIdentifier path, final Optional<X> result,
609                                                      final ReadDataResult<X> readData) {
610         if (t != null) {
611             LOG.warn("Exception by reading {} via Restconf: {}", datastore.name(), path, t);
612             throw new RestconfDocumentedException("Problem to get data from transaction.", t);
613         } else {
614             LOG.debug("Reading result data from transaction.");
615             if (result != null) {
616                 if (result.isPresent()) {
617                     readData.setResult(result.get());
618                 }
619             }
620         }
621     }
622
623     public void registerToListenNotification(final NotificationListenerAdapter listener) {
624         checkPreconditions();
625
626         if (listener.isListening()) {
627             return;
628         }
629
630         final SchemaPath path = listener.getSchemaPath();
631         final ListenerRegistration<DOMNotificationListener> registration = this.domNotification
632                 .registerNotificationListener(listener, path);
633
634         listener.setRegistration(registration);
635     }
636
637     private final class PATCHStatusContextHelper {
638         PATCHStatusContext status;
639
640         public PATCHStatusContext getStatus() {
641             return this.status;
642         }
643
644         public void setStatus(PATCHStatusContext status) {
645             this.status = status;
646         }
647     }
648 }