Transition ListenerAdapter to ClusteredDOMDataTreeListener
[netconf.git] / restconf / restconf-nb-bierman02 / 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 java.util.ArrayList;
20 import java.util.Collection;
21 import java.util.Iterator;
22 import java.util.List;
23 import java.util.Map.Entry;
24 import java.util.concurrent.CountDownLatch;
25 import javax.annotation.Nullable;
26 import javax.ws.rs.core.Response.Status;
27 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
28 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
29 import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
30 import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
31 import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
32 import org.opendaylight.controller.md.sal.dom.api.DOMDataReadOnlyTransaction;
33 import org.opendaylight.controller.md.sal.dom.api.DOMDataReadTransaction;
34 import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
35 import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeChangeService;
36 import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeIdentifier;
37 import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
38 import org.opendaylight.controller.md.sal.dom.api.DOMMountPoint;
39 import org.opendaylight.controller.md.sal.dom.api.DOMNotificationListener;
40 import org.opendaylight.controller.md.sal.dom.api.DOMNotificationService;
41 import org.opendaylight.controller.md.sal.dom.api.DOMRpcException;
42 import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult;
43 import org.opendaylight.controller.md.sal.dom.api.DOMRpcService;
44 import org.opendaylight.netconf.sal.streams.listeners.ListenerAdapter;
45 import org.opendaylight.netconf.sal.streams.listeners.NotificationListenerAdapter;
46 import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
47 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
48 import org.opendaylight.restconf.common.errors.RestconfError;
49 import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag;
50 import org.opendaylight.restconf.common.errors.RestconfError.ErrorType;
51 import org.opendaylight.restconf.common.patch.PatchContext;
52 import org.opendaylight.restconf.common.patch.PatchEditOperation;
53 import org.opendaylight.restconf.common.patch.PatchEntity;
54 import org.opendaylight.restconf.common.patch.PatchStatusContext;
55 import org.opendaylight.restconf.common.patch.PatchStatusEntity;
56 import org.opendaylight.yangtools.concepts.ListenerRegistration;
57 import org.opendaylight.yangtools.yang.common.QName;
58 import org.opendaylight.yangtools.yang.common.RpcError;
59 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
60 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
61 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
62 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
63 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
64 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
65 import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
66 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
67 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
68 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
69 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
70 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
71 import org.opendaylight.yangtools.yang.data.api.schema.OrderedLeafSetNode;
72 import org.opendaylight.yangtools.yang.data.api.schema.OrderedMapNode;
73 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
74 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
75 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.CollectionNodeBuilder;
76 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeAttrBuilder;
77 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.NormalizedNodeAttrBuilder;
78 import org.opendaylight.yangtools.yang.data.util.DataSchemaContextNode;
79 import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
80 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
81 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
82 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
83 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
84 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
85 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
86 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
87 import org.slf4j.Logger;
88 import org.slf4j.LoggerFactory;
89
90 public class BrokerFacade {
91     private static final Logger LOG = LoggerFactory.getLogger(BrokerFacade.class);
92     private static final BrokerFacade INSTANCE = new BrokerFacade();
93
94     private volatile DOMRpcService rpcService;
95
96     private DOMDataBroker domDataBroker;
97     private DOMNotificationService domNotification;
98
99     private BrokerFacade() {}
100
101     public void setRpcService(final DOMRpcService router) {
102         this.rpcService = router;
103     }
104
105     public void setDomNotificationService(final DOMNotificationService domNotification) {
106         this.domNotification = domNotification;
107     }
108
109     public static BrokerFacade getInstance() {
110         return BrokerFacade.INSTANCE;
111     }
112
113     private void checkPreconditions() {
114         if (this.domDataBroker == null) {
115             throw new RestconfDocumentedException(Status.SERVICE_UNAVAILABLE);
116         }
117     }
118
119     /**
120      * Read config data by path.
121      *
122      * @param path
123      *            path of data
124      * @return read date
125      */
126     public NormalizedNode<?, ?> readConfigurationData(final YangInstanceIdentifier path) {
127         return readConfigurationData(path, null);
128     }
129
130     /**
131      * Read config data by path.
132      *
133      * @param path
134      *            path of data
135      * @param withDefa
136      *            value of with-defaults parameter
137      * @return read date
138      */
139     public NormalizedNode<?, ?> readConfigurationData(final YangInstanceIdentifier path, final String withDefa) {
140         checkPreconditions();
141         try (DOMDataReadOnlyTransaction tx = this.domDataBroker.newReadOnlyTransaction()) {
142             return readDataViaTransaction(tx, CONFIGURATION, path, withDefa);
143         }
144     }
145
146     /**
147      * Read config data from mount point by path.
148      *
149      * @param mountPoint
150      *            mount point for reading data
151      * @param path
152      *            path of data
153      * @return read data
154      */
155     public NormalizedNode<?, ?> readConfigurationData(final DOMMountPoint mountPoint,
156             final YangInstanceIdentifier path) {
157         return readConfigurationData(mountPoint, path, null);
158     }
159
160     /**
161      * Read config data from mount point by path.
162      *
163      * @param mountPoint
164      *            mount point for reading data
165      * @param path
166      *            path of data
167      * @param withDefa
168      *            value of with-defaults parameter
169      * @return read data
170      */
171     public NormalizedNode<?, ?> readConfigurationData(final DOMMountPoint mountPoint, final YangInstanceIdentifier path,
172             final String withDefa) {
173         final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
174         if (domDataBrokerService.isPresent()) {
175             try (DOMDataReadOnlyTransaction tx = domDataBrokerService.get().newReadOnlyTransaction()) {
176                 return readDataViaTransaction(tx, CONFIGURATION, path, withDefa);
177             }
178         }
179         final String errMsg = "DOM data broker service isn't available for mount point " + path;
180         LOG.warn(errMsg);
181         throw new RestconfDocumentedException(errMsg);
182     }
183
184     /**
185      * Read operational data by path.
186      *
187      * @param path
188      *            path of data
189      * @return read data
190      */
191     public NormalizedNode<?, ?> readOperationalData(final YangInstanceIdentifier path) {
192         checkPreconditions();
193
194         try (DOMDataReadOnlyTransaction tx = this.domDataBroker.newReadOnlyTransaction()) {
195             return readDataViaTransaction(tx, OPERATIONAL, path);
196         }
197     }
198
199     /**
200      * Read operational data from mount point by path.
201      *
202      * @param mountPoint
203      *            mount point for reading data
204      * @param path
205      *            path of data
206      * @return read data
207      */
208     public NormalizedNode<?, ?> readOperationalData(final DOMMountPoint mountPoint, final YangInstanceIdentifier path) {
209         final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
210         if (domDataBrokerService.isPresent()) {
211             try (DOMDataReadOnlyTransaction tx = domDataBrokerService.get().newReadOnlyTransaction()) {
212                 return readDataViaTransaction(tx, OPERATIONAL, path);
213             }
214         }
215         final String errMsg = "DOM data broker service isn't available for mount point " + path;
216         LOG.warn(errMsg);
217         throw new RestconfDocumentedException(errMsg);
218     }
219
220     /**
221      * <b>PUT configuration data</b>
222      *
223      * <p>
224      * Prepare result(status) for PUT operation and PUT data via transaction.
225      * Return wrapped status and future from PUT.
226      *
227      * @param globalSchema
228      *            used by merge parents (if contains list)
229      * @param path
230      *            path of node
231      * @param payload
232      *            input data
233      * @param point
234      *            point
235      * @param insert
236      *            insert
237      * @return wrapper of status and future of PUT
238      */
239     public PutResult commitConfigurationDataPut(
240             final SchemaContext globalSchema, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload,
241             final String insert, final String point) {
242         Preconditions.checkNotNull(globalSchema);
243         Preconditions.checkNotNull(path);
244         Preconditions.checkNotNull(payload);
245
246         checkPreconditions();
247
248         final DOMDataReadWriteTransaction newReadWriteTransaction = this.domDataBroker.newReadWriteTransaction();
249         final Status status = readDataViaTransaction(newReadWriteTransaction, CONFIGURATION, path) != null ? Status.OK
250                 : Status.CREATED;
251         final CheckedFuture<Void, TransactionCommitFailedException> future = putDataViaTransaction(
252                 newReadWriteTransaction, CONFIGURATION, path, payload, globalSchema, insert, point);
253         return new PutResult(status, future);
254     }
255
256     /**
257      * <b>PUT configuration data (Mount point)</b>
258      *
259      * <p>
260      * Prepare result(status) for PUT operation and PUT data via transaction.
261      * Return wrapped status and future from PUT.
262      *
263      * @param mountPoint
264      *            mount point for getting transaction for operation and schema
265      *            context for merging parents(if contains list)
266      * @param path
267      *            path of node
268      * @param payload
269      *            input data
270      * @param point
271      *            point
272      * @param insert
273      *            insert
274      * @return wrapper of status and future of PUT
275      */
276     public PutResult commitMountPointDataPut(
277             final DOMMountPoint mountPoint, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload,
278             final String insert, final String point) {
279         Preconditions.checkNotNull(mountPoint);
280         Preconditions.checkNotNull(path);
281         Preconditions.checkNotNull(payload);
282
283         final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
284         if (domDataBrokerService.isPresent()) {
285             final DOMDataReadWriteTransaction newReadWriteTransaction =
286                     domDataBrokerService.get().newReadWriteTransaction();
287             final Status status = readDataViaTransaction(newReadWriteTransaction, CONFIGURATION, path) != null
288                     ? Status.OK : Status.CREATED;
289             final CheckedFuture<Void, TransactionCommitFailedException> future = putDataViaTransaction(
290                     newReadWriteTransaction, CONFIGURATION, path, payload, mountPoint.getSchemaContext(), insert,
291                     point);
292             return new PutResult(status, future);
293         }
294         final String errMsg = "DOM data broker service isn't available for mount point " + path;
295         LOG.warn(errMsg);
296         throw new RestconfDocumentedException(errMsg);
297     }
298
299     public PatchStatusContext patchConfigurationDataWithinTransaction(final PatchContext patchContext)
300             throws Exception {
301         final DOMMountPoint mountPoint = patchContext.getInstanceIdentifierContext().getMountPoint();
302
303         // get new transaction and schema context on server or on mounted device
304         final SchemaContext schemaContext;
305         final DOMDataReadWriteTransaction patchTransaction;
306         if (mountPoint == null) {
307             schemaContext = patchContext.getInstanceIdentifierContext().getSchemaContext();
308             patchTransaction = this.domDataBroker.newReadWriteTransaction();
309         } else {
310             schemaContext = mountPoint.getSchemaContext();
311
312             final Optional<DOMDataBroker> optional = mountPoint.getService(DOMDataBroker.class);
313
314             if (optional.isPresent()) {
315                 patchTransaction = optional.get().newReadWriteTransaction();
316             } else {
317                 // if mount point does not have broker it is not possible to continue and global error is reported
318                 LOG.error("Http Patch {} has failed - device {} does not support broker service",
319                         patchContext.getPatchId(), mountPoint.getIdentifier());
320                 return new PatchStatusContext(
321                         patchContext.getPatchId(),
322                         null,
323                         false,
324                         ImmutableList.of(new RestconfError(
325                                 ErrorType.APPLICATION,
326                                 ErrorTag.OPERATION_FAILED,
327                                 "DOM data broker service isn't available for mount point "
328                                         + mountPoint.getIdentifier()))
329                 );
330             }
331         }
332
333         final List<PatchStatusEntity> editCollection = new ArrayList<>();
334         List<RestconfError> editErrors;
335         boolean withoutError = true;
336
337         for (final PatchEntity patchEntity : patchContext.getData()) {
338             final PatchEditOperation operation = patchEntity.getOperation();
339             switch (operation) {
340                 case CREATE:
341                     if (withoutError) {
342                         try {
343                             postDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity.getTargetNode(),
344                                     patchEntity.getNode(), schemaContext);
345                             editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), true, null));
346                         } catch (final RestconfDocumentedException e) {
347                             LOG.error("Error call http Patch operation {} on target {}",
348                                     operation,
349                                     patchEntity.getTargetNode().toString());
350
351                             editErrors = new ArrayList<>();
352                             editErrors.addAll(e.getErrors());
353                             editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), false, editErrors));
354                             withoutError = false;
355                         }
356                     }
357                     break;
358                 case REPLACE:
359                     if (withoutError) {
360                         try {
361                             putDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity
362                                     .getTargetNode(), patchEntity.getNode(), schemaContext);
363                             editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), true, null));
364                         } catch (final RestconfDocumentedException e) {
365                             LOG.error("Error call http Patch operation {} on target {}",
366                                     operation,
367                                     patchEntity.getTargetNode().toString());
368
369                             editErrors = new ArrayList<>();
370                             editErrors.addAll(e.getErrors());
371                             editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), false, editErrors));
372                             withoutError = false;
373                         }
374                     }
375                     break;
376                 case DELETE:
377                     if (withoutError) {
378                         try {
379                             deleteDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity
380                                     .getTargetNode());
381                             editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), true, null));
382                         } catch (final RestconfDocumentedException e) {
383                             LOG.error("Error call http Patch operation {} on target {}",
384                                     operation,
385                                     patchEntity.getTargetNode().toString());
386
387                             editErrors = new ArrayList<>();
388                             editErrors.addAll(e.getErrors());
389                             editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), false, editErrors));
390                             withoutError = false;
391                         }
392                     }
393                     break;
394                 case REMOVE:
395                     if (withoutError) {
396                         try {
397                             deleteDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity
398                                     .getTargetNode());
399                             editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), true, null));
400                         } catch (final RestconfDocumentedException e) {
401                             LOG.error("Error call http Patch operation {} on target {}",
402                                     operation,
403                                     patchEntity.getTargetNode().toString());
404
405                             editErrors = new ArrayList<>();
406                             editErrors.addAll(e.getErrors());
407                             editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), false, editErrors));
408                             withoutError = false;
409                         }
410                     }
411                     break;
412                 case MERGE:
413                     if (withoutError) {
414                         try {
415                             mergeDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity.getTargetNode(),
416                                     patchEntity.getNode(), schemaContext);
417                             editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), true, null));
418                         } catch (final RestconfDocumentedException e) {
419                             LOG.error("Error call http Patch operation {} on target {}",
420                                     operation,
421                                     patchEntity.getTargetNode().toString());
422
423                             editErrors = new ArrayList<>();
424                             editErrors.addAll(e.getErrors());
425                             editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), false, editErrors));
426                             withoutError = false;
427                         }
428                     }
429                     break;
430                 default:
431                     LOG.error("Unsupported http Patch operation {} on target {}",
432                             operation,
433                             patchEntity.getTargetNode().toString());
434                     break;
435             }
436         }
437
438         // if errors then cancel transaction and return error status
439         if (!withoutError) {
440             patchTransaction.cancel();
441             return new PatchStatusContext(patchContext.getPatchId(), ImmutableList.copyOf(editCollection), false, null);
442         }
443
444         // if no errors commit transaction
445         final CountDownLatch waiter = new CountDownLatch(1);
446         final CheckedFuture<Void, TransactionCommitFailedException> future = patchTransaction.submit();
447         final PatchStatusContextHelper status = new PatchStatusContextHelper();
448
449         Futures.addCallback(future, new FutureCallback<Void>() {
450             @Override
451             public void onSuccess(@Nullable final Void result) {
452                 status.setStatus(new PatchStatusContext(patchContext.getPatchId(), ImmutableList.copyOf(editCollection),
453                         true, null));
454                 waiter.countDown();
455             }
456
457             @Override
458             public void onFailure(final Throwable throwable) {
459                 // if commit failed it is global error
460                 LOG.error("Http Patch {} transaction commit has failed", patchContext.getPatchId());
461                 status.setStatus(new PatchStatusContext(patchContext.getPatchId(), ImmutableList.copyOf(editCollection),
462                         false, ImmutableList.of(
463                         new RestconfError(ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED, throwable.getMessage()))));
464                 waiter.countDown();
465             }
466         });
467
468         waiter.await();
469         return status.getStatus();
470     }
471
472     // POST configuration
473     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataPost(
474             final SchemaContext globalSchema, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload,
475             final String insert, final String point) {
476         checkPreconditions();
477         return postDataViaTransaction(this.domDataBroker.newReadWriteTransaction(), CONFIGURATION, path, payload,
478                 globalSchema, insert, point);
479     }
480
481     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataPost(
482             final DOMMountPoint mountPoint, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload,
483             final String insert, final String point) {
484         final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
485         if (domDataBrokerService.isPresent()) {
486             return postDataViaTransaction(domDataBrokerService.get().newReadWriteTransaction(), CONFIGURATION, path,
487                     payload, mountPoint.getSchemaContext(), insert, point);
488         }
489         final String errMsg = "DOM data broker service isn't available for mount point " + path;
490         LOG.warn(errMsg);
491         throw new RestconfDocumentedException(errMsg);
492     }
493
494     // DELETE configuration
495     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataDelete(
496             final YangInstanceIdentifier path) {
497         checkPreconditions();
498         return deleteDataViaTransaction(this.domDataBroker.newReadWriteTransaction(), CONFIGURATION, path);
499     }
500
501     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataDelete(
502             final DOMMountPoint mountPoint, final YangInstanceIdentifier path) {
503         final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
504         if (domDataBrokerService.isPresent()) {
505             return deleteDataViaTransaction(domDataBrokerService.get().newReadWriteTransaction(), CONFIGURATION, path);
506         }
507         final String errMsg = "DOM data broker service isn't available for mount point " + path;
508         LOG.warn(errMsg);
509         throw new RestconfDocumentedException(errMsg);
510     }
511
512     // RPC
513     public CheckedFuture<DOMRpcResult, DOMRpcException> invokeRpc(final SchemaPath type,
514                                                                   final NormalizedNode<?, ?> input) {
515         checkPreconditions();
516         if (this.rpcService == null) {
517             throw new RestconfDocumentedException(Status.SERVICE_UNAVAILABLE);
518         }
519         LOG.trace("Invoke RPC {} with input: {}", type, input);
520         return this.rpcService.invokeRpc(type, input);
521     }
522
523     public void registerToListenDataChanges(final LogicalDatastoreType datastore, final DataChangeScope scope,
524             final ListenerAdapter listener) {
525         checkPreconditions();
526
527         if (listener.isListening()) {
528             return;
529         }
530
531         final YangInstanceIdentifier path = listener.getPath();
532         DOMDataTreeChangeService changeService = (DOMDataTreeChangeService)
533                                     this.domDataBroker.getSupportedExtensions().get(DOMDataTreeChangeService.class);
534         if (changeService == null) {
535             throw new UnsupportedOperationException("DOMDataBroker does not support the DOMDataTreeChangeService"
536                                                         + this.domDataBroker);
537         }
538         DOMDataTreeIdentifier root = new DOMDataTreeIdentifier(datastore, path);
539         ListenerRegistration<ListenerAdapter> registration =
540                                     changeService.registerDataTreeChangeListener(root, listener);
541         listener.setRegistration(registration);
542     }
543
544     private NormalizedNode<?, ?> readDataViaTransaction(final DOMDataReadTransaction transaction,
545             final LogicalDatastoreType datastore, final YangInstanceIdentifier path) {
546         return readDataViaTransaction(transaction, datastore, path, null);
547     }
548
549     private NormalizedNode<?, ?> readDataViaTransaction(final DOMDataReadTransaction transaction,
550             final LogicalDatastoreType datastore, final YangInstanceIdentifier path, final String withDefa) {
551         LOG.trace("Read {} via Restconf: {}", datastore.name(), path);
552
553         try {
554             final Optional<NormalizedNode<?, ?>> optional = transaction.read(datastore, path).checkedGet();
555             return !optional.isPresent() ? null : withDefa == null ? optional.get() :
556                 prepareDataByParamWithDef(optional.get(), path, withDefa);
557         } catch (ReadFailedException e) {
558             LOG.warn("Error reading {} from datastore {}", path, datastore.name(), e);
559             for (final RpcError error : e.getErrorList()) {
560                 if (error.getErrorType() == RpcError.ErrorType.TRANSPORT
561                         && error.getTag().equals(ErrorTag.RESOURCE_DENIED.getTagValue())) {
562                     throw new RestconfDocumentedException(
563                             error.getMessage(),
564                             ErrorType.TRANSPORT,
565                             ErrorTag.RESOURCE_DENIED_TRANSPORT);
566                 }
567             }
568             throw new RestconfDocumentedException("Error reading data.", e, e.getErrorList());
569         }
570     }
571
572     private NormalizedNode<?, ?> prepareDataByParamWithDef(final NormalizedNode<?, ?> result,
573             final YangInstanceIdentifier path, final String withDefa) {
574         boolean trim;
575         switch (withDefa) {
576             case "trim":
577                 trim = true;
578                 break;
579             case "explicit":
580                 trim = false;
581                 break;
582             default:
583                 throw new RestconfDocumentedException("Bad value used with with-defaults parameter : " + withDefa);
584         }
585
586         final SchemaContext ctx = ControllerContext.getInstance().getGlobalSchema();
587         final DataSchemaContextTree baseSchemaCtxTree = DataSchemaContextTree.from(ctx);
588         final DataSchemaNode baseSchemaNode = baseSchemaCtxTree.getChild(path).getDataSchemaNode();
589         if (result instanceof ContainerNode) {
590             final DataContainerNodeAttrBuilder<NodeIdentifier, ContainerNode> builder =
591                     Builders.containerBuilder((ContainerSchemaNode) baseSchemaNode);
592             buildCont(builder, (ContainerNode) result, baseSchemaCtxTree, path, trim);
593             return builder.build();
594         }
595
596         final DataContainerNodeAttrBuilder<NodeIdentifierWithPredicates, MapEntryNode> builder =
597                 Builders.mapEntryBuilder((ListSchemaNode) baseSchemaNode);
598         buildMapEntryBuilder(builder, (MapEntryNode) result, baseSchemaCtxTree, path, trim,
599             ((ListSchemaNode) baseSchemaNode).getKeyDefinition());
600         return builder.build();
601     }
602
603     private void buildMapEntryBuilder(
604             final DataContainerNodeAttrBuilder<NodeIdentifierWithPredicates, MapEntryNode> builder,
605             final MapEntryNode result, final DataSchemaContextTree baseSchemaCtxTree,
606             final YangInstanceIdentifier actualPath, final boolean trim, final List<QName> keys) {
607         for (final DataContainerChild<? extends PathArgument, ?> child : result.getValue()) {
608             final YangInstanceIdentifier path = actualPath.node(child.getIdentifier());
609             final DataSchemaNode childSchema = baseSchemaCtxTree.getChild(path).getDataSchemaNode();
610             if (child instanceof ContainerNode) {
611                 final DataContainerNodeAttrBuilder<NodeIdentifier, ContainerNode> childBuilder =
612                         Builders.containerBuilder((ContainerSchemaNode) childSchema);
613                 buildCont(childBuilder, (ContainerNode) child, baseSchemaCtxTree, path, trim);
614                 builder.withChild(childBuilder.build());
615             } else if (child instanceof MapNode) {
616                 final CollectionNodeBuilder<MapEntryNode, MapNode> childBuilder =
617                         Builders.mapBuilder((ListSchemaNode) childSchema);
618                 buildList(childBuilder, (MapNode) child, baseSchemaCtxTree, path, trim,
619                         ((ListSchemaNode) childSchema).getKeyDefinition());
620                 builder.withChild(childBuilder.build());
621             } else if (child instanceof LeafNode) {
622                 final String defaultVal = ((LeafSchemaNode) childSchema).getDefault();
623                 final String nodeVal = ((LeafNode<String>) child).getValue();
624                 final NormalizedNodeAttrBuilder<NodeIdentifier, Object, LeafNode<Object>> leafBuilder =
625                         Builders.leafBuilder((LeafSchemaNode) childSchema);
626                 if (keys.contains(child.getNodeType())) {
627                     leafBuilder.withValue(((LeafNode<?>) child).getValue());
628                     builder.withChild(leafBuilder.build());
629                 } else {
630                     if (trim) {
631                         if (defaultVal == null || !defaultVal.equals(nodeVal)) {
632                             leafBuilder.withValue(((LeafNode<?>) child).getValue());
633                             builder.withChild(leafBuilder.build());
634                         }
635                     } else {
636                         if (defaultVal != null && defaultVal.equals(nodeVal)) {
637                             leafBuilder.withValue(((LeafNode<?>) child).getValue());
638                             builder.withChild(leafBuilder.build());
639                         }
640                     }
641                 }
642             }
643         }
644     }
645
646     private void buildList(final CollectionNodeBuilder<MapEntryNode, MapNode> builder, final MapNode result,
647             final DataSchemaContextTree baseSchemaCtxTree, final YangInstanceIdentifier path, final boolean trim,
648             final List<QName> keys) {
649         for (final MapEntryNode mapEntryNode : result.getValue()) {
650             final YangInstanceIdentifier actualNode = path.node(mapEntryNode.getIdentifier());
651             final DataSchemaNode childSchema = baseSchemaCtxTree.getChild(actualNode).getDataSchemaNode();
652             final DataContainerNodeAttrBuilder<NodeIdentifierWithPredicates, MapEntryNode> mapEntryBuilder =
653                     Builders.mapEntryBuilder((ListSchemaNode) childSchema);
654             buildMapEntryBuilder(mapEntryBuilder, mapEntryNode, baseSchemaCtxTree, actualNode, trim, keys);
655             builder.withChild(mapEntryBuilder.build());
656         }
657     }
658
659     private void buildCont(final DataContainerNodeAttrBuilder<NodeIdentifier, ContainerNode> builder,
660             final ContainerNode result, final DataSchemaContextTree baseSchemaCtxTree,
661             final YangInstanceIdentifier actualPath, final boolean trim) {
662         for (final DataContainerChild<? extends PathArgument, ?> child : result.getValue()) {
663             final YangInstanceIdentifier path = actualPath.node(child.getIdentifier());
664             final DataSchemaNode childSchema = baseSchemaCtxTree.getChild(path).getDataSchemaNode();
665             if (child instanceof ContainerNode) {
666                 final DataContainerNodeAttrBuilder<NodeIdentifier, ContainerNode> builderChild =
667                         Builders.containerBuilder((ContainerSchemaNode) childSchema);
668                 buildCont(builderChild, result, baseSchemaCtxTree, actualPath, trim);
669                 builder.withChild(builderChild.build());
670             } else if (child instanceof MapNode) {
671                 final CollectionNodeBuilder<MapEntryNode, MapNode> childBuilder =
672                         Builders.mapBuilder((ListSchemaNode) childSchema);
673                 buildList(childBuilder, (MapNode) child, baseSchemaCtxTree, path, trim,
674                         ((ListSchemaNode) childSchema).getKeyDefinition());
675                 builder.withChild(childBuilder.build());
676             } else if (child instanceof LeafNode) {
677                 final String defaultVal = ((LeafSchemaNode) childSchema).getDefault();
678                 final String nodeVal = ((LeafNode<String>) child).getValue();
679                 final NormalizedNodeAttrBuilder<NodeIdentifier, Object, LeafNode<Object>> leafBuilder =
680                         Builders.leafBuilder((LeafSchemaNode) childSchema);
681                 if (trim) {
682                     if (defaultVal == null || !defaultVal.equals(nodeVal)) {
683                         leafBuilder.withValue(((LeafNode<?>) child).getValue());
684                         builder.withChild(leafBuilder.build());
685                     }
686                 } else {
687                     if (defaultVal != null && defaultVal.equals(nodeVal)) {
688                         leafBuilder.withValue(((LeafNode<?>) child).getValue());
689                         builder.withChild(leafBuilder.build());
690                     }
691                 }
692             }
693         }
694     }
695
696     /**
697      * POST data and submit transaction {@link DOMDataReadWriteTransaction}.
698      */
699     private CheckedFuture<Void, TransactionCommitFailedException> postDataViaTransaction(
700             final DOMDataReadWriteTransaction rwTransaction, final LogicalDatastoreType datastore,
701             final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext,
702             final String insert, final String point) {
703         LOG.trace("POST {} via Restconf: {} with payload {}", datastore.name(), path, payload);
704         postData(rwTransaction, datastore, path, payload, schemaContext, insert, point);
705         return rwTransaction.submit();
706     }
707
708     /**
709      * POST data and do NOT submit transaction {@link DOMDataReadWriteTransaction}.
710      */
711     private void postDataWithinTransaction(
712             final DOMDataReadWriteTransaction rwTransaction, final LogicalDatastoreType datastore,
713             final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext) {
714         LOG.trace("POST {} within Restconf Patch: {} with payload {}", datastore.name(), path, payload);
715         postData(rwTransaction, datastore, path, payload, schemaContext, null, null);
716     }
717
718     private void postData(final DOMDataReadWriteTransaction rwTransaction, final LogicalDatastoreType datastore,
719                           final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload,
720             final SchemaContext schemaContext, final String insert, final String point) {
721         if (insert == null) {
722             makeNormalPost(rwTransaction, datastore, path, payload, schemaContext);
723             return;
724         }
725
726         final DataSchemaNode schemaNode = checkListAndOrderedType(schemaContext, path);
727         checkItemDoesNotExists(rwTransaction, datastore, path);
728         switch (insert) {
729             case "first":
730                 if (schemaNode instanceof ListSchemaNode) {
731                     final OrderedMapNode readList =
732                             (OrderedMapNode) this.readConfigurationData(path.getParent().getParent());
733                     if (readList == null || readList.getValue().isEmpty()) {
734                         simplePostPut(rwTransaction, datastore, path, payload, schemaContext);
735                     } else {
736                         rwTransaction.delete(datastore, path.getParent().getParent());
737                         simplePostPut(rwTransaction, datastore, path, payload, schemaContext);
738                         makeNormalPost(rwTransaction, datastore, path.getParent().getParent(), readList,
739                             schemaContext);
740                     }
741                 } else {
742                     final OrderedLeafSetNode<?> readLeafList =
743                             (OrderedLeafSetNode<?>) readConfigurationData(path.getParent());
744                     if (readLeafList == null || readLeafList.getValue().isEmpty()) {
745                         simplePostPut(rwTransaction, datastore, path, payload, schemaContext);
746                     } else {
747                         rwTransaction.delete(datastore, path.getParent());
748                         simplePostPut(rwTransaction, datastore, path, payload, schemaContext);
749                         makeNormalPost(rwTransaction, datastore, path.getParent().getParent(), readLeafList,
750                             schemaContext);
751                     }
752                 }
753                 break;
754             case "last":
755                 simplePostPut(rwTransaction, datastore, path, payload, schemaContext);
756                 break;
757             case "before":
758                 if (schemaNode instanceof ListSchemaNode) {
759                     final OrderedMapNode readList =
760                             (OrderedMapNode) this.readConfigurationData(path.getParent().getParent());
761                     if (readList == null || readList.getValue().isEmpty()) {
762                         simplePostPut(rwTransaction, datastore, path, payload, schemaContext);
763                     } else {
764                         insertWithPointListPost(rwTransaction, datastore, path, payload, schemaContext, point,
765                             readList,
766                             true);
767                     }
768                 } else {
769                     final OrderedLeafSetNode<?> readLeafList =
770                             (OrderedLeafSetNode<?>) readConfigurationData(path.getParent());
771                     if (readLeafList == null || readLeafList.getValue().isEmpty()) {
772                         simplePostPut(rwTransaction, datastore, path, payload, schemaContext);
773                     } else {
774                         insertWithPointLeafListPost(rwTransaction, datastore, path, payload, schemaContext, point,
775                             readLeafList, true);
776                     }
777                 }
778                 break;
779             case "after":
780                 if (schemaNode instanceof ListSchemaNode) {
781                     final OrderedMapNode readList =
782                             (OrderedMapNode) this.readConfigurationData(path.getParent().getParent());
783                     if (readList == null || readList.getValue().isEmpty()) {
784                         simplePostPut(rwTransaction, datastore, path, payload, schemaContext);
785                     } else {
786                         insertWithPointListPost(rwTransaction, datastore, path, payload, schemaContext, point,
787                             readList,
788                             false);
789                     }
790                 } else {
791                     final OrderedLeafSetNode<?> readLeafList =
792                             (OrderedLeafSetNode<?>) readConfigurationData(path.getParent());
793                     if (readLeafList == null || readLeafList.getValue().isEmpty()) {
794                         simplePostPut(rwTransaction, datastore, path, payload, schemaContext);
795                     } else {
796                         insertWithPointLeafListPost(rwTransaction, datastore, path, payload, schemaContext, point,
797                             readLeafList, false);
798                     }
799                 }
800                 break;
801             default:
802                 throw new RestconfDocumentedException(
803                     "Used bad value of insert parameter. Possible values are first, last, before or after, "
804                             + "but was: " + insert);
805         }
806     }
807
808     private static void insertWithPointLeafListPost(final DOMDataReadWriteTransaction rwTransaction,
809             final LogicalDatastoreType datastore, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload,
810             final SchemaContext schemaContext, final String point, final OrderedLeafSetNode<?> readLeafList,
811             final boolean before) {
812         rwTransaction.delete(datastore, path.getParent().getParent());
813         final InstanceIdentifierContext<?> instanceIdentifier =
814                 ControllerContext.getInstance().toInstanceIdentifier(point);
815         int lastItemPosition = 0;
816         for (final LeafSetEntryNode<?> nodeChild : readLeafList.getValue()) {
817             if (nodeChild.getIdentifier().equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
818                 break;
819             }
820             lastItemPosition++;
821         }
822         if (!before) {
823             lastItemPosition++;
824         }
825         int lastInsertedPosition = 0;
826         final NormalizedNode<?, ?> emptySubtree =
827                 ImmutableNodes.fromInstanceId(schemaContext, path.getParent().getParent());
828         rwTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
829         for (final LeafSetEntryNode<?> nodeChild : readLeafList.getValue()) {
830             if (lastInsertedPosition == lastItemPosition) {
831                 checkItemDoesNotExists(rwTransaction, datastore, path);
832                 simplePostPut(rwTransaction, datastore, path, payload, schemaContext);
833             }
834             final YangInstanceIdentifier childPath = path.getParent().getParent().node(nodeChild.getIdentifier());
835             checkItemDoesNotExists(rwTransaction, datastore, childPath);
836             rwTransaction.put(datastore, childPath, nodeChild);
837             lastInsertedPosition++;
838         }
839     }
840
841     private static void insertWithPointListPost(final DOMDataReadWriteTransaction rwTransaction,
842             final LogicalDatastoreType datastore,
843             final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext,
844             final String point, final MapNode readList, final boolean before) {
845         rwTransaction.delete(datastore, path.getParent().getParent());
846         final InstanceIdentifierContext<?> instanceIdentifier =
847                 ControllerContext.getInstance().toInstanceIdentifier(point);
848         int lastItemPosition = 0;
849         for (final MapEntryNode mapEntryNode : readList.getValue()) {
850             if (mapEntryNode.getIdentifier()
851                     .equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
852                 break;
853             }
854             lastItemPosition++;
855         }
856         if (!before) {
857             lastItemPosition++;
858         }
859         int lastInsertedPosition = 0;
860         final NormalizedNode<?, ?> emptySubtree =
861                 ImmutableNodes.fromInstanceId(schemaContext, path.getParent().getParent());
862         rwTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
863         for (final MapEntryNode mapEntryNode : readList.getValue()) {
864             if (lastInsertedPosition == lastItemPosition) {
865                 checkItemDoesNotExists(rwTransaction, datastore, path);
866                 simplePostPut(rwTransaction, datastore, path, payload, schemaContext);
867             }
868             final YangInstanceIdentifier childPath = path.getParent().getParent().node(mapEntryNode.getIdentifier());
869             checkItemDoesNotExists(rwTransaction, datastore, childPath);
870             rwTransaction.put(datastore, childPath, mapEntryNode);
871             lastInsertedPosition++;
872         }
873     }
874
875     private static DataSchemaNode checkListAndOrderedType(final SchemaContext ctx, final YangInstanceIdentifier path) {
876         final YangInstanceIdentifier parent = path.getParent();
877         final DataSchemaContextNode<?> node = DataSchemaContextTree.from(ctx).getChild(parent);
878         final DataSchemaNode dataSchemaNode = node.getDataSchemaNode();
879
880         if (dataSchemaNode instanceof ListSchemaNode) {
881             if (!((ListSchemaNode) dataSchemaNode).isUserOrdered()) {
882                 throw new RestconfDocumentedException("Insert parameter can be used only with ordered-by user list.");
883             }
884             return dataSchemaNode;
885         }
886         if (dataSchemaNode instanceof LeafListSchemaNode) {
887             if (!((LeafListSchemaNode) dataSchemaNode).isUserOrdered()) {
888                 throw new RestconfDocumentedException(
889                         "Insert parameter can be used only with ordered-by user leaf-list.");
890             }
891             return dataSchemaNode;
892         }
893         throw new RestconfDocumentedException("Insert parameter can be used only with list or leaf-list");
894     }
895
896     private static void makeNormalPost(final DOMDataReadWriteTransaction rwTransaction,
897             final LogicalDatastoreType datastore, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload,
898             final SchemaContext schemaContext) {
899         final Collection<? extends NormalizedNode<?, ?>> children;
900         if (payload instanceof MapNode) {
901             children = ((MapNode) payload).getValue();
902         } else if (payload instanceof LeafSetNode) {
903             children = ((LeafSetNode<?>) payload).getValue();
904         } else {
905             simplePostPut(rwTransaction, datastore, path, payload, schemaContext);
906             return;
907         }
908
909         final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
910         if (children.isEmpty()) {
911             rwTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
912             ensureParentsByMerge(datastore, path, rwTransaction, schemaContext);
913             return;
914         }
915
916         // Kick off batch existence check first...
917         final BatchedExistenceCheck check = BatchedExistenceCheck.start(rwTransaction, datastore, path, children);
918
919         // ... now enqueue modifications. This relies on proper ordering of requests, i.e. these will not affect the
920         // result of the existence checks...
921         rwTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
922         ensureParentsByMerge(datastore, path, rwTransaction, schemaContext);
923         for (final NormalizedNode<?, ?> child : children) {
924             // FIXME: we really want a create(YangInstanceIdentifier, NormalizedNode) method in the transaction,
925             //        as that would allow us to skip the existence checks
926             rwTransaction.put(datastore, path.node(child.getIdentifier()), child);
927         }
928
929         // ... finally collect existence checks and abort the transaction if any of them failed.
930         final Entry<YangInstanceIdentifier, ReadFailedException> failure;
931         try {
932             failure = check.getFailure();
933         } catch (InterruptedException e) {
934             rwTransaction.cancel();
935             throw new RestconfDocumentedException("Could not determine the existence of path " + path, e);
936         }
937
938         if (failure != null) {
939             rwTransaction.cancel();
940             final ReadFailedException e = failure.getValue();
941             if (e == null) {
942                 throw new RestconfDocumentedException("Data already exists for path: " + failure.getKey(),
943                     ErrorType.PROTOCOL, ErrorTag.DATA_EXISTS);
944             }
945
946             throw new RestconfDocumentedException("Could not determine the existence of path " + failure.getKey(), e,
947                 e.getErrorList());
948         }
949     }
950
951     private static void simplePostPut(final DOMDataReadWriteTransaction rwTransaction,
952             final LogicalDatastoreType datastore, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload,
953             final SchemaContext schemaContext) {
954         checkItemDoesNotExists(rwTransaction, datastore, path);
955         ensureParentsByMerge(datastore, path, rwTransaction, schemaContext);
956         rwTransaction.put(datastore, path, payload);
957     }
958
959     private static boolean doesItemExist(final DOMDataReadWriteTransaction rwTransaction,
960             final LogicalDatastoreType store, final YangInstanceIdentifier path) {
961         try {
962             return rwTransaction.exists(store, path).checkedGet();
963         } catch (ReadFailedException e) {
964             rwTransaction.cancel();
965             throw new RestconfDocumentedException("Could not determine the existence of path " + path,
966                     e, e.getErrorList());
967         }
968     }
969
970     /**
971      * Check if item already exists. Throws error if it does NOT already exist.
972      * @param rwTransaction Current transaction
973      * @param store Used datastore
974      * @param path Path to item to verify its existence
975      */
976     private static void checkItemExists(final DOMDataReadWriteTransaction rwTransaction,
977             final LogicalDatastoreType store, final YangInstanceIdentifier path) {
978         if (!doesItemExist(rwTransaction, store, path)) {
979             final String errMsg = "Operation via Restconf was not executed because data does not exist";
980             LOG.trace("{}:{}", errMsg, path);
981             rwTransaction.cancel();
982             throw new RestconfDocumentedException("Data does not exist for path: " + path, ErrorType.PROTOCOL,
983                     ErrorTag.DATA_MISSING);
984         }
985     }
986
987     /**
988      * Check if item does NOT already exist. Throws error if it already exists.
989      * @param rwTransaction Current transaction
990      * @param store Used datastore
991      * @param path Path to item to verify its existence
992      */
993     private static void checkItemDoesNotExists(final DOMDataReadWriteTransaction rwTransaction,
994             final LogicalDatastoreType store, final YangInstanceIdentifier path) {
995         if (doesItemExist(rwTransaction, store, path)) {
996             final String errMsg = "Operation via Restconf was not executed because data already exists";
997             LOG.trace("{}:{}", errMsg, path);
998             rwTransaction.cancel();
999             throw new RestconfDocumentedException("Data already exists for path: " + path, ErrorType.PROTOCOL,
1000                     ErrorTag.DATA_EXISTS);
1001         }
1002     }
1003
1004     /**
1005      * PUT data and submit {@link DOMDataReadWriteTransaction}.
1006      *
1007      * @param point
1008      *            point
1009      * @param insert
1010      *            insert
1011      */
1012     private CheckedFuture<Void, TransactionCommitFailedException> putDataViaTransaction(
1013             final DOMDataReadWriteTransaction readWriteTransaction, final LogicalDatastoreType datastore,
1014             final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext,
1015             final String insert, final String point) {
1016         LOG.trace("Put {} via Restconf: {} with payload {}", datastore.name(), path, payload);
1017         putData(readWriteTransaction, datastore, path, payload, schemaContext, insert, point);
1018         return readWriteTransaction.submit();
1019     }
1020
1021     /**
1022      * PUT data and do NOT submit {@link DOMDataReadWriteTransaction}.
1023      */
1024     private void putDataWithinTransaction(
1025             final DOMDataReadWriteTransaction writeTransaction, final LogicalDatastoreType datastore,
1026             final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext) {
1027         LOG.trace("Put {} within Restconf Patch: {} with payload {}", datastore.name(), path, payload);
1028         putData(writeTransaction, datastore, path, payload, schemaContext, null, null);
1029     }
1030
1031     // FIXME: This is doing correct put for container and list children, not sure if this will work for choice case
1032     private void putData(final DOMDataReadWriteTransaction rwTransaction, final LogicalDatastoreType datastore,
1033             final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext,
1034             final String insert, final String point) {
1035         if (insert == null) {
1036             makePut(rwTransaction, datastore, path, payload, schemaContext);
1037             return;
1038         }
1039
1040         final DataSchemaNode schemaNode = checkListAndOrderedType(schemaContext, path);
1041         checkItemDoesNotExists(rwTransaction, datastore, path);
1042         switch (insert) {
1043             case "first":
1044                 if (schemaNode instanceof ListSchemaNode) {
1045                     final OrderedMapNode readList =
1046                             (OrderedMapNode) this.readConfigurationData(path.getParent());
1047                     if (readList == null || readList.getValue().isEmpty()) {
1048                         simplePut(datastore, path, rwTransaction, schemaContext, payload);
1049                     } else {
1050                         rwTransaction.delete(datastore, path.getParent());
1051                         simplePut(datastore, path, rwTransaction, schemaContext, payload);
1052                         makePut(rwTransaction, datastore, path.getParent(), readList, schemaContext);
1053                     }
1054                 } else {
1055                     final OrderedLeafSetNode<?> readLeafList =
1056                             (OrderedLeafSetNode<?>) readConfigurationData(path.getParent());
1057                     if (readLeafList == null || readLeafList.getValue().isEmpty()) {
1058                         simplePut(datastore, path, rwTransaction, schemaContext, payload);
1059                     } else {
1060                         rwTransaction.delete(datastore, path.getParent());
1061                         simplePut(datastore, path, rwTransaction, schemaContext, payload);
1062                         makePut(rwTransaction, datastore, path.getParent(), readLeafList,
1063                             schemaContext);
1064                     }
1065                 }
1066                 break;
1067             case "last":
1068                 simplePut(datastore, path, rwTransaction, schemaContext, payload);
1069                 break;
1070             case "before":
1071                 if (schemaNode instanceof ListSchemaNode) {
1072                     final OrderedMapNode readList =
1073                             (OrderedMapNode) this.readConfigurationData(path.getParent());
1074                     if (readList == null || readList.getValue().isEmpty()) {
1075                         simplePut(datastore, path, rwTransaction, schemaContext, payload);
1076                     } else {
1077                         insertWithPointListPut(rwTransaction, datastore, path, payload, schemaContext, point,
1078                             readList, true);
1079                     }
1080                 } else {
1081                     final OrderedLeafSetNode<?> readLeafList =
1082                             (OrderedLeafSetNode<?>) readConfigurationData(path.getParent());
1083                     if (readLeafList == null || readLeafList.getValue().isEmpty()) {
1084                         simplePut(datastore, path, rwTransaction, schemaContext, payload);
1085                     } else {
1086                         insertWithPointLeafListPut(rwTransaction, datastore, path, payload, schemaContext, point,
1087                             readLeafList, true);
1088                     }
1089                 }
1090                 break;
1091             case "after":
1092                 if (schemaNode instanceof ListSchemaNode) {
1093                     final OrderedMapNode readList =
1094                             (OrderedMapNode) this.readConfigurationData(path.getParent());
1095                     if (readList == null || readList.getValue().isEmpty()) {
1096                         simplePut(datastore, path, rwTransaction, schemaContext, payload);
1097                     } else {
1098                         insertWithPointListPut(rwTransaction, datastore, path, payload, schemaContext, point,
1099                             readList, false);
1100                     }
1101                 } else {
1102                     final OrderedLeafSetNode<?> readLeafList =
1103                             (OrderedLeafSetNode<?>) readConfigurationData(path.getParent());
1104                     if (readLeafList == null || readLeafList.getValue().isEmpty()) {
1105                         simplePut(datastore, path, rwTransaction, schemaContext, payload);
1106                     } else {
1107                         insertWithPointLeafListPut(rwTransaction, datastore, path, payload, schemaContext, point,
1108                             readLeafList, false);
1109                     }
1110                 }
1111                 break;
1112             default:
1113                 throw new RestconfDocumentedException(
1114                     "Used bad value of insert parameter. Possible values are first, last, before or after, but was: "
1115                             + insert);
1116         }
1117     }
1118
1119     private static void insertWithPointLeafListPut(final DOMDataWriteTransaction tx,
1120             final LogicalDatastoreType datastore, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload,
1121             final SchemaContext schemaContext, final String point, final OrderedLeafSetNode<?> readLeafList,
1122             final boolean before) {
1123         tx.delete(datastore, path.getParent());
1124         final InstanceIdentifierContext<?> instanceIdentifier =
1125                 ControllerContext.getInstance().toInstanceIdentifier(point);
1126         int index1 = 0;
1127         for (final LeafSetEntryNode<?> nodeChild : readLeafList.getValue()) {
1128             if (nodeChild.getIdentifier().equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
1129                 break;
1130             }
1131             index1++;
1132         }
1133         if (!before) {
1134             index1++;
1135         }
1136         int index2 = 0;
1137         final NormalizedNode<?, ?> emptySubtree =
1138                 ImmutableNodes.fromInstanceId(schemaContext, path.getParent());
1139         tx.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
1140         for (final LeafSetEntryNode<?> nodeChild : readLeafList.getValue()) {
1141             if (index2 == index1) {
1142                 simplePut(datastore, path, tx, schemaContext, payload);
1143             }
1144             final YangInstanceIdentifier childPath = path.getParent().node(nodeChild.getIdentifier());
1145             tx.put(datastore, childPath, nodeChild);
1146             index2++;
1147         }
1148     }
1149
1150     private static void insertWithPointListPut(final DOMDataWriteTransaction tx, final LogicalDatastoreType datastore,
1151             final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext,
1152             final String point, final OrderedMapNode readList, final boolean before) {
1153         tx.delete(datastore, path.getParent());
1154         final InstanceIdentifierContext<?> instanceIdentifier =
1155                 ControllerContext.getInstance().toInstanceIdentifier(point);
1156         int index1 = 0;
1157         for (final MapEntryNode mapEntryNode : readList.getValue()) {
1158             if (mapEntryNode.getIdentifier().equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
1159                 break;
1160             }
1161             index1++;
1162         }
1163         if (!before) {
1164             index1++;
1165         }
1166         int index2 = 0;
1167         final NormalizedNode<?, ?> emptySubtree =
1168                 ImmutableNodes.fromInstanceId(schemaContext, path.getParent());
1169         tx.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
1170         for (final MapEntryNode mapEntryNode : readList.getValue()) {
1171             if (index2 == index1) {
1172                 simplePut(datastore, path, tx, schemaContext, payload);
1173             }
1174             final YangInstanceIdentifier childPath = path.getParent().node(mapEntryNode.getIdentifier());
1175             tx.put(datastore, childPath, mapEntryNode);
1176             index2++;
1177         }
1178     }
1179
1180     private static void makePut(final DOMDataWriteTransaction tx, final LogicalDatastoreType datastore,
1181             final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext) {
1182         if (payload instanceof MapNode) {
1183             final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
1184             tx.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
1185             ensureParentsByMerge(datastore, path, tx, schemaContext);
1186             for (final MapEntryNode child : ((MapNode) payload).getValue()) {
1187                 final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
1188                 tx.put(datastore, childPath, child);
1189             }
1190         } else {
1191             simplePut(datastore, path, tx, schemaContext, payload);
1192         }
1193     }
1194
1195     private static void simplePut(final LogicalDatastoreType datastore, final YangInstanceIdentifier path,
1196             final DOMDataWriteTransaction tx, final SchemaContext schemaContext, final NormalizedNode<?, ?> payload) {
1197         ensureParentsByMerge(datastore, path, tx, schemaContext);
1198         tx.put(datastore, path, payload);
1199     }
1200
1201     private static CheckedFuture<Void, TransactionCommitFailedException> deleteDataViaTransaction(
1202             final DOMDataReadWriteTransaction readWriteTransaction, final LogicalDatastoreType datastore,
1203             final YangInstanceIdentifier path) {
1204         LOG.trace("Delete {} via Restconf: {}", datastore.name(), path);
1205         checkItemExists(readWriteTransaction, datastore, path);
1206         readWriteTransaction.delete(datastore, path);
1207         return readWriteTransaction.submit();
1208     }
1209
1210     private static void deleteDataWithinTransaction(final DOMDataWriteTransaction tx,
1211             final LogicalDatastoreType datastore, final YangInstanceIdentifier path) {
1212         LOG.trace("Delete {} within Restconf Patch: {}", datastore.name(), path);
1213         tx.delete(datastore, path);
1214     }
1215
1216     private static void mergeDataWithinTransaction(final DOMDataWriteTransaction tx,
1217             final LogicalDatastoreType datastore, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload,
1218             final SchemaContext schemaContext) {
1219         LOG.trace("Merge {} within Restconf Patch: {} with payload {}", datastore.name(), path, payload);
1220         ensureParentsByMerge(datastore, path, tx, schemaContext);
1221
1222         // Since YANG Patch provides the option to specify what kind of operation for each edit,
1223         // OpenDaylight should not change it.
1224         tx.merge(datastore, path, payload);
1225     }
1226
1227     public void setDomDataBroker(final DOMDataBroker domDataBroker) {
1228         this.domDataBroker = domDataBroker;
1229     }
1230
1231     public void registerToListenNotification(final NotificationListenerAdapter listener) {
1232         checkPreconditions();
1233
1234         if (listener.isListening()) {
1235             return;
1236         }
1237
1238         final SchemaPath path = listener.getSchemaPath();
1239         final ListenerRegistration<DOMNotificationListener> registration = this.domNotification
1240                 .registerNotificationListener(listener, path);
1241
1242         listener.setRegistration(registration);
1243     }
1244
1245     private static void ensureParentsByMerge(final LogicalDatastoreType store,
1246             final YangInstanceIdentifier normalizedPath, final DOMDataWriteTransaction tx,
1247             final SchemaContext schemaContext) {
1248         final List<PathArgument> normalizedPathWithoutChildArgs = new ArrayList<>();
1249         YangInstanceIdentifier rootNormalizedPath = null;
1250
1251         final Iterator<PathArgument> it = normalizedPath.getPathArguments().iterator();
1252
1253         while (it.hasNext()) {
1254             final PathArgument pathArgument = it.next();
1255             if (rootNormalizedPath == null) {
1256                 rootNormalizedPath = YangInstanceIdentifier.create(pathArgument);
1257             }
1258
1259             if (it.hasNext()) {
1260                 normalizedPathWithoutChildArgs.add(pathArgument);
1261             }
1262         }
1263
1264         if (normalizedPathWithoutChildArgs.isEmpty()) {
1265             return;
1266         }
1267
1268         Preconditions.checkArgument(rootNormalizedPath != null, "Empty path received");
1269
1270         final NormalizedNode<?, ?> parentStructure = ImmutableNodes.fromInstanceId(schemaContext,
1271                 YangInstanceIdentifier.create(normalizedPathWithoutChildArgs));
1272         tx.merge(store, rootNormalizedPath, parentStructure);
1273     }
1274
1275     private static final class PatchStatusContextHelper {
1276         PatchStatusContext status;
1277
1278         public PatchStatusContext getStatus() {
1279             return this.status;
1280         }
1281
1282         public void setStatus(final PatchStatusContext status) {
1283             this.status = status;
1284         }
1285     }
1286 }