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.utils.parser.ParserIdentifier;
28 import org.opendaylight.yangtools.yang.common.QName;
29 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
30 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
31 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
32 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
33 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
34 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
35 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
36 import org.opendaylight.yangtools.yang.data.api.schema.OrderedLeafSetNode;
37 import org.opendaylight.yangtools.yang.data.api.schema.OrderedMapNode;
38 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
39 import org.opendaylight.yangtools.yang.data.util.DataSchemaContextNode;
40 import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
41 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
42 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
43 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
44 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
45 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
46 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
49 * Util class for put data to DS.
52 public final class PutDataTransactionUtil {
54 private PutDataTransactionUtil() {
58 * Valid input data with {@link SchemaNode}.
60 * @param schemaNode {@link SchemaNode}
61 * @param payload input data
63 public static void validInputData(final SchemaNode schemaNode, final NormalizedNodeContext payload) {
64 if (schemaNode != null && payload.getData() == null) {
65 throw new RestconfDocumentedException("Input is required.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
66 } else if (schemaNode == null && payload.getData() != null) {
67 throw new RestconfDocumentedException("No input expected.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
72 * Valid top level node name.
74 * @param path path of node
77 public static void validTopLevelNodeName(final YangInstanceIdentifier path, final NormalizedNodeContext payload) {
78 final String payloadName = payload.getData().getNodeType().getLocalName();
81 if (!payload.getData().getNodeType().equals(RestconfDataServiceConstant.NETCONF_BASE_QNAME)) {
82 throw new RestconfDocumentedException("Instance identifier has to contain at least one path argument",
83 ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
86 final String identifierName = path.getLastPathArgument().getNodeType().getLocalName();
87 if (!payloadName.equals(identifierName)) {
88 throw new RestconfDocumentedException(
89 "Payload name (" + payloadName + ") is different from identifier name (" + identifierName + ")",
90 ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
96 * Validates whether keys in {@code payload} are equal to values of keys in
97 * {@code iiWithData} for list schema node.
99 * @throws RestconfDocumentedException if key values or key count in payload and URI isn't equal
101 public static void validateListKeysEqualityInPayloadAndUri(final NormalizedNodeContext payload) {
102 final InstanceIdentifierContext<?> iiWithData = payload.getInstanceIdentifierContext();
103 final PathArgument lastPathArgument = iiWithData.getInstanceIdentifier().getLastPathArgument();
104 final SchemaNode schemaNode = iiWithData.getSchemaNode();
105 final NormalizedNode<?, ?> data = payload.getData();
106 if (schemaNode instanceof ListSchemaNode) {
107 final List<QName> keyDefinitions = ((ListSchemaNode) schemaNode).getKeyDefinition();
108 if (lastPathArgument instanceof NodeIdentifierWithPredicates && data instanceof MapEntryNode) {
109 final Map<QName, Object> uriKeyValues = ((NodeIdentifierWithPredicates) lastPathArgument).asMap();
110 isEqualUriAndPayloadKeyValues(uriKeyValues, (MapEntryNode) data, keyDefinitions);
115 private static void isEqualUriAndPayloadKeyValues(final Map<QName, Object> uriKeyValues, final MapEntryNode payload,
116 final List<QName> keyDefinitions) {
117 final Map<QName, Object> mutableCopyUriKeyValues = new HashMap<>(uriKeyValues);
118 for (final QName keyDefinition : keyDefinitions) {
119 final Object uriKeyValue = RestconfDocumentedException.throwIfNull(
120 mutableCopyUriKeyValues.remove(keyDefinition), ErrorType.PROTOCOL, ErrorTag.DATA_MISSING,
121 "Missing key %s in URI.", keyDefinition);
123 final Object dataKeyValue = payload.getIdentifier().getValue(keyDefinition);
125 if (!uriKeyValue.equals(dataKeyValue)) {
126 final String errMsg = "The value '" + uriKeyValue + "' for key '" + keyDefinition.getLocalName()
127 + "' specified in the URI doesn't match the value '" + dataKeyValue
128 + "' specified in the message body. ";
129 throw new RestconfDocumentedException(errMsg, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
135 * Check mount point and prepare variables for put data to DS. Close {@link DOMTransactionChain} if any
136 * inside of object {@link RestconfStrategy} provided as a parameter if any.
138 * @param payload data to put
139 * @param schemaContext reference to {@link EffectiveModelContext}
140 * @param strategy object that perform the actual DS operations
141 * @param point query parameter
142 * @param insert query parameter
143 * @return {@link Response}
145 public static Response putData(final NormalizedNodeContext payload, final EffectiveModelContext schemaContext,
146 final RestconfStrategy strategy, final String insert, final String point) {
147 final YangInstanceIdentifier path = payload.getInstanceIdentifierContext().getInstanceIdentifier();
149 strategy.prepareReadWriteExecution();
150 final FluentFuture<Boolean> existsFuture = strategy.exists(LogicalDatastoreType.CONFIGURATION, path);
151 final FutureDataFactory<Boolean> existsResponse = new FutureDataFactory<>();
152 FutureCallbackTx.addCallback(existsFuture, RestconfDataServiceConstant.PutData.PUT_TX_TYPE, existsResponse);
154 final ResponseFactory responseFactory =
155 new ResponseFactory(existsResponse.result ? Status.NO_CONTENT : Status.CREATED);
156 final FluentFuture<? extends CommitInfo> submitData = submitData(path, schemaContext, strategy,
157 payload.getData(), insert, point, existsResponse.result);
158 //This method will close transactionChain if any
159 FutureCallbackTx.addCallback(submitData, RestconfDataServiceConstant.PutData.PUT_TX_TYPE, responseFactory,
160 strategy.getTransactionChain());
161 return responseFactory.build();
167 * @param path path of data
168 * @param schemaContext {@link SchemaContext}
169 * @param strategy object that perform the actual DS operations
171 * @param point query parameter
172 * @param insert query parameter
173 * @return {@link FluentFuture}
175 private static FluentFuture<? extends CommitInfo> submitData(
176 final YangInstanceIdentifier path,
177 final EffectiveModelContext schemaContext,
178 final RestconfStrategy strategy,
179 final NormalizedNode<?, ?> data, final String insert, final String point, final boolean exists) {
180 if (insert == null) {
181 return makePut(path, schemaContext, strategy, data, exists);
184 final DataSchemaNode schemaNode = checkListAndOrderedType(schemaContext, path);
187 if (schemaNode instanceof ListSchemaNode) {
188 final NormalizedNode<?, ?> readData = readList(path, schemaContext, strategy, schemaNode);
189 final OrderedMapNode readList = (OrderedMapNode) readData;
190 if (readList == null || readList.getValue().isEmpty()) {
191 return makePut(path, schemaContext, strategy, data, exists);
193 strategy.delete(LogicalDatastoreType.CONFIGURATION, path.getParent());
194 simplePut(LogicalDatastoreType.CONFIGURATION, path, strategy, schemaContext, data, exists);
195 listPut(LogicalDatastoreType.CONFIGURATION, path.getParent(), strategy,
196 schemaContext, readList, exists);
197 return strategy.commit();
200 final NormalizedNode<?, ?> readData = readList(path, schemaContext, strategy, schemaNode);
202 final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
203 if (readLeafList == null || readLeafList.getValue().isEmpty()) {
204 return makePut(path, schemaContext, strategy, data, exists);
206 strategy.delete(LogicalDatastoreType.CONFIGURATION, path.getParent());
207 simplePut(LogicalDatastoreType.CONFIGURATION, path, strategy,
208 schemaContext, data, exists);
209 listPut(LogicalDatastoreType.CONFIGURATION, path.getParent(), strategy,
210 schemaContext, readLeafList, exists);
211 return strategy.commit();
215 return makePut(path, schemaContext, strategy, data, exists);
217 if (schemaNode instanceof ListSchemaNode) {
218 final NormalizedNode<?, ?> readData = readList(path, schemaContext, strategy, schemaNode);
219 final OrderedMapNode readList = (OrderedMapNode) readData;
220 if (readList == null || readList.getValue().isEmpty()) {
221 return makePut(path, schemaContext, strategy, data, exists);
223 insertWithPointListPut(strategy, LogicalDatastoreType.CONFIGURATION, path,
224 data, schemaContext, point, readList, true, exists);
225 return strategy.commit();
228 final NormalizedNode<?, ?> readData = readList(path, schemaContext, strategy, schemaNode);
230 final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
231 if (readLeafList == null || readLeafList.getValue().isEmpty()) {
232 return makePut(path, schemaContext, strategy, data, exists);
234 insertWithPointLeafListPut(strategy, LogicalDatastoreType.CONFIGURATION,
235 path, data, schemaContext, point, readLeafList, true, exists);
236 return strategy.commit();
240 if (schemaNode instanceof ListSchemaNode) {
241 final NormalizedNode<?, ?> readData = readList(path, schemaContext, strategy, schemaNode);
242 final OrderedMapNode readList = (OrderedMapNode) readData;
243 if (readList == null || readList.getValue().isEmpty()) {
244 return makePut(path, schemaContext, strategy, data, exists);
246 insertWithPointListPut(strategy, LogicalDatastoreType.CONFIGURATION,
247 path, data, schemaContext, point, readList, false, exists);
248 return strategy.commit();
251 final NormalizedNode<?, ?> readData = readList(path, schemaContext, strategy, schemaNode);
253 final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
254 if (readLeafList == null || readLeafList.getValue().isEmpty()) {
255 return makePut(path, schemaContext, strategy, data, exists);
257 insertWithPointLeafListPut(strategy, LogicalDatastoreType.CONFIGURATION,
258 path, data, schemaContext, point, readLeafList, true, exists);
259 return strategy.commit();
263 throw new RestconfDocumentedException(
264 "Used bad value of insert parameter. Possible values are first, last, before or after, "
265 + "but was: " + insert, RestconfError.ErrorType.PROTOCOL, ErrorTag.BAD_ATTRIBUTE);
269 public static NormalizedNode<?, ?> readList(final YangInstanceIdentifier path,
270 final EffectiveModelContext schemaContext,
271 final RestconfStrategy strategy,
272 final DataSchemaNode schemaNode) {
273 final InstanceIdentifierContext<?> iid = new InstanceIdentifierContext<SchemaNode>(
274 path.getParent(), schemaNode, null, schemaContext);
275 final RestconfStrategy restconfStrategy = strategy.buildStrategy(iid);
276 return ReadDataTransactionUtil.readData(
277 RestconfDataServiceConstant.ReadData.CONFIG, restconfStrategy, schemaContext);
280 private static void insertWithPointLeafListPut(final RestconfStrategy strategy,
281 final LogicalDatastoreType datastore,
282 final YangInstanceIdentifier path,
283 final NormalizedNode<?, ?> data,
284 final EffectiveModelContext schemaContext, final String point,
285 final OrderedLeafSetNode<?> readLeafList, final boolean before,
286 final boolean exists) {
287 strategy.delete(datastore, path.getParent());
288 final InstanceIdentifierContext<?> instanceIdentifier =
289 ParserIdentifier.toInstanceIdentifier(point, schemaContext, Optional.empty());
290 int lastItemPosition = 0;
291 for (final LeafSetEntryNode<?> nodeChild : readLeafList.getValue()) {
292 if (nodeChild.getIdentifier().equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
300 int lastInsertedPosition = 0;
301 final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path.getParent());
302 strategy.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
303 for (final LeafSetEntryNode<?> nodeChild : readLeafList.getValue()) {
304 if (lastInsertedPosition == lastItemPosition) {
305 simplePut(datastore, path, strategy, schemaContext, data, exists);
307 final YangInstanceIdentifier childPath = path.getParent().node(nodeChild.getIdentifier());
309 strategy.replace(datastore, childPath, nodeChild);
311 strategy.create(datastore, childPath, nodeChild);
313 lastInsertedPosition++;
317 private static void insertWithPointListPut(final RestconfStrategy strategy,
318 final LogicalDatastoreType datastore, final YangInstanceIdentifier path,
319 final NormalizedNode<?, ?> data,
320 final EffectiveModelContext schemaContext, final String point,
321 final OrderedMapNode readList, final boolean before,
322 final boolean exists) {
323 strategy.delete(datastore, path.getParent());
324 final InstanceIdentifierContext<?> instanceIdentifier =
325 ParserIdentifier.toInstanceIdentifier(point, schemaContext, Optional.empty());
326 int lastItemPosition = 0;
327 for (final MapEntryNode mapEntryNode : readList.getValue()) {
328 if (mapEntryNode.getIdentifier().equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
336 int lastInsertedPosition = 0;
337 final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path.getParent());
338 strategy.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
339 for (final MapEntryNode mapEntryNode : readList.getValue()) {
340 if (lastInsertedPosition == lastItemPosition) {
341 simplePut(datastore, path, strategy, schemaContext, data, exists);
343 final YangInstanceIdentifier childPath = path.getParent().node(mapEntryNode.getIdentifier());
345 strategy.replace(datastore, childPath, mapEntryNode);
347 strategy.create(datastore, childPath, mapEntryNode);
349 lastInsertedPosition++;
353 private static void listPut(final LogicalDatastoreType datastore, final YangInstanceIdentifier path,
354 final RestconfStrategy strategy, final SchemaContext schemaContext,
355 final OrderedLeafSetNode<?> payload, final boolean exists) {
356 final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
357 strategy.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
358 TransactionUtil.ensureParentsByMerge(path, schemaContext, strategy);
359 for (final LeafSetEntryNode<?> child : ((LeafSetNode<?>) payload).getValue()) {
360 final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
362 strategy.replace(datastore, childPath, child);
364 strategy.create(datastore, childPath, child);
369 private static void listPut(final LogicalDatastoreType datastore, final YangInstanceIdentifier path,
370 final RestconfStrategy strategy, final SchemaContext schemaContext,
371 final OrderedMapNode payload, final boolean exists) {
372 final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
373 strategy.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
374 TransactionUtil.ensureParentsByMerge(path, schemaContext, strategy);
375 for (final MapEntryNode child : payload.getValue()) {
376 final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
378 strategy.replace(datastore, childPath, child);
380 strategy.create(datastore, childPath, child);
385 private static void simplePut(final LogicalDatastoreType configuration, final YangInstanceIdentifier path,
386 final RestconfStrategy strategy, final SchemaContext schemaContext,
387 final NormalizedNode<?, ?> data, final boolean exists) {
388 TransactionUtil.ensureParentsByMerge(path, schemaContext, strategy);
390 strategy.replace(LogicalDatastoreType.CONFIGURATION, path, data);
392 strategy.create(LogicalDatastoreType.CONFIGURATION, path, data);
396 private static FluentFuture<? extends CommitInfo> makePut(final YangInstanceIdentifier path,
397 final SchemaContext schemaContext,
398 final RestconfStrategy strategy,
399 final NormalizedNode<?, ?> data,
400 final boolean exists) {
401 TransactionUtil.ensureParentsByMerge(path, schemaContext, strategy);
403 strategy.replace(LogicalDatastoreType.CONFIGURATION, path, data);
405 strategy.create(LogicalDatastoreType.CONFIGURATION, path, data);
407 return strategy.commit();
410 public static DataSchemaNode checkListAndOrderedType(final SchemaContext ctx, final YangInstanceIdentifier path) {
411 final YangInstanceIdentifier parent = path.getParent();
412 final DataSchemaContextNode<?> node = DataSchemaContextTree.from(ctx).getChild(parent);
413 final DataSchemaNode dataSchemaNode = node.getDataSchemaNode();
415 if (dataSchemaNode instanceof ListSchemaNode) {
416 if (!((ListSchemaNode) dataSchemaNode).isUserOrdered()) {
417 throw new RestconfDocumentedException("Insert parameter can be used only with ordered-by user list.",
418 RestconfError.ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT);
420 return dataSchemaNode;
422 if (dataSchemaNode instanceof LeafListSchemaNode) {
423 if (!((LeafListSchemaNode) dataSchemaNode).isUserOrdered()) {
424 throw new RestconfDocumentedException(
425 "Insert parameter can be used only with ordered-by user leaf-list.",
426 RestconfError.ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT);
428 return dataSchemaNode;
430 throw new RestconfDocumentedException("Insert parameter can be used only with list or leaf-list",
431 RestconfError.ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT);