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