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.base.Optional;
11 import com.google.common.collect.Maps;
12 import com.google.common.util.concurrent.CheckedFuture;
13 import java.util.List;
15 import javax.ws.rs.core.Response;
16 import javax.ws.rs.core.Response.Status;
17 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
18 import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
19 import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
20 import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
21 import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
22 import org.opendaylight.controller.md.sal.dom.api.DOMTransactionChain;
23 import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
24 import org.opendaylight.restconf.common.context.NormalizedNodeContext;
25 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
26 import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag;
27 import org.opendaylight.restconf.common.errors.RestconfError.ErrorType;
28 import org.opendaylight.restconf.common.validation.RestconfValidationUtils;
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.MapNode;
40 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
41 import org.opendaylight.yangtools.yang.data.api.schema.OrderedLeafSetNode;
42 import org.opendaylight.yangtools.yang.data.api.schema.OrderedMapNode;
43 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
44 import org.opendaylight.yangtools.yang.data.util.DataSchemaContextNode;
45 import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
46 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
47 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
48 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
49 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
50 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
53 * Util class for put data to DS.
56 public final class PutDataTransactionUtil {
58 private PutDataTransactionUtil() {
63 * Valid input data with {@link SchemaNode}.
70 public static void validInputData(final SchemaNode schemaNode, final NormalizedNodeContext payload) {
71 if (schemaNode != null && payload.getData() == null) {
72 throw new RestconfDocumentedException("Input is required.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
73 } else if (schemaNode == null && payload.getData() != null) {
74 throw new RestconfDocumentedException("No input expected.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
79 * Valid top level node name.
86 public static void validTopLevelNodeName(final YangInstanceIdentifier path, final NormalizedNodeContext payload) {
87 final String payloadName = payload.getData().getNodeType().getLocalName();
90 if (!payload.getData().getNodeType().equals(RestconfDataServiceConstant.NETCONF_BASE_QNAME)) {
91 throw new RestconfDocumentedException("Instance identifier has to contain at least one path argument",
92 ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
95 final String identifierName = path.getLastPathArgument().getNodeType().getLocalName();
96 if (!payloadName.equals(identifierName)) {
97 throw new RestconfDocumentedException(
98 "Payload name (" + payloadName + ") is different from identifier name (" + identifierName + ")",
99 ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
105 * Validates whether keys in {@code payload} are equal to values of keys in
106 * {@code iiWithData} for list schema node.
108 * @throws RestconfDocumentedException
109 * if key values or key count in payload and URI isn't equal
111 public static void validateListKeysEqualityInPayloadAndUri(final NormalizedNodeContext payload) {
112 final InstanceIdentifierContext<?> iiWithData = payload.getInstanceIdentifierContext();
113 final PathArgument lastPathArgument = iiWithData.getInstanceIdentifier().getLastPathArgument();
114 final SchemaNode schemaNode = iiWithData.getSchemaNode();
115 final NormalizedNode<?, ?> data = payload.getData();
116 if (schemaNode instanceof ListSchemaNode) {
117 final List<QName> keyDefinitions = ((ListSchemaNode) schemaNode).getKeyDefinition();
118 if (lastPathArgument instanceof NodeIdentifierWithPredicates && data instanceof MapEntryNode) {
119 final Map<QName, Object> uriKeyValues = ((NodeIdentifierWithPredicates) lastPathArgument)
121 isEqualUriAndPayloadKeyValues(uriKeyValues, (MapEntryNode) data, keyDefinitions);
126 private static void isEqualUriAndPayloadKeyValues(final Map<QName, Object> uriKeyValues, final MapEntryNode payload,
127 final List<QName> keyDefinitions) {
128 final Map<QName, Object> mutableCopyUriKeyValues = Maps.newHashMap(uriKeyValues);
129 for (final QName keyDefinition : keyDefinitions) {
130 final Object uriKeyValue = mutableCopyUriKeyValues.remove(keyDefinition);
131 RestconfValidationUtils.checkDocumentedError(uriKeyValue != null, ErrorType.PROTOCOL, ErrorTag.DATA_MISSING,
132 "Missing key " + keyDefinition + " in URI.");
134 final Object dataKeyValue = payload.getIdentifier().getKeyValues().get(keyDefinition);
136 if (!uriKeyValue.equals(dataKeyValue)) {
137 final String errMsg = "The value '" + uriKeyValue + "' for key '" + keyDefinition.getLocalName()
138 + "' specified in the URI doesn't match the value '" + dataKeyValue
139 + "' specified in the message body. ";
140 throw new RestconfDocumentedException(errMsg, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
146 * Check mount point and prepare variables for put data to DS.
150 * @param schemaCtxRef
151 * reference to {@link SchemaContext}
152 * @param transactionNode
153 * wrapper of variables for transaction
158 * @return {@link CheckedFuture}
160 public static Response putData(final NormalizedNodeContext payload, final SchemaContextRef schemaCtxRef,
161 final TransactionVarsWrapper transactionNode, final String insert, final String point) {
162 final YangInstanceIdentifier path = payload.getInstanceIdentifierContext().getInstanceIdentifier();
163 final SchemaContext schemaContext = schemaCtxRef.get();
165 final DOMDataReadWriteTransaction readWriteTransaction =
166 transactionNode.getTransactionChain().newReadWriteTransaction();
168 final CheckedFuture<Boolean, ReadFailedException> existsFuture =
169 readWriteTransaction.exists(LogicalDatastoreType.CONFIGURATION, path);
170 final FutureDataFactory<Boolean> existsResponse = new FutureDataFactory<>();
171 FutureCallbackTx.addCallback(existsFuture, RestconfDataServiceConstant.PutData.PUT_TX_TYPE, existsResponse);
173 final ResponseFactory responseFactory = new ResponseFactory(existsResponse.result ? Status.OK : Status.CREATED);
174 final CheckedFuture<Void, TransactionCommitFailedException> submitData = submitData(path, schemaContext,
175 transactionNode.getTransactionChain(), 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 domTransactionChain
195 * @return {@link CheckedFuture}
197 private static CheckedFuture<Void, TransactionCommitFailedException> submitData(final YangInstanceIdentifier path,
198 final SchemaContext schemaContext, final DOMTransactionChain domTransactionChain,
199 final DOMDataReadWriteTransaction readWriteTransaction,
200 final NormalizedNode<?, ?> data, final String insert, final String point) {
201 if (insert == null) {
202 return makePut(path, schemaContext, readWriteTransaction, data);
204 final DataSchemaNode schemaNode = checkListAndOrderedType(schemaContext, path);
207 if (schemaNode instanceof ListSchemaNode) {
208 final NormalizedNode<?, ?> readData =
209 readList(path, schemaContext, domTransactionChain, schemaNode);
210 final OrderedMapNode readList = (OrderedMapNode) readData;
211 if (readList == null || readList.getValue().isEmpty()) {
212 return makePut(path, schemaContext, readWriteTransaction, data);
214 readWriteTransaction.delete(LogicalDatastoreType.CONFIGURATION, path.getParent());
215 simplePut(LogicalDatastoreType.CONFIGURATION, path, readWriteTransaction,
216 schemaContext, data);
217 listPut(LogicalDatastoreType.CONFIGURATION, path.getParent(), readWriteTransaction,
218 schemaContext, readList);
219 return readWriteTransaction.submit();
222 final NormalizedNode<?, ?> readData =
223 readList(path, schemaContext, domTransactionChain, schemaNode);
225 final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
226 if (readLeafList == null || readLeafList.getValue().isEmpty()) {
227 return makePut(path, schemaContext, readWriteTransaction, data);
229 readWriteTransaction.delete(LogicalDatastoreType.CONFIGURATION, path.getParent());
230 simplePut(LogicalDatastoreType.CONFIGURATION, path, readWriteTransaction,
231 schemaContext, data);
232 listPut(LogicalDatastoreType.CONFIGURATION, path.getParent(), readWriteTransaction,
233 schemaContext, readLeafList);
234 return readWriteTransaction.submit();
238 return makePut(path, schemaContext, readWriteTransaction, data);
240 if (schemaNode instanceof ListSchemaNode) {
241 final NormalizedNode<?, ?> readData =
242 readList(path, schemaContext, domTransactionChain, schemaNode);
243 final OrderedMapNode readList = (OrderedMapNode) readData;
244 if (readList == null || readList.getValue().isEmpty()) {
245 return makePut(path, schemaContext, readWriteTransaction, data);
247 insertWithPointListPut(readWriteTransaction, LogicalDatastoreType.CONFIGURATION, path,
248 data, schemaContext, point, readList, true);
249 return readWriteTransaction.submit();
252 final NormalizedNode<?, ?> readData =
253 readList(path, schemaContext, domTransactionChain, schemaNode);
255 final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
256 if (readLeafList == null || readLeafList.getValue().isEmpty()) {
257 return makePut(path, schemaContext, readWriteTransaction, data);
259 insertWithPointLeafListPut(readWriteTransaction, LogicalDatastoreType.CONFIGURATION,
260 path, data, schemaContext, point, readLeafList, true);
261 return readWriteTransaction.submit();
265 if (schemaNode instanceof ListSchemaNode) {
266 final NormalizedNode<?, ?> readData =
267 readList(path, schemaContext, domTransactionChain, schemaNode);
268 final OrderedMapNode readList = (OrderedMapNode) readData;
269 if (readList == null || readList.getValue().isEmpty()) {
270 return makePut(path, schemaContext, readWriteTransaction, data);
272 insertWithPointListPut(readWriteTransaction, LogicalDatastoreType.CONFIGURATION,
273 path, data, schemaContext, point, readList, false);
274 return readWriteTransaction.submit();
277 final NormalizedNode<?, ?> readData =
278 readList(path, schemaContext, domTransactionChain, schemaNode);
280 final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
281 if (readLeafList == null || readLeafList.getValue().isEmpty()) {
282 return makePut(path, schemaContext, readWriteTransaction, data);
284 insertWithPointLeafListPut(readWriteTransaction, LogicalDatastoreType.CONFIGURATION,
285 path, data, schemaContext, point, readLeafList, true);
286 return readWriteTransaction.submit();
290 throw new RestconfDocumentedException(
291 "Used bad value of insert parameter. Possible values are first, last, before or after, "
292 + "but was: " + insert);
297 public static NormalizedNode<?, ?> readList(final YangInstanceIdentifier path, final SchemaContext schemaContext,
298 final DOMTransactionChain domTransactionChain, final DataSchemaNode schemaNode) {
299 final InstanceIdentifierContext<?> iid = new InstanceIdentifierContext<SchemaNode>(
300 path.getParent(), schemaNode, null, schemaContext);
301 final TransactionVarsWrapper transactionNode =
302 new TransactionVarsWrapper(iid, null, domTransactionChain);
303 final NormalizedNode<?, ?> readData = ReadDataTransactionUtil
304 .readData(RestconfDataServiceConstant.ReadData.CONFIG, transactionNode, schemaContext);
308 private static void insertWithPointLeafListPut(final DOMDataReadWriteTransaction rwTransaction,
309 final LogicalDatastoreType datastore, final YangInstanceIdentifier path,
310 final NormalizedNode<?, ?> data, final SchemaContext schemaContext, final String point,
311 final OrderedLeafSetNode<?> readLeafList, final boolean before) {
312 rwTransaction.delete(datastore, path.getParent());
313 final InstanceIdentifierContext<?> instanceIdentifier =
314 ParserIdentifier.toInstanceIdentifier(point, schemaContext, Optional.absent());
315 int lastItemPosition = 0;
316 for (final LeafSetEntryNode<?> nodeChild : readLeafList.getValue()) {
317 if (nodeChild.getIdentifier().equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
325 int lastInsertedPosition = 0;
326 final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path.getParent());
327 rwTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
328 for (final LeafSetEntryNode<?> nodeChild : readLeafList.getValue()) {
329 if (lastInsertedPosition == lastItemPosition) {
330 simplePut(datastore, path, rwTransaction, schemaContext, data);
332 final YangInstanceIdentifier childPath = path.getParent().node(nodeChild.getIdentifier());
333 rwTransaction.put(datastore, childPath, nodeChild);
334 lastInsertedPosition++;
338 private static void insertWithPointListPut(final DOMDataReadWriteTransaction writeTx,
339 final LogicalDatastoreType datastore, final YangInstanceIdentifier path,
340 final NormalizedNode<?, ?> data, final SchemaContext schemaContext, final String point,
341 final OrderedMapNode readList, final boolean before) {
342 writeTx.delete(datastore, path.getParent());
343 final InstanceIdentifierContext<?> instanceIdentifier =
344 ParserIdentifier.toInstanceIdentifier(point, schemaContext, Optional.absent());
345 int lastItemPosition = 0;
346 for (final MapEntryNode mapEntryNode : readList.getValue()) {
347 if (mapEntryNode.getIdentifier().equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
355 int lastInsertedPosition = 0;
356 final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path.getParent());
357 writeTx.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
358 for (final MapEntryNode mapEntryNode : readList.getValue()) {
359 if (lastInsertedPosition == lastItemPosition) {
360 simplePut(datastore, path, writeTx, schemaContext, data);
362 final YangInstanceIdentifier childPath = path.getParent().node(mapEntryNode.getIdentifier());
363 writeTx.put(datastore, childPath, mapEntryNode);
364 lastInsertedPosition++;
368 private static void listPut(final LogicalDatastoreType datastore, final YangInstanceIdentifier path,
369 final DOMDataReadWriteTransaction writeTx, final SchemaContext schemaContext,
370 final OrderedLeafSetNode<?> payload) {
371 final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
372 writeTx.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
373 TransactionUtil.ensureParentsByMerge(path, schemaContext, writeTx);
374 for (final LeafSetEntryNode<?> child : ((LeafSetNode<?>) payload).getValue()) {
375 final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
376 writeTx.put(datastore, childPath, child);
380 private static void listPut(final LogicalDatastoreType datastore, final YangInstanceIdentifier path,
381 final DOMDataReadWriteTransaction writeTx, final SchemaContext schemaContext,
382 final OrderedMapNode payload) {
383 final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
384 writeTx.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
385 TransactionUtil.ensureParentsByMerge(path, schemaContext, writeTx);
386 for (final MapEntryNode child : ((MapNode) payload).getValue()) {
387 final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
388 writeTx.put(datastore, childPath, child);
392 private static void simplePut(final LogicalDatastoreType configuration, final YangInstanceIdentifier path,
393 final DOMDataReadWriteTransaction writeTx, final SchemaContext schemaContext,
394 final NormalizedNode<?, ?> data) {
395 TransactionUtil.ensureParentsByMerge(path, schemaContext, writeTx);
396 writeTx.put(LogicalDatastoreType.CONFIGURATION, path, data);
399 private static CheckedFuture<Void, TransactionCommitFailedException> makePut(final YangInstanceIdentifier path,
400 final SchemaContext schemaContext, final DOMDataWriteTransaction writeTx, final NormalizedNode<?, ?> data) {
401 TransactionUtil.ensureParentsByMerge(path, schemaContext, writeTx);
402 writeTx.put(LogicalDatastoreType.CONFIGURATION, path, data);
403 return writeTx.submit();
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.");
415 return dataSchemaNode;
417 if (dataSchemaNode instanceof LeafListSchemaNode) {
418 if (!((LeafListSchemaNode) dataSchemaNode).isUserOrdered()) {
419 throw new RestconfDocumentedException(
420 "Insert parameter can be used only with ordered-by user leaf-list.");
422 return dataSchemaNode;
424 throw new RestconfDocumentedException("Insert parameter can be used only with list or leaf-list");