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;
11 import com.google.common.util.concurrent.ListenableFuture;
13 import java.util.concurrent.ExecutionException;
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.api.query.InsertParam;
21 import org.opendaylight.restconf.api.query.PointParam;
22 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
23 import org.opendaylight.restconf.nb.rfc8040.WriteDataParams;
24 import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
25 import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfStrategy;
26 import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfTransaction;
27 import org.opendaylight.restconf.nb.rfc8040.utils.parser.IdentifierCodec;
28 import org.opendaylight.restconf.nb.rfc8040.utils.parser.YangInstanceIdentifierDeserializer;
29 import org.opendaylight.yangtools.yang.common.ErrorTag;
30 import org.opendaylight.yangtools.yang.common.ErrorType;
31 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
32 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
33 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
34 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
35 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
36 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
37 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
42 * Util class to post data to DS.
44 public final class PostDataTransactionUtil {
45 private static final Logger LOG = LoggerFactory.getLogger(PostDataTransactionUtil.class);
46 private static final String POST_TX_TYPE = "POST";
48 private PostDataTransactionUtil() {
53 * Check mount point and prepare variables for post data. Close {@link DOMTransactionChain} if any inside of object
54 * {@link RestconfStrategy} provided as a parameter.
56 * @param uriInfo uri info
58 * @param strategy Object that perform the actual DS operations
59 * @param schemaContext reference to actual {@link EffectiveModelContext}
60 * @param params {@link WriteDataParams}
61 * @return {@link Response}
63 public static Response postData(final UriInfo uriInfo, final NormalizedNodePayload payload,
64 final RestconfStrategy strategy,
65 final EffectiveModelContext schemaContext, final WriteDataParams params) {
66 final YangInstanceIdentifier path = payload.getInstanceIdentifierContext().getInstanceIdentifier();
67 final FluentFuture<? extends CommitInfo> future = submitData(path, payload.getData(),
68 strategy, schemaContext, params);
69 final URI location = resolveLocation(uriInfo, path, schemaContext, payload.getData());
70 final ResponseFactory dataFactory = new ResponseFactory(Status.CREATED).location(location);
71 //This method will close transactionChain if any
72 FutureCallbackTx.addCallback(future, POST_TX_TYPE, dataFactory, path);
73 return dataFactory.build();
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}
87 private static FluentFuture<? extends CommitInfo> submitData(final YangInstanceIdentifier path,
88 final NormalizedNode data,
89 final RestconfStrategy strategy,
90 final EffectiveModelContext schemaContext,
91 final WriteDataParams params) {
92 final RestconfTransaction transaction = strategy.prepareWriteExecution();
93 final InsertParam insert = params.insert();
95 makePost(path, data, schemaContext, transaction);
96 return transaction.commit();
99 PutDataTransactionUtil.checkListAndOrderedType(schemaContext, path);
100 final NormalizedNode readData;
103 readData = PutDataTransactionUtil.readList(strategy, path.getParent().getParent());
104 if (readData == null || ((NormalizedNodeContainer<?>) readData).isEmpty()) {
105 transaction.replace(path, data, schemaContext);
106 return transaction.commit();
108 checkItemDoesNotExists(strategy.exists(LogicalDatastoreType.CONFIGURATION, path), path);
109 transaction.remove(path.getParent().getParent());
110 transaction.replace(path, data, schemaContext);
111 transaction.replace(path.getParent().getParent(), readData, schemaContext);
112 return transaction.commit();
114 makePost(path, data, schemaContext, transaction);
115 return transaction.commit();
117 readData = PutDataTransactionUtil.readList(strategy, path.getParent().getParent());
118 if (readData == null || ((NormalizedNodeContainer<?>) readData).isEmpty()) {
119 transaction.replace(path, data, schemaContext);
120 return transaction.commit();
122 checkItemDoesNotExists(strategy.exists(LogicalDatastoreType.CONFIGURATION, path), path);
123 insertWithPointPost(path, data, schemaContext, params.getPoint(),
124 (NormalizedNodeContainer<?>) readData, true, transaction);
125 return transaction.commit();
127 readData = PutDataTransactionUtil.readList(strategy, path.getParent().getParent());
128 if (readData == null || ((NormalizedNodeContainer<?>) readData).isEmpty()) {
129 transaction.replace(path, data, schemaContext);
130 return transaction.commit();
132 checkItemDoesNotExists(strategy.exists(LogicalDatastoreType.CONFIGURATION, path), path);
133 insertWithPointPost(path, data, schemaContext, params.getPoint(),
134 (NormalizedNodeContainer<?>) readData, false, transaction);
135 return transaction.commit();
137 throw new RestconfDocumentedException(
138 "Used bad value of insert parameter. Possible values are first, last, before or after, but was: "
139 + insert, ErrorType.PROTOCOL, ErrorTag.BAD_ATTRIBUTE);
143 private static void insertWithPointPost(final YangInstanceIdentifier path, final NormalizedNode data,
144 final EffectiveModelContext schemaContext, final PointParam point,
145 final NormalizedNodeContainer<?> readList, final boolean before,
146 final RestconfTransaction transaction) {
147 final YangInstanceIdentifier parent = path.getParent().getParent();
148 transaction.remove(parent);
149 final var pointArg = YangInstanceIdentifierDeserializer.create(schemaContext, point.value()).path
150 .getLastPathArgument();
151 int lastItemPosition = 0;
152 for (var nodeChild : readList.body()) {
153 if (nodeChild.name().equals(pointArg)) {
161 int lastInsertedPosition = 0;
162 final var emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, parent);
163 transaction.merge(YangInstanceIdentifier.of(emptySubtree.name()), emptySubtree);
164 for (var nodeChild : readList.body()) {
165 if (lastInsertedPosition == lastItemPosition) {
166 transaction.replace(path, data, schemaContext);
168 final YangInstanceIdentifier childPath = parent.node(nodeChild.name());
169 transaction.replace(childPath, nodeChild, schemaContext);
170 lastInsertedPosition++;
174 private static void makePost(final YangInstanceIdentifier path, final NormalizedNode data,
175 final EffectiveModelContext schemaContext, final RestconfTransaction transaction) {
177 transaction.create(path, data, schemaContext);
178 } catch (RestconfDocumentedException e) {
179 // close transaction if any and pass exception further
180 transaction.cancel();
186 * Get location from {@link YangInstanceIdentifier} and {@link UriInfo}.
188 * @param uriInfo uri info
189 * @param initialPath data path
190 * @param schemaContext reference to {@link SchemaContext}
191 * @return {@link URI}
193 private static URI resolveLocation(final UriInfo uriInfo, final YangInstanceIdentifier initialPath,
194 final EffectiveModelContext schemaContext, final NormalizedNode data) {
195 if (uriInfo == null) {
199 YangInstanceIdentifier path = initialPath;
200 if (data instanceof MapNode mapData) {
201 final var children = mapData.body();
202 if (!children.isEmpty()) {
203 path = path.node(children.iterator().next().name());
207 return uriInfo.getBaseUriBuilder().path("data").path(IdentifierCodec.serialize(path, schemaContext)).build();
211 * Check if items do NOT already exists at specified {@code path}.
213 * @param existsFuture if checked data exists
214 * @param path Path to be checked
215 * @throws RestconfDocumentedException if data already exists.
217 public static void checkItemDoesNotExists(final ListenableFuture<Boolean> existsFuture,
218 final YangInstanceIdentifier path) {
219 final boolean exists;
221 exists = existsFuture.get();
222 } catch (ExecutionException e) {
223 throw new RestconfDocumentedException("Failed to access " + path, e);
224 } catch (InterruptedException e) {
225 Thread.currentThread().interrupt();
226 throw new RestconfDocumentedException("Interrupted while accessing " + path, e);
230 LOG.trace("Operation via Restconf was not executed because data at {} already exists", path);
231 throw new RestconfDocumentedException(
232 "Data already exists", ErrorType.PROTOCOL, ErrorTag.DATA_EXISTS, path);