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.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.nb.rfc8040.handlers.TransactionChainHandler;
28 import org.opendaylight.restconf.nb.rfc8040.references.SchemaContextRef;
29 import org.opendaylight.restconf.nb.rfc8040.rests.transactions.TransactionVarsWrapper;
30 import org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserIdentifier;
31 import org.opendaylight.yangtools.yang.common.QName;
32 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
33 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
34 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
35 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
36 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
37 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
38 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
39 import org.opendaylight.yangtools.yang.data.api.schema.OrderedLeafSetNode;
40 import org.opendaylight.yangtools.yang.data.api.schema.OrderedMapNode;
41 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
42 import org.opendaylight.yangtools.yang.data.util.DataSchemaContextNode;
43 import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
44 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
45 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
46 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
47 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
48 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
51 * Util class for put data to DS.
54 public final class PutDataTransactionUtil {
56 private PutDataTransactionUtil() {
61 * Valid input data with {@link SchemaNode}.
68 public static void validInputData(final SchemaNode schemaNode, final NormalizedNodeContext payload) {
69 if (schemaNode != null && payload.getData() == null) {
70 throw new RestconfDocumentedException("Input is required.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
71 } else if (schemaNode == null && payload.getData() != null) {
72 throw new RestconfDocumentedException("No input expected.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
77 * Valid top level node name.
84 public static void validTopLevelNodeName(final YangInstanceIdentifier path, final NormalizedNodeContext payload) {
85 final String payloadName = payload.getData().getNodeType().getLocalName();
88 if (!payload.getData().getNodeType().equals(RestconfDataServiceConstant.NETCONF_BASE_QNAME)) {
89 throw new RestconfDocumentedException("Instance identifier has to contain at least one path argument",
90 ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
93 final String identifierName = path.getLastPathArgument().getNodeType().getLocalName();
94 if (!payloadName.equals(identifierName)) {
95 throw new RestconfDocumentedException(
96 "Payload name (" + payloadName + ") is different from identifier name (" + identifierName + ")",
97 ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
103 * Validates whether keys in {@code payload} are equal to values of keys in
104 * {@code iiWithData} for list schema node.
106 * @throws RestconfDocumentedException
107 * if key values or key count in payload and URI isn't equal
109 public static void validateListKeysEqualityInPayloadAndUri(final NormalizedNodeContext payload) {
110 final InstanceIdentifierContext<?> iiWithData = payload.getInstanceIdentifierContext();
111 final PathArgument lastPathArgument = iiWithData.getInstanceIdentifier().getLastPathArgument();
112 final SchemaNode schemaNode = iiWithData.getSchemaNode();
113 final NormalizedNode<?, ?> data = payload.getData();
114 if (schemaNode instanceof ListSchemaNode) {
115 final List<QName> keyDefinitions = ((ListSchemaNode) schemaNode).getKeyDefinition();
116 if (lastPathArgument instanceof NodeIdentifierWithPredicates && data instanceof MapEntryNode) {
117 final Map<QName, Object> uriKeyValues = ((NodeIdentifierWithPredicates) lastPathArgument).asMap();
118 isEqualUriAndPayloadKeyValues(uriKeyValues, (MapEntryNode) data, keyDefinitions);
123 private static void isEqualUriAndPayloadKeyValues(final Map<QName, Object> uriKeyValues, final MapEntryNode payload,
124 final List<QName> keyDefinitions) {
125 final Map<QName, Object> mutableCopyUriKeyValues = new HashMap<>(uriKeyValues);
126 for (final QName keyDefinition : keyDefinitions) {
127 final Object uriKeyValue = RestconfDocumentedException.throwIfNull(
128 mutableCopyUriKeyValues.remove(keyDefinition), ErrorType.PROTOCOL, ErrorTag.DATA_MISSING,
129 "Missing key %s in URI.", keyDefinition);
131 final Object dataKeyValue = payload.getIdentifier().getValue(keyDefinition);
133 if (!uriKeyValue.equals(dataKeyValue)) {
134 final String errMsg = "The value '" + uriKeyValue + "' for key '" + keyDefinition.getLocalName()
135 + "' specified in the URI doesn't match the value '" + dataKeyValue
136 + "' specified in the message body. ";
137 throw new RestconfDocumentedException(errMsg, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
143 * Check mount point and prepare variables for put data to DS.
147 * @param schemaCtxRef
148 * reference to {@link SchemaContext}
149 * @param transactionNode
150 * wrapper of variables for transaction
155 * @return {@link Response}
157 public static Response putData(final NormalizedNodeContext payload, final SchemaContextRef schemaCtxRef,
158 final TransactionVarsWrapper transactionNode, final String insert, final String point) {
159 final YangInstanceIdentifier path = payload.getInstanceIdentifierContext().getInstanceIdentifier();
160 final SchemaContext schemaContext = schemaCtxRef.get();
162 final DOMDataTreeReadWriteTransaction readWriteTransaction =
163 transactionNode.getTransactionChain().newReadWriteTransaction();
165 final FluentFuture<Boolean> existsFuture = readWriteTransaction.exists(LogicalDatastoreType.CONFIGURATION,
167 final FutureDataFactory<Boolean> existsResponse = new FutureDataFactory<>();
168 FutureCallbackTx.addCallback(existsFuture, RestconfDataServiceConstant.PutData.PUT_TX_TYPE, existsResponse);
170 final ResponseFactory responseFactory =
171 new ResponseFactory(existsResponse.result ? Status.NO_CONTENT : Status.CREATED);
172 final FluentFuture<? extends CommitInfo> submitData = submitData(path, schemaContext,
173 transactionNode.getTransactionChainHandler(), readWriteTransaction, payload.getData(), insert, point);
174 FutureCallbackTx.addCallback(submitData, RestconfDataServiceConstant.PutData.PUT_TX_TYPE, responseFactory);
175 return responseFactory.build();
183 * @param schemaContext
184 * {@link SchemaContext}
185 * @param transactionChainHandler
193 * @return {@link FluentFuture}
195 private static FluentFuture<? extends CommitInfo> submitData(final YangInstanceIdentifier path,
196 final SchemaContext schemaContext, final TransactionChainHandler transactionChainHandler,
197 final DOMDataTreeReadWriteTransaction readWriteTransaction,
198 final NormalizedNode<?, ?> data, final String insert, final String point) {
199 if (insert == null) {
200 return makePut(path, schemaContext, readWriteTransaction, data);
203 final DataSchemaNode schemaNode = checkListAndOrderedType(schemaContext, path);
206 if (schemaNode instanceof ListSchemaNode) {
207 final NormalizedNode<?, ?> readData =
208 readList(path, schemaContext, transactionChainHandler, schemaNode);
209 final OrderedMapNode readList = (OrderedMapNode) readData;
210 if (readList == null || readList.getValue().isEmpty()) {
211 return makePut(path, schemaContext, readWriteTransaction, data);
213 readWriteTransaction.delete(LogicalDatastoreType.CONFIGURATION, path.getParent());
214 simplePut(LogicalDatastoreType.CONFIGURATION, path, readWriteTransaction,
215 schemaContext, data);
216 listPut(LogicalDatastoreType.CONFIGURATION, path.getParent(), readWriteTransaction,
217 schemaContext, readList);
218 return readWriteTransaction.commit();
221 final NormalizedNode<?, ?> readData =
222 readList(path, schemaContext, transactionChainHandler, schemaNode);
224 final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
225 if (readLeafList == null || readLeafList.getValue().isEmpty()) {
226 return makePut(path, schemaContext, readWriteTransaction, data);
228 readWriteTransaction.delete(LogicalDatastoreType.CONFIGURATION, path.getParent());
229 simplePut(LogicalDatastoreType.CONFIGURATION, path, readWriteTransaction,
230 schemaContext, data);
231 listPut(LogicalDatastoreType.CONFIGURATION, path.getParent(), readWriteTransaction,
232 schemaContext, readLeafList);
233 return readWriteTransaction.commit();
237 return makePut(path, schemaContext, readWriteTransaction, data);
239 if (schemaNode instanceof ListSchemaNode) {
240 final NormalizedNode<?, ?> readData =
241 readList(path, schemaContext, transactionChainHandler, schemaNode);
242 final OrderedMapNode readList = (OrderedMapNode) readData;
243 if (readList == null || readList.getValue().isEmpty()) {
244 return makePut(path, schemaContext, readWriteTransaction, data);
246 insertWithPointListPut(readWriteTransaction, LogicalDatastoreType.CONFIGURATION, path,
247 data, schemaContext, point, readList, true);
248 return readWriteTransaction.commit();
251 final NormalizedNode<?, ?> readData =
252 readList(path, schemaContext, transactionChainHandler, schemaNode);
254 final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
255 if (readLeafList == null || readLeafList.getValue().isEmpty()) {
256 return makePut(path, schemaContext, readWriteTransaction, data);
258 insertWithPointLeafListPut(readWriteTransaction, LogicalDatastoreType.CONFIGURATION,
259 path, data, schemaContext, point, readLeafList, true);
260 return readWriteTransaction.commit();
264 if (schemaNode instanceof ListSchemaNode) {
265 final NormalizedNode<?, ?> readData =
266 readList(path, schemaContext, transactionChainHandler, schemaNode);
267 final OrderedMapNode readList = (OrderedMapNode) readData;
268 if (readList == null || readList.getValue().isEmpty()) {
269 return makePut(path, schemaContext, readWriteTransaction, data);
271 insertWithPointListPut(readWriteTransaction, LogicalDatastoreType.CONFIGURATION,
272 path, data, schemaContext, point, readList, false);
273 return readWriteTransaction.commit();
276 final NormalizedNode<?, ?> readData =
277 readList(path, schemaContext, transactionChainHandler, schemaNode);
279 final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
280 if (readLeafList == null || readLeafList.getValue().isEmpty()) {
281 return makePut(path, schemaContext, readWriteTransaction, data);
283 insertWithPointLeafListPut(readWriteTransaction, LogicalDatastoreType.CONFIGURATION,
284 path, data, schemaContext, point, readLeafList, true);
285 return readWriteTransaction.commit();
289 throw new RestconfDocumentedException(
290 "Used bad value of insert parameter. Possible values are first, last, before or after, "
291 + "but was: " + insert, RestconfError.ErrorType.PROTOCOL, ErrorTag.BAD_ATTRIBUTE);
295 public static NormalizedNode<?, ?> readList(final YangInstanceIdentifier path, final SchemaContext schemaContext,
296 final TransactionChainHandler transactionChainHandler, final DataSchemaNode schemaNode) {
297 final InstanceIdentifierContext<?> iid = new InstanceIdentifierContext<SchemaNode>(
298 path.getParent(), schemaNode, null, schemaContext);
299 final TransactionVarsWrapper transactionNode = new TransactionVarsWrapper(iid, null, transactionChainHandler);
300 final NormalizedNode<?, ?> readData = ReadDataTransactionUtil
301 .readData(RestconfDataServiceConstant.ReadData.CONFIG, transactionNode, schemaContext);
305 private static void insertWithPointLeafListPut(final DOMDataTreeReadWriteTransaction rwTransaction,
306 final LogicalDatastoreType datastore, final YangInstanceIdentifier path,
307 final NormalizedNode<?, ?> data, final SchemaContext schemaContext, final String point,
308 final OrderedLeafSetNode<?> readLeafList, final boolean before) {
309 rwTransaction.delete(datastore, path.getParent());
310 final InstanceIdentifierContext<?> instanceIdentifier =
311 ParserIdentifier.toInstanceIdentifier(point, schemaContext, Optional.empty());
312 int lastItemPosition = 0;
313 for (final LeafSetEntryNode<?> nodeChild : readLeafList.getValue()) {
314 if (nodeChild.getIdentifier().equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
322 int lastInsertedPosition = 0;
323 final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path.getParent());
324 rwTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
325 for (final LeafSetEntryNode<?> nodeChild : readLeafList.getValue()) {
326 if (lastInsertedPosition == lastItemPosition) {
327 simplePut(datastore, path, rwTransaction, schemaContext, data);
329 final YangInstanceIdentifier childPath = path.getParent().node(nodeChild.getIdentifier());
330 rwTransaction.put(datastore, childPath, nodeChild);
331 lastInsertedPosition++;
335 private static void insertWithPointListPut(final DOMDataTreeReadWriteTransaction writeTx,
336 final LogicalDatastoreType datastore, final YangInstanceIdentifier path,
337 final NormalizedNode<?, ?> data, final SchemaContext schemaContext, final String point,
338 final OrderedMapNode readList, final boolean before) {
339 writeTx.delete(datastore, path.getParent());
340 final InstanceIdentifierContext<?> instanceIdentifier =
341 ParserIdentifier.toInstanceIdentifier(point, schemaContext, Optional.empty());
342 int lastItemPosition = 0;
343 for (final MapEntryNode mapEntryNode : readList.getValue()) {
344 if (mapEntryNode.getIdentifier().equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
352 int lastInsertedPosition = 0;
353 final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path.getParent());
354 writeTx.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
355 for (final MapEntryNode mapEntryNode : readList.getValue()) {
356 if (lastInsertedPosition == lastItemPosition) {
357 simplePut(datastore, path, writeTx, schemaContext, data);
359 final YangInstanceIdentifier childPath = path.getParent().node(mapEntryNode.getIdentifier());
360 writeTx.put(datastore, childPath, mapEntryNode);
361 lastInsertedPosition++;
365 private static void listPut(final LogicalDatastoreType datastore, final YangInstanceIdentifier path,
366 final DOMDataTreeWriteTransaction writeTx, final SchemaContext schemaContext,
367 final OrderedLeafSetNode<?> payload) {
368 final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
369 writeTx.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
370 TransactionUtil.ensureParentsByMerge(path, schemaContext, writeTx);
371 for (final LeafSetEntryNode<?> child : ((LeafSetNode<?>) payload).getValue()) {
372 final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
373 writeTx.put(datastore, childPath, child);
377 private static void listPut(final LogicalDatastoreType datastore, final YangInstanceIdentifier path,
378 final DOMDataTreeWriteTransaction writeTx, final SchemaContext schemaContext,
379 final OrderedMapNode payload) {
380 final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
381 writeTx.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
382 TransactionUtil.ensureParentsByMerge(path, schemaContext, writeTx);
383 for (final MapEntryNode child : payload.getValue()) {
384 final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
385 writeTx.put(datastore, childPath, child);
389 private static void simplePut(final LogicalDatastoreType configuration, final YangInstanceIdentifier path,
390 final DOMDataTreeWriteTransaction writeTx, final SchemaContext schemaContext,
391 final NormalizedNode<?, ?> data) {
392 TransactionUtil.ensureParentsByMerge(path, schemaContext, writeTx);
393 writeTx.put(LogicalDatastoreType.CONFIGURATION, path, data);
396 private static FluentFuture<? extends CommitInfo> makePut(final YangInstanceIdentifier path,
397 final SchemaContext schemaContext, final DOMDataTreeWriteTransaction writeTx,
398 final NormalizedNode<?, ?> data) {
399 TransactionUtil.ensureParentsByMerge(path, schemaContext, writeTx);
400 writeTx.put(LogicalDatastoreType.CONFIGURATION, path, data);
401 return writeTx.commit();
404 public static DataSchemaNode checkListAndOrderedType(final SchemaContext ctx, final YangInstanceIdentifier path) {
405 final YangInstanceIdentifier parent = path.getParent();
406 final DataSchemaContextNode<?> node = DataSchemaContextTree.from(ctx).getChild(parent);
407 final DataSchemaNode dataSchemaNode = node.getDataSchemaNode();
409 if (dataSchemaNode instanceof ListSchemaNode) {
410 if (!((ListSchemaNode) dataSchemaNode).isUserOrdered()) {
411 throw new RestconfDocumentedException("Insert parameter can be used only with ordered-by user list.",
412 RestconfError.ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT);
414 return dataSchemaNode;
416 if (dataSchemaNode instanceof LeafListSchemaNode) {
417 if (!((LeafListSchemaNode) dataSchemaNode).isUserOrdered()) {
418 throw new RestconfDocumentedException(
419 "Insert parameter can be used only with ordered-by user leaf-list.",
420 RestconfError.ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT);
422 return dataSchemaNode;
424 throw new RestconfDocumentedException("Insert parameter can be used only with list or leaf-list",
425 RestconfError.ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT);