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.errors.RestconfDocumentedException;
22 import org.opendaylight.restconf.nb.rfc8040.InsertParam;
23 import org.opendaylight.restconf.nb.rfc8040.PointParam;
24 import org.opendaylight.restconf.nb.rfc8040.WriteDataParams;
25 import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
26 import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfStrategy;
27 import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfTransaction;
28 import org.opendaylight.restconf.nb.rfc8040.utils.parser.IdentifierCodec;
29 import org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserIdentifier;
30 import org.opendaylight.yangtools.yang.common.ErrorTag;
31 import org.opendaylight.yangtools.yang.common.ErrorType;
32 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
33 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
34 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
35 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
36 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
37 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
38 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
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.
46 public final class PostDataTransactionUtil {
47 private static final Logger LOG = LoggerFactory.getLogger(PostDataTransactionUtil.class);
48 private static final String POST_TX_TYPE = "POST";
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}
62 * @param params {@link WriteDataParams}
63 * @return {@link Response}
65 public static Response postData(final UriInfo uriInfo, final NormalizedNodePayload payload,
66 final RestconfStrategy strategy,
67 final EffectiveModelContext schemaContext, final WriteDataParams params) {
68 final YangInstanceIdentifier path = payload.getInstanceIdentifierContext().getInstanceIdentifier();
69 final FluentFuture<? extends CommitInfo> future = submitData(path, payload.getData(),
70 strategy, schemaContext, params);
71 final URI location = resolveLocation(uriInfo, path, schemaContext, payload.getData());
72 final ResponseFactory dataFactory = new ResponseFactory(Status.CREATED).location(location);
73 //This method will close transactionChain if any
74 FutureCallbackTx.addCallback(future, POST_TX_TYPE, dataFactory, path);
75 return dataFactory.build();
83 * @param strategy object that perform the actual DS operations
84 * @param schemaContext schema context of data
85 * @param point query parameter
86 * @param insert query parameter
87 * @return {@link FluentFuture}
89 private static FluentFuture<? extends CommitInfo> submitData(final YangInstanceIdentifier path,
90 final NormalizedNode data,
91 final RestconfStrategy strategy,
92 final EffectiveModelContext schemaContext,
93 final WriteDataParams params) {
94 final RestconfTransaction transaction = strategy.prepareWriteExecution();
95 final InsertParam insert = params.insert();
97 makePost(path, data, schemaContext, transaction);
98 return transaction.commit();
101 PutDataTransactionUtil.checkListAndOrderedType(schemaContext, path);
102 final NormalizedNode readData;
105 readData = PutDataTransactionUtil.readList(strategy, path.getParent().getParent());
106 if (readData == null || ((NormalizedNodeContainer<?>) readData).isEmpty()) {
107 transaction.replace(path, data, schemaContext);
108 return transaction.commit();
110 checkItemDoesNotExists(strategy.exists(LogicalDatastoreType.CONFIGURATION, path), path);
111 transaction.remove(path.getParent().getParent());
112 transaction.replace(path, data, schemaContext);
113 transaction.replace(path.getParent().getParent(), readData, schemaContext);
114 return transaction.commit();
116 makePost(path, data, schemaContext, transaction);
117 return transaction.commit();
119 readData = PutDataTransactionUtil.readList(strategy, path.getParent().getParent());
120 if (readData == null || ((NormalizedNodeContainer<?>) readData).isEmpty()) {
121 transaction.replace(path, data, schemaContext);
122 return transaction.commit();
124 checkItemDoesNotExists(strategy.exists(LogicalDatastoreType.CONFIGURATION, path), path);
125 insertWithPointPost(path, data, schemaContext, params.getPoint(),
126 (NormalizedNodeContainer<?>) readData, true, transaction);
127 return transaction.commit();
129 readData = PutDataTransactionUtil.readList(strategy, path.getParent().getParent());
130 if (readData == null || ((NormalizedNodeContainer<?>) readData).isEmpty()) {
131 transaction.replace(path, data, schemaContext);
132 return transaction.commit();
134 checkItemDoesNotExists(strategy.exists(LogicalDatastoreType.CONFIGURATION, path), path);
135 insertWithPointPost(path, data, schemaContext, params.getPoint(),
136 (NormalizedNodeContainer<?>) readData, false, transaction);
137 return transaction.commit();
139 throw new RestconfDocumentedException(
140 "Used bad value of insert parameter. Possible values are first, last, before or after, but was: "
141 + insert, ErrorType.PROTOCOL, ErrorTag.BAD_ATTRIBUTE);
145 private static void insertWithPointPost(final YangInstanceIdentifier path, final NormalizedNode data,
146 final EffectiveModelContext schemaContext, final PointParam point,
147 final NormalizedNodeContainer<?> readList, final boolean before,
148 final RestconfTransaction transaction) {
149 final YangInstanceIdentifier parent = path.getParent().getParent();
150 transaction.remove(parent);
151 final InstanceIdentifierContext instanceIdentifier =
152 // FIXME: Point should be able to give us this method
153 ParserIdentifier.toInstanceIdentifier(point.value(), schemaContext, Optional.empty());
154 int lastItemPosition = 0;
155 for (final NormalizedNode nodeChild : readList.body()) {
156 if (nodeChild.getIdentifier().equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
164 int lastInsertedPosition = 0;
165 final NormalizedNode emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, parent);
166 transaction.merge(YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
167 for (final NormalizedNode nodeChild : readList.body()) {
168 if (lastInsertedPosition == lastItemPosition) {
169 transaction.replace(path, data, schemaContext);
171 final YangInstanceIdentifier childPath = parent.node(nodeChild.getIdentifier());
172 transaction.replace(childPath, nodeChild, schemaContext);
173 lastInsertedPosition++;
177 private static void makePost(final YangInstanceIdentifier path, final NormalizedNode data,
178 final EffectiveModelContext schemaContext, final RestconfTransaction transaction) {
180 transaction.create(path, data, schemaContext);
181 } catch (RestconfDocumentedException e) {
182 // close transaction if any and pass exception further
183 transaction.cancel();
189 * Get location from {@link YangInstanceIdentifier} and {@link UriInfo}.
191 * @param uriInfo uri info
192 * @param initialPath data path
193 * @param schemaContext reference to {@link SchemaContext}
194 * @return {@link URI}
196 private static URI resolveLocation(final UriInfo uriInfo, final YangInstanceIdentifier initialPath,
197 final EffectiveModelContext schemaContext, final NormalizedNode data) {
198 if (uriInfo == null) {
202 YangInstanceIdentifier path = initialPath;
203 if (data instanceof MapNode) {
204 final Collection<MapEntryNode> children = ((MapNode) data).body();
205 if (!children.isEmpty()) {
206 path = path.node(children.iterator().next().getIdentifier());
210 return uriInfo.getBaseUriBuilder().path("data").path(IdentifierCodec.serialize(path, schemaContext)).build();
214 * Check if items do NOT already exists at specified {@code path}. Throws {@link RestconfDocumentedException} if
215 * data already exists.
217 * @param isExistsFuture if checked data exists
218 * @param path Path to be checked
220 public static void checkItemDoesNotExists(final FluentFuture<Boolean> isExistsFuture,
221 final YangInstanceIdentifier path) {
222 final FutureDataFactory<Boolean> response = new FutureDataFactory<>();
223 FutureCallbackTx.addCallback(isExistsFuture, POST_TX_TYPE, response);
225 if (response.result) {
226 LOG.trace("Operation via Restconf was not executed because data at {} already exists", path);
227 throw new RestconfDocumentedException(
228 "Data already exists", ErrorType.PROTOCOL, ErrorTag.DATA_EXISTS, path);