Eliminate unnecessary blocking checks
[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.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.NormalizedNodeContainer;
34 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
35 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
36 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
39
40 /**
41  * Util class to post data to DS.
42  *
43  */
44 public final class PostDataTransactionUtil {
45     private static final Logger LOG = LoggerFactory.getLogger(PostDataTransactionUtil.class);
46     // FIXME: why is this being reused from other places?
47     static final String POST_TX_TYPE = "POST";
48
49     private PostDataTransactionUtil() {
50         // Hidden on purpose
51     }
52
53     /**
54      * Check mount point and prepare variables for post data. Close {@link DOMTransactionChain} if any inside of object
55      * {@link RestconfStrategy} provided as a parameter.
56      *
57      * @param uriInfo       uri info
58      * @param payload       data
59      * @param strategy      Object that perform the actual DS operations
60      * @param schemaContext reference to actual {@link EffectiveModelContext}
61      * @param point         point
62      * @param insert        insert
63      * @return {@link Response}
64      */
65     public static Response postData(final UriInfo uriInfo, final NormalizedNodeContext payload,
66                                     final RestconfStrategy strategy,
67                                     final EffectiveModelContext schemaContext, final Insert insert,
68                                     final String point) {
69         final YangInstanceIdentifier path = payload.getInstanceIdentifierContext().getInstanceIdentifier();
70         final FluentFuture<? extends CommitInfo> future = submitData(path, payload.getData(),
71                 strategy, schemaContext, insert, point);
72         final URI location = resolveLocation(uriInfo, path, schemaContext, payload.getData());
73         final ResponseFactory dataFactory = new ResponseFactory(Status.CREATED).location(location);
74         //This method will close transactionChain if any
75         FutureCallbackTx.addCallback(future, POST_TX_TYPE, dataFactory, strategy.getTransactionChain(), path);
76         return dataFactory.build();
77     }
78
79     /**
80      * Post data by type.
81      *
82      * @param path          path
83      * @param data          data
84      * @param strategy      object that perform the actual DS operations
85      * @param schemaContext schema context of data
86      * @param point         query parameter
87      * @param insert        query parameter
88      * @return {@link FluentFuture}
89      */
90     private static FluentFuture<? extends CommitInfo> submitData(final YangInstanceIdentifier path,
91                                                                  final NormalizedNode<?, ?> data,
92                                                                  final RestconfStrategy strategy,
93                                                                  final EffectiveModelContext schemaContext,
94                                                                  final Insert insert, final String point) {
95         strategy.prepareReadWriteExecution();
96         if (insert == null) {
97             makePost(path, data, schemaContext, strategy);
98             return strategy.commit();
99         }
100
101         PutDataTransactionUtil.checkListAndOrderedType(schemaContext, path);
102         final NormalizedNode<?, ?> readData;
103         switch (insert) {
104             case FIRST:
105                 readData = PutDataTransactionUtil.readList(strategy, path.getParent().getParent());
106                 if (readData == null || ((NormalizedNodeContainer<?, ?, ?>) readData).getValue().isEmpty()) {
107                     strategy.replace(LogicalDatastoreType.CONFIGURATION, path, data, schemaContext);
108                     return strategy.commit();
109                 }
110                 checkItemDoesNotExists(strategy, LogicalDatastoreType.CONFIGURATION, path);
111                 strategy.remove(LogicalDatastoreType.CONFIGURATION, path.getParent().getParent());
112                 strategy.replace(LogicalDatastoreType.CONFIGURATION, path, data, schemaContext);
113                 strategy.replace(LogicalDatastoreType.CONFIGURATION, path.getParent().getParent(), readData,
114                     schemaContext);
115                 return strategy.commit();
116             case LAST:
117                 makePost(path, data, schemaContext, strategy);
118                 return strategy.commit();
119             case BEFORE:
120                 readData = PutDataTransactionUtil.readList(strategy, path.getParent().getParent());
121                 if (readData == null || ((NormalizedNodeContainer<?, ?, ?>) readData).getValue().isEmpty()) {
122                     strategy.replace(LogicalDatastoreType.CONFIGURATION, path, data, schemaContext);
123                     return strategy.commit();
124                 }
125                 checkItemDoesNotExists(strategy, LogicalDatastoreType.CONFIGURATION, path);
126                 insertWithPointPost(path, data, schemaContext, point,
127                     (NormalizedNodeContainer<?, ?, NormalizedNode<?, ?>>) readData, true, strategy);
128                 return strategy.commit();
129             case AFTER:
130                 readData = PutDataTransactionUtil.readList(strategy, path.getParent().getParent());
131                 if (readData == null || ((NormalizedNodeContainer<?, ?, ?>) readData).getValue().isEmpty()) {
132                     strategy.replace(LogicalDatastoreType.CONFIGURATION, path, data, schemaContext);
133                     return strategy.commit();
134                 }
135                 checkItemDoesNotExists(strategy, LogicalDatastoreType.CONFIGURATION, path);
136                 insertWithPointPost(path, data, schemaContext, point,
137                     (NormalizedNodeContainer<?, ?, NormalizedNode<?, ?>>) readData, false, strategy);
138                 return strategy.commit();
139             default:
140                 throw new RestconfDocumentedException(
141                     "Used bad value of insert parameter. Possible values are first, last, before or after, but was: "
142                         + insert, RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.BAD_ATTRIBUTE);
143         }
144     }
145
146     private static void insertWithPointPost(final YangInstanceIdentifier path,
147                                             final NormalizedNode<?, ?> data,
148                                             final EffectiveModelContext schemaContext, final String point,
149                                             final NormalizedNodeContainer<?, ?, NormalizedNode<?, ?>> readList,
150                                             final boolean before, final RestconfStrategy strategy) {
151         final YangInstanceIdentifier parent = path.getParent().getParent();
152         strategy.remove(LogicalDatastoreType.CONFIGURATION, parent);
153         final InstanceIdentifierContext<?> instanceIdentifier =
154             ParserIdentifier.toInstanceIdentifier(point, schemaContext, Optional.empty());
155         int lastItemPosition = 0;
156         for (final NormalizedNode<?, ?> nodeChild : readList.getValue()) {
157             if (nodeChild.getIdentifier().equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
158                 break;
159             }
160             lastItemPosition++;
161         }
162         if (!before) {
163             lastItemPosition++;
164         }
165         int lastInsertedPosition = 0;
166         final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, parent);
167         strategy.merge(LogicalDatastoreType.CONFIGURATION,
168             YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
169         for (final NormalizedNode<?, ?> nodeChild : readList.getValue()) {
170             if (lastInsertedPosition == lastItemPosition) {
171                 strategy.replace(LogicalDatastoreType.CONFIGURATION, path, data, schemaContext);
172             }
173             final YangInstanceIdentifier childPath = parent.node(nodeChild.getIdentifier());
174             strategy.replace(LogicalDatastoreType.CONFIGURATION, childPath, nodeChild, schemaContext);
175             lastInsertedPosition++;
176         }
177     }
178
179     private static void makePost(final YangInstanceIdentifier path, final NormalizedNode<?, ?> data,
180                                  final SchemaContext schemaContext, final RestconfStrategy strategy) {
181         try {
182             strategy.create(LogicalDatastoreType.CONFIGURATION, path, data, schemaContext);
183         } catch (RestconfDocumentedException e) {
184             // close transaction if any and pass exception further
185             strategy.cancel();
186             throw e;
187         }
188     }
189
190     /**
191      * Get location from {@link YangInstanceIdentifier} and {@link UriInfo}.
192      *
193      * @param uriInfo       uri info
194      * @param initialPath   data path
195      * @param schemaContext reference to {@link SchemaContext}
196      * @return {@link URI}
197      */
198     private static URI resolveLocation(final UriInfo uriInfo, final YangInstanceIdentifier initialPath,
199                                        final EffectiveModelContext schemaContext, final NormalizedNode<?, ?> data) {
200         if (uriInfo == null) {
201             return null;
202         }
203
204         YangInstanceIdentifier path = initialPath;
205         if (data instanceof MapNode) {
206             final Collection<MapEntryNode> children = ((MapNode) data).getValue();
207             if (!children.isEmpty()) {
208                 path = path.node(children.iterator().next().getIdentifier());
209             }
210         }
211
212         return uriInfo.getBaseUriBuilder()
213                 .path("data")
214                 .path(ParserIdentifier.stringFromYangInstanceIdentifier(path, schemaContext))
215                 .build();
216     }
217
218     /**
219      * Check if items do NOT already exists at specified {@code path}. Throws {@link RestconfDocumentedException} if
220      * data already exists.
221      *
222      * @param strategy      Object that perform the actual DS operations
223      * @param store         Datastore
224      * @param path          Path to be checked
225      */
226     public static void checkItemDoesNotExists(final RestconfStrategy strategy,
227                                                final LogicalDatastoreType store, final YangInstanceIdentifier path) {
228         final FluentFuture<Boolean> future = strategy.exists(store, path);
229         final FutureDataFactory<Boolean> response = new FutureDataFactory<>();
230
231         FutureCallbackTx.addCallback(future, POST_TX_TYPE, response);
232
233         if (response.result) {
234             LOG.trace("Operation via Restconf was not executed because data at {} already exists", path);
235             throw new RestconfDocumentedException(
236                 "Data already exists", ErrorType.PROTOCOL, ErrorTag.DATA_EXISTS, path);
237         }
238     }
239 }