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