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.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;
41 * Util class to post data to DS.
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";
49 private PostDataTransactionUtil() {
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.
57 * @param uriInfo uri info
59 * @param strategy Object that perform the actual DS operations
60 * @param schemaContext reference to actual {@link EffectiveModelContext}
62 * @param insert insert
63 * @return {@link Response}
65 public static Response postData(final UriInfo uriInfo, final NormalizedNodeContext payload,
66 final RestconfStrategy strategy,
67 final EffectiveModelContext schemaContext, final Insert insert,
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();
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}
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();
97 makePost(path, data, schemaContext, strategy);
98 return strategy.commit();
101 PutDataTransactionUtil.checkListAndOrderedType(schemaContext, path);
102 final NormalizedNode<?, ?> readData;
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();
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,
115 return strategy.commit();
117 makePost(path, data, schemaContext, strategy);
118 return strategy.commit();
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();
125 checkItemDoesNotExists(strategy, LogicalDatastoreType.CONFIGURATION, path);
126 insertWithPointPost(path, data, schemaContext, point,
127 (NormalizedNodeContainer<?, ?, NormalizedNode<?, ?>>) readData, true, strategy);
128 return strategy.commit();
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();
135 checkItemDoesNotExists(strategy, LogicalDatastoreType.CONFIGURATION, path);
136 insertWithPointPost(path, data, schemaContext, point,
137 (NormalizedNodeContainer<?, ?, NormalizedNode<?, ?>>) readData, false, strategy);
138 return strategy.commit();
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);
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())) {
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);
173 final YangInstanceIdentifier childPath = parent.node(nodeChild.getIdentifier());
174 strategy.replace(LogicalDatastoreType.CONFIGURATION, childPath, nodeChild, schemaContext);
175 lastInsertedPosition++;
179 private static void makePost(final YangInstanceIdentifier path, final NormalizedNode<?, ?> data,
180 final SchemaContext schemaContext, final RestconfStrategy strategy) {
182 strategy.create(LogicalDatastoreType.CONFIGURATION, path, data, schemaContext);
183 } catch (RestconfDocumentedException e) {
184 // close transaction if any and pass exception further
191 * Get location from {@link YangInstanceIdentifier} and {@link UriInfo}.
193 * @param uriInfo uri info
194 * @param initialPath data path
195 * @param schemaContext reference to {@link SchemaContext}
196 * @return {@link URI}
198 private static URI resolveLocation(final UriInfo uriInfo, final YangInstanceIdentifier initialPath,
199 final EffectiveModelContext schemaContext, final NormalizedNode<?, ?> data) {
200 if (uriInfo == null) {
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());
212 return uriInfo.getBaseUriBuilder()
214 .path(ParserIdentifier.stringFromYangInstanceIdentifier(path, schemaContext))
219 * Check if items do NOT already exists at specified {@code path}. Throws {@link RestconfDocumentedException} if
220 * data already exists.
222 * @param strategy Object that perform the actual DS operations
223 * @param store Datastore
224 * @param path Path to be checked
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<>();
231 FutureCallbackTx.addCallback(future, POST_TX_TYPE, response);
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);