Eliminate (ReadData)TransactionUtil 32/107832/3
authorRobert Varga <robert.varga@pantheon.tech>
Tue, 12 Sep 2023 11:30:22 +0000 (13:30 +0200)
committerRobert Varga <robert.varga@pantheon.tech>
Tue, 12 Sep 2023 12:34:40 +0000 (14:34 +0200)
Integrate these utilities directly into RestconfStrategy.

JIRA: NETCONF-1107
Change-Id: Ic0ff3bcc2a7df03c7aa9faf0ac6e94c5b7ef7e96
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/services/impl/RestconfDataServiceImpl.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/transactions/MdsalRestconfTransaction.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/transactions/NetconfRestconfStrategy.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/transactions/RestconfStrategy.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/transactions/RestconfTransaction.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/transactions/TransactionUtil.java [moved from restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/TransactionUtil.java with 70% similarity]
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/ReadDataTransactionUtil.java [deleted file]
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/rests/transactions/AbstractRestconfStrategyTest.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/rests/transactions/MdsalRestconfStrategyTest.java

index 382b2b61541ef5f1ce920d0669bb1685bc60f8e5..e7fc9c0541400942fb7773f204a5717cf1c415d8 100644 (file)
@@ -83,7 +83,6 @@ import org.opendaylight.restconf.nb.rfc8040.monitoring.RestconfStateStreams;
 import org.opendaylight.restconf.nb.rfc8040.rests.services.api.RestconfStreamsSubscriptionService;
 import org.opendaylight.restconf.nb.rfc8040.rests.transactions.MdsalRestconfStrategy;
 import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfStrategy;
-import org.opendaylight.restconf.nb.rfc8040.rests.utils.ReadDataTransactionUtil;
 import org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfStreamsConstants;
 import org.opendaylight.restconf.nb.rfc8040.streams.StreamsConfiguration;
 import org.opendaylight.restconf.nb.rfc8040.streams.listeners.ListenersBroker;
