3feab015633f4e1be6a6f9aa8d88ff435e3bb9a7
[netconf.git] / restconf / sal-rest-connector / src / main / java / org / opendaylight / restconf / restful / utils / PutDataTransactionUtil.java
1 /*
2  * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.restconf.restful.utils;
9
10 import com.google.common.collect.Maps;
11 import com.google.common.util.concurrent.CheckedFuture;
12 import java.util.List;
13 import java.util.Map;
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;
48
49 /**
50  * Util class for put data to DS.
51  *
52  */
53 public final class PutDataTransactionUtil {
54
55     /**
56      * Valid input data with {@link SchemaNode}.
57      *
58      * @param schemaNode
59      *             {@link SchemaNode}
60      * @param payload
61      *             input data
62      */
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);
68         }
69     }
70
71     /**
72      * Valid top level node name.
73      *
74      * @param path
75      *             path of node
76      * @param payload
77      *             data
78      */
79     public static void validTopLevelNodeName(final YangInstanceIdentifier path, final NormalizedNodeContext payload) {
80         final String payloadName = payload.getData().getNodeType().getLocalName();
81
82         if (path.isEmpty()) {
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);
86             }
87         } else {
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);
93             }
94         }
95     }
96
97     /**
98      * Validates whether keys in {@code payload} are equal to values of keys in
99      * {@code iiWithData} for list schema node.
100      *
101      * @throws RestconfDocumentedException
102      *             if key values or key count in payload and URI isn't equal
103      */
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)
113                         .getKeyValues();
114                 isEqualUriAndPayloadKeyValues(uriKeyValues, (MapEntryNode) data, keyDefinitions);
115             }
116         }
117     }
118
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.");
126
127             final Object dataKeyValue = payload.getIdentifier().getKeyValues().get(keyDefinition);
128
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);
134             }
135         }
136     }
137
138     /**
139      * Check mount point and prepare variables for put data to DS.
140      *
141      * @param payload
142      *             data to put
143      * @param schemaCtxRef
144      *             reference to {@link SchemaContext}
145      * @param transactionNode
146      *             wrapper of variables for transaction
147      * @param point
148      *             query parameter
149      * @param insert
150      *             query parameter
151      * @return {@link CheckedFuture}
152      */
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();
162     }
163
164     /**
165      * Put data to DS.
166      *
167      * @param path
168      *             path of data
169      * @param schemaContext
170      *             {@link SchemaContext}
171      * @param domTransactionChain
172      *             write transaction
173      * @param data
174      *             data
175      * @param point
176      *             query parameter
177      * @param insert
178      *             query parameter
179      * @return {@link CheckedFuture}
180      */
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);
187         } else {
188             final DataSchemaNode schemaNode = checkListAndOrderedType(schemaContext, path);
189             switch (insert) {
190                 case "first":
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);
197                         } else {
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();
204                         }
205                     } else {
206                         final NormalizedNode<?, ?> readData =
207                                 readList(path, schemaContext, domTransactionChain, schemaNode);
208
209                         final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
210                         if ((readLeafList == null) || readLeafList.getValue().isEmpty()) {
211                             return makePut(path, schemaContext, newReadWriteTransaction, data);
212                         } else {
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();
219                         }
220                     }
221                 case "last":
222                     return makePut(path, schemaContext, newReadWriteTransaction, data);
223                 case "before":
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);
230                         } else {
231                             insertWithPointListPut(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION, path,
232                                     data, schemaContext, point, readList, true);
233                             return newReadWriteTransaction.submit();
234                         }
235                     } else {
236                         final NormalizedNode<?, ?> readData =
237                                 readList(path, schemaContext, domTransactionChain, schemaNode);
238
239                         final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
240                         if ((readLeafList == null) || readLeafList.getValue().isEmpty()) {
241                             return makePut(path, schemaContext, newReadWriteTransaction, data);
242                         } else {
243                             insertWithPointLeafListPut(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION,
244                                     path, data, schemaContext, point, readLeafList, true);
245                             return newReadWriteTransaction.submit();
246                         }
247                     }
248                 case "after":
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);
255                         } else {
256                             insertWithPointListPut(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION,
257                                     path, data, schemaContext, point, readList, false);
258                             return newReadWriteTransaction.submit();
259                         }
260                     } else {
261                         final NormalizedNode<?, ?> readData =
262                                 readList(path, schemaContext, domTransactionChain, schemaNode);
263
264                         final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
265                         if ((readLeafList == null) || readLeafList.getValue().isEmpty()) {
266                             return makePut(path, schemaContext, newReadWriteTransaction, data);
267                         } else {
268                             insertWithPointLeafListPut(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION,
269                                     path, data, schemaContext, point, readLeafList, true);
270                             return newReadWriteTransaction.submit();
271                         }
272                     }
273                 default:
274                     throw new RestconfDocumentedException(
275                             "Used bad value of insert parameter. Possible values are first, last, before or after, "
276                                     + "but was: " + insert);
277             }
278         }
279     }
280
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);
289         return readData;
290     }
291
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())) {
302                 break;
303             }
304             lastItemPosition++;
305         }
306         if (!before) {
307             lastItemPosition++;
308         }
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);
315             }
316             final YangInstanceIdentifier childPath = path.getParent().node(nodeChild.getIdentifier());
317             rwTransaction.put(datastore, childPath, nodeChild);
318             lastInsertedPosition++;
319         }
320     }
321
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())) {
332                 break;
333             }
334             lastItemPosition++;
335         }
336         if (!before) {
337             lastItemPosition++;
338         }
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);
345             }
346             final YangInstanceIdentifier childPath = path.getParent().node(mapEntryNode.getIdentifier());
347             writeTx.put(datastore, childPath, mapEntryNode);
348             lastInsertedPosition++;
349         }
350     }
351
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);
361         }
362     }
363
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);
373         }
374     }
375
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);
381     }
382
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();
388     }
389
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();
394
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.");
398             }
399             return dataSchemaNode;
400         }
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.");
405             }
406             return dataSchemaNode;
407         }
408         throw new RestconfDocumentedException("Insert parameter can be used only with list or leaf-list");
409     }
410 }