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.restful.utils;
10 import com.google.common.collect.Maps;
11 import com.google.common.util.concurrent.CheckedFuture;
12 import java.util.List;
14 import javax.ws.rs.core.Response;
15 import javax.ws.rs.core.Response.Status;
16 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
17 import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
18 import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
19 import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
20 import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
21 import org.opendaylight.controller.md.sal.dom.api.DOMTransactionChain;
22 import org.opendaylight.netconf.md.sal.rest.common.RestconfValidationUtils;
23 import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
24 import org.opendaylight.netconf.sal.restconf.impl.InstanceIdentifierContext;
25 import org.opendaylight.netconf.sal.restconf.impl.NormalizedNodeContext;
26 import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
27 import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorTag;
28 import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorType;
29 import org.opendaylight.restconf.common.references.SchemaContextRef;
30 import org.opendaylight.restconf.restful.transaction.TransactionVarsWrapper;
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.MapNode;
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 {
58 * Valid input data with {@link SchemaNode}.
65 public static void validInputData(final SchemaNode schemaNode, final NormalizedNodeContext payload) {
66 if (schemaNode != null && payload.getData() == null) {
67 throw new RestconfDocumentedException("Input is required.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
68 } else if (schemaNode == null && payload.getData() != null) {
69 throw new RestconfDocumentedException("No input expected.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
74 * Valid top level node name.
81 public static void validTopLevelNodeName(final YangInstanceIdentifier path, final NormalizedNodeContext payload) {
82 final String payloadName = payload.getData().getNodeType().getLocalName();
85 if (!payload.getData().getNodeType().equals(RestconfDataServiceConstant.NETCONF_BASE_QNAME)) {
86 throw new RestconfDocumentedException("Instance identifier has to contain at least one path argument",
87 ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
90 final String identifierName = path.getLastPathArgument().getNodeType().getLocalName();
91 if (!payloadName.equals(identifierName)) {
92 throw new RestconfDocumentedException(
93 "Payload name (" + payloadName + ") is different from identifier name (" + identifierName + ")",
94 ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
100 * Validates whether keys in {@code payload} are equal to values of keys in
101 * {@code iiWithData} for list schema node.
103 * @throws RestconfDocumentedException
104 * if key values or key count in payload and URI isn't equal
106 public static void validateListKeysEqualityInPayloadAndUri(final NormalizedNodeContext payload) {
107 final InstanceIdentifierContext<?> iiWithData = payload.getInstanceIdentifierContext();
108 final PathArgument lastPathArgument = iiWithData.getInstanceIdentifier().getLastPathArgument();
109 final SchemaNode schemaNode = iiWithData.getSchemaNode();
110 final NormalizedNode<?, ?> data = payload.getData();
111 if (schemaNode instanceof ListSchemaNode) {
112 final List<QName> keyDefinitions = ((ListSchemaNode) schemaNode).getKeyDefinition();
113 if (lastPathArgument instanceof NodeIdentifierWithPredicates && data instanceof MapEntryNode) {
114 final Map<QName, Object> uriKeyValues = ((NodeIdentifierWithPredicates) lastPathArgument)
116 isEqualUriAndPayloadKeyValues(uriKeyValues, (MapEntryNode) data, keyDefinitions);
121 private static void isEqualUriAndPayloadKeyValues(final Map<QName, Object> uriKeyValues, final MapEntryNode payload,
122 final List<QName> keyDefinitions) {
123 final Map<QName, Object> mutableCopyUriKeyValues = Maps.newHashMap(uriKeyValues);
124 for (final QName keyDefinition : keyDefinitions) {
125 final Object uriKeyValue = mutableCopyUriKeyValues.remove(keyDefinition);
126 RestconfValidationUtils.checkDocumentedError(uriKeyValue != null, ErrorType.PROTOCOL, ErrorTag.DATA_MISSING,
127 "Missing key " + keyDefinition + " in URI.");
129 final Object dataKeyValue = payload.getIdentifier().getKeyValues().get(keyDefinition);
131 if (!uriKeyValue.equals(dataKeyValue)) {
132 final String errMsg = "The value '" + uriKeyValue + "' for key '" + keyDefinition.getLocalName()
133 + "' specified in the URI doesn't match the value '" + dataKeyValue
134 + "' specified in the message body. ";
135 throw new RestconfDocumentedException(errMsg, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
141 * Check mount point and prepare variables for put data to DS.
145 * @param schemaCtxRef
146 * reference to {@link SchemaContext}
147 * @param transactionNode
148 * wrapper of variables for transaction
153 * @return {@link CheckedFuture}
155 public static Response putData(final NormalizedNodeContext payload, final SchemaContextRef schemaCtxRef,
156 final TransactionVarsWrapper transactionNode, final String insert, final String point) {
157 final YangInstanceIdentifier path = payload.getInstanceIdentifierContext().getInstanceIdentifier();
158 final SchemaContext schemaContext = schemaCtxRef.get();
160 final DOMDataReadWriteTransaction readWriteTransaction =
161 transactionNode.getTransactionChain().newReadWriteTransaction();
163 final CheckedFuture<Boolean, ReadFailedException> existsFuture =
164 readWriteTransaction.exists(LogicalDatastoreType.CONFIGURATION, path);
165 final FutureDataFactory<Boolean> existsResponse = new FutureDataFactory<>();
166 FutureCallbackTx.addCallback(existsFuture, RestconfDataServiceConstant.PutData.PUT_TX_TYPE, existsResponse);
168 final ResponseFactory responseFactory = new ResponseFactory(existsResponse.result ? Status.OK : Status.CREATED);
169 final CheckedFuture<Void, TransactionCommitFailedException> submitData = submitData(path, schemaContext,
170 transactionNode.getTransactionChain(), readWriteTransaction, payload.getData(), insert, point);
171 FutureCallbackTx.addCallback(submitData, RestconfDataServiceConstant.PutData.PUT_TX_TYPE, responseFactory);
172 return responseFactory.build();
180 * @param schemaContext
181 * {@link SchemaContext}
182 * @param domTransactionChain
190 * @return {@link CheckedFuture}
192 private static CheckedFuture<Void, TransactionCommitFailedException> submitData(final YangInstanceIdentifier path,
193 final SchemaContext schemaContext, final DOMTransactionChain domTransactionChain,
194 final DOMDataReadWriteTransaction readWriteTransaction,
195 final NormalizedNode<?, ?> data, final String insert, final String point) {
196 if (insert == null) {
197 return makePut(path, schemaContext, readWriteTransaction, data);
199 final DataSchemaNode schemaNode = checkListAndOrderedType(schemaContext, path);
202 if (schemaNode instanceof ListSchemaNode) {
203 final NormalizedNode<?, ?> readData =
204 readList(path, schemaContext, domTransactionChain, schemaNode);
205 final OrderedMapNode readList = (OrderedMapNode) readData;
206 if (readList == null || readList.getValue().isEmpty()) {
207 return makePut(path, schemaContext, readWriteTransaction, data);
209 readWriteTransaction.delete(LogicalDatastoreType.CONFIGURATION, path.getParent());
210 simplePut(LogicalDatastoreType.CONFIGURATION, path, readWriteTransaction,
211 schemaContext, data);
212 listPut(LogicalDatastoreType.CONFIGURATION, path.getParent(), readWriteTransaction,
213 schemaContext, readList);
214 return readWriteTransaction.submit();
217 final NormalizedNode<?, ?> readData =
218 readList(path, schemaContext, domTransactionChain, schemaNode);
220 final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
221 if (readLeafList == null || readLeafList.getValue().isEmpty()) {
222 return makePut(path, schemaContext, readWriteTransaction, data);
224 readWriteTransaction.delete(LogicalDatastoreType.CONFIGURATION, path.getParent());
225 simplePut(LogicalDatastoreType.CONFIGURATION, path, readWriteTransaction,
226 schemaContext, data);
227 listPut(LogicalDatastoreType.CONFIGURATION, path.getParent(), readWriteTransaction,
228 schemaContext, readLeafList);
229 return readWriteTransaction.submit();
233 return makePut(path, schemaContext, readWriteTransaction, data);
235 if (schemaNode instanceof ListSchemaNode) {
236 final NormalizedNode<?, ?> readData =
237 readList(path, schemaContext, domTransactionChain, schemaNode);
238 final OrderedMapNode readList = (OrderedMapNode) readData;
239 if (readList == null || readList.getValue().isEmpty()) {
240 return makePut(path, schemaContext, readWriteTransaction, data);
242 insertWithPointListPut(readWriteTransaction, LogicalDatastoreType.CONFIGURATION, path,
243 data, schemaContext, point, readList, true);
244 return readWriteTransaction.submit();
247 final NormalizedNode<?, ?> readData =
248 readList(path, schemaContext, domTransactionChain, schemaNode);
250 final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
251 if (readLeafList == null || readLeafList.getValue().isEmpty()) {
252 return makePut(path, schemaContext, readWriteTransaction, data);
254 insertWithPointLeafListPut(readWriteTransaction, LogicalDatastoreType.CONFIGURATION,
255 path, data, schemaContext, point, readLeafList, true);
256 return readWriteTransaction.submit();
260 if (schemaNode instanceof ListSchemaNode) {
261 final NormalizedNode<?, ?> readData =
262 readList(path, schemaContext, domTransactionChain, schemaNode);
263 final OrderedMapNode readList = (OrderedMapNode) readData;
264 if (readList == null || readList.getValue().isEmpty()) {
265 return makePut(path, schemaContext, readWriteTransaction, data);
267 insertWithPointListPut(readWriteTransaction, LogicalDatastoreType.CONFIGURATION,
268 path, data, schemaContext, point, readList, false);
269 return readWriteTransaction.submit();
272 final NormalizedNode<?, ?> readData =
273 readList(path, schemaContext, domTransactionChain, schemaNode);
275 final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
276 if (readLeafList == null || readLeafList.getValue().isEmpty()) {
277 return makePut(path, schemaContext, readWriteTransaction, data);
279 insertWithPointLeafListPut(readWriteTransaction, LogicalDatastoreType.CONFIGURATION,
280 path, data, schemaContext, point, readLeafList, true);
281 return readWriteTransaction.submit();
285 throw new RestconfDocumentedException(
286 "Used bad value of insert parameter. Possible values are first, last, before or after, "
287 + "but was: " + insert);
292 public static NormalizedNode<?, ?> readList(final YangInstanceIdentifier path, final SchemaContext schemaContext,
293 final DOMTransactionChain domTransactionChain, final DataSchemaNode schemaNode) {
294 final InstanceIdentifierContext<?> iid = new InstanceIdentifierContext<SchemaNode>(
295 path.getParent(), schemaNode, null, schemaContext);
296 final TransactionVarsWrapper transactionNode =
297 new TransactionVarsWrapper(iid, null, domTransactionChain);
298 final NormalizedNode<?, ?> readData = ReadDataTransactionUtil
299 .readData(RestconfDataServiceConstant.ReadData.CONFIG, transactionNode);
303 private static void insertWithPointLeafListPut(final DOMDataReadWriteTransaction rwTransaction,
304 final LogicalDatastoreType datastore, final YangInstanceIdentifier path,
305 final NormalizedNode<?, ?> data, final SchemaContext schemaContext, final String point,
306 final OrderedLeafSetNode<?> readLeafList, final boolean before) {
307 rwTransaction.delete(datastore, path.getParent());
308 final InstanceIdentifierContext<?> instanceIdentifier =
309 ControllerContext.getInstance().toInstanceIdentifier(point);
310 int lastItemPosition = 0;
311 for (final LeafSetEntryNode<?> nodeChild : readLeafList.getValue()) {
312 if (nodeChild.getIdentifier().equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
320 int lastInsertedPosition = 0;
321 final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path.getParent());
322 rwTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
323 for (final LeafSetEntryNode<?> nodeChild : readLeafList.getValue()) {
324 if (lastInsertedPosition == lastItemPosition) {
325 simplePut(datastore, path, rwTransaction, schemaContext, data);
327 final YangInstanceIdentifier childPath = path.getParent().node(nodeChild.getIdentifier());
328 rwTransaction.put(datastore, childPath, nodeChild);
329 lastInsertedPosition++;
333 private static void insertWithPointListPut(final DOMDataReadWriteTransaction writeTx,
334 final LogicalDatastoreType datastore, final YangInstanceIdentifier path,
335 final NormalizedNode<?, ?> data, final SchemaContext schemaContext, final String point,
336 final OrderedMapNode readList, final boolean before) {
337 writeTx.delete(datastore, path.getParent());
338 final InstanceIdentifierContext<?> instanceIdentifier =
339 ControllerContext.getInstance().toInstanceIdentifier(point);
340 int lastItemPosition = 0;
341 for (final MapEntryNode mapEntryNode : readList.getValue()) {
342 if (mapEntryNode.getIdentifier().equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
350 int lastInsertedPosition = 0;
351 final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path.getParent());
352 writeTx.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
353 for (final MapEntryNode mapEntryNode : readList.getValue()) {
354 if (lastInsertedPosition == lastItemPosition) {
355 simplePut(datastore, path, writeTx, schemaContext, data);
357 final YangInstanceIdentifier childPath = path.getParent().node(mapEntryNode.getIdentifier());
358 writeTx.put(datastore, childPath, mapEntryNode);
359 lastInsertedPosition++;
363 private static void listPut(final LogicalDatastoreType datastore, final YangInstanceIdentifier path,
364 final DOMDataReadWriteTransaction writeTx, final SchemaContext schemaContext,
365 final OrderedLeafSetNode<?> payload) {
366 final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
367 writeTx.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
368 TransactionUtil.ensureParentsByMerge(path, schemaContext, writeTx);
369 for (final LeafSetEntryNode<?> child : ((LeafSetNode<?>) payload).getValue()) {
370 final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
371 writeTx.put(datastore, childPath, child);
375 private static void listPut(final LogicalDatastoreType datastore, final YangInstanceIdentifier path,
376 final DOMDataReadWriteTransaction writeTx, final SchemaContext schemaContext,
377 final OrderedMapNode payload) {
378 final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
379 writeTx.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
380 TransactionUtil.ensureParentsByMerge(path, schemaContext, writeTx);
381 for (final MapEntryNode child : ((MapNode) payload).getValue()) {
382 final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
383 writeTx.put(datastore, childPath, child);
387 private static void simplePut(final LogicalDatastoreType configuration, final YangInstanceIdentifier path,
388 final DOMDataReadWriteTransaction writeTx, final SchemaContext schemaContext,
389 final NormalizedNode<?, ?> data) {
390 TransactionUtil.ensureParentsByMerge(path, schemaContext, writeTx);
391 writeTx.put(LogicalDatastoreType.CONFIGURATION, path, data);
394 private static CheckedFuture<Void, TransactionCommitFailedException> makePut(final YangInstanceIdentifier path,
395 final SchemaContext schemaContext, final DOMDataWriteTransaction writeTx, final NormalizedNode<?, ?> data) {
396 TransactionUtil.ensureParentsByMerge(path, schemaContext, writeTx);
397 writeTx.put(LogicalDatastoreType.CONFIGURATION, path, data);
398 return writeTx.submit();
401 public static DataSchemaNode checkListAndOrderedType(final SchemaContext ctx, final YangInstanceIdentifier path) {
402 final YangInstanceIdentifier parent = path.getParent();
403 final DataSchemaContextNode<?> node = DataSchemaContextTree.from(ctx).getChild(parent);
404 final DataSchemaNode dataSchemaNode = node.getDataSchemaNode();
406 if (dataSchemaNode instanceof ListSchemaNode) {
407 if (!((ListSchemaNode) dataSchemaNode).isUserOrdered()) {
408 throw new RestconfDocumentedException("Insert parameter can be used only with ordered-by user list.");
410 return dataSchemaNode;
412 if (dataSchemaNode instanceof LeafListSchemaNode) {
413 if (!((LeafListSchemaNode) dataSchemaNode).isUserOrdered()) {
414 throw new RestconfDocumentedException(
415 "Insert parameter can be used only with ordered-by user leaf-list.");
417 return dataSchemaNode;
419 throw new RestconfDocumentedException("Insert parameter can be used only with list or leaf-list");