@@ -198,11 +197,11 @@ public final class RestconfDataServiceImpl {
         final RestconfStrategy strategy = getRestconfStrategy(mountPoint);
         final NormalizedNode node;
         if (fieldPaths != null && !fieldPaths.isEmpty()) {
-            node = ReadDataTransactionUtil.readData(readParams.content(), instanceIdentifier.getInstanceIdentifier(),
-                    strategy, readParams.withDefaults(), schemaContextRef, fieldPaths);
+            node = strategy.readData(readParams.content(), instanceIdentifier.getInstanceIdentifier(),
+                readParams.withDefaults(), schemaContextRef, fieldPaths);
         } else {
-            node = ReadDataTransactionUtil.readData(readParams.content(), instanceIdentifier.getInstanceIdentifier(),
-                    strategy, readParams.withDefaults(), schemaContextRef);
+            node = strategy.readData(readParams.content(), instanceIdentifier.getInstanceIdentifier(),
+                readParams.withDefaults(), schemaContextRef);
         }
 
         // FIXME: this is utter craziness, refactor it properly!
index f014032e970f832ebb032d08550583554033b3ff..78961c8f6c07670847827552b5ce17d5a84b53e8 100644 (file)
@@ -19,7 +19,6 @@ import org.opendaylight.mdsal.common.api.ReadFailedException;
 import org.opendaylight.mdsal.dom.api.DOMDataBroker;
 import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction;
 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
-import org.opendaylight.restconf.nb.rfc8040.rests.utils.TransactionUtil;
 import org.opendaylight.yangtools.yang.common.ErrorTag;
 import org.opendaylight.yangtools.yang.common.ErrorType;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
@@ -76,7 +75,7 @@ final class MdsalRestconfTransaction extends RestconfTransaction {
         if (data instanceof MapNode || data instanceof LeafSetNode) {
             final var emptySubTree = ImmutableNodes.fromInstanceId(context, path);
             merge(YangInstanceIdentifier.of(emptySubTree.name()), emptySubTree);
-            TransactionUtil.ensureParentsByMerge(path, context, this);
+            ensureParentsByMerge(path, context);
 
             final var children = ((DistinctNodeContainer<?, ?>) data).body();
             final var check = BatchedExistenceCheck.start(verifyNotNull(rwTx), CONFIGURATION, path, children);
@@ -89,7 +88,7 @@ final class MdsalRestconfTransaction extends RestconfTransaction {
             checkExistence(path, check);
         } else {
             RestconfStrategy.checkItemDoesNotExists(verifyNotNull(rwTx).exists(CONFIGURATION, path), path);
-            TransactionUtil.ensureParentsByMerge(path, context, this);
+            ensureParentsByMerge(path, context);
             verifyNotNull(rwTx).put(CONFIGURATION, path, data);
         }
     }
@@ -100,14 +99,14 @@ final class MdsalRestconfTransaction extends RestconfTransaction {
         if (data instanceof MapNode || data instanceof LeafSetNode) {
             final var emptySubtree = ImmutableNodes.fromInstanceId(context, path);
             merge(YangInstanceIdentifier.of(emptySubtree.name()), emptySubtree);
-            TransactionUtil.ensureParentsByMerge(path, context, this);
+            ensureParentsByMerge(path, context);
 
             for (var child : ((NormalizedNodeContainer<?>) data).body()) {
                 final var childPath = path.node(child.name());
                 verifyNotNull(rwTx).put(CONFIGURATION, childPath, child);
             }
         } else {
-            TransactionUtil.ensureParentsByMerge(path, context, this);
+            ensureParentsByMerge(path, context);
             verifyNotNull(rwTx).put(CONFIGURATION, path, data);
         }
     }
index be91400b25c83b8f9b9a234d47bb9322ee318462..fa9bf1b610a3fc218c9d7aadab32ec2d86980795 100644 (file)
@@ -21,7 +21,6 @@ import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
 import org.opendaylight.mdsal.common.api.ReadFailedException;
 import org.opendaylight.netconf.dom.api.NetconfDataTreeService;
 import org.opendaylight.restconf.common.errors.SettableRestconfFuture;
-import org.opendaylight.restconf.nb.rfc8040.rests.utils.TransactionUtil;
 import org.opendaylight.yangtools.yang.common.Empty;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
index 7a29ecd8067e2fb2b1bda0b5b86a94c842480ae2..781855a17120625c3a618024f3db97e2afb1808f 100644 (file)
@@ -15,16 +15,24 @@ import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.MoreExecutors;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
 import java.util.Optional;
+import java.util.function.Function;
+import java.util.stream.Collectors;
 import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
 import org.opendaylight.mdsal.common.api.CommitInfo;
 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
 import org.opendaylight.mdsal.dom.api.DOMDataBroker;
 import org.opendaylight.mdsal.dom.api.DOMMountPoint;
+import org.opendaylight.mdsal.dom.api.DOMTransactionChain;
 import org.opendaylight.netconf.dom.api.NetconfDataTreeService;
+import org.opendaylight.restconf.api.query.ContentParam;
 import org.opendaylight.restconf.api.query.PointParam;
+import org.opendaylight.restconf.api.query.WithDefaultsParam;
 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
 import org.opendaylight.restconf.common.errors.RestconfError;
 import org.opendaylight.restconf.common.errors.RestconfFuture;
@@ -33,20 +41,45 @@ import org.opendaylight.restconf.common.patch.PatchContext;
 import org.opendaylight.restconf.common.patch.PatchStatusContext;
 import org.opendaylight.restconf.common.patch.PatchStatusEntity;
 import org.opendaylight.restconf.nb.rfc8040.Insert;
-import org.opendaylight.restconf.nb.rfc8040.rests.utils.TransactionUtil;
 import org.opendaylight.restconf.nb.rfc8040.utils.parser.YangInstanceIdentifierDeserializer;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.with.defaults.rev110601.WithDefaultsMode;
 import org.opendaylight.yangtools.yang.common.Empty;
 import org.opendaylight.yangtools.yang.common.ErrorTag;
 import org.opendaylight.yangtools.yang.common.ErrorType;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.QNameModule;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+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.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.LeafNode;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
+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.api.schema.NormalizedNodeContainer;
+import org.opendaylight.yangtools.yang.data.api.schema.SystemLeafSetNode;
+import org.opendaylight.yangtools.yang.data.api.schema.SystemMapNode;
+import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListNode;
+import org.opendaylight.yangtools.yang.data.api.schema.UserLeafSetNode;
+import org.opendaylight.yangtools.yang.data.api.schema.UserMapNode;
+import org.opendaylight.yangtools.yang.data.api.schema.builder.CollectionNodeBuilder;
+import org.opendaylight.yangtools.yang.data.api.schema.builder.DataContainerNodeBuilder;
+import org.opendaylight.yangtools.yang.data.api.schema.builder.NormalizedNodeContainerBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
+import org.opendaylight.yangtools.yang.data.util.DataSchemaContext;
 import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -175,7 +208,7 @@ public abstract class RestconfStrategy {
             final @NonNull EffectiveModelContext context) {
         final var tx = prepareWriteExecution();
         // FIXME: this method should be further specialized to eliminate this call -- it is only needed for MD-SAL
-        TransactionUtil.ensureParentsByMerge(path, context, tx);
+        tx.ensureParentsByMerge(path, context);
         tx.merge(path, data);
         Futures.addCallback(tx.commit(), new FutureCallback<CommitInfo>() {
             @Override
@@ -412,7 +445,7 @@ public abstract class RestconfStrategy {
                         break;
                     case Merge:
                         try {
-                            TransactionUtil.ensureParentsByMerge(targetNode, context, tx);
+                            tx.ensureParentsByMerge(targetNode, context);
                             tx.merge(targetNode, patchEntity.getNode());
                             editCollection.add(new PatchStatusEntity(editId, true, null));
                         } catch (RestconfDocumentedException e) {
@@ -523,4 +556,447 @@ public abstract class RestconfStrategy {
                 path);
         }
     }
+
+    /**
+     * Read specific type of data from data store via transaction. Close {@link DOMTransactionChain} if any
+     * inside of object {@link RestconfStrategy} provided as a parameter.
+     *
+     * @param content        type of data to read (config, state, all)
+     * @param path           the path to read
+     * @param defaultsMode   value of with-defaults parameter
+     * @param ctx            schema context
+     * @return {@link NormalizedNode}
+     */
+    public @Nullable NormalizedNode readData(final @NonNull ContentParam content,
+            final @NonNull YangInstanceIdentifier path, final WithDefaultsParam defaultsMode,
+            final EffectiveModelContext ctx) {
+        return switch (content) {
+            case ALL -> {
+                // PREPARE STATE DATA NODE
+                final var stateDataNode = readDataViaTransaction(LogicalDatastoreType.OPERATIONAL, path);
+                // PREPARE CONFIG DATA NODE
+                final var configDataNode = readDataViaTransaction(LogicalDatastoreType.CONFIGURATION, path);
+
+                yield mergeConfigAndSTateDataIfNeeded(stateDataNode, defaultsMode == null ? configDataNode
+                    : prepareDataByParamWithDef(configDataNode, path, defaultsMode.mode(), ctx));
+            }
+            case CONFIG -> {
+                final var read = readDataViaTransaction(LogicalDatastoreType.CONFIGURATION, path);
+                yield defaultsMode == null ? read : prepareDataByParamWithDef(read, path, defaultsMode.mode(), ctx);
+            }
+            case NONCONFIG -> readDataViaTransaction(LogicalDatastoreType.OPERATIONAL, path);
+        };
+    }
+
+    /**
+     * Read specific type of data from data store via transaction with specified subtrees that should only be read.
+     * Close {@link DOMTransactionChain} inside of object {@link RestconfStrategy} provided as a parameter.
+     *
+     * @param content        type of data to read (config, state, all)
+     * @param path           the parent path to read
+     * @param withDefa       value of with-defaults parameter
+     * @param ctx            schema context
+     * @param fields         paths to selected subtrees which should be read, relative to to the parent path
+     * @return {@link NormalizedNode}
+     */
+    public @Nullable NormalizedNode readData(final @NonNull ContentParam content,
+            final @NonNull YangInstanceIdentifier path, final @Nullable WithDefaultsParam withDefa,
+            final @NonNull EffectiveModelContext ctx, final @NonNull List<YangInstanceIdentifier> fields) {
+        return switch (content) {
+            case ALL -> {
+                // PREPARE STATE DATA NODE
+                final var stateDataNode = readDataViaTransaction(LogicalDatastoreType.OPERATIONAL, path, fields);
+                // PREPARE CONFIG DATA NODE
+                final var configDataNode = readDataViaTransaction(LogicalDatastoreType.CONFIGURATION, path, fields);
+
+                yield mergeConfigAndSTateDataIfNeeded(stateDataNode, withDefa == null ? configDataNode
+                    : prepareDataByParamWithDef(configDataNode, path, withDefa.mode(), ctx));
+            }
+            case CONFIG -> {
+                final var read = readDataViaTransaction(LogicalDatastoreType.CONFIGURATION, path, fields);
+                yield withDefa == null ? read : prepareDataByParamWithDef(read, path, withDefa.mode(), ctx);
+            }
+            case NONCONFIG -> readDataViaTransaction(LogicalDatastoreType.OPERATIONAL, path, fields);
+        };
+    }
+
+    private static NormalizedNode prepareDataByParamWithDef(final NormalizedNode readData,
+            final YangInstanceIdentifier path, final WithDefaultsMode defaultsMode, final EffectiveModelContext ctx) {
+        final boolean trim = switch (defaultsMode) {
+            case Trim -> true;
+            case Explicit -> false;
+            case ReportAll, ReportAllTagged -> throw new RestconfDocumentedException(
+                "Unsupported with-defaults value " + defaultsMode.getName());
+        };
+
+        final var ctxNode = DataSchemaContextTree.from(ctx).findChild(path).orElseThrow();
+        if (readData instanceof ContainerNode container) {
+            final var builder = Builders.containerBuilder().withNodeIdentifier(container.name());
+            buildCont(builder, container.body(), ctxNode, trim);
+            return builder.build();
+        } else if (readData instanceof MapEntryNode mapEntry) {
+            if (!(ctxNode.dataSchemaNode() instanceof ListSchemaNode listSchema)) {
+                throw new IllegalStateException("Input " + mapEntry + " does not match " + ctxNode);
+            }
+
+            final var builder = Builders.mapEntryBuilder().withNodeIdentifier(mapEntry.name());
+            buildMapEntryBuilder(builder, mapEntry.body(), ctxNode, trim, listSchema.getKeyDefinition());
+            return builder.build();
+        } else {
+            throw new IllegalStateException("Unhandled data contract " + readData.contract());
+        }
+    }
+
+    private static void buildMapEntryBuilder(
+            final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> builder,
+            final Collection<@NonNull DataContainerChild> children, final DataSchemaContext ctxNode,
+            final boolean trim, final List<QName> keys) {
+        for (var child : children) {
+            final var childCtx = getChildContext(ctxNode, child);
+
+            if (child instanceof ContainerNode container) {
+                appendContainer(builder, container, childCtx, trim);
+            } else if (child instanceof MapNode map) {
+                appendMap(builder, map, childCtx, trim);
+            } else if (child instanceof LeafNode<?> leaf) {
+                appendLeaf(builder, leaf, childCtx, trim, keys);
+            } else {
+                // FIXME: we should never hit this, throw an ISE if this ever happens
+                LOG.debug("Ignoring unhandled child contract {}", child.contract());
+            }
+        }
+    }
+
+    private static void appendContainer(final DataContainerNodeBuilder<?, ?> builder, final ContainerNode container,
+            final DataSchemaContext ctxNode, final boolean trim) {
+        final var childBuilder = Builders.containerBuilder().withNodeIdentifier(container.name());
+        buildCont(childBuilder, container.body(), ctxNode, trim);
+        builder.withChild(childBuilder.build());
+    }
+
+    private static void appendLeaf(final DataContainerNodeBuilder<?, ?> builder, final LeafNode<?> leaf,
+            final DataSchemaContext ctxNode, final boolean trim, final List<QName> keys) {
+        if (!(ctxNode.dataSchemaNode() instanceof LeafSchemaNode leafSchema)) {
+            throw new IllegalStateException("Input " + leaf + " does not match " + ctxNode);
+        }
+
+        // FIXME: Document now this works with the likes of YangInstanceIdentifier. I bet it does not.
+        final var defaultVal = leafSchema.getType().getDefaultValue().orElse(null);
+
+        // This is a combined check for when we need to emit the leaf.
+        if (
+            // We always have to emit key leaf values
+            keys.contains(leafSchema.getQName())
+            // trim == WithDefaultsParam.TRIM and the source is assumed to store explicit values:
+            //
+            //            When data is retrieved with a <with-defaults> parameter equal to
+            //            'trim', data nodes MUST NOT be reported if they contain the schema
+            //            default value.  Non-configuration data nodes containing the schema
+            //            default value MUST NOT be reported.
+            //
+            || trim && (defaultVal == null || !defaultVal.equals(leaf.body()))
+            // !trim == WithDefaultsParam.EXPLICIT and the source is assume to store explicit values... but I fail to
+            // grasp what we are doing here... emit only if it matches default ???!!!
+            // FIXME: The WithDefaultsParam.EXPLICIT says:
+            //
+            //            Data nodes set to the YANG default by the client are reported.
+            //
+            //        and RFC8040 (https://www.rfc-editor.org/rfc/rfc8040#page-60) says:
+            //
+            //            If the "with-defaults" parameter is set to "explicit", then the
+            //            server MUST adhere to the default-reporting behavior defined in
+            //            Section 3.3 of [RFC6243].
+            //
+            //        and then RFC6243 (https://www.rfc-editor.org/rfc/rfc6243#section-3.3) says:
+            //
+            //            When data is retrieved with a <with-defaults> parameter equal to
+            //            'explicit', a data node that was set by a client to its schema
+            //            default value MUST be reported.  A conceptual data node that would be
+            //            set by the server to the schema default value MUST NOT be reported.
+            //            Non-configuration data nodes containing the schema default value MUST
+            //            be reported.
+            //
+            // (rovarga): The source reports explicitly-defined leaves and does *not* create defaults by itself.
+            //            This seems to disregard the 'trim = true' case semantics (see above).
+            //            Combining the above, though, these checks are missing the 'non-config' check, which would
+            //            distinguish, but barring that this check is superfluous and results in the wrong semantics.
+            //            Without that input, this really should be  covered by the previous case.
+                || !trim && defaultVal != null && defaultVal.equals(leaf.body())) {
+            builder.withChild(leaf);
+        }
+    }
+
+    private static void appendMap(final DataContainerNodeBuilder<?, ?> builder, final MapNode map,
+            final DataSchemaContext childCtx, final boolean trim) {
+        if (!(childCtx.dataSchemaNode() instanceof ListSchemaNode listSchema)) {
+            throw new IllegalStateException("Input " + map + " does not match " + childCtx);
+        }
+
+        final var childBuilder = switch (map.ordering()) {
+            case SYSTEM -> Builders.mapBuilder();
+            case USER -> Builders.orderedMapBuilder();
+        };
+        buildList(childBuilder.withNodeIdentifier(map.name()), map.body(), childCtx, trim,
+            listSchema.getKeyDefinition());
+        builder.withChild(childBuilder.build());
+    }
+
+    private static void buildList(final CollectionNodeBuilder<MapEntryNode, ? extends MapNode> builder,
+            final Collection<@NonNull MapEntryNode> entries, final DataSchemaContext ctxNode, final boolean trim,
+            final List<@NonNull QName> keys) {
+        for (var entry : entries) {
+            final var childCtx = getChildContext(ctxNode, entry);
+            final var mapEntryBuilder = Builders.mapEntryBuilder().withNodeIdentifier(entry.name());
+            buildMapEntryBuilder(mapEntryBuilder, entry.body(), childCtx, trim, keys);
+            builder.withChild(mapEntryBuilder.build());
+        }
+    }
+
+    private static void buildCont(final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> builder,
+            final Collection<DataContainerChild> children, final DataSchemaContext ctxNode, final boolean trim) {
+        for (var child : children) {
+            final var childCtx = getChildContext(ctxNode, child);
+            if (child instanceof ContainerNode container) {
+                appendContainer(builder, container, childCtx, trim);
+            } else if (child instanceof MapNode map) {
+                appendMap(builder, map, childCtx, trim);
+            } else if (child instanceof LeafNode<?> leaf) {
+                appendLeaf(builder, leaf, childCtx, trim, List.of());
+            }
+        }
+    }
+
+    private static @NonNull DataSchemaContext getChildContext(final DataSchemaContext ctxNode,
+            final NormalizedNode child) {
+        final var childId = child.name();
+        final var childCtx = ctxNode instanceof DataSchemaContext.Composite composite ? composite.childByArg(childId)
+            : null;
+        if (childCtx == null) {
+            throw new NoSuchElementException("Cannot resolve child " + childId + " in " + ctxNode);
+        }
+        return childCtx;
+    }
+
+    private @Nullable NormalizedNode readDataViaTransaction(final LogicalDatastoreType store,
+            final YangInstanceIdentifier path) {
+        return TransactionUtil.syncAccess(read(store, path), path).orElse(null);
+    }
+
+    /**
+     * Read specific type of data {@link LogicalDatastoreType} via transaction in {@link RestconfStrategy} with
+     * specified subtrees that should only be read.
+     *
+     * @param store                 datastore type
+     * @param path                  parent path to selected fields
+     * @param closeTransactionChain if it is set to {@code true}, after transaction it will close transactionChain
+     *                              in {@link RestconfStrategy} if any
+     * @param fields                paths to selected subtrees which should be read, relative to to the parent path
+     * @return {@link NormalizedNode}
+     */
+    private @Nullable NormalizedNode readDataViaTransaction(final @NonNull LogicalDatastoreType store,
+            final @NonNull YangInstanceIdentifier path, final @NonNull List<YangInstanceIdentifier> fields) {
+        return TransactionUtil.syncAccess(read(store, path, fields), path).orElse(null);
+    }
+
+    private static NormalizedNode mergeConfigAndSTateDataIfNeeded(final NormalizedNode stateDataNode,
+                                                                  final NormalizedNode configDataNode) {
+        // if no data exists
+        if (stateDataNode == null && configDataNode == null) {
+            return null;
+        }
+
+        // return config data
+        if (stateDataNode == null) {
+            return configDataNode;
+        }
+
+        // return state data
+        if (configDataNode == null) {
+            return stateDataNode;
+        }
+
+        // merge data from config and state
+        return mergeStateAndConfigData(stateDataNode, configDataNode);
+    }
+
+    /**
+     * Merge state and config data into a single NormalizedNode.
+     *
+     * @param stateDataNode  data node of state data
+     * @param configDataNode data node of config data
+     * @return {@link NormalizedNode}
+     */
+    private static @NonNull NormalizedNode mergeStateAndConfigData(
+            final @NonNull NormalizedNode stateDataNode, final @NonNull NormalizedNode configDataNode) {
+        validateNodeMerge(stateDataNode, configDataNode);
+        // FIXME: this check is bogus, as it confuses yang.data.api (NormalizedNode) with yang.model.api (RpcDefinition)
+        if (configDataNode instanceof RpcDefinition) {
+            return prepareRpcData(configDataNode, stateDataNode);
+        } else {
+            return prepareData(configDataNode, stateDataNode);
+        }
+    }
+
+    /**
+     * Validates whether the two NormalizedNodes can be merged.
+     *
+     * @param stateDataNode  data node of state data
+     * @param configDataNode data node of config data
+     */
+    private static void validateNodeMerge(final @NonNull NormalizedNode stateDataNode,
+                                          final @NonNull NormalizedNode configDataNode) {
+        final QNameModule moduleOfStateData = stateDataNode.name().getNodeType().getModule();
+        final QNameModule moduleOfConfigData = configDataNode.name().getNodeType().getModule();
+        if (!moduleOfStateData.equals(moduleOfConfigData)) {
+            throw new RestconfDocumentedException("Unable to merge data from different modules.");
+        }
+    }
+
+    /**
+     * Prepare and map data for rpc.
+     *
+     * @param configDataNode data node of config data
+     * @param stateDataNode  data node of state data
+     * @return {@link NormalizedNode}
+     */
+    private static @NonNull NormalizedNode prepareRpcData(final @NonNull NormalizedNode configDataNode,
+                                                          final @NonNull NormalizedNode stateDataNode) {
+        final var mapEntryBuilder = Builders.mapEntryBuilder()
+            .withNodeIdentifier((NodeIdentifierWithPredicates) configDataNode.name());
+
+        // MAP CONFIG DATA
+        mapRpcDataNode(configDataNode, mapEntryBuilder);
+        // MAP STATE DATA
+        mapRpcDataNode(stateDataNode, mapEntryBuilder);
+
+        return Builders.mapBuilder()
+            .withNodeIdentifier(NodeIdentifier.create(configDataNode.name().getNodeType()))
+            .addChild(mapEntryBuilder.build())
+            .build();
+    }
+
+    /**
+     * Map node to map entry builder.
+     *
+     * @param dataNode        data node
+     * @param mapEntryBuilder builder for mapping data
+     */
+    private static void mapRpcDataNode(final @NonNull NormalizedNode dataNode,
+            final @NonNull DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> mapEntryBuilder) {
+        ((ContainerNode) dataNode).body().forEach(mapEntryBuilder::addChild);
+    }
+
+    /**
+     * Prepare and map all data from DS.
+     *
+     * @param configDataNode data node of config data
+     * @param stateDataNode  data node of state data
+     * @return {@link NormalizedNode}
+     */
+    @SuppressWarnings("unchecked")
+    private static @NonNull NormalizedNode prepareData(final @NonNull NormalizedNode configDataNode,
+                                                       final @NonNull NormalizedNode stateDataNode) {
+        if (configDataNode instanceof UserMapNode configMap) {
+            final var builder = Builders.orderedMapBuilder().withNodeIdentifier(configMap.name());
+            mapValueToBuilder(configMap.body(), ((UserMapNode) stateDataNode).body(), builder);
+            return builder.build();
+        } else if (configDataNode instanceof SystemMapNode configMap) {
+            final var builder = Builders.mapBuilder().withNodeIdentifier(configMap.name());
+            mapValueToBuilder(configMap.body(), ((SystemMapNode) stateDataNode).body(), builder);
+            return builder.build();
+        } else if (configDataNode instanceof MapEntryNode configEntry) {
+            final var builder = Builders.mapEntryBuilder().withNodeIdentifier(configEntry.name());
+            mapValueToBuilder(configEntry.body(), ((MapEntryNode) stateDataNode).body(), builder);
+            return builder.build();
+        } else if (configDataNode instanceof ContainerNode configContaienr) {
+            final var builder = Builders.containerBuilder().withNodeIdentifier(configContaienr.name());
+            mapValueToBuilder(configContaienr.body(), ((ContainerNode) stateDataNode).body(), builder);
+            return builder.build();
+        } else if (configDataNode instanceof ChoiceNode configChoice) {
+            final var builder = Builders.choiceBuilder().withNodeIdentifier(configChoice.name());
+            mapValueToBuilder(configChoice.body(), ((ChoiceNode) stateDataNode).body(), builder);
+            return builder.build();
+        } else if (configDataNode instanceof LeafNode configLeaf) {
+            // config trumps oper
+            return configLeaf;
+        } else if (configDataNode instanceof UserLeafSetNode) {
+            final var configLeafSet = (UserLeafSetNode<Object>) configDataNode;
+            final var builder = Builders.<Object>orderedLeafSetBuilder().withNodeIdentifier(configLeafSet.name());
+            mapValueToBuilder(configLeafSet.body(), ((UserLeafSetNode<Object>) stateDataNode).body(), builder);
+            return builder.build();
+        } else if (configDataNode instanceof SystemLeafSetNode) {
+            final var configLeafSet = (SystemLeafSetNode<Object>) configDataNode;
+            final var builder = Builders.<Object>leafSetBuilder().withNodeIdentifier(configLeafSet.name());
+            mapValueToBuilder(configLeafSet.body(), ((SystemLeafSetNode<Object>) stateDataNode).body(), builder);
+            return builder.build();
+        } else if (configDataNode instanceof LeafSetEntryNode<?> configEntry) {
+            // config trumps oper
+            return configEntry;
+        } else if (configDataNode instanceof UnkeyedListNode configList) {
+            final var builder = Builders.unkeyedListBuilder().withNodeIdentifier(configList.name());
+            mapValueToBuilder(configList.body(), ((UnkeyedListNode) stateDataNode).body(), builder);
+            return builder.build();
+        } else if (configDataNode instanceof UnkeyedListEntryNode configEntry) {
+            final var builder = Builders.unkeyedListEntryBuilder().withNodeIdentifier(configEntry.name());
+            mapValueToBuilder(configEntry.body(), ((UnkeyedListEntryNode) stateDataNode).body(), builder);
+            return builder.build();
+        } else {
+            throw new RestconfDocumentedException("Unexpected node type: " + configDataNode.getClass().getName());
+        }
+    }
+
+    /**
+     * Map value from container node to builder.
+     *
+     * @param configData collection of config data nodes
+     * @param stateData  collection of state data nodes
+     * @param builder    builder
+     */
+    private static <T extends NormalizedNode> void mapValueToBuilder(
+            final @NonNull Collection<T> configData, final @NonNull Collection<T> stateData,
+            final @NonNull NormalizedNodeContainerBuilder<?, PathArgument, T, ?> builder) {
+        final var configMap = configData.stream().collect(Collectors.toMap(NormalizedNode::name, Function.identity()));
+        final var stateMap = stateData.stream().collect(Collectors.toMap(NormalizedNode::name, Function.identity()));
+
+        // 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);
+    }
+
+    /**
+     * Map data with different identifiers to builder. Data with different identifiers can be just added
+     * as childs to parent node.
+     *
+     * @param configMap map of config data nodes
+     * @param stateMap  map of state data nodes
+     * @param builder   - builder
+     */
+    private static <T extends NormalizedNode> void mapDataToBuilder(
+            final @NonNull Map<PathArgument, T> configMap, final @NonNull Map<PathArgument, T> stateMap,
+            final @NonNull 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()));
+    }
+
+    /**
+     * 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 configMap immutable config data
+     * @param stateMap  immutable state data
+     * @param builder   - builder
+     */
+    @SuppressWarnings("unchecked")
+    private static <T extends NormalizedNode> void mergeDataToBuilder(
+            final @NonNull Map<PathArgument, T> configMap, final @NonNull Map<PathArgument, T> stateMap,
+            final @NonNull 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()))));
+    }
+
 }
