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