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