Hide NetconfFieldsTranslator 52/111252/2
authorRobert Varga <robert.varga@pantheon.tech>
Thu, 4 Apr 2024 01:16:11 +0000 (03:16 +0200)
committerRobert Varga <robert.varga@pantheon.tech>
Thu, 4 Apr 2024 01:35:25 +0000 (03:35 +0200)
There is only one user of this class, move it to the same package and
hide it by inlining it into NetconfRestconfStrategy.

Change-Id: Ib614b046bd6dcdf51e4b09d8b7cdec3b43c16629
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
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/utils/parser/NetconfFieldsTranslator.java [deleted file]
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/rests/transactions/NetconfFieldsParamTest.java [moved from restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/utils/parser/NetconfFieldsTranslatorTest.java with 96% similarity]

index b29249d6fa17db47e5c9254bedb90b5e6cee3194..10ff23f32d87bb9702b2afaba6db0c405006c951 100644 (file)
@@ -9,14 +9,19 @@ package org.opendaylight.restconf.nb.rfc8040.rests.transactions;
 
 import static java.util.Objects.requireNonNull;
 
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.util.concurrent.FutureCallback;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.MoreExecutors;
 import com.google.common.util.concurrent.SettableFuture;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Optional;
+import java.util.Set;
 import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
 import org.opendaylight.mdsal.common.api.CommitInfo;
@@ -29,19 +34,32 @@ import org.opendaylight.mdsal.dom.api.DOMSchemaService.YangTextSourceExtension;
 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.FieldsParam;
+import org.opendaylight.restconf.api.query.FieldsParam.NodeSelector;
 import org.opendaylight.restconf.api.query.WithDefaultsParam;
 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
 import org.opendaylight.restconf.common.errors.RestconfFuture;
 import org.opendaylight.restconf.common.errors.SettableRestconfFuture;
 import org.opendaylight.restconf.nb.rfc8040.legacy.QueryParameters;
-import org.opendaylight.restconf.nb.rfc8040.utils.parser.NetconfFieldsTranslator;
 import org.opendaylight.restconf.server.api.DataGetParams;
 import org.opendaylight.restconf.server.api.DataGetResult;
 import org.opendaylight.restconf.server.api.DatabindContext;
 import org.opendaylight.restconf.server.api.DatabindPath.Data;
 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.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.util.DataSchemaContext;
