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 org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
16 import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
17 import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
18 import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
19 import org.opendaylight.controller.md.sal.dom.api.DOMTransactionChain;
20 import org.opendaylight.netconf.md.sal.rest.common.RestconfValidationUtils;
21 import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
22 import org.opendaylight.netconf.sal.restconf.impl.InstanceIdentifierContext;
23 import org.opendaylight.netconf.sal.restconf.impl.NormalizedNodeContext;
24 import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
25 import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorTag;
26 import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorType;
27 import org.opendaylight.restconf.common.references.SchemaContextRef;
28 import org.opendaylight.restconf.restful.transaction.TransactionVarsWrapper;
29 import org.opendaylight.yangtools.yang.common.QName;
30 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
31 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
32 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
33 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
34 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
35 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
36 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
37 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
38 import org.opendaylight.yangtools.yang.data.api.schema.OrderedLeafSetNode;
39 import org.opendaylight.yangtools.yang.data.api.schema.OrderedMapNode;
40 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
41 import org.opendaylight.yangtools.yang.data.util.DataSchemaContextNode;
42 import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
43 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
44 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
45 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
46 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
47 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
50 * Util class for put data to DS.
53 public final class PutDataTransactionUtil {
56 * Valid input data with {@link SchemaNode}.
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.
79 public static void validTopLevelNodeName(final YangInstanceIdentifier path, final NormalizedNodeContext payload) {
80 final String payloadName = payload.getData().getNodeType().getLocalName();
83 if (!payload.getData().getNodeType().equals(RestconfDataServiceConstant.NETCONF_BASE_QNAME)) {
84 throw new RestconfDocumentedException("Instance identifier has to contain at least one path argument",
85 ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
88 final String identifierName = path.getLastPathArgument().getNodeType().getLocalName();
89 if (!payloadName.equals(identifierName)) {
90 throw new RestconfDocumentedException(
91 "Payload name (" + payloadName + ") is different from identifier name (" + identifierName + ")",
92 ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
98 * Validates whether keys in {@code payload} are equal to values of keys in
99 * {@code iiWithData} for list schema node.
101 * @throws RestconfDocumentedException
102 * if key values or key count in payload and URI isn't equal
104 public static void validateListKeysEqualityInPayloadAndUri(final NormalizedNodeContext payload) {
105 final InstanceIdentifierContext<?> iiWithData = payload.getInstanceIdentifierContext();
106 final PathArgument lastPathArgument = iiWithData.getInstanceIdentifier().getLastPathArgument();
107 final SchemaNode schemaNode = iiWithData.getSchemaNode();
108 final NormalizedNode<?, ?> data = payload.getData();
109 if (schemaNode instanceof ListSchemaNode) {
110 final List<QName> keyDefinitions = ((ListSchemaNode) schemaNode).getKeyDefinition();
111 if ((lastPathArgument instanceof NodeIdentifierWithPredicates) && (data instanceof MapEntryNode)) {
112 final Map<QName, Object> uriKeyValues = ((NodeIdentifierWithPredicates) lastPathArgument)
114 isEqualUriAndPayloadKeyValues(uriKeyValues, (MapEntryNode) data, keyDefinitions);
119 private static void isEqualUriAndPayloadKeyValues(final Map<QName, Object> uriKeyValues, final MapEntryNode payload,
120 final List<QName> keyDefinitions) {
121 final Map<QName, Object> mutableCopyUriKeyValues = Maps.newHashMap(uriKeyValues);
122 for (final QName keyDefinition : keyDefinitions) {
123 final Object uriKeyValue = mutableCopyUriKeyValues.remove(keyDefinition);
124 RestconfValidationUtils.checkDocumentedError(uriKeyValue != null, ErrorType.PROTOCOL, ErrorTag.DATA_MISSING,
125 "Missing key " + keyDefinition + " in URI.");
127 final Object dataKeyValue = payload.getIdentifier().getKeyValues().get(keyDefinition);
129 if (!uriKeyValue.equals(dataKeyValue)) {
130 final String errMsg = "The value '" + uriKeyValue + "' for key '" + keyDefinition.getLocalName()
131 + "' specified in the URI doesn't match the value '" + dataKeyValue
132 + "' specified in the message body. ";
133 throw new RestconfDocumentedException(errMsg, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
139 * Check mount point and prepare variables for put data to DS.
143 * @param schemaCtxRef
144 * reference to {@link SchemaContext}
145 * @param transactionNode
146 * wrapper of variables for transaction
151 * @return {@link CheckedFuture}
153 public static Response putData(final NormalizedNodeContext payload, final SchemaContextRef schemaCtxRef,
154 final TransactionVarsWrapper transactionNode, final String insert, final String point) {
155 final YangInstanceIdentifier path = payload.getInstanceIdentifierContext().getInstanceIdentifier();
156 final ResponseFactory responseFactory = new ResponseFactory(
157 ReadDataTransactionUtil.readData(RestconfDataServiceConstant.ReadData.CONFIG, transactionNode));
158 final CheckedFuture<Void, TransactionCommitFailedException> submitData = submitData(path, schemaCtxRef.get(),
159 transactionNode.getTransactionChain(), payload.getData(), insert, point);
160 FutureCallbackTx.addCallback(submitData, RestconfDataServiceConstant.PutData.PUT_TX_TYPE, responseFactory);
161 return responseFactory.build();
169 * @param schemaContext
170 * {@link SchemaContext}
171 * @param domTransactionChain
179 * @return {@link CheckedFuture}
181 private static CheckedFuture<Void, TransactionCommitFailedException> submitData(final YangInstanceIdentifier path,
182 final SchemaContext schemaContext, final DOMTransactionChain domTransactionChain,
183 final NormalizedNode<?, ?> data, final String insert, final String point) {
184 final DOMDataReadWriteTransaction newReadWriteTransaction = domTransactionChain.newReadWriteTransaction();
185 if (insert == null) {
186 return makePut(path, schemaContext, newReadWriteTransaction, data);
188 final DataSchemaNode schemaNode = checkListAndOrderedType(schemaContext, path);
191 if (schemaNode instanceof ListSchemaNode) {
192 final NormalizedNode<?, ?> readData =
193 readList(path, schemaContext, domTransactionChain, schemaNode);
194 final OrderedMapNode readList = (OrderedMapNode) readData;
195 if ((readList == null) || readList.getValue().isEmpty()) {
196 return makePut(path, schemaContext, newReadWriteTransaction, data);
198 newReadWriteTransaction.delete(LogicalDatastoreType.CONFIGURATION, path.getParent());
199 simplePut(LogicalDatastoreType.CONFIGURATION, path, newReadWriteTransaction,
200 schemaContext, data);
201 listPut(LogicalDatastoreType.CONFIGURATION, path.getParent(), newReadWriteTransaction,
202 schemaContext, readList);
203 return newReadWriteTransaction.submit();
206 final NormalizedNode<?, ?> readData =
207 readList(path, schemaContext, domTransactionChain, schemaNode);
209 final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
210 if ((readLeafList == null) || readLeafList.getValue().isEmpty()) {
211 return makePut(path, schemaContext, newReadWriteTransaction, data);
213 newReadWriteTransaction.delete(LogicalDatastoreType.CONFIGURATION, path.getParent());
214 simplePut(LogicalDatastoreType.CONFIGURATION, path, newReadWriteTransaction,
215 schemaContext, data);
216 listPut(LogicalDatastoreType.CONFIGURATION, path.getParent(), newReadWriteTransaction,
217 schemaContext, readLeafList);
218 return newReadWriteTransaction.submit();
222 return makePut(path, schemaContext, newReadWriteTransaction, data);
224 if (schemaNode instanceof ListSchemaNode) {
225 final NormalizedNode<?, ?> readData =
226 readList(path, schemaContext, domTransactionChain, schemaNode);
227 final OrderedMapNode readList = (OrderedMapNode) readData;
228 if ((readList == null) || readList.getValue().isEmpty()) {
229 return makePut(path, schemaContext, newReadWriteTransaction, data);
231 insertWithPointListPut(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION, path,
232 data, schemaContext, point, readList, true);
233 return newReadWriteTransaction.submit();
236 final NormalizedNode<?, ?> readData =
237 readList(path, schemaContext, domTransactionChain, schemaNode);
239 final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
240 if ((readLeafList == null) || readLeafList.getValue().isEmpty()) {
241 return makePut(path, schemaContext, newReadWriteTransaction, data);
243 insertWithPointLeafListPut(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION,
244 path, data, schemaContext, point, readLeafList, true);
245 return newReadWriteTransaction.submit();
249 if (schemaNode instanceof ListSchemaNode) {
250 final NormalizedNode<?, ?> readData =
251 readList(path, schemaContext, domTransactionChain, schemaNode);
252 final OrderedMapNode readList = (OrderedMapNode) readData;
253 if ((readList == null) || readList.getValue().isEmpty()) {
254 return makePut(path, schemaContext, newReadWriteTransaction, data);
256 insertWithPointListPut(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION,
257 path, data, schemaContext, point, readList, false);
258 return newReadWriteTransaction.submit();
261 final NormalizedNode<?, ?> readData =
262 readList(path, schemaContext, domTransactionChain, schemaNode);
264 final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
265 if ((readLeafList == null) || readLeafList.getValue().isEmpty()) {
266 return makePut(path, schemaContext, newReadWriteTransaction, data);
268 insertWithPointLeafListPut(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION,
269 path, data, schemaContext, point, readLeafList, true);
270 return newReadWriteTransaction.submit();
274 throw new RestconfDocumentedException(
275 "Used bad value of insert parameter. Possible values are first, last, before or after, "
276 + "but was: " + insert);
281 public static NormalizedNode<?, ?> readList(final YangInstanceIdentifier path, final SchemaContext schemaContext,
282 final DOMTransactionChain domTransactionChain, final DataSchemaNode schemaNode) {
283 final InstanceIdentifierContext<?> iid = new InstanceIdentifierContext<SchemaNode>(
284 path.getParent(), schemaNode, null, schemaContext);
285 final TransactionVarsWrapper transactionNode =
286 new TransactionVarsWrapper(iid, null, domTransactionChain);
287 final NormalizedNode<?, ?> readData = ReadDataTransactionUtil
288 .readData(RestconfDataServiceConstant.ReadData.CONFIG, transactionNode);
292 private static void insertWithPointLeafListPut(final DOMDataReadWriteTransaction rwTransaction,
293 final LogicalDatastoreType datastore, final YangInstanceIdentifier path,
294 final NormalizedNode<?, ?> data, final SchemaContext schemaContext, final String point,
295 final OrderedLeafSetNode<?> readLeafList, final boolean before) {
296 rwTransaction.delete(datastore, path.getParent());
297 final InstanceIdentifierContext<?> instanceIdentifier =
298 ControllerContext.getInstance().toInstanceIdentifier(point);
299 int lastItemPosition = 0;
300 for (final LeafSetEntryNode<?> nodeChild : readLeafList.getValue()) {
301 if (nodeChild.getIdentifier().equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
309 int lastInsertedPosition = 0;
310 final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path.getParent());
311 rwTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
312 for (final LeafSetEntryNode<?> nodeChild : readLeafList.getValue()) {
313 if (lastInsertedPosition == lastItemPosition) {
314 simplePut(datastore, path, rwTransaction, schemaContext, data);
316 final YangInstanceIdentifier childPath = path.getParent().node(nodeChild.getIdentifier());
317 rwTransaction.put(datastore, childPath, nodeChild);
318 lastInsertedPosition++;
322 private static void insertWithPointListPut(final DOMDataReadWriteTransaction writeTx,
323 final LogicalDatastoreType datastore, final YangInstanceIdentifier path,
324 final NormalizedNode<?, ?> data, final SchemaContext schemaContext, final String point,
325 final OrderedMapNode readList, final boolean before) {
326 writeTx.delete(datastore, path.getParent());
327 final InstanceIdentifierContext<?> instanceIdentifier =
328 ControllerContext.getInstance().toInstanceIdentifier(point);
329 int lastItemPosition = 0;
330 for (final MapEntryNode mapEntryNode : readList.getValue()) {
331 if (mapEntryNode.getIdentifier().equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
339 int lastInsertedPosition = 0;
340 final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path.getParent());
341 writeTx.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
342 for (final MapEntryNode mapEntryNode : readList.getValue()) {
343 if (lastInsertedPosition == lastItemPosition) {
344 simplePut(datastore, path, writeTx, schemaContext, data);
346 final YangInstanceIdentifier childPath = path.getParent().node(mapEntryNode.getIdentifier());
347 writeTx.put(datastore, childPath, mapEntryNode);
348 lastInsertedPosition++;
352 private static void listPut(final LogicalDatastoreType datastore, final YangInstanceIdentifier path,
353 final DOMDataReadWriteTransaction writeTx, final SchemaContext schemaContext,
354 final OrderedLeafSetNode<?> payload) {
355 final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
356 writeTx.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
357 TransactionUtil.ensureParentsByMerge(path, schemaContext, writeTx);
358 for (final LeafSetEntryNode<?> child : ((LeafSetNode<?>) payload).getValue()) {
359 final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
360 writeTx.put(datastore, childPath, child);
364 private static void listPut(final LogicalDatastoreType datastore, final YangInstanceIdentifier path,
365 final DOMDataReadWriteTransaction writeTx, final SchemaContext schemaContext,
366 final OrderedMapNode payload) {
367 final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
368 writeTx.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
369 TransactionUtil.ensureParentsByMerge(path, schemaContext, writeTx);
370 for (final MapEntryNode child : ((MapNode) payload).getValue()) {
371 final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
372 writeTx.put(datastore, childPath, child);
376 private static void simplePut(final LogicalDatastoreType configuration, final YangInstanceIdentifier path,
377 final DOMDataReadWriteTransaction writeTx, final SchemaContext schemaContext,
378 final NormalizedNode<?, ?> data) {
379 TransactionUtil.ensureParentsByMerge(path, schemaContext, writeTx);
380 writeTx.put(LogicalDatastoreType.CONFIGURATION, path, data);
383 private static CheckedFuture<Void, TransactionCommitFailedException> makePut(final YangInstanceIdentifier path,
384 final SchemaContext schemaContext, final DOMDataWriteTransaction writeTx, final NormalizedNode<?, ?> data) {
385 TransactionUtil.ensureParentsByMerge(path, schemaContext, writeTx);
386 writeTx.put(LogicalDatastoreType.CONFIGURATION, path, data);
387 return writeTx.submit();
390 public static DataSchemaNode checkListAndOrderedType(final SchemaContext ctx, final YangInstanceIdentifier path) {
391 final YangInstanceIdentifier parent = path.getParent();
392 final DataSchemaContextNode<?> node = DataSchemaContextTree.from(ctx).getChild(parent);
393 final DataSchemaNode dataSchemaNode = node.getDataSchemaNode();
395 if (dataSchemaNode instanceof ListSchemaNode) {
396 if (!((ListSchemaNode) dataSchemaNode).isUserOrdered()) {
397 throw new RestconfDocumentedException("Insert parameter can be used only with ordered-by user list.");
399 return dataSchemaNode;
401 if (dataSchemaNode instanceof LeafListSchemaNode) {
402 if (!((LeafListSchemaNode) dataSchemaNode).isUserOrdered()) {
403 throw new RestconfDocumentedException(
404 "Insert parameter can be used only with ordered-by user leaf-list.");
406 return dataSchemaNode;
408 throw new RestconfDocumentedException("Insert parameter can be used only with list or leaf-list");