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