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