2 * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.restconf.nb.rfc8040.rests.utils;
10 import com.google.common.util.concurrent.FluentFuture;
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;
44 * Util class to post data to DS.
47 public final class PostDataTransactionUtil {
48 private static final Logger LOG = LoggerFactory.getLogger(PostDataTransactionUtil.class);
50 private PostDataTransactionUtil() {
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.
58 * @param uriInfo uri info
60 * @param strategy Object that perform the actual DS operations
61 * @param schemaContext reference to actual {@link EffectiveModelContext}
63 * @param insert insert
64 * @return {@link Response}
66 public static Response postData(final UriInfo uriInfo, final NormalizedNodeContext payload,
67 final RestconfStrategy strategy,
68 final EffectiveModelContext schemaContext, final String insert,
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();
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}
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();
99 makePost(path, data, schemaContext, strategy);
100 return strategy.commit();
103 final DataSchemaNode schemaNode = PutDataTransactionUtil.checkListAndOrderedType(schemaContext, path);
106 if (schemaNode instanceof ListSchemaNode) {
107 final NormalizedNode<?, ?> readData = PutDataTransactionUtil.readList(path.getParent(),
108 schemaContext, strategy, schemaNode);
109 final OrderedMapNode readList = (OrderedMapNode) readData;
110 if (readList == null || readList.getValue().isEmpty()) {
111 makePost(path, data, schemaContext, strategy);
112 return strategy.commit();
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();
120 final NormalizedNode<?, ?> readData = PutDataTransactionUtil.readList(path.getParent(),
121 schemaContext, strategy, schemaNode);
123 final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
124 if (readLeafList == null || readLeafList.getValue().isEmpty()) {
125 makePost(path, data, schemaContext, strategy);
126 return strategy.commit();
129 strategy.delete(LogicalDatastoreType.CONFIGURATION, path.getParent().getParent());
130 simplePost(LogicalDatastoreType.CONFIGURATION, path, data, schemaContext, strategy);
131 makePost(path, readData, schemaContext, strategy);
132 return strategy.commit();
135 makePost(path, data, schemaContext, strategy);
136 return strategy.commit();
138 if (schemaNode instanceof ListSchemaNode) {
139 final NormalizedNode<?, ?> readData = PutDataTransactionUtil.readList(path.getParent(),
140 schemaContext, strategy, schemaNode);
141 final OrderedMapNode readList = (OrderedMapNode) readData;
142 if (readList == null || readList.getValue().isEmpty()) {
143 makePost(path, data, schemaContext, strategy);
144 return strategy.commit();
147 insertWithPointListPost(LogicalDatastoreType.CONFIGURATION, path,
148 data, schemaContext, point, readList, true, strategy);
149 return strategy.commit();
151 final NormalizedNode<?, ?> readData = PutDataTransactionUtil.readList(path.getParent(),
152 schemaContext, strategy, schemaNode);
154 final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
155 if (readLeafList == null || readLeafList.getValue().isEmpty()) {
156 makePost(path, data, schemaContext, strategy);
157 return strategy.commit();
160 insertWithPointLeafListPost(LogicalDatastoreType.CONFIGURATION,
161 path, data, schemaContext, point, readLeafList, true, strategy);
162 return strategy.commit();
165 if (schemaNode instanceof ListSchemaNode) {
166 final NormalizedNode<?, ?> readData = PutDataTransactionUtil.readList(path.getParent(),
167 schemaContext, strategy, schemaNode);
168 final OrderedMapNode readList = (OrderedMapNode) readData;
169 if (readList == null || readList.getValue().isEmpty()) {
170 makePost(path, data, schemaContext, strategy);
171 return strategy.commit();
174 insertWithPointListPost(LogicalDatastoreType.CONFIGURATION, path,
175 data, schemaContext, point, readList, false, strategy);
176 return strategy.commit();
178 final NormalizedNode<?, ?> readData = PutDataTransactionUtil.readList(path.getParent(),
179 schemaContext, strategy, schemaNode);
181 final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
182 if (readLeafList == null || readLeafList.getValue().isEmpty()) {
183 makePost(path, data, schemaContext, strategy);
184 return strategy.commit();
187 insertWithPointLeafListPost(LogicalDatastoreType.CONFIGURATION,
188 path, data, schemaContext, point, readLeafList, true, strategy);
189 return strategy.commit();
192 throw new RestconfDocumentedException(
193 "Used bad value of insert parameter. Possible values are first, last, before or after, but was: "
194 + insert, RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.BAD_ATTRIBUTE);
198 private static void insertWithPointLeafListPost(final LogicalDatastoreType datastore,
199 final YangInstanceIdentifier path,
200 final NormalizedNode<?, ?> payload,
201 final EffectiveModelContext schemaContext, final String point,
202 final OrderedLeafSetNode<?> readLeafList,
203 final boolean before, final RestconfStrategy strategy) {
204 strategy.delete(datastore, path.getParent().getParent());
205 final InstanceIdentifierContext<?> instanceIdentifier =
206 ParserIdentifier.toInstanceIdentifier(point, schemaContext, Optional.empty());
207 int lastItemPosition = 0;
208 for (final LeafSetEntryNode<?> nodeChild : readLeafList.getValue()) {
209 if (nodeChild.getIdentifier().equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
217 int lastInsertedPosition = 0;
218 final NormalizedNode<?, ?> emptySubtree =
219 ImmutableNodes.fromInstanceId(schemaContext, path.getParent().getParent());
220 strategy.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
221 for (final LeafSetEntryNode<?> nodeChild : readLeafList.getValue()) {
222 if (lastInsertedPosition == lastItemPosition) {
223 checkItemDoesNotExists(strategy, datastore, path, RestconfDataServiceConstant.PostData.POST_TX_TYPE);
224 strategy.create(datastore, path, payload);
226 final YangInstanceIdentifier childPath = path.getParent().getParent().node(nodeChild.getIdentifier());
227 checkItemDoesNotExists(strategy, datastore, childPath, RestconfDataServiceConstant.PostData.POST_TX_TYPE);
228 strategy.create(datastore, childPath, nodeChild);
229 lastInsertedPosition++;
233 private static void insertWithPointListPost(final LogicalDatastoreType datastore, final YangInstanceIdentifier path,
234 final NormalizedNode<?, ?> payload,
235 final EffectiveModelContext schemaContext, final String point,
236 final MapNode readList, final boolean before,
237 final RestconfStrategy strategy) {
238 strategy.delete(datastore, path.getParent().getParent());
239 final InstanceIdentifierContext<?> instanceIdentifier =
240 ParserIdentifier.toInstanceIdentifier(point, schemaContext, Optional.empty());
241 int lastItemPosition = 0;
242 for (final MapEntryNode mapEntryNode : readList.getValue()) {
243 if (mapEntryNode.getIdentifier().equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
251 int lastInsertedPosition = 0;
252 final NormalizedNode<?, ?> emptySubtree =
253 ImmutableNodes.fromInstanceId(schemaContext, path.getParent().getParent());
254 strategy.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
255 for (final MapEntryNode mapEntryNode : readList.getValue()) {
256 if (lastInsertedPosition == lastItemPosition) {
257 checkItemDoesNotExists(strategy, datastore, path, RestconfDataServiceConstant.PostData.POST_TX_TYPE);
258 strategy.create(datastore, path, payload);
260 final YangInstanceIdentifier childPath = path.getParent().getParent().node(mapEntryNode.getIdentifier());
261 checkItemDoesNotExists(strategy, datastore, childPath, RestconfDataServiceConstant.PostData.POST_TX_TYPE);
262 strategy.create(datastore, childPath, mapEntryNode);
263 lastInsertedPosition++;
267 private static void makePost(final YangInstanceIdentifier path, final NormalizedNode<?, ?> data,
268 final SchemaContext schemaContext, final RestconfStrategy strategy) {
269 if (data instanceof MapNode) {
270 boolean merge = false;
271 for (final MapEntryNode child : ((MapNode) data).getValue()) {
272 final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
273 checkItemDoesNotExists(strategy, LogicalDatastoreType.CONFIGURATION, childPath,
274 RestconfDataServiceConstant.PostData.POST_TX_TYPE);
277 TransactionUtil.ensureParentsByMerge(path, schemaContext, strategy);
278 final NormalizedNode<?, ?> emptySubTree = ImmutableNodes.fromInstanceId(schemaContext, path);
279 strategy.merge(LogicalDatastoreType.CONFIGURATION,
280 YangInstanceIdentifier.create(emptySubTree.getIdentifier()), emptySubTree);
282 strategy.create(LogicalDatastoreType.CONFIGURATION, childPath, child);
285 checkItemDoesNotExists(strategy, LogicalDatastoreType.CONFIGURATION, path,
286 RestconfDataServiceConstant.PostData.POST_TX_TYPE);
288 TransactionUtil.ensureParentsByMerge(path, schemaContext, strategy);
289 strategy.create(LogicalDatastoreType.CONFIGURATION, path, data);
294 * Get location from {@link YangInstanceIdentifier} and {@link UriInfo}.
296 * @param uriInfo uri info
297 * @param initialPath data path
298 * @param schemaContext reference to {@link SchemaContext}
299 * @return {@link URI}
301 private static URI resolveLocation(final UriInfo uriInfo, final YangInstanceIdentifier initialPath,
302 final EffectiveModelContext schemaContext, final NormalizedNode<?, ?> data) {
303 if (uriInfo == null) {
307 YangInstanceIdentifier path = initialPath;
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());
315 return uriInfo.getBaseUriBuilder()
317 .path(ParserIdentifier.stringFromYangInstanceIdentifier(path, schemaContext))
321 private static void simplePost(final LogicalDatastoreType datastore, final YangInstanceIdentifier path,
322 final NormalizedNode<?, ?> payload,
323 final SchemaContext schemaContext, final RestconfStrategy strategy) {
324 checkItemDoesNotExists(strategy, datastore, path, RestconfDataServiceConstant.PostData.POST_TX_TYPE);
325 TransactionUtil.ensureParentsByMerge(path, schemaContext, strategy);
326 strategy.create(datastore, path, payload);
331 * Check if items do NOT already exists at specified {@code path}. Throws {@link RestconfDocumentedException} if
332 * data already exists.
334 * @param strategy Object that perform the actual DS operations
335 * @param store Datastore
336 * @param path Path to be checked
337 * @param operationType Type of operation (READ, POST, PUT, DELETE...)
339 private static void checkItemDoesNotExists(final RestconfStrategy strategy,
340 final LogicalDatastoreType store, final YangInstanceIdentifier path,
341 final String operationType) {
342 final FluentFuture<Boolean> future = strategy.exists(store, path);
343 final FutureDataFactory<Boolean> response = new FutureDataFactory<>();
345 FutureCallbackTx.addCallback(future, operationType, response);
347 if (response.result) {
351 LOG.trace("Operation via Restconf was not executed because data at {} already exists", path);
352 throw new RestconfDocumentedException(
353 "Data already exists", ErrorType.PROTOCOL, ErrorTag.DATA_EXISTS, path);