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