Integration of RESTCONF fields to NETCONF filters
[netconf.git] / restconf / restconf-nb-rfc8040 / src / main / java / org / opendaylight / restconf / nb / rfc8040 / rests / 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.nb.rfc8040.rests.utils;
9
10 import static org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserFieldsParameter.parseFieldsParameter;
11 import static org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserFieldsParameter.parseFieldsPaths;
12
13 import com.google.common.annotations.VisibleForTesting;
14 import com.google.common.collect.Sets;
15 import com.google.common.primitives.Ints;
16 import com.google.common.util.concurrent.ListenableFuture;
17 import java.util.Collection;
18 import java.util.Collections;
19 import java.util.List;
20 import java.util.Map;
21 import java.util.Optional;
22 import java.util.Set;
23 import java.util.function.Function;
24 import java.util.stream.Collectors;
25 import javax.ws.rs.core.UriInfo;
26 import org.eclipse.jdt.annotation.NonNull;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
29 import org.opendaylight.mdsal.dom.api.DOMTransactionChain;
30 import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
31 import org.opendaylight.restconf.common.context.WriterParameters;
32 import org.opendaylight.restconf.common.context.WriterParameters.WriterParametersBuilder;
33 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
34 import org.opendaylight.restconf.common.errors.RestconfError;
35 import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag;
36 import org.opendaylight.restconf.common.errors.RestconfError.ErrorType;
37 import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfStrategy;
38 import org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfDataServiceConstant.ReadData.WithDefaults;
39 import org.opendaylight.yangtools.yang.common.QName;
40 import org.opendaylight.yangtools.yang.common.QNameModule;
41 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
42 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
43 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
44 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
45 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
46 import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode;
47 import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode;
48 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
49 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
50 import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
51 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
52 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
53 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
54 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
55 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
56 import org.opendaylight.yangtools.yang.data.api.schema.OrderedLeafSetNode;
57 import org.opendaylight.yangtools.yang.data.api.schema.OrderedMapNode;
58 import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListEntryNode;
59 import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListNode;
60 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
61 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
62 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.CollectionNodeBuilder;
63 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeBuilder;
64 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.ListNodeBuilder;
65 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.NormalizedNodeBuilder;
66 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.NormalizedNodeContainerBuilder;
67 import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
68 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
69 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
70 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
71 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
72 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
73 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
74
75 /**
76  * Util class for read data from data store via transaction.
77  * <ul>
78  * <li>config
79  * <li>state
80  * <li>all (config + state)
81  * </ul>
82  */
83 public final class ReadDataTransactionUtil {
84     private static final String READ_TYPE_TX = "READ";
85
86     private ReadDataTransactionUtil() {
87         throw new UnsupportedOperationException("Util class.");
88     }
89
90     /**
91      * Parse parameters from URI request and check their types and values.
92      *
93      * @param identifier {@link InstanceIdentifierContext}
94      * @param uriInfo    URI info
95      * @return {@link WriterParameters}
96      */
97     public static WriterParameters parseUriParameters(final InstanceIdentifierContext<?> identifier,
98                                                       final UriInfo uriInfo) {
99         final WriterParametersBuilder builder = new WriterParametersBuilder();
100
101         if (uriInfo == null) {
102             return builder.build();
103         }
104
105         // check only allowed parameters
106         checkParametersTypes(uriInfo.getQueryParameters().keySet(),
107                 RestconfDataServiceConstant.ReadData.CONTENT,
108                 RestconfDataServiceConstant.ReadData.DEPTH,
109                 RestconfDataServiceConstant.ReadData.FIELDS, RestconfDataServiceConstant.ReadData.WITH_DEFAULTS);
110
111         // read parameters from URI or set default values
112         final List<String> content = uriInfo.getQueryParameters().getOrDefault(
113                 RestconfDataServiceConstant.ReadData.CONTENT,
114                 Collections.singletonList(RestconfDataServiceConstant.ReadData.ALL));
115         final List<String> depth = uriInfo.getQueryParameters().getOrDefault(
116                 RestconfDataServiceConstant.ReadData.DEPTH,
117                 Collections.singletonList(RestconfDataServiceConstant.ReadData.UNBOUNDED));
118         final List<String> withDefaults = uriInfo.getQueryParameters().getOrDefault(
119                 RestconfDataServiceConstant.ReadData.WITH_DEFAULTS,
120                 Collections.emptyList());
121         // fields
122         final List<String> fields = uriInfo.getQueryParameters().getOrDefault(
123                 RestconfDataServiceConstant.ReadData.FIELDS,
124                 Collections.emptyList());
125
126         // parameter can be in URI at most once
127         checkParameterCount(content, RestconfDataServiceConstant.ReadData.CONTENT);
128         checkParameterCount(depth, RestconfDataServiceConstant.ReadData.DEPTH);
129         checkParameterCount(fields, RestconfDataServiceConstant.ReadData.FIELDS);
130         checkParameterCount(fields, RestconfDataServiceConstant.ReadData.WITH_DEFAULTS);
131
132         // check and set content
133         final String contentValue = content.get(0);
134         if (!contentValue.equals(RestconfDataServiceConstant.ReadData.ALL)) {
135             if (!contentValue.equals(RestconfDataServiceConstant.ReadData.CONFIG)
136                     && !contentValue.equals(RestconfDataServiceConstant.ReadData.NONCONFIG)) {
137                 throw new RestconfDocumentedException(
138                         new RestconfError(RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.INVALID_VALUE,
139                                 "Invalid content parameter: " + contentValue, null,
140                                 "The content parameter value must be either config, nonconfig or all (default)"));
141             }
142         }
143
144         builder.setContent(content.get(0));
145
146         // check and set depth
147         if (!depth.get(0).equals(RestconfDataServiceConstant.ReadData.UNBOUNDED)) {
148             final Integer value = Ints.tryParse(depth.get(0));
149
150             if (value == null || value < RestconfDataServiceConstant.ReadData.MIN_DEPTH
151                     || value > RestconfDataServiceConstant.ReadData.MAX_DEPTH) {
152                 throw new RestconfDocumentedException(
153                         new RestconfError(RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.INVALID_VALUE,
154                                 "Invalid depth parameter: " + depth, null,
155                                 "The depth parameter must be an integer between 1 and 65535 or \"unbounded\""));
156             } else {
157                 builder.setDepth(value);
158             }
159         }
160
161         // check and set fields
162         if (!fields.isEmpty()) {
163             if (identifier.getMountPoint() != null) {
164                 builder.setFieldPaths(parseFieldsPaths(identifier, fields.get(0)));
165             } else {
166                 builder.setFields(parseFieldsParameter(identifier, fields.get(0)));
167             }
168         }
169
170         // check and set withDefaults parameter
171         if (!withDefaults.isEmpty()) {
172             final String str = withDefaults.get(0);
173             final WithDefaults val = WithDefaults.forValue(str);
174             if (val == null) {
175                 throw new RestconfDocumentedException(new RestconfError(RestconfError.ErrorType.PROTOCOL,
176                     RestconfError.ErrorTag.INVALID_VALUE, "Invalid with-defaults parameter: " + str, null,
177                     "The with-defaults parameter must be a string in " + WithDefaults.possibleValues()));
178             }
179
180             switch (val) {
181                 case REPORT_ALL:
182                     break;
183                 case REPORT_ALL_TAGGED:
184                     builder.setTagged(true);
185                     break;
186                 default:
187                     builder.setWithDefault(val.value());
188             }
189         }
190         return builder.build();
191     }
192
193     /**
194      * Read specific type of data from data store via transaction. Close {@link DOMTransactionChain} if any
195      * inside of object {@link RestconfStrategy} provided as a parameter.
196      *
197      * @param valueOfContent type of data to read (config, state, all)
198      * @param path           the path to read
199      * @param strategy       {@link RestconfStrategy} - object that perform the actual DS operations
200      * @param withDefa       value of with-defaults parameter
201      * @param ctx            schema context
202      * @return {@link NormalizedNode}
203      */
204     public static @Nullable NormalizedNode<?, ?> readData(final @NonNull String valueOfContent,
205                                                           final @NonNull YangInstanceIdentifier path,
206                                                           final @NonNull RestconfStrategy strategy,
207                                                           final String withDefa, final EffectiveModelContext ctx) {
208         switch (valueOfContent) {
209             case RestconfDataServiceConstant.ReadData.CONFIG:
210                 if (withDefa == null) {
211                     return readDataViaTransaction(strategy, LogicalDatastoreType.CONFIGURATION, path, true);
212                 } else {
213                     return prepareDataByParamWithDef(
214                             readDataViaTransaction(strategy, LogicalDatastoreType.CONFIGURATION, path, true),
215                             path, withDefa, ctx);
216                 }
217             case RestconfDataServiceConstant.ReadData.NONCONFIG:
218                 return readDataViaTransaction(strategy, LogicalDatastoreType.OPERATIONAL, path, true);
219             case RestconfDataServiceConstant.ReadData.ALL:
220                 return readAllData(strategy, path, withDefa, ctx);
221             default:
222                 strategy.close();
223                 throw new RestconfDocumentedException(
224                         new RestconfError(RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.INVALID_VALUE,
225                                 "Invalid content parameter: " + valueOfContent, null,
226                                 "The content parameter value must be either config, nonconfig or all (default)"));
227         }
228     }
229
230     /**
231      * Read specific type of data from data store via transaction with specified subtrees that should only be read.
232      * Close {@link DOMTransactionChain} inside of object {@link RestconfStrategy} provided as a parameter.
233      *
234      * @param valueOfContent type of data to read (config, state, all)
235      * @param path           the parent path to read
236      * @param strategy       {@link RestconfStrategy} - object that perform the actual DS operations
237      * @param withDefa       value of with-defaults parameter
238      * @param ctx            schema context
239      * @param fields         paths to selected subtrees which should be read, relative to to the parent path
240      * @return {@link NormalizedNode}
241      */
242     public static @Nullable NormalizedNode<?, ?> readData(final @NonNull String valueOfContent,
243             final @NonNull YangInstanceIdentifier path, final @NonNull RestconfStrategy strategy,
244             final @Nullable String withDefa, @NonNull final EffectiveModelContext ctx,
245             final @NonNull List<YangInstanceIdentifier> fields) {
246         switch (valueOfContent) {
247             case RestconfDataServiceConstant.ReadData.CONFIG:
248                 if (withDefa == null) {
249                     return readDataViaTransaction(strategy, LogicalDatastoreType.CONFIGURATION, path, true, fields);
250                 } else {
251                     return prepareDataByParamWithDef(
252                             readDataViaTransaction(strategy, LogicalDatastoreType.CONFIGURATION, path, true, fields),
253                             path, withDefa, ctx);
254                 }
255             case RestconfDataServiceConstant.ReadData.NONCONFIG:
256                 return readDataViaTransaction(strategy, LogicalDatastoreType.OPERATIONAL, path, true, fields);
257             case RestconfDataServiceConstant.ReadData.ALL:
258                 return readAllData(strategy, path, withDefa, ctx, fields);
259             default:
260                 strategy.close();
261                 throw new RestconfDocumentedException(new RestconfError(RestconfError.ErrorType.PROTOCOL,
262                         RestconfError.ErrorTag.INVALID_VALUE, "Invalid content parameter: " + valueOfContent, null,
263                         "The content parameter value must be either config, nonconfig or all (default)"));
264         }
265     }
266
267     /**
268      * Check if URI does not contain value for the same parameter more than once.
269      *
270      * @param parameterValues URI parameter values
271      * @param parameterName URI parameter name
272      */
273     @VisibleForTesting
274     static void checkParameterCount(final @NonNull List<String> parameterValues, final @NonNull String parameterName) {
275         if (parameterValues.size() > 1) {
276             throw new RestconfDocumentedException(
277                     "Parameter " + parameterName + " can appear at most once in request URI",
278                     ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
279         }
280     }
281
282     /**
283      * Check if URI does not contain not allowed parameters for specified operation.
284      *
285      * @param usedParameters parameters used in URI request
286      * @param allowedParameters allowed parameters for operation
287      */
288     @VisibleForTesting
289     static void checkParametersTypes(final @NonNull Set<String> usedParameters,
290                                      final @NonNull String... allowedParameters) {
291         // FIXME: there should be a speedier way to do this
292         final Set<String> notAllowedParameters = Sets.newHashSet(usedParameters);
293         notAllowedParameters.removeAll(Sets.newHashSet(allowedParameters));
294
295         if (!notAllowedParameters.isEmpty()) {
296             throw new RestconfDocumentedException(
297                     "Not allowed parameters for " + READ_TYPE_TX + " operation: " + notAllowedParameters,
298                     RestconfError.ErrorType.PROTOCOL,
299                     RestconfError.ErrorTag.INVALID_VALUE);
300         }
301     }
302
303     private static NormalizedNode<?, ?> prepareDataByParamWithDef(final NormalizedNode<?, ?> result,
304             final YangInstanceIdentifier path, final String withDefa, final EffectiveModelContext ctx) {
305         boolean trim;
306         switch (withDefa) {
307             case "trim":
308                 trim = true;
309                 break;
310             case "explicit":
311                 trim = false;
312                 break;
313             default:
314                 throw new RestconfDocumentedException("");
315         }
316
317         final DataSchemaContextTree baseSchemaCtxTree = DataSchemaContextTree.from(ctx);
318         final DataSchemaNode baseSchemaNode = baseSchemaCtxTree.findChild(path).orElseThrow().getDataSchemaNode();
319         if (result instanceof ContainerNode) {
320             final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> builder =
321                     Builders.containerBuilder((ContainerSchemaNode) baseSchemaNode);
322             buildCont(builder, (ContainerNode) result, baseSchemaCtxTree, path, trim);
323             return builder.build();
324         } else {
325             final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> builder =
326                     Builders.mapEntryBuilder((ListSchemaNode) baseSchemaNode);
327             buildMapEntryBuilder(builder, (MapEntryNode) result, baseSchemaCtxTree, path, trim,
328                     ((ListSchemaNode) baseSchemaNode).getKeyDefinition());
329             return builder.build();
330         }
331     }
332
333     private static void buildMapEntryBuilder(
334             final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> builder,
335             final MapEntryNode result, final DataSchemaContextTree baseSchemaCtxTree,
336             final YangInstanceIdentifier actualPath, final boolean trim, final List<QName> keys) {
337         for (final DataContainerChild<? extends PathArgument, ?> child : result.getValue()) {
338             final YangInstanceIdentifier path = actualPath.node(child.getIdentifier());
339             final DataSchemaNode childSchema = baseSchemaCtxTree.findChild(path).orElseThrow().getDataSchemaNode();
340             if (child instanceof ContainerNode) {
341                 final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> childBuilder =
342                         Builders.containerBuilder((ContainerSchemaNode) childSchema);
343                 buildCont(childBuilder, (ContainerNode) child, baseSchemaCtxTree, path, trim);
344                 builder.withChild(childBuilder.build());
345             } else if (child instanceof MapNode) {
346                 final CollectionNodeBuilder<MapEntryNode, MapNode> childBuilder =
347                         Builders.mapBuilder((ListSchemaNode) childSchema);
348                 buildList(childBuilder, (MapNode) child, baseSchemaCtxTree, path, trim,
349                         ((ListSchemaNode) childSchema).getKeyDefinition());
350                 builder.withChild(childBuilder.build());
351             } else if (child instanceof LeafNode) {
352                 final Object defaultVal = ((LeafSchemaNode) childSchema).getType().getDefaultValue().orElse(null);
353                 final Object nodeVal = child.getValue();
354                 final NormalizedNodeBuilder<NodeIdentifier, Object, LeafNode<Object>> leafBuilder =
355                         Builders.leafBuilder((LeafSchemaNode) childSchema);
356                 if (keys.contains(child.getNodeType())) {
357                     leafBuilder.withValue(((LeafNode<?>) child).getValue());
358                     builder.withChild(leafBuilder.build());
359                 } else {
360                     if (trim) {
361                         if (defaultVal == null || !defaultVal.equals(nodeVal)) {
362                             leafBuilder.withValue(((LeafNode<?>) child).getValue());
363                             builder.withChild(leafBuilder.build());
364                         }
365                     } else {
366                         if (defaultVal != null && defaultVal.equals(nodeVal)) {
367                             leafBuilder.withValue(((LeafNode<?>) child).getValue());
368                             builder.withChild(leafBuilder.build());
369                         }
370                     }
371                 }
372             }
373         }
374     }
375
376     private static void buildList(final CollectionNodeBuilder<MapEntryNode, MapNode> builder, final MapNode result,
377             final DataSchemaContextTree baseSchemaCtxTree, final YangInstanceIdentifier path, final boolean trim,
378             final List<QName> keys) {
379         for (final MapEntryNode mapEntryNode : result.getValue()) {
380             final YangInstanceIdentifier actualNode = path.node(mapEntryNode.getIdentifier());
381             final DataSchemaNode childSchema = baseSchemaCtxTree.findChild(actualNode).orElseThrow()
382                     .getDataSchemaNode();
383             final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> mapEntryBuilder =
384                     Builders.mapEntryBuilder((ListSchemaNode) childSchema);
385             buildMapEntryBuilder(mapEntryBuilder, mapEntryNode, baseSchemaCtxTree, actualNode, trim, keys);
386             builder.withChild(mapEntryBuilder.build());
387         }
388     }
389
390     private static void buildCont(final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> builder,
391             final ContainerNode result, final DataSchemaContextTree baseSchemaCtxTree,
392             final YangInstanceIdentifier actualPath, final boolean trim) {
393         for (final DataContainerChild<? extends PathArgument, ?> child : result.getValue()) {
394             final YangInstanceIdentifier path = actualPath.node(child.getIdentifier());
395             final DataSchemaNode childSchema = baseSchemaCtxTree.findChild(path).orElseThrow().getDataSchemaNode();
396             if (child instanceof ContainerNode) {
397                 final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> builderChild =
398                         Builders.containerBuilder((ContainerSchemaNode) childSchema);
399                 buildCont(builderChild, result, baseSchemaCtxTree, actualPath, trim);
400                 builder.withChild(builderChild.build());
401             } else if (child instanceof MapNode) {
402                 final CollectionNodeBuilder<MapEntryNode, MapNode> childBuilder =
403                         Builders.mapBuilder((ListSchemaNode) childSchema);
404                 buildList(childBuilder, (MapNode) child, baseSchemaCtxTree, path, trim,
405                         ((ListSchemaNode) childSchema).getKeyDefinition());
406                 builder.withChild(childBuilder.build());
407             } else if (child instanceof LeafNode) {
408                 final Object defaultVal = ((LeafSchemaNode) childSchema).getType().getDefaultValue().orElse(null);
409                 final Object nodeVal = child.getValue();
410                 final NormalizedNodeBuilder<NodeIdentifier, Object, LeafNode<Object>> leafBuilder =
411                         Builders.leafBuilder((LeafSchemaNode) childSchema);
412                 if (trim) {
413                     if (defaultVal == null || !defaultVal.equals(nodeVal)) {
414                         leafBuilder.withValue(((LeafNode<?>) child).getValue());
415                         builder.withChild(leafBuilder.build());
416                     }
417                 } else {
418                     if (defaultVal != null && defaultVal.equals(nodeVal)) {
419                         leafBuilder.withValue(((LeafNode<?>) child).getValue());
420                         builder.withChild(leafBuilder.build());
421                     }
422                 }
423             }
424         }
425     }
426
427     /**
428      * If is set specific {@link LogicalDatastoreType} in {@link RestconfStrategy}, then read this type of data from DS.
429      * If don't, we have to read all data from DS (state + config)
430      *
431      * @param strategy              {@link RestconfStrategy} - object that perform the actual DS operations
432      * @param closeTransactionChain If is set to true, after transaction it will close transactionChain
433      *                              in {@link RestconfStrategy} if any
434      * @return {@link NormalizedNode}
435      */
436     static @Nullable NormalizedNode<?, ?> readDataViaTransaction(final @NonNull RestconfStrategy strategy,
437             final LogicalDatastoreType store, final YangInstanceIdentifier path,
438             final boolean closeTransactionChain) {
439         final ListenableFuture<Optional<NormalizedNode<?, ?>>> listenableFuture = strategy.read(store, path);
440         return extractReadData(strategy, path, closeTransactionChain, listenableFuture);
441     }
442
443     /**
444      * Read specific type of data {@link LogicalDatastoreType} via transaction in {@link RestconfStrategy} with
445      * specified subtrees that should only be read.
446      *
447      * @param strategy              {@link RestconfStrategy} - object that perform the actual DS operations
448      * @param store                 datastore type
449      * @param path                  parent path to selected fields
450      * @param closeTransactionChain if it is set to {@code true}, after transaction it will close transactionChain
451      *                              in {@link RestconfStrategy} if any
452      * @param fields                paths to selected subtrees which should be read, relative to to the parent path
453      * @return {@link NormalizedNode}
454      */
455     private static @Nullable NormalizedNode<?, ?> readDataViaTransaction(final @NonNull RestconfStrategy strategy,
456             final @NonNull LogicalDatastoreType store, final @NonNull YangInstanceIdentifier path,
457             final boolean closeTransactionChain, final @NonNull List<YangInstanceIdentifier> fields) {
458         final ListenableFuture<Optional<NormalizedNode<?, ?>>> listenableFuture = strategy.read(store, path, fields);
459         return extractReadData(strategy, path, closeTransactionChain, listenableFuture);
460     }
461
462     private static NormalizedNode<?, ?> extractReadData(final RestconfStrategy strategy,
463             final YangInstanceIdentifier path, final boolean closeTransactionChain,
464             final ListenableFuture<Optional<NormalizedNode<?, ?>>> dataFuture) {
465         final NormalizedNodeFactory dataFactory = new NormalizedNodeFactory();
466         if (closeTransactionChain) {
467             //Method close transactionChain if any
468             FutureCallbackTx.addCallback(dataFuture, READ_TYPE_TX, dataFactory, strategy, path);
469         } else {
470             FutureCallbackTx.addCallback(dataFuture, READ_TYPE_TX, dataFactory);
471         }
472         return dataFactory.build();
473     }
474
475     /**
476      * Read config and state data, then map them. Close {@link DOMTransactionChain} inside of object
477      * {@link RestconfStrategy} provided as a parameter if any.
478      *
479      * @param strategy {@link RestconfStrategy} - object that perform the actual DS operations
480      * @param withDefa with-defaults parameter
481      * @param ctx      schema context
482      * @return {@link NormalizedNode}
483      */
484     private static @Nullable NormalizedNode<?, ?> readAllData(final @NonNull RestconfStrategy strategy,
485             final YangInstanceIdentifier path, final String withDefa, final EffectiveModelContext ctx) {
486         // PREPARE STATE DATA NODE
487         final NormalizedNode<?, ?> stateDataNode = readDataViaTransaction(
488                 strategy, LogicalDatastoreType.OPERATIONAL, path, false);
489
490         // PREPARE CONFIG DATA NODE
491         final NormalizedNode<?, ?> configDataNode;
492         //Here will be closed transactionChain if any
493         if (withDefa == null) {
494             configDataNode = readDataViaTransaction(
495                     strategy, LogicalDatastoreType.CONFIGURATION, path, true);
496         } else {
497             configDataNode = prepareDataByParamWithDef(
498                     readDataViaTransaction(strategy, LogicalDatastoreType.CONFIGURATION, path, true),
499                     path, withDefa, ctx);
500         }
501
502         return mergeConfigAndSTateDataIfNeeded(stateDataNode, configDataNode);
503     }
504
505     /**
506      * Read config and state data with selected subtrees that should only be read, then map them.
507      * Close {@link DOMTransactionChain} inside of object {@link RestconfStrategy} provided as a parameter.
508      *
509      * @param strategy {@link RestconfStrategy} - object that perform the actual DS operations
510      * @param path     parent path to selected fields
511      * @param withDefa with-defaults parameter
512      * @param ctx      schema context
513      * @param fields   paths to selected subtrees which should be read, relative to to the parent path
514      * @return {@link NormalizedNode}
515      */
516     private static @Nullable NormalizedNode<?, ?> readAllData(final @NonNull RestconfStrategy strategy,
517             final @NonNull YangInstanceIdentifier path, final @Nullable String withDefa,
518             final @NonNull EffectiveModelContext ctx, final @NonNull List<YangInstanceIdentifier> fields) {
519         // PREPARE STATE DATA NODE
520         final NormalizedNode<?, ?> stateDataNode = readDataViaTransaction(
521                 strategy, LogicalDatastoreType.OPERATIONAL, path, false, fields);
522
523         // PREPARE CONFIG DATA NODE
524         final NormalizedNode<?, ?> configDataNode;
525         //Here will be closed transactionChain if any
526         if (withDefa == null) {
527             configDataNode = readDataViaTransaction(strategy, LogicalDatastoreType.CONFIGURATION, path, true, fields);
528         } else {
529             configDataNode = prepareDataByParamWithDef(
530                     readDataViaTransaction(strategy, LogicalDatastoreType.CONFIGURATION, path, true, fields),
531                     path, withDefa, ctx);
532         }
533
534         return mergeConfigAndSTateDataIfNeeded(stateDataNode, configDataNode);
535     }
536
537     private static NormalizedNode<?, ?> mergeConfigAndSTateDataIfNeeded(final NormalizedNode<?, ?> stateDataNode,
538                                                                         final NormalizedNode<?, ?> configDataNode) {
539         // if no data exists
540         if (stateDataNode == null && configDataNode == null) {
541             return null;
542         }
543
544         // return config data
545         if (stateDataNode == null) {
546             return configDataNode;
547         }
548
549         // return state data
550         if (configDataNode == null) {
551             return stateDataNode;
552         }
553
554         // merge data from config and state
555         return mergeStateAndConfigData(stateDataNode, configDataNode);
556     }
557
558     /**
559      * Merge state and config data into a single NormalizedNode.
560      *
561      * @param stateDataNode  data node of state data
562      * @param configDataNode data node of config data
563      * @return {@link NormalizedNode}
564      */
565     private static @NonNull NormalizedNode<?, ?> mergeStateAndConfigData(
566             final @NonNull NormalizedNode<?, ?> stateDataNode, final @NonNull NormalizedNode<?, ?> configDataNode) {
567         validateNodeMerge(stateDataNode, configDataNode);
568         if (configDataNode instanceof RpcDefinition) {
569             return prepareRpcData(configDataNode, stateDataNode);
570         } else {
571             return prepareData(configDataNode, stateDataNode);
572         }
573     }
574
575     /**
576      * Validates whether the two NormalizedNodes can be merged.
577      *
578      * @param stateDataNode  data node of state data
579      * @param configDataNode data node of config data
580      */
581     private static void validateNodeMerge(final @NonNull NormalizedNode<?, ?> stateDataNode,
582                                           final @NonNull NormalizedNode<?, ?> configDataNode) {
583         final QNameModule moduleOfStateData = stateDataNode.getIdentifier().getNodeType().getModule();
584         final QNameModule moduleOfConfigData = configDataNode.getIdentifier().getNodeType().getModule();
585         if (!moduleOfStateData.equals(moduleOfConfigData)) {
586             throw new RestconfDocumentedException("Unable to merge data from different modules.");
587         }
588     }
589
590     /**
591      * Prepare and map data for rpc.
592      *
593      * @param configDataNode data node of config data
594      * @param stateDataNode  data node of state data
595      * @return {@link NormalizedNode}
596      */
597     private static @NonNull NormalizedNode<?, ?> prepareRpcData(final @NonNull NormalizedNode<?, ?> configDataNode,
598                                                                 final @NonNull NormalizedNode<?, ?> stateDataNode) {
599         final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> mapEntryBuilder = ImmutableNodes
600                 .mapEntryBuilder();
601         mapEntryBuilder.withNodeIdentifier((NodeIdentifierWithPredicates) configDataNode.getIdentifier());
602
603         // MAP CONFIG DATA
604         mapRpcDataNode(configDataNode, mapEntryBuilder);
605         // MAP STATE DATA
606         mapRpcDataNode(stateDataNode, mapEntryBuilder);
607
608         return ImmutableNodes.mapNodeBuilder(configDataNode.getNodeType()).addChild(mapEntryBuilder.build()).build();
609     }
610
611     /**
612      * Map node to map entry builder.
613      *
614      * @param dataNode        data node
615      * @param mapEntryBuilder builder for mapping data
616      */
617     private static void mapRpcDataNode(final @NonNull NormalizedNode<?, ?> dataNode,
618             final @NonNull DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> mapEntryBuilder) {
619         ((ContainerNode) dataNode).getValue().forEach(mapEntryBuilder::addChild);
620     }
621
622     /**
623      * Prepare and map all data from DS.
624      *
625      * @param configDataNode data node of config data
626      * @param stateDataNode  data node of state data
627      * @return {@link NormalizedNode}
628      */
629     @SuppressWarnings("unchecked")
630     private static @NonNull NormalizedNode<?, ?> prepareData(final @NonNull NormalizedNode<?, ?> configDataNode,
631                                                              final @NonNull NormalizedNode<?, ?> stateDataNode) {
632         if (configDataNode instanceof OrderedMapNode) {
633             final CollectionNodeBuilder<MapEntryNode, OrderedMapNode> builder = Builders
634                     .orderedMapBuilder().withNodeIdentifier(((MapNode) configDataNode).getIdentifier());
635
636             mapValueToBuilder(
637                     ((OrderedMapNode) configDataNode).getValue(), ((OrderedMapNode) stateDataNode).getValue(), builder);
638
639             return builder.build();
640         } else if (configDataNode instanceof MapNode) {
641             final CollectionNodeBuilder<MapEntryNode, MapNode> builder = ImmutableNodes
642                     .mapNodeBuilder().withNodeIdentifier(((MapNode) configDataNode).getIdentifier());
643
644             mapValueToBuilder(
645                     ((MapNode) configDataNode).getValue(), ((MapNode) stateDataNode).getValue(), builder);
646
647             return builder.build();
648         } else if (configDataNode instanceof MapEntryNode) {
649             final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> builder = ImmutableNodes
650                     .mapEntryBuilder().withNodeIdentifier(((MapEntryNode) configDataNode).getIdentifier());
651
652             mapValueToBuilder(
653                     ((MapEntryNode) configDataNode).getValue(), ((MapEntryNode) stateDataNode).getValue(), builder);
654
655             return builder.build();
656         } else if (configDataNode instanceof ContainerNode) {
657             final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> builder = Builders
658                     .containerBuilder().withNodeIdentifier(((ContainerNode) configDataNode).getIdentifier());
659
660             mapValueToBuilder(
661                     ((ContainerNode) configDataNode).getValue(), ((ContainerNode) stateDataNode).getValue(), builder);
662
663             return builder.build();
664         } else if (configDataNode instanceof AugmentationNode) {
665             final DataContainerNodeBuilder<AugmentationIdentifier, AugmentationNode> builder = Builders
666                     .augmentationBuilder().withNodeIdentifier(((AugmentationNode) configDataNode).getIdentifier());
667
668             mapValueToBuilder(((AugmentationNode) configDataNode).getValue(),
669                     ((AugmentationNode) stateDataNode).getValue(), builder);
670
671             return builder.build();
672         } else if (configDataNode instanceof ChoiceNode) {
673             final DataContainerNodeBuilder<NodeIdentifier, ChoiceNode> builder = Builders
674                     .choiceBuilder().withNodeIdentifier(((ChoiceNode) configDataNode).getIdentifier());
675
676             mapValueToBuilder(
677                     ((ChoiceNode) configDataNode).getValue(), ((ChoiceNode) stateDataNode).getValue(), builder);
678
679             return builder.build();
680         } else if (configDataNode instanceof LeafNode) {
681             return ImmutableNodes.leafNode(configDataNode.getNodeType(), configDataNode.getValue());
682         } else if (configDataNode instanceof OrderedLeafSetNode) {
683             final ListNodeBuilder<Object, LeafSetEntryNode<Object>> builder = Builders
684                 .orderedLeafSetBuilder().withNodeIdentifier(((OrderedLeafSetNode<?>) configDataNode).getIdentifier());
685
686             mapValueToBuilder(((OrderedLeafSetNode<Object>) configDataNode).getValue(),
687                     ((OrderedLeafSetNode<Object>) stateDataNode).getValue(), builder);
688             return builder.build();
689         } else if (configDataNode instanceof LeafSetNode) {
690             final ListNodeBuilder<Object, LeafSetEntryNode<Object>> builder = Builders
691                     .leafSetBuilder().withNodeIdentifier(((LeafSetNode<?>) configDataNode).getIdentifier());
692
693             mapValueToBuilder(((LeafSetNode<Object>) configDataNode).getValue(),
694                     ((LeafSetNode<Object>) stateDataNode).getValue(), builder);
695             return builder.build();
696         } else if (configDataNode instanceof LeafSetEntryNode) {
697             return Builders.leafSetEntryBuilder()
698                     .withNodeIdentifier(((LeafSetEntryNode<?>) configDataNode).getIdentifier())
699                     .withValue(configDataNode.getValue())
700                     .build();
701         } else if (configDataNode instanceof UnkeyedListNode) {
702             final CollectionNodeBuilder<UnkeyedListEntryNode, UnkeyedListNode> builder = Builders
703                     .unkeyedListBuilder().withNodeIdentifier(((UnkeyedListNode) configDataNode).getIdentifier());
704
705             mapValueToBuilder(((UnkeyedListNode) configDataNode).getValue(),
706                     ((UnkeyedListNode) stateDataNode).getValue(), builder);
707             return builder.build();
708         } else if (configDataNode instanceof UnkeyedListEntryNode) {
709             final DataContainerNodeBuilder<NodeIdentifier, UnkeyedListEntryNode> builder = Builders
710                 .unkeyedListEntryBuilder().withNodeIdentifier(((UnkeyedListEntryNode) configDataNode).getIdentifier());
711
712             mapValueToBuilder(((UnkeyedListEntryNode) configDataNode).getValue(),
713                     ((UnkeyedListEntryNode) stateDataNode).getValue(), builder);
714             return builder.build();
715         } else {
716             throw new RestconfDocumentedException("Unexpected node type: " + configDataNode.getClass().getName());
717         }
718     }
719
720     /**
721      * Map value from container node to builder.
722      *
723      * @param configData collection of config data nodes
724      * @param stateData  collection of state data nodes
725      * @param builder    builder
726      */
727     private static <T extends NormalizedNode<? extends PathArgument, ?>> void mapValueToBuilder(
728             final @NonNull Collection<T> configData, final @NonNull Collection<T> stateData,
729             final @NonNull NormalizedNodeContainerBuilder<?, PathArgument, T, ?> builder) {
730         final Map<PathArgument, T> configMap = configData.stream().collect(
731                 Collectors.toMap(NormalizedNode::getIdentifier, Function.identity()));
732         final Map<PathArgument, T> stateMap = stateData.stream().collect(
733                 Collectors.toMap(NormalizedNode::getIdentifier, Function.identity()));
734
735         // merge config and state data of children with different identifiers
736         mapDataToBuilder(configMap, stateMap, builder);
737
738         // merge config and state data of children with the same identifiers
739         mergeDataToBuilder(configMap, stateMap, builder);
740     }
741
742     /**
743      * Map data with different identifiers to builder. Data with different identifiers can be just added
744      * as childs to parent node.
745      *
746      * @param configMap map of config data nodes
747      * @param stateMap  map of state data nodes
748      * @param builder   - builder
749      */
750     private static <T extends NormalizedNode<? extends PathArgument, ?>> void mapDataToBuilder(
751             final @NonNull Map<PathArgument, T> configMap, final @NonNull Map<PathArgument, T> stateMap,
752             final @NonNull NormalizedNodeContainerBuilder<?, PathArgument, T, ?> builder) {
753         configMap.entrySet().stream().filter(x -> !stateMap.containsKey(x.getKey())).forEach(
754             y -> builder.addChild(y.getValue()));
755         stateMap.entrySet().stream().filter(x -> !configMap.containsKey(x.getKey())).forEach(
756             y -> builder.addChild(y.getValue()));
757     }
758
759     /**
760      * Map data with the same identifiers to builder. Data with the same identifiers cannot be just added but we need to
761      * go one level down with {@code prepareData} method.
762      *
763      * @param configMap immutable config data
764      * @param stateMap  immutable state data
765      * @param builder   - builder
766      */
767     @SuppressWarnings("unchecked")
768     private static <T extends NormalizedNode<? extends PathArgument, ?>> void mergeDataToBuilder(
769             final @NonNull Map<PathArgument, T> configMap, final @NonNull Map<PathArgument, T> stateMap,
770             final @NonNull NormalizedNodeContainerBuilder<?, PathArgument, T, ?> builder) {
771         // it is enough to process only config data because operational contains the same data
772         configMap.entrySet().stream().filter(x -> stateMap.containsKey(x.getKey())).forEach(
773             y -> builder.addChild((T) prepareData(y.getValue(), stateMap.get(y.getKey()))));
774     }
775 }