Shortcut PutDataTransactionUtil.readData()
[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(strategy, path.getParent());
108                     final OrderedMapNode readList = (OrderedMapNode) readData;
109                     if (readList == null || readList.getValue().isEmpty()) {
110                         makePost(path, data, schemaContext, strategy);
111                         return strategy.commit();
112                     }
113
114                     strategy.delete(LogicalDatastoreType.CONFIGURATION, path.getParent().getParent());
115                     simplePost(LogicalDatastoreType.CONFIGURATION, path, data, schemaContext, strategy);
116                     makePost(path, readData, schemaContext, strategy);
117                     return strategy.commit();
118                 } else {
119                     final NormalizedNode<?, ?> readData = PutDataTransactionUtil.readList(strategy, path.getParent());
120
121                     final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
122                     if (readLeafList == null || readLeafList.getValue().isEmpty()) {
123                         makePost(path, data, schemaContext, strategy);
124                         return strategy.commit();
125                     }
126
127                     strategy.delete(LogicalDatastoreType.CONFIGURATION, path.getParent().getParent());
128                     simplePost(LogicalDatastoreType.CONFIGURATION, path, data, schemaContext, strategy);
129                     makePost(path, readData, schemaContext, strategy);
130                     return strategy.commit();
131                 }
132             case "last":
133                 makePost(path, data, schemaContext, strategy);
134                 return strategy.commit();
135             case "before":
136                 if (schemaNode instanceof ListSchemaNode) {
137                     final NormalizedNode<?, ?> readData = PutDataTransactionUtil.readList(strategy, path.getParent());
138                     final OrderedMapNode readList = (OrderedMapNode) readData;
139                     if (readList == null || readList.getValue().isEmpty()) {
140                         makePost(path, data, schemaContext, strategy);
141                         return strategy.commit();
142                     }
143
144                     insertWithPointListPost(LogicalDatastoreType.CONFIGURATION, path,
145                             data, schemaContext, point, readList, true, strategy);
146                     return strategy.commit();
147                 } else {
148                     final NormalizedNode<?, ?> readData = PutDataTransactionUtil.readList(strategy, path.getParent());
149
150                     final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
151                     if (readLeafList == null || readLeafList.getValue().isEmpty()) {
152                         makePost(path, data, schemaContext, strategy);
153                         return strategy.commit();
154                     }
155
156                     insertWithPointLeafListPost(LogicalDatastoreType.CONFIGURATION,
157                             path, data, schemaContext, point, readLeafList, true, strategy);
158                     return strategy.commit();
159                 }
160             case "after":
161                 if (schemaNode instanceof ListSchemaNode) {
162                     final NormalizedNode<?, ?> readData = PutDataTransactionUtil.readList(strategy, path.getParent());
163                     final OrderedMapNode readList = (OrderedMapNode) readData;
164                     if (readList == null || readList.getValue().isEmpty()) {
165                         makePost(path, data, schemaContext, strategy);
166                         return strategy.commit();
167                     }
168
169                     insertWithPointListPost(LogicalDatastoreType.CONFIGURATION, path,
170                             data, schemaContext, point, readList, false, strategy);
171                     return strategy.commit();
172                 } else {
173                     final NormalizedNode<?, ?> readData = PutDataTransactionUtil.readList(strategy, path.getParent());
174                     final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
175                     if (readLeafList == null || readLeafList.getValue().isEmpty()) {
176                         makePost(path, data, schemaContext, strategy);
177                         return strategy.commit();
178                     }
179
180                     insertWithPointLeafListPost(LogicalDatastoreType.CONFIGURATION,
181                             path, data, schemaContext, point, readLeafList, true, strategy);
182                     return strategy.commit();
183                 }
184             default:
185                 throw new RestconfDocumentedException(
186                     "Used bad value of insert parameter. Possible values are first, last, before or after, but was: "
187                             + insert, RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.BAD_ATTRIBUTE);
188         }
189     }
190
191     private static void insertWithPointLeafListPost(final LogicalDatastoreType datastore,
192                                                     final YangInstanceIdentifier path,
193                                                     final NormalizedNode<?, ?> payload,
194                                                     final EffectiveModelContext schemaContext, final String point,
195                                                     final OrderedLeafSetNode<?> readLeafList,
196                                                     final boolean before, final RestconfStrategy strategy) {
197         strategy.delete(datastore, path.getParent().getParent());
198         final InstanceIdentifierContext<?> instanceIdentifier =
199                 ParserIdentifier.toInstanceIdentifier(point, schemaContext, Optional.empty());
200         int lastItemPosition = 0;
201         for (final LeafSetEntryNode<?> nodeChild : readLeafList.getValue()) {
202             if (nodeChild.getIdentifier().equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
203                 break;
204             }
205             lastItemPosition++;
206         }
207         if (!before) {
208             lastItemPosition++;
209         }
210         int lastInsertedPosition = 0;
211         final NormalizedNode<?, ?> emptySubtree =
212                 ImmutableNodes.fromInstanceId(schemaContext, path.getParent().getParent());
213         strategy.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
214         for (final LeafSetEntryNode<?> nodeChild : readLeafList.getValue()) {
215             if (lastInsertedPosition == lastItemPosition) {
216                 checkItemDoesNotExists(strategy, datastore, path, RestconfDataServiceConstant.PostData.POST_TX_TYPE);
217                 strategy.create(datastore, path, payload);
218             }
219             final YangInstanceIdentifier childPath = path.getParent().getParent().node(nodeChild.getIdentifier());
220             checkItemDoesNotExists(strategy, datastore, childPath, RestconfDataServiceConstant.PostData.POST_TX_TYPE);
221             strategy.create(datastore, childPath, nodeChild);
222             lastInsertedPosition++;
223         }
224     }
225
226     private static void insertWithPointListPost(final LogicalDatastoreType datastore, final YangInstanceIdentifier path,
227                                                 final NormalizedNode<?, ?> payload,
228                                                 final EffectiveModelContext schemaContext, final String point,
229                                                 final MapNode readList, final boolean before,
230                                                 final RestconfStrategy strategy) {
231         strategy.delete(datastore, path.getParent().getParent());
232         final InstanceIdentifierContext<?> instanceIdentifier =
233                 ParserIdentifier.toInstanceIdentifier(point, schemaContext, Optional.empty());
234         int lastItemPosition = 0;
235         for (final MapEntryNode mapEntryNode : readList.getValue()) {
236             if (mapEntryNode.getIdentifier().equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
237                 break;
238             }
239             lastItemPosition++;
240         }
241         if (!before) {
242             lastItemPosition++;
243         }
244         int lastInsertedPosition = 0;
245         final NormalizedNode<?, ?> emptySubtree =
246                 ImmutableNodes.fromInstanceId(schemaContext, path.getParent().getParent());
247         strategy.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
248         for (final MapEntryNode mapEntryNode : readList.getValue()) {
249             if (lastInsertedPosition == lastItemPosition) {
250                 checkItemDoesNotExists(strategy, datastore, path, RestconfDataServiceConstant.PostData.POST_TX_TYPE);
251                 strategy.create(datastore, path, payload);
252             }
253             final YangInstanceIdentifier childPath = path.getParent().getParent().node(mapEntryNode.getIdentifier());
254             checkItemDoesNotExists(strategy, datastore, childPath, RestconfDataServiceConstant.PostData.POST_TX_TYPE);
255             strategy.create(datastore, childPath, mapEntryNode);
256             lastInsertedPosition++;
257         }
258     }
259
260     private static void makePost(final YangInstanceIdentifier path, final NormalizedNode<?, ?> data,
261                                  final SchemaContext schemaContext, final RestconfStrategy strategy) {
262         if (data instanceof MapNode) {
263             boolean merge = false;
264             for (final MapEntryNode child : ((MapNode) data).getValue()) {
265                 final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
266                 checkItemDoesNotExists(strategy, LogicalDatastoreType.CONFIGURATION, childPath,
267                         RestconfDataServiceConstant.PostData.POST_TX_TYPE);
268                 if (!merge) {
269                     merge = true;
270                     TransactionUtil.ensureParentsByMerge(path, schemaContext, strategy);
271                     final NormalizedNode<?, ?> emptySubTree = ImmutableNodes.fromInstanceId(schemaContext, path);
272                     strategy.merge(LogicalDatastoreType.CONFIGURATION,
273                             YangInstanceIdentifier.create(emptySubTree.getIdentifier()), emptySubTree);
274                 }
275                 strategy.create(LogicalDatastoreType.CONFIGURATION, childPath, child);
276             }
277         } else {
278             checkItemDoesNotExists(strategy, LogicalDatastoreType.CONFIGURATION, path,
279                     RestconfDataServiceConstant.PostData.POST_TX_TYPE);
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, RestconfDataServiceConstant.PostData.POST_TX_TYPE);
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 String operationType) {
335         final FluentFuture<Boolean> future = strategy.exists(store, path);
336         final FutureDataFactory<Boolean> response = new FutureDataFactory<>();
337
338         FutureCallbackTx.addCallback(future, operationType, response);
339
340         if (response.result) {
341             // close transaction
342             strategy.cancel();
343             // throw error
344             LOG.trace("Operation via Restconf was not executed because data at {} already exists", path);
345             throw new RestconfDocumentedException(
346                     "Data already exists", ErrorType.PROTOCOL, ErrorTag.DATA_EXISTS, path);
347         }
348     }
349
350 }