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