Bug 6272 - support RESTCONF PATCH for mounted NETCONF nodes
[netconf.git] / opendaylight / 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.ListenableFuture;
18 import java.util.ArrayList;
19 import java.util.Iterator;
20 import java.util.List;
21 import java.util.concurrent.ExecutionException;
22 import javax.ws.rs.core.Response.Status;
23 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
24 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
25 import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
26 import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
27 import org.opendaylight.controller.md.sal.dom.api.DOMDataChangeListener;
28 import org.opendaylight.controller.md.sal.dom.api.DOMDataReadTransaction;
29 import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
30 import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
31 import org.opendaylight.controller.md.sal.dom.api.DOMMountPoint;
32 import org.opendaylight.controller.md.sal.dom.api.DOMNotificationListener;
33 import org.opendaylight.controller.md.sal.dom.api.DOMNotificationService;
34 import org.opendaylight.controller.md.sal.dom.api.DOMRpcException;
35 import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult;
36 import org.opendaylight.controller.md.sal.dom.api.DOMRpcService;
37 import org.opendaylight.controller.sal.core.api.Broker.ConsumerSession;
38 import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorTag;
39 import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorType;
40 import org.opendaylight.netconf.sal.streams.listeners.ListenerAdapter;
41 import org.opendaylight.netconf.sal.streams.listeners.NotificationListenerAdapter;
42 import org.opendaylight.yangtools.concepts.ListenerRegistration;
43 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
44 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
45 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
46 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
47 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
48 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
49 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
50 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
51 import org.slf4j.Logger;
52 import org.slf4j.LoggerFactory;
53
54 public class BrokerFacade {
55     private final static Logger LOG = LoggerFactory.getLogger(BrokerFacade.class);
56
57     private final static BrokerFacade INSTANCE = new BrokerFacade();
58     private volatile DOMRpcService rpcService;
59     private volatile ConsumerSession context;
60     private DOMDataBroker domDataBroker;
61     private DOMNotificationService domNotification;
62
63     private BrokerFacade() {
64     }
65
66     public void setRpcService(final DOMRpcService router) {
67         this.rpcService = router;
68     }
69
70     public void setDomNotificationService(final DOMNotificationService domNotification) {
71         this.domNotification = domNotification;
72     }
73
74     public void setContext(final ConsumerSession context) {
75         this.context = context;
76     }
77
78     public static BrokerFacade getInstance() {
79         return BrokerFacade.INSTANCE;
80     }
81
82     private void checkPreconditions() {
83         if ((this.context == null) || (this.domDataBroker == null)) {
84             throw new RestconfDocumentedException(Status.SERVICE_UNAVAILABLE);
85         }
86     }
87
88     // READ configuration
89     public NormalizedNode<?, ?> readConfigurationData(final YangInstanceIdentifier path) {
90         checkPreconditions();
91         return readDataViaTransaction(this.domDataBroker.newReadOnlyTransaction(), CONFIGURATION, path);
92     }
93
94     public NormalizedNode<?, ?> readConfigurationData(final DOMMountPoint mountPoint, final YangInstanceIdentifier path) {
95         final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
96         if (domDataBrokerService.isPresent()) {
97             return readDataViaTransaction(domDataBrokerService.get().newReadOnlyTransaction(), CONFIGURATION, path);
98         }
99         final String errMsg = "DOM data broker service isn't available for mount point " + path;
100         LOG.warn(errMsg);
101         throw new RestconfDocumentedException(errMsg);
102     }
103
104     // READ operational
105     public NormalizedNode<?, ?> readOperationalData(final YangInstanceIdentifier path) {
106         checkPreconditions();
107         return readDataViaTransaction(this.domDataBroker.newReadOnlyTransaction(), OPERATIONAL, path);
108     }
109
110     public NormalizedNode<?, ?> readOperationalData(final DOMMountPoint mountPoint, final YangInstanceIdentifier path) {
111         final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
112         if (domDataBrokerService.isPresent()) {
113             return readDataViaTransaction(domDataBrokerService.get().newReadOnlyTransaction(), OPERATIONAL, path);
114         }
115         final String errMsg = "DOM data broker service isn't available for mount point " + path;
116         LOG.warn(errMsg);
117         throw new RestconfDocumentedException(errMsg);
118     }
119
120     // PUT configuration
121     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataPut(
122             final SchemaContext globalSchema, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload) {
123         checkPreconditions();
124         return putDataViaTransaction(this.domDataBroker, CONFIGURATION, path, payload, globalSchema);
125     }
126
127     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataPut(
128             final DOMMountPoint mountPoint, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload) {
129         final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
130         if (domDataBrokerService.isPresent()) {
131             return putDataViaTransaction(domDataBrokerService.get(), CONFIGURATION, path,
132                     payload, mountPoint.getSchemaContext());
133         }
134         final String errMsg = "DOM data broker service isn't available for mount point " + path;
135         LOG.warn(errMsg);
136         throw new RestconfDocumentedException(errMsg);
137     }
138
139     public PATCHStatusContext patchConfigurationDataWithinTransaction(final PATCHContext patchContext)
140             throws TransactionCommitFailedException {
141         final DOMMountPoint mountPoint = patchContext.getInstanceIdentifierContext().getMountPoint();
142
143         // get new transaction and schema context on server or on mounted device
144         final SchemaContext schemaContext;
145         final DOMDataReadWriteTransaction patchTransaction;
146         if (mountPoint == null) {
147             schemaContext = patchContext.getInstanceIdentifierContext().getSchemaContext();
148             patchTransaction = this.domDataBroker.newReadWriteTransaction();
149         } else {
150             schemaContext = mountPoint.getSchemaContext();
151
152             final Optional<DOMDataBroker> optional = mountPoint.getService(DOMDataBroker.class);
153
154             if (optional.isPresent()) {
155                 patchTransaction = optional.get().newReadWriteTransaction();
156             } else {
157                 // if mount point does not have broker it is not possible to continue and global error is reported
158                 LOG.error("Http PATCH {} has failed - device {} does not support broker service",
159                         patchContext.getPatchId(), mountPoint.getIdentifier());
160                 return new PATCHStatusContext(
161                         patchContext.getPatchId(),
162                         null,
163                         false,
164                         ImmutableList.of(new RestconfError(
165                                 ErrorType.APPLICATION,
166                                 ErrorTag.OPERATION_FAILED,
167                                 "DOM data broker service isn't available for mount point "
168                                         + mountPoint.getIdentifier()))
169                 );
170             }
171         }
172
173         final List<PATCHStatusEntity> editCollection = new ArrayList<>();
174         List<RestconfError> editErrors;
175         boolean withoutError = true;
176
177         for (final PATCHEntity patchEntity : patchContext.getData()) {
178             final PATCHEditOperation operation = PATCHEditOperation.valueOf(patchEntity.getOperation().toUpperCase());
179
180             switch (operation) {
181                 case CREATE:
182                     if (withoutError) {
183                         try {
184                             postDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity.getTargetNode(),
185                                     patchEntity.getNode(), schemaContext);
186                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
187                         } catch (final RestconfDocumentedException e) {
188                             LOG.error("Error call http PATCH operation {} on target {}",
189                                     operation,
190                                     patchEntity.getTargetNode().toString());
191
192                             editErrors = new ArrayList<>();
193                             editErrors.addAll(e.getErrors());
194                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), false, editErrors));
195                             withoutError = false;
196                         }
197                     }
198                     break;
199                 case REPLACE:
200                     if (withoutError) {
201                         try {
202                             putDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity
203                                     .getTargetNode(), patchEntity.getNode(), schemaContext);
204                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
205                         } catch (final RestconfDocumentedException e) {
206                             LOG.error("Error call http PATCH operation {} on target {}",
207                                     operation,
208                                     patchEntity.getTargetNode().toString());
209
210                             editErrors = new ArrayList<>();
211                             editErrors.addAll(e.getErrors());
212                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), false, editErrors));
213                             withoutError = false;
214                         }
215                     }
216                     break;
217                 case DELETE:
218                     if (withoutError) {
219                         try {
220                             deleteDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity
221                                     .getTargetNode());
222                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
223                         } catch (final RestconfDocumentedException e) {
224                             LOG.error("Error call http PATCH operation {} on target {}",
225                                     operation,
226                                     patchEntity.getTargetNode().toString());
227
228                             editErrors = new ArrayList<>();
229                             editErrors.addAll(e.getErrors());
230                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), false, editErrors));
231                             withoutError = false;
232                         }
233                     }
234                     break;
235                 case REMOVE:
236                     if (withoutError) {
237                         try {
238                             deleteDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity
239                                     .getTargetNode());
240                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
241                         } catch (final RestconfDocumentedException e) {
242                             LOG.error("Error call http PATCH operation {} on target {}",
243                                     operation,
244                                     patchEntity.getTargetNode().toString());
245
246                             editErrors = new ArrayList<>();
247                             editErrors.addAll(e.getErrors());
248                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), false, editErrors));
249                             withoutError = false;
250                         }
251                     }
252                     break;
253                 case MERGE:
254                     if (withoutError) {
255                         try {
256                             mergeDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity.getTargetNode(),
257                                     patchEntity.getNode(), schemaContext);
258                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
259                         } catch (final RestconfDocumentedException e) {
260                             LOG.error("Error call http PATCH operation {} on target {}",
261                                     operation,
262                                     patchEntity.getTargetNode().toString());
263
264                             editErrors = new ArrayList<>();
265                             editErrors.addAll(e.getErrors());
266                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), false, editErrors));
267                             withoutError = false;
268                         }
269                     }
270                     break;
271             }
272         }
273
274         //TODO: make sure possible global errors are filled up correctly and decide transaction submission based on that
275         if (withoutError) {
276             patchTransaction.submit().checkedGet();
277             return new PATCHStatusContext(patchContext.getPatchId(), ImmutableList.copyOf(editCollection), true, null);
278         } else {
279             patchTransaction.cancel();
280             return new PATCHStatusContext(patchContext.getPatchId(), ImmutableList.copyOf(editCollection), false, null);
281         }
282     }
283
284     // POST configuration
285     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataPost(
286             final SchemaContext globalSchema, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload) {
287         checkPreconditions();
288         return postDataViaTransaction(this.domDataBroker, CONFIGURATION, path, payload, globalSchema);
289     }
290
291     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataPost(
292             final DOMMountPoint mountPoint, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload) {
293         final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
294         if (domDataBrokerService.isPresent()) {
295             return postDataViaTransaction(domDataBrokerService.get(), CONFIGURATION, path,
296                     payload, mountPoint.getSchemaContext());
297         }
298         final String errMsg = "DOM data broker service isn't available for mount point " + path;
299         LOG.warn(errMsg);
300         throw new RestconfDocumentedException(errMsg);
301     }
302
303     // DELETE configuration
304     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataDelete(
305             final YangInstanceIdentifier path) {
306         checkPreconditions();
307         return deleteDataViaTransaction(this.domDataBroker.newReadWriteTransaction(), CONFIGURATION, path);
308     }
309
310     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataDelete(
311             final DOMMountPoint mountPoint, final YangInstanceIdentifier path) {
312         final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
313         if (domDataBrokerService.isPresent()) {
314             return deleteDataViaTransaction(domDataBrokerService.get().newReadWriteTransaction(), CONFIGURATION, path);
315         }
316         final String errMsg = "DOM data broker service isn't available for mount point " + path;
317         LOG.warn(errMsg);
318         throw new RestconfDocumentedException(errMsg);
319     }
320
321     // RPC
322     public CheckedFuture<DOMRpcResult, DOMRpcException> invokeRpc(final SchemaPath type, final NormalizedNode<?, ?> input) {
323         checkPreconditions();
324         if (this.rpcService == null) {
325             throw new RestconfDocumentedException(Status.SERVICE_UNAVAILABLE);
326         }
327         LOG.trace("Invoke RPC {} with input: {}", type, input);
328         return this.rpcService.invokeRpc(type, input);
329     }
330
331     public void registerToListenDataChanges(final LogicalDatastoreType datastore, final DataChangeScope scope,
332             final ListenerAdapter listener) {
333         checkPreconditions();
334
335         if (listener.isListening()) {
336             return;
337         }
338
339         final YangInstanceIdentifier path = listener.getPath();
340         final ListenerRegistration<DOMDataChangeListener> registration = this.domDataBroker.registerDataChangeListener(
341                 datastore, path, listener, scope);
342
343         listener.setRegistration(registration);
344     }
345
346     private NormalizedNode<?, ?> readDataViaTransaction(final DOMDataReadTransaction transaction,
347             final LogicalDatastoreType datastore, final YangInstanceIdentifier path) {
348         LOG.trace("Read {} via Restconf: {}", datastore.name(), path);
349         final ListenableFuture<Optional<NormalizedNode<?, ?>>> listenableFuture = transaction.read(datastore, path);
350         if (listenableFuture != null) {
351             Optional<NormalizedNode<?, ?>> optional;
352             try {
353                 LOG.debug("Reading result data from transaction.");
354                 optional = listenableFuture.get();
355             } catch (InterruptedException | ExecutionException e) {
356                 LOG.warn("Exception by reading {} via Restconf: {}", datastore.name(), path, e);
357                 throw new RestconfDocumentedException("Problem to get data from transaction.", e.getCause());
358
359             }
360             if (optional != null) {
361                 if (optional.isPresent()) {
362                     return optional.get();
363                 }
364             }
365         }
366         return null;
367     }
368
369     private CheckedFuture<Void, TransactionCommitFailedException> postDataViaTransaction(
370             final DOMDataBroker domDataBroker, final LogicalDatastoreType datastore,
371             final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext) {
372         // FIXME: This is doing correct post for container and list children
373         //        not sure if this will work for choice case
374         DOMDataReadWriteTransaction transaction = domDataBroker.newReadWriteTransaction();
375         if(payload instanceof MapNode) {
376             LOG.trace("POST {} via Restconf: {} with payload {}", datastore.name(), path, payload);
377             final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
378             try {
379                 transaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
380             } catch (RuntimeException e) {
381                 // FIXME: Figure out and catch specific RunTimeExceptions thrown by NETCONF instead of generic one.
382                 //        to make this cleaner and easier to maintain.
383                 transaction.cancel();
384                 transaction = domDataBroker.newReadWriteTransaction();
385                 LOG.debug("Empty subtree merge failed", e);
386             }
387             if (!ensureParentsByMerge(datastore, path, transaction, schemaContext)) {
388                 transaction.cancel();
389                 transaction = domDataBroker.newReadWriteTransaction();
390             }
391             for(final MapEntryNode child : ((MapNode) payload).getValue()) {
392                 final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
393                 checkItemDoesNotExists(transaction, datastore, childPath);
394                 transaction.put(datastore, childPath, child);
395             }
396         } else {
397             checkItemDoesNotExists(transaction,datastore, path);
398             if(!ensureParentsByMerge(datastore, path, transaction, schemaContext)) {
399                 transaction.cancel();
400                 transaction = domDataBroker.newReadWriteTransaction();
401             }
402             transaction.put(datastore, path, payload);
403         }
404         return transaction.submit();
405     }
406
407     // FIXME: This is doing correct post for container and list children
408     //        not sure if this will work for choice case
409     private void postDataWithinTransaction(
410             final DOMDataReadWriteTransaction rWTransaction, final LogicalDatastoreType datastore,
411             final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext) {
412         LOG.trace("POST {} within Restconf PATCH: {} with payload {}", datastore.name(), path, payload);
413
414         if (payload instanceof MapNode) {
415             final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
416             rWTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
417             ensureParentsByMerge(datastore, path, rWTransaction, schemaContext);
418             for (final MapEntryNode child : ((MapNode) payload).getValue()) {
419                 final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
420                 checkItemDoesNotExists(rWTransaction, datastore, childPath);
421                 rWTransaction.put(datastore, childPath, child);
422             }
423         } else {
424             checkItemDoesNotExists(rWTransaction, datastore, path);
425             ensureParentsByMerge(datastore, path, rWTransaction, schemaContext);
426             rWTransaction.put(datastore, path, payload);
427         }
428     }
429
430     private void checkItemExists(final DOMDataReadWriteTransaction rWTransaction,
431                                  final LogicalDatastoreType store, final YangInstanceIdentifier path) {
432         final ListenableFuture<Boolean> futureDatastoreData = rWTransaction.exists(store, path);
433         try {
434             if (!futureDatastoreData.get()) {
435                 final String errMsg = "Operation via Restconf was not executed because data does not exist";
436                 LOG.trace("{}:{}", errMsg, path);
437                 rWTransaction.cancel();
438                 throw new RestconfDocumentedException("Data does not exist for path: " + path, ErrorType.PROTOCOL,
439                         ErrorTag.DATA_MISSING);
440             }
441         } catch (InterruptedException | ExecutionException e) {
442             LOG.warn("It wasn't possible to get data loaded from datastore at path {}", path, e);
443         }
444     }
445
446     private void checkItemDoesNotExists(final DOMDataReadWriteTransaction rWTransaction,
447                                         final LogicalDatastoreType store, final YangInstanceIdentifier path) {
448         final ListenableFuture<Boolean> futureDatastoreData = rWTransaction.exists(store, path);
449         try {
450             if (futureDatastoreData.get()) {
451                 final String errMsg = "Operation via Restconf was not executed because data already exists";
452                 LOG.trace("{}:{}", errMsg, path);
453                 rWTransaction.cancel();
454                 throw new RestconfDocumentedException("Data already exists for path: " + path, ErrorType.PROTOCOL,
455                         ErrorTag.DATA_EXISTS);
456             }
457         } catch (InterruptedException | ExecutionException e) {
458             LOG.warn("It wasn't possible to get data loaded from datastore at path {}", path, e);
459         }
460     }
461
462     private CheckedFuture<Void, TransactionCommitFailedException> putDataViaTransaction(
463             final DOMDataBroker domDataBroker, final LogicalDatastoreType datastore,
464             final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext)
465     {
466         DOMDataReadWriteTransaction transaction = domDataBroker.newReadWriteTransaction();
467         LOG.trace("Put {} via Restconf: {} with payload {}", datastore.name(), path, payload);
468         if (!ensureParentsByMerge(datastore, path, transaction, schemaContext)) {
469             transaction.cancel();
470             transaction = domDataBroker.newReadWriteTransaction();
471         }
472         transaction.put(datastore, path, payload);
473         return transaction.submit();
474     }
475
476     // FIXME: This is doing correct post for container and list children
477     //        not sure if this will work for choice case
478     private void putDataWithinTransaction(
479             final DOMDataReadWriteTransaction rWTransaction, final LogicalDatastoreType datastore,
480             final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext) {
481         LOG.trace("PUT {} within Restconf PATCH: {} with payload {}", datastore.name(), path, payload);
482
483         if (payload instanceof MapNode) {
484             final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
485             rWTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
486             ensureParentsByMerge(datastore, path, rWTransaction, schemaContext);
487             for (final MapEntryNode child : ((MapNode) payload).getValue()) {
488                 final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
489                 checkItemDoesNotExists(rWTransaction, datastore, childPath);
490                 rWTransaction.put(datastore, childPath, child);
491             }
492         } else {
493             checkItemDoesNotExists(rWTransaction, datastore, path);
494             ensureParentsByMerge(datastore, path, rWTransaction, schemaContext);
495             rWTransaction.put(datastore, path, payload);
496         }
497     }
498
499     private CheckedFuture<Void, TransactionCommitFailedException> deleteDataViaTransaction(
500             final DOMDataReadWriteTransaction readWriteTransaction, final LogicalDatastoreType datastore,
501             final YangInstanceIdentifier path) {
502         LOG.trace("Delete {} via Restconf: {}", datastore.name(), path);
503         checkItemExists(readWriteTransaction, datastore, path);
504         readWriteTransaction.delete(datastore, path);
505         return readWriteTransaction.submit();
506     }
507
508     private void deleteDataWithinTransaction(
509             final DOMDataWriteTransaction writeTransaction, final LogicalDatastoreType datastore,
510             final YangInstanceIdentifier path) {
511         LOG.trace("Delete {} within Restconf PATCH: {}", datastore.name(), path);
512         writeTransaction.delete(datastore, path);
513     }
514
515     private void mergeDataWithinTransaction(
516             final DOMDataReadWriteTransaction writeTransaction, final LogicalDatastoreType datastore,
517             final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext) {
518         LOG.trace("Merge {} within Restconf PATCH: {} with payload {}", datastore.name(), path, payload);
519         ensureParentsByMerge(datastore, path, writeTransaction, schemaContext);
520
521         // merging is necessary only for lists otherwise we can call put method
522         if (payload instanceof MapNode) {
523             writeTransaction.merge(datastore, path, payload);
524         } else {
525             writeTransaction.put(datastore, path, payload);
526         }
527     }
528
529     public void setDomDataBroker(final DOMDataBroker domDataBroker) {
530         this.domDataBroker = domDataBroker;
531     }
532
533     private boolean ensureParentsByMerge(final LogicalDatastoreType store,
534                                       final YangInstanceIdentifier normalizedPath, final DOMDataReadWriteTransaction rwTx, final SchemaContext schemaContext) {
535
536         boolean mergeResult = true;
537         final List<PathArgument> normalizedPathWithoutChildArgs = new ArrayList<>();
538         YangInstanceIdentifier rootNormalizedPath = null;
539
540         final Iterator<PathArgument> it = normalizedPath.getPathArguments().iterator();
541
542         while(it.hasNext()) {
543             final PathArgument pathArgument = it.next();
544             if(rootNormalizedPath == null) {
545                 rootNormalizedPath = YangInstanceIdentifier.create(pathArgument);
546             }
547
548             // Skip last element, its not a parent
549             if(it.hasNext()) {
550                 normalizedPathWithoutChildArgs.add(pathArgument);
551             }
552         }
553
554         // No parent structure involved, no need to ensure parents
555         if(normalizedPathWithoutChildArgs.isEmpty()) {
556             return mergeResult;
557         }
558
559         Preconditions.checkArgument(rootNormalizedPath != null, "Empty path received");
560
561         final NormalizedNode<?, ?> parentStructure =
562                 ImmutableNodes.fromInstanceId(schemaContext, YangInstanceIdentifier.create(normalizedPathWithoutChildArgs));
563         try {
564             rwTx.merge(store, rootNormalizedPath, parentStructure);
565         } catch (RuntimeException e) {
566             /*
567              * Catching the exception here, logging it and proceeding further
568              * for the following reasons.
569              *
570              * 1. For MD-SAL store if it fails we'll go with the next call
571              * anyway and let the failure happen there. 2. For NETCONF devices
572              * that can not handle these calls such as creation of empty lists
573              * etc, instead of failing we'll go with the actual call. Devices
574              * should be able to handle the actual calls made without the need
575              * to create parents. So instead of failing we will give a device a
576              * chance to configure the management entity in question. 3. If this
577              * merge call is handled properly by MD-SAL data store or a Netconf
578              * device this is a no-op.
579              */
580              // FIXME: Figure out and catch specific RunTimeExceptions thrown by NETCONF instead of generic one.
581              //        to make this cleaner and easier to maintain.
582             mergeResult = false;
583             LOG.debug("Exception while creating the parent in ensureParentsByMerge. Proceeding with the actual request", e);
584         }
585         return mergeResult;
586     }
587
588     public void registerToListenNotification(final NotificationListenerAdapter listener) {
589         checkPreconditions();
590
591         if (listener.isListening()) {
592             return;
593         }
594
595         final SchemaPath path = listener.getSchemaPath();
596         final ListenerRegistration<DOMNotificationListener> registration = this.domNotification
597                 .registerNotificationListener((DOMNotificationListener) listener, path);
598
599         listener.setRegistration(registration);
600     }
601 }