Reorganize transactionChainHandler usage.
[netconf.git] / restconf / restconf-nb-rfc8040 / src / main / java / org / opendaylight / restconf / nb / rfc8040 / rests / utils / PostDataTransactionUtil.java
1 /*
2  * Copyright (c) 2016 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.restconf.nb.rfc8040.rests.utils;
9
10 import com.google.common.util.concurrent.FluentFuture;
11 import java.net.URI;
12 import java.util.Optional;
13 import javax.ws.rs.core.Response;
14 import javax.ws.rs.core.Response.Status;
15 import javax.ws.rs.core.UriBuilder;
16 import javax.ws.rs.core.UriInfo;
17 import org.opendaylight.mdsal.common.api.CommitInfo;
18 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
19 import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction;
20 import org.opendaylight.mdsal.dom.api.DOMTransactionChain;
21 import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
22 import org.opendaylight.restconf.common.context.NormalizedNodeContext;
23 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
24 import org.opendaylight.restconf.common.errors.RestconfError;
25 import org.opendaylight.restconf.nb.rfc8040.references.SchemaContextRef;
26 import org.opendaylight.restconf.nb.rfc8040.rests.transactions.TransactionVarsWrapper;
27 import org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserIdentifier;
28 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
29 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
30 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
31 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
32 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
33 import org.opendaylight.yangtools.yang.data.api.schema.OrderedLeafSetNode;
34 import org.opendaylight.yangtools.yang.data.api.schema.OrderedMapNode;
35 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
36 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
37 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
38 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
39
40 /**
41  * Util class to post data to DS.
42  *
43  */
44 public final class PostDataTransactionUtil {
45     private PostDataTransactionUtil() {
46         throw new UnsupportedOperationException("Util class.");
47     }
48
49     /**
50      * Check mount point and prepare variables for post data. Close {@link DOMTransactionChain} inside of object
51      * {@link TransactionVarsWrapper} provided as a parameter.
52      *
53      * @param uriInfo
54      *
55      * @param payload
56      *             data
57      * @param transactionNode
58      *             wrapper for transaction data
59      * @param schemaContextRef
60      *             reference to actual {@link SchemaContext}
61      * @param point
62      *             point
63      * @param insert
64      *             insert
65      * @return {@link Response}
66      */
67     public static Response postData(final UriInfo uriInfo, final NormalizedNodeContext payload,
68             final TransactionVarsWrapper transactionNode, final SchemaContextRef schemaContextRef, final String insert,
69             final String point) {
70         final FluentFuture<? extends CommitInfo> future = submitData(
71                 payload.getInstanceIdentifierContext().getInstanceIdentifier(), payload.getData(),
72                 transactionNode, schemaContextRef.get(), insert, point);
73         final URI location = PostDataTransactionUtil.resolveLocation(uriInfo, transactionNode, schemaContextRef);
74         final ResponseFactory dataFactory = new ResponseFactory(Status.CREATED).location(location);
75         //This method will close transactionChain
76         FutureCallbackTx.addCallback(future, RestconfDataServiceConstant.PostData.POST_TX_TYPE, dataFactory,
77                 transactionNode.getTransactionChain());
78         return dataFactory.build();
79     }
80
81     /**
82      * Post data by type.
83      *
84      * @param path
85      *             path
86      * @param data
87      *             data
88      * @param transactionNode
89      *             wrapper for data to transaction
90      * @param schemaContext
91      *             schema context of data
92      * @param point
93      *             query parameter
94      * @param insert
95      *             query parameter
96      * @return {@link FluentFuture}
97      */
98     private static FluentFuture<? extends CommitInfo> submitData(final YangInstanceIdentifier path,
99             final NormalizedNode<?, ?> data, final TransactionVarsWrapper transactionNode,
100             final SchemaContext schemaContext, final String insert, final String point) {
101         final DOMTransactionChain transactionChain = transactionNode.getTransactionChain();
102         final DOMDataTreeReadWriteTransaction newReadWriteTransaction = transactionChain.newReadWriteTransaction();
103         if (insert == null) {
104             makePost(path, data, schemaContext, transactionChain, newReadWriteTransaction);
105             return newReadWriteTransaction.commit();
106         } else {
107             final DataSchemaNode schemaNode = PutDataTransactionUtil.checkListAndOrderedType(schemaContext, path);
108             switch (insert) {
109                 case "first":
110                     if (schemaNode instanceof ListSchemaNode) {
111                         final NormalizedNode<?, ?> readData = PutDataTransactionUtil.readList(path.getParent(),
112                             schemaContext, transactionNode.getTransactionChainHandler(), schemaNode);
113                         final OrderedMapNode readList = (OrderedMapNode) readData;
114                         if (readList == null || readList.getValue().isEmpty()) {
115                             makePost(path, data, schemaContext, transactionChain,
116                                     newReadWriteTransaction);
117                             return newReadWriteTransaction.commit();
118                         } else {
119                             newReadWriteTransaction.delete(LogicalDatastoreType.CONFIGURATION,
120                                     path.getParent().getParent());
121                             simplePost(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION, path, data,
122                                     schemaContext, transactionChain);
123                             makePost(path, readData, schemaContext, transactionChain,
124                                     newReadWriteTransaction);
125                             return newReadWriteTransaction.commit();
126                         }
127                     } else {
128                         final NormalizedNode<?, ?> readData = PutDataTransactionUtil.readList(path.getParent(),
129                             schemaContext, transactionNode.getTransactionChainHandler(), schemaNode);
130
131                         final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
132                         if (readLeafList == null || readLeafList.getValue().isEmpty()) {
133                             makePost(path, data, schemaContext, transactionChain,
134                                     newReadWriteTransaction);
135                             return newReadWriteTransaction.commit();
136                         } else {
137                             newReadWriteTransaction.delete(LogicalDatastoreType.CONFIGURATION,
138                                     path.getParent().getParent());
139                             simplePost(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION, path, data,
140                                     schemaContext, transactionChain);
141                             makePost(path, readData, schemaContext, transactionChain,
142                                     newReadWriteTransaction);
143                             return newReadWriteTransaction.commit();
144                         }
145                     }
146                 case "last":
147                     makePost(path, data, schemaContext, transactionChain,
148                             newReadWriteTransaction);
149                     return newReadWriteTransaction.commit();
150                 case "before":
151                     if (schemaNode instanceof ListSchemaNode) {
152                         final NormalizedNode<?, ?> readData = PutDataTransactionUtil.readList(path.getParent(),
153                             schemaContext, transactionNode.getTransactionChainHandler(), schemaNode);
154                         final OrderedMapNode readList = (OrderedMapNode) readData;
155                         if (readList == null || readList.getValue().isEmpty()) {
156                             makePost(path, data, schemaContext, transactionChain,
157                                     newReadWriteTransaction);
158                             return newReadWriteTransaction.commit();
159                         } else {
160                             insertWithPointListPost(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION, path,
161                                     data, schemaContext, point, readList, true,
162                                     transactionChain);
163                             return newReadWriteTransaction.commit();
164                         }
165                     } else {
166                         final NormalizedNode<?, ?> readData = PutDataTransactionUtil.readList(path.getParent(),
167                             schemaContext, transactionNode.getTransactionChainHandler(), schemaNode);
168
169                         final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
170                         if (readLeafList == null || readLeafList.getValue().isEmpty()) {
171                             makePost(path, data, schemaContext, transactionChain,
172                                     newReadWriteTransaction);
173                             return newReadWriteTransaction.commit();
174                         } else {
175                             insertWithPointLeafListPost(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION,
176                                     path, data, schemaContext, point, readLeafList, true,
177                                     transactionChain);
178                             return newReadWriteTransaction.commit();
179                         }
180                     }
181                 case "after":
182                     if (schemaNode instanceof ListSchemaNode) {
183                         final NormalizedNode<?, ?> readData = PutDataTransactionUtil.readList(path.getParent(),
184                             schemaContext, transactionNode.getTransactionChainHandler(), schemaNode);
185                         final OrderedMapNode readList = (OrderedMapNode) readData;
186                         if (readList == null || readList.getValue().isEmpty()) {
187                             makePost(path, data, schemaContext, transactionChain,
188                                     newReadWriteTransaction);
189                             return newReadWriteTransaction.commit();
190                         } else {
191                             insertWithPointListPost(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION, path,
192                                     data, schemaContext, point, readList, false,
193                                     transactionChain);
194                             return newReadWriteTransaction.commit();
195                         }
196                     } else {
197                         final NormalizedNode<?, ?> readData = PutDataTransactionUtil.readList(path.getParent(),
198                             schemaContext, transactionNode.getTransactionChainHandler(), schemaNode);
199
200                         final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
201                         if (readLeafList == null || readLeafList.getValue().isEmpty()) {
202                             makePost(path, data, schemaContext, transactionChain,
203                                     newReadWriteTransaction);
204                             return newReadWriteTransaction.commit();
205                         } else {
206                             insertWithPointLeafListPost(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION,
207                                     path, data, schemaContext, point, readLeafList, true,
208                                     transactionChain);
209                             return newReadWriteTransaction.commit();
210                         }
211                     }
212                 default:
213                     throw new RestconfDocumentedException(
214                             "Used bad value of insert parameter. Possible values are first, last, before or after, "
215                                     + "but was: " + insert, RestconfError.ErrorType.PROTOCOL,
216                             RestconfError.ErrorTag.BAD_ATTRIBUTE);
217             }
218         }
219     }
220
221     private static void insertWithPointLeafListPost(final DOMDataTreeReadWriteTransaction rwTransaction,
222             final LogicalDatastoreType datastore, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload,
223             final SchemaContext schemaContext, final String point, final OrderedLeafSetNode<?> readLeafList,
224             final boolean before, final DOMTransactionChain transactionChain) {
225         rwTransaction.delete(datastore, path.getParent().getParent());
226         final InstanceIdentifierContext<?> instanceIdentifier =
227                 ParserIdentifier.toInstanceIdentifier(point, schemaContext, Optional.empty());
228         int lastItemPosition = 0;
229         for (final LeafSetEntryNode<?> nodeChild : readLeafList.getValue()) {
230             if (nodeChild.getIdentifier().equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
231                 break;
232             }
233             lastItemPosition++;
234         }
235         if (!before) {
236             lastItemPosition++;
237         }
238         int lastInsertedPosition = 0;
239         final NormalizedNode<?, ?> emptySubtree =
240                 ImmutableNodes.fromInstanceId(schemaContext, path.getParent().getParent());
241         rwTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
242         for (final LeafSetEntryNode<?> nodeChild : readLeafList.getValue()) {
243             if (lastInsertedPosition == lastItemPosition) {
244                 TransactionUtil.checkItemDoesNotExists(transactionChain, rwTransaction, datastore, path,
245                         RestconfDataServiceConstant.PostData.POST_TX_TYPE);
246                 rwTransaction.put(datastore, path, payload);
247             }
248             final YangInstanceIdentifier childPath = path.getParent().getParent().node(nodeChild.getIdentifier());
249             TransactionUtil.checkItemDoesNotExists(transactionChain, rwTransaction, datastore, childPath,
250                     RestconfDataServiceConstant.PostData.POST_TX_TYPE);
251             rwTransaction.put(datastore, childPath, nodeChild);
252             lastInsertedPosition++;
253         }
254     }
255
256     private static void insertWithPointListPost(final DOMDataTreeReadWriteTransaction rwTransaction,
257             final LogicalDatastoreType datastore, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload,
258             final SchemaContext schemaContext, final String point, final MapNode readList, final boolean before,
259             final DOMTransactionChain transactionChain) {
260         rwTransaction.delete(datastore, path.getParent().getParent());
261         final InstanceIdentifierContext<?> instanceIdentifier =
262                 ParserIdentifier.toInstanceIdentifier(point, schemaContext, Optional.empty());
263         int lastItemPosition = 0;
264         for (final MapEntryNode mapEntryNode : readList.getValue()) {
265             if (mapEntryNode.getIdentifier().equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
266                 break;
267             }
268             lastItemPosition++;
269         }
270         if (!before) {
271             lastItemPosition++;
272         }
273         int lastInsertedPosition = 0;
274         final NormalizedNode<?, ?> emptySubtree =
275                 ImmutableNodes.fromInstanceId(schemaContext, path.getParent().getParent());
276         rwTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
277         for (final MapEntryNode mapEntryNode : readList.getValue()) {
278             if (lastInsertedPosition == lastItemPosition) {
279                 TransactionUtil.checkItemDoesNotExists(transactionChain, rwTransaction, datastore, path,
280                         RestconfDataServiceConstant.PostData.POST_TX_TYPE);
281                 rwTransaction.put(datastore, path, payload);
282             }
283             final YangInstanceIdentifier childPath = path.getParent().getParent().node(mapEntryNode.getIdentifier());
284             TransactionUtil.checkItemDoesNotExists(transactionChain, rwTransaction, datastore, childPath,
285                     RestconfDataServiceConstant.PostData.POST_TX_TYPE);
286             rwTransaction.put(datastore, childPath, mapEntryNode);
287             lastInsertedPosition++;
288         }
289     }
290
291     private static void makePost(final YangInstanceIdentifier path, final NormalizedNode<?, ?> data,
292             final SchemaContext schemaContext, final DOMTransactionChain transactionChain,
293             final DOMDataTreeReadWriteTransaction transaction) {
294         if (data instanceof MapNode) {
295             boolean merge = false;
296             for (final MapEntryNode child : ((MapNode) data).getValue()) {
297                 final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
298                 TransactionUtil.checkItemDoesNotExists(
299                         transactionChain, transaction, LogicalDatastoreType.CONFIGURATION, childPath,
300                         RestconfDataServiceConstant.PostData.POST_TX_TYPE);
301                 if (!merge) {
302                     merge = true;
303                     TransactionUtil.ensureParentsByMerge(path, schemaContext, transaction);
304                     final NormalizedNode<?, ?> emptySubTree = ImmutableNodes.fromInstanceId(schemaContext, path);
305                     transaction.merge(LogicalDatastoreType.CONFIGURATION,
306                             YangInstanceIdentifier.create(emptySubTree.getIdentifier()), emptySubTree);
307                 }
308                 transaction.put(LogicalDatastoreType.CONFIGURATION, childPath, child);
309             }
310         } else {
311             TransactionUtil.checkItemDoesNotExists(
312                     transactionChain, transaction, LogicalDatastoreType.CONFIGURATION, path,
313                     RestconfDataServiceConstant.PostData.POST_TX_TYPE);
314
315             TransactionUtil.ensureParentsByMerge(path, schemaContext, transaction);
316             transaction.put(LogicalDatastoreType.CONFIGURATION, path, data);
317         }
318     }
319
320     /**
321      * Get location from {@link YangInstanceIdentifier} and {@link UriInfo}.
322      *
323      * @param uriInfo
324      *             uri info
325      * @param transactionNode
326      *             wrapper for data of transaction
327      * @param schemaContextRef
328      *            reference to {@link SchemaContext}
329      * @return {@link URI}
330      */
331     private static URI resolveLocation(final UriInfo uriInfo, final TransactionVarsWrapper transactionNode,
332             final SchemaContextRef schemaContextRef) {
333         if (uriInfo == null) {
334             return null;
335         }
336
337         final UriBuilder uriBuilder = uriInfo.getBaseUriBuilder();
338         uriBuilder.path("data");
339         uriBuilder.path(ParserIdentifier
340                 .stringFromYangInstanceIdentifier(transactionNode.getInstanceIdentifier().getInstanceIdentifier(),
341                 schemaContextRef.get()));
342
343         return uriBuilder.build();
344     }
345
346     private static void simplePost(final DOMDataTreeReadWriteTransaction rwTransaction,
347             final LogicalDatastoreType datastore, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload,
348             final SchemaContext schemaContext, final DOMTransactionChain transactionChain) {
349         TransactionUtil.checkItemDoesNotExists(transactionChain, rwTransaction, datastore, path,
350                 RestconfDataServiceConstant.PostData.POST_TX_TYPE);
351         TransactionUtil.ensureParentsByMerge(path, schemaContext, rwTransaction);
352         rwTransaction.put(datastore, path, payload);
353     }
354 }