Bug 6903 - Implement Query parameters - fields
[netconf.git] / restconf / sal-rest-connector / src / main / java / org / opendaylight / restconf / restful / utils / ReadDataTransactionUtil.java
index 6dc8a9463622fcf36421aef22420e455bc186dd5..e18ea0961337881ae5a7b32d913df1b5c7954728 100644 (file)
@@ -8,34 +8,44 @@
 package org.opendaylight.restconf.restful.utils;
 
 import com.google.common.base.Optional;
-import com.google.common.base.Preconditions;
+import com.google.common.primitives.Ints;
 import com.google.common.util.concurrent.CheckedFuture;
 import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
 import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.ws.rs.core.UriInfo;
 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
 import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
+import org.opendaylight.netconf.sal.restconf.impl.InstanceIdentifierContext;
 import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
-import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorTag;
-import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorType;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfError;
+import org.opendaylight.netconf.sal.restconf.impl.WriterParameters;
+import org.opendaylight.netconf.sal.restconf.impl.WriterParameters.WriterParametersBuilder;
 import org.opendaylight.restconf.restful.transaction.TransactionVarsWrapper;
+import org.opendaylight.restconf.utils.parser.ParserFieldsParameter;
 import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
 import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode;
 import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode;
 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
-import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
-import org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode;
 import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
-import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.CollectionNodeBuilder;
 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeAttrBuilder;
 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.NormalizedNodeContainerBuilder;
 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
 
 /**
@@ -53,6 +63,87 @@ public final class ReadDataTransactionUtil {
         throw new UnsupportedOperationException("Util class.");
     }
 
+    /**
+     * Parse parameters from URI request and check their types and values.
+     *
+     *
+     * @param identifier
+     *            - {@link InstanceIdentifierContext}
+     * @param uriInfo
+     *            - URI info
+     * @return {@link WriterParameters}
+     */
+    public static @Nonnull WriterParameters parseUriParameters(@Nonnull final InstanceIdentifierContext<?> identifier,
+                                                               @Nullable final UriInfo uriInfo) {
+        final WriterParametersBuilder builder = new WriterParametersBuilder();
+
+        if (uriInfo == null) {
+            return builder.build();
+        }
+
+        // check only allowed parameters
+        ParametersUtil.checkParametersTypes(
+                RestconfDataServiceConstant.ReadData.READ_TYPE_TX,
+                uriInfo.getQueryParameters().keySet(),
+                RestconfDataServiceConstant.ReadData.CONTENT,
+                RestconfDataServiceConstant.ReadData.DEPTH,
+                RestconfDataServiceConstant.ReadData.FIELDS);
+
+        // read parameters from URI or set default values
+        final List<String> content = uriInfo.getQueryParameters().getOrDefault(
+                RestconfDataServiceConstant.ReadData.CONTENT,
+                Collections.singletonList(RestconfDataServiceConstant.ReadData.ALL));
+        final List<String> depth = uriInfo.getQueryParameters().getOrDefault(
+                RestconfDataServiceConstant.ReadData.DEPTH,
+                Collections.singletonList(RestconfDataServiceConstant.ReadData.UNBOUNDED));
+        // fields
+        final List<String> fields = uriInfo.getQueryParameters().getOrDefault(
+                RestconfDataServiceConstant.ReadData.FIELDS,
+                Collections.emptyList());
+
+        // parameter can be in URI at most once
+        ParametersUtil.checkParameterCount(content, RestconfDataServiceConstant.ReadData.CONTENT);
+        ParametersUtil.checkParameterCount(depth, RestconfDataServiceConstant.ReadData.DEPTH);
+        ParametersUtil.checkParameterCount(fields, RestconfDataServiceConstant.ReadData.FIELDS);
+
+        // check and set content
+        final String contentValue = content.get(0);
+        if (!contentValue.equals(RestconfDataServiceConstant.ReadData.ALL)) {
+            if (!contentValue.equals(RestconfDataServiceConstant.ReadData.CONFIG)
+                    && !contentValue.equals(RestconfDataServiceConstant.ReadData.NONCONFIG)) {
+                throw new RestconfDocumentedException(
+                        new RestconfError(RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.INVALID_VALUE,
+                                "Invalid content parameter: " + contentValue, null,
+                                "The content parameter value must be either config, nonconfig or all (default)"));
+            }
+        }
+
+        builder.setContent(content.get(0));
+
+        // check and set depth
+        if (!depth.get(0).equals(RestconfDataServiceConstant.ReadData.UNBOUNDED)) {
+            final Integer value = Ints.tryParse(depth.get(0));
+
+            if (value == null
+                    || (!(value >= RestconfDataServiceConstant.ReadData.MIN_DEPTH
+                        && value <= RestconfDataServiceConstant.ReadData.MAX_DEPTH))) {
+                throw new RestconfDocumentedException(
+                        new RestconfError(RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.INVALID_VALUE,
+                                "Invalid depth parameter: " + depth, null,
+                                "The depth parameter must be an integer between 1 and 65535 or \"unbounded\""));
+            } else {
+                builder.setDepth(value);
+            }
+        }
+
+        // check and set fields
+        if (!fields.isEmpty()) {
+            builder.setFields(ParserFieldsParameter.parseFieldsParameter(identifier, fields.get(0)));
+        }
+
+        return builder.build();
+    }
+
     /**
      * Read specific type of data from data store via transaction.
      *
@@ -62,30 +153,26 @@ public final class ReadDataTransactionUtil {
      *            - {@link TransactionVarsWrapper} - wrapper for variables
      * @return {@link NormalizedNode}
      */
