Update MRI projects for Aluminium
[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.util.concurrent.FluentFuture;
11 import java.util.HashMap;
12 import java.util.List;
13 import java.util.Map;
14 import java.util.Optional;
15 import javax.ws.rs.core.Response;
16 import javax.ws.rs.core.Response.Status;
17 import org.opendaylight.mdsal.common.api.CommitInfo;
18 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
19 import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction;
20 import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction;
21 import org.opendaylight.mdsal.dom.api.DOMTransactionChain;
22 import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
23 import org.opendaylight.restconf.common.context.NormalizedNodeContext;
24 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
25 import org.opendaylight.restconf.common.errors.RestconfError;
26 import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag;
27 import org.opendaylight.restconf.common.errors.RestconfError.ErrorType;
28 import org.opendaylight.restconf.nb.rfc8040.handlers.TransactionChainHandler;
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.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.EffectiveModelContext;
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;
51
52 /**
53  * Util class for put data to DS.
54  *
55  */
56 public final class PutDataTransactionUtil {
57
58     private PutDataTransactionUtil() {
59
60     }
61
62     /**
63      * Valid input data with {@link SchemaNode}.
64      *
65      * @param schemaNode
66      *             {@link SchemaNode}
67      * @param payload
68      *             input data
69      */
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);
75         }
76     }
77
78     /**
79      * Valid top level node name.
80      *
81      * @param path
82      *             path of node
83      * @param payload
84      *             data
85      */
86     public static void validTopLevelNodeName(final YangInstanceIdentifier path, final NormalizedNodeContext payload) {
87         final String payloadName = payload.getData().getNodeType().getLocalName();
88
89         if (path.isEmpty()) {
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);
93             }
94         } else {
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);
100             }
101         }
102     }
103
104     /**
105      * Validates whether keys in {@code payload} are equal to values of keys in
106      * {@code iiWithData} for list schema node.
107      *
108      * @throws RestconfDocumentedException
109      *             if key values or key count in payload and URI isn't equal
110      */
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).asMap();
120                 isEqualUriAndPayloadKeyValues(uriKeyValues, (MapEntryNode) data, keyDefinitions);
121             }
122         }
123     }
124
125     private static void isEqualUriAndPayloadKeyValues(final Map<QName, Object> uriKeyValues, final MapEntryNode payload,
126             final List<QName> keyDefinitions) {
127         final Map<QName, Object> mutableCopyUriKeyValues = new HashMap<>(uriKeyValues);
128         for (final QName keyDefinition : keyDefinitions) {
129             final Object uriKeyValue = RestconfDocumentedException.throwIfNull(
130                 mutableCopyUriKeyValues.remove(keyDefinition), ErrorType.PROTOCOL, ErrorTag.DATA_MISSING,
131                 "Missing key %s in URI.", keyDefinition);
132
133             final Object dataKeyValue = payload.getIdentifier().getValue(keyDefinition);
134
135             if (!uriKeyValue.equals(dataKeyValue)) {
136                 final String errMsg = "The value '" + uriKeyValue + "' for key '" + keyDefinition.getLocalName()
137                         + "' specified in the URI doesn't match the value '" + dataKeyValue
138                         + "' specified in the message body. ";
139                 throw new RestconfDocumentedException(errMsg, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
140             }
141         }
142     }
143
144     /**
145      * Check mount point and prepare variables for put data to DS. Close {@link DOMTransactionChain} inside of object
146      * {@link TransactionVarsWrapper} provided as a parameter.
147      *
148      * @param payload
149      *             data to put
150      * @param schemaCtxRef
151      *             reference to {@link SchemaContext}
152      * @param transactionNode
153      *             wrapper of variables for transaction
154      * @param point
155      *             query parameter
156      * @param insert
157      *             query parameter
158      * @return {@link Response}
159      */
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 EffectiveModelContext schemaContext = schemaCtxRef.get();
164
165         final DOMDataTreeReadWriteTransaction readWriteTransaction =
166                 transactionNode.getTransactionChain().newReadWriteTransaction();
167
168         final FluentFuture<Boolean> existsFuture = readWriteTransaction.exists(LogicalDatastoreType.CONFIGURATION,
169             path);
170         final FutureDataFactory<Boolean> existsResponse = new FutureDataFactory<>();
171         FutureCallbackTx.addCallback(existsFuture, RestconfDataServiceConstant.PutData.PUT_TX_TYPE, existsResponse);
172
173         final ResponseFactory responseFactory =
174                 new ResponseFactory(existsResponse.result ? Status.NO_CONTENT : Status.CREATED);
175         final FluentFuture<? extends CommitInfo> submitData = submitData(path, schemaContext,
176                 transactionNode.getTransactionChainHandler(), readWriteTransaction, payload.getData(), insert, point);
177         //This method will close transactionChain
178         FutureCallbackTx.addCallback(submitData, RestconfDataServiceConstant.PutData.PUT_TX_TYPE, responseFactory,
179                 transactionNode.getTransactionChain());
180         return responseFactory.build();
181     }
182
183     /**
184      * Put data to DS.
185      *
186      * @param path
187      *             path of data
188      * @param schemaContext
189      *             {@link SchemaContext}
190      * @param transactionChainHandler
191      *             write transaction
192      * @param data
193      *             data
194      * @param point
195      *             query parameter
196      * @param insert
197      *             query parameter
198      * @return {@link FluentFuture}
199      */
200     private static FluentFuture<? extends CommitInfo> submitData(final YangInstanceIdentifier path,
201             final EffectiveModelContext schemaContext, final TransactionChainHandler transactionChainHandler,
202             final DOMDataTreeReadWriteTransaction readWriteTransaction,
203             final NormalizedNode<?, ?> data, final String insert, final String point) {
204         if (insert == null) {
205             return makePut(path, schemaContext, readWriteTransaction, data);
206         }
207
208         final DataSchemaNode schemaNode = checkListAndOrderedType(schemaContext, path);
209         switch (insert) {
210             case "first":
211                 if (schemaNode instanceof ListSchemaNode) {
212                     final NormalizedNode<?, ?> readData =
213                             readList(path, schemaContext, transactionChainHandler, schemaNode);
214                     final OrderedMapNode readList = (OrderedMapNode) readData;
215                     if (readList == null || readList.getValue().isEmpty()) {
216                         return makePut(path, schemaContext, readWriteTransaction, data);
217                     } else {
218                         readWriteTransaction.delete(LogicalDatastoreType.CONFIGURATION, path.getParent());
219                         simplePut(LogicalDatastoreType.CONFIGURATION, path, readWriteTransaction,
220                             schemaContext, data);
221                         listPut(LogicalDatastoreType.CONFIGURATION, path.getParent(), readWriteTransaction,
222                             schemaContext, readList);
223                         return readWriteTransaction.commit();
224                     }
225                 } else {
226                     final NormalizedNode<?, ?> readData =
227                             readList(path, schemaContext, transactionChainHandler, schemaNode);
228
229                     final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
230                     if (readLeafList == null || readLeafList.getValue().isEmpty()) {
231                         return makePut(path, schemaContext, readWriteTransaction, data);
232                     } else {
233                         readWriteTransaction.delete(LogicalDatastoreType.CONFIGURATION, path.getParent());
234                         simplePut(LogicalDatastoreType.CONFIGURATION, path, readWriteTransaction,
235                             schemaContext, data);
236                         listPut(LogicalDatastoreType.CONFIGURATION, path.getParent(), readWriteTransaction,
237                             schemaContext, readLeafList);
238                         return readWriteTransaction.commit();
239                     }
240                 }
241             case "last":
242                 return makePut(path, schemaContext, readWriteTransaction, data);
243             case "before":
244                 if (schemaNode instanceof ListSchemaNode) {
245                     final NormalizedNode<?, ?> readData =
246                             readList(path, schemaContext, transactionChainHandler, schemaNode);
247                     final OrderedMapNode readList = (OrderedMapNode) readData;
248                     if (readList == null || readList.getValue().isEmpty()) {
249                         return makePut(path, schemaContext, readWriteTransaction, data);
250                     } else {
251                         insertWithPointListPut(readWriteTransaction, LogicalDatastoreType.CONFIGURATION, path,
252                             data, schemaContext, point, readList, true);
253                         return readWriteTransaction.commit();
254                     }
255                 } else {
256                     final NormalizedNode<?, ?> readData =
257                             readList(path, schemaContext, transactionChainHandler, schemaNode);
258
259                     final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
260                     if (readLeafList == null || readLeafList.getValue().isEmpty()) {
261                         return makePut(path, schemaContext, readWriteTransaction, data);
262                     } else {
263                         insertWithPointLeafListPut(readWriteTransaction, LogicalDatastoreType.CONFIGURATION,
264                             path, data, schemaContext, point, readLeafList, true);
265                         return readWriteTransaction.commit();
266                     }
267                 }
268             case "after":
269                 if (schemaNode instanceof ListSchemaNode) {
270                     final NormalizedNode<?, ?> readData =
271                             readList(path, schemaContext, transactionChainHandler, schemaNode);
272                     final OrderedMapNode readList = (OrderedMapNode) readData;
273                     if (readList == null || readList.getValue().isEmpty()) {
274                         return makePut(path, schemaContext, readWriteTransaction, data);
275                     } else {
276                         insertWithPointListPut(readWriteTransaction, LogicalDatastoreType.CONFIGURATION,
277                             path, data, schemaContext, point, readList, false);
278                         return readWriteTransaction.commit();
279                     }
280                 } else {
281                     final NormalizedNode<?, ?> readData =
282                             readList(path, schemaContext, transactionChainHandler, schemaNode);
283
284                     final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
285                     if (readLeafList == null || readLeafList.getValue().isEmpty()) {
286                         return makePut(path, schemaContext, readWriteTransaction, data);
287                     } else {
288                         insertWithPointLeafListPut(readWriteTransaction, LogicalDatastoreType.CONFIGURATION,
289                             path, data, schemaContext, point, readLeafList, true);
290                         return readWriteTransaction.commit();
291                     }
292                 }
293             default:
294                 throw new RestconfDocumentedException(
295                         "Used bad value of insert parameter. Possible values are first, last, before or after, "
296                                 + "but was: " + insert, RestconfError.ErrorType.PROTOCOL, ErrorTag.BAD_ATTRIBUTE);
297         }
298     }
299
300     public static NormalizedNode<?, ?> readList(final YangInstanceIdentifier path,
301             final EffectiveModelContext schemaContext, final TransactionChainHandler transactionChainHandler,
302             final DataSchemaNode schemaNode) {
303         final InstanceIdentifierContext<?> iid = new InstanceIdentifierContext<SchemaNode>(
304                 path.getParent(), schemaNode, null, schemaContext);
305         final TransactionVarsWrapper transactionNode = new TransactionVarsWrapper(iid, null, transactionChainHandler);
306         final NormalizedNode<?, ?> readData = ReadDataTransactionUtil
307                 .readData(RestconfDataServiceConstant.ReadData.CONFIG, transactionNode, schemaContext);
308         return readData;
309     }
310
311     private static void insertWithPointLeafListPut(final DOMDataTreeReadWriteTransaction rwTransaction,
312             final LogicalDatastoreType datastore, final YangInstanceIdentifier path,
313             final NormalizedNode<?, ?> data, final EffectiveModelContext schemaContext, final String point,
314             final OrderedLeafSetNode<?> readLeafList, final boolean before) {
315         rwTransaction.delete(datastore, path.getParent());
316         final InstanceIdentifierContext<?> instanceIdentifier =
317                 ParserIdentifier.toInstanceIdentifier(point, schemaContext, Optional.empty());
318         int lastItemPosition = 0;
319         for (final LeafSetEntryNode<?> nodeChild : readLeafList.getValue()) {
320             if (nodeChild.getIdentifier().equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
321                 break;
322             }
323             lastItemPosition++;
324         }
325         if (!before) {
326             lastItemPosition++;
327         }
328         int lastInsertedPosition = 0;
329         final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path.getParent());
330         rwTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
331         for (final LeafSetEntryNode<?> nodeChild : readLeafList.getValue()) {
332             if (lastInsertedPosition == lastItemPosition) {
333                 simplePut(datastore, path, rwTransaction, schemaContext, data);
334             }
335             final YangInstanceIdentifier childPath = path.getParent().node(nodeChild.getIdentifier());
336             rwTransaction.put(datastore, childPath, nodeChild);
337             lastInsertedPosition++;
338         }
339     }
340
341     private static void insertWithPointListPut(final DOMDataTreeReadWriteTransaction writeTx,
342             final LogicalDatastoreType datastore, final YangInstanceIdentifier path,
343             final NormalizedNode<?, ?> data, final EffectiveModelContext schemaContext, final String point,
344             final OrderedMapNode readList, final boolean before) {
345         writeTx.delete(datastore, path.getParent());
346         final InstanceIdentifierContext<?> instanceIdentifier =
347                 ParserIdentifier.toInstanceIdentifier(point, schemaContext, Optional.empty());
348         int lastItemPosition = 0;
349         for (final MapEntryNode mapEntryNode : readList.getValue()) {
350             if (mapEntryNode.getIdentifier().equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
351                 break;
352             }
353             lastItemPosition++;
354         }
355         if (!before) {
356             lastItemPosition++;
357         }
358         int lastInsertedPosition = 0;
359         final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path.getParent());
360         writeTx.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
361         for (final MapEntryNode mapEntryNode : readList.getValue()) {
362             if (lastInsertedPosition == lastItemPosition) {
363                 simplePut(datastore, path, writeTx, schemaContext, data);
364             }
365             final YangInstanceIdentifier childPath = path.getParent().node(mapEntryNode.getIdentifier());
366             writeTx.put(datastore, childPath, mapEntryNode);
367             lastInsertedPosition++;
368         }
369     }
370
371     private static void listPut(final LogicalDatastoreType datastore, final YangInstanceIdentifier path,
372             final DOMDataTreeWriteTransaction writeTx, final SchemaContext schemaContext,
373             final OrderedLeafSetNode<?> payload) {
374         final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
375         writeTx.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
376         TransactionUtil.ensureParentsByMerge(path, schemaContext, writeTx);
377         for (final LeafSetEntryNode<?> child : ((LeafSetNode<?>) payload).getValue()) {
378             final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
379             writeTx.put(datastore, childPath, child);
380         }
381     }
382
383     private static void listPut(final LogicalDatastoreType datastore, final YangInstanceIdentifier path,
384             final DOMDataTreeWriteTransaction writeTx, final SchemaContext schemaContext,
385             final OrderedMapNode payload) {
386         final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
387         writeTx.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
388         TransactionUtil.ensureParentsByMerge(path, schemaContext, writeTx);
389         for (final MapEntryNode child : payload.getValue()) {
390             final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
391             writeTx.put(datastore, childPath, child);
392         }
393     }
394
395     private static void simplePut(final LogicalDatastoreType configuration, final YangInstanceIdentifier path,
396             final DOMDataTreeWriteTransaction writeTx, final SchemaContext schemaContext,
397             final NormalizedNode<?, ?> data) {
398         TransactionUtil.ensureParentsByMerge(path, schemaContext, writeTx);
399         writeTx.put(LogicalDatastoreType.CONFIGURATION, path, data);
400     }
401
402     private static FluentFuture<? extends CommitInfo> makePut(final YangInstanceIdentifier path,
403             final SchemaContext schemaContext, final DOMDataTreeWriteTransaction writeTx,
404             final NormalizedNode<?, ?> data) {
405         TransactionUtil.ensureParentsByMerge(path, schemaContext, writeTx);
406         writeTx.put(LogicalDatastoreType.CONFIGURATION, path, data);
407         return writeTx.commit();
408     }
409
410     public static DataSchemaNode checkListAndOrderedType(final SchemaContext ctx, final YangInstanceIdentifier path) {
411         final YangInstanceIdentifier parent = path.getParent();
412         final DataSchemaContextNode<?> node = DataSchemaContextTree.from(ctx).getChild(parent);
413         final DataSchemaNode dataSchemaNode = node.getDataSchemaNode();
414
415         if (dataSchemaNode instanceof ListSchemaNode) {
416             if (!((ListSchemaNode) dataSchemaNode).isUserOrdered()) {
417                 throw new RestconfDocumentedException("Insert parameter can be used only with ordered-by user list.",
418                         RestconfError.ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT);
419             }
420             return dataSchemaNode;
421         }
422         if (dataSchemaNode instanceof LeafListSchemaNode) {
423             if (!((LeafListSchemaNode) dataSchemaNode).isUserOrdered()) {
424                 throw new RestconfDocumentedException(
425                         "Insert parameter can be used only with ordered-by user leaf-list.",
426                         RestconfError.ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT);
427             }
428             return dataSchemaNode;
429         }
430         throw new RestconfDocumentedException("Insert parameter can be used only with list or leaf-list",
431                 RestconfError.ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT);
432     }
433 }