60347f98f665f286032047bb181164df60755702
[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.util.concurrent.CheckedFuture;
17 import com.google.common.util.concurrent.FutureCallback;
18 import com.google.common.util.concurrent.Futures;
19 import com.google.common.util.concurrent.ListenableFuture;
20 import java.util.ArrayList;
21 import java.util.Iterator;
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.yangtools.concepts.ListenerRegistration;
47 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
48 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
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 patchContext) throws Exception {
190         final DOMMountPoint mountPoint = patchContext.getInstanceIdentifierContext().getMountPoint();
191
192         // get new transaction and schema context on server or on mounted device
193         final SchemaContext schemaContext;
194         final DOMDataReadWriteTransaction patchTransaction;
195         if (mountPoint == null) {
196             schemaContext = patchContext.getInstanceIdentifierContext().getSchemaContext();
197             patchTransaction = this.domDataBroker.newReadWriteTransaction();
198         } else {
199             schemaContext = mountPoint.getSchemaContext();
200
201             final Optional<DOMDataBroker> optional = mountPoint.getService(DOMDataBroker.class);
202
203             if (optional.isPresent()) {
204                 patchTransaction = optional.get().newReadWriteTransaction();
205             } else {
206                 // if mount point does not have broker it is not possible to continue and global error is reported
207                 LOG.error("Http PATCH {} has failed - device {} does not support broker service",
208                         patchContext.getPatchId(), mountPoint.getIdentifier());
209                 return new PATCHStatusContext(
210                         patchContext.getPatchId(),
211                         null,
212                         false,
213                         ImmutableList.of(new RestconfError(
214                                 ErrorType.APPLICATION,
215                                 ErrorTag.OPERATION_FAILED,
216                                 "DOM data broker service isn't available for mount point "
217                                         + mountPoint.getIdentifier()))
218                 );
219             }
220         }
221
222         final List<PATCHStatusEntity> editCollection = new ArrayList<>();
223         List<RestconfError> editErrors;
224         boolean withoutError = true;
225
226         for (final PATCHEntity patchEntity : patchContext.getData()) {
227             final PATCHEditOperation operation = PATCHEditOperation.valueOf(patchEntity.getOperation().toUpperCase());
228
229             switch (operation) {
230                 case CREATE:
231                     if (withoutError) {
232                         try {
233                             postDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity.getTargetNode(),
234                                     patchEntity.getNode(), schemaContext);
235                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
236                         } catch (final RestconfDocumentedException e) {
237                             LOG.error("Error call http PATCH operation {} on target {}",
238                                     operation,
239                                     patchEntity.getTargetNode().toString());
240
241                             editErrors = new ArrayList<>();
242                             editErrors.addAll(e.getErrors());
243                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), false, editErrors));
244                             withoutError = false;
245                         }
246                     }
247                     break;
248                 case REPLACE:
249                     if (withoutError) {
250                         try {
251                             putDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity
252                                     .getTargetNode(), patchEntity.getNode(), schemaContext);
253                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
254                         } catch (final RestconfDocumentedException e) {
255                             LOG.error("Error call http PATCH operation {} on target {}",
256                                     operation,
257                                     patchEntity.getTargetNode().toString());
258
259                             editErrors = new ArrayList<>();
260                             editErrors.addAll(e.getErrors());
261                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), false, editErrors));
262                             withoutError = false;
263                         }
264                     }
265                     break;
266                 case DELETE:
267                     if (withoutError) {
268                         try {
269                             deleteDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity
270                                     .getTargetNode());
271                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
272                         } catch (final RestconfDocumentedException e) {
273                             LOG.error("Error call http PATCH operation {} on target {}",
274                                     operation,
275                                     patchEntity.getTargetNode().toString());
276
277                             editErrors = new ArrayList<>();
278                             editErrors.addAll(e.getErrors());
279                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), false, editErrors));
280                             withoutError = false;
281                         }
282                     }
283                     break;
284                 case REMOVE:
285                     if (withoutError) {
286                         try {
287                             deleteDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity
288                                     .getTargetNode());
289                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
290                         } catch (final RestconfDocumentedException e) {
291                             LOG.error("Error call http PATCH operation {} on target {}",
292                                     operation,
293                                     patchEntity.getTargetNode().toString());
294
295                             editErrors = new ArrayList<>();
296                             editErrors.addAll(e.getErrors());
297                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), false, editErrors));
298                             withoutError = false;
299                         }
300                     }
301                     break;
302                 case MERGE:
303                     if (withoutError) {
304                         try {
305                             mergeDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity.getTargetNode(),
306                                     patchEntity.getNode(), schemaContext);
307                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
308                         } catch (final RestconfDocumentedException e) {
309                             LOG.error("Error call http PATCH operation {} on target {}",
310                                     operation,
311                                     patchEntity.getTargetNode().toString());
312
313                             editErrors = new ArrayList<>();
314                             editErrors.addAll(e.getErrors());
315                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), false, editErrors));
316                             withoutError = false;
317                         }
318                     }
319                     break;
320                 default:
321                     LOG.error("Unsupported http PATCH operation {} on target {}",
322                             operation,
323                             patchEntity.getTargetNode().toString());
324                     break;
325             }
326         }
327
328         // if errors then cancel transaction and return error status
329         if (!withoutError) {
330             patchTransaction.cancel();
331             return new PATCHStatusContext(patchContext.getPatchId(), ImmutableList.copyOf(editCollection), false, null);
332         }
333
334         // if no errors commit transaction
335         final CountDownLatch waiter = new CountDownLatch(1);
336         final CheckedFuture<Void, TransactionCommitFailedException> future = patchTransaction.submit();
337         final PATCHStatusContextHelper status = new PATCHStatusContextHelper();
338
339         Futures.addCallback(future, new FutureCallback<Void>() {
340             @Override
341             public void onSuccess(@Nullable final Void result) {
342                 status.setStatus(new PATCHStatusContext(patchContext.getPatchId(), ImmutableList.copyOf(editCollection),
343                         true, null));
344                 waiter.countDown();
345             }
346
347             @Override
348             public void onFailure(final Throwable t) {
349                 // if commit failed it is global error
350                 LOG.error("Http PATCH {} transaction commit has failed", patchContext.getPatchId());
351                 status.setStatus(new PATCHStatusContext(patchContext.getPatchId(), ImmutableList.copyOf(editCollection),
352                         false, ImmutableList.of(
353                         new RestconfError(ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED, t.getMessage()))));
354                 waiter.countDown();
355             }
356         });
357
358         waiter.await();
359         return status.getStatus();
360     }
361
362     // POST configuration
363     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataPost(
364             final SchemaContext globalSchema, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload) {
365         checkPreconditions();
366         return postDataViaTransaction(this.domDataBroker.newReadWriteTransaction(), CONFIGURATION, path, payload, globalSchema);
367     }
368
369     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataPost(
370             final DOMMountPoint mountPoint, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload) {
371         final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
372         if (domDataBrokerService.isPresent()) {
373             return postDataViaTransaction(domDataBrokerService.get().newReadWriteTransaction(), CONFIGURATION, path,
374                     payload, mountPoint.getSchemaContext());
375         }
376         final String errMsg = "DOM data broker service isn't available for mount point " + path;
377         LOG.warn(errMsg);
378         throw new RestconfDocumentedException(errMsg);
379     }
380
381     // DELETE configuration
382     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataDelete(
383             final YangInstanceIdentifier path) {
384         checkPreconditions();
385         return deleteDataViaTransaction(this.domDataBroker.newReadWriteTransaction(), CONFIGURATION, path);
386     }
387
388     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataDelete(
389             final DOMMountPoint mountPoint, final YangInstanceIdentifier path) {
390         final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
391         if (domDataBrokerService.isPresent()) {
392             return deleteDataViaTransaction(domDataBrokerService.get().newReadWriteTransaction(), CONFIGURATION, path);
393         }
394         final String errMsg = "DOM data broker service isn't available for mount point " + path;
395         LOG.warn(errMsg);
396         throw new RestconfDocumentedException(errMsg);
397     }
398
399     // RPC
400     public CheckedFuture<DOMRpcResult, DOMRpcException> invokeRpc(final SchemaPath type, final NormalizedNode<?, ?> input) {
401         checkPreconditions();
402         if (this.rpcService == null) {
403             throw new RestconfDocumentedException(Status.SERVICE_UNAVAILABLE);
404         }
405         LOG.trace("Invoke RPC {} with input: {}", type, input);
406         return this.rpcService.invokeRpc(type, input);
407     }
408
409     public void registerToListenDataChanges(final LogicalDatastoreType datastore, final DataChangeScope scope,
410             final ListenerAdapter listener) {
411         checkPreconditions();
412
413         if (listener.isListening()) {
414             return;
415         }
416
417         final YangInstanceIdentifier path = listener.getPath();
418         final ListenerRegistration<DOMDataChangeListener> registration = this.domDataBroker.registerDataChangeListener(
419                 datastore, path, listener, scope);
420
421         listener.setRegistration(registration);
422     }
423
424     private NormalizedNode<?, ?> readDataViaTransaction(final DOMDataReadTransaction transaction,
425             final LogicalDatastoreType datastore, final YangInstanceIdentifier path) {
426         LOG.trace("Read {} via Restconf: {}", datastore.name(), path);
427         final ListenableFuture<Optional<NormalizedNode<?, ?>>> listenableFuture = transaction.read(datastore, path);
428         final ReadDataResult<NormalizedNode<?, ?>> readData = new ReadDataResult<>();
429         final CountDownLatch responseWaiter = new CountDownLatch(1);
430
431         Futures.addCallback(listenableFuture, new FutureCallback<Optional<NormalizedNode<?, ?>>>() {
432
433             @Override
434             public void onSuccess(final Optional<NormalizedNode<?, ?>> result) {
435                 handlingCallback(null, datastore, path, result, readData);
436                 responseWaiter.countDown();
437             }
438
439             @Override
440             public void onFailure(final Throwable t) {
441                 responseWaiter.countDown();
442                 handlingCallback(t, datastore, path, null, null);
443             }
444         });
445
446         try {
447             responseWaiter.await();
448         } catch (final Exception e) {
449             final String msg = "Problem while waiting for response";
450             LOG.warn(msg);
451             throw new RestconfDocumentedException(msg, e);
452         }
453         return readData.getResult();
454     }
455
456     /**
457      * POST data and submit transaction {@link DOMDataReadWriteTransaction}
458      * @return
459      */
460     private CheckedFuture<Void, TransactionCommitFailedException> postDataViaTransaction(
461             final DOMDataReadWriteTransaction rWTransaction, final LogicalDatastoreType datastore,
462             final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext) {
463         LOG.trace("POST {} via Restconf: {} with payload {}", datastore.name(), path, payload);
464         postData(rWTransaction, datastore, path, payload, schemaContext);
465         return rWTransaction.submit();
466     }
467
468     /**
469      * POST data and do NOT submit transaction {@link DOMDataReadWriteTransaction}
470      */
471     private void postDataWithinTransaction(
472             final DOMDataReadWriteTransaction rWTransaction, final LogicalDatastoreType datastore,
473             final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext) {
474         LOG.trace("POST {} within Restconf PATCH: {} with payload {}", datastore.name(), path, payload);
475         postData(rWTransaction, datastore, path, payload, schemaContext);
476     }
477
478     // FIXME: This is doing correct post for container and list children, not sure if this will work for choice case
479     private void postData(final DOMDataReadWriteTransaction rWTransaction, final LogicalDatastoreType datastore,
480                           final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload,
481                           final SchemaContext schemaContext) {
482         if (payload instanceof MapNode) {
483             final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
484             rWTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
485             ensureParentsByMerge(datastore, path, rWTransaction, schemaContext);
486             for (final MapEntryNode child : ((MapNode) payload).getValue()) {
487                 final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
488                 checkItemDoesNotExists(rWTransaction, datastore, childPath);
489                 rWTransaction.put(datastore, childPath, child);
490             }
491         } else {
492             checkItemDoesNotExists(rWTransaction, datastore, path);
493             ensureParentsByMerge(datastore, path, rWTransaction, schemaContext);
494             rWTransaction.put(datastore, path, payload);
495         }
496     }
497
498     /**
499      * Check if item already exists. Throws error if it does NOT already exist.
500      * @param rWTransaction Current transaction
501      * @param store Used datastore
502      * @param path Path to item to verify its existence
503      */
504     private void checkItemExists(final DOMDataReadWriteTransaction rWTransaction,
505                                  final LogicalDatastoreType store, final YangInstanceIdentifier path) {
506         final CountDownLatch responseWaiter = new CountDownLatch(1);
507         final ReadDataResult<Boolean> readData = new ReadDataResult<>();
508         final CheckedFuture<Boolean, ReadFailedException> future = rWTransaction.exists(store, path);
509
510         Futures.addCallback(future, new FutureCallback<Boolean>() {
511             @Override
512             public void onSuccess(@Nullable final Boolean result) {
513                 handlingCallback(null, store, path, Optional.of(result), readData);
514                 responseWaiter.countDown();
515             }
516
517             @Override
518             public void onFailure(final Throwable t) {
519                 responseWaiter.countDown();
520                 handlingCallback(t, store, path, null, null);
521             }
522         });
523
524         try {
525             responseWaiter.await();
526         } catch (final Exception e) {
527             final String msg = "Problem while waiting for response";
528             LOG.warn(msg);
529             throw new RestconfDocumentedException(msg, e);
530         }
531
532         if ((readData.getResult() == null) || !readData.getResult()) {
533             final String errMsg = "Operation via Restconf was not executed because data does not exist";
534             LOG.trace("{}:{}", errMsg, path);
535             rWTransaction.cancel();
536             throw new RestconfDocumentedException("Data does not exist for path: " + path, ErrorType.PROTOCOL,
537                     ErrorTag.DATA_MISSING);
538         }
539     }
540
541     /**
542      * Check if item does NOT already exist. Throws error if it already exists.
543      * @param rWTransaction Current transaction
544      * @param store Used datastore
545      * @param path Path to item to verify its existence
546      */
547     private void checkItemDoesNotExists(final DOMDataReadWriteTransaction rWTransaction,
548                                         final LogicalDatastoreType store, final YangInstanceIdentifier path) {
549         final CountDownLatch responseWaiter = new CountDownLatch(1);
550         final ReadDataResult<Boolean> readData = new ReadDataResult<>();
551         final CheckedFuture<Boolean, ReadFailedException> future = rWTransaction.exists(store, path);
552
553         Futures.addCallback(future, new FutureCallback<Boolean>() {
554             @Override
555             public void onSuccess(@Nullable final Boolean result) {
556                 handlingCallback(null, store, path, Optional.of(result), readData);
557                 responseWaiter.countDown();
558             }
559
560             @Override
561             public void onFailure(final Throwable t) {
562                 responseWaiter.countDown();
563                 handlingCallback(t, store, path, null, null);
564             }
565         });
566
567         try {
568             responseWaiter.await();
569         } catch (final Exception e) {
570             final String msg = "Problem while waiting for response";
571             LOG.warn(msg);
572             throw new RestconfDocumentedException(msg, e);
573         }
574
575         if (readData.getResult()) {
576             final String errMsg = "Operation via Restconf was not executed because data already exists";
577             LOG.trace("{}:{}", errMsg, path);
578             rWTransaction.cancel();
579             throw new RestconfDocumentedException("Data already exists for path: " + path, ErrorType.PROTOCOL,
580                     ErrorTag.DATA_EXISTS);
581         }
582     }
583
584     /**
585      * PUT data and submit {@link DOMDataReadWriteTransaction}
586      */
587     private CheckedFuture<Void, TransactionCommitFailedException> putDataViaTransaction(
588             final DOMDataReadWriteTransaction readWriteTransaction, final LogicalDatastoreType datastore,
589             final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext) {
590         LOG.trace("Put {} via Restconf: {} with payload {}", datastore.name(), path, payload);
591         putData(readWriteTransaction, datastore, path, payload, schemaContext);
592         return readWriteTransaction.submit();
593     }
594
595     /**
596      * PUT data and do NOT submit {@link DOMDataReadWriteTransaction}
597      */
598     private void putDataWithinTransaction(
599             final DOMDataReadWriteTransaction writeTransaction, final LogicalDatastoreType datastore,
600             final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext) {
601         LOG.trace("Put {} within Restconf PATCH: {} with payload {}", datastore.name(), path, payload);
602         putData(writeTransaction, datastore, path, payload, schemaContext);
603     }
604
605     // FIXME: This is doing correct put for container and list children, not sure if this will work for choice case
606     private void putData(final DOMDataReadWriteTransaction writeTransaction, final LogicalDatastoreType datastore,
607             final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext) {
608         if (payload instanceof MapNode) {
609             final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
610             writeTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
611             ensureParentsByMerge(datastore, path, writeTransaction, schemaContext);
612             for (final MapEntryNode child : ((MapNode) payload).getValue()) {
613                 final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
614                 writeTransaction.put(datastore, childPath, child);
615             }
616         } else {
617             ensureParentsByMerge(datastore, path, writeTransaction, schemaContext);
618             writeTransaction.put(datastore, path, payload);
619         }
620     }
621
622     private CheckedFuture<Void, TransactionCommitFailedException> deleteDataViaTransaction(
623             final DOMDataReadWriteTransaction readWriteTransaction, final LogicalDatastoreType datastore,
624             final YangInstanceIdentifier path) {
625         LOG.trace("Delete {} via Restconf: {}", datastore.name(), path);
626         checkItemExists(readWriteTransaction, datastore, path);
627         readWriteTransaction.delete(datastore, path);
628         return readWriteTransaction.submit();
629     }
630
631     private void deleteDataWithinTransaction(
632             final DOMDataWriteTransaction writeTransaction, final LogicalDatastoreType datastore,
633             final YangInstanceIdentifier path) {
634         LOG.trace("Delete {} within Restconf PATCH: {}", datastore.name(), path);
635         writeTransaction.delete(datastore, path);
636     }
637
638     private void mergeDataWithinTransaction(
639             final DOMDataReadWriteTransaction writeTransaction, final LogicalDatastoreType datastore,
640             final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext) {
641         LOG.trace("Merge {} within Restconf PATCH: {} with payload {}", datastore.name(), path, payload);
642         ensureParentsByMerge(datastore, path, writeTransaction, schemaContext);
643
644         // merging is necessary only for lists otherwise we can call put method
645         if (payload instanceof MapNode) {
646             writeTransaction.merge(datastore, path, payload);
647         } else {
648             writeTransaction.put(datastore, path, payload);
649         }
650     }
651
652     public void setDomDataBroker(final DOMDataBroker domDataBroker) {
653         this.domDataBroker = domDataBroker;
654     }
655
656     /**
657      * Helper class for result of transaction commit callback.
658      * @param <T> Type of result
659      */
660     private final class ReadDataResult<T> {
661         T result = null;
662
663         T getResult() {
664             return this.result;
665         }
666
667         void setResult(final T result) {
668             this.result = result;
669         }
670     }
671
672     /**
673      * Set result from transaction commit callback.
674      * @param t Throwable if transaction commit failed
675      * @param datastore Datastore from which data are read
676      * @param path Path from which data are read
677      * @param result Result of read from {@code datastore}
678      * @param readData Result value which will be set
679      * @param <X> Result type
680      */
681     protected final static <X> void handlingCallback(final Throwable t, final LogicalDatastoreType datastore,
682                                                      final YangInstanceIdentifier path, final Optional<X> result,
683                                                      final ReadDataResult<X> readData) {
684         if (t != null) {
685             LOG.warn("Exception by reading {} via Restconf: {}", datastore.name(), path, t);
686             throw new RestconfDocumentedException("Problem to get data from transaction.", t);
687         } else {
688             LOG.debug("Reading result data from transaction.");
689             if (result != null) {
690                 if (result.isPresent()) {
691                     readData.setResult(result.get());
692                 }
693             }
694         }
695     }
696
697     public void registerToListenNotification(final NotificationListenerAdapter listener) {
698         checkPreconditions();
699
700         if (listener.isListening()) {
701             return;
702         }
703
704         final SchemaPath path = listener.getSchemaPath();
705         final ListenerRegistration<DOMNotificationListener> registration = this.domNotification
706                 .registerNotificationListener(listener, path);
707
708         listener.setRegistration(registration);
709     }
710
711     private final class PATCHStatusContextHelper {
712         PATCHStatusContext status;
713
714         public PATCHStatusContext getStatus() {
715             return this.status;
716         }
717
718         public void setStatus(final PATCHStatusContext status) {
719             this.status = status;
720         }
721     }
722
723     private void ensureParentsByMerge(final LogicalDatastoreType store, final YangInstanceIdentifier normalizedPath,
724             final DOMDataReadWriteTransaction rwTx, final SchemaContext schemaContext) {
725         final List<PathArgument> normalizedPathWithoutChildArgs = new ArrayList<>();
726         YangInstanceIdentifier rootNormalizedPath = null;
727
728         final Iterator<PathArgument> it = normalizedPath.getPathArguments().iterator();
729
730         while (it.hasNext()) {
731             final PathArgument pathArgument = it.next();
732             if (rootNormalizedPath == null) {
733                 rootNormalizedPath = YangInstanceIdentifier.create(pathArgument);
734             }
735
736             if (it.hasNext()) {
737                 normalizedPathWithoutChildArgs.add(pathArgument);
738             }
739         }
740
741         if (normalizedPathWithoutChildArgs.isEmpty()) {
742             return;
743         }
744
745         Preconditions.checkArgument(rootNormalizedPath != null, "Empty path received");
746
747         final NormalizedNode<?, ?> parentStructure = ImmutableNodes.fromInstanceId(schemaContext,
748                 YangInstanceIdentifier.create(normalizedPathWithoutChildArgs));
749         rwTx.merge(store, rootNormalizedPath, parentStructure);
750     }
751 }