Remove static TransactionChainHandler instance
[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 javax.ws.rs.core.Response.Status;
17 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
18 import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
19 import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
20 import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
21 import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
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.ErrorTag;
26 import org.opendaylight.restconf.common.errors.RestconfError.ErrorType;
27 import org.opendaylight.restconf.common.validation.RestconfValidationUtils;
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.MapNode;
40 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
41 import org.opendaylight.yangtools.yang.data.api.schema.OrderedLeafSetNode;
42 import org.opendaylight.yangtools.yang.data.api.schema.OrderedMapNode;
43 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
44 import org.opendaylight.yangtools.yang.data.util.DataSchemaContextNode;
45 import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
46 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
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)
120                         .getKeyValues();
121                 isEqualUriAndPayloadKeyValues(uriKeyValues, (MapEntryNode) data, keyDefinitions);
122             }
123         }
124     }
125
126     private static void isEqualUriAndPayloadKeyValues(final Map<QName, Object> uriKeyValues, final MapEntryNode payload,
127             final List<QName> keyDefinitions) {
128         final Map<QName, Object> mutableCopyUriKeyValues = Maps.newHashMap(uriKeyValues);
129         for (final QName keyDefinition : keyDefinitions) {
130             final Object uriKeyValue = mutableCopyUriKeyValues.remove(keyDefinition);
131             RestconfValidationUtils.checkDocumentedError(uriKeyValue != null, ErrorType.PROTOCOL, ErrorTag.DATA_MISSING,
132                     "Missing key " + keyDefinition + " in URI.");
133
134             final Object dataKeyValue = payload.getIdentifier().getKeyValues().get(keyDefinition);
135
136             if (!uriKeyValue.equals(dataKeyValue)) {
137                 final String errMsg = "The value '" + uriKeyValue + "' for key '" + keyDefinition.getLocalName()
138                         + "' specified in the URI doesn't match the value '" + dataKeyValue
139                         + "' specified in the message body. ";
140                 throw new RestconfDocumentedException(errMsg, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
141             }
142         }
143     }
144
145     /**
146      * Check mount point and prepare variables for put data to DS.
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 CheckedFuture}
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 SchemaContext schemaContext = schemaCtxRef.get();
164
165         final DOMDataReadWriteTransaction readWriteTransaction =
166                 transactionNode.getTransactionChain().newReadWriteTransaction();
167
168         final CheckedFuture<Boolean, ReadFailedException> existsFuture =
169                 readWriteTransaction.exists(LogicalDatastoreType.CONFIGURATION, 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 CheckedFuture<Void, TransactionCommitFailedException> submitData = submitData(path, schemaContext,
176                 transactionNode.getTransactionChainHandler(), readWriteTransaction, payload.getData(), insert, point);
177         FutureCallbackTx.addCallback(submitData, RestconfDataServiceConstant.PutData.PUT_TX_TYPE, responseFactory);
178         return responseFactory.build();
179     }
180
181     /**
182      * Put data to DS.
183      *
184      * @param path
185      *             path of data
186      * @param schemaContext
187      *             {@link SchemaContext}
188      * @param transactionChainHandler
189      *             write transaction
190      * @param data
191      *             data
192      * @param point
193      *             query parameter
194      * @param insert
195      *             query parameter
196      * @return {@link CheckedFuture}
197      */
198     private static CheckedFuture<Void, TransactionCommitFailedException> submitData(final YangInstanceIdentifier path,
199             final SchemaContext schemaContext, final TransactionChainHandler transactionChainHandler,
200             final DOMDataReadWriteTransaction readWriteTransaction,
201             final NormalizedNode<?, ?> data, final String insert, final String point) {
202         if (insert == null) {
203             return makePut(path, schemaContext, readWriteTransaction, data);
204         } else {
205             final DataSchemaNode schemaNode = checkListAndOrderedType(schemaContext, path);
206             switch (insert) {
207                 case "first":
208                     if (schemaNode instanceof ListSchemaNode) {
209                         final NormalizedNode<?, ?> readData =
210                                 readList(path, schemaContext, transactionChainHandler, schemaNode);
211                         final OrderedMapNode readList = (OrderedMapNode) readData;
212                         if (readList == null || readList.getValue().isEmpty()) {
213                             return makePut(path, schemaContext, readWriteTransaction, data);
214                         } else {
215                             readWriteTransaction.delete(LogicalDatastoreType.CONFIGURATION, path.getParent());
216                             simplePut(LogicalDatastoreType.CONFIGURATION, path, readWriteTransaction,
217                                     schemaContext, data);
218                             listPut(LogicalDatastoreType.CONFIGURATION, path.getParent(), readWriteTransaction,
219                                     schemaContext, readList);
220                             return readWriteTransaction.submit();
221                         }
222                     } else {
223                         final NormalizedNode<?, ?> readData =
224                                 readList(path, schemaContext, transactionChainHandler, schemaNode);
225
226                         final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
227                         if (readLeafList == null || readLeafList.getValue().isEmpty()) {
228                             return makePut(path, schemaContext, readWriteTransaction, data);
229                         } else {
230                             readWriteTransaction.delete(LogicalDatastoreType.CONFIGURATION, path.getParent());
231                             simplePut(LogicalDatastoreType.CONFIGURATION, path, readWriteTransaction,
232                                     schemaContext, data);
233                             listPut(LogicalDatastoreType.CONFIGURATION, path.getParent(), readWriteTransaction,
234                                     schemaContext, readLeafList);
235                             return readWriteTransaction.submit();
236                         }
237                     }
238                 case "last":
239                     return makePut(path, schemaContext, readWriteTransaction, data);
240                 case "before":
241                     if (schemaNode instanceof ListSchemaNode) {
242                         final NormalizedNode<?, ?> readData =
243                                 readList(path, schemaContext, transactionChainHandler, schemaNode);
244                         final OrderedMapNode readList = (OrderedMapNode) readData;
245                         if (readList == null || readList.getValue().isEmpty()) {
246                             return makePut(path, schemaContext, readWriteTransaction, data);
247                         } else {
248                             insertWithPointListPut(readWriteTransaction, LogicalDatastoreType.CONFIGURATION, path,
249                                     data, schemaContext, point, readList, true);
250                             return readWriteTransaction.submit();
251                         }
252                     } else {
253                         final NormalizedNode<?, ?> readData =
254                                 readList(path, schemaContext, transactionChainHandler, schemaNode);
255
256                         final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
257                         if (readLeafList == null || readLeafList.getValue().isEmpty()) {
258                             return makePut(path, schemaContext, readWriteTransaction, data);
259                         } else {
260                             insertWithPointLeafListPut(readWriteTransaction, LogicalDatastoreType.CONFIGURATION,
261                                     path, data, schemaContext, point, readLeafList, true);
262                             return readWriteTransaction.submit();
263                         }
264                     }
265                 case "after":
266                     if (schemaNode instanceof ListSchemaNode) {
267                         final NormalizedNode<?, ?> readData =
268                                 readList(path, schemaContext, transactionChainHandler, schemaNode);
269                         final OrderedMapNode readList = (OrderedMapNode) readData;
270                         if (readList == null || readList.getValue().isEmpty()) {
271                             return makePut(path, schemaContext, readWriteTransaction, data);
272                         } else {
273                             insertWithPointListPut(readWriteTransaction, LogicalDatastoreType.CONFIGURATION,
274                                     path, data, schemaContext, point, readList, false);
275                             return readWriteTransaction.submit();
276                         }
277                     } else {
278                         final NormalizedNode<?, ?> readData =
279                                 readList(path, schemaContext, transactionChainHandler, schemaNode);
280
281                         final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
282                         if (readLeafList == null || readLeafList.getValue().isEmpty()) {
283                             return makePut(path, schemaContext, readWriteTransaction, data);
284                         } else {
285                             insertWithPointLeafListPut(readWriteTransaction, LogicalDatastoreType.CONFIGURATION,
286                                     path, data, schemaContext, point, readLeafList, true);
287                             return readWriteTransaction.submit();
288                         }
289                     }
290                 default:
291                     throw new RestconfDocumentedException(
292                             "Used bad value of insert parameter. Possible values are first, last, before or after, "
293                                     + "but was: " + insert);
294             }
295         }
296     }
297
298     public static NormalizedNode<?, ?> readList(final YangInstanceIdentifier path, final SchemaContext schemaContext,
299             final TransactionChainHandler transactionChainHandler, final DataSchemaNode schemaNode) {
300         final InstanceIdentifierContext<?> iid = new InstanceIdentifierContext<SchemaNode>(
301                 path.getParent(), schemaNode, null, schemaContext);
302         final TransactionVarsWrapper transactionNode = new TransactionVarsWrapper(iid, null, transactionChainHandler);
303         final NormalizedNode<?, ?> readData = ReadDataTransactionUtil
304                 .readData(RestconfDataServiceConstant.ReadData.CONFIG, transactionNode, schemaContext);
305         return readData;
306     }
307
308     private static void insertWithPointLeafListPut(final DOMDataReadWriteTransaction rwTransaction,
309             final LogicalDatastoreType datastore, final YangInstanceIdentifier path,
310             final NormalizedNode<?, ?> data, final SchemaContext schemaContext, final String point,
311             final OrderedLeafSetNode<?> readLeafList, final boolean before) {
312         rwTransaction.delete(datastore, path.getParent());
313         final InstanceIdentifierContext<?> instanceIdentifier =
314                 ParserIdentifier.toInstanceIdentifier(point, schemaContext, Optional.absent());
315         int lastItemPosition = 0;
316         for (final LeafSetEntryNode<?> nodeChild : readLeafList.getValue()) {
317             if (nodeChild.getIdentifier().equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
318                 break;
319             }
320             lastItemPosition++;
321         }
322         if (!before) {
323             lastItemPosition++;
324         }
325         int lastInsertedPosition = 0;
326         final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path.getParent());
327         rwTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
328         for (final LeafSetEntryNode<?> nodeChild : readLeafList.getValue()) {
329             if (lastInsertedPosition == lastItemPosition) {
330                 simplePut(datastore, path, rwTransaction, schemaContext, data);
331             }
332             final YangInstanceIdentifier childPath = path.getParent().node(nodeChild.getIdentifier());
333             rwTransaction.put(datastore, childPath, nodeChild);
334             lastInsertedPosition++;
335         }
336     }
337
338     private static void insertWithPointListPut(final DOMDataReadWriteTransaction writeTx,
339             final LogicalDatastoreType datastore, final YangInstanceIdentifier path,
340             final NormalizedNode<?, ?> data, final SchemaContext schemaContext, final String point,
341             final OrderedMapNode readList, final boolean before) {
342         writeTx.delete(datastore, path.getParent());
343         final InstanceIdentifierContext<?> instanceIdentifier =
344                 ParserIdentifier.toInstanceIdentifier(point, schemaContext, Optional.absent());
345         int lastItemPosition = 0;
346         for (final MapEntryNode mapEntryNode : readList.getValue()) {
347             if (mapEntryNode.getIdentifier().equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
348                 break;
349             }
350             lastItemPosition++;
351         }
352         if (!before) {
353             lastItemPosition++;
354         }
355         int lastInsertedPosition = 0;
356         final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path.getParent());
357         writeTx.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
358         for (final MapEntryNode mapEntryNode : readList.getValue()) {
359             if (lastInsertedPosition == lastItemPosition) {
360                 simplePut(datastore, path, writeTx, schemaContext, data);
361             }
362             final YangInstanceIdentifier childPath = path.getParent().node(mapEntryNode.getIdentifier());
363             writeTx.put(datastore, childPath, mapEntryNode);
364             lastInsertedPosition++;
365         }
366     }
367
368     private static void listPut(final LogicalDatastoreType datastore, final YangInstanceIdentifier path,
369             final DOMDataReadWriteTransaction writeTx, final SchemaContext schemaContext,
370             final OrderedLeafSetNode<?> payload) {
371         final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
372         writeTx.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
373         TransactionUtil.ensureParentsByMerge(path, schemaContext, writeTx);
374         for (final LeafSetEntryNode<?> child : ((LeafSetNode<?>) payload).getValue()) {
375             final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
376             writeTx.put(datastore, childPath, child);
377         }
378     }
379
380     private static void listPut(final LogicalDatastoreType datastore, final YangInstanceIdentifier path,
381             final DOMDataReadWriteTransaction writeTx, final SchemaContext schemaContext,
382             final OrderedMapNode payload) {
383         final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
384         writeTx.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
385         TransactionUtil.ensureParentsByMerge(path, schemaContext, writeTx);
386         for (final MapEntryNode child : ((MapNode) payload).getValue()) {
387             final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
388             writeTx.put(datastore, childPath, child);
389         }
390     }
391
392     private static void simplePut(final LogicalDatastoreType configuration, final YangInstanceIdentifier path,
393             final DOMDataReadWriteTransaction writeTx, final SchemaContext schemaContext,
394             final NormalizedNode<?, ?> data) {
395         TransactionUtil.ensureParentsByMerge(path, schemaContext, writeTx);
396         writeTx.put(LogicalDatastoreType.CONFIGURATION, path, data);
397     }
398
399     private static CheckedFuture<Void, TransactionCommitFailedException> makePut(final YangInstanceIdentifier path,
400             final SchemaContext schemaContext, final DOMDataWriteTransaction writeTx, final NormalizedNode<?, ?> data) {
401         TransactionUtil.ensureParentsByMerge(path, schemaContext, writeTx);
402         writeTx.put(LogicalDatastoreType.CONFIGURATION, path, data);
403         return writeTx.submit();
404     }
405
406     public static DataSchemaNode checkListAndOrderedType(final SchemaContext ctx, final YangInstanceIdentifier path) {
407         final YangInstanceIdentifier parent = path.getParent();
408         final DataSchemaContextNode<?> node = DataSchemaContextTree.from(ctx).getChild(parent);
409         final DataSchemaNode dataSchemaNode = node.getDataSchemaNode();
410
411         if (dataSchemaNode instanceof ListSchemaNode) {
412             if (!((ListSchemaNode) dataSchemaNode).isUserOrdered()) {
413                 throw new RestconfDocumentedException("Insert parameter can be used only with ordered-by user list.");
414             }
415             return dataSchemaNode;
416         }
417         if (dataSchemaNode instanceof LeafListSchemaNode) {
418             if (!((LeafListSchemaNode) dataSchemaNode).isUserOrdered()) {
419                 throw new RestconfDocumentedException(
420                         "Insert parameter can be used only with ordered-by user leaf-list.");
421             }
422             return dataSchemaNode;
423         }
424         throw new RestconfDocumentedException("Insert parameter can be used only with list or leaf-list");
425     }
426 }