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