Add local MultivaluedHashMap implementation
[netconf.git] / restconf / restconf-nb-rfc8040 / src / main / java / org / opendaylight / restconf / nb / rfc8040 / rests / 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.nb.rfc8040.rests.utils;
9
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;
14 import java.util.Map;
15 import javax.ws.rs.core.Response;
16 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
17 import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
18 import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
19 import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
20 import org.opendaylight.controller.md.sal.dom.api.DOMTransactionChain;
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.ErrorTag;
25 import org.opendaylight.restconf.common.errors.RestconfError.ErrorType;
26 import org.opendaylight.restconf.common.validation.RestconfValidationUtils;
27 import org.opendaylight.restconf.nb.rfc8040.references.SchemaContextRef;
28 import org.opendaylight.restconf.nb.rfc8040.rests.transactions.TransactionVarsWrapper;
29 import org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserIdentifier;
30 import org.opendaylight.yangtools.yang.common.QName;
31 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
32 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
33 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
34 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
35 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
36 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
37 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
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;
49
50 /**
51  * Util class for put data to DS.
52  *
53  */
54 public final class PutDataTransactionUtil {
55
56     /**
57      * Valid input data with {@link SchemaNode}.
58      *
59      * @param schemaNode
60      *             {@link SchemaNode}
61      * @param payload
62      *             input data
63      */
64     public static void validInputData(final SchemaNode schemaNode, final NormalizedNodeContext payload) {
65         if ((schemaNode != null) && (payload.getData() == null)) {
66             throw new RestconfDocumentedException("Input is required.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
67         } else if ((schemaNode == null) && (payload.getData() != null)) {
68             throw new RestconfDocumentedException("No input expected.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
69         }
70     }
71
72     /**
73      * Valid top level node name.
74      *
75      * @param path
76      *             path of node
77      * @param payload
78      *             data
79      */
80     public static void validTopLevelNodeName(final YangInstanceIdentifier path, final NormalizedNodeContext payload) {
81         final String payloadName = payload.getData().getNodeType().getLocalName();
82
83         if (path.isEmpty()) {
84             if (!payload.getData().getNodeType().equals(RestconfDataServiceConstant.NETCONF_BASE_QNAME)) {
85                 throw new RestconfDocumentedException("Instance identifier has to contain at least one path argument",
86                         ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
87             }
88         } else {
89             final String identifierName = path.getLastPathArgument().getNodeType().getLocalName();
90             if (!payloadName.equals(identifierName)) {
91                 throw new RestconfDocumentedException(
92                         "Payload name (" + payloadName + ") is different from identifier name (" + identifierName + ")",
93                         ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
94             }
95         }
96     }
97
98     /**
99      * Validates whether keys in {@code payload} are equal to values of keys in
100      * {@code iiWithData} for list schema node.
101      *
102      * @throws RestconfDocumentedException
103      *             if key values or key count in payload and URI isn't equal
104      */
105     public static void validateListKeysEqualityInPayloadAndUri(final NormalizedNodeContext payload) {
106         final InstanceIdentifierContext<?> iiWithData = payload.getInstanceIdentifierContext();
107         final PathArgument lastPathArgument = iiWithData.getInstanceIdentifier().getLastPathArgument();
108         final SchemaNode schemaNode = iiWithData.getSchemaNode();
109         final NormalizedNode<?, ?> data = payload.getData();
110         if (schemaNode instanceof ListSchemaNode) {
111             final List<QName> keyDefinitions = ((ListSchemaNode) schemaNode).getKeyDefinition();
112             if ((lastPathArgument instanceof NodeIdentifierWithPredicates) && (data instanceof MapEntryNode)) {
113                 final Map<QName, Object> uriKeyValues = ((NodeIdentifierWithPredicates) lastPathArgument)
114                         .getKeyValues();
115                 isEqualUriAndPayloadKeyValues(uriKeyValues, (MapEntryNode) data, keyDefinitions);
116             }
117         }
118     }
119
120     private static void isEqualUriAndPayloadKeyValues(final Map<QName, Object> uriKeyValues, final MapEntryNode payload,
121             final List<QName> keyDefinitions) {
122         final Map<QName, Object> mutableCopyUriKeyValues = Maps.newHashMap(uriKeyValues);
123         for (final QName keyDefinition : keyDefinitions) {
124             final Object uriKeyValue = mutableCopyUriKeyValues.remove(keyDefinition);
125             RestconfValidationUtils.checkDocumentedError(uriKeyValue != null, ErrorType.PROTOCOL, ErrorTag.DATA_MISSING,
126                     "Missing key " + keyDefinition + " in URI.");
127
128             final Object dataKeyValue = payload.getIdentifier().getKeyValues().get(keyDefinition);
129
130             if (!uriKeyValue.equals(dataKeyValue)) {
131                 final String errMsg = "The value '" + uriKeyValue + "' for key '" + keyDefinition.getLocalName()
132                         + "' specified in the URI doesn't match the value '" + dataKeyValue
133                         + "' specified in the message body. ";
134                 throw new RestconfDocumentedException(errMsg, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
135             }
136         }
137     }
138
139     /**
140      * Check mount point and prepare variables for put data to DS.
141      *
142      * @param payload
143      *             data to put
144      * @param schemaCtxRef
145      *             reference to {@link SchemaContext}
146      * @param transactionNode
147      *             wrapper of variables for transaction
148      * @param point
149      *             query parameter
150      * @param insert
151      *             query parameter
152      * @return {@link CheckedFuture}
153      */
154     public static Response putData(final NormalizedNodeContext payload, final SchemaContextRef schemaCtxRef,
155                                final TransactionVarsWrapper transactionNode, final String insert, final String point) {
156         final YangInstanceIdentifier path = payload.getInstanceIdentifierContext().getInstanceIdentifier();
157         final SchemaContext schemaContext = schemaCtxRef.get();
158         final ResponseFactory responseFactory = new ResponseFactory(
159                 ReadDataTransactionUtil.readData(RestconfDataServiceConstant.ReadData.CONFIG, transactionNode,
160                         schemaContext));
161         final CheckedFuture<Void, TransactionCommitFailedException> submitData = submitData(path, schemaContext,
162                 transactionNode.getTransactionChain(), payload.getData(), insert, point);
163         FutureCallbackTx.addCallback(submitData, RestconfDataServiceConstant.PutData.PUT_TX_TYPE, responseFactory);
164         return responseFactory.build();
165     }
166
167     /**
168      * Put data to DS.
169      *
170      * @param path
171      *             path of data
172      * @param schemaContext
173      *             {@link SchemaContext}
174      * @param domTransactionChain
175      *             write transaction
176      * @param data
177      *             data
178      * @param point
179      *             query parameter
180      * @param insert
181      *             query parameter
182      * @return {@link CheckedFuture}
183      */
184     private static CheckedFuture<Void, TransactionCommitFailedException> submitData(final YangInstanceIdentifier path,
185             final SchemaContext schemaContext, final DOMTransactionChain domTransactionChain,
186             final NormalizedNode<?, ?> data, final String insert, final String point) {
187         final DOMDataReadWriteTransaction newReadWriteTransaction = domTransactionChain.newReadWriteTransaction();
188         if (insert == null) {
189             return makePut(path, schemaContext, newReadWriteTransaction, data);
190         } else {
191             final DataSchemaNode schemaNode = checkListAndOrderedType(schemaContext, path);
192             switch (insert) {
193                 case "first":
194                     if (schemaNode instanceof ListSchemaNode) {
195                         final NormalizedNode<?, ?> readData =
196                                 readList(path, schemaContext, domTransactionChain, schemaNode);
197                         final OrderedMapNode readList = (OrderedMapNode) readData;
198                         if ((readList == null) || readList.getValue().isEmpty()) {
199                             return makePut(path, schemaContext, newReadWriteTransaction, data);
200                         } else {
201                             newReadWriteTransaction.delete(LogicalDatastoreType.CONFIGURATION, path.getParent());
202                             simplePut(LogicalDatastoreType.CONFIGURATION, path, newReadWriteTransaction,
203                                     schemaContext, data);
204                             listPut(LogicalDatastoreType.CONFIGURATION, path.getParent(), newReadWriteTransaction,
205                                     schemaContext, readList);
206                             return newReadWriteTransaction.submit();
207                         }
208                     } else {
209                         final NormalizedNode<?, ?> readData =
210                                 readList(path, schemaContext, domTransactionChain, schemaNode);
211
212                         final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
213                         if ((readLeafList == null) || readLeafList.getValue().isEmpty()) {
214                             return makePut(path, schemaContext, newReadWriteTransaction, data);
215                         } else {
216                             newReadWriteTransaction.delete(LogicalDatastoreType.CONFIGURATION, path.getParent());
217                             simplePut(LogicalDatastoreType.CONFIGURATION, path, newReadWriteTransaction,
218                                     schemaContext, data);
219                             listPut(LogicalDatastoreType.CONFIGURATION, path.getParent(), newReadWriteTransaction,
220                                     schemaContext, readLeafList);
221                             return newReadWriteTransaction.submit();
222                         }
223                     }
224                 case "last":
225                     return makePut(path, schemaContext, newReadWriteTransaction, data);
226                 case "before":
227                     if (schemaNode instanceof ListSchemaNode) {
228                         final NormalizedNode<?, ?> readData =
229                                 readList(path, schemaContext, domTransactionChain, schemaNode);
230                         final OrderedMapNode readList = (OrderedMapNode) readData;
231                         if ((readList == null) || readList.getValue().isEmpty()) {
232                             return makePut(path, schemaContext, newReadWriteTransaction, data);
233                         } else {
234                             insertWithPointListPut(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION, path,
235                                     data, schemaContext, point, readList, true);
236                             return newReadWriteTransaction.submit();
237                         }
238                     } else {
239                         final NormalizedNode<?, ?> readData =
240                                 readList(path, schemaContext, domTransactionChain, schemaNode);
241
242                         final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
243                         if ((readLeafList == null) || readLeafList.getValue().isEmpty()) {
244                             return makePut(path, schemaContext, newReadWriteTransaction, data);
245                         } else {
246                             insertWithPointLeafListPut(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION,
247                                     path, data, schemaContext, point, readLeafList, true);
248                             return newReadWriteTransaction.submit();
249                         }
250                     }
251                 case "after":
252                     if (schemaNode instanceof ListSchemaNode) {
253                         final NormalizedNode<?, ?> readData =
254                                 readList(path, schemaContext, domTransactionChain, schemaNode);
255                         final OrderedMapNode readList = (OrderedMapNode) readData;
256                         if ((readList == null) || readList.getValue().isEmpty()) {
257                             return makePut(path, schemaContext, newReadWriteTransaction, data);
258                         } else {
259                             insertWithPointListPut(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION,
260                                     path, data, schemaContext, point, readList, false);
261                             return newReadWriteTransaction.submit();
262                         }
263                     } else {
264                         final NormalizedNode<?, ?> readData =
265                                 readList(path, schemaContext, domTransactionChain, schemaNode);
266
267                         final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
268                         if ((readLeafList == null) || readLeafList.getValue().isEmpty()) {
269                             return makePut(path, schemaContext, newReadWriteTransaction, data);
270                         } else {
271                             insertWithPointLeafListPut(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION,
272                                     path, data, schemaContext, point, readLeafList, true);
273                             return newReadWriteTransaction.submit();
274                         }
275                     }
276                 default:
277                     throw new RestconfDocumentedException(
278                             "Used bad value of insert parameter. Possible values are first, last, before or after, "
279                                     + "but was: " + insert);
280             }
281         }
282     }
283
284     public static NormalizedNode<?, ?> readList(final YangInstanceIdentifier path, final SchemaContext schemaContext,
285             final DOMTransactionChain domTransactionChain, final DataSchemaNode schemaNode) {
286         final InstanceIdentifierContext<?> iid = new InstanceIdentifierContext<SchemaNode>(
287                 path.getParent(), schemaNode, null, schemaContext);
288         final TransactionVarsWrapper transactionNode =
289                 new TransactionVarsWrapper(iid, null, domTransactionChain);
290         final NormalizedNode<?, ?> readData = ReadDataTransactionUtil
291                 .readData(RestconfDataServiceConstant.ReadData.CONFIG, transactionNode, schemaContext);
292         return readData;
293     }
294
295     private static void insertWithPointLeafListPut(final DOMDataReadWriteTransaction rwTransaction,
296             final LogicalDatastoreType datastore, final YangInstanceIdentifier path,
297             final NormalizedNode<?, ?> data, final SchemaContext schemaContext, final String point,
298             final OrderedLeafSetNode<?> readLeafList, final boolean before) {
299         rwTransaction.delete(datastore, path.getParent());
300         final InstanceIdentifierContext<?> instanceIdentifier =
301                 ParserIdentifier.toInstanceIdentifier(point, schemaContext, Optional.absent());
302         int lastItemPosition = 0;
303         for (final LeafSetEntryNode<?> nodeChild : readLeafList.getValue()) {
304             if (nodeChild.getIdentifier().equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
305                 break;
306             }
307             lastItemPosition++;
308         }
309         if (!before) {
310             lastItemPosition++;
311         }
312         int lastInsertedPosition = 0;
313         final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path.getParent());
314         rwTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
315         for (final LeafSetEntryNode<?> nodeChild : readLeafList.getValue()) {
316             if (lastInsertedPosition == lastItemPosition) {
317                 simplePut(datastore, path, rwTransaction, schemaContext, data);
318             }
319             final YangInstanceIdentifier childPath = path.getParent().node(nodeChild.getIdentifier());
320             rwTransaction.put(datastore, childPath, nodeChild);
321             lastInsertedPosition++;
322         }
323     }
324
325     private static void insertWithPointListPut(final DOMDataReadWriteTransaction writeTx,
326             final LogicalDatastoreType datastore, final YangInstanceIdentifier path,
327             final NormalizedNode<?, ?> data, final SchemaContext schemaContext, final String point,
328             final OrderedMapNode readList, final boolean before) {
329         writeTx.delete(datastore, path.getParent());
330         final InstanceIdentifierContext<?> instanceIdentifier =
331                 ParserIdentifier.toInstanceIdentifier(point, schemaContext, Optional.absent());
332         int lastItemPosition = 0;
333         for (final MapEntryNode mapEntryNode : readList.getValue()) {
334             if (mapEntryNode.getIdentifier().equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
335                 break;
336             }
337             lastItemPosition++;
338         }
339         if (!before) {
340             lastItemPosition++;
341         }
342         int lastInsertedPosition = 0;
343         final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path.getParent());
344         writeTx.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
345         for (final MapEntryNode mapEntryNode : readList.getValue()) {
346             if (lastInsertedPosition == lastItemPosition) {
347                 simplePut(datastore, path, writeTx, schemaContext, data);
348             }
349             final YangInstanceIdentifier childPath = path.getParent().node(mapEntryNode.getIdentifier());
350             writeTx.put(datastore, childPath, mapEntryNode);
351             lastInsertedPosition++;
352         }
353     }
354
355     private static void listPut(final LogicalDatastoreType datastore, final YangInstanceIdentifier path,
356             final DOMDataReadWriteTransaction writeTx, final SchemaContext schemaContext,
357             final OrderedLeafSetNode<?> payload) {
358         final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
359         writeTx.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
360         TransactionUtil.ensureParentsByMerge(path, schemaContext, writeTx);
361         for (final LeafSetEntryNode<?> child : ((LeafSetNode<?>) payload).getValue()) {
362             final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
363             writeTx.put(datastore, childPath, child);
364         }
365     }
366
367     private static void listPut(final LogicalDatastoreType datastore, final YangInstanceIdentifier path,
368             final DOMDataReadWriteTransaction writeTx, final SchemaContext schemaContext,
369             final OrderedMapNode payload) {
370         final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
371         writeTx.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
372         TransactionUtil.ensureParentsByMerge(path, schemaContext, writeTx);
373         for (final MapEntryNode child : ((MapNode) payload).getValue()) {
374             final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
375             writeTx.put(datastore, childPath, child);
376         }
377     }
378
379     private static void simplePut(final LogicalDatastoreType configuration, final YangInstanceIdentifier path,
380             final DOMDataReadWriteTransaction writeTx, final SchemaContext schemaContext,
381             final NormalizedNode<?, ?> data) {
382         TransactionUtil.ensureParentsByMerge(path, schemaContext, writeTx);
383         writeTx.put(LogicalDatastoreType.CONFIGURATION, path, data);
384     }
385
386     private static CheckedFuture<Void, TransactionCommitFailedException> makePut(final YangInstanceIdentifier path,
387             final SchemaContext schemaContext, final DOMDataWriteTransaction writeTx, final NormalizedNode<?, ?> data) {
388         TransactionUtil.ensureParentsByMerge(path, schemaContext, writeTx);
389         writeTx.put(LogicalDatastoreType.CONFIGURATION, path, data);
390         return writeTx.submit();
391     }
392
393     public static DataSchemaNode checkListAndOrderedType(final SchemaContext ctx, final YangInstanceIdentifier path) {
394         final YangInstanceIdentifier parent = path.getParent();
395         final DataSchemaContextNode<?> node = DataSchemaContextTree.from(ctx).getChild(parent);
396         final DataSchemaNode dataSchemaNode = node.getDataSchemaNode();
397
398         if (dataSchemaNode instanceof ListSchemaNode) {
399             if (!((ListSchemaNode) dataSchemaNode).isUserOrdered()) {
400                 throw new RestconfDocumentedException("Insert parameter can be used only with ordered-by user list.");
401             }
402             return dataSchemaNode;
403         }
404         if (dataSchemaNode instanceof LeafListSchemaNode) {
405             if (!((LeafListSchemaNode) dataSchemaNode).isUserOrdered()) {
406                 throw new RestconfDocumentedException(
407                         "Insert parameter can be used only with ordered-by user leaf-list.");
408             }
409             return dataSchemaNode;
410         }
411         throw new RestconfDocumentedException("Insert parameter can be used only with list or leaf-list");
412     }
413 }