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