index 073b165be0c2d54f3169ab321e0eeaf4bdb5398c..e97bc96b3989333944f6a93eb6f7da4ff4c6e3f1 100644 (file)
@@ -11,16 +11,19 @@ import static java.util.Objects.requireNonNull;
 
 import com.google.common.annotations.Beta;
 import com.google.common.util.concurrent.ListenableFuture;
+import java.util.ArrayList;
 import java.util.Optional;
 import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
 import org.opendaylight.mdsal.common.api.CommitInfo;
 import org.opendaylight.odlparent.logging.markers.Markers;
-import org.opendaylight.restconf.nb.rfc8040.rests.utils.TransactionUtil;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -128,4 +131,38 @@ public abstract class RestconfTransaction {
     }
 
     abstract ListenableFuture<Optional<NormalizedNode>> read(YangInstanceIdentifier path);
+
+    /**
+     * Merge parents of data.
+     *
+     * @param path    path of data
+     * @param context {@link SchemaContext}
+     */
+    // FIXME: this method should only be invoked in MdsalRestconfStrategy, and even then only if we are crossing
+    //        an implicit list.
+    final void ensureParentsByMerge(final YangInstanceIdentifier path, final EffectiveModelContext context) {
+        final var normalizedPathWithoutChildArgs = new ArrayList<PathArgument>();
+        YangInstanceIdentifier rootNormalizedPath = null;
+
+        final var it = path.getPathArguments().iterator();
+
+        while (it.hasNext()) {
+            final var pathArgument = it.next();
+            if (rootNormalizedPath == null) {
+                rootNormalizedPath = YangInstanceIdentifier.of(pathArgument);
+            }
+
+            if (it.hasNext()) {
+                normalizedPathWithoutChildArgs.add(pathArgument);
+            }
+        }
+
+        if (normalizedPathWithoutChildArgs.isEmpty()) {
+            return;
+        }
+
+        merge(rootNormalizedPath,
+            ImmutableNodes.fromInstanceId(context, YangInstanceIdentifier.of(normalizedPathWithoutChildArgs)));
+    }
+
 }
