Refactor RestconfStrategy
[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.nb.rfc8040.rests.transactions.RestconfStrategy;
25 import org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserIdentifier;
26 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
27 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
28 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
29 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
30 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
31 import org.opendaylight.yangtools.yang.data.api.schema.OrderedLeafSetNode;
32 import org.opendaylight.yangtools.yang.data.api.schema.OrderedMapNode;
33 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
34 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
35 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
36 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
37 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
38
39 /**
40  * Util class to post data to DS.
41  *
42  */
43 public final class PostDataTransactionUtil {
44     private PostDataTransactionUtil() {
45         // Hidden on purpose
46     }
47
48     /**
49      * Check mount point and prepare variables for post data. Close {@link DOMTransactionChain} if any inside of object
50      * {@link RestconfStrategy} provided as a parameter.
51      *
52      * @param uriInfo       uri info
53      * @param payload       data
54      * @param strategy      Object that perform the actual DS operations
55      * @param schemaContext reference to actual {@link EffectiveModelContext}
56      * @param point         point
57      * @param insert        insert
58      * @return {@link Response}
59      */
60     public static Response postData(final UriInfo uriInfo, final NormalizedNodeContext payload,
61                                     final RestconfStrategy strategy,
62                                     final EffectiveModelContext schemaContext, final String insert,
63                                     final String point) {
64         final YangInstanceIdentifier path = payload.getInstanceIdentifierContext().getInstanceIdentifier();
65         final FluentFuture<? extends CommitInfo> future = submitData(path, payload.getData(),
66                 strategy, schemaContext, insert, point);
67         final URI location = resolveLocation(uriInfo, path, schemaContext, payload.getData());
68         final ResponseFactory dataFactory = new ResponseFactory(Status.CREATED).location(location);
69         //This method will close transactionChain if any
70         FutureCallbackTx.addCallback(future, RestconfDataServiceConstant.PostData.POST_TX_TYPE, dataFactory,
71                 strategy.getTransactionChain());
72         return dataFactory.build();
73     }
74
75     /**
76      * Post data by type.
77      *
78      * @param path          path
79      * @param data          data
80      * @param strategy      object that perform the actual DS operations
81      * @param schemaContext schema context of data
82      * @param point         query parameter
83      * @param insert        query parameter
84      * @return {@link FluentFuture}
85      */
86     private static FluentFuture<? extends CommitInfo> submitData(final YangInstanceIdentifier path,
87                                                                  final NormalizedNode<?, ?> data,
88                                                                  final RestconfStrategy strategy,
89                                                                  final EffectiveModelContext schemaContext,
90                                                                  final String insert, final String point) {
91         strategy.prepareReadWriteExecution();
92         if (insert == null) {
93             makePost(path, data, schemaContext, strategy);
94             return strategy.commit();
95         }
96
97         final DataSchemaNode schemaNode = PutDataTransactionUtil.checkListAndOrderedType(schemaContext, path);
98         switch (insert) {
99             case "first":
100                 if (schemaNode instanceof ListSchemaNode) {
101                     final NormalizedNode<?, ?> readData = PutDataTransactionUtil.readList(path.getParent(),
102                             schemaContext, strategy, schemaNode);
103                     final OrderedMapNode readList = (OrderedMapNode) readData;
104                     if (readList == null || readList.getValue().isEmpty()) {
105                         makePost(path, data, schemaContext, strategy);
106                         return strategy.commit();
107                     }
108
109                     strategy.delete(LogicalDatastoreType.CONFIGURATION, path.getParent().getParent());
110                     simplePost(LogicalDatastoreType.CONFIGURATION, path, data, schemaContext, strategy);
111                     makePost(path, readData, schemaContext, strategy);
112                     return strategy.commit();
113                 } else {
114                     final NormalizedNode<?, ?> readData = PutDataTransactionUtil.readList(path.getParent(),
115                             schemaContext, strategy, schemaNode);
116
117                     final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
118                     if (readLeafList == null || readLeafList.getValue().isEmpty()) {
119                         makePost(path, data, schemaContext, strategy);
120                         return strategy.commit();
121                     }
122
123                     strategy.delete(LogicalDatastoreType.CONFIGURATION, path.getParent().getParent());
124                     simplePost(LogicalDatastoreType.CONFIGURATION, path, data, schemaContext, strategy);
125                     makePost(path, readData, schemaContext, strategy);
126                     return strategy.commit();
127                 }
128             case "last":
129                 makePost(path, data, schemaContext, strategy);
130                 return strategy.commit();
131             case "before":
132                 if (schemaNode instanceof ListSchemaNode) {
133                     final NormalizedNode<?, ?> readData = PutDataTransactionUtil.readList(path.getParent(),
134                             schemaContext, strategy, schemaNode);
135                     final OrderedMapNode readList = (OrderedMapNode) readData;
136                     if (readList == null || readList.getValue().isEmpty()) {
137                         makePost(path, data, schemaContext, strategy);
138                         return strategy.commit();
139                     }
140
141                     insertWithPointListPost(LogicalDatastoreType.CONFIGURATION, path,
142                             data, schemaContext, point, readList, true, strategy);
143                     return strategy.commit();
144                 } else {
145                     final NormalizedNode<?, ?> readData = PutDataTransactionUtil.readList(path.getParent(),
146                             schemaContext, strategy, schemaNode);
147
148                     final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
149                     if (readLeafList == null || readLeafList.getValue().isEmpty()) {
150                         makePost(path, data, schemaContext, strategy);
151                         return strategy.commit();
152                     }
153
154                     insertWithPointLeafListPost(LogicalDatastoreType.CONFIGURATION,
155                             path, data, schemaContext, point, readLeafList, true, strategy);
156                     return strategy.commit();
157                 }
158             case "after":
159                 if (schemaNode instanceof ListSchemaNode) {
160                     final NormalizedNode<?, ?> readData = PutDataTransactionUtil.readList(path.getParent(),
161                             schemaContext, strategy, schemaNode);
162                     final OrderedMapNode readList = (OrderedMapNode) readData;
163                     if (readList == null || readList.getValue().isEmpty()) {
164                         makePost(path, data, schemaContext, strategy);
165                         return strategy.commit();
166                     }
167
168                     insertWithPointListPost(LogicalDatastoreType.CONFIGURATION, path,
169                             data, schemaContext, point, readList, false, strategy);
170                     return strategy.commit();
171                 } else {
172                     final NormalizedNode<?, ?> readData = PutDataTransactionUtil.readList(path.getParent(),
173                             schemaContext, strategy, schemaNode);
174
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                 TransactionUtil.checkItemDoesNotExists(strategy, datastore, path,
218                         RestconfDataServiceConstant.PostData.POST_TX_TYPE);
219                 strategy.create(datastore, path, payload);
220             }
221             final YangInstanceIdentifier childPath = path.getParent().getParent().node(nodeChild.getIdentifier());
222             TransactionUtil.checkItemDoesNotExists(strategy, datastore, childPath,
223                     RestconfDataServiceConstant.PostData.POST_TX_TYPE);
224             strategy.create(datastore, childPath, nodeChild);
225             lastInsertedPosition++;
226         }
227     }
228
229     private static void insertWithPointListPost(final LogicalDatastoreType datastore, final YangInstanceIdentifier path,
230                                                 final NormalizedNode<?, ?> payload,
231                                                 final EffectiveModelContext schemaContext, final String point,
232                                                 final MapNode readList, final boolean before,
233                                                 final RestconfStrategy strategy) {
234         strategy.delete(datastore, path.getParent().getParent());
235         final InstanceIdentifierContext<?> instanceIdentifier =
236                 ParserIdentifier.toInstanceIdentifier(point, schemaContext, Optional.empty());
237         int lastItemPosition = 0;
238         for (final MapEntryNode mapEntryNode : readList.getValue()) {
239             if (mapEntryNode.getIdentifier().equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
240                 break;
241             }
242             lastItemPosition++;
243         }
244         if (!before) {
245             lastItemPosition++;
246         }
247         int lastInsertedPosition = 0;
248         final NormalizedNode<?, ?> emptySubtree =
249                 ImmutableNodes.fromInstanceId(schemaContext, path.getParent().getParent());
250         strategy.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
251         for (final MapEntryNode mapEntryNode : readList.getValue()) {
252             if (lastInsertedPosition == lastItemPosition) {
253                 TransactionUtil.checkItemDoesNotExists(strategy, datastore, path,
254                         RestconfDataServiceConstant.PostData.POST_TX_TYPE);
255                 strategy.create(datastore, path, payload);
256             }
257             final YangInstanceIdentifier childPath = path.getParent().getParent().node(mapEntryNode.getIdentifier());
258             TransactionUtil.checkItemDoesNotExists(strategy, datastore, childPath,
259                     RestconfDataServiceConstant.PostData.POST_TX_TYPE);
260             strategy.create(datastore, childPath, mapEntryNode);
261             lastInsertedPosition++;
262         }
263     }
264
265     private static void makePost(final YangInstanceIdentifier path, final NormalizedNode<?, ?> data,
266                                  final SchemaContext schemaContext, final RestconfStrategy strategy) {
267         if (data instanceof MapNode) {
268             boolean merge = false;
269             for (final MapEntryNode child : ((MapNode) data).getValue()) {
270                 final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
271                 TransactionUtil.checkItemDoesNotExists(strategy, LogicalDatastoreType.CONFIGURATION, childPath,
272                         RestconfDataServiceConstant.PostData.POST_TX_TYPE);
273                 if (!merge) {
274                     merge = true;
275                     TransactionUtil.ensureParentsByMerge(path, schemaContext, strategy);
276                     final NormalizedNode<?, ?> emptySubTree = ImmutableNodes.fromInstanceId(schemaContext, path);
277                     strategy.merge(LogicalDatastoreType.CONFIGURATION,
278                             YangInstanceIdentifier.create(emptySubTree.getIdentifier()), emptySubTree);
279                 }
280                 strategy.create(LogicalDatastoreType.CONFIGURATION, childPath, child);
281             }
282         } else {
283             TransactionUtil.checkItemDoesNotExists(strategy, LogicalDatastoreType.CONFIGURATION, path,
284                     RestconfDataServiceConstant.PostData.POST_TX_TYPE);
285
286             TransactionUtil.ensureParentsByMerge(path, schemaContext, strategy);
287             strategy.create(LogicalDatastoreType.CONFIGURATION, path, data);
288         }
289     }
290
291     /**
292      * Get location from {@link YangInstanceIdentifier} and {@link UriInfo}.
293      *
294      * @param uriInfo       uri info
295      * @param initialPath          data path
296      * @param schemaContext reference to {@link SchemaContext}
297      * @return {@link URI}
298      */
299     private static URI resolveLocation(final UriInfo uriInfo, final YangInstanceIdentifier initialPath,
300                                        final EffectiveModelContext schemaContext, final NormalizedNode<?, ?> data) {
301         if (uriInfo == null) {
302             return null;
303         }
304
305         YangInstanceIdentifier path = initialPath;
306         if (data instanceof MapNode) {
307             final Collection<MapEntryNode> children = ((MapNode) data).getValue();
308             if (!children.isEmpty()) {
309                 path = path.node(children.iterator().next().getIdentifier());
310             }
311         }
312
313         return uriInfo.getBaseUriBuilder()
314                 .path("data")
315                 .path(ParserIdentifier.stringFromYangInstanceIdentifier(path, schemaContext))
316                 .build();
317     }
318
319     private static void simplePost(final LogicalDatastoreType datastore, final YangInstanceIdentifier path,
320                                    final NormalizedNode<?, ?> payload,
321                                    final SchemaContext schemaContext, final RestconfStrategy strategy) {
322         TransactionUtil.checkItemDoesNotExists(strategy, datastore, path,
323                 RestconfDataServiceConstant.PostData.POST_TX_TYPE);
324         TransactionUtil.ensureParentsByMerge(path, schemaContext, strategy);
325         strategy.create(datastore, path, payload);
326     }
327 }