2 * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.restconf.restful.utils;
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;
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;
50 * Util class for read data from data store via transaction.
54 * <li>all (config + state)
58 public final class ReadDataTransactionUtil {
60 private ReadDataTransactionUtil() {
61 throw new UnsupportedOperationException("Util class.");
65 * Parse parameters from URI request and check their types and values.
69 * @return {@link WriterParameters}
71 @Nonnull public static WriterParameters parseUriParameters(@Nullable final UriInfo uriInfo) {
72 final WriterParametersBuilder builder = new WriterParametersBuilder();
74 if (uriInfo == null) {
75 return builder.build();
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);
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));
93 // parameter can be in URI at most once
94 ParametersUtil.checkParameterCount(content, RestconfDataServiceConstant.ReadData.CONTENT);
95 ParametersUtil.checkParameterCount(depth, RestconfDataServiceConstant.ReadData.DEPTH);
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)"));
109 builder.setContent(content.get(0));
111 // check and set depth
112 if (!depth.get(0).equals(RestconfDataServiceConstant.ReadData.UNBOUNDED)) {
113 final Integer value = Ints.tryParse(depth.get(0));
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\""));
123 builder.setDepth(value);
127 return builder.build();
131 * Read specific type of data from data store via transaction.
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}
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);
146 case RestconfDataServiceConstant.ReadData.NONCONFIG:
147 transactionNode.setLogicalDatastoreType(LogicalDatastoreType.OPERATIONAL);
148 return readDataViaTransaction(transactionNode);
150 case RestconfDataServiceConstant.ReadData.ALL:
151 return readAllData(transactionNode);
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)"));
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)
166 * @param transactionNode
167 * - {@link TransactionVarsWrapper} - wrapper for variables
168 * @return {@link NormalizedNode}
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,
178 return dataFactory.build();
182 * Read config and state data, then map them.
184 * @param transactionNode
185 * - {@link TransactionVarsWrapper} - wrapper for variables
186 * @return {@link NormalizedNode}
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);
193 // PREPARE CONFIG DATA NODE
194 transactionNode.setLogicalDatastoreType(LogicalDatastoreType.CONFIGURATION);
195 final NormalizedNode<?, ?> configDataNode = readDataViaTransaction(transactionNode);
198 if ((stateDataNode == null) && (configDataNode == null)) {
202 // return config data
203 if (stateDataNode == null) {
204 return configDataNode;
208 if (configDataNode == null) {
209 return stateDataNode;
212 // merge data from config and state
213 return mapNode(stateDataNode, configDataNode);
217 * Map data by type of read node.
219 * @param stateDataNode
220 * - data node of state data
221 * @param configDataNode
222 * - data node of config data
223 * @return {@link NormalizedNode}
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);
231 return prepareData(configDataNode, stateDataNode);
236 * Valid of can be data merged together.
238 * @param stateDataNode
239 * - data node of state data
240 * @param configDataNode
241 * - data node of config data
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 ");
253 * Prepare and map data for rpc
255 * @param configDataNode
256 * - data node of config data
257 * @param stateDataNode
258 * - data node of state data
259 * @return {@link NormalizedNode}
261 private static @Nonnull NormalizedNode<?, ?> prepareRpcData(@Nonnull final NormalizedNode<?, ?> configDataNode,
262 @Nonnull final NormalizedNode<?, ?> stateDataNode) {
263 final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> mapEntryBuilder = ImmutableNodes
265 mapEntryBuilder.withNodeIdentifier((NodeIdentifierWithPredicates) configDataNode.getIdentifier());
268 mapRpcDataNode(configDataNode, mapEntryBuilder);
270 mapRpcDataNode(stateDataNode, mapEntryBuilder);
272 return ImmutableNodes.mapNodeBuilder(configDataNode.getNodeType()).addChild(mapEntryBuilder.build()).build();
276 * Map node to map entry builder.
280 * @param mapEntryBuilder
281 * - builder for mapping data
283 private static void mapRpcDataNode(@Nonnull final NormalizedNode<?, ?> dataNode,
284 @Nonnull final DataContainerNodeBuilder<
285 NodeIdentifierWithPredicates, MapEntryNode> mapEntryBuilder) {
286 ((ContainerNode) dataNode).getValue().forEach(mapEntryBuilder::addChild);
290 * Prepare and map all data from DS
292 * @param configDataNode
293 * - data node of config data
294 * @param stateDataNode
295 * - data node of state data
296 * @return {@link NormalizedNode}
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());
305 ((MapNode) configDataNode).getValue(), ((MapNode) stateDataNode).getValue(), builder);
307 return builder.build();
308 } else if (configDataNode instanceof MapEntryNode) {
309 final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> builder = ImmutableNodes
310 .mapEntryBuilder().withNodeIdentifier(((MapEntryNode) configDataNode).getIdentifier());
313 ((MapEntryNode) configDataNode).getValue(), ((MapEntryNode) stateDataNode).getValue(), builder);
315 return builder.build();
316 } else if (configDataNode instanceof ContainerNode) {
317 final DataContainerNodeAttrBuilder<NodeIdentifier, ContainerNode> builder = Builders
318 .containerBuilder().withNodeIdentifier(((ContainerNode) configDataNode).getIdentifier());
321 ((ContainerNode) configDataNode).getValue(), ((ContainerNode) stateDataNode).getValue(), builder);
323 return builder.build();
324 } else if (configDataNode instanceof AugmentationNode) {
325 final DataContainerNodeBuilder<AugmentationIdentifier, AugmentationNode> builder = Builders
326 .augmentationBuilder().withNodeIdentifier(((AugmentationNode) configDataNode).getIdentifier());
329 ((AugmentationNode) configDataNode).getValue(), ((AugmentationNode) stateDataNode).getValue(), builder);
331 return builder.build();
332 } else if (configDataNode instanceof ChoiceNode) {
333 final DataContainerNodeBuilder<NodeIdentifier, ChoiceNode> builder = Builders
334 .choiceBuilder().withNodeIdentifier(((ChoiceNode) configDataNode).getIdentifier());
337 ((ChoiceNode) configDataNode).getValue(), ((ChoiceNode) stateDataNode).getValue(), builder);
339 return builder.build();
340 } else if (configDataNode instanceof LeafNode) {
341 return ImmutableNodes.leafNode(configDataNode.getNodeType(), configDataNode.getValue());
343 throw new RestconfDocumentedException("Bad type of node.");
348 * Map value from container node to builder.
351 * - collection of config data nodes
353 * - collection of state data nodes
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()));
366 // merge config and state data of children with different identifiers
367 mapDataToBuilder(configMap, stateMap, builder);
369 // merge config and state data of children with the same identifiers
370 mergeDataToBuilder(configMap, stateMap, builder);
374 * Map data with different identifiers to builder. Data with different identifiers can be just added
375 * as childs to parent node.
378 * - map of config data nodes
380 * - map of state data nodes
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()));
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.
399 * - immutable config data
401 * - immutable state data
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()))));