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