-    public static NormalizedNode<?, ?> readData(final String valueOfContent, final TransactionVarsWrapper transactionNode) {
-        final NormalizedNode<?, ?> data;
-        if (valueOfContent != null) {
-            switch (valueOfContent) {
-                case RestconfDataServiceConstant.ReadData.CONFIG:
-                    transactionNode.setLogicalDatastoreType(LogicalDatastoreType.CONFIGURATION);
-                    data = readDataViaTransaction(transactionNode);
-                    break;
-                case RestconfDataServiceConstant.ReadData.NONCONFIG:
-                    transactionNode.setLogicalDatastoreType(LogicalDatastoreType.OPERATIONAL);
-                    data = readDataViaTransaction(transactionNode);
-                    break;
-                case RestconfDataServiceConstant.ReadData.ALL:
-                    data = readAllData(transactionNode);
-                    break;
-                default:
-                    throw new RestconfDocumentedException("Bad querry parameter for content.", ErrorType.APPLICATION,
-                            ErrorTag.INVALID_VALUE);
-            }
-        } else {
-            data = readAllData(transactionNode);
-        }
+    public static @Nullable NormalizedNode<?, ?> readData(@Nonnull final String valueOfContent,
+                                                          @Nonnull final TransactionVarsWrapper transactionNode) {
+        switch (valueOfContent) {
+            case RestconfDataServiceConstant.ReadData.CONFIG:
+                transactionNode.setLogicalDatastoreType(LogicalDatastoreType.CONFIGURATION);
+                return readDataViaTransaction(transactionNode);
 
-        return data;
+            case RestconfDataServiceConstant.ReadData.NONCONFIG:
+                transactionNode.setLogicalDatastoreType(LogicalDatastoreType.OPERATIONAL);
+                return readDataViaTransaction(transactionNode);
+
+            case RestconfDataServiceConstant.ReadData.ALL:
+                return readAllData(transactionNode);
+
+            default:
+                throw new RestconfDocumentedException(
+                        new RestconfError(RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.INVALID_VALUE,
+                                "Invalid content parameter: " + valueOfContent, null,
+                                "The content parameter value must be either config, nonconfig or all (default)"));
+        }
     }
 
     /**
@@ -97,14 +184,15 @@ public final class ReadDataTransactionUtil {
      *            - {@link TransactionVarsWrapper} - wrapper for variables
      * @return {@link NormalizedNode}
      */
-    private static NormalizedNode<?, ?> readDataViaTransaction(final TransactionVarsWrapper transactionNode) {
-            final CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> listenableFuture = transactionNode
-                    .getTransactionChain().newReadOnlyTransaction().read(transactionNode.getLogicalDatastoreType(),
-                            transactionNode.getInstanceIdentifier().getInstanceIdentifier());
-            final NormalizedNodeFactory dataFactory = new NormalizedNodeFactory();
-            FutureCallbackTx.addCallback(listenableFuture, RestconfDataServiceConstant.ReadData.READ_TYPE_TX,
-                    dataFactory);
-            return dataFactory.build();
+    private static @Nullable NormalizedNode<?, ?> readDataViaTransaction(
+            @Nonnull final TransactionVarsWrapper transactionNode) {
+        final CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> listenableFuture = transactionNode
+                .getTransactionChain().newReadOnlyTransaction().read(transactionNode.getLogicalDatastoreType(),
+                        transactionNode.getInstanceIdentifier().getInstanceIdentifier());
+        final NormalizedNodeFactory dataFactory = new NormalizedNodeFactory();
+        FutureCallbackTx.addCallback(listenableFuture, RestconfDataServiceConstant.ReadData.READ_TYPE_TX,
+                dataFactory);
+        return dataFactory.build();
     }
 
     /**
@@ -114,7 +202,7 @@ public final class ReadDataTransactionUtil {
      *            - {@link TransactionVarsWrapper} - wrapper for variables
      * @return {@link NormalizedNode}
      */
-    private static NormalizedNode<?, ?> readAllData(final TransactionVarsWrapper transactionNode) {
+    private static @Nullable NormalizedNode<?, ?> readAllData(@Nonnull final TransactionVarsWrapper transactionNode) {
         // PREPARE STATE DATA NODE
         transactionNode.setLogicalDatastoreType(LogicalDatastoreType.OPERATIONAL);
         final NormalizedNode<?, ?> stateDataNode = readDataViaTransaction(transactionNode);
@@ -151,8 +239,8 @@ public final class ReadDataTransactionUtil {
      *            - data node of config data
      * @return {@link NormalizedNode}
      */
-    private static NormalizedNode<?, ?> mapNode(final NormalizedNode<?, ?> stateDataNode,
-            final NormalizedNode<?, ?> configDataNode) {
+    private static @Nonnull NormalizedNode<?, ?> mapNode(@Nonnull final NormalizedNode<?, ?> stateDataNode,
+                                                         @Nonnull final NormalizedNode<?, ?> configDataNode) {
         validPossibilityOfMergeNodes(stateDataNode, configDataNode);
         if (configDataNode instanceof RpcDefinition) {
             return prepareRpcData(configDataNode, stateDataNode);
@@ -161,6 +249,23 @@ public final class ReadDataTransactionUtil {
         }
     }
 
+    /**
+     * Valid of can be data merged together.
+     *
+     * @param stateDataNode
+     *            - data node of state data
+     * @param configDataNode
+     *            - data node of config data
+     */
+    private static void validPossibilityOfMergeNodes(@Nonnull final NormalizedNode<?, ?> stateDataNode,
+                                                     @Nonnull final NormalizedNode<?, ?> configDataNode) {
+        final QNameModule moduleOfStateData = stateDataNode.getIdentifier().getNodeType().getModule();
+        final QNameModule moduleOfConfigData = configDataNode.getIdentifier().getNodeType().getModule();
+        if (moduleOfStateData != moduleOfConfigData) {
+            throw new RestconfDocumentedException("It is not possible to merge ");
+        }
+    }
+
     /**
      * Prepare and map data for rpc
      *
@@ -170,8 +275,8 @@ public final class ReadDataTransactionUtil {
      *            - data node of state data
      * @return {@link NormalizedNode}
      */
-    private static NormalizedNode<?, ?> prepareRpcData(final NormalizedNode<?, ?> configDataNode,
-            final NormalizedNode<?, ?> stateDataNode) {
+    private static @Nonnull NormalizedNode<?, ?> prepareRpcData(@Nonnull final NormalizedNode<?, ?> configDataNode,
+                                                                @Nonnull final NormalizedNode<?, ?> stateDataNode) {
         final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> mapEntryBuilder = ImmutableNodes
                 .mapEntryBuilder();
         mapEntryBuilder.withNodeIdentifier((NodeIdentifierWithPredicates) configDataNode.getIdentifier());
@@ -192,11 +297,10 @@ public final class ReadDataTransactionUtil {
      * @param mapEntryBuilder
      *            - builder for mapping data
      */
-    private static void mapRpcDataNode(final NormalizedNode<?, ?> dataNode,
-            final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> mapEntryBuilder) {
-        for (final DataContainerChild<? extends PathArgument, ?> child : ((ContainerNode) dataNode).getValue()) {
-            mapEntryBuilder.addChild(child);
-        }
+    private static void mapRpcDataNode(@Nonnull final NormalizedNode<?, ?> dataNode,
+                                       @Nonnull final DataContainerNodeBuilder<
+                                               NodeIdentifierWithPredicates, MapEntryNode> mapEntryBuilder) {
+        ((ContainerNode) dataNode).getValue().forEach(mapEntryBuilder::addChild);
     }
 
     /**
@@ -208,116 +312,120 @@ public final class ReadDataTransactionUtil {
      *            - data node of state data
      * @return {@link NormalizedNode}
      */
-    private static NormalizedNode<?, ?> prepareData(final NormalizedNode<?, ?> configDataNode,
-            final NormalizedNode<?, ?> stateDataNode) {
+    private static @Nonnull NormalizedNode<?, ?> prepareData(@Nonnull final NormalizedNode<?, ?> configDataNode,
+                                                             @Nonnull final NormalizedNode<?, ?> stateDataNode) {
+        if (configDataNode instanceof MapNode) {
+            final CollectionNodeBuilder<MapEntryNode, MapNode> builder = ImmutableNodes
+                    .mapNodeBuilder().withNodeIdentifier(((MapNode) configDataNode).getIdentifier());
 
-        if (configDataNode instanceof MapNode) { // part for lists mapping
-            final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> mapEntryBuilder = ImmutableNodes
-                .mapEntryBuilder();
-            final NodeIdentifierWithPredicates node = ((MapNode) configDataNode).getValue().iterator().next().getIdentifier();
-            mapEntryBuilder.withNodeIdentifier(node);
-
-            // MAP CONFIG DATA
-            mapDataNode((MapNode) configDataNode, mapEntryBuilder);
-            // MAP STATE DATA
-            mapDataNode((MapNode) stateDataNode, mapEntryBuilder);
-            return ImmutableNodes.mapNodeBuilder(configDataNode.getNodeType()).addChild(mapEntryBuilder.build()).build();
-        } else if (configDataNode instanceof ContainerNode) { // part for containers mapping
-            final DataContainerNodeAttrBuilder<NodeIdentifier, ContainerNode> containerBuilder = Builders
-                    .containerBuilder((ContainerNode) configDataNode);
-
-            // MAP CONFIG DATA
-            mapCont(containerBuilder, ((ContainerNode) configDataNode).getValue());
-            // MAP STATE DATA
-            mapCont(containerBuilder, ((ContainerNode) stateDataNode).getValue());
-            return containerBuilder.build();
+            mapValueToBuilder(
+                    ((MapNode) configDataNode).getValue(), ((MapNode) stateDataNode).getValue(), builder);
+
+            return builder.build();
+        } else if (configDataNode instanceof MapEntryNode) {
+            final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> builder = ImmutableNodes
+                    .mapEntryBuilder().withNodeIdentifier(((MapEntryNode) configDataNode).getIdentifier());
+
+            mapValueToBuilder(
+                    ((MapEntryNode) configDataNode).getValue(), ((MapEntryNode) stateDataNode).getValue(), builder);
+
+            return builder.build();
+        } else if (configDataNode instanceof ContainerNode) {
+            final DataContainerNodeAttrBuilder<NodeIdentifier, ContainerNode> builder = Builders
+                    .containerBuilder().withNodeIdentifier(((ContainerNode) configDataNode).getIdentifier());
+
+            mapValueToBuilder(
+                    ((ContainerNode) configDataNode).getValue(), ((ContainerNode) stateDataNode).getValue(), builder);
+
+            return builder.build();
+        } else if (configDataNode instanceof AugmentationNode) {
+            final DataContainerNodeBuilder<AugmentationIdentifier, AugmentationNode> builder = Builders
+                    .augmentationBuilder().withNodeIdentifier(((AugmentationNode) configDataNode).getIdentifier());
+
+            mapValueToBuilder(
+                    ((AugmentationNode) configDataNode).getValue(), ((AugmentationNode) stateDataNode).getValue(), builder);
+
+            return builder.build();
+        } else if (configDataNode instanceof ChoiceNode) {
+            final DataContainerNodeBuilder<NodeIdentifier, ChoiceNode> builder = Builders
+                    .choiceBuilder().withNodeIdentifier(((ChoiceNode) configDataNode).getIdentifier());
+
+            mapValueToBuilder(
+                    ((ChoiceNode) configDataNode).getValue(), ((ChoiceNode) stateDataNode).getValue(), builder);
+
+            return builder.build();
+        } else if (configDataNode instanceof LeafNode) {
+            return ImmutableNodes.leafNode(configDataNode.getNodeType(), configDataNode.getValue());
         } else {
             throw new RestconfDocumentedException("Bad type of node.");
         }
     }
 
     /**
-     * Map data to builder
+     * Map value from container node to builder.
      *
-     * @param containerBuilder
-     *            - builder for mapping data
-     * @param childs
-     *            - childs of data (container)
+     * @param configData
+     *            - collection of config data nodes
+     * @param stateData
+     *            - collection of state data nodes
+     * @param builder
+     *            - builder
      */
-    private static void mapCont(final DataContainerNodeAttrBuilder<NodeIdentifier, ContainerNode> containerBuilder,
-            final Collection<DataContainerChild<? extends PathArgument, ?>> childs) {
-        for (final DataContainerChild<? extends PathArgument, ?> child : childs) {
-            containerBuilder.addChild(child);
-        }
-    }
+    private static <T extends NormalizedNode<? extends PathArgument, ?>> void mapValueToBuilder(
+            @Nonnull final Collection<T> configData,
+            @Nonnull final Collection<T> stateData,
+            @Nonnull final NormalizedNodeContainerBuilder<?, PathArgument, T, ?> builder) {
+        final Map<PathArgument, T> configMap = configData.stream().collect(
+                Collectors.toMap(NormalizedNode::getIdentifier, Function.identity()));
+        final Map<PathArgument, T> stateMap = stateData.stream().collect(
+                Collectors.toMap(NormalizedNode::getIdentifier, Function.identity()));
 
-    /**
-     * Map data to builder
-     *
-     * @param immutableData
-     *            - immutable data - {@link MapNode}
-     * @param mapEntryBuilder
-     *            - builder for mapping data
-     */
-    private static void mapDataNode(final MapNode immutableData,
-            final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> mapEntryBuilder) {
-        for (final DataContainerChild<? extends PathArgument, ?> child : immutableData.getValue().iterator()
-                .next().getValue()) {
-            Preconditions.checkNotNull(child);
-            if (child instanceof ContainerNode) {
-                addChildToMap(ContainerNode.class, child, mapEntryBuilder);
-            } else if (child instanceof AugmentationNode) {
-                addChildToMap(AugmentationNode.class, child, mapEntryBuilder);
-            } else if(child instanceof MapNode){
-                final MapNode listNode = (MapNode) child;
-                for (final MapEntryNode listChild : listNode.getValue()) {
-                    for (final DataContainerChild<? extends PathArgument, ?> entryChild : listChild.getValue()) {
-                        addChildToMap(MapEntryNode.class, entryChild, mapEntryBuilder);
-                    }
-                }
-            } else if (child instanceof ChoiceNode) {
-                addChildToMap(ChoiceNode.class, child, mapEntryBuilder);
-            } else if ((child instanceof LeafSetNode<?>) || (child instanceof LeafNode)) {
-                mapEntryBuilder.addChild(child);
-            }
+        // merge config and state data of children with different identifiers
+        mapDataToBuilder(configMap, stateMap, builder);
 
-        }
+        // merge config and state data of children with the same identifiers
+        mergeDataToBuilder(configMap, stateMap, builder);
     }
 
     /**
-     * Mapping child
+     * Map data with different identifiers to builder. Data with different identifiers can be just added
+     * as childs to parent node.
      *
-     * @param type
-     *            - type of data
-     * @param child
-     *            - child to map
-     * @param mapEntryBuilder
-     *            - builder for mapping child
+     * @param configMap
+     *            - map of config data nodes
+     * @param stateMap
+     *            - map of state data nodes
+     * @param builder
+     *           - builder
      */
-    private static <T extends DataContainerNode<? extends PathArgument>> void addChildToMap(final Class<T> type,
-            final DataContainerChild<? extends PathArgument, ?> child,
-            final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> mapEntryBuilder) {
-        @SuppressWarnings("unchecked")
-        final T node = (T) child;
-        for (final DataContainerChild<? extends PathArgument, ?> childNode : node.getValue()) {
-            mapEntryBuilder.addChild(childNode);
-        }
+    private static <T extends NormalizedNode<? extends PathArgument, ?>> void mapDataToBuilder(
+            @Nonnull final Map<PathArgument, T> configMap,
+            @Nonnull final Map<PathArgument, T> stateMap,
+            @Nonnull final NormalizedNodeContainerBuilder<?, PathArgument, T, ?> builder) {
+        configMap.entrySet().stream().filter(x -> !stateMap.containsKey(x.getKey())).forEach(
+                y -> builder.addChild(y.getValue()));
+        stateMap.entrySet().stream().filter(x -> !configMap.containsKey(x.getKey())).forEach(
+                y -> builder.addChild(y.getValue()));
     }
 
     /**
-     * Valid of can be data merged together.
+     * Map data with the same identifiers to builder. Data with the same identifiers cannot be just added but we need to
+     * go one level down with {@code prepareData} method.
      *
-     * @param stateDataNode
-     *            - data node of state data
-     * @param configDataNode
-     *            - data node of config data
+     * @param configMap
+     *            - immutable config data
+     * @param stateMap
+     *            - immutable state data
+     * @param builder
+     *           - builder
      */
-    private static void validPossibilityOfMergeNodes(@Nonnull final NormalizedNode<?, ?> stateDataNode,
-            @Nonnull final NormalizedNode<?, ?> configDataNode) {
-        final QNameModule moduleOfStateData = stateDataNode.getIdentifier().getNodeType().getModule();
-        final QNameModule moduleOfConfigData = configDataNode.getIdentifier().getNodeType().getModule();
-        if (moduleOfStateData != moduleOfConfigData) {
-            throw new RestconfDocumentedException("It is not possible to merge ");
-        }
+    @SuppressWarnings("unchecked")
+    private static <T extends NormalizedNode<? extends PathArgument, ?>> void mergeDataToBuilder(
+            @Nonnull final Map<PathArgument, T> configMap,
+            @Nonnull final Map<PathArgument, T> stateMap,
+            @Nonnull final NormalizedNodeContainerBuilder<?, PathArgument, T, ?> builder) {
+        // it is enough to process only config data because operational contains the same data
+        configMap.entrySet().stream().filter(x -> stateMap.containsKey(x.getKey())).forEach(
+                y -> builder.addChild((T) prepareData(y.getValue(), stateMap.get(y.getKey()))));
     }
 }