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