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