similarity index 70%
rename from restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/TransactionUtil.java
rename to restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/transactions/TransactionUtil.java
index a3c2c4b2f9afad1bd0b27676c5169bdcd735cdda..efa3daf57c6e0d6bf67e8ac10557591fcc43396d 100644 (file)
@@ -5,11 +5,10 @@
  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
  * and is available at http://www.eclipse.org/legal/epl-v10.html
  */
-package org.opendaylight.restconf.nb.rfc8040.rests.utils;
+package org.opendaylight.restconf.nb.rfc8040.rests.transactions;
 
 import com.google.common.base.Throwables;
 import com.google.common.util.concurrent.ListenableFuture;
-import java.util.ArrayList;
 import java.util.concurrent.ExecutionException;
 import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.mdsal.common.api.CommitInfo;
@@ -18,63 +17,22 @@ import org.opendaylight.netconf.api.DocumentedException;
 import org.opendaylight.netconf.api.NetconfDocumentedException;
 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
 import org.opendaylight.restconf.common.errors.RestconfError;
-import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfTransaction;
 import org.opendaylight.yangtools.yang.common.ErrorTag;
 import org.opendaylight.yangtools.yang.common.ErrorType;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
-import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
-import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
-import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
  * Util class for common methods of transactions.
  */
