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