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.collect.Maps;
11 import com.google.common.util.concurrent.FluentFuture;
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.DOMDataTreeReadWriteTransaction;
20 import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction;
21 import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
22 import org.opendaylight.restconf.common.context.NormalizedNodeContext;
23 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
24 import org.opendaylight.restconf.common.errors.RestconfError;
25 import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag;
26 import org.opendaylight.restconf.common.errors.RestconfError.ErrorType;
27 import org.opendaylight.restconf.common.validation.RestconfValidationUtils;
28 import org.opendaylight.restconf.nb.rfc8040.handlers.TransactionChainHandler;
29 import org.opendaylight.restconf.nb.rfc8040.references.SchemaContextRef;
30 import org.opendaylight.restconf.nb.rfc8040.rests.transactions.TransactionVarsWrapper;
31 import org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserIdentifier;
32 import org.opendaylight.yangtools.yang.common.QName;
33 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
34 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
35 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
36 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
37 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
38 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
39 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
40 import org.opendaylight.yangtools.yang.data.api.schema.OrderedLeafSetNode;
41 import org.opendaylight.yangtools.yang.data.api.schema.OrderedMapNode;
42 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
43 import org.opendaylight.yangtools.yang.data.util.DataSchemaContextNode;
44 import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
45 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
46 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
47 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
48 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
49 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
52 * Util class for put data to DS.
55 public final class PutDataTransactionUtil {
57 private PutDataTransactionUtil() {
62 * Valid input data with {@link SchemaNode}.
69 public static void validInputData(final SchemaNode schemaNode, final NormalizedNodeContext payload) {
70 if (schemaNode != null && payload.getData() == null) {
71 throw new RestconfDocumentedException("Input is required.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
72 } else if (schemaNode == null && payload.getData() != null) {
73 throw new RestconfDocumentedException("No input expected.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
78 * Valid top level node name.
85 public static void validTopLevelNodeName(final YangInstanceIdentifier path, final NormalizedNodeContext payload) {
86 final String payloadName = payload.getData().getNodeType().getLocalName();
89 if (!payload.getData().getNodeType().equals(RestconfDataServiceConstant.NETCONF_BASE_QNAME)) {
90 throw new RestconfDocumentedException("Instance identifier has to contain at least one path argument",
91 ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
94 final String identifierName = path.getLastPathArgument().getNodeType().getLocalName();
95 if (!payloadName.equals(identifierName)) {
96 throw new RestconfDocumentedException(
97 "Payload name (" + payloadName + ") is different from identifier name (" + identifierName + ")",
98 ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
104 * Validates whether keys in {@code payload} are equal to values of keys in
105 * {@code iiWithData} for list schema node.
107 * @throws RestconfDocumentedException
108 * if key values or key count in payload and URI isn't equal
110 public static void validateListKeysEqualityInPayloadAndUri(final NormalizedNodeContext payload) {
111 final InstanceIdentifierContext<?> iiWithData = payload.getInstanceIdentifierContext();
112 final PathArgument lastPathArgument = iiWithData.getInstanceIdentifier().getLastPathArgument();
113 final SchemaNode schemaNode = iiWithData.getSchemaNode();
114 final NormalizedNode<?, ?> data = payload.getData();
115 if (schemaNode instanceof ListSchemaNode) {
116 final List<QName> keyDefinitions = ((ListSchemaNode) schemaNode).getKeyDefinition();
117 if (lastPathArgument instanceof NodeIdentifierWithPredicates && data instanceof MapEntryNode) {
118 final Map<QName, Object> uriKeyValues = ((NodeIdentifierWithPredicates) lastPathArgument)
120 isEqualUriAndPayloadKeyValues(uriKeyValues, (MapEntryNode) data, keyDefinitions);
125 private static void isEqualUriAndPayloadKeyValues(final Map<QName, Object> uriKeyValues, final MapEntryNode payload,
126 final List<QName> keyDefinitions) {
127 final Map<QName, Object> mutableCopyUriKeyValues = Maps.newHashMap(uriKeyValues);
128 for (final QName keyDefinition : keyDefinitions) {
129 final Object uriKeyValue = mutableCopyUriKeyValues.remove(keyDefinition);
130 RestconfValidationUtils.checkDocumentedError(uriKeyValue != null, ErrorType.PROTOCOL, ErrorTag.DATA_MISSING,
131 "Missing key " + keyDefinition + " in URI.");
133 final Object dataKeyValue = payload.getIdentifier().getKeyValues().get(keyDefinition);
135 if (!uriKeyValue.equals(dataKeyValue)) {
136 final String errMsg = "The value '" + uriKeyValue + "' for key '" + keyDefinition.getLocalName()
137 + "' specified in the URI doesn't match the value '" + dataKeyValue
138 + "' specified in the message body. ";
139 throw new RestconfDocumentedException(errMsg, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
145 * Check mount point and prepare variables for put data to DS.
149 * @param schemaCtxRef
150 * reference to {@link SchemaContext}
151 * @param transactionNode
152 * wrapper of variables for transaction
157 * @return {@link Response}
159 public static Response putData(final NormalizedNodeContext payload, final SchemaContextRef schemaCtxRef,
160 final TransactionVarsWrapper transactionNode, final String insert, final String point) {
161 final YangInstanceIdentifier path = payload.getInstanceIdentifierContext().getInstanceIdentifier();
162 final SchemaContext schemaContext = schemaCtxRef.get();
164 final DOMDataTreeReadWriteTransaction readWriteTransaction =
165 transactionNode.getTransactionChain().newReadWriteTransaction();
167 final FluentFuture<Boolean> existsFuture = readWriteTransaction.exists(LogicalDatastoreType.CONFIGURATION,
169 final FutureDataFactory<Boolean> existsResponse = new FutureDataFactory<>();
170 FutureCallbackTx.addCallback(existsFuture, RestconfDataServiceConstant.PutData.PUT_TX_TYPE, existsResponse);
172 final ResponseFactory responseFactory =
173 new ResponseFactory(existsResponse.result ? Status.NO_CONTENT : Status.CREATED);
174 final FluentFuture<? extends CommitInfo> submitData = submitData(path, schemaContext,
175 transactionNode.getTransactionChainHandler(), readWriteTransaction, payload.getData(), insert, point);
176 FutureCallbackTx.addCallback(submitData, RestconfDataServiceConstant.PutData.PUT_TX_TYPE, responseFactory);
177 return responseFactory.build();
185 * @param schemaContext
186 * {@link SchemaContext}
187 * @param transactionChainHandler
195 * @return {@link FluentFuture}
197 private static FluentFuture<? extends CommitInfo> submitData(final YangInstanceIdentifier path,
198 final SchemaContext schemaContext, final TransactionChainHandler transactionChainHandler,
199 final DOMDataTreeReadWriteTransaction readWriteTransaction,
200 final NormalizedNode<?, ?> data, final String insert, final String point) {
201 if (insert == null) {
202 return makePut(path, schemaContext, readWriteTransaction, data);
205 final DataSchemaNode schemaNode = checkListAndOrderedType(schemaContext, path);
208 if (schemaNode instanceof ListSchemaNode) {
209 final NormalizedNode<?, ?> readData =
210 readList(path, schemaContext, transactionChainHandler, schemaNode);
211 final OrderedMapNode readList = (OrderedMapNode) readData;
212 if (readList == null || readList.getValue().isEmpty()) {
213 return makePut(path, schemaContext, readWriteTransaction, data);
215 readWriteTransaction.delete(LogicalDatastoreType.CONFIGURATION, path.getParent());
216 simplePut(LogicalDatastoreType.CONFIGURATION, path, readWriteTransaction,
217 schemaContext, data);
218 listPut(LogicalDatastoreType.CONFIGURATION, path.getParent(), readWriteTransaction,
219 schemaContext, readList);
220 return readWriteTransaction.commit();
223 final NormalizedNode<?, ?> readData =
224 readList(path, schemaContext, transactionChainHandler, schemaNode);
226 final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
227 if (readLeafList == null || readLeafList.getValue().isEmpty()) {
228 return makePut(path, schemaContext, readWriteTransaction, data);
230 readWriteTransaction.delete(LogicalDatastoreType.CONFIGURATION, path.getParent());
231 simplePut(LogicalDatastoreType.CONFIGURATION, path, readWriteTransaction,
232 schemaContext, data);
233 listPut(LogicalDatastoreType.CONFIGURATION, path.getParent(), readWriteTransaction,
234 schemaContext, readLeafList);
235 return readWriteTransaction.commit();
239 return makePut(path, schemaContext, readWriteTransaction, data);
241 if (schemaNode instanceof ListSchemaNode) {
242 final NormalizedNode<?, ?> readData =
243 readList(path, schemaContext, transactionChainHandler, schemaNode);
244 final OrderedMapNode readList = (OrderedMapNode) readData;
245 if (readList == null || readList.getValue().isEmpty()) {
246 return makePut(path, schemaContext, readWriteTransaction, data);
248 insertWithPointListPut(readWriteTransaction, LogicalDatastoreType.CONFIGURATION, path,
249 data, schemaContext, point, readList, true);
250 return readWriteTransaction.commit();
253 final NormalizedNode<?, ?> readData =
254 readList(path, schemaContext, transactionChainHandler, schemaNode);
256 final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
257 if (readLeafList == null || readLeafList.getValue().isEmpty()) {
258 return makePut(path, schemaContext, readWriteTransaction, data);
260 insertWithPointLeafListPut(readWriteTransaction, LogicalDatastoreType.CONFIGURATION,
261 path, data, schemaContext, point, readLeafList, true);
262 return readWriteTransaction.commit();
266 if (schemaNode instanceof ListSchemaNode) {
267 final NormalizedNode<?, ?> readData =
268 readList(path, schemaContext, transactionChainHandler, schemaNode);
269 final OrderedMapNode readList = (OrderedMapNode) readData;
270 if (readList == null || readList.getValue().isEmpty()) {
271 return makePut(path, schemaContext, readWriteTransaction, data);
273 insertWithPointListPut(readWriteTransaction, LogicalDatastoreType.CONFIGURATION,
274 path, data, schemaContext, point, readList, false);
275 return readWriteTransaction.commit();
278 final NormalizedNode<?, ?> readData =
279 readList(path, schemaContext, transactionChainHandler, schemaNode);
281 final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
282 if (readLeafList == null || readLeafList.getValue().isEmpty()) {
283 return makePut(path, schemaContext, readWriteTransaction, data);
285 insertWithPointLeafListPut(readWriteTransaction, LogicalDatastoreType.CONFIGURATION,
286 path, data, schemaContext, point, readLeafList, true);
287 return readWriteTransaction.commit();
291 throw new RestconfDocumentedException(
292 "Used bad value of insert parameter. Possible values are first, last, before or after, "
293 + "but was: " + insert, RestconfError.ErrorType.PROTOCOL, ErrorTag.BAD_ATTRIBUTE);
297 public static NormalizedNode<?, ?> readList(final YangInstanceIdentifier path, final SchemaContext schemaContext,
298 final TransactionChainHandler transactionChainHandler, final DataSchemaNode schemaNode) {
299 final InstanceIdentifierContext<?> iid = new InstanceIdentifierContext<SchemaNode>(
300 path.getParent(), schemaNode, null, schemaContext);
301 final TransactionVarsWrapper transactionNode = new TransactionVarsWrapper(iid, null, transactionChainHandler);
302 final NormalizedNode<?, ?> readData = ReadDataTransactionUtil
303 .readData(RestconfDataServiceConstant.ReadData.CONFIG, transactionNode, schemaContext);
307 private static void insertWithPointLeafListPut(final DOMDataTreeReadWriteTransaction rwTransaction,
308 final LogicalDatastoreType datastore, final YangInstanceIdentifier path,
309 final NormalizedNode<?, ?> data, final SchemaContext schemaContext, final String point,
310 final OrderedLeafSetNode<?> readLeafList, final boolean before) {
311 rwTransaction.delete(datastore, path.getParent());
312 final InstanceIdentifierContext<?> instanceIdentifier =
313 ParserIdentifier.toInstanceIdentifier(point, schemaContext, Optional.empty());
314 int lastItemPosition = 0;
315 for (final LeafSetEntryNode<?> nodeChild : readLeafList.getValue()) {
316 if (nodeChild.getIdentifier().equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
324 int lastInsertedPosition = 0;
325 final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path.getParent());
326 rwTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
327 for (final LeafSetEntryNode<?> nodeChild : readLeafList.getValue()) {
328 if (lastInsertedPosition == lastItemPosition) {
329 simplePut(datastore, path, rwTransaction, schemaContext, data);
331 final YangInstanceIdentifier childPath = path.getParent().node(nodeChild.getIdentifier());
332 rwTransaction.put(datastore, childPath, nodeChild);
333 lastInsertedPosition++;
337 private static void insertWithPointListPut(final DOMDataTreeReadWriteTransaction writeTx,
338 final LogicalDatastoreType datastore, final YangInstanceIdentifier path,
339 final NormalizedNode<?, ?> data, final SchemaContext schemaContext, final String point,
340 final OrderedMapNode readList, final boolean before) {
341 writeTx.delete(datastore, path.getParent());
342 final InstanceIdentifierContext<?> instanceIdentifier =
343 ParserIdentifier.toInstanceIdentifier(point, schemaContext, Optional.empty());
344 int lastItemPosition = 0;
345 for (final MapEntryNode mapEntryNode : readList.getValue()) {
346 if (mapEntryNode.getIdentifier().equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
354 int lastInsertedPosition = 0;
355 final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path.getParent());
356 writeTx.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
357 for (final MapEntryNode mapEntryNode : readList.getValue()) {
358 if (lastInsertedPosition == lastItemPosition) {
359 simplePut(datastore, path, writeTx, schemaContext, data);
361 final YangInstanceIdentifier childPath = path.getParent().node(mapEntryNode.getIdentifier());
362 writeTx.put(datastore, childPath, mapEntryNode);
363 lastInsertedPosition++;
367 private static void listPut(final LogicalDatastoreType datastore, final YangInstanceIdentifier path,
368 final DOMDataTreeWriteTransaction writeTx, final SchemaContext schemaContext,
369 final OrderedLeafSetNode<?> payload) {
370 final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
371 writeTx.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
372 TransactionUtil.ensureParentsByMerge(path, schemaContext, writeTx);
373 for (final LeafSetEntryNode<?> child : ((LeafSetNode<?>) payload).getValue()) {
374 final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
375 writeTx.put(datastore, childPath, child);
379 private static void listPut(final LogicalDatastoreType datastore, final YangInstanceIdentifier path,
380 final DOMDataTreeWriteTransaction writeTx, final SchemaContext schemaContext,
381 final OrderedMapNode payload) {
382 final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
383 writeTx.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
384 TransactionUtil.ensureParentsByMerge(path, schemaContext, writeTx);
385 for (final MapEntryNode child : payload.getValue()) {
386 final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
387 writeTx.put(datastore, childPath, child);
391 private static void simplePut(final LogicalDatastoreType configuration, final YangInstanceIdentifier path,
392 final DOMDataTreeWriteTransaction writeTx, final SchemaContext schemaContext,
393 final NormalizedNode<?, ?> data) {
394 TransactionUtil.ensureParentsByMerge(path, schemaContext, writeTx);
395 writeTx.put(LogicalDatastoreType.CONFIGURATION, path, data);
398 private static FluentFuture<? extends CommitInfo> makePut(final YangInstanceIdentifier path,
399 final SchemaContext schemaContext, final DOMDataTreeWriteTransaction writeTx,
400 final NormalizedNode<?, ?> data) {
401 TransactionUtil.ensureParentsByMerge(path, schemaContext, writeTx);
402 writeTx.put(LogicalDatastoreType.CONFIGURATION, path, data);
403 return writeTx.commit();
406 public static DataSchemaNode checkListAndOrderedType(final SchemaContext ctx, final YangInstanceIdentifier path) {
407 final YangInstanceIdentifier parent = path.getParent();
408 final DataSchemaContextNode<?> node = DataSchemaContextTree.from(ctx).getChild(parent);
409 final DataSchemaNode dataSchemaNode = node.getDataSchemaNode();
411 if (dataSchemaNode instanceof ListSchemaNode) {
412 if (!((ListSchemaNode) dataSchemaNode).isUserOrdered()) {
413 throw new RestconfDocumentedException("Insert parameter can be used only with ordered-by user list.",
414 RestconfError.ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT);
416 return dataSchemaNode;
418 if (dataSchemaNode instanceof LeafListSchemaNode) {
419 if (!((LeafListSchemaNode) dataSchemaNode).isUserOrdered()) {
420 throw new RestconfDocumentedException(
421 "Insert parameter can be used only with ordered-by user leaf-list.",
422 RestconfError.ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT);
424 return dataSchemaNode;
426 throw new RestconfDocumentedException("Insert parameter can be used only with list or leaf-list",
427 RestconfError.ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT);