cf4ced08b15437d8269f908555438453a8622cec
[netconf.git] / restconf / sal-rest-connector / src / main / java / org / opendaylight / restconf / restful / utils / ReadDataTransactionUtil.java
1 /*
2  * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.restconf.restful.utils;
9
10 import com.google.common.base.Optional;
11 import com.google.common.primitives.Ints;
12 import com.google.common.util.concurrent.CheckedFuture;
13 import java.util.Collection;
14 import java.util.Collections;
15 import java.util.List;
16 import java.util.Map;
17 import java.util.function.Function;
18 import java.util.stream.Collectors;
19 import javax.annotation.Nonnull;
20 import javax.annotation.Nullable;
21 import javax.ws.rs.core.UriInfo;
22 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
23 import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
24 import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
25 import org.opendaylight.netconf.sal.restconf.impl.RestconfError;
26 import org.opendaylight.netconf.sal.restconf.impl.WriterParameters;
27 import org.opendaylight.netconf.sal.restconf.impl.WriterParameters.WriterParametersBuilder;
28 import org.opendaylight.restconf.restful.transaction.TransactionVarsWrapper;
29 import org.opendaylight.yangtools.yang.common.QNameModule;
30 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
31 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
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.AugmentationNode;
35 import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode;
36 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
37 import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
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.impl.schema.Builders;
42 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
43 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.CollectionNodeBuilder;
44 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeAttrBuilder;
45 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeBuilder;
46 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.NormalizedNodeContainerBuilder;
47 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
48
49 /**
50  * Util class for read data from data store via transaction.
51  * <ul>
52  * <li>config
53  * <li>state
54  * <li>all (config + state)
55  * </ul>
56  *
57  */
58 public final class ReadDataTransactionUtil {
59
60     private ReadDataTransactionUtil() {
61         throw new UnsupportedOperationException("Util class.");
62     }
63
64     /**
65      * Parse parameters from URI request and check their types and values.
66      *
67      * @param uriInfo
68      *            - URI info
69      * @return {@link WriterParameters}
70      */
71     @Nonnull public static WriterParameters parseUriParameters(@Nullable final UriInfo uriInfo) {
72         final WriterParametersBuilder builder = new WriterParametersBuilder();
73
74         if (uriInfo == null) {
75             return builder.build();
76         }
77
78         // check only allowed parameters
79         ParametersUtil.checkParametersTypes(
80                 RestconfDataServiceConstant.ReadData.READ_TYPE_TX,
81                 uriInfo.getQueryParameters().keySet(),
82                 RestconfDataServiceConstant.ReadData.CONTENT,
83                 RestconfDataServiceConstant.ReadData.DEPTH);
84
85         // read parameters from URI or set default values
86         final List<String> content = uriInfo.getQueryParameters().getOrDefault(
87                 RestconfDataServiceConstant.ReadData.CONTENT,
88                 Collections.singletonList(RestconfDataServiceConstant.ReadData.ALL));
89         final List<String> depth = uriInfo.getQueryParameters().getOrDefault(
90                 RestconfDataServiceConstant.ReadData.DEPTH,
91                 Collections.singletonList(RestconfDataServiceConstant.ReadData.UNBOUNDED));
92
93         // parameter can be in URI at most once
94         ParametersUtil.checkParameterCount(content, RestconfDataServiceConstant.ReadData.CONTENT);
95         ParametersUtil.checkParameterCount(depth, RestconfDataServiceConstant.ReadData.DEPTH);
96
97         // check and set content
98         final String contentValue = content.get(0);
99         if (!contentValue.equals(RestconfDataServiceConstant.ReadData.ALL)) {
100             if (!contentValue.equals(RestconfDataServiceConstant.ReadData.CONFIG)
101                     && !contentValue.equals(RestconfDataServiceConstant.ReadData.NONCONFIG)) {
102                 throw new RestconfDocumentedException(
103                         new RestconfError(RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.INVALID_VALUE,
104                                 "Invalid content parameter: " + contentValue, null,
105                                 "The content parameter value must be either config, nonconfig or all (default)"));
106             }
107         }
108
109         builder.setContent(content.get(0));
110
111         // check and set depth
112         if (!depth.get(0).equals(RestconfDataServiceConstant.ReadData.UNBOUNDED)) {
113             final Integer value = Ints.tryParse(depth.get(0));
114
115             if (value == null
116                     || (!(value >= RestconfDataServiceConstant.ReadData.MIN_DEPTH
117                         && value <= RestconfDataServiceConstant.ReadData.MAX_DEPTH))) {
118                 throw new RestconfDocumentedException(
119                         new RestconfError(RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.INVALID_VALUE,
120                                 "Invalid depth parameter: " + depth, null,
121                                 "The depth parameter must be an integer between 1 and 65535 or \"unbounded\""));
122             } else {
123                 builder.setDepth(value);
124             }
125         }
126
127         return builder.build();
128     }
129
130     /**
131      * Read specific type of data from data store via transaction.
132      *
133      * @param valueOfContent
134      *            - type of data to read (config, state, all)
135      * @param transactionNode
136      *            - {@link TransactionVarsWrapper} - wrapper for variables
137      * @return {@link NormalizedNode}
138      */
139     public static @Nullable NormalizedNode<?, ?> readData(@Nonnull final String valueOfContent,
140                                                           @Nonnull final TransactionVarsWrapper transactionNode) {
141         switch (valueOfContent) {
142             case RestconfDataServiceConstant.ReadData.CONFIG:
143                 transactionNode.setLogicalDatastoreType(LogicalDatastoreType.CONFIGURATION);
144                 return readDataViaTransaction(transactionNode);
145
146             case RestconfDataServiceConstant.ReadData.NONCONFIG:
147                 transactionNode.setLogicalDatastoreType(LogicalDatastoreType.OPERATIONAL);
148                 return readDataViaTransaction(transactionNode);
149
150             case RestconfDataServiceConstant.ReadData.ALL:
151                 return readAllData(transactionNode);
152
153             default:
154                 throw new RestconfDocumentedException(
155                         new RestconfError(RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.INVALID_VALUE,
156                                 "Invalid content parameter: " + valueOfContent, null,
157                                 "The content parameter value must be either config, nonconfig or all (default)"));
158         }
159     }
160
161     /**
162      * If is set specific {@link LogicalDatastoreType} in
163      * {@link TransactionVarsWrapper}, then read this type of data from DS. If
164      * don't, we have to read all data from DS (state + config)
165      *
166      * @param transactionNode
167      *            - {@link TransactionVarsWrapper} - wrapper for variables
168      * @return {@link NormalizedNode}
169      */
170     private static @Nullable NormalizedNode<?, ?> readDataViaTransaction(
171             @Nonnull final TransactionVarsWrapper transactionNode) {
172         final CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> listenableFuture = transactionNode
173                 .getTransactionChain().newReadOnlyTransaction().read(transactionNode.getLogicalDatastoreType(),
174                         transactionNode.getInstanceIdentifier().getInstanceIdentifier());
175         final NormalizedNodeFactory dataFactory = new NormalizedNodeFactory();
176         FutureCallbackTx.addCallback(listenableFuture, RestconfDataServiceConstant.ReadData.READ_TYPE_TX,
177                 dataFactory);
178         return dataFactory.build();
179     }
180
181     /**
182      * Read config and state data, then map them.
183      *
184      * @param transactionNode
185      *            - {@link TransactionVarsWrapper} - wrapper for variables
186      * @return {@link NormalizedNode}
187      */
188     private static @Nullable NormalizedNode<?, ?> readAllData(@Nonnull final TransactionVarsWrapper transactionNode) {
189         // PREPARE STATE DATA NODE
190         transactionNode.setLogicalDatastoreType(LogicalDatastoreType.OPERATIONAL);
191         final NormalizedNode<?, ?> stateDataNode = readDataViaTransaction(transactionNode);
192
193         // PREPARE CONFIG DATA NODE
194         transactionNode.setLogicalDatastoreType(LogicalDatastoreType.CONFIGURATION);
195         final NormalizedNode<?, ?> configDataNode = readDataViaTransaction(transactionNode);
196
197         // if no data exists
198         if ((stateDataNode == null) && (configDataNode == null)) {
199             return null;
200         }
201
202         // return config data
203         if (stateDataNode == null) {
204             return configDataNode;
205         }
206
207         // return state data
208         if (configDataNode == null) {
209             return stateDataNode;
210         }
211
212         // merge data from config and state
213         return mapNode(stateDataNode, configDataNode);
214     }
215
216     /**
217      * Map data by type of read node.
218      *
219      * @param stateDataNode
220      *            - data node of state data
221      * @param configDataNode
222      *            - data node of config data
223      * @return {@link NormalizedNode}
224      */
225     private static @Nonnull NormalizedNode<?, ?> mapNode(@Nonnull final NormalizedNode<?, ?> stateDataNode,
226                                                          @Nonnull final NormalizedNode<?, ?> configDataNode) {
227         validPossibilityOfMergeNodes(stateDataNode, configDataNode);
228         if (configDataNode instanceof RpcDefinition) {
229             return prepareRpcData(configDataNode, stateDataNode);
230         } else {
231             return prepareData(configDataNode, stateDataNode);
232         }
233     }
234
235     /**
236      * Valid of can be data merged together.
237      *
238      * @param stateDataNode
239      *            - data node of state data
240      * @param configDataNode
241      *            - data node of config data
242      */
243     private static void validPossibilityOfMergeNodes(@Nonnull final NormalizedNode<?, ?> stateDataNode,
244                                                      @Nonnull final NormalizedNode<?, ?> configDataNode) {
245         final QNameModule moduleOfStateData = stateDataNode.getIdentifier().getNodeType().getModule();
246         final QNameModule moduleOfConfigData = configDataNode.getIdentifier().getNodeType().getModule();
247         if (moduleOfStateData != moduleOfConfigData) {
248             throw new RestconfDocumentedException("It is not possible to merge ");
249         }
250     }
251
252     /**
253      * Prepare and map data for rpc
254      *
255      * @param configDataNode
256      *            - data node of config data
257      * @param stateDataNode
258      *            - data node of state data
259      * @return {@link NormalizedNode}
260      */
261     private static @Nonnull NormalizedNode<?, ?> prepareRpcData(@Nonnull final NormalizedNode<?, ?> configDataNode,
262                                                                 @Nonnull final NormalizedNode<?, ?> stateDataNode) {
263         final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> mapEntryBuilder = ImmutableNodes
264                 .mapEntryBuilder();
265         mapEntryBuilder.withNodeIdentifier((NodeIdentifierWithPredicates) configDataNode.getIdentifier());
266
267         // MAP CONFIG DATA
268         mapRpcDataNode(configDataNode, mapEntryBuilder);
269         // MAP STATE DATA
270         mapRpcDataNode(stateDataNode, mapEntryBuilder);
271
272         return ImmutableNodes.mapNodeBuilder(configDataNode.getNodeType()).addChild(mapEntryBuilder.build()).build();
273     }
274
275     /**
276      * Map node to map entry builder.
277      *
278      * @param dataNode
279      *            - data node
280      * @param mapEntryBuilder
281      *            - builder for mapping data
282      */
283     private static void mapRpcDataNode(@Nonnull final NormalizedNode<?, ?> dataNode,
284                                        @Nonnull final DataContainerNodeBuilder<
285                                                NodeIdentifierWithPredicates, MapEntryNode> mapEntryBuilder) {
286         ((ContainerNode) dataNode).getValue().forEach(mapEntryBuilder::addChild);
287     }
288
289     /**
290      * Prepare and map all data from DS
291      *
292      * @param configDataNode
293      *            - data node of config data
294      * @param stateDataNode
295      *            - data node of state data
296      * @return {@link NormalizedNode}
297      */
298     private static @Nonnull NormalizedNode<?, ?> prepareData(@Nonnull final NormalizedNode<?, ?> configDataNode,
299                                                              @Nonnull final NormalizedNode<?, ?> stateDataNode) {
300         if (configDataNode instanceof MapNode) {
301             final CollectionNodeBuilder<MapEntryNode, MapNode> builder = ImmutableNodes
302                     .mapNodeBuilder().withNodeIdentifier(((MapNode) configDataNode).getIdentifier());
303
304             mapValueToBuilder(
305                     ((MapNode) configDataNode).getValue(), ((MapNode) stateDataNode).getValue(), builder);
306
307             return builder.build();
308         } else if (configDataNode instanceof MapEntryNode) {
309             final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> builder = ImmutableNodes
310                     .mapEntryBuilder().withNodeIdentifier(((MapEntryNode) configDataNode).getIdentifier());
311
312             mapValueToBuilder(
313                     ((MapEntryNode) configDataNode).getValue(), ((MapEntryNode) stateDataNode).getValue(), builder);
314
315             return builder.build();
316         } else if (configDataNode instanceof ContainerNode) {
317             final DataContainerNodeAttrBuilder<NodeIdentifier, ContainerNode> builder = Builders
318                     .containerBuilder().withNodeIdentifier(((ContainerNode) configDataNode).getIdentifier());
319
320             mapValueToBuilder(
321                     ((ContainerNode) configDataNode).getValue(), ((ContainerNode) stateDataNode).getValue(), builder);
322
323             return builder.build();
324         } else if (configDataNode instanceof AugmentationNode) {
325             final DataContainerNodeBuilder<AugmentationIdentifier, AugmentationNode> builder = Builders
326                     .augmentationBuilder().withNodeIdentifier(((AugmentationNode) configDataNode).getIdentifier());
327
328             mapValueToBuilder(
329                     ((AugmentationNode) configDataNode).getValue(), ((AugmentationNode) stateDataNode).getValue(), builder);
330
331             return builder.build();
332         } else if (configDataNode instanceof ChoiceNode) {
333             final DataContainerNodeBuilder<NodeIdentifier, ChoiceNode> builder = Builders
334                     .choiceBuilder().withNodeIdentifier(((ChoiceNode) configDataNode).getIdentifier());
335
336             mapValueToBuilder(
337                     ((ChoiceNode) configDataNode).getValue(), ((ChoiceNode) stateDataNode).getValue(), builder);
338
339             return builder.build();
340         } else if (configDataNode instanceof LeafNode) {
341             return ImmutableNodes.leafNode(configDataNode.getNodeType(), configDataNode.getValue());
342         } else {
343             throw new RestconfDocumentedException("Bad type of node.");
344         }
345     }
346
347     /**
348      * Map value from container node to builder.
349      *
350      * @param configData
351      *            - collection of config data nodes
352      * @param stateData
353      *            - collection of state data nodes
354      * @param builder
355      *            - builder
356      */
357     private static <T extends NormalizedNode<? extends PathArgument, ?>> void mapValueToBuilder(
358             @Nonnull final Collection<T> configData,
359             @Nonnull final Collection<T> stateData,
360             @Nonnull final NormalizedNodeContainerBuilder<?, PathArgument, T, ?> builder) {
361         final Map<PathArgument, T> configMap = configData.stream().collect(
362                 Collectors.toMap(NormalizedNode::getIdentifier, Function.identity()));
363         final Map<PathArgument, T> stateMap = stateData.stream().collect(
364                 Collectors.toMap(NormalizedNode::getIdentifier, Function.identity()));
365
366         // merge config and state data of children with different identifiers
367         mapDataToBuilder(configMap, stateMap, builder);
368
369         // merge config and state data of children with the same identifiers
370         mergeDataToBuilder(configMap, stateMap, builder);
371     }
372
373     /**
374      * Map data with different identifiers to builder. Data with different identifiers can be just added
375      * as childs to parent node.
376      *
377      * @param configMap
378      *            - map of config data nodes
379      * @param stateMap
380      *            - map of state data nodes
381      * @param builder
382      *           - builder
383      */
384     private static <T extends NormalizedNode<? extends PathArgument, ?>> void mapDataToBuilder(
385             @Nonnull final Map<PathArgument, T> configMap,
386             @Nonnull final Map<PathArgument, T> stateMap,
387             @Nonnull final NormalizedNodeContainerBuilder<?, PathArgument, T, ?> builder) {
388         configMap.entrySet().stream().filter(x -> !stateMap.containsKey(x.getKey())).forEach(
389                 y -> builder.addChild(y.getValue()));
390         stateMap.entrySet().stream().filter(x -> !configMap.containsKey(x.getKey())).forEach(
391                 y -> builder.addChild(y.getValue()));
392     }
393
394     /**
395      * Map data with the same identifiers to builder. Data with the same identifiers cannot be just added but we need to
396      * go one level down with {@code prepareData} method.
397      *
398      * @param configMap
399      *            - immutable config data
400      * @param stateMap
401      *            - immutable state data
402      * @param builder
403      *           - builder
404      */
405     @SuppressWarnings("unchecked")
406     private static <T extends NormalizedNode<? extends PathArgument, ?>> void mergeDataToBuilder(
407             @Nonnull final Map<PathArgument, T> configMap,
408             @Nonnull final Map<PathArgument, T> stateMap,
409             @Nonnull final NormalizedNodeContainerBuilder<?, PathArgument, T, ?> builder) {
410         // it is enough to process only config data because operational contains the same data
411         configMap.entrySet().stream().filter(x -> stateMap.containsKey(x.getKey())).forEach(
412                 y -> builder.addChild((T) prepareData(y.getValue(), stateMap.get(y.getKey()))));
413     }
414 }