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