-public final class TransactionUtil {
+final class TransactionUtil {
     private static final Logger LOG = LoggerFactory.getLogger(TransactionUtil.class);
 
     private TransactionUtil() {
         // Hidden on purpose
     }
 
-    /**
-     * Merged parents of data.
-     *
-     * @param path          path of data
-     * @param schemaContext {@link SchemaContext}
-     * @param transaction   A handle to a set of DS operations
-     */
-    // FIXME: this method should only be invoked in MdsalRestconfStrategy, and even then only if we are crossing
-    //        an implicit list.
-    public static void ensureParentsByMerge(final YangInstanceIdentifier path,
-                                            final EffectiveModelContext schemaContext,
-                                            final RestconfTransaction transaction) {
-        final var normalizedPathWithoutChildArgs = new ArrayList<PathArgument>();
-        YangInstanceIdentifier rootNormalizedPath = null;
-
-        final var it = path.getPathArguments().iterator();
-
-        while (it.hasNext()) {
-            final var pathArgument = it.next();
-            if (rootNormalizedPath == null) {
-                rootNormalizedPath = YangInstanceIdentifier.of(pathArgument);
-            }
-
-            if (it.hasNext()) {
-                normalizedPathWithoutChildArgs.add(pathArgument);
-            }
-        }
-
-        if (normalizedPathWithoutChildArgs.isEmpty()) {
-            return;
-        }
-
-        transaction.merge(rootNormalizedPath,
-            ImmutableNodes.fromInstanceId(schemaContext, YangInstanceIdentifier.of(normalizedPathWithoutChildArgs)));
-    }
-
     /**
      * Synchronize access to a path resource, translating any failure to a {@link RestconfDocumentedException}.
      *
@@ -84,7 +42,7 @@ public final class TransactionUtil {
      * @return The accessed value
      * @throws RestconfDocumentedException if commit fails
      */
-    public static <T> T syncAccess(final ListenableFuture<T> future, final YangInstanceIdentifier path) {
+    static <T> T syncAccess(final ListenableFuture<T> future, final YangInstanceIdentifier path) {
         try {
             return future.get();
         } catch (ExecutionException e) {
@@ -103,7 +61,7 @@ public final class TransactionUtil {
      * @param path Modified path
      * @throws RestconfDocumentedException if commit fails
      */
-    public static void syncCommit(final ListenableFuture<? extends CommitInfo> future, final String txType,
+    static void syncCommit(final ListenableFuture<? extends CommitInfo> future, final String txType,
             final YangInstanceIdentifier path) {
         try {
             future.get();
@@ -117,7 +75,7 @@ public final class TransactionUtil {
         LOG.trace("Transaction({}) SUCCESSFUL", txType);
     }
 
-    public static @NonNull RestconfDocumentedException decodeException(final Throwable throwable,
+    static @NonNull RestconfDocumentedException decodeException(final Throwable throwable,
             final String txType, final YangInstanceIdentifier path) {
         return decodeException(throwable, throwable, txType, path);
     }
diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/ReadDataTransactionUtil.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/ReadDataTransactionUtil.java
deleted file mode 100644 (file)
index a9619a8..0000000
+++ /dev/null
@@ -1,530 +0,0 @@
-/*
- * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License v1.0 which accompanies this distribution,
- * and is available at http://www.eclipse.org/legal/epl-v10.html
- */
-package org.opendaylight.restconf.nb.rfc8040.rests.utils;
-
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.NoSuchElementException;
-import java.util.function.Function;
-import java.util.stream.Collectors;
-import org.eclipse.jdt.annotation.NonNull;
-import org.eclipse.jdt.annotation.Nullable;
-import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
-import org.opendaylight.mdsal.dom.api.DOMTransactionChain;
-import org.opendaylight.restconf.api.query.ContentParam;
-import org.opendaylight.restconf.api.query.WithDefaultsParam;
-import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
-import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfStrategy;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.with.defaults.rev110601.WithDefaultsMode;
-import org.opendaylight.yangtools.yang.common.QName;
-import org.opendaylight.yangtools.yang.common.QNameModule;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
-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.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.LeafNode;
-import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
-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.api.schema.SystemLeafSetNode;
-import org.opendaylight.yangtools.yang.data.api.schema.SystemMapNode;
-import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListEntryNode;
-import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListNode;
-import org.opendaylight.yangtools.yang.data.api.schema.UserLeafSetNode;
-import org.opendaylight.yangtools.yang.data.api.schema.UserMapNode;
-import org.opendaylight.yangtools.yang.data.api.schema.builder.CollectionNodeBuilder;
-import org.opendaylight.yangtools.yang.data.api.schema.builder.DataContainerNodeBuilder;
-import org.opendaylight.yangtools.yang.data.api.schema.builder.NormalizedNodeContainerBuilder;
-import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
-import org.opendaylight.yangtools.yang.data.util.DataSchemaContext;
-import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
-import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
-import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Util class for read data from data store via transaction.
- * <ul>
- * <li>config
- * <li>state
- * <li>all (config + state)
- * </ul>
- */
-public final class ReadDataTransactionUtil {
-    private static final Logger LOG = LoggerFactory.getLogger(ReadDataTransactionUtil.class);
-
-    private ReadDataTransactionUtil() {
-        // Hidden on purpose
-    }
-
-    /**
-     * Read specific type of data from data store via transaction. Close {@link DOMTransactionChain} if any
-     * inside of object {@link RestconfStrategy} provided as a parameter.
-     *
-     * @param content        type of data to read (config, state, all)
-     * @param path           the path to read
-     * @param strategy       {@link RestconfStrategy} - object that perform the actual DS operations
-     * @param defaultsMode   value of with-defaults parameter
-     * @param ctx            schema context
-     * @return {@link NormalizedNode}
-     */
-    public static @Nullable NormalizedNode readData(final @NonNull ContentParam content,
-            final @NonNull YangInstanceIdentifier path, final @NonNull RestconfStrategy strategy,
-            final WithDefaultsParam defaultsMode, final EffectiveModelContext ctx) {
-        return switch (content) {
-            case ALL -> {
-                // PREPARE STATE DATA NODE
-                final var stateDataNode = readDataViaTransaction(strategy, LogicalDatastoreType.OPERATIONAL, path);
-                // PREPARE CONFIG DATA NODE
-                final var configDataNode = readDataViaTransaction(strategy, LogicalDatastoreType.CONFIGURATION, path);
-
-                yield mergeConfigAndSTateDataIfNeeded(stateDataNode, defaultsMode == null ? configDataNode
-                    : prepareDataByParamWithDef(configDataNode, path, defaultsMode.mode(), ctx));
-            }
-            case CONFIG -> {
-                final var read = readDataViaTransaction(strategy, LogicalDatastoreType.CONFIGURATION, path);
-                yield defaultsMode == null ? read : prepareDataByParamWithDef(read, path, defaultsMode.mode(), ctx);
-            }
-            case NONCONFIG -> readDataViaTransaction(strategy, LogicalDatastoreType.OPERATIONAL, path);
-        };
-    }
-
-    /**
-     * Read specific type of data from data store via transaction with specified subtrees that should only be read.
-     * Close {@link DOMTransactionChain} inside of object {@link RestconfStrategy} provided as a parameter.
-     *
-     * @param content        type of data to read (config, state, all)
-     * @param path           the parent path to read
-     * @param strategy       {@link RestconfStrategy} - object that perform the actual DS operations
-     * @param withDefa       value of with-defaults parameter
-     * @param ctx            schema context
-     * @param fields         paths to selected subtrees which should be read, relative to to the parent path
-     * @return {@link NormalizedNode}
-     */
-    public static @Nullable NormalizedNode readData(final @NonNull ContentParam content,
-            final @NonNull YangInstanceIdentifier path, final @NonNull RestconfStrategy strategy,
-            final @Nullable WithDefaultsParam withDefa, @NonNull final EffectiveModelContext ctx,
-            final @NonNull List<YangInstanceIdentifier> fields) {
-        return switch (content) {
-            case ALL -> {
-                // PREPARE STATE DATA NODE
-                final var stateDataNode = readDataViaTransaction(strategy, LogicalDatastoreType.OPERATIONAL, path,
-                    fields);
-                // PREPARE CONFIG DATA NODE
-                final var configDataNode = readDataViaTransaction(strategy, LogicalDatastoreType.CONFIGURATION, path,
-                    fields);
-
-                yield mergeConfigAndSTateDataIfNeeded(stateDataNode, withDefa == null ? configDataNode
-                    : prepareDataByParamWithDef(configDataNode, path, withDefa.mode(), ctx));
-            }
-            case CONFIG -> {
-                final var read = readDataViaTransaction(strategy, LogicalDatastoreType.CONFIGURATION, path, fields);
-                yield withDefa == null ? read : prepareDataByParamWithDef(read, path, withDefa.mode(), ctx);
-            }
-            case NONCONFIG -> readDataViaTransaction(strategy, LogicalDatastoreType.OPERATIONAL, path, fields);
-        };
-    }
-
-    private static NormalizedNode prepareDataByParamWithDef(final NormalizedNode readData,
-            final YangInstanceIdentifier path, final WithDefaultsMode defaultsMode, final EffectiveModelContext ctx) {
-        final boolean trim = switch (defaultsMode) {
-            case Trim -> true;
-            case Explicit -> false;
-            case ReportAll, ReportAllTagged -> throw new RestconfDocumentedException(
-                "Unsupported with-defaults value " + defaultsMode.getName());
-        };
-
-        final var ctxNode = DataSchemaContextTree.from(ctx).findChild(path).orElseThrow();
-        if (readData instanceof ContainerNode container) {
-            final var builder = Builders.containerBuilder().withNodeIdentifier(container.name());
-            buildCont(builder, container.body(), ctxNode, trim);
-            return builder.build();
-        } else if (readData instanceof MapEntryNode mapEntry) {
-            if (!(ctxNode.dataSchemaNode() instanceof ListSchemaNode listSchema)) {
-                throw new IllegalStateException("Input " + mapEntry + " does not match " + ctxNode);
-            }
-
-            final var builder = Builders.mapEntryBuilder().withNodeIdentifier(mapEntry.name());
-            buildMapEntryBuilder(builder, mapEntry.body(), ctxNode, trim, listSchema.getKeyDefinition());
-            return builder.build();
-        } else {
-            throw new IllegalStateException("Unhandled data contract " + readData.contract());
-        }
-    }
-
-    private static void buildMapEntryBuilder(
-            final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> builder,
-            final Collection<@NonNull DataContainerChild> children, final DataSchemaContext ctxNode,
-            final boolean trim, final List<QName> keys) {
-        for (var child : children) {
-            final var childCtx = getChildContext(ctxNode, child);
-
-            if (child instanceof ContainerNode container) {
-                appendContainer(builder, container, childCtx, trim);
-            } else if (child instanceof MapNode map) {
-                appendMap(builder, map, childCtx, trim);
-            } else if (child instanceof LeafNode<?> leaf) {
-                appendLeaf(builder, leaf, childCtx, trim, keys);
-            } else {
-                // FIXME: we should never hit this, throw an ISE if this ever happens
-                LOG.debug("Ignoring unhandled child contract {}", child.contract());
-            }
-        }
-    }
-
-    private static void appendContainer(final DataContainerNodeBuilder<?, ?> builder, final ContainerNode container,
-            final DataSchemaContext ctxNode, final boolean trim) {
-        final var childBuilder = Builders.containerBuilder().withNodeIdentifier(container.name());
-        buildCont(childBuilder, container.body(), ctxNode, trim);
-        builder.withChild(childBuilder.build());
-    }
-
-    private static void appendLeaf(final DataContainerNodeBuilder<?, ?> builder, final LeafNode<?> leaf,
-            final DataSchemaContext ctxNode, final boolean trim, final List<QName> keys) {
-        if (!(ctxNode.dataSchemaNode() instanceof LeafSchemaNode leafSchema)) {
-            throw new IllegalStateException("Input " + leaf + " does not match " + ctxNode);
-        }
-
-        // FIXME: Document now this works with the likes of YangInstanceIdentifier. I bet it does not.
-        final var defaultVal = leafSchema.getType().getDefaultValue().orElse(null);
-
-        // This is a combined check for when we need to emit the leaf.
-        if (
-            // We always have to emit key leaf values
-            keys.contains(leafSchema.getQName())
-            // trim == WithDefaultsParam.TRIM and the source is assumed to store explicit values:
-            //
-            //            When data is retrieved with a <with-defaults> parameter equal to
-            //            'trim', data nodes MUST NOT be reported if they contain the schema
-            //            default value.  Non-configuration data nodes containing the schema
-            //            default value MUST NOT be reported.
-            //
-            || trim && (defaultVal == null || !defaultVal.equals(leaf.body()))
-            // !trim == WithDefaultsParam.EXPLICIT and the source is assume to store explicit values... but I fail to
-            // grasp what we are doing here... emit only if it matches default ???!!!
-            // FIXME: The WithDefaultsParam.EXPLICIT says:
-            //
-            //            Data nodes set to the YANG default by the client are reported.
-            //
-            //        and RFC8040 (https://www.rfc-editor.org/rfc/rfc8040#page-60) says:
-            //
-            //            If the "with-defaults" parameter is set to "explicit", then the
-            //            server MUST adhere to the default-reporting behavior defined in
-            //            Section 3.3 of [RFC6243].
-            //
-            //        and then RFC6243 (https://www.rfc-editor.org/rfc/rfc6243#section-3.3) says:
-            //
-            //            When data is retrieved with a <with-defaults> parameter equal to
-            //            'explicit', a data node that was set by a client to its schema
-            //            default value MUST be reported.  A conceptual data node that would be
-            //            set by the server to the schema default value MUST NOT be reported.
-            //            Non-configuration data nodes containing the schema default value MUST
-            //            be reported.
-            //
-            // (rovarga): The source reports explicitly-defined leaves and does *not* create defaults by itself.
-            //            This seems to disregard the 'trim = true' case semantics (see above).
-            //            Combining the above, though, these checks are missing the 'non-config' check, which would
-            //            distinguish, but barring that this check is superfluous and results in the wrong semantics.
-            //            Without that input, this really should be  covered by the previous case.
-                || !trim && defaultVal != null && defaultVal.equals(leaf.body())) {
-            builder.withChild(leaf);
-        }
-    }
-
-    private static void appendMap(final DataContainerNodeBuilder<?, ?> builder, final MapNode map,
-            final DataSchemaContext childCtx, final boolean trim) {
-        if (!(childCtx.dataSchemaNode() instanceof ListSchemaNode listSchema)) {
-            throw new IllegalStateException("Input " + map + " does not match " + childCtx);
-        }
-
-        final var childBuilder = switch (map.ordering()) {
-            case SYSTEM -> Builders.mapBuilder();
-            case USER -> Builders.orderedMapBuilder();
-        };
-        buildList(childBuilder.withNodeIdentifier(map.name()), map.body(), childCtx, trim,
-            listSchema.getKeyDefinition());
-        builder.withChild(childBuilder.build());
-    }
-
-    private static void buildList(final CollectionNodeBuilder<MapEntryNode, ? extends MapNode> builder,
-            final Collection<@NonNull MapEntryNode> entries, final DataSchemaContext ctxNode, final boolean trim,
-            final List<@NonNull QName> keys) {
-        for (var entry : entries) {
-            final var childCtx = getChildContext(ctxNode, entry);
-            final var mapEntryBuilder = Builders.mapEntryBuilder().withNodeIdentifier(entry.name());
-            buildMapEntryBuilder(mapEntryBuilder, entry.body(), childCtx, trim, keys);
-            builder.withChild(mapEntryBuilder.build());
-        }
-    }
-
-    private static void buildCont(final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> builder,
-            final Collection<DataContainerChild> children, final DataSchemaContext ctxNode, final boolean trim) {
-        for (var child : children) {
-            final var childCtx = getChildContext(ctxNode, child);
-            if (child instanceof ContainerNode container) {
-                appendContainer(builder, container, childCtx, trim);
-            } else if (child instanceof MapNode map) {
-                appendMap(builder, map, childCtx, trim);
-            } else if (child instanceof LeafNode<?> leaf) {
-                appendLeaf(builder, leaf, childCtx, trim, List.of());
-            }
-        }
-    }
-
-    private static @NonNull DataSchemaContext getChildContext(final DataSchemaContext ctxNode,
-            final NormalizedNode child) {
-        final var childId = child.name();
-        final var childCtx = ctxNode instanceof DataSchemaContext.Composite composite ? composite.childByArg(childId)
-            : null;
-        if (childCtx == null) {
-            throw new NoSuchElementException("Cannot resolve child " + childId + " in " + ctxNode);
-        }
-        return childCtx;
-    }
-
-    /**
-     * If is set specific {@link LogicalDatastoreType} in {@link RestconfStrategy}, then read this type of data from DS.
-     * If don't, we have to read all data from DS (state + config)
-     *
-     * @param strategy              {@link RestconfStrategy} - object that perform the actual DS operations
-     * @param closeTransactionChain If is set to true, after transaction it will close transactionChain
-     *                              in {@link RestconfStrategy} if any
-     * @return {@link NormalizedNode}
-     */
-    private static @Nullable NormalizedNode readDataViaTransaction(final @NonNull RestconfStrategy strategy,
-            final LogicalDatastoreType store, final YangInstanceIdentifier path) {
-        return TransactionUtil.syncAccess(strategy.read(store, path), path).orElse(null);
-    }
-
-    /**
-     * Read specific type of data {@link LogicalDatastoreType} via transaction in {@link RestconfStrategy} with
-     * specified subtrees that should only be read.
-     *
-     * @param strategy              {@link RestconfStrategy} - object that perform the actual DS operations
-     * @param store                 datastore type
-     * @param path                  parent path to selected fields
-     * @param closeTransactionChain if it is set to {@code true}, after transaction it will close transactionChain
-     *                              in {@link RestconfStrategy} if any
-     * @param fields                paths to selected subtrees which should be read, relative to to the parent path
-     * @return {@link NormalizedNode}
-     */
-    private static @Nullable NormalizedNode readDataViaTransaction(final @NonNull RestconfStrategy strategy,
-            final @NonNull LogicalDatastoreType store, final @NonNull YangInstanceIdentifier path,
-            final @NonNull List<YangInstanceIdentifier> fields) {
-        return TransactionUtil.syncAccess(strategy.read(store, path, fields), path).orElse(null);
-    }
-
-    private static NormalizedNode mergeConfigAndSTateDataIfNeeded(final NormalizedNode stateDataNode,
-                                                                  final NormalizedNode configDataNode) {
-        // if no data exists
-        if (stateDataNode == null && configDataNode == null) {
-            return null;
-        }
-
-        // return config data
-        if (stateDataNode == null) {
-            return configDataNode;
-        }
-
-        // return state data
-        if (configDataNode == null) {
-            return stateDataNode;
-        }
-
-        // merge data from config and state
-        return mergeStateAndConfigData(stateDataNode, configDataNode);
-    }
-
-    /**
-     * Merge state and config data into a single NormalizedNode.
-     *
-     * @param stateDataNode  data node of state data
-     * @param configDataNode data node of config data
-     * @return {@link NormalizedNode}
-     */
-    private static @NonNull NormalizedNode mergeStateAndConfigData(
-            final @NonNull NormalizedNode stateDataNode, final @NonNull NormalizedNode configDataNode) {
-        validateNodeMerge(stateDataNode, configDataNode);
-        // FIXME: this check is bogus, as it confuses yang.data.api (NormalizedNode) with yang.model.api (RpcDefinition)
-        if (configDataNode instanceof RpcDefinition) {
-            return prepareRpcData(configDataNode, stateDataNode);
-        } else {
-            return prepareData(configDataNode, stateDataNode);
-        }
-    }
-
-    /**
-     * Validates whether the two NormalizedNodes can be merged.
-     *
-     * @param stateDataNode  data node of state data
-     * @param configDataNode data node of config data
-     */
-    private static void validateNodeMerge(final @NonNull NormalizedNode stateDataNode,
-                                          final @NonNull NormalizedNode configDataNode) {
-        final QNameModule moduleOfStateData = stateDataNode.name().getNodeType().getModule();
-        final QNameModule moduleOfConfigData = configDataNode.name().getNodeType().getModule();
-        if (!moduleOfStateData.equals(moduleOfConfigData)) {
-            throw new RestconfDocumentedException("Unable to merge data from different modules.");
-        }
-    }
-
-    /**
-     * Prepare and map data for rpc.
-     *
-     * @param configDataNode data node of config data
-     * @param stateDataNode  data node of state data
-     * @return {@link NormalizedNode}
-     */
-    private static @NonNull NormalizedNode prepareRpcData(final @NonNull NormalizedNode configDataNode,
-                                                          final @NonNull NormalizedNode stateDataNode) {
-        final var mapEntryBuilder = Builders.mapEntryBuilder()
-            .withNodeIdentifier((NodeIdentifierWithPredicates) configDataNode.name());
-
-        // MAP CONFIG DATA
-        mapRpcDataNode(configDataNode, mapEntryBuilder);
-        // MAP STATE DATA
-        mapRpcDataNode(stateDataNode, mapEntryBuilder);
-
-        return Builders.mapBuilder()
-            .withNodeIdentifier(NodeIdentifier.create(configDataNode.name().getNodeType()))
-            .addChild(mapEntryBuilder.build())
-            .build();
-    }
-
-    /**
-     * Map node to map entry builder.
-     *
-     * @param dataNode        data node
-     * @param mapEntryBuilder builder for mapping data
-     */
-    private static void mapRpcDataNode(final @NonNull NormalizedNode dataNode,
-            final @NonNull DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> mapEntryBuilder) {
-        ((ContainerNode) dataNode).body().forEach(mapEntryBuilder::addChild);
-    }
-
-    /**
-     * Prepare and map all data from DS.
-     *
-     * @param configDataNode data node of config data
-     * @param stateDataNode  data node of state data
-     * @return {@link NormalizedNode}
-     */
-    @SuppressWarnings("unchecked")
-    private static @NonNull NormalizedNode prepareData(final @NonNull NormalizedNode configDataNode,
-                                                       final @NonNull NormalizedNode stateDataNode) {
-        if (configDataNode instanceof UserMapNode configMap) {
-            final var builder = Builders.orderedMapBuilder().withNodeIdentifier(configMap.name());
-            mapValueToBuilder(configMap.body(), ((UserMapNode) stateDataNode).body(), builder);
-            return builder.build();
-        } else if (configDataNode instanceof SystemMapNode configMap) {
-            final var builder = Builders.mapBuilder().withNodeIdentifier(configMap.name());
-            mapValueToBuilder(configMap.body(), ((SystemMapNode) stateDataNode).body(), builder);
-            return builder.build();
-        } else if (configDataNode instanceof MapEntryNode configEntry) {
-            final var builder = Builders.mapEntryBuilder().withNodeIdentifier(configEntry.name());
-            mapValueToBuilder(configEntry.body(), ((MapEntryNode) stateDataNode).body(), builder);
-            return builder.build();
-        } else if (configDataNode instanceof ContainerNode configContaienr) {
-            final var builder = Builders.containerBuilder().withNodeIdentifier(configContaienr.name());
-            mapValueToBuilder(configContaienr.body(), ((ContainerNode) stateDataNode).body(), builder);
-            return builder.build();
-        } else if (configDataNode instanceof ChoiceNode configChoice) {
-            final var builder = Builders.choiceBuilder().withNodeIdentifier(configChoice.name());
-            mapValueToBuilder(configChoice.body(), ((ChoiceNode) stateDataNode).body(), builder);
-            return builder.build();
-        } else if (configDataNode instanceof LeafNode configLeaf) {
-            // config trumps oper
-            return configLeaf;
-        } else if (configDataNode instanceof UserLeafSetNode) {
-            final var configLeafSet = (UserLeafSetNode<Object>) configDataNode;
-            final var builder = Builders.<Object>orderedLeafSetBuilder().withNodeIdentifier(configLeafSet.name());
-            mapValueToBuilder(configLeafSet.body(), ((UserLeafSetNode<Object>) stateDataNode).body(), builder);
-            return builder.build();
-        } else if (configDataNode instanceof SystemLeafSetNode) {
-            final var configLeafSet = (SystemLeafSetNode<Object>) configDataNode;
-            final var builder = Builders.<Object>leafSetBuilder().withNodeIdentifier(configLeafSet.name());
-            mapValueToBuilder(configLeafSet.body(), ((SystemLeafSetNode<Object>) stateDataNode).body(), builder);
-            return builder.build();
-        } else if (configDataNode instanceof LeafSetEntryNode<?> configEntry) {
-            // config trumps oper
-            return configEntry;
-        } else if (configDataNode instanceof UnkeyedListNode configList) {
-            final var builder = Builders.unkeyedListBuilder().withNodeIdentifier(configList.name());
-            mapValueToBuilder(configList.body(), ((UnkeyedListNode) stateDataNode).body(), builder);
-            return builder.build();
-        } else if (configDataNode instanceof UnkeyedListEntryNode configEntry) {
-            final var builder = Builders.unkeyedListEntryBuilder().withNodeIdentifier(configEntry.name());
-            mapValueToBuilder(configEntry.body(), ((UnkeyedListEntryNode) stateDataNode).body(), builder);
-            return builder.build();
-        } else {
-            throw new RestconfDocumentedException("Unexpected node type: " + configDataNode.getClass().getName());
-        }
-    }
-
-    /**
-     * Map value from container node to builder.
-     *
-     * @param configData collection of config data nodes
-     * @param stateData  collection of state data nodes
-     * @param builder    builder
-     */
-    private static <T extends NormalizedNode> void mapValueToBuilder(
-            final @NonNull Collection<T> configData, final @NonNull Collection<T> stateData,
-            final @NonNull NormalizedNodeContainerBuilder<?, PathArgument, T, ?> builder) {
-        final var configMap = configData.stream().collect(Collectors.toMap(NormalizedNode::name, Function.identity()));
-        final var stateMap = stateData.stream().collect(Collectors.toMap(NormalizedNode::name, Function.identity()));
-
-        // 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);
-    }
-
-    /**
-     * Map data with different identifiers to builder. Data with different identifiers can be just added
-     * as childs to parent node.
-     *
-     * @param configMap map of config data nodes
-     * @param stateMap  map of state data nodes
-     * @param builder   - builder
-     */
-    private static <T extends NormalizedNode> void mapDataToBuilder(
-            final @NonNull Map<PathArgument, T> configMap, final @NonNull Map<PathArgument, T> stateMap,
-            final @NonNull 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()));
-    }
-
-    /**
-     * 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 configMap immutable config data
-     * @param stateMap  immutable state data
-     * @param builder   - builder
-     */
-    @SuppressWarnings("unchecked")
-    private static <T extends NormalizedNode> void mergeDataToBuilder(
-            final @NonNull Map<PathArgument, T> configMap, final @NonNull Map<PathArgument, T> stateMap,
-            final @NonNull 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()))));
-    }
-}
index 981519b3f43fe15eb8a533e71a3cef01b44c2cd0..f046fe6b9019ce3c7869aa65a896e7778c754fda 100644 (file)
@@ -33,7 +33,6 @@ import org.opendaylight.restconf.common.patch.PatchContext;
 import org.opendaylight.restconf.common.patch.PatchEntity;
 import org.opendaylight.restconf.common.patch.PatchStatusContext;
 import org.opendaylight.restconf.nb.rfc8040.AbstractJukeboxTest;
-import org.opendaylight.restconf.nb.rfc8040.rests.utils.ReadDataTransactionUtil;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.patch.rev170222.yang.patch.yang.patch.Edit.Operation;
 import org.opendaylight.yangtools.yang.common.ErrorTag;
 import org.opendaylight.yangtools.yang.common.ErrorType;
@@ -469,7 +468,7 @@ abstract class AbstractRestconfStrategyTest extends AbstractJukeboxTest {
      */
     private @Nullable NormalizedNode readData(final @NonNull ContentParam content,
             final YangInstanceIdentifier path, final @NonNull RestconfStrategy strategy) {
-        return ReadDataTransactionUtil.readData(content, path, strategy, null, mockSchemaContext);
+        return strategy.readData(content, path, null, mockSchemaContext);
     }
 
     private static void patch(final PatchContext patchContext, final RestconfStrategy strategy, final boolean failed) {
index 93009f339231765c1db4d5ade629485e0fbad49c..2ccf2bee20e746325fbc7049b99762f52a033885 100644 (file)
@@ -33,7 +33,6 @@ import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction;
 import org.opendaylight.restconf.api.query.ContentParam;
 import org.opendaylight.restconf.api.query.WithDefaultsParam;
 import org.opendaylight.restconf.common.patch.PatchStatusContext;
-import org.opendaylight.restconf.nb.rfc8040.rests.utils.ReadDataTransactionUtil;
 import org.opendaylight.yangtools.yang.common.ErrorTag;
 import org.opendaylight.yangtools.yang.common.ErrorType;
 import org.opendaylight.yangtools.yang.common.QName;
@@ -337,8 +336,8 @@ public final class MdsalRestconfStrategyTest extends AbstractRestconfStrategyTes
         doReturn(immediateFluentFuture(Optional.of(data))).when(read)
                 .read(LogicalDatastoreType.OPERATIONAL, path);
 
-        assertEquals(data, ReadDataTransactionUtil.readData(ContentParam.ALL, path,
-            new MdsalRestconfStrategy(mockDataBroker), WithDefaultsParam.TRIM, MODULES_SCHEMA));
+        assertEquals(data, new MdsalRestconfStrategy(mockDataBroker).readData(ContentParam.ALL, path,
+            WithDefaultsParam.TRIM, MODULES_SCHEMA));
     }
 
     @Test
@@ -368,8 +367,8 @@ public final class MdsalRestconfStrategyTest extends AbstractRestconfStrategyTes
         doReturn(immediateFluentFuture(Optional.of(data))).when(read)
                 .read(LogicalDatastoreType.OPERATIONAL, path);
 
-        assertEquals(data, ReadDataTransactionUtil.readData(ContentParam.ALL, path,
-            new MdsalRestconfStrategy(mockDataBroker), WithDefaultsParam.TRIM, MODULES_SCHEMA));
+        assertEquals(data, new MdsalRestconfStrategy(mockDataBroker).readData(ContentParam.ALL, path,
+            WithDefaultsParam.TRIM, MODULES_SCHEMA));
     }
 
     @Test
@@ -392,7 +391,7 @@ public final class MdsalRestconfStrategyTest extends AbstractRestconfStrategyTes
         doReturn(immediateFluentFuture(Optional.of(content))).when(read)
                 .read(LogicalDatastoreType.OPERATIONAL, path);
 
-        assertEquals(content, ReadDataTransactionUtil.readData(ContentParam.ALL, path,
-            new MdsalRestconfStrategy(mockDataBroker), WithDefaultsParam.TRIM, MODULES_SCHEMA));
+        assertEquals(content, new MdsalRestconfStrategy(mockDataBroker).readData(ContentParam.ALL, path,
+            WithDefaultsParam.TRIM, MODULES_SCHEMA));
     }
 }