Create NetconfDataTreeService with base and additional operations for netconf
[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 FluentFuture<? extends CommitInfo> future = submitData(
65                 payload.getInstanceIdentifierContext().getInstanceIdentifier(), payload.getData(),
66                 strategy, schemaContext, insert, point);
67         final URI location = resolveLocation(uriInfo, strategy.getInstanceIdentifier(),
68                 schemaContext, payload.getData());
69         final ResponseFactory dataFactory = new ResponseFactory(Status.CREATED).location(location);
70         //This method will close transactionChain if any
71         FutureCallbackTx.addCallback(future, RestconfDataServiceConstant.PostData.POST_TX_TYPE, dataFactory,
72                 strategy.getTransactionChain());
73         return dataFactory.build();
74     }
75
76     /**
77      * Post data by type.
78      *
79      * @param path          path
80      * @param data          data
81      * @param strategy      object that perform the actual DS operations
82      * @param schemaContext schema context of data
83      * @param point         query parameter
84      * @param insert        query parameter
85      * @return {@link FluentFuture}
86      */
87     private static FluentFuture<? extends CommitInfo> submitData(final YangInstanceIdentifier path,
88                                                                  final NormalizedNode<?, ?> data,
89                                                                  final RestconfStrategy strategy,
90                                                                  final EffectiveModelContext schemaContext,
91                                                                  final String insert, final String point) {
92         strategy.prepareReadWriteExecution();
93         if (insert == null) {
94             makePost(path, data, schemaContext, strategy);
95             return strategy.commit();
96         }
97
98         final DataSchemaNode schemaNode = PutDataTransactionUtil.checkListAndOrderedType(schemaContext, path);
99         switch (insert) {
100             case "first":
101                 if (schemaNode instanceof ListSchemaNode) {
102                     final NormalizedNode<?, ?> readData = PutDataTransactionUtil.readList(path.getParent(),
103                             schemaContext, strategy, schemaNode);
104                     final OrderedMapNode readList = (OrderedMapNode) readData;
105                     if (readList == null || readList.getValue().isEmpty()) {
106                         makePost(path, data, schemaContext, strategy);
107                         return strategy.commit();
108                     }
109
110                     strategy.delete(LogicalDatastoreType.CONFIGURATION, path.getParent().getParent());
111                     simplePost(LogicalDatastoreType.CONFIGURATION, path, data, schemaContext, strategy);
112                     makePost(path, readData, schemaContext, strategy);
113                     return strategy.commit();
114                 } else {
115                     final NormalizedNode<?, ?> readData = PutDataTransactionUtil.readList(path.getParent(),
116                             schemaContext, strategy, schemaNode);
117
118                     final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
119                     if (readLeafList == null || readLeafList.getValue().isEmpty()) {
120                         makePost(path, data, schemaContext, strategy);
121                         return strategy.commit();
122                     }
123
124                     strategy.delete(LogicalDatastoreType.CONFIGURATION, path.getParent().getParent());
125                     simplePost(LogicalDatastoreType.CONFIGURATION, path, data, schemaContext, strategy);
126                     makePost(path, readData, schemaContext, strategy);
127                     return strategy.commit();
128                 }
129             case "last":
130                 makePost(path, data, schemaContext, strategy);
131                 return strategy.commit();
132             case "before":
133                 if (schemaNode instanceof ListSchemaNode) {
134                     final NormalizedNode<?, ?> readData = PutDataTransactionUtil.readList(path.getParent(),
135                             schemaContext, strategy, schemaNode);
136                     final OrderedMapNode readList = (OrderedMapNode) readData;
137                     if (readList == null || readList.getValue().isEmpty()) {
138                         makePost(path, data, schemaContext, strategy);
139                         return strategy.commit();
140                     }
141
142                     insertWithPointListPost(LogicalDatastoreType.CONFIGURATION, path,
143                             data, schemaContext, point, readList, true, strategy);
144                     return strategy.commit();
145                 } else {
146                     final NormalizedNode<?, ?> readData = PutDataTransactionUtil.readList(path.getParent(),
147                             schemaContext, strategy, schemaNode);
148
149                     final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
150                     if (readLeafList == null || readLeafList.getValue().isEmpty()) {
151                         makePost(path, data, schemaContext, strategy);
152                         return strategy.commit();
153                     }
154
155                     insertWithPointLeafListPost(LogicalDatastoreType.CONFIGURATION,
156                             path, data, schemaContext, point, readLeafList, true, strategy);
157                     return strategy.commit();
158                 }
159             case "after":
160                 if (schemaNode instanceof ListSchemaNode) {
161                     final NormalizedNode<?, ?> readData = PutDataTransactionUtil.readList(path.getParent(),
162                             schemaContext, strategy, schemaNode);
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(path.getParent(),
174                             schemaContext, strategy, schemaNode);
175
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                 TransactionUtil.checkItemDoesNotExists(strategy, datastore, path,
219                         RestconfDataServiceConstant.PostData.POST_TX_TYPE);
220                 strategy.create(datastore, path, payload);
221             }
222             final YangInstanceIdentifier childPath = path.getParent().getParent().node(nodeChild.getIdentifier());
223             TransactionUtil.checkItemDoesNotExists(strategy, datastore, childPath,
224                     RestconfDataServiceConstant.PostData.POST_TX_TYPE);
225             strategy.create(datastore, childPath, nodeChild);
226             lastInsertedPosition++;
227         }
228     }
229
230     private static void insertWithPointListPost(final LogicalDatastoreType datastore, final YangInstanceIdentifier path,
231                                                 final NormalizedNode<?, ?> payload,
232                                                 final EffectiveModelContext schemaContext, final String point,
233                                                 final MapNode readList, final boolean before,
234                                                 final RestconfStrategy strategy) {
235         strategy.delete(datastore, path.getParent().getParent());
236         final InstanceIdentifierContext<?> instanceIdentifier =
237                 ParserIdentifier.toInstanceIdentifier(point, schemaContext, Optional.empty());
238         int lastItemPosition = 0;
239         for (final MapEntryNode mapEntryNode : readList.getValue()) {
240             if (mapEntryNode.getIdentifier().equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
241                 break;
242             }
243             lastItemPosition++;
244         }
245         if (!before) {
246             lastItemPosition++;
247         }
248         int lastInsertedPosition = 0;
249         final NormalizedNode<?, ?> emptySubtree =
250                 ImmutableNodes.fromInstanceId(schemaContext, path.getParent().getParent());
251         strategy.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
252         for (final MapEntryNode mapEntryNode : readList.getValue()) {
253             if (lastInsertedPosition == lastItemPosition) {
254                 TransactionUtil.checkItemDoesNotExists(strategy, datastore, path,
255                         RestconfDataServiceConstant.PostData.POST_TX_TYPE);
256                 strategy.create(datastore, path, payload);
257             }
258             final YangInstanceIdentifier childPath = path.getParent().getParent().node(mapEntryNode.getIdentifier());
259             TransactionUtil.checkItemDoesNotExists(strategy, datastore, childPath,
260                     RestconfDataServiceConstant.PostData.POST_TX_TYPE);
261             strategy.create(datastore, childPath, mapEntryNode);
262             lastInsertedPosition++;
263         }
264     }
265
266     private static void makePost(final YangInstanceIdentifier path, final NormalizedNode<?, ?> data,
267                                  final SchemaContext schemaContext, final RestconfStrategy strategy) {
268         if (data instanceof MapNode) {
269             boolean merge = false;
270             for (final MapEntryNode child : ((MapNode) data).getValue()) {
271                 final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
272                 TransactionUtil.checkItemDoesNotExists(strategy, LogicalDatastoreType.CONFIGURATION, childPath,
273                         RestconfDataServiceConstant.PostData.POST_TX_TYPE);
274                 if (!merge) {
275                     merge = true;
276                     TransactionUtil.ensureParentsByMerge(path, schemaContext, strategy);
277                     final NormalizedNode<?, ?> emptySubTree = ImmutableNodes.fromInstanceId(schemaContext, path);
278                     strategy.merge(LogicalDatastoreType.CONFIGURATION,
279                             YangInstanceIdentifier.create(emptySubTree.getIdentifier()), emptySubTree);
280                 }
281                 strategy.create(LogicalDatastoreType.CONFIGURATION, childPath, child);
282             }
283         } else {
284             TransactionUtil.checkItemDoesNotExists(strategy, LogicalDatastoreType.CONFIGURATION, path,
285                     RestconfDataServiceConstant.PostData.POST_TX_TYPE);
286
287             TransactionUtil.ensureParentsByMerge(path, schemaContext, strategy);
288             strategy.create(LogicalDatastoreType.CONFIGURATION, path, data);
289         }
290     }
291
292     /**
293      * Get location from {@link YangInstanceIdentifier} and {@link UriInfo}.
294      *
295      * @param uriInfo                uri info
296      * @param yangInstanceIdentifier reference to {@link InstanceIdentifierContext}
297      * @param schemaContext          reference to {@link SchemaContext}
298      * @return {@link URI}
299      */
300     private static URI resolveLocation(final UriInfo uriInfo, final InstanceIdentifierContext<?> yangInstanceIdentifier,
301                                        final EffectiveModelContext schemaContext, final NormalizedNode<?, ?> data) {
302         if (uriInfo == null) {
303             return null;
304         }
305
306         YangInstanceIdentifier path = yangInstanceIdentifier.getInstanceIdentifier();
307
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         TransactionUtil.checkItemDoesNotExists(strategy, datastore, path,
325                 RestconfDataServiceConstant.PostData.POST_TX_TYPE);
326         TransactionUtil.ensureParentsByMerge(path, schemaContext, strategy);
327         strategy.create(datastore, path, payload);
328     }
329 }