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