+import org.opendaylight.yangtools.yang.data.util.DataSchemaContext.PathMixin;
+import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
+import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
 
 /**
  * Implementation of RESTCONF operations on top of a raw NETCONF backend.
@@ -89,7 +107,7 @@ public final class NetconfRestconfStrategy extends RestconfStrategy {
         if (fields != null) {
             final List<YangInstanceIdentifier> tmp;
             try {
-                tmp = NetconfFieldsTranslator.translate(inference.modelContext(), path.schema(), fields);
+                tmp = fieldsParamToPaths(inference.modelContext(), path.schema(), fields);
             } catch (RestconfDocumentedException e) {
                 return RestconfFuture.failed(e);
             }
@@ -193,4 +211,166 @@ public final class NetconfRestconfStrategy extends RestconfStrategy {
         }, MoreExecutors.directExecutor());
         return ret;
     }
+
+    /**
+     * Translate a {@link FieldsParam} to a list of child node paths saved in lists, suitable for use with
+     * {@link NetconfDataTreeService}.
+     *
+     * <p>
+     * Fields parser that stores a set of all the leaf {@link LinkedPathElement}s specified in {@link FieldsParam}.
+     * Using {@link LinkedPathElement} it is possible to create a chain of path arguments and build complete paths
+     * since this element contains identifiers of intermediary mixin nodes and also linked to its parent
+     * {@link LinkedPathElement}.
+     *
+     * <p>
+     * Example: field 'a(b/c;d/e)' ('e' is place under choice node 'x') is parsed into following levels:
+     * <pre>
+     *   - './a' +- 'a/b' - 'b/c'
+     *           |
+     *           +- 'a/d' - 'd/x/e'
+     * </pre>
+     *
+     *
+     * @param modelContext EffectiveModelContext
+     * @param startNode Root DataSchemaNode
+     * @param input input value of fields parameter
+     * @return {@link List} of {@link YangInstanceIdentifier} that are relative to the last {@link PathArgument}
+     *         of provided {@code identifier}
+     */
+    @VisibleForTesting
+    static @NonNull List<YangInstanceIdentifier> fieldsParamToPaths(final @NonNull EffectiveModelContext modelContext,
+            final @NonNull DataSchemaContext startNode, final @NonNull FieldsParam input) {
+        final var parsed = new HashSet<LinkedPathElement>();
+        processSelectors(parsed, modelContext, startNode.dataSchemaNode().getQName().getModule(),
+            new LinkedPathElement(null, List.of(), startNode), input.nodeSelectors());
+        return parsed.stream().map(NetconfRestconfStrategy::buildPath).toList();
+    }
+
+    private static void processSelectors(final Set<LinkedPathElement> parsed, final EffectiveModelContext context,
+            final QNameModule startNamespace, final LinkedPathElement startPathElement,
+            final List<NodeSelector> selectors) {
+        for (var selector : selectors) {
+            var pathElement = startPathElement;
+            var namespace = startNamespace;
+
+            // Note: path is guaranteed to have at least one step
+            final var it = selector.path().iterator();
+            do {
+                final var step = it.next();
+                final var module = step.module();
+                if (module != null) {
+                    // FIXME: this is not defensive enough, as we can fail to find the module
+                    namespace = context.findModules(module).iterator().next().getQNameModule();
+                }
+
+                // add parsed path element linked to its parent
+                pathElement = addChildPathElement(pathElement, step.identifier().bindTo(namespace));
+            } while (it.hasNext());
+
+            final var subs = selector.subSelectors();
+            if (!subs.isEmpty()) {
+                processSelectors(parsed, context, namespace, pathElement, subs);
+            } else {
+                parsed.add(pathElement);
+            }
+        }
+    }
+
+    private static LinkedPathElement addChildPathElement(final LinkedPathElement currentElement,
+            final QName childQName) {
+        final var collectedMixinNodes = new ArrayList<PathArgument>();
+
+        DataSchemaContext currentNode = currentElement.targetNode;
+        DataSchemaContext actualContextNode = childByQName(currentNode, childQName);
+        if (actualContextNode == null) {
+            actualContextNode = resolveMixinNode(currentNode, currentNode.getPathStep().getNodeType());
+            actualContextNode = childByQName(actualContextNode, childQName);
+        }
+
+        while (actualContextNode != null && actualContextNode instanceof PathMixin) {
+            final var actualDataSchemaNode = actualContextNode.dataSchemaNode();
+            if (actualDataSchemaNode instanceof ListSchemaNode listSchema && listSchema.getKeyDefinition().isEmpty()) {
+                // we need just a single node identifier from list in the path IFF it is an unkeyed list, otherwise
+                // we need both (which is the default case)
+                actualContextNode = childByQName(actualContextNode, childQName);
+            } else if (actualDataSchemaNode instanceof LeafListSchemaNode) {
+                // NodeWithValue is unusable - stop parsing
+                break;
+            } else {
+                collectedMixinNodes.add(actualContextNode.getPathStep());
+                actualContextNode = childByQName(actualContextNode, childQName);
+            }
+        }
+
+        if (actualContextNode == null) {
+            throw new RestconfDocumentedException("Child " + childQName.getLocalName() + " node missing in "
+                + currentNode.getPathStep().getNodeType().getLocalName(),
+                ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
+        }
+
+        return new LinkedPathElement(currentElement, collectedMixinNodes, actualContextNode);
+    }
+
+    private static @Nullable DataSchemaContext childByQName(final DataSchemaContext parent, final QName qname) {
+        return parent instanceof DataSchemaContext.Composite composite ? composite.childByQName(qname) : null;
+    }
+
+    private static YangInstanceIdentifier buildPath(final LinkedPathElement lastPathElement) {
+        var pathElement = lastPathElement;
+        final var path = new LinkedList<PathArgument>();
+        do {
+            path.addFirst(contextPathArgument(pathElement.targetNode));
+            path.addAll(0, pathElement.mixinNodesToTarget);
+            pathElement = pathElement.parentPathElement;
+        } while (pathElement.parentPathElement != null);
+
+        return YangInstanceIdentifier.of(path);
+    }
+
+    private static @NonNull PathArgument contextPathArgument(final DataSchemaContext context) {
+        final var arg = context.pathStep();
+        if (arg != null) {
+            return arg;
+        }
+
+        final var schema = context.dataSchemaNode();
+        if (schema instanceof ListSchemaNode listSchema && !listSchema.getKeyDefinition().isEmpty()) {
+            return NodeIdentifierWithPredicates.of(listSchema.getQName());
+        }
+        if (schema instanceof LeafListSchemaNode leafListSchema) {
+            return new NodeWithValue<>(leafListSchema.getQName(), Empty.value());
+        }
+        throw new UnsupportedOperationException("Unsupported schema " + schema);
+    }
+
+    private static DataSchemaContext resolveMixinNode(final DataSchemaContext node,
+            final @NonNull QName qualifiedName) {
+        DataSchemaContext currentNode = node;
+        while (currentNode != null && currentNode instanceof PathMixin currentMixin) {
+            currentNode = currentMixin.childByQName(qualifiedName);
+        }
+        return currentNode;
+    }
+
+    /**
+     * {@link DataSchemaContext} of data element grouped with identifiers of leading mixin nodes and previous path
+     * element.<br>
+     *  - identifiers of mixin nodes on the path to the target node - required for construction of full valid
+     *    DOM paths,<br>
+     *  - {@link LinkedPathElement} of the previous non-mixin node - required to successfully create a chain
+     *    of {@link PathArgument}s
+     *
+     * @param parentPathElement     parent path element
+     * @param mixinNodesToTarget    identifiers of mixin nodes on the path to the target node
+     * @param targetNode            target non-mixin node
+     */
+    private record LinkedPathElement(
+            @Nullable LinkedPathElement parentPathElement,
+            @NonNull List<PathArgument> mixinNodesToTarget,
+            @NonNull DataSchemaContext targetNode) {
+        LinkedPathElement {
+            requireNonNull(mixinNodesToTarget);
+            requireNonNull(targetNode);
+        }
+    }
 }
diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/utils/parser/NetconfFieldsTranslator.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/utils/parser/NetconfFieldsTranslator.java
deleted file mode 100644 (file)
index 01f8044..0000000
+++ /dev/null
@@ -1,208 +0,0 @@
-/*
- * Copyright © 2020 FRINX s.r.o. and others.  All rights reserved.
- * Copyright © 2021 PANTHEON.tech, s.r.o.
- *
- * 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.utils.parser;
-
-import static java.util.Objects.requireNonNull;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Set;
-import org.eclipse.jdt.annotation.NonNull;
-import org.eclipse.jdt.annotation.Nullable;
-import org.opendaylight.netconf.dom.api.NetconfDataTreeService;
-import org.opendaylight.restconf.api.query.FieldsParam;
-import org.opendaylight.restconf.api.query.FieldsParam.NodeSelector;
-import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
-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.NodeIdentifierWithPredicates;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
-import org.opendaylight.yangtools.yang.data.util.DataSchemaContext;
-import org.opendaylight.yangtools.yang.data.util.DataSchemaContext.PathMixin;
-import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
-import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
-
-/**
- * A translator between {@link FieldsParam} and {@link YangInstanceIdentifier}s suitable for use as field identifiers
- * in {@code netconf-dom-api}.
- *
- * <p>
- * Fields parser that stores a set of all the leaf {@link LinkedPathElement}s specified in {@link FieldsParam}.
- * Using {@link LinkedPathElement} it is possible to create a chain of path arguments and build complete paths
- * since this element contains identifiers of intermediary mixin nodes and also linked to its parent
- * {@link LinkedPathElement}.
- *
- * <p>
- * Example: field 'a(b/c;d/e)' ('e' is place under choice node 'x') is parsed into following levels:
- * <pre>
- *   - './a' +- 'a/b' - 'b/c'
- *           |
- *           +- 'a/d' - 'd/x/e'
- * </pre>
- */
-public final class NetconfFieldsTranslator {
-    /**
-     * {@link DataSchemaContext} of data element grouped with identifiers of leading mixin nodes and previous path
-     * element.<br>
-     *  - identifiers of mixin nodes on the path to the target node - required for construction of full valid
-     *    DOM paths,<br>
-     *  - {@link LinkedPathElement} of the previous non-mixin node - required to successfully create a chain
-     *    of {@link PathArgument}s
-     *
-     * @param parentPathElement     parent path element
-     * @param mixinNodesToTarget    identifiers of mixin nodes on the path to the target node
-     * @param targetNode            target non-mixin node
-     */
-    private record LinkedPathElement(
-            @Nullable LinkedPathElement parentPathElement,
-            @NonNull List<PathArgument> mixinNodesToTarget,
-            @NonNull DataSchemaContext targetNode) {
-        LinkedPathElement {
-            requireNonNull(mixinNodesToTarget);
-            requireNonNull(targetNode);
-        }
-    }
-
-    private NetconfFieldsTranslator() {
-        // Hidden on purpose
-    }
-
-    /**
-     * Translate a {@link FieldsParam} to a list of child node paths saved in lists, suitable for use with
-     * {@link NetconfDataTreeService}.
-     *
-     * @param modelContext EffectiveModelContext
-     * @param startNode Root DataSchemaNode
-     * @param input input value of fields parameter
-     * @return {@link List} of {@link YangInstanceIdentifier} that are relative to the last {@link PathArgument}
-     *         of provided {@code identifier}
-     */
-    public static @NonNull List<YangInstanceIdentifier> translate(
-            final @NonNull EffectiveModelContext modelContext, final @NonNull DataSchemaContext startNode,
-            final @NonNull FieldsParam input) {
-        final var parsed = new HashSet<LinkedPathElement>();
-        processSelectors(parsed, modelContext, startNode.dataSchemaNode().getQName().getModule(),
-            new LinkedPathElement(null, List.of(), startNode), input.nodeSelectors());
-        return parsed.stream().map(NetconfFieldsTranslator::buildPath).toList();
-    }
-
-    private static void processSelectors(final Set<LinkedPathElement> parsed, final EffectiveModelContext context,
-            final QNameModule startNamespace, final LinkedPathElement startPathElement,
-            final List<NodeSelector> selectors) {
-        for (var selector : selectors) {
-            var pathElement = startPathElement;
-            var namespace = startNamespace;
-
-            // Note: path is guaranteed to have at least one step
-            final var it = selector.path().iterator();
-            do {
-                final var step = it.next();
-                final var module = step.module();
-                if (module != null) {
-                    // FIXME: this is not defensive enough, as we can fail to find the module
-                    namespace = context.findModules(module).iterator().next().getQNameModule();
-                }
-
-                // add parsed path element linked to its parent
-                pathElement = addChildPathElement(pathElement, step.identifier().bindTo(namespace));
-            } while (it.hasNext());
-
-            final var subs = selector.subSelectors();
-            if (!subs.isEmpty()) {
-                processSelectors(parsed, context, namespace, pathElement, subs);
-            } else {
-                parsed.add(pathElement);
-            }
-        }
-    }
-
-    private static LinkedPathElement addChildPathElement(final LinkedPathElement currentElement,
-            final QName childQName) {
-        final var collectedMixinNodes = new ArrayList<PathArgument>();
-
-        DataSchemaContext currentNode = currentElement.targetNode;
-        DataSchemaContext actualContextNode = childByQName(currentNode, childQName);
-        if (actualContextNode == null) {
-            actualContextNode = resolveMixinNode(currentNode, currentNode.getPathStep().getNodeType());
-            actualContextNode = childByQName(actualContextNode, childQName);
-        }
-
-        while (actualContextNode != null && actualContextNode instanceof PathMixin) {
-            final var actualDataSchemaNode = actualContextNode.dataSchemaNode();
-            if (actualDataSchemaNode instanceof ListSchemaNode listSchema && listSchema.getKeyDefinition().isEmpty()) {
-                // we need just a single node identifier from list in the path IFF it is an unkeyed list, otherwise
-                // we need both (which is the default case)
-                actualContextNode = childByQName(actualContextNode, childQName);
-            } else if (actualDataSchemaNode instanceof LeafListSchemaNode) {
-                // NodeWithValue is unusable - stop parsing
-                break;
-            } else {
-                collectedMixinNodes.add(actualContextNode.getPathStep());
-                actualContextNode = childByQName(actualContextNode, childQName);
-            }
-        }
-
-        if (actualContextNode == null) {
-            throw new RestconfDocumentedException("Child " + childQName.getLocalName() + " node missing in "
-                + currentNode.getPathStep().getNodeType().getLocalName(),
-                ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
-        }
-
-        return new LinkedPathElement(currentElement, collectedMixinNodes, actualContextNode);
-    }
-
-    private static @Nullable DataSchemaContext childByQName(final DataSchemaContext parent, final QName qname) {
-        return parent instanceof DataSchemaContext.Composite composite ? composite.childByQName(qname) : null;
-    }
-
-    private static YangInstanceIdentifier buildPath(final LinkedPathElement lastPathElement) {
-        LinkedPathElement pathElement = lastPathElement;
-        final var path = new LinkedList<PathArgument>();
-        do {
-            path.addFirst(contextPathArgument(pathElement.targetNode));
-            path.addAll(0, pathElement.mixinNodesToTarget);
-            pathElement = pathElement.parentPathElement;
-        } while (pathElement.parentPathElement != null);
-
-        return YangInstanceIdentifier.of(path);
-    }
-
-    private static @NonNull PathArgument contextPathArgument(final DataSchemaContext context) {
-        final var arg = context.pathStep();
-        if (arg != null) {
-            return arg;
-        }
-
-        final var schema = context.dataSchemaNode();
-        if (schema instanceof ListSchemaNode listSchema && !listSchema.getKeyDefinition().isEmpty()) {
-            return NodeIdentifierWithPredicates.of(listSchema.getQName());
-        }
-        if (schema instanceof LeafListSchemaNode leafListSchema) {
-            return new NodeWithValue<>(leafListSchema.getQName(), Empty.value());
-        }
-        throw new UnsupportedOperationException("Unsupported schema " + schema);
-    }
-
-    private static DataSchemaContext resolveMixinNode(final DataSchemaContext node,
-            final @NonNull QName qualifiedName) {
-        DataSchemaContext currentNode = node;
-        while (currentNode != null && currentNode instanceof PathMixin currentMixin) {
-            currentNode = currentMixin.childByQName(qualifiedName);
-        }
-        return currentNode;
-    }
-}
@@ -6,7 +6,7 @@
  * 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.utils.parser;
+package org.opendaylight.restconf.nb.rfc8040.rests.transactions;
 
 import static org.junit.Assert.assertEquals;
 
@@ -16,6 +16,7 @@ import java.util.stream.Collectors;
 import org.junit.runner.RunWith;
 import org.mockito.junit.MockitoJUnitRunner;
 import org.opendaylight.restconf.api.query.FieldsParam;
+import org.opendaylight.restconf.nb.rfc8040.utils.parser.AbstractFieldsTranslatorTest;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
@@ -24,14 +25,14 @@ import org.opendaylight.yangtools.yang.data.util.DataSchemaContext;
 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
 
 /**
- * Unit test for {@link NetconfFieldsTranslator}.
+ * Unit test for {@link NetconfFieldsParam}.
  */
 @RunWith(MockitoJUnitRunner.class)
-public class NetconfFieldsTranslatorTest extends AbstractFieldsTranslatorTest<YangInstanceIdentifier> {
+public class NetconfFieldsParamTest extends AbstractFieldsTranslatorTest<YangInstanceIdentifier> {
     @Override
     protected List<YangInstanceIdentifier> translateFields(final EffectiveModelContext modelContext,
             final DataSchemaContext startNode, final FieldsParam fields) {
-        return NetconfFieldsTranslator.translate(modelContext, startNode, fields);
+        return NetconfRestconfStrategy.fieldsParamToPaths(modelContext, startNode, fields);
     }
 
     @Override