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