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