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