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