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