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