Adjust to yangtools-2.0.0/odlparent-3.0.0 changes
[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 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.validation.RestconfValidationUtils;
27 import org.opendaylight.restconf.nb.rfc8040.references.SchemaContextRef;
28 import org.opendaylight.restconf.nb.rfc8040.rests.transactions.TransactionVarsWrapper;
29 import org.opendaylight.restconf.nb.rfc8040.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  */
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)
118                         .getKeyValues();
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 = Maps.newHashMap(uriKeyValues);
127         for (final QName keyDefinition : keyDefinitions) {
128             final Object uriKeyValue = mutableCopyUriKeyValues.remove(keyDefinition);
129             RestconfValidationUtils.checkDocumentedError(uriKeyValue != null, ErrorType.PROTOCOL, ErrorTag.DATA_MISSING,
130                     "Missing key " + keyDefinition + " in URI.");
131
132             final Object dataKeyValue = payload.getIdentifier().getKeyValues().get(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.
145      *
146      * @param payload
147      *             data to put
148      * @param schemaCtxRef
149      *             reference to {@link SchemaContext}
150      * @param transactionNode
151      *             wrapper of variables for transaction
152      * @param point
153      *             query parameter
154      * @param insert
155      *             query parameter
156      * @return {@link CheckedFuture}
157      */
158     public static Response putData(final NormalizedNodeContext payload, final SchemaContextRef schemaCtxRef,
159                                final TransactionVarsWrapper transactionNode, final String insert, final String point) {
160         final YangInstanceIdentifier path = payload.getInstanceIdentifierContext().getInstanceIdentifier();
161         final SchemaContext schemaContext = schemaCtxRef.get();
162         final ResponseFactory responseFactory = new ResponseFactory(
163                 ReadDataTransactionUtil.readData(RestconfDataServiceConstant.ReadData.CONFIG, transactionNode,
164                         schemaContext));
165         final CheckedFuture<Void, TransactionCommitFailedException> submitData = submitData(path, schemaContext,
166                 transactionNode.getTransactionChain(), payload.getData(), insert, point);
167         FutureCallbackTx.addCallback(submitData, RestconfDataServiceConstant.PutData.PUT_TX_TYPE, responseFactory);
168         return responseFactory.build();
169     }
170
171     /**
172      * Put data to DS.
173      *
174      * @param path
175      *             path of data
176      * @param schemaContext
177      *             {@link SchemaContext}
178      * @param domTransactionChain
179      *             write transaction
180      * @param data
181      *             data
182      * @param point
183      *             query parameter
184      * @param insert
185      *             query parameter
186      * @return {@link CheckedFuture}
187      */
188     private static CheckedFuture<Void, TransactionCommitFailedException> submitData(final YangInstanceIdentifier path,
189             final SchemaContext schemaContext, final DOMTransactionChain domTransactionChain,
190             final NormalizedNode<?, ?> data, final String insert, final String point) {
191         final DOMDataReadWriteTransaction newReadWriteTransaction = domTransactionChain.newReadWriteTransaction();
192         if (insert == null) {
193             return makePut(path, schemaContext, newReadWriteTransaction, data);
194         } else {
195             final DataSchemaNode schemaNode = checkListAndOrderedType(schemaContext, path);
196             switch (insert) {
197                 case "first":
198                     if (schemaNode instanceof ListSchemaNode) {
199                         final NormalizedNode<?, ?> readData =
200                                 readList(path, schemaContext, domTransactionChain, schemaNode);
201                         final OrderedMapNode readList = (OrderedMapNode) readData;
202                         if ((readList == null) || readList.getValue().isEmpty()) {
203                             return makePut(path, schemaContext, newReadWriteTransaction, data);
204                         } else {
205                             newReadWriteTransaction.delete(LogicalDatastoreType.CONFIGURATION, path.getParent());
206                             simplePut(LogicalDatastoreType.CONFIGURATION, path, newReadWriteTransaction,
207                                     schemaContext, data);
208                             listPut(LogicalDatastoreType.CONFIGURATION, path.getParent(), newReadWriteTransaction,
209                                     schemaContext, readList);
210                             return newReadWriteTransaction.submit();
211                         }
212                     } else {
213                         final NormalizedNode<?, ?> readData =
214                                 readList(path, schemaContext, domTransactionChain, schemaNode);
215
216                         final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
217                         if ((readLeafList == null) || readLeafList.getValue().isEmpty()) {
218                             return makePut(path, schemaContext, newReadWriteTransaction, data);
219                         } else {
220                             newReadWriteTransaction.delete(LogicalDatastoreType.CONFIGURATION, path.getParent());
221                             simplePut(LogicalDatastoreType.CONFIGURATION, path, newReadWriteTransaction,
222                                     schemaContext, data);
223                             listPut(LogicalDatastoreType.CONFIGURATION, path.getParent(), newReadWriteTransaction,
224                                     schemaContext, readLeafList);
225                             return newReadWriteTransaction.submit();
226                         }
227                     }
228                 case "last":
229                     return makePut(path, schemaContext, newReadWriteTransaction, data);
230                 case "before":
231                     if (schemaNode instanceof ListSchemaNode) {
232                         final NormalizedNode<?, ?> readData =
233                                 readList(path, schemaContext, domTransactionChain, schemaNode);
234                         final OrderedMapNode readList = (OrderedMapNode) readData;
235                         if ((readList == null) || readList.getValue().isEmpty()) {
236                             return makePut(path, schemaContext, newReadWriteTransaction, data);
237                         } else {
238                             insertWithPointListPut(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION, path,
239                                     data, schemaContext, point, readList, true);
240                             return newReadWriteTransaction.submit();
241                         }
242                     } else {
243                         final NormalizedNode<?, ?> readData =
244                                 readList(path, schemaContext, domTransactionChain, schemaNode);
245
246                         final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
247                         if ((readLeafList == null) || readLeafList.getValue().isEmpty()) {
248                             return makePut(path, schemaContext, newReadWriteTransaction, data);
249                         } else {
250                             insertWithPointLeafListPut(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION,
251                                     path, data, schemaContext, point, readLeafList, true);
252                             return newReadWriteTransaction.submit();
253                         }
254                     }
255                 case "after":
256                     if (schemaNode instanceof ListSchemaNode) {
257                         final NormalizedNode<?, ?> readData =
258                                 readList(path, schemaContext, domTransactionChain, schemaNode);
259                         final OrderedMapNode readList = (OrderedMapNode) readData;
260                         if ((readList == null) || readList.getValue().isEmpty()) {
261                             return makePut(path, schemaContext, newReadWriteTransaction, data);
262                         } else {
263                             insertWithPointListPut(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION,
264                                     path, data, schemaContext, point, readList, false);
265                             return newReadWriteTransaction.submit();
266                         }
267                     } else {
268                         final NormalizedNode<?, ?> readData =
269                                 readList(path, schemaContext, domTransactionChain, schemaNode);
270
271                         final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
272                         if ((readLeafList == null) || readLeafList.getValue().isEmpty()) {
273                             return makePut(path, schemaContext, newReadWriteTransaction, data);
274                         } else {
275                             insertWithPointLeafListPut(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION,
276                                     path, data, schemaContext, point, readLeafList, true);
277                             return newReadWriteTransaction.submit();
278                         }
279                     }
280                 default:
281                     throw new RestconfDocumentedException(
282                             "Used bad value of insert parameter. Possible values are first, last, before or after, "
283                                     + "but was: " + insert);
284             }
285         }
286     }
287
288     public static NormalizedNode<?, ?> readList(final YangInstanceIdentifier path, final SchemaContext schemaContext,
289             final DOMTransactionChain domTransactionChain, final DataSchemaNode schemaNode) {
290         final InstanceIdentifierContext<?> iid = new InstanceIdentifierContext<SchemaNode>(
291                 path.getParent(), schemaNode, null, schemaContext);
292         final TransactionVarsWrapper transactionNode =
293                 new TransactionVarsWrapper(iid, null, domTransactionChain);
294         final NormalizedNode<?, ?> readData = ReadDataTransactionUtil
295                 .readData(RestconfDataServiceConstant.ReadData.CONFIG, transactionNode, schemaContext);
296         return readData;
297     }
298
299     private static void insertWithPointLeafListPut(final DOMDataReadWriteTransaction rwTransaction,
300             final LogicalDatastoreType datastore, final YangInstanceIdentifier path,
301             final NormalizedNode<?, ?> data, final SchemaContext schemaContext, final String point,
302             final OrderedLeafSetNode<?> readLeafList, final boolean before) {
303         rwTransaction.delete(datastore, path.getParent());
304         final InstanceIdentifierContext<?> instanceIdentifier =
305                 ParserIdentifier.toInstanceIdentifier(point, schemaContext, Optional.absent());
306         int lastItemPosition = 0;
307         for (final LeafSetEntryNode<?> nodeChild : readLeafList.getValue()) {
308             if (nodeChild.getIdentifier().equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
309                 break;
310             }
311             lastItemPosition++;
312         }
313         if (!before) {
314             lastItemPosition++;
315         }
316         int lastInsertedPosition = 0;
317         final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path.getParent());
318         rwTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
319         for (final LeafSetEntryNode<?> nodeChild : readLeafList.getValue()) {
320             if (lastInsertedPosition == lastItemPosition) {
321                 simplePut(datastore, path, rwTransaction, schemaContext, data);
322             }
323             final YangInstanceIdentifier childPath = path.getParent().node(nodeChild.getIdentifier());
324             rwTransaction.put(datastore, childPath, nodeChild);
325             lastInsertedPosition++;
326         }
327     }
328
329     private static void insertWithPointListPut(final DOMDataReadWriteTransaction writeTx,
330             final LogicalDatastoreType datastore, final YangInstanceIdentifier path,
331             final NormalizedNode<?, ?> data, final SchemaContext schemaContext, final String point,
332             final OrderedMapNode readList, final boolean before) {
333         writeTx.delete(datastore, path.getParent());
334         final InstanceIdentifierContext<?> instanceIdentifier =
335                 ParserIdentifier.toInstanceIdentifier(point, schemaContext, Optional.absent());
336         int lastItemPosition = 0;
337         for (final MapEntryNode mapEntryNode : readList.getValue()) {
338             if (mapEntryNode.getIdentifier().equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
339                 break;
340             }
341             lastItemPosition++;
342         }
343         if (!before) {
344             lastItemPosition++;
345         }
346         int lastInsertedPosition = 0;
347         final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path.getParent());
348         writeTx.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
349         for (final MapEntryNode mapEntryNode : readList.getValue()) {
350             if (lastInsertedPosition == lastItemPosition) {
351                 simplePut(datastore, path, writeTx, schemaContext, data);
352             }
353             final YangInstanceIdentifier childPath = path.getParent().node(mapEntryNode.getIdentifier());
354             writeTx.put(datastore, childPath, mapEntryNode);
355             lastInsertedPosition++;
356         }
357     }
358
359     private static void listPut(final LogicalDatastoreType datastore, final YangInstanceIdentifier path,
360             final DOMDataReadWriteTransaction writeTx, final SchemaContext schemaContext,
361             final OrderedLeafSetNode<?> payload) {
362         final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
363         writeTx.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
364         TransactionUtil.ensureParentsByMerge(path, schemaContext, writeTx);
365         for (final LeafSetEntryNode<?> child : ((LeafSetNode<?>) payload).getValue()) {
366             final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
367             writeTx.put(datastore, childPath, child);
368         }
369     }
370
371     private static void listPut(final LogicalDatastoreType datastore, final YangInstanceIdentifier path,
372             final DOMDataReadWriteTransaction writeTx, final SchemaContext schemaContext,
373             final OrderedMapNode payload) {
374         final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
375         writeTx.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
376         TransactionUtil.ensureParentsByMerge(path, schemaContext, writeTx);
377         for (final MapEntryNode child : ((MapNode) payload).getValue()) {
378             final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
379             writeTx.put(datastore, childPath, child);
380         }
381     }
382
383     private static void simplePut(final LogicalDatastoreType configuration, final YangInstanceIdentifier path,
384             final DOMDataReadWriteTransaction writeTx, final SchemaContext schemaContext,
385             final NormalizedNode<?, ?> data) {
386         TransactionUtil.ensureParentsByMerge(path, schemaContext, writeTx);
387         writeTx.put(LogicalDatastoreType.CONFIGURATION, path, data);
388     }
389
390     private static CheckedFuture<Void, TransactionCommitFailedException> makePut(final YangInstanceIdentifier path,
391             final SchemaContext schemaContext, final DOMDataWriteTransaction writeTx, final NormalizedNode<?, ?> data) {
392         TransactionUtil.ensureParentsByMerge(path, schemaContext, writeTx);
393         writeTx.put(LogicalDatastoreType.CONFIGURATION, path, data);
394         return writeTx.submit();
395     }
396
397     public static DataSchemaNode checkListAndOrderedType(final SchemaContext ctx, final YangInstanceIdentifier path) {
398         final YangInstanceIdentifier parent = path.getParent();
399         final DataSchemaContextNode<?> node = DataSchemaContextTree.from(ctx).getChild(parent);
400         final DataSchemaNode dataSchemaNode = node.getDataSchemaNode();
401
402         if (dataSchemaNode instanceof ListSchemaNode) {
403             if (!((ListSchemaNode) dataSchemaNode).isUserOrdered()) {
404                 throw new RestconfDocumentedException("Insert parameter can be used only with ordered-by user list.");
405             }
406             return dataSchemaNode;
407         }
408         if (dataSchemaNode instanceof LeafListSchemaNode) {
409             if (!((LeafListSchemaNode) dataSchemaNode).isUserOrdered()) {
410                 throw new RestconfDocumentedException(
411                         "Insert parameter can be used only with ordered-by user leaf-list.");
412             }
413             return dataSchemaNode;
414         }
415         throw new RestconfDocumentedException("Insert parameter can be used only with list or leaf-list");
416     }
417 }