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 javax.ws.rs.core.Response;
14 import javax.ws.rs.core.UriInfo;
15 import org.opendaylight.mdsal.common.api.CommitInfo;
16 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
17 import org.opendaylight.mdsal.dom.api.DOMTransactionChain;
18 import org.opendaylight.restconf.api.query.PointParam;
19 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
20 import org.opendaylight.restconf.nb.rfc8040.WriteDataParams;
21 import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfStrategy;
22 import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfTransaction;
23 import org.opendaylight.restconf.nb.rfc8040.utils.parser.IdentifierCodec;
24 import org.opendaylight.restconf.nb.rfc8040.utils.parser.YangInstanceIdentifierDeserializer;
25 import org.opendaylight.yangtools.yang.common.ErrorTag;
26 import org.opendaylight.yangtools.yang.common.ErrorType;
27 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
28 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
29 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
30 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
31 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
32 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
33 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
34 import org.slf4j.Logger;
35 import org.slf4j.LoggerFactory;
38 * Util class to post data to DS.
40 public final class PostDataTransactionUtil {
41 private static final Logger LOG = LoggerFactory.getLogger(PostDataTransactionUtil.class);
42 private static final String POST_TX_TYPE = "POST";
44 private PostDataTransactionUtil() {
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.
52 * @param uriInfo uri info
55 * @param strategy Object that perform the actual DS operations
56 * @param schemaContext reference to actual {@link EffectiveModelContext}
57 * @param params {@link WriteDataParams}
58 * @return {@link Response}
60 public static Response postData(final UriInfo uriInfo, final YangInstanceIdentifier path, final NormalizedNode data,
61 final RestconfStrategy strategy, final EffectiveModelContext schemaContext, final WriteDataParams params) {
62 TransactionUtil.syncCommit(submitData(path, data, strategy, schemaContext, params), POST_TX_TYPE, path);
63 return Response.created(resolveLocation(uriInfo, path, schemaContext, data)).build();
71 * @param strategy object that perform the actual DS operations
72 * @param schemaContext schema context of data
73 * @param point query parameter
74 * @param insert query parameter
75 * @return {@link FluentFuture}
77 private static ListenableFuture<? extends CommitInfo> submitData(final YangInstanceIdentifier path,
78 final NormalizedNode data, final RestconfStrategy strategy, final EffectiveModelContext schemaContext,
79 final WriteDataParams params) {
80 final var transaction = strategy.prepareWriteExecution();
81 final var insert = params.insert();
83 return makePost(path, data, schemaContext, transaction);
86 final var parentPath = path.coerceParent();
87 PutDataTransactionUtil.checkListAndOrderedType(schemaContext, parentPath);
88 final var grandParentPath = parentPath.coerceParent();
90 return switch (insert) {
92 final var readData = PutDataTransactionUtil.readList(strategy, grandParentPath);
93 if (readData == null || ((NormalizedNodeContainer<?>) readData).isEmpty()) {
94 transaction.replace(path, data, schemaContext);
95 yield transaction.commit();
97 checkItemDoesNotExists(strategy.exists(LogicalDatastoreType.CONFIGURATION, path), path);
98 transaction.remove(grandParentPath);
99 transaction.replace(path, data, schemaContext);
100 transaction.replace(grandParentPath, readData, schemaContext);
101 yield transaction.commit();
103 case LAST -> makePost(path, data, schemaContext, transaction);
105 final var readData = PutDataTransactionUtil.readList(strategy, grandParentPath);
106 if (readData == null || ((NormalizedNodeContainer<?>) readData).isEmpty()) {
107 transaction.replace(path, data, schemaContext);
108 yield transaction.commit();
110 checkItemDoesNotExists(strategy.exists(LogicalDatastoreType.CONFIGURATION, path), path);
111 insertWithPointPost(path, data, schemaContext, params.getPoint(),
112 (NormalizedNodeContainer<?>) readData, true, transaction);
113 yield transaction.commit();
116 final var readData = PutDataTransactionUtil.readList(strategy, grandParentPath);
117 if (readData == null || ((NormalizedNodeContainer<?>) readData).isEmpty()) {
118 transaction.replace(path, data, schemaContext);
119 yield transaction.commit();
121 checkItemDoesNotExists(strategy.exists(LogicalDatastoreType.CONFIGURATION, path), path);
122 insertWithPointPost(path, data, schemaContext, params.getPoint(),
123 (NormalizedNodeContainer<?>) readData, false, transaction);
124 yield transaction.commit();
129 private static void insertWithPointPost(final YangInstanceIdentifier path, final NormalizedNode data,
130 final EffectiveModelContext schemaContext, final PointParam point,
131 final NormalizedNodeContainer<?> readList, final boolean before,
132 final RestconfTransaction transaction) {
133 final YangInstanceIdentifier parent = path.coerceParent().coerceParent();
134 transaction.remove(parent);
135 final var pointArg = YangInstanceIdentifierDeserializer.create(schemaContext, point.value()).path
136 .getLastPathArgument();
137 int lastItemPosition = 0;
138 for (var nodeChild : readList.body()) {
139 if (nodeChild.name().equals(pointArg)) {
147 int lastInsertedPosition = 0;
148 final var emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, parent);
149 transaction.merge(YangInstanceIdentifier.of(emptySubtree.name()), emptySubtree);
150 for (var nodeChild : readList.body()) {
151 if (lastInsertedPosition == lastItemPosition) {
152 transaction.replace(path, data, schemaContext);
154 final YangInstanceIdentifier childPath = parent.node(nodeChild.name());
155 transaction.replace(childPath, nodeChild, schemaContext);
156 lastInsertedPosition++;
160 private static ListenableFuture<? extends CommitInfo> makePost(final YangInstanceIdentifier path,
161 final NormalizedNode data, final EffectiveModelContext schemaContext,
162 final RestconfTransaction transaction) {
164 transaction.create(path, data, schemaContext);
165 } catch (RestconfDocumentedException e) {
166 // close transaction if any and pass exception further
167 transaction.cancel();
171 return transaction.commit();
175 * Get location from {@link YangInstanceIdentifier} and {@link UriInfo}.
177 * @param uriInfo uri info
178 * @param initialPath data path
179 * @param schemaContext reference to {@link SchemaContext}
180 * @return {@link URI}
182 private static URI resolveLocation(final UriInfo uriInfo, final YangInstanceIdentifier initialPath,
183 final EffectiveModelContext schemaContext, final NormalizedNode data) {
184 if (uriInfo == null) {
188 YangInstanceIdentifier path = initialPath;
189 if (data instanceof MapNode mapData) {
190 final var children = mapData.body();
191 if (!children.isEmpty()) {
192 path = path.node(children.iterator().next().name());
196 return uriInfo.getBaseUriBuilder().path("data").path(IdentifierCodec.serialize(path, schemaContext)).build();
200 * Check if items do NOT already exists at specified {@code path}.
202 * @param existsFuture if checked data exists
203 * @param path Path to be checked
204 * @throws RestconfDocumentedException if data already exists.
206 public static void checkItemDoesNotExists(final ListenableFuture<Boolean> existsFuture,
207 final YangInstanceIdentifier path) {
208 if (TransactionUtil.syncAccess(existsFuture, path)) {
209 LOG.trace("Operation via Restconf was not executed because data at {} already exists", path);
210 throw new RestconfDocumentedException("Data already exists", ErrorType.PROTOCOL, ErrorTag.DATA_EXISTS,