937a6361a0b31e583c63c98398083006f8958997
[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.handlers.TransactionChainHandler;
26 import org.opendaylight.restconf.nb.rfc8040.references.SchemaContextRef;
27 import org.opendaylight.restconf.nb.rfc8040.rests.transactions.TransactionVarsWrapper;
28 import org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserIdentifier;
29 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
30 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
31 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
32 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
33 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
34 import org.opendaylight.yangtools.yang.data.api.schema.OrderedLeafSetNode;
35 import org.opendaylight.yangtools.yang.data.api.schema.OrderedMapNode;
36 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
37 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
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         throw new UnsupportedOperationException("Util class.");
48     }
49
50     /**
51      * Check mount point and prepare variables for post data.
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         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 FluentFuture}
95      */
96     private static FluentFuture<? extends CommitInfo> 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 DOMDataTreeReadWriteTransaction newReadWriteTransaction = domTransactionChain.newReadWriteTransaction();
101         if (insert == null) {
102             makePost(path, data, schemaContext, transactionNode.getTransactionChainHandler(), newReadWriteTransaction);
103             return newReadWriteTransaction.commit();
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 = PutDataTransactionUtil.readList(path.getParent(),
110                             schemaContext, transactionNode.getTransactionChainHandler(), schemaNode);
111                         final OrderedMapNode readList = (OrderedMapNode) readData;
112                         if (readList == null || readList.getValue().isEmpty()) {
113                             makePost(path, data, schemaContext, transactionNode.getTransactionChainHandler(),
114                                     newReadWriteTransaction);
115                             return newReadWriteTransaction.commit();
116                         } else {
117                             newReadWriteTransaction.delete(LogicalDatastoreType.CONFIGURATION,
118                                     path.getParent().getParent());
119                             simplePost(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION, path, data,
120                                     schemaContext, transactionNode.getTransactionChainHandler());
121                             makePost(path, readData, schemaContext, transactionNode.getTransactionChainHandler(),
122                                     newReadWriteTransaction);
123                             return newReadWriteTransaction.commit();
124                         }
125                     } else {
126                         final NormalizedNode<?, ?> readData = PutDataTransactionUtil.readList(path.getParent(),
127                             schemaContext, transactionNode.getTransactionChainHandler(), schemaNode);
128
129                         final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
130                         if (readLeafList == null || readLeafList.getValue().isEmpty()) {
131                             makePost(path, data, schemaContext, transactionNode.getTransactionChainHandler(),
132                                     newReadWriteTransaction);
133                             return newReadWriteTransaction.commit();
134                         } else {
135                             newReadWriteTransaction.delete(LogicalDatastoreType.CONFIGURATION,
136                                     path.getParent().getParent());
137                             simplePost(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION, path, data,
138                                     schemaContext, transactionNode.getTransactionChainHandler());
139                             makePost(path, readData, schemaContext, transactionNode.getTransactionChainHandler(),
140                                     newReadWriteTransaction);
141                             return newReadWriteTransaction.commit();
142                         }
143                     }
144                 case "last":
145                     makePost(path, data, schemaContext, transactionNode.getTransactionChainHandler(),
146                             newReadWriteTransaction);
147                     return newReadWriteTransaction.commit();
148                 case "before":
149                     if (schemaNode instanceof ListSchemaNode) {
150                         final NormalizedNode<?, ?> readData = PutDataTransactionUtil.readList(path.getParent(),
151                             schemaContext, transactionNode.getTransactionChainHandler(), schemaNode);
152                         final OrderedMapNode readList = (OrderedMapNode) readData;
153                         if (readList == null || readList.getValue().isEmpty()) {
154                             makePost(path, data, schemaContext, transactionNode.getTransactionChainHandler(),
155                                     newReadWriteTransaction);
156                             return newReadWriteTransaction.commit();
157                         } else {
158                             insertWithPointListPost(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION, path,
159                                     data, schemaContext, point, readList, true,
160                                     transactionNode.getTransactionChainHandler());
161                             return newReadWriteTransaction.commit();
162                         }
163                     } else {
164                         final NormalizedNode<?, ?> readData = PutDataTransactionUtil.readList(path.getParent(),
165                             schemaContext, transactionNode.getTransactionChainHandler(), schemaNode);
166
167                         final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
168                         if (readLeafList == null || readLeafList.getValue().isEmpty()) {
169                             makePost(path, data, schemaContext, transactionNode.getTransactionChainHandler(),
170                                     newReadWriteTransaction);
171                             return newReadWriteTransaction.commit();
172                         } else {
173                             insertWithPointLeafListPost(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION,
174                                     path, data, schemaContext, point, readLeafList, true,
175                                     transactionNode.getTransactionChainHandler());
176                             return newReadWriteTransaction.commit();
177                         }
178                     }
179                 case "after":
180                     if (schemaNode instanceof ListSchemaNode) {
181                         final NormalizedNode<?, ?> readData = PutDataTransactionUtil.readList(path.getParent(),
182                             schemaContext, transactionNode.getTransactionChainHandler(), schemaNode);
183                         final OrderedMapNode readList = (OrderedMapNode) readData;
184                         if (readList == null || readList.getValue().isEmpty()) {
185                             makePost(path, data, schemaContext, transactionNode.getTransactionChainHandler(),
186                                     newReadWriteTransaction);
187                             return newReadWriteTransaction.commit();
188                         } else {
189                             insertWithPointListPost(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION, path,
190                                     data, schemaContext, point, readList, false,
191                                     transactionNode.getTransactionChainHandler());
192                             return newReadWriteTransaction.commit();
193                         }
194                     } else {
195                         final NormalizedNode<?, ?> readData = PutDataTransactionUtil.readList(path.getParent(),
196                             schemaContext, transactionNode.getTransactionChainHandler(), schemaNode);
197
198                         final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
199                         if (readLeafList == null || readLeafList.getValue().isEmpty()) {
200                             makePost(path, data, schemaContext, transactionNode.getTransactionChainHandler(),
201                                     newReadWriteTransaction);
202                             return newReadWriteTransaction.commit();
203                         } else {
204                             insertWithPointLeafListPost(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION,
205                                     path, data, schemaContext, point, readLeafList, true,
206                                     transactionNode.getTransactionChainHandler());
207                             return newReadWriteTransaction.commit();
208                         }
209                     }
210                 default:
211                     throw new RestconfDocumentedException(
212                             "Used bad value of insert parameter. Possible values are first, last, before or after, "
213                                     + "but was: " + insert, RestconfError.ErrorType.PROTOCOL,
214                             RestconfError.ErrorTag.BAD_ATTRIBUTE);
215             }
216         }
217     }
218
219     private static void insertWithPointLeafListPost(final DOMDataTreeReadWriteTransaction rwTransaction,
220             final LogicalDatastoreType datastore, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload,
221             final SchemaContext schemaContext, final String point, final OrderedLeafSetNode<?> readLeafList,
222             final boolean before, final TransactionChainHandler transactionChainHandler) {
223         rwTransaction.delete(datastore, path.getParent().getParent());
224         final InstanceIdentifierContext<?> instanceIdentifier =
225                 ParserIdentifier.toInstanceIdentifier(point, schemaContext, Optional.empty());
226         int lastItemPosition = 0;
227         for (final LeafSetEntryNode<?> nodeChild : readLeafList.getValue()) {
228             if (nodeChild.getIdentifier().equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
229                 break;
230             }
231             lastItemPosition++;
232         }
233         if (!before) {
234             lastItemPosition++;
235         }
236         int lastInsertedPosition = 0;
237         final NormalizedNode<?, ?> emptySubtree =
238                 ImmutableNodes.fromInstanceId(schemaContext, path.getParent().getParent());
239         rwTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
240         for (final LeafSetEntryNode<?> nodeChild : readLeafList.getValue()) {
241             if (lastInsertedPosition == lastItemPosition) {
242                 TransactionUtil.checkItemDoesNotExists(transactionChainHandler, rwTransaction, datastore, path,
243                         RestconfDataServiceConstant.PostData.POST_TX_TYPE);
244                 rwTransaction.put(datastore, path, payload);
245             }
246             final YangInstanceIdentifier childPath = path.getParent().getParent().node(nodeChild.getIdentifier());
247             TransactionUtil.checkItemDoesNotExists(transactionChainHandler, rwTransaction, datastore, childPath,
248                     RestconfDataServiceConstant.PostData.POST_TX_TYPE);
249             rwTransaction.put(datastore, childPath, nodeChild);
250             lastInsertedPosition++;
251         }
252     }
253
254     private static void insertWithPointListPost(final DOMDataTreeReadWriteTransaction rwTransaction,
255             final LogicalDatastoreType datastore, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload,
256             final SchemaContext schemaContext, final String point, final MapNode readList, final boolean before,
257             final TransactionChainHandler transactionChainHandler) {
258         rwTransaction.delete(datastore, path.getParent().getParent());
259         final InstanceIdentifierContext<?> instanceIdentifier =
260                 ParserIdentifier.toInstanceIdentifier(point, schemaContext, Optional.empty());
261         int lastItemPosition = 0;
262         for (final MapEntryNode mapEntryNode : readList.getValue()) {
263             if (mapEntryNode.getIdentifier().equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
264                 break;
265             }
266             lastItemPosition++;
267         }
268         if (!before) {
269             lastItemPosition++;
270         }
271         int lastInsertedPosition = 0;
272         final NormalizedNode<?, ?> emptySubtree =
273                 ImmutableNodes.fromInstanceId(schemaContext, path.getParent().getParent());
274         rwTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
275         for (final MapEntryNode mapEntryNode : readList.getValue()) {
276             if (lastInsertedPosition == lastItemPosition) {
277                 TransactionUtil.checkItemDoesNotExists(transactionChainHandler, rwTransaction, datastore, path,
278                         RestconfDataServiceConstant.PostData.POST_TX_TYPE);
279                 rwTransaction.put(datastore, path, payload);
280             }
281             final YangInstanceIdentifier childPath = path.getParent().getParent().node(mapEntryNode.getIdentifier());
282             TransactionUtil.checkItemDoesNotExists(transactionChainHandler, rwTransaction, datastore, childPath,
283                     RestconfDataServiceConstant.PostData.POST_TX_TYPE);
284             rwTransaction.put(datastore, childPath, mapEntryNode);
285             lastInsertedPosition++;
286         }
287     }
288
289     private static void makePost(final YangInstanceIdentifier path, final NormalizedNode<?, ?> data,
290             final SchemaContext schemaContext, final TransactionChainHandler transactionChainHandler,
291             final DOMDataTreeReadWriteTransaction transaction) {
292         if (data instanceof MapNode) {
293             boolean merge = false;
294             for (final MapEntryNode child : ((MapNode) data).getValue()) {
295                 final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
296                 TransactionUtil.checkItemDoesNotExists(
297                         transactionChainHandler, transaction, LogicalDatastoreType.CONFIGURATION, childPath,
298                         RestconfDataServiceConstant.PostData.POST_TX_TYPE);
299                 if (!merge) {
300                     merge = true;
301                     TransactionUtil.ensureParentsByMerge(path, schemaContext, transaction);
302                     final NormalizedNode<?, ?> emptySubTree = ImmutableNodes.fromInstanceId(schemaContext, path);
303                     transaction.merge(LogicalDatastoreType.CONFIGURATION,
304                             YangInstanceIdentifier.create(emptySubTree.getIdentifier()), emptySubTree);
305                 }
306                 transaction.put(LogicalDatastoreType.CONFIGURATION, childPath, child);
307             }
308         } else {
309             TransactionUtil.checkItemDoesNotExists(
310                     transactionChainHandler, transaction, LogicalDatastoreType.CONFIGURATION, path,
311                     RestconfDataServiceConstant.PostData.POST_TX_TYPE);
312
313             TransactionUtil.ensureParentsByMerge(path, schemaContext, transaction);
314             transaction.put(LogicalDatastoreType.CONFIGURATION, path, data);
315         }
316     }
317
318     /**
319      * Get location from {@link YangInstanceIdentifier} and {@link UriInfo}.
320      *
321      * @param uriInfo
322      *             uri info
323      * @param transactionNode
324      *             wrapper for data of transaction
325      * @param schemaContextRef
326      *            reference to {@link SchemaContext}
327      * @return {@link URI}
328      */
329     private static URI resolveLocation(final UriInfo uriInfo, final TransactionVarsWrapper transactionNode,
330             final SchemaContextRef schemaContextRef) {
331         if (uriInfo == null) {
332             return null;
333         }
334
335         final UriBuilder uriBuilder = uriInfo.getBaseUriBuilder();
336         uriBuilder.path("data");
337         uriBuilder.path(ParserIdentifier
338                 .stringFromYangInstanceIdentifier(transactionNode.getInstanceIdentifier().getInstanceIdentifier(),
339                 schemaContextRef.get()));
340
341         return uriBuilder.build();
342     }
343
344     private static void simplePost(final DOMDataTreeReadWriteTransaction rwTransaction,
345             final LogicalDatastoreType datastore, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload,
346             final SchemaContext schemaContext, final TransactionChainHandler transactionChainHandler) {
347         TransactionUtil.checkItemDoesNotExists(transactionChainHandler, rwTransaction, datastore, path,
348                 RestconfDataServiceConstant.PostData.POST_TX_TYPE);
349         TransactionUtil.ensureParentsByMerge(path, schemaContext, rwTransaction);
350         rwTransaction.put(datastore, path, payload);
351     }
352 }