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 java.util.HashMap;
12 import java.util.List;
14 import java.util.Optional;
15 import javax.ws.rs.core.Response;
16 import javax.ws.rs.core.Response.Status;
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.common.QName;
30 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
31 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
32 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
33 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
34 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
35 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
36 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
37 import org.opendaylight.yangtools.yang.data.util.DataSchemaContextNode;
38 import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
39 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
40 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
41 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
42 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
43 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
44 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
47 * Util class for put data to DS.
50 public final class PutDataTransactionUtil {
51 private static final String PUT_TX_TYPE = "PUT";
53 private PutDataTransactionUtil() {
57 * Valid input data with {@link SchemaNode}.
59 * @param schemaNode {@link SchemaNode}
60 * @param payload input data
62 public static void validInputData(final SchemaNode schemaNode, final NormalizedNodeContext payload) {
63 if (schemaNode != null && payload.getData() == null) {
64 throw new RestconfDocumentedException("Input is required.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
65 } else if (schemaNode == null && payload.getData() != null) {
66 throw new RestconfDocumentedException("No input expected.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
71 * Valid top level node name.
73 * @param path path of node
76 public static void validTopLevelNodeName(final YangInstanceIdentifier path, final NormalizedNodeContext payload) {
77 final String payloadName = payload.getData().getNodeType().getLocalName();
80 if (!payload.getData().getNodeType().equals(RestconfDataServiceConstant.NETCONF_BASE_QNAME)) {
81 throw new RestconfDocumentedException("Instance identifier has to contain at least one path argument",
82 ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
85 final String identifierName = path.getLastPathArgument().getNodeType().getLocalName();
86 if (!payloadName.equals(identifierName)) {
87 throw new RestconfDocumentedException(
88 "Payload name (" + payloadName + ") is different from identifier name (" + identifierName + ")",
89 ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
95 * Validates whether keys in {@code payload} are equal to values of keys in
96 * {@code iiWithData} for list schema node.
98 * @throws RestconfDocumentedException if key values or key count in payload and URI isn't equal
100 public static void validateListKeysEqualityInPayloadAndUri(final NormalizedNodeContext payload) {
101 final InstanceIdentifierContext<?> iiWithData = payload.getInstanceIdentifierContext();
102 final PathArgument lastPathArgument = iiWithData.getInstanceIdentifier().getLastPathArgument();
103 final SchemaNode schemaNode = iiWithData.getSchemaNode();
104 final NormalizedNode<?, ?> data = payload.getData();
105 if (schemaNode instanceof ListSchemaNode) {
106 final List<QName> keyDefinitions = ((ListSchemaNode) schemaNode).getKeyDefinition();
107 if (lastPathArgument instanceof NodeIdentifierWithPredicates && data instanceof MapEntryNode) {
108 final Map<QName, Object> uriKeyValues = ((NodeIdentifierWithPredicates) lastPathArgument).asMap();
109 isEqualUriAndPayloadKeyValues(uriKeyValues, (MapEntryNode) data, keyDefinitions);
114 private static void isEqualUriAndPayloadKeyValues(final Map<QName, Object> uriKeyValues, final MapEntryNode payload,
115 final List<QName> keyDefinitions) {
116 final Map<QName, Object> mutableCopyUriKeyValues = new HashMap<>(uriKeyValues);
117 for (final QName keyDefinition : keyDefinitions) {
118 final Object uriKeyValue = RestconfDocumentedException.throwIfNull(
119 mutableCopyUriKeyValues.remove(keyDefinition), ErrorType.PROTOCOL, ErrorTag.DATA_MISSING,
120 "Missing key %s in URI.", keyDefinition);
122 final Object dataKeyValue = payload.getIdentifier().getValue(keyDefinition);
124 if (!uriKeyValue.equals(dataKeyValue)) {
125 final String errMsg = "The value '" + uriKeyValue + "' for key '" + keyDefinition.getLocalName()
126 + "' specified in the URI doesn't match the value '" + dataKeyValue
127 + "' specified in the message body. ";
128 throw new RestconfDocumentedException(errMsg, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
134 * Check mount point and prepare variables for put data to DS. Close {@link DOMTransactionChain} if any
135 * inside of object {@link RestconfStrategy} provided as a parameter if any.
137 * @param payload data to put
138 * @param schemaContext reference to {@link EffectiveModelContext}
139 * @param strategy object that perform the actual DS operations
140 * @param point query parameter
141 * @param insert query parameter
142 * @return {@link Response}
144 public static Response putData(final NormalizedNodeContext payload, final EffectiveModelContext schemaContext,
145 final RestconfStrategy strategy, final Insert insert, final String point) {
146 final YangInstanceIdentifier path = payload.getInstanceIdentifierContext().getInstanceIdentifier();
148 strategy.prepareReadWriteExecution();
149 final FluentFuture<Boolean> existsFuture = strategy.exists(LogicalDatastoreType.CONFIGURATION, path);
150 final FluentFuture<? extends CommitInfo> submitData = submitData(path, schemaContext, strategy,
151 payload.getData(), insert, point);
152 final ResponseFactory response = new ResponseFactory();
153 //This method will close transactionChain if any
154 FutureCallbackTx.addCallback(submitData, PUT_TX_TYPE, response, strategy.getTransactionChain());
156 final FutureDataFactory<Boolean> isExists = new FutureDataFactory<>();
157 FutureCallbackTx.addCallback(existsFuture, PUT_TX_TYPE, isExists);
158 return response.status(isExists.result ? Status.NO_CONTENT : Status.CREATED).build();
164 * @param path path of data
165 * @param schemaContext {@link SchemaContext}
166 * @param strategy object that perform the actual DS operations
168 * @param point query parameter
169 * @param insert query parameter
170 * @return {@link FluentFuture}
172 private static FluentFuture<? extends CommitInfo> submitData(final YangInstanceIdentifier path,
173 final EffectiveModelContext schemaContext,
174 final RestconfStrategy strategy,
175 final NormalizedNode<?, ?> data,
176 final Insert insert, final String point) {
177 if (insert == null) {
178 return makePut(path, schemaContext, strategy, data);
181 checkListAndOrderedType(schemaContext, path);
182 final NormalizedNode<?, ?> readData;
185 readData = readList(strategy, path.getParent());
186 if (readData == null || ((NormalizedNodeContainer<?, ?, ?>) readData).getValue().isEmpty()) {
187 return makePut(path, schemaContext, strategy, data);
189 strategy.remove(LogicalDatastoreType.CONFIGURATION, path.getParent());
190 strategy.replace(LogicalDatastoreType.CONFIGURATION, path, data, schemaContext);
191 strategy.replace(LogicalDatastoreType.CONFIGURATION, path.getParent(), readData, schemaContext);
192 return strategy.commit();
194 return makePut(path, schemaContext, strategy, data);
196 readData = readList(strategy, path.getParent());
197 if (readData == null || ((NormalizedNodeContainer<?, ?, ?>) readData).getValue().isEmpty()) {
198 return makePut(path, schemaContext, strategy, data);
200 insertWithPointPut(strategy, path, data, schemaContext, point,
201 (NormalizedNodeContainer<?, ?, NormalizedNode<?, ?>>) readData, true);
202 return strategy.commit();
204 readData = readList(strategy, path.getParent());
205 if (readData == null || ((NormalizedNodeContainer<?, ?, ?>) readData).getValue().isEmpty()) {
206 return makePut(path, schemaContext, strategy, data);
208 insertWithPointPut(strategy, path, data, schemaContext, point,
209 (NormalizedNodeContainer<?, ?, NormalizedNode<?, ?>>) readData, false);
210 return strategy.commit();
212 throw new RestconfDocumentedException(
213 "Used bad value of insert parameter. Possible values are first, last, before or after, "
214 + "but was: " + insert, RestconfError.ErrorType.PROTOCOL, ErrorTag.BAD_ATTRIBUTE);
218 // FIXME: this method is only called from a context where we are modifying data. This should be part of strategy,
219 // requiring an already-open transaction. It also must return a future, so it can be properly composed.
220 static NormalizedNode<?, ?> readList(final RestconfStrategy strategy, final YangInstanceIdentifier path) {
221 return ReadDataTransactionUtil.readDataViaTransaction(strategy, LogicalDatastoreType.CONFIGURATION,
225 private static void insertWithPointPut(final RestconfStrategy strategy,
226 final YangInstanceIdentifier path,
227 final NormalizedNode<?, ?> data,
228 final EffectiveModelContext schemaContext, final String point,
229 final NormalizedNodeContainer<?, ?, NormalizedNode<?, ?>> readList,
230 final boolean before) {
231 strategy.remove(LogicalDatastoreType.CONFIGURATION, path.getParent());
232 final InstanceIdentifierContext<?> instanceIdentifier =
233 ParserIdentifier.toInstanceIdentifier(point, schemaContext, Optional.empty());
234 int lastItemPosition = 0;
235 for (final NormalizedNode<?, ?> nodeChild : readList.getValue()) {
236 if (nodeChild.getIdentifier().equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
244 int lastInsertedPosition = 0;
245 final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path.getParent());
246 strategy.merge(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.create(emptySubtree.getIdentifier()),
248 for (final NormalizedNode<?, ?> nodeChild : readList.getValue()) {
249 if (lastInsertedPosition == lastItemPosition) {
250 strategy.replace(LogicalDatastoreType.CONFIGURATION, path, data, schemaContext);
252 final YangInstanceIdentifier childPath = path.getParent().node(nodeChild.getIdentifier());
253 strategy.replace(LogicalDatastoreType.CONFIGURATION, childPath, nodeChild, schemaContext);
254 lastInsertedPosition++;
258 private static FluentFuture<? extends CommitInfo> makePut(final YangInstanceIdentifier path,
259 final SchemaContext schemaContext,
260 final RestconfStrategy strategy,
261 final NormalizedNode<?, ?> data) {
262 strategy.replace(LogicalDatastoreType.CONFIGURATION, path, data, schemaContext);
263 return strategy.commit();
266 public static DataSchemaNode checkListAndOrderedType(final EffectiveModelContext ctx,
267 final YangInstanceIdentifier path) {
268 final YangInstanceIdentifier parent = path.getParent();
269 final DataSchemaContextNode<?> node = DataSchemaContextTree.from(ctx).findChild(parent).orElseThrow();
270 final DataSchemaNode dataSchemaNode = node.getDataSchemaNode();
272 if (dataSchemaNode instanceof ListSchemaNode) {
273 if (!((ListSchemaNode) dataSchemaNode).isUserOrdered()) {
274 throw new RestconfDocumentedException("Insert parameter can be used only with ordered-by user list.",
275 RestconfError.ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT);
277 return dataSchemaNode;
279 if (dataSchemaNode instanceof LeafListSchemaNode) {
280 if (!((LeafListSchemaNode) dataSchemaNode).isUserOrdered()) {
281 throw new RestconfDocumentedException(
282 "Insert parameter can be used only with ordered-by user leaf-list.",
283 RestconfError.ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT);
285 return dataSchemaNode;
287 throw new RestconfDocumentedException("Insert parameter can be used only with list or leaf-list",
288 RestconfError.ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT);