Eliminate InstanceIdentifierContext 70/109070/16
authorRobert Varga <robert.varga@pantheon.tech>
Tue, 28 Nov 2023 18:42:41 +0000 (19:42 +0100)
committerRobert Varga <robert.varga@pantheon.tech>
Fri, 1 Dec 2023 20:08:22 +0000 (21:08 +0100)
Rework the way we lookup strategies and generally resolve ApiPath to
actual request paths.

The primary logic now rests with RestconfStrategy, which has a way to
recursively deal with yang-ext:mount steps, resulting in a strategy +
ApiPath tail.

These are then picked up by MdsalRestconfServer and routed to the target
strategy's actual method -- which then interprets the tail ApiPath as
appropriate.

JIRA: NETCONF-1157
Change-Id: I8a6391f5538ba5662bad1bef79186e9a1e08ae81
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
33 files changed:
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/databind/PatchBody.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/legacy/InstanceIdentifierContext.java [deleted file]
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/legacy/NormalizedNodePayload.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/legacy/QueryParameters.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/transactions/MdsalRestconfStrategy.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/utils/parser/NetconfFieldsTranslator.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/utils/parser/WriterFieldsTranslator.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/DataPostPath.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/DataPutPath.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/mdsal/MdsalRestconfServer.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/ApiPathNormalizer.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/RestconfDataDeleteTest.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/RestconfDataGetTest.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/RestconfDataPutTest.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/RestconfModulesGetTest.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/RestconfOperationsGetTest.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/RestconfOperationsPostTest.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/databind/AbstractPatchBodyTest.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/databind/AbstractResourceBodyTest.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/databind/XmlResourceBodyTest.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/databind/jaxrs/QueryParamsTest.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/legacy/InstanceIdentifierContextTest.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
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/rests/transactions/NetconfRestconfStrategyTest.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/utils/parser/AbstractFieldsTranslatorTest.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/utils/parser/NetconfFieldsTranslatorTest.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/utils/parser/WriterFieldsTranslatorTest.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/utils/parser/YangInstanceIdentifierSerializerTest.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/server/mdsal/MdsalRestconfServerTest.java [deleted file]
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/server/spi/ApiPathNormalizerTest.java

index b0b9a73b36689dbc7677795a50757b2b53396d92..24ea9f2409f8f917437b58441af121a749775128 100644 (file)
@@ -16,9 +16,9 @@ import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.restconf.api.ApiPath;
 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
 import org.opendaylight.restconf.common.patch.PatchContext;
-import org.opendaylight.restconf.nb.rfc8040.legacy.InstanceIdentifierContext;
 import org.opendaylight.restconf.nb.rfc8040.utils.parser.YangInstanceIdentifierSerializer;
 import org.opendaylight.restconf.server.api.DataPatchPath;
+import org.opendaylight.restconf.server.spi.ApiPathNormalizer;
 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;
@@ -49,6 +49,8 @@ public abstract sealed class PatchBody extends AbstractBody permits JsonPatchBod
             return urlPath;
         }
 
+        // FIXME: NETCONF-1157: these two operations should really be a single ApiPathNormalizer step -- but for that
+        //                      we need to switch to ApiPath forms
         final var databind = path.databind();
         final String targetUrl;
         if (urlPath.isEmpty()) {
@@ -58,8 +60,7 @@ public abstract sealed class PatchBody extends AbstractBody permits JsonPatchBod
         }
 
         try {
-            return InstanceIdentifierContext.ofApiPath(ApiPath.parse(targetUrl), databind, null)
-                .getInstanceIdentifier();
+            return new ApiPathNormalizer(databind).normalizeDataPath(ApiPath.parse(targetUrl)).instance();
         } catch (ParseException | RestconfDocumentedException e) {
             throw new RestconfDocumentedException("Failed to parse target " + target,
                 ErrorType.RPC, ErrorTag.MALFORMED_MESSAGE, e);
diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/legacy/InstanceIdentifierContext.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/legacy/InstanceIdentifierContext.java
deleted file mode 100644 (file)
index efd95be..0000000
+++ /dev/null
@@ -1,217 +0,0 @@
-/*
- * Copyright (c) 2014 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.legacy;
-
-import static com.google.common.base.Verify.verify;
-import static java.util.Objects.requireNonNull;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.ImmutableList;
-import org.eclipse.jdt.annotation.NonNull;
-import org.eclipse.jdt.annotation.Nullable;
-import org.opendaylight.mdsal.dom.api.DOMMountPoint;
-import org.opendaylight.mdsal.dom.api.DOMMountPointService;
-import org.opendaylight.mdsal.dom.api.DOMSchemaService;
-import org.opendaylight.restconf.api.ApiPath;
-import org.opendaylight.restconf.api.ApiPath.Step;
-import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
-import org.opendaylight.restconf.server.api.DatabindContext;
-import org.opendaylight.restconf.server.spi.ApiPathNormalizer;
-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.model.api.SchemaNode;
-import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
-import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
-
-public abstract class InstanceIdentifierContext {
-    private static final class Root extends InstanceIdentifierContext {
-        Root(final DatabindContext databind, final DOMMountPoint mountPoint) {
-            super(databind, databind.modelContext(), mountPoint);
-        }
-
-        @Override
-        public YangInstanceIdentifier getInstanceIdentifier() {
-            return YangInstanceIdentifier.of();
-        }
-
-        @Override
-        public Inference inference() {
-            return SchemaInferenceStack.of(databind().modelContext()).toInference();
-        }
-    }
-
-    private static final class DataPath extends InstanceIdentifierContext {
-        private final @NonNull YangInstanceIdentifier path;
-        private final @NonNull SchemaInferenceStack stack;
-
-        private DataPath(final DatabindContext databind, final SchemaNode schemaNode, final DOMMountPoint mountPoint,
-                final SchemaInferenceStack stack, final YangInstanceIdentifier path) {
-            super(databind, schemaNode, mountPoint);
-            this.stack = requireNonNull(stack);
-            this.path = requireNonNull(path);
-        }
-
-        static @NonNull DataPath of(final DatabindContext databind, final YangInstanceIdentifier path,
-                final DOMMountPoint mountPoint) {
-            final var nodeAndStack = databind.schemaTree().enterPath(path).orElseThrow();
-            return new DataPath(databind, nodeAndStack.node().dataSchemaNode(), mountPoint, nodeAndStack.stack(), path);
-        }
-
-        @Override
-        public YangInstanceIdentifier getInstanceIdentifier() {
-            return path;
-        }
-
-        @Override
-        public Inference inference() {
-            return stack.toInference();
-        }
-    }
-
-    private static final class WithoutDataPath extends InstanceIdentifierContext {
-        private final @NonNull SchemaInferenceStack stack;
-
-        private WithoutDataPath(final DatabindContext databind, final SchemaNode schemaNode,
-                final DOMMountPoint mountPoint, final SchemaInferenceStack stack) {
-            super(databind, schemaNode, mountPoint);
-            this.stack = requireNonNull(stack);
-        }
-
-        @Override
-        public Inference inference() {
-            return stack.toInference();
-        }
-
-        @Override
-        public @Nullable YangInstanceIdentifier getInstanceIdentifier() {
-            return null;
-        }
-    }
-
-    private final @NonNull DatabindContext databind;
-    private final @NonNull SchemaNode schemaNode;
-    private final @Nullable DOMMountPoint mountPoint;
-
-    InstanceIdentifierContext(final DatabindContext databind, final SchemaNode schemaNode,
-            final DOMMountPoint mountPoint) {
-        this.databind = requireNonNull(databind);
-        this.schemaNode = requireNonNull(schemaNode);
-        this.mountPoint = mountPoint;
-    }
-
-    // FIXME: NETCONF-773: this recursion should really live in MdsalRestconfServer
-    public static @NonNull InstanceIdentifierContext ofApiPath(final ApiPath path, final DatabindContext databind,
-            final DOMMountPointService mountPointService) {
-        final var steps = path.steps();
-        final var limit = steps.size() - 1;
-
-        var prefix = 0;
-        DOMMountPoint currentMountPoint = null;
-        var currentDatabind = databind;
-        while (prefix <= limit) {
-            final var mount = indexOfMount(steps, prefix, limit);
-            if (mount == -1) {
-                break;
-            }
-
-            final var mountService = currentMountPoint == null ? mountPointService
-                : currentMountPoint.getService(DOMMountPointService.class).orElse(null);
-            if (mountService == null) {
-                throw new RestconfDocumentedException("Mount point service is not available",
-                    ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED);
-            }
-
-            final var mountPath = new ApiPathNormalizer(databind).normalizePath(path.subPath(prefix, mount)).path;
-            final var userPath = path.subPath(0, mount);
-            final var nextMountPoint = mountService.getMountPoint(mountPath)
-                .orElseThrow(() -> new RestconfDocumentedException("Mount point '" + userPath + "' does not exist",
-                    ErrorType.PROTOCOL, ErrorTags.RESOURCE_DENIED_TRANSPORT));
-            final var nextModelContext = nextMountPoint.getService(DOMSchemaService.class)
-                .orElseThrow(() -> new RestconfDocumentedException(
-                    "Mount point '" + userPath + "' does not expose DOMSchemaService",
-                    ErrorType.PROTOCOL, ErrorTags.RESOURCE_DENIED_TRANSPORT))
-                .getGlobalContext();
-            if (nextModelContext == null) {
-                throw new RestconfDocumentedException("Mount point '" + userPath + "' does not have any models",
-                    ErrorType.PROTOCOL, ErrorTags.RESOURCE_DENIED_TRANSPORT);
-            }
-
-            prefix = mount + 1;
-            currentDatabind = DatabindContext.ofModel(nextModelContext);
-            currentMountPoint = nextMountPoint;
-        }
-
-        final var result = new ApiPathNormalizer(currentDatabind).normalizePath(path.subPath(prefix));
-        return InstanceIdentifierContext.ofPath(currentDatabind, result.stack, result.node, result.path,
-            currentMountPoint);
-    }
-
-    private static int indexOfMount(final ImmutableList<Step> steps, final int fromIndex, final int limit) {
-        for (int i = fromIndex; i <= limit; ++ i) {
-            final var step = steps.get(i);
-            if ("yang-ext".equals(step.module()) && "mount".equals(step.identifier().getLocalName())) {
-                return i;
-            }
-        }
-        return -1;
-    }
-
-    public static @NonNull InstanceIdentifierContext ofLocalRoot(final DatabindContext databind) {
-        return new Root(databind, null);
-    }
-
-    @VisibleForTesting
-    public static @NonNull InstanceIdentifierContext ofLocalPath(final DatabindContext databind,
-            final YangInstanceIdentifier path) {
-        return DataPath.of(databind, path, null);
-    }
-
-    // Invocations of various identifier-less details
-    public static @NonNull InstanceIdentifierContext ofStack(final DatabindContext databind,
-            final SchemaInferenceStack stack) {
-        return ofStack(databind, stack, null);
-    }
-
-    // Invocations of various identifier-less details, potentially having a mount point
-    public static @NonNull InstanceIdentifierContext ofStack(final DatabindContext databind,
-            final SchemaInferenceStack stack, final @Nullable DOMMountPoint mountPoint) {
-        final SchemaNode schemaNode;
-        if (!stack.isEmpty()) {
-            final var stmt = stack.currentStatement();
-            verify(stmt instanceof SchemaNode, "Unexpected statement %s", stmt);
-            schemaNode = (SchemaNode) stmt;
-        } else {
-            schemaNode = stack.getEffectiveModelContext();
-        }
-
-        return new WithoutDataPath(databind, schemaNode, mountPoint, stack);
-    }
-
-    public static @NonNull InstanceIdentifierContext ofPath(final DatabindContext databind,
-            final SchemaInferenceStack stack, final SchemaNode schemaNode, final YangInstanceIdentifier path,
-            final @Nullable DOMMountPoint mountPoint) {
-        return new DataPath(databind, schemaNode, mountPoint, stack, path);
-    }
-
-    public final @NonNull DatabindContext databind() {
-        return databind;
-    }
-
-    public final @NonNull SchemaNode getSchemaNode() {
-        return schemaNode;
-    }
-
-    public final @Nullable DOMMountPoint getMountPoint() {
-        return mountPoint;
-    }
-
-    public abstract @NonNull Inference inference();
-
-    public abstract @Nullable YangInstanceIdentifier getInstanceIdentifier();
-}
index 272695ff8415a69e392a0f3dc467f5b600926231..3cc4f755b142cf82b8db6047f4f830fa7173fc90 100644 (file)
@@ -26,6 +26,6 @@ public record NormalizedNodePayload(Inference inference, NormalizedNode data, Qu
     }
 
     public NormalizedNodePayload(final Inference inference, final NormalizedNode data) {
-        this(inference, data, QueryParameters.empty());
+        this(inference, data, QueryParameters.EMPTY);
     }
 }
index 6d2015b6c5b3775cc32dd2b26dd75a079079e775..65cc2e0d5daed4e27c700a85d9b7884b3ab2b34f 100644 (file)
@@ -7,8 +7,6 @@
  */
 package org.opendaylight.restconf.nb.rfc8040.legacy;
 
-import static java.util.Objects.requireNonNull;
-
 import com.google.common.annotations.Beta;
 import java.util.List;
 import java.util.Set;
@@ -18,7 +16,6 @@ import org.opendaylight.restconf.api.query.DepthParam;
 import org.opendaylight.restconf.api.query.PrettyPrintParam;
 import org.opendaylight.restconf.server.api.DataGetParams;
 import org.opendaylight.yangtools.yang.common.QName;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 
 /**
  * This holds various options acquired from a requests's query part. This class needs to be further split up to make
@@ -27,50 +24,19 @@ import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
  */
 @Beta
 // FIXME: this probably needs to be renamed back to WriterParams, or somesuch
-public final class QueryParameters {
-    private static final @NonNull QueryParameters EMPTY = of(DataGetParams.EMPTY);
-
-    private final @NonNull DataGetParams params;
-    private final List<YangInstanceIdentifier> fieldPaths;
-    private final List<Set<QName>> fields;
-
-    private QueryParameters(final DataGetParams params, final List<Set<QName>> fields,
-            final List<YangInstanceIdentifier> fieldPaths) {
-        this.params = requireNonNull(params);
-        this.fields = fields;
-        this.fieldPaths = fieldPaths;
-    }
-
-    public static @NonNull QueryParameters empty() {
-        return EMPTY;
-    }
+public record QueryParameters(
+        @Nullable DepthParam depth,
+        @Nullable PrettyPrintParam prettyPrint,
+        @Nullable List<Set<QName>> fields) {
+    public static final @NonNull QueryParameters EMPTY = new QueryParameters(null, null, null);
 
     public static @NonNull QueryParameters of(final DataGetParams params) {
-        return new QueryParameters(params, null, null);
-    }
-
-    public static @NonNull QueryParameters ofFields(final DataGetParams params, final List<Set<QName>> fields) {
-        return new QueryParameters(params, fields, null);
-    }
-
-    public static @NonNull QueryParameters ofFieldPaths(final DataGetParams params,
-            final List<YangInstanceIdentifier> fieldPaths) {
-        return new QueryParameters(params, null, fieldPaths);
-    }
-
-    public @Nullable DepthParam depth() {
-        return params.depth();
-    }
-
-    public @Nullable PrettyPrintParam prettyPrint() {
-        return params.prettyPrint();
-    }
-
-    public @Nullable List<Set<QName>> fields() {
-        return fields;
+        final var depth = params.depth();
+        final var prettyPrint = params.prettyPrint();
+        return depth == null && prettyPrint == null ? EMPTY : new QueryParameters(depth, prettyPrint, null);
     }
 
-    public @Nullable List<YangInstanceIdentifier> fieldPaths() {
-        return fieldPaths;
+    public static @NonNull QueryParameters of(final DataGetParams params, final List<Set<QName>> fields) {
+        return fields == null ? of(params) : new QueryParameters(params.depth(), params.prettyPrint(), fields);
     }
 }
index 1c040dc57981a6a180b980bb593d991dc1a01890..1198ca47c3cd0b866dac6dd28c910076af60f7c9 100644 (file)
@@ -12,22 +12,28 @@ import static org.opendaylight.mdsal.common.api.LogicalDatastoreType.CONFIGURATI
 
 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 java.util.List;
 import java.util.Optional;
 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.DOMActionService;
 import org.opendaylight.mdsal.dom.api.DOMDataBroker;
 import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction;
+import org.opendaylight.mdsal.dom.api.DOMMountPointService;
 import org.opendaylight.mdsal.dom.api.DOMRpcService;
 import org.opendaylight.mdsal.dom.api.DOMTransactionChain;
 import org.opendaylight.mdsal.dom.api.DOMYangTextSourceProvider;
 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.NormalizedNodePayload;
+import org.opendaylight.restconf.nb.rfc8040.legacy.QueryParameters;
+import org.opendaylight.restconf.nb.rfc8040.utils.parser.WriterFieldsTranslator;
+import org.opendaylight.restconf.server.api.DataGetParams;
 import org.opendaylight.restconf.server.api.DatabindContext;
+import org.opendaylight.restconf.server.spi.ApiPathNormalizer.DataPath;
 import org.opendaylight.restconf.server.spi.RpcImplementation;
 import org.opendaylight.yangtools.yang.common.Empty;
 import org.opendaylight.yangtools.yang.common.ErrorTag;
@@ -46,15 +52,19 @@ public final class MdsalRestconfStrategy extends RestconfStrategy {
     private final DOMDataBroker dataBroker;
 
     public MdsalRestconfStrategy(final DatabindContext databind, final DOMDataBroker dataBroker,
-            final @Nullable DOMRpcService rpcService, final @Nullable DOMYangTextSourceProvider sourceProvider,
+            final @Nullable DOMRpcService rpcService, final @Nullable DOMActionService actionService,
+            final @Nullable DOMYangTextSourceProvider sourceProvider,
+            final @Nullable DOMMountPointService mountPointService,
             final ImmutableMap<QName, RpcImplementation> localRpcs) {
-        super(databind, localRpcs, rpcService, sourceProvider);
+        super(databind, localRpcs, rpcService, actionService, sourceProvider, mountPointService);
         this.dataBroker = requireNonNull(dataBroker);
     }
 
     public MdsalRestconfStrategy(final DatabindContext databind, final DOMDataBroker dataBroker,
-            final @Nullable DOMRpcService rpcService, final @Nullable  DOMYangTextSourceProvider sourceProvider) {
-        this(databind, dataBroker, rpcService, sourceProvider, ImmutableMap.of());
+            final @Nullable DOMRpcService rpcService, final @Nullable DOMActionService actionService,
+            final @Nullable DOMYangTextSourceProvider sourceProvider,
+            final @Nullable DOMMountPointService mountPointService) {
+        this(databind, dataBroker, rpcService, actionService, sourceProvider, mountPointService, ImmutableMap.of());
     }
 
     @Override
@@ -101,6 +111,16 @@ public final class MdsalRestconfStrategy extends RestconfStrategy {
         }, MoreExecutors.directExecutor());
     }
 
+    @Override
+    RestconfFuture<NormalizedNodePayload> dataGET(final DataPath path, final DataGetParams params) {
+        final var inference = path.inference();
+        final var fields = params.fields();
+        final var translatedFields = fields == null ? null
+            : WriterFieldsTranslator.translate(inference.getEffectiveModelContext(), path.schema(), fields);
+        return completeDataGET(inference, QueryParameters.of(params, translatedFields),
+            readData(params.content(), path.instance(), params.withDefaults()));
+    }
+
     @Override
     ListenableFuture<Optional<NormalizedNode>> read(final LogicalDatastoreType store,
             final YangInstanceIdentifier path) {
@@ -109,13 +129,6 @@ public final class MdsalRestconfStrategy extends RestconfStrategy {
         }
     }
 
-    @Override
-    ListenableFuture<Optional<NormalizedNode>> read(final LogicalDatastoreType store, final YangInstanceIdentifier path,
-            final List<YangInstanceIdentifier> fields) {
-        return Futures.immediateFailedFuture(new UnsupportedOperationException(
-                "Reading of selected subtrees is currently not supported in: " + MdsalRestconfStrategy.class));
-    }
-
     @Override
     ListenableFuture<Boolean> exists(final YangInstanceIdentifier path) {
         try (var tx = dataBroker.newReadOnlyTransaction()) {
index ab6d07d03450b01ff07664e49b0de5eb19e9e098..07e2cf1084e4073f8e9e18804ead704995501985 100644 (file)
@@ -17,15 +17,27 @@ import com.google.common.util.concurrent.MoreExecutors;
 import com.google.common.util.concurrent.SettableFuture;
 import java.util.List;
 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.mdsal.common.api.LogicalDatastoreType;
 import org.opendaylight.mdsal.common.api.ReadFailedException;
+import org.opendaylight.mdsal.dom.api.DOMActionService;
+import org.opendaylight.mdsal.dom.api.DOMMountPointService;
 import org.opendaylight.mdsal.dom.api.DOMRpcService;
+import org.opendaylight.mdsal.dom.api.DOMTransactionChain;
 import org.opendaylight.mdsal.dom.api.DOMYangTextSourceProvider;
 import org.opendaylight.netconf.dom.api.NetconfDataTreeService;
+import org.opendaylight.restconf.api.query.ContentParam;
+import org.opendaylight.restconf.api.query.WithDefaultsParam;
+import org.opendaylight.restconf.common.errors.RestconfFuture;
 import org.opendaylight.restconf.common.errors.SettableRestconfFuture;
+import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
+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.DatabindContext;
+import org.opendaylight.restconf.server.spi.ApiPathNormalizer.DataPath;
 import org.opendaylight.yangtools.yang.common.Empty;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
@@ -39,11 +51,18 @@ public final class NetconfRestconfStrategy extends RestconfStrategy {
     private final NetconfDataTreeService netconfService;
 
     public NetconfRestconfStrategy(final DatabindContext databind, final NetconfDataTreeService netconfService,
-            final @Nullable DOMRpcService rpcService, final @Nullable DOMYangTextSourceProvider sourceProvider) {
-        super(databind, ImmutableMap.of(), rpcService, sourceProvider);
+            final @Nullable DOMRpcService rpcService, final @Nullable DOMActionService actionService,
+            final @Nullable DOMYangTextSourceProvider sourceProvider,
+            final @Nullable DOMMountPointService mountPointService) {
+        super(databind, ImmutableMap.of(), rpcService, actionService, sourceProvider, mountPointService);
         this.netconfService = requireNonNull(netconfService);
     }
 
+    @Override
+    RestconfTransaction prepareWriteExecution() {
+        return new NetconfRestconfTransaction(modelContext(), netconfService);
+    }
+
     @Override
     void delete(final SettableRestconfFuture<Empty> future, final YangInstanceIdentifier path) {
         final var tx = prepareWriteExecution();
@@ -62,8 +81,25 @@ public final class NetconfRestconfStrategy extends RestconfStrategy {
     }
 
     @Override
-    RestconfTransaction prepareWriteExecution() {
-        return new NetconfRestconfTransaction(modelContext(), netconfService);
+    RestconfFuture<NormalizedNodePayload> dataGET(final DataPath path, final DataGetParams params) {
+        final var inference = path.inference();
+        final var fields = params.fields();
+        final List<YangInstanceIdentifier> fieldPaths;
+        if (fields != null) {
+            final var tmp = NetconfFieldsTranslator.translate(inference.getEffectiveModelContext(), path.schema(),
+                fields);
+            fieldPaths = tmp == null || tmp.isEmpty() ? null : tmp;
+        } else {
+            fieldPaths = null;
+        }
+
+        final NormalizedNode node;
+        if (fieldPaths != null) {
+            node = readData(params.content(), path.instance(), params.withDefaults(), fieldPaths);
+        } else {
+            node = readData(params.content(), path.instance(), params.withDefaults());
+        }
+        return completeDataGET(inference, QueryParameters.of(params), node);
     }
 
     @Override
@@ -75,8 +111,7 @@ public final class NetconfRestconfStrategy extends RestconfStrategy {
         };
     }
 
-    @Override
-    ListenableFuture<Optional<NormalizedNode>> read(final LogicalDatastoreType store,
+    private ListenableFuture<Optional<NormalizedNode>> read(final LogicalDatastoreType store,
             final YangInstanceIdentifier path, final List<YangInstanceIdentifier> fields) {
         return switch (store) {
             case CONFIGURATION -> netconfService.getConfig(path, fields);
@@ -84,6 +119,54 @@ public final class NetconfRestconfStrategy extends RestconfStrategy {
         };
     }
 
+    /**
+     * 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 fields   paths to selected subtrees which should be read, relative to to the parent path
+     * @return {@link NormalizedNode}
+     */
+    // FIXME: NETCONF-1155: this method should asynchronous
+    public @Nullable NormalizedNode readData(final @NonNull ContentParam content,
+            final @NonNull YangInstanceIdentifier path, final @Nullable WithDefaultsParam withDefa,
+            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()));
+            }
+            case CONFIG -> {
+                final var read = readDataViaTransaction(LogicalDatastoreType.CONFIGURATION, path, fields);
+                yield withDefa == null ? read : prepareDataByParamWithDef(read, path, withDefa.mode());
+            }
+            case NONCONFIG -> readDataViaTransaction(LogicalDatastoreType.OPERATIONAL, path, fields);
+        };
+    }
+
+    /**
+     * 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);
+    }
+
     @Override
     ListenableFuture<Boolean> exists(final YangInstanceIdentifier path) {
         return Futures.transform(remapException(netconfService.getConfig(path)),
index 4042f9ea4a3f87963ce22a5eb084c5f2c61df5da..a2a9cb6ea78d88de7ecb7b11690a2ac535a64e47 100644 (file)
@@ -11,33 +11,48 @@ import static com.google.common.base.Verify.verifyNotNull;
 import static java.util.Objects.requireNonNull;
 
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSetMultimap;
 import com.google.common.io.CharSource;
 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 java.io.IOException;
 import java.net.URI;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.NoSuchElementException;
 import java.util.Optional;
+import java.util.concurrent.CancellationException;
 import java.util.function.BiFunction;
 import java.util.function.Function;
 import java.util.stream.Collectors;
 import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.NonNullByDefault;
 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.DOMActionException;
+import org.opendaylight.mdsal.dom.api.DOMActionResult;
+import org.opendaylight.mdsal.dom.api.DOMActionService;
 import org.opendaylight.mdsal.dom.api.DOMDataBroker;
+import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier;
 import org.opendaylight.mdsal.dom.api.DOMMountPoint;
+import org.opendaylight.mdsal.dom.api.DOMMountPointService;
 import org.opendaylight.mdsal.dom.api.DOMRpcResult;
 import org.opendaylight.mdsal.dom.api.DOMRpcService;
 import org.opendaylight.mdsal.dom.api.DOMSchemaService;
 import org.opendaylight.mdsal.dom.api.DOMTransactionChain;
 import org.opendaylight.mdsal.dom.api.DOMYangTextSourceProvider;
+import org.opendaylight.mdsal.dom.spi.SimpleDOMActionResult;
 import org.opendaylight.netconf.dom.api.NetconfDataTreeService;
+import org.opendaylight.restconf.api.ApiPath;
 import org.opendaylight.restconf.api.query.ContentParam;
 import org.opendaylight.restconf.api.query.WithDefaultsParam;
 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
@@ -48,11 +63,29 @@ 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.databind.ChildBody;
+import org.opendaylight.restconf.nb.rfc8040.databind.DataPostBody;
+import org.opendaylight.restconf.nb.rfc8040.databind.OperationInputBody;
+import org.opendaylight.restconf.nb.rfc8040.databind.ResourceBody;
+import org.opendaylight.restconf.nb.rfc8040.legacy.ErrorTags;
+import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
+import org.opendaylight.restconf.nb.rfc8040.legacy.QueryParameters;
 import org.opendaylight.restconf.nb.rfc8040.utils.parser.YangInstanceIdentifierSerializer;
+import org.opendaylight.restconf.server.api.DataGetParams;
+import org.opendaylight.restconf.server.api.DataPostPath;
+import org.opendaylight.restconf.server.api.DataPostResult;
 import org.opendaylight.restconf.server.api.DataPostResult.CreateResource;
+import org.opendaylight.restconf.server.api.DataPostResult.InvokeOperation;
+import org.opendaylight.restconf.server.api.DataPutPath;
 import org.opendaylight.restconf.server.api.DataPutResult;
 import org.opendaylight.restconf.server.api.DatabindContext;
+import org.opendaylight.restconf.server.api.OperationsGetResult;
+import org.opendaylight.restconf.server.api.OperationsPostPath;
 import org.opendaylight.restconf.server.api.OperationsPostResult;
+import org.opendaylight.restconf.server.spi.ApiPathNormalizer;
+import org.opendaylight.restconf.server.spi.ApiPathNormalizer.DataPath;
+import org.opendaylight.restconf.server.spi.ApiPathNormalizer.OperationPath;
+import org.opendaylight.restconf.server.spi.ApiPathNormalizer.OperationPath.Rpc;
 import org.opendaylight.restconf.server.spi.OperationInput;
 import org.opendaylight.restconf.server.spi.RpcImplementation;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.with.defaults.rev110601.WithDefaultsMode;
@@ -61,6 +94,9 @@ 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.common.Revision;
+import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
+import org.opendaylight.yangtools.yang.common.XMLNamespace;
 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;
@@ -93,11 +129,14 @@ 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.opendaylight.yangtools.yang.model.api.stmt.ModuleEffectiveStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.RpcEffectiveStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.SubmoduleEffectiveStatement;
 import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceRepresentation;
 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
 import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
 import org.opendaylight.yangtools.yang.model.repo.api.YinTextSchemaSource;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -110,45 +149,113 @@ import org.slf4j.LoggerFactory;
 // FIXME: it seems the first three operations deal with lifecycle of a transaction, while others invoke various
 //        operations. This should be handled through proper allocation indirection.
 public abstract class RestconfStrategy {
+    @NonNullByDefault
+    public record StrategyAndPath(RestconfStrategy strategy, DataPath path) {
+        public StrategyAndPath {
+            requireNonNull(strategy);
+            requireNonNull(path);
+        }
+    }
+
+    /**
+     * Result of a partial {@link ApiPath} lookup for the purposes of supporting {@code yang-ext:mount}-delimited mount
+     * points with possible nesting.
+     *
+     * @param strategy the strategy to use
+     * @param tail the {@link ApiPath} tail to use with the strategy
+     */
+    @NonNullByDefault
+    public record StrategyAndTail(RestconfStrategy strategy, ApiPath tail) {
+        public StrategyAndTail {
+            requireNonNull(strategy);
+            requireNonNull(tail);
+        }
+    }
+
     private static final Logger LOG = LoggerFactory.getLogger(RestconfStrategy.class);
 
-    private final @NonNull DatabindContext databind;
     private final @NonNull ImmutableMap<QName, RpcImplementation> localRpcs;
+    private final @NonNull ApiPathNormalizer pathNormalizer;
+    private final @NonNull DatabindContext databind;
     private final DOMYangTextSourceProvider sourceProvider;
+    private final DOMMountPointService mountPointService;
+    private final DOMActionService actionService;
     private final DOMRpcService rpcService;
 
     RestconfStrategy(final DatabindContext databind, final ImmutableMap<QName, RpcImplementation> localRpcs,
-            final @Nullable DOMRpcService rpcService, final DOMYangTextSourceProvider sourceProvider) {
+            final @Nullable DOMRpcService rpcService, final @Nullable DOMActionService actionService,
+            final DOMYangTextSourceProvider sourceProvider, final @Nullable DOMMountPointService mountPointService) {
         this.databind = requireNonNull(databind);
         this.localRpcs = requireNonNull(localRpcs);
         this.rpcService = rpcService;
+        this.actionService = actionService;
         this.sourceProvider = sourceProvider;
+        this.mountPointService = mountPointService;
+        pathNormalizer = new ApiPathNormalizer(databind);
+    }
+
+    public final @NonNull StrategyAndPath resolveStrategyPath(final ApiPath path) {
+        final var andTail = resolveStrategy(path);
+        final var strategy = andTail.strategy();
+        return new StrategyAndPath(strategy, strategy.pathNormalizer.normalizeDataPath(andTail.tail()));
     }
 
     /**
-     * Look up the appropriate strategy for a particular mount point.
+     * Resolve any and all {@code yang-ext:mount} to the target {@link StrategyAndTail}.
      *
-     * @param databind {@link DatabindContext} of target mount point
-     * @param mountPoint Target mount point
-     * @return A strategy, or null if the mount point does not expose a supported interface
-     * @throws NullPointerException if any argument is {@code null}
+     * @param path {@link ApiPath} to resolve
+     * @return A strategy and the remaining path
+     * @throws NullPointerException if {@code path} is {@code null}
      */
-    public static @Nullable RestconfStrategy forMountPoint(final DatabindContext databind,
-            final DOMMountPoint mountPoint) {
+    public final @NonNull StrategyAndTail resolveStrategy(final ApiPath path) {
+        var mount = path.indexOf("yang-ext", "mount");
+        if (mount == -1) {
+            return new StrategyAndTail(this, path);
+        }
+        if (mountPointService == null) {
+            throw new RestconfDocumentedException("Mount point service is not available",
+                ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED);
+        }
+        final var mountPath = path.subPath(0, mount);
+        final var dataPath = pathNormalizer.normalizeDataPath(path.subPath(0, mount));
+        final var mountPoint = mountPointService.getMountPoint(dataPath.instance())
+            .orElseThrow(() -> new RestconfDocumentedException("Mount point '" + mountPath + "' does not exist",
+                ErrorType.PROTOCOL, ErrorTags.RESOURCE_DENIED_TRANSPORT));
+
+        return createStrategy(mountPath, mountPoint).resolveStrategy(path.subPath(mount + 1));
+    }
+
+    private static @NonNull RestconfStrategy createStrategy(final ApiPath mountPath, final DOMMountPoint mountPoint) {
+        final var mountSchemaService = mountPoint.getService(DOMSchemaService.class)
+            .orElseThrow(() -> new RestconfDocumentedException(
+                "Mount point '" + mountPath + "' does not expose DOMSchemaService",
+                ErrorType.PROTOCOL, ErrorTags.RESOURCE_DENIED_TRANSPORT));
+        final var mountModelContext = mountSchemaService.getGlobalContext();
+        if (mountModelContext == null) {
+            throw new RestconfDocumentedException("Mount point '" + mountPath + "' does not have any models",
+                ErrorType.PROTOCOL, ErrorTags.RESOURCE_DENIED_TRANSPORT);
+        }
+        final var mountDatabind = DatabindContext.ofModel(mountModelContext);
+        final var mountPointService = mountPoint.getService(DOMMountPointService.class).orElse(null);
         final var rpcService = mountPoint.getService(DOMRpcService.class).orElse(null);
+        final var actionService = mountPoint.getService(DOMActionService.class).orElse(null);
         final var sourceProvider = mountPoint.getService(DOMSchemaService.class)
             .flatMap(schema -> Optional.ofNullable(schema.getExtensions().getInstance(DOMYangTextSourceProvider.class)))
             .orElse(null);
 
         final var netconfService = mountPoint.getService(NetconfDataTreeService.class);
         if (netconfService.isPresent()) {
-            return new NetconfRestconfStrategy(databind, netconfService.orElseThrow(), rpcService, sourceProvider);
+            return new NetconfRestconfStrategy(mountDatabind, netconfService.orElseThrow(), rpcService, actionService,
+                sourceProvider, mountPointService);
         }
         final var dataBroker = mountPoint.getService(DOMDataBroker.class);
         if (dataBroker.isPresent()) {
-            return new MdsalRestconfStrategy(databind, dataBroker.orElseThrow(), rpcService, sourceProvider);
+            return new MdsalRestconfStrategy(mountDatabind, dataBroker.orElseThrow(), rpcService, actionService,
+                sourceProvider, mountPointService);
         }
-        return null;
+        LOG.warn("Mount point {} does not expose a suitable access interface", mountPath);
+        throw new RestconfDocumentedException("Could not find a supported access interface in mount point",
+            ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED, mountPoint.getIdentifier());
     }
 
     public final @NonNull DatabindContext databind() {
@@ -176,17 +283,6 @@ public abstract class RestconfStrategy {
      */
     abstract ListenableFuture<Optional<NormalizedNode>> read(LogicalDatastoreType store, YangInstanceIdentifier path);
 
-    /**
-     * Read data selected using fields from the datastore.
-     *
-     * @param store the logical data store which should be modified
-     * @param path the parent data object path
-     * @param fields paths to selected fields relative to parent path
-     * @return a ListenableFuture containing the result of the read
-     */
-    abstract ListenableFuture<Optional<NormalizedNode>> read(LogicalDatastoreType store, YangInstanceIdentifier path,
-        List<YangInstanceIdentifier> fields);
-
     /**
      * Check if data already exists in the configuration datastore.
      *
@@ -247,6 +343,26 @@ public abstract class RestconfStrategy {
         }, MoreExecutors.directExecutor());
     }
 
+    public @NonNull RestconfFuture<DataPutResult> dataPUT(final ApiPath apiPath, final ResourceBody body,
+            final Map<String, String> queryParameters) {
+        final var path = pathNormalizer.normalizeDataPath(apiPath);
+
+        final Insert insert;
+        try {
+            insert = Insert.ofQueryParameters(databind, queryParameters);
+        } catch (IllegalArgumentException e) {
+            return RestconfFuture.failed(new RestconfDocumentedException(e.getMessage(),
+                ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE, e));
+        }
+        final NormalizedNode data;
+        try {
+            data = body.toNormalizedNode(new DataPutPath(databind, path.inference(), path.instance()));
+        } catch (RestconfDocumentedException e) {
+            return RestconfFuture.failed(e);
+        }
+        return putData(path.instance(), data, insert);
+    }
+
     /**
      * Check mount point and prepare variables for put data to DS.
      *
@@ -255,7 +371,7 @@ public abstract class RestconfStrategy {
      * @param insert  {@link Insert}
      * @return A {@link DataPutResult}
      */
-    public final RestconfFuture<DataPutResult> putData(final YangInstanceIdentifier path,
+    public final @NonNull RestconfFuture<DataPutResult> putData(final YangInstanceIdentifier path,
             final NormalizedNode data, final @Nullable Insert insert) {
         final var exists = TransactionUtil.syncAccess(exists(path), path);
 
@@ -613,6 +729,24 @@ public abstract class RestconfStrategy {
         }
     }
 
+    public final @NonNull RestconfFuture<NormalizedNodePayload> dataGET(final ApiPath apiPath,
+            final DataGetParams params) {
+        return dataGET(pathNormalizer.normalizeDataPath(apiPath), params);
+    }
+
+    abstract @NonNull RestconfFuture<NormalizedNodePayload> dataGET(DataPath path, DataGetParams params);
+
+    static final @NonNull RestconfFuture<NormalizedNodePayload> completeDataGET(final Inference inference,
+            final QueryParameters queryParams, final NormalizedNode node) {
+        if (node == null) {
+            return RestconfFuture.failed(new RestconfDocumentedException(
+                "Request could not be completed because the relevant data model content does not exist",
+                ErrorType.PROTOCOL, ErrorTag.DATA_MISSING));
+        }
+
+        return RestconfFuture.of(new NormalizedNodePayload(inference, node, queryParams));
+    }
+
     /**
      * Read specific type of data from data store via transaction. Close {@link DOMTransactionChain} if any
      * inside of object {@link RestconfStrategy} provided as a parameter.
@@ -623,7 +757,7 @@ public abstract class RestconfStrategy {
      * @return {@link NormalizedNode}
      */
     // FIXME: NETCONF-1155: this method should asynchronous
-    public @Nullable NormalizedNode readData(final @NonNull ContentParam content,
+    public final @Nullable NormalizedNode readData(final @NonNull ContentParam content,
             final @NonNull YangInstanceIdentifier path, final WithDefaultsParam defaultsMode) {
         return switch (content) {
             case ALL -> {
@@ -644,60 +778,12 @@ public abstract class RestconfStrategy {
         };
     }
 
-    /**
-     * 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 fields   paths to selected subtrees which should be read, relative to to the parent path
-     * @return {@link NormalizedNode}
-     */
-    // FIXME: NETCONF-1155: this method should asynchronous
-    public @Nullable NormalizedNode readData(final @NonNull ContentParam content,
-            final @NonNull YangInstanceIdentifier path, final @Nullable WithDefaultsParam withDefa,
-            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()));
-            }
-            case CONFIG -> {
-                final var read = readDataViaTransaction(LogicalDatastoreType.CONFIGURATION, path, fields);
-                yield withDefa == null ? read : prepareDataByParamWithDef(read, path, withDefa.mode());
-            }
-            case NONCONFIG -> readDataViaTransaction(LogicalDatastoreType.OPERATIONAL, path, fields);
-        };
-    }
-
     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 NormalizedNode prepareDataByParamWithDef(final NormalizedNode readData, final YangInstanceIdentifier path,
+    final NormalizedNode prepareDataByParamWithDef(final NormalizedNode readData, final YangInstanceIdentifier path,
             final WithDefaultsMode defaultsMode) {
         final boolean trim = switch (defaultsMode) {
             case Trim -> true;
@@ -855,8 +941,8 @@ public abstract class RestconfStrategy {
         return childCtx;
     }
 
-    private static NormalizedNode mergeConfigAndSTateDataIfNeeded(final NormalizedNode stateDataNode,
-                                                                  final NormalizedNode configDataNode) {
+    static final NormalizedNode mergeConfigAndSTateDataIfNeeded(final NormalizedNode stateDataNode,
+            final NormalizedNode configDataNode) {
         // if no data exists
         if (stateDataNode == null && configDataNode == null) {
             return null;
@@ -1056,11 +1142,78 @@ public abstract class RestconfStrategy {
             y -> builder.addChild((T) prepareData(y.getValue(), stateMap.get(y.getKey()))));
     }
 
-    public @NonNull RestconfFuture<OperationsPostResult> invokeRpc(final URI restconfURI, final QName type,
-            final OperationInput input) {
+    public @NonNull RestconfFuture<OperationsGetResult> operationsGET() {
+        final var modelContext = modelContext();
+        final var modules = modelContext.getModuleStatements();
+        if (modules.isEmpty()) {
+            // No modules, or defensive return empty content
+            return RestconfFuture.of(new OperationsGetResult.Container(modelContext, ImmutableSetMultimap.of()));
+        }
+
+        // RPC QNames by their XMLNamespace/Revision. This should be a Table, but Revision can be null, which wrecks us.
+        final var table = new HashMap<XMLNamespace, Map<Revision, ImmutableSet<QName>>>();
+        for (var entry : modules.entrySet()) {
+            final var module = entry.getValue();
+            final var rpcNames = module.streamEffectiveSubstatements(RpcEffectiveStatement.class)
+                .map(RpcEffectiveStatement::argument)
+                .collect(ImmutableSet.toImmutableSet());
+            if (!rpcNames.isEmpty()) {
+                final var namespace = entry.getKey();
+                table.computeIfAbsent(namespace.getNamespace(), ignored -> new HashMap<>())
+                    .put(namespace.getRevision().orElse(null), rpcNames);
+            }
+        }
+
+        // Now pick the latest revision for each namespace
+        final var rpcs = ImmutableSetMultimap.<QNameModule, QName>builder();
+        for (var entry : table.entrySet()) {
+            entry.getValue().entrySet().stream()
+            .sorted(Comparator.comparing(Entry::getKey, (first, second) -> Revision.compare(second, first)))
+            .findFirst()
+            .ifPresent(row -> rpcs.putAll(QNameModule.create(entry.getKey(), row.getKey()), row.getValue()));
+        }
+        return RestconfFuture.of(new OperationsGetResult.Container(modelContext, rpcs.build()));
+    }
+
+    public @NonNull RestconfFuture<OperationsGetResult> operationsGET(final ApiPath apiPath) {
+        if (apiPath.steps().isEmpty()) {
+            return operationsGET();
+        }
+
+        final Rpc rpc;
+        try {
+            rpc = pathNormalizer.normalizeRpcPath(apiPath);
+        } catch (RestconfDocumentedException e) {
+            return RestconfFuture.failed(e);
+        }
+
+        return RestconfFuture.of(
+            new OperationsGetResult.Leaf(rpc.inference().getEffectiveModelContext(), rpc.rpc().argument()));
+    }
+
+    public @NonNull RestconfFuture<OperationsPostResult> operationsPOST(final URI restconfURI, final ApiPath apiPath,
+            final OperationInputBody body) {
+        final OperationPath.Rpc path;
+        try {
+            path = pathNormalizer.normalizeRpcPath(apiPath);
+        } catch (RestconfDocumentedException e) {
+            return RestconfFuture.failed(e);
+        }
+
+        final var postPath = new OperationsPostPath(databind, path.inference());
+        final ContainerNode data;
+        try {
+            data = body.toContainerNode(postPath);
+        } catch (IOException e) {
+            LOG.debug("Error reading input", e);
+            return RestconfFuture.failed(new RestconfDocumentedException("Error parsing input: " + e.getMessage(),
+                ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE, e));
+        }
+
+        final var type = path.rpc().argument();
         final var local = localRpcs.get(type);
         if (local != null) {
-            return local.invoke(restconfURI, input);
+            return local.invoke(restconfURI, new OperationInput(databind, postPath.operation(), data));
         }
         if (rpcService == null) {
             LOG.debug("RPC invocation is not available");
@@ -1069,32 +1222,31 @@ public abstract class RestconfStrategy {
         }
 
         final var ret = new SettableRestconfFuture<OperationsPostResult>();
-        Futures.addCallback(rpcService.invokeRpc(requireNonNull(type), input.input()),
-            new FutureCallback<DOMRpcResult>() {
-                @Override
-                public void onSuccess(final DOMRpcResult response) {
-                    final var errors = response.errors();
-                    if (errors.isEmpty()) {
-                        ret.set(input.newOperationOutput(response.value()));
-                    } else {
-                        LOG.debug("RPC invocation reported {}", response.errors());
-                        ret.setFailure(new RestconfDocumentedException("RPC implementation reported errors", null,
-                            response.errors()));
-                    }
+        Futures.addCallback(rpcService.invokeRpc(type, data), new FutureCallback<DOMRpcResult>() {
+            @Override
+            public void onSuccess(final DOMRpcResult response) {
+                final var errors = response.errors();
+                if (errors.isEmpty()) {
+                    ret.set(new OperationsPostResult(databind, postPath.operation(), response.value()));
+                } else {
+                    LOG.debug("RPC invocation reported {}", response.errors());
+                    ret.setFailure(new RestconfDocumentedException("RPC implementation reported errors", null,
+                        response.errors()));
                 }
+            }
 
-                @Override
-                public void onFailure(final Throwable cause) {
-                    LOG.debug("RPC invocation failed, cause");
-                    if (cause instanceof RestconfDocumentedException ex) {
-                        ret.setFailure(ex);
-                    } else {
-                        // TODO: YangNetconfErrorAware if we ever get into a broader invocation scope
-                        ret.setFailure(new RestconfDocumentedException(cause,
-                            new RestconfError(ErrorType.RPC, ErrorTag.OPERATION_FAILED, cause.getMessage())));
-                    }
+            @Override
+            public void onFailure(final Throwable cause) {
+                LOG.debug("RPC invocation failed, cause");
+                if (cause instanceof RestconfDocumentedException ex) {
+                    ret.setFailure(ex);
+                } else {
+                    // TODO: YangNetconfErrorAware if we ever get into a broader invocation scope
+                    ret.setFailure(new RestconfDocumentedException(cause,
+                        new RestconfError(ErrorType.RPC, ErrorTag.OPERATION_FAILED, cause.getMessage())));
                 }
-            }, MoreExecutors.directExecutor());
+            }
+        }, MoreExecutors.directExecutor());
         return ret;
     }
 
@@ -1156,4 +1308,124 @@ public abstract class RestconfStrategy {
             ErrorType.APPLICATION, ErrorTag.DATA_MISSING));
     }
 
+    public final @NonNull RestconfFuture<? extends DataPostResult> dataPOST(final ApiPath apiPath,
+            final DataPostBody body, final Map<String, String> queryParameters) {
+        if (apiPath.steps().isEmpty()) {
+            return dataCreatePOST(body.toResource(), queryParameters);
+        }
+        final var path = pathNormalizer.normalizeDataOrActionPath(apiPath);
+        if (path instanceof DataPath dataPath) {
+            try (var resourceBody = body.toResource()) {
+                return dataCreatePOST(new DataPostPath(databind, dataPath.inference(), dataPath.instance()),
+                    resourceBody, queryParameters);
+            }
+        }
+        if (path instanceof OperationPath.Action actionPath) {
+            try (var inputBody = body.toOperationInput()) {
+                return dataInvokePOST(actionPath, inputBody);
+            }
+        }
+        // Note: this should never happen
+        // FIXME: we should be able to eliminate this path with Java 21+ pattern matching
+        return RestconfFuture.failed(new RestconfDocumentedException("Unhandled path " + path));
+    }
+
+    public @NonNull RestconfFuture<CreateResource> dataCreatePOST(final ChildBody body,
+            final Map<String, String> queryParameters) {
+        return dataCreatePOST(new DataPostPath(databind,
+            SchemaInferenceStack.of(databind.modelContext()).toInference(), YangInstanceIdentifier.of()), body,
+            queryParameters);
+    }
+
+    private @NonNull RestconfFuture<CreateResource> dataCreatePOST(final DataPostPath path, final ChildBody body,
+            final Map<String, String> queryParameters) {
+        final Insert insert;
+        try {
+            insert = Insert.ofQueryParameters(path.databind(), queryParameters);
+        } catch (IllegalArgumentException e) {
+            return RestconfFuture.failed(new RestconfDocumentedException(e.getMessage(),
+                ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE, e));
+        }
+
+        final var payload = body.toPayload(path);
+        return postData(concat(path.instance(), payload.prefix()), payload.body(), insert);
+    }
+
+    private static YangInstanceIdentifier concat(final YangInstanceIdentifier parent, final List<PathArgument> args) {
+        var ret = parent;
+        for (var arg : args) {
+            ret = ret.node(arg);
+        }
+        return ret;
+    }
+
+    private @NonNull RestconfFuture<InvokeOperation> dataInvokePOST(final OperationPath.Action path,
+            final OperationInputBody body) {
+        final var inference = path.inference();
+        final ContainerNode input;
+        try {
+            input = body.toContainerNode(new OperationsPostPath(databind, inference));
+        } catch (IOException e) {
+            LOG.debug("Error reading input", e);
+            return RestconfFuture.failed(new RestconfDocumentedException("Error parsing input: " + e.getMessage(),
+                ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE, e));
+        }
+
+        if (actionService == null) {
+            return RestconfFuture.failed(new RestconfDocumentedException("DOMActionService is missing."));
+        }
+
+        final var future = dataInvokePOST(actionService, path, input);
+        return future.transform(result -> result.getOutput()
+            .flatMap(output -> output.isEmpty() ? Optional.empty()
+                : Optional.of(new InvokeOperation(new NormalizedNodePayload(inference, output))))
+            .orElse(InvokeOperation.EMPTY));
+    }
+
+    /**
+     * Invoke Action via ActionServiceHandler.
+     *
+     * @param input input data
+     * @param yangIId invocation context
+     * @param schemaPath schema path of data
+     * @param actionService action service to invoke action
+     * @return {@link DOMActionResult}
+     */
+    private static RestconfFuture<DOMActionResult> dataInvokePOST(final DOMActionService actionService,
+            final OperationPath.Action path, final @NonNull ContainerNode input) {
+        final var ret = new SettableRestconfFuture<DOMActionResult>();
+
+        Futures.addCallback(actionService.invokeAction(
+            path.inference().toSchemaInferenceStack().toSchemaNodeIdentifier(),
+            new DOMDataTreeIdentifier(LogicalDatastoreType.OPERATIONAL, path.instance()), input),
+            new FutureCallback<DOMActionResult>() {
+                @Override
+                public void onSuccess(final DOMActionResult result) {
+                    final var errors = result.getErrors();
+                    LOG.debug("InvokeAction Error Message {}", errors);
+                    if (errors.isEmpty()) {
+                        ret.set(result);
+                    } else {
+                        ret.setFailure(new RestconfDocumentedException("InvokeAction Error Message ", null, errors));
+                    }
+                }
+
+                @Override
+                public void onFailure(final Throwable cause) {
+                    if (cause instanceof DOMActionException) {
+                        ret.set(new SimpleDOMActionResult(List.of(RpcResultBuilder.newError(
+                            ErrorType.RPC, ErrorTag.OPERATION_FAILED, cause.getMessage()))));
+                    } else if (cause instanceof RestconfDocumentedException e) {
+                        ret.setFailure(e);
+                    } else if (cause instanceof CancellationException) {
+                        ret.setFailure(new RestconfDocumentedException("Action cancelled while executing",
+                            ErrorType.RPC, ErrorTag.PARTIAL_OPERATION, cause));
+                    } else {
+                        ret.setFailure(new RestconfDocumentedException("Invocation failed", cause));
+                    }
+                }
+            }, MoreExecutors.directExecutor());
+
+        return ret;
+    }
 }
index 8814589c1dc4b1d6a5eb59a541acbea8460ab190..01f804435bc6b39747cf519985f79b74855ce5a9 100644 (file)
@@ -32,7 +32,6 @@ import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithV
 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.DataSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
@@ -87,34 +86,18 @@ public final class NetconfFieldsTranslator {
      * {@link NetconfDataTreeService}.
      *
      * @param modelContext EffectiveModelContext
-     * @param schemaNode Root DataSchemaNode
+     * @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 DataSchemaNode schemaNode,
+            final @NonNull EffectiveModelContext modelContext, final @NonNull DataSchemaContext startNode,
             final @NonNull FieldsParam input) {
-        return parseFields(modelContext, schemaNode, input).stream()
-            .map(NetconfFieldsTranslator::buildPath)
-            .toList();
-    }
-
-    private static @NonNull Set<LinkedPathElement> parseFields(final @NonNull EffectiveModelContext modelContext,
-            final @NonNull DataSchemaNode schemaNode, final @NonNull FieldsParam input) {
-        final DataSchemaContext startNode;
-        try {
-            startNode = DataSchemaContext.of(schemaNode);
-        } catch (IllegalStateException e) {
-            throw new RestconfDocumentedException(
-                "Start node missing in " + input, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE, e);
-        }
-
         final var parsed = new HashSet<LinkedPathElement>();
-        processSelectors(parsed, modelContext, schemaNode.getQName().getModule(),
+        processSelectors(parsed, modelContext, startNode.dataSchemaNode().getQName().getModule(),
             new LinkedPathElement(null, List.of(), startNode), input.nodeSelectors());
-
-        return parsed;
+        return parsed.stream().map(NetconfFieldsTranslator::buildPath).toList();
     }
 
     private static void processSelectors(final Set<LinkedPathElement> parsed, final EffectiveModelContext context,
index c7b68093f1e01c35d7208300279b7b9a339fb976..5c806e16990403f3fe104ccecbd5f0bd68b069f6 100644 (file)
@@ -24,7 +24,6 @@ import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.common.QNameModule;
 import org.opendaylight.yangtools.yang.data.util.DataSchemaContext;
 import org.opendaylight.yangtools.yang.data.util.DataSchemaContext.PathMixin;
-import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
 
 /**
@@ -48,18 +47,14 @@ public final class WriterFieldsTranslator {
      * {@link ParameterAwareNormalizedNodeWriter}.
      *
      * @param modelContext EffectiveModelContext
-     * @param schemaNode Root DataSchemaNode
+     * @param startNode {@link DataSchemaContext} of the API request path
      * @param input input value of fields parameter
      * @return {@link List} of levels; each level contains set of {@link QName}
      */
     public static @NonNull List<Set<QName>> translate(final @NonNull EffectiveModelContext modelContext,
-            final DataSchemaNode schemaNode, final @NonNull FieldsParam input) {
-        if (schemaNode == null) {
-            throw new RestconfDocumentedException(
-                    "Start node missing in " + input, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
-        }
+            final DataSchemaContext startNode, final @NonNull FieldsParam input) {
         final var parsed = new ArrayList<Set<QName>>();
-        processSelectors(parsed, modelContext, schemaNode.getQName().getModule(), DataSchemaContext.of(schemaNode),
+        processSelectors(parsed, modelContext, startNode.dataSchemaNode().getQName().getModule(), startNode,
             input.nodeSelectors(), 0);
         return parsed;
     }
index ce0dd783e6f7d86f0fbec8704eb91dd24b37fe08..acba90f79b2f0c643c194db2ba01b2b5bd5acca6 100644 (file)
@@ -40,4 +40,8 @@ public record DataPostPath(DatabindContext databind, Inference inference, YangIn
         requireNonNull(inference);
         requireNonNull(instance);
     }
+
+    public DataPostPath(final DatabindContext databind) {
+        this(databind, Inference.ofDataTreePath(databind.modelContext()), YangInstanceIdentifier.of());
+    }
 }
index ace15e9065460fd107dfc78bcb7f1748e263e3e9..3b346f0a717c7b51f72aa894cdc41569f919a0f0 100644 (file)
@@ -39,4 +39,8 @@ public record DataPutPath(DatabindContext databind, Inference inference, YangIns
         requireNonNull(inference);
         requireNonNull(instance);
     }
+
+    public DataPutPath(final DatabindContext databind) {
+        this(databind, Inference.ofDataTreePath(databind.modelContext()), YangInstanceIdentifier.of());
+    }
 }
index 4c0598d842297edb1b1fa5bc9920cd09492442e0..236824c10b319dc45f47ede9a4afe7de551d2943 100644 (file)
@@ -10,80 +10,53 @@ package org.opendaylight.restconf.server.mdsal;
 import static com.google.common.base.Verify.verifyNotNull;
 import static java.util.Objects.requireNonNull;
 
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Splitter;
 import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.ImmutableSetMultimap;
 import com.google.common.collect.Maps;
-import com.google.common.util.concurrent.FutureCallback;
-import com.google.common.util.concurrent.Futures;
-import com.google.common.util.concurrent.MoreExecutors;
 import java.io.IOException;
 import java.lang.invoke.MethodHandles;
 import java.lang.invoke.VarHandle;
 import java.net.URI;
 import java.time.format.DateTimeParseException;
-import java.util.Comparator;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Optional;
-import java.util.concurrent.CancellationException;
 import javax.annotation.PreDestroy;
 import javax.inject.Inject;
 import javax.inject.Singleton;
 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.DOMActionException;
-import org.opendaylight.mdsal.dom.api.DOMActionResult;
 import org.opendaylight.mdsal.dom.api.DOMActionService;
 import org.opendaylight.mdsal.dom.api.DOMDataBroker;
-import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier;
-import org.opendaylight.mdsal.dom.api.DOMMountPoint;
 import org.opendaylight.mdsal.dom.api.DOMMountPointService;
 import org.opendaylight.mdsal.dom.api.DOMRpcService;
 import org.opendaylight.mdsal.dom.api.DOMSchemaService;
 import org.opendaylight.mdsal.dom.api.DOMYangTextSourceProvider;
-import org.opendaylight.mdsal.dom.spi.SimpleDOMActionResult;
 import org.opendaylight.restconf.api.ApiPath;
 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.common.patch.PatchContext;
 import org.opendaylight.restconf.common.patch.PatchStatusContext;
-import org.opendaylight.restconf.nb.rfc8040.Insert;
 import org.opendaylight.restconf.nb.rfc8040.databind.ChildBody;
 import org.opendaylight.restconf.nb.rfc8040.databind.DataPostBody;
 import org.opendaylight.restconf.nb.rfc8040.databind.OperationInputBody;
 import org.opendaylight.restconf.nb.rfc8040.databind.PatchBody;
 import org.opendaylight.restconf.nb.rfc8040.databind.ResourceBody;
-import org.opendaylight.restconf.nb.rfc8040.legacy.InstanceIdentifierContext;
 import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
-import org.opendaylight.restconf.nb.rfc8040.legacy.QueryParameters;
 import org.opendaylight.restconf.nb.rfc8040.rests.transactions.MdsalRestconfStrategy;
 import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfStrategy;
-import org.opendaylight.restconf.nb.rfc8040.utils.parser.NetconfFieldsTranslator;
-import org.opendaylight.restconf.nb.rfc8040.utils.parser.WriterFieldsTranslator;
+import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfStrategy.StrategyAndTail;
 import org.opendaylight.restconf.server.api.DataGetParams;
 import org.opendaylight.restconf.server.api.DataPatchPath;
-import org.opendaylight.restconf.server.api.DataPostPath;
 import org.opendaylight.restconf.server.api.DataPostResult;
 import org.opendaylight.restconf.server.api.DataPostResult.CreateResource;
-import org.opendaylight.restconf.server.api.DataPostResult.InvokeOperation;
 import org.opendaylight.restconf.server.api.DataPutPath;
 import org.opendaylight.restconf.server.api.DataPutResult;
 import org.opendaylight.restconf.server.api.DatabindContext;
 import org.opendaylight.restconf.server.api.ModulesGetResult;
 import org.opendaylight.restconf.server.api.OperationsGetResult;
-import org.opendaylight.restconf.server.api.OperationsPostPath;
 import org.opendaylight.restconf.server.api.OperationsPostResult;
 import org.opendaylight.restconf.server.api.RestconfServer;
 import org.opendaylight.restconf.server.spi.DatabindProvider;
-import org.opendaylight.restconf.server.spi.OperationInput;
 import org.opendaylight.restconf.server.spi.RpcImplementation;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.restconf.rev170126.YangApi;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.restconf.rev170126.restconf.Restconf;
@@ -92,22 +65,13 @@ 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.common.Revision;
-import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
-import org.opendaylight.yangtools.yang.common.XMLNamespace;
 import org.opendaylight.yangtools.yang.common.YangNames;
 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.ContainerNode;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
-import org.opendaylight.yangtools.yang.model.api.ActionDefinition;
-import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContextListener;
-import org.opendaylight.yangtools.yang.model.api.stmt.RpcEffectiveStatement;
-import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Absolute;
 import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceRepresentation;
 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
 import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
@@ -140,15 +104,6 @@ public final class MdsalRestconfServer
         }
     }
 
-    // FIXME: Remove this constant. All logic relying on this constant should instead rely on YangInstanceIdentifier
-    //        equivalent coming out of argument parsing. This may require keeping List<YangInstanceIdentifier> as the
-    //        nested path split on yang-ext:mount. This splitting needs to be based on consulting the
-    //        EffectiveModelContext and allowing it only where yang-ext:mount is actually used in models.
-    @Deprecated(forRemoval = true)
-    private static final String MOUNT = "yang-ext:mount";
-    @Deprecated(forRemoval = true)
-    private static final Splitter SLASH_SPLITTER = Splitter.on('/');
-
     private final @NonNull ImmutableMap<QName, RpcImplementation> localRpcs;
     private final @NonNull DOMMountPointService mountPointService;
     private final @NonNull DOMDataBroker dataBroker;
@@ -199,21 +154,14 @@ public final class MdsalRestconfServer
     }
 
     private @NonNull MdsalRestconfStrategy createLocalStrategy(final EffectiveModelContext modelContext) {
-        return new MdsalRestconfStrategy(DatabindContext.ofModel(modelContext), dataBroker, rpcService, sourceProvider,
-            localRpcs);
+        return new MdsalRestconfStrategy(DatabindContext.ofModel(modelContext), dataBroker, rpcService, actionService,
+            sourceProvider, mountPointService, localRpcs);
     }
 
     private @NonNull MdsalRestconfStrategy localStrategy() {
         return verifyNotNull((MdsalRestconfStrategy) LOCAL_STRATEGY.getAcquire(this));
     }
 
-    @Deprecated(forRemoval = true)
-    private @NonNull MdsalRestconfStrategy localStrategy(final DatabindContext databind) {
-        final var local = localStrategy();
-        return local.databind().equals(databind) ? local
-            : new MdsalRestconfStrategy(databind, dataBroker, rpcService, sourceProvider, localRpcs);
-    }
-
     @PreDestroy
     @Deactivate
     @Override
@@ -224,248 +172,94 @@ public final class MdsalRestconfServer
 
     @Override
     public RestconfFuture<Empty> dataDELETE(final ApiPath identifier) {
-        final var reqPath = bindRequestPath(identifier);
-        final var strategy = getRestconfStrategy(reqPath.databind(), reqPath.getMountPoint());
-        return strategy.delete(reqPath.getInstanceIdentifier());
+        final var stratAndPath = localStrategy().resolveStrategyPath(identifier);
+        return stratAndPath.strategy().delete(stratAndPath.path().instance());
     }
 
     @Override
     public RestconfFuture<NormalizedNodePayload> dataGET(final DataGetParams params) {
-        return readData(bindRequestRoot(), params);
+        return localStrategy().dataGET(ApiPath.empty(), params);
     }
 
     @Override
     public RestconfFuture<NormalizedNodePayload> dataGET(final ApiPath identifier, final DataGetParams params) {
-        return readData(bindRequestPath(identifier), params);
-    }
-
-    private @NonNull RestconfFuture<NormalizedNodePayload> readData(final InstanceIdentifierContext reqPath,
-            final DataGetParams params) {
-        final var fields = params.fields();
-        final QueryParameters queryParams;
-        if (fields != null) {
-            final var modelContext = reqPath.databind().modelContext();
-            final var schemaNode = (DataSchemaNode) reqPath.getSchemaNode();
-            if (reqPath.getMountPoint() != null) {
-                queryParams = QueryParameters.ofFieldPaths(params, NetconfFieldsTranslator.translate(modelContext,
-                    schemaNode, fields));
-            } else {
-                queryParams = QueryParameters.ofFields(params, WriterFieldsTranslator.translate(modelContext,
-                    schemaNode, fields));
-            }
-        } else {
-            queryParams = QueryParameters.of(params);
-        }
-
-        final var fieldPaths = queryParams.fieldPaths();
-        final var strategy = getRestconfStrategy(reqPath.databind(), reqPath.getMountPoint());
-        final NormalizedNode node;
-        if (fieldPaths != null && !fieldPaths.isEmpty()) {
-            node = strategy.readData(params.content(), reqPath.getInstanceIdentifier(), params.withDefaults(),
-                fieldPaths);
-        } else {
-            node = strategy.readData(params.content(), reqPath.getInstanceIdentifier(), params.withDefaults());
-        }
-        if (node == null) {
-            return RestconfFuture.failed(new RestconfDocumentedException(
-                "Request could not be completed because the relevant data model content does not exist",
-                ErrorType.PROTOCOL, ErrorTag.DATA_MISSING));
-        }
-
-        return RestconfFuture.of(new NormalizedNodePayload(reqPath.inference(), node, queryParams));
+        final var stratAndTail = localStrategy().resolveStrategy(identifier);
+        return stratAndTail.strategy().dataGET(stratAndTail.tail(), params);
     }
 
     @Override
     public RestconfFuture<Empty> dataPATCH(final ResourceBody body) {
-        return dataPATCH(bindRequestRoot(), body);
+        final var strategy = localStrategy();
+        return dataPATCH(strategy, new DataPutPath(strategy.databind()), body);
     }
 
     @Override
     public RestconfFuture<Empty> dataPATCH(final ApiPath identifier, final ResourceBody body) {
-        return dataPATCH(bindRequestPath(identifier), body);
+        final var strategyAndPath = localStrategy().resolveStrategyPath(identifier);
+        final var strategy = strategyAndPath.strategy();
+        final var path = strategyAndPath.path();
+        return dataPATCH(strategy, new DataPutPath(strategy.databind(), path.inference(), path.instance()), body);
     }
 
-    private @NonNull RestconfFuture<Empty> dataPATCH(final InstanceIdentifierContext reqPath, final ResourceBody body) {
-        final var req = bindResourceRequest(reqPath, body);
-        return req.strategy().merge(req.path(), req.data());
+    private static @NonNull RestconfFuture<Empty> dataPATCH(final RestconfStrategy strategy, final DataPutPath path,
+            final ResourceBody body) {
+        final NormalizedNode data;
+        try {
+            data = body.toNormalizedNode(path);
+        } catch (RestconfDocumentedException e) {
+            return RestconfFuture.failed(e);
+        }
+        return strategy.merge(path.instance(), data);
     }
 
     @Override
     public RestconfFuture<PatchStatusContext> dataPATCH(final PatchBody body) {
-        return dataPATCH(bindRequestRoot(), body);
+        final var strategy = localStrategy();
+        return dataPATCH(strategy, new DataPatchPath(strategy.databind(), YangInstanceIdentifier.of()), body);
     }
 
     @Override
     public RestconfFuture<PatchStatusContext> dataPATCH(final ApiPath identifier, final PatchBody body) {
-        return dataPATCH(bindRequestPath(identifier), body);
+        final var stratAndPath = localStrategy().resolveStrategyPath(identifier);
+        final var strategy = stratAndPath.strategy();
+        return dataPATCH(strategy, new DataPatchPath(strategy.databind(), stratAndPath.path().instance()), body);
     }
 
-    private @NonNull RestconfFuture<PatchStatusContext> dataPATCH(final InstanceIdentifierContext reqPath,
-            final PatchBody body) {
-        final var patchPath = new DataPatchPath(reqPath.databind(), reqPath.getInstanceIdentifier());
+    private static @NonNull RestconfFuture<PatchStatusContext> dataPATCH(final RestconfStrategy strategy,
+            final DataPatchPath path, final PatchBody body) {
         final PatchContext patch;
         try {
-            patch = body.toPatchContext(patchPath);
+            patch = body.toPatchContext(path);
         } catch (IOException e) {
             LOG.debug("Error parsing YANG Patch input", e);
             return RestconfFuture.failed(new RestconfDocumentedException("Error parsing input: " + e.getMessage(),
                 ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE, e));
         }
-        return getRestconfStrategy(reqPath.databind(), reqPath.getMountPoint()).patchData(patch);
+        return strategy.patchData(patch);
     }
 
     @Override
     public RestconfFuture<CreateResource> dataPOST(final ChildBody body, final Map<String, String> queryParameters) {
-        return dataCreatePOST(bindRequestRoot(), body, queryParameters);
+        return localStrategy().dataCreatePOST(body, queryParameters);
     }
 
     @Override
     public RestconfFuture<? extends DataPostResult> dataPOST(final ApiPath identifier, final DataPostBody body,
             final Map<String, String> queryParameters) {
-        final var reqPath = bindRequestPath(identifier);
-        if (reqPath.getSchemaNode() instanceof ActionDefinition) {
-            try (var inputBody = body.toOperationInput()) {
-                return dataInvokePOST(reqPath, inputBody);
-            }
-        }
-
-        try (var childBody = body.toResource()) {
-            return dataCreatePOST(reqPath, childBody, queryParameters);
-        }
-    }
-
-    private @NonNull RestconfFuture<CreateResource> dataCreatePOST(final InstanceIdentifierContext reqPath,
-            final ChildBody body, final Map<String, String> queryParameters) {
-        final var postPath = new DataPostPath(reqPath.databind(), reqPath.inference(), reqPath.getInstanceIdentifier());
-
-        final Insert insert;
-        try {
-            insert = Insert.ofQueryParameters(postPath.databind(), queryParameters);
-        } catch (IllegalArgumentException e) {
-            return RestconfFuture.failed(new RestconfDocumentedException(e.getMessage(),
-                ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE, e));
-        }
-
-        final var payload = body.toPayload(postPath);
-        return getRestconfStrategy(reqPath.databind(), reqPath.getMountPoint())
-            .postData(concat(postPath.instance(), payload.prefix()), payload.body(), insert);
-    }
-
-    private static YangInstanceIdentifier concat(final YangInstanceIdentifier parent, final List<PathArgument> args) {
-        var ret = parent;
-        for (var arg : args) {
-            ret = ret.node(arg);
-        }
-        return ret;
-    }
-
-    private RestconfFuture<InvokeOperation> dataInvokePOST(final InstanceIdentifierContext reqPath,
-            final OperationInputBody body) {
-        final var postPath = new OperationsPostPath(reqPath.databind(), reqPath.inference());
-        final var yangIIdContext = reqPath.getInstanceIdentifier();
-        final ContainerNode input;
-        try {
-            input = body.toContainerNode(postPath);
-        } catch (IOException e) {
-            LOG.debug("Error reading input", e);
-            return RestconfFuture.failed(new RestconfDocumentedException("Error parsing input: " + e.getMessage(),
-                ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE, e));
-        }
-
-        final var mountPoint = reqPath.getMountPoint();
-        final var schemaPath = postPath.operation().toSchemaInferenceStack().toSchemaNodeIdentifier();
-        final var future = mountPoint != null ? dataInvokePOST(input, schemaPath, yangIIdContext, mountPoint)
-            : dataInvokePOST(input, schemaPath, yangIIdContext, actionService);
-
-        return future.transform(result -> result.getOutput()
-            .flatMap(output -> output.isEmpty() ? Optional.empty()
-                : Optional.of(new InvokeOperation(new NormalizedNodePayload(reqPath.inference(), output))))
-            .orElse(InvokeOperation.EMPTY));
-    }
-
-    /**
-     * Invoke Action via ActionServiceHandler.
-     *
-     * @param data input data
-     * @param yangIId invocation context
-     * @param schemaPath schema path of data
-     * @param actionService action service to invoke action
-     * @return {@link DOMActionResult}
-     */
-    private static RestconfFuture<DOMActionResult> dataInvokePOST(final ContainerNode data, final Absolute schemaPath,
-            final YangInstanceIdentifier yangIId, final DOMActionService actionService) {
-        final var ret = new SettableRestconfFuture<DOMActionResult>();
-
-        Futures.addCallback(actionService.invokeAction(schemaPath,
-            new DOMDataTreeIdentifier(LogicalDatastoreType.OPERATIONAL, yangIId.getParent()), data),
-            new FutureCallback<DOMActionResult>() {
-                @Override
-                public void onSuccess(final DOMActionResult result) {
-                    final var errors = result.getErrors();
-                    LOG.debug("InvokeAction Error Message {}", errors);
-                    if (errors.isEmpty()) {
-                        ret.set(result);
-                    } else {
-                        ret.setFailure(new RestconfDocumentedException("InvokeAction Error Message ", null, errors));
-                    }
-                }
-
-                @Override
-                public void onFailure(final Throwable cause) {
-                    if (cause instanceof DOMActionException) {
-                        ret.set(new SimpleDOMActionResult(List.of(RpcResultBuilder.newError(
-                            ErrorType.RPC, ErrorTag.OPERATION_FAILED, cause.getMessage()))));
-                    } else if (cause instanceof RestconfDocumentedException e) {
-                        ret.setFailure(e);
-                    } else if (cause instanceof CancellationException) {
-                        ret.setFailure(new RestconfDocumentedException("Action cancelled while executing",
-                            ErrorType.RPC, ErrorTag.PARTIAL_OPERATION, cause));
-                    } else {
-                        ret.setFailure(new RestconfDocumentedException("Invocation failed", cause));
-                    }
-                }
-            }, MoreExecutors.directExecutor());
-
-        return ret;
-    }
-
-    /**
-     * Invoking Action via mount point.
-     *
-     * @param mountPoint mount point
-     * @param data input data
-     * @param schemaPath schema path of data
-     * @return {@link DOMActionResult}
-     */
-    private static RestconfFuture<DOMActionResult> dataInvokePOST(final ContainerNode data, final Absolute schemaPath,
-            final YangInstanceIdentifier yangIId, final DOMMountPoint mountPoint) {
-        final var actionService = mountPoint.getService(DOMActionService.class);
-        return actionService.isPresent() ? dataInvokePOST(data, schemaPath, yangIId, actionService.orElseThrow())
-            : RestconfFuture.failed(new RestconfDocumentedException("DOMActionService is missing."));
+        final var strategyAndTail = localStrategy().resolveStrategy(identifier);
+        return strategyAndTail.strategy().dataPOST(strategyAndTail.tail(), body, queryParameters);
     }
 
     @Override
     public RestconfFuture<DataPutResult> dataPUT(final ResourceBody body, final Map<String, String> query) {
-        return dataPUT(bindRequestRoot(), body, query);
+        return localStrategy().dataPUT(ApiPath.empty(), body, query);
     }
 
     @Override
     public RestconfFuture<DataPutResult> dataPUT(final ApiPath identifier, final ResourceBody body,
              final Map<String, String> queryParameters) {
-        return dataPUT(bindRequestPath(identifier), body, queryParameters);
-    }
-
-    private @NonNull RestconfFuture<DataPutResult> dataPUT(final InstanceIdentifierContext reqPath,
-            final ResourceBody body, final Map<String, String> queryParameters) {
-        final Insert insert;
-        try {
-            insert = Insert.ofQueryParameters(reqPath.databind(), queryParameters);
-        } catch (IllegalArgumentException e) {
-            return RestconfFuture.failed(new RestconfDocumentedException(e.getMessage(),
-                ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE, e));
-        }
-        final var req = bindResourceRequest(reqPath, body);
-        return req.strategy().putData(req.path(), req.data(), insert);
+        final var strategyAndTail = localStrategy().resolveStrategy(identifier);
+        return strategyAndTail.strategy().dataPUT(strategyAndTail.tail(), body, queryParameters);
     }
 
     @Override
@@ -502,20 +296,14 @@ public final class MdsalRestconfServer
             return RestconfFuture.failed(new RestconfDocumentedException("Mount path has to end with yang-ext:mount"));
         }
 
-        final InstanceIdentifierContext point;
-        try {
-            point = InstanceIdentifierContext.ofApiPath(mountPath, localStrategy().databind(), mountPointService);
-        } catch (RestconfDocumentedException e) {
-            return RestconfFuture.failed(e);
-        }
-
-        final RestconfStrategy strategy;
+        final StrategyAndTail stratAndTail;
         try {
-            strategy = forMountPoint(point.databind(), point.getMountPoint());
+            stratAndTail = localStrategy().resolveStrategy(mountPath);
         } catch (RestconfDocumentedException e) {
             return RestconfFuture.failed(e);
         }
-        return modulesGET(strategy, fileName, revision, representation);
+        // FIXME: require remnant to be empty
+        return modulesGET(stratAndTail.strategy(), fileName, revision, representation);
     }
 
     private static @NonNull RestconfFuture<ModulesGetResult> modulesGET(final RestconfStrategy strategy,
@@ -554,77 +342,21 @@ public final class MdsalRestconfServer
 
     @Override
     public RestconfFuture<OperationsGetResult> operationsGET() {
-        return operationsGET(localStrategy().modelContext());
+        return localStrategy().operationsGET();
     }
 
     @Override
     public RestconfFuture<OperationsGetResult> operationsGET(final ApiPath operation) {
-        // get current module RPCs/actions by RPC/action name
-        final var inference = bindRequestPath(operation).inference();
-        if (inference.isEmpty()) {
-            return operationsGET(inference.getEffectiveModelContext());
-        }
-
-        final var stmt = inference.toSchemaInferenceStack().currentStatement();
-        if (stmt instanceof RpcEffectiveStatement rpc) {
-            return RestconfFuture.of(
-                new OperationsGetResult.Leaf(inference.getEffectiveModelContext(), rpc.argument()));
-        }
-        return RestconfFuture.failed(new RestconfDocumentedException("RPC not found",
-            ErrorType.PROTOCOL, ErrorTag.DATA_MISSING));
-    }
-
-    private static @NonNull RestconfFuture<OperationsGetResult> operationsGET(
-            final EffectiveModelContext modelContext) {
-        final var modules = modelContext.getModuleStatements();
-        if (modules.isEmpty()) {
-            // No modules, or defensive return empty content
-            return RestconfFuture.of(new OperationsGetResult.Container(modelContext, ImmutableSetMultimap.of()));
-        }
-
-        // RPC QNames by their XMLNamespace/Revision. This should be a Table, but Revision can be null, which wrecks us.
-        final var table = new HashMap<XMLNamespace, Map<Revision, ImmutableSet<QName>>>();
-        for (var entry : modules.entrySet()) {
-            final var module = entry.getValue();
-            final var rpcNames = module.streamEffectiveSubstatements(RpcEffectiveStatement.class)
-                .map(RpcEffectiveStatement::argument)
-                .collect(ImmutableSet.toImmutableSet());
-            if (!rpcNames.isEmpty()) {
-                final var namespace = entry.getKey();
-                table.computeIfAbsent(namespace.getNamespace(), ignored -> new HashMap<>())
-                    .put(namespace.getRevision().orElse(null), rpcNames);
-            }
-        }
-
-        // Now pick the latest revision for each namespace
-        final var rpcs = ImmutableSetMultimap.<QNameModule, QName>builder();
-        for (var entry : table.entrySet()) {
-            entry.getValue().entrySet().stream()
-                .sorted(Comparator.comparing(Entry::getKey, (first, second) -> Revision.compare(second, first)))
-                .findFirst()
-                .ifPresent(row -> rpcs.putAll(QNameModule.create(entry.getKey(), row.getKey()), row.getValue()));
-        }
-        return RestconfFuture.of(new OperationsGetResult.Container(modelContext, rpcs.build()));
+        final var strategyAndTail = localStrategy().resolveStrategy(operation);
+        return strategyAndTail.strategy().operationsGET(strategyAndTail.tail());
     }
 
     @Override
     public RestconfFuture<OperationsPostResult> operationsPOST(final URI restconfURI, final ApiPath apiPath,
             final OperationInputBody body) {
-        final var reqPath = bindRequestPath(localStrategy(), apiPath);
-        final var postPath = new OperationsPostPath(reqPath.databind(), reqPath.inference());
-
-        final ContainerNode input;
-        try {
-            input = body.toContainerNode(postPath);
-        } catch (IOException e) {
-            LOG.debug("Error reading input", e);
-            return RestconfFuture.failed(new RestconfDocumentedException("Error parsing input: " + e.getMessage(),
-                ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE, e));
-        }
-
-        final var strategy = getRestconfStrategy(reqPath.databind(), reqPath.getMountPoint());
-        return strategy.invokeRpc(restconfURI, reqPath.getSchemaNode().getQName(),
-                new OperationInput(strategy.databind(), postPath.operation(), input));
+        final var strategyAndTail = localStrategy().resolveStrategy(apiPath);
+        final var strategy = strategyAndTail.strategy();
+        return strategy.operationsPOST(restconfURI, strategyAndTail.tail(), body);
     }
 
     @Override
@@ -642,46 +374,4 @@ public final class MdsalRestconfServer
                 .findModuleStatements("ietf-yang-library").iterator().next().localQNameModule().getRevision()
                 .map(Revision::toString).orElse(""))));
     }
-
-    private @NonNull InstanceIdentifierContext bindRequestPath(final @NonNull ApiPath identifier) {
-        return bindRequestPath(localStrategy(), identifier);
-    }
-
-    private @NonNull InstanceIdentifierContext bindRequestPath(final @NonNull MdsalRestconfStrategy strategy,
-            final @NonNull ApiPath identifier) {
-        // FIXME: DatabindContext looks like it should be internal
-        return InstanceIdentifierContext.ofApiPath(identifier, strategy.databind(), mountPointService);
-    }
-
-    private @NonNull InstanceIdentifierContext bindRequestRoot() {
-        return InstanceIdentifierContext.ofLocalRoot(localStrategy().databind());
-    }
-
-    private @NonNull ResourceRequest bindResourceRequest(final InstanceIdentifierContext reqPath,
-            final ResourceBody body) {
-        final var putPath = new DataPutPath(reqPath.databind(), reqPath.inference(), reqPath.getInstanceIdentifier());
-        return new ResourceRequest(getRestconfStrategy(putPath.databind(), reqPath.getMountPoint()), putPath.instance(),
-            body.toNormalizedNode(putPath));
-    }
-
-    @VisibleForTesting
-    @NonNull RestconfStrategy getRestconfStrategy(final DatabindContext databind,
-            final @Nullable DOMMountPoint mountPoint) {
-        if (mountPoint == null) {
-            return localStrategy(databind);
-        }
-        return forMountPoint(databind, mountPoint);
-    }
-
-    private static @NonNull RestconfStrategy forMountPoint(final DatabindContext databind,
-            final DOMMountPoint mountPoint) {
-        final var ret = RestconfStrategy.forMountPoint(databind, mountPoint);
-        if (ret == null) {
-            final var mountId = mountPoint.getIdentifier();
-            LOG.warn("Mount point {} does not expose a suitable access interface", mountId);
-            throw new RestconfDocumentedException("Could not find a supported access interface in mount point",
-                ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED, mountId);
-        }
-        return ret;
-    }
 }
index ef8ab0e84b54d74a52d7362812c383a3f0e7213b..5cae3c8e6b527a1bd49db212a2971dc3537adcd8 100644 (file)
@@ -15,17 +15,18 @@ import com.google.common.collect.ImmutableMap;
 import java.util.ArrayList;
 import java.util.List;
 import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.opendaylight.restconf.api.ApiPath;
 import org.opendaylight.restconf.api.ApiPath.ListInstance;
 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
 import org.opendaylight.restconf.nb.rfc8040.Insert.PointNormalizer;
 import org.opendaylight.restconf.server.api.DatabindContext;
+import org.opendaylight.restconf.server.spi.ApiPathNormalizer.OperationPath.Rpc;
 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.NodeWithValue;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
@@ -38,45 +39,73 @@ 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.opendaylight.yangtools.yang.model.api.SchemaNode;
 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.stmt.ActionEffectiveStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.IdentityEffectiveStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.InputEffectiveStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.RpcEffectiveStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.SchemaTreeEffectiveStatement;
 import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition;
 import org.opendaylight.yangtools.yang.model.api.type.InstanceIdentifierTypeDefinition;
 import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition;
 import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
 
 /**
  * Deserializer for {@link String} to {@link YangInstanceIdentifier} for restconf.
  */
 public final class ApiPathNormalizer implements PointNormalizer {
-    public static final class Result {
-        public final @NonNull YangInstanceIdentifier path;
-        public final @NonNull SchemaInferenceStack stack;
-        public final @NonNull SchemaNode node;
-
-        Result(final EffectiveModelContext modelContext) {
-            path = YangInstanceIdentifier.of();
-            stack = SchemaInferenceStack.of(modelContext);
-            node = requireNonNull(modelContext);
+    @NonNullByDefault
+    public sealed interface Path {
+
+        Inference inference();
+    }
+
+    @NonNullByDefault
+    public sealed interface InstanceReference extends Path {
+
+        YangInstanceIdentifier instance();
+    }
+
+    @NonNullByDefault
+    public record DataPath(Inference inference, YangInstanceIdentifier instance, DataSchemaContext schema)
+            implements InstanceReference {
+        public DataPath {
+            requireNonNull(inference);
+            requireNonNull(instance);
+            requireNonNull(schema);
         }
+    }
 
-        Result(final EffectiveModelContext modelContext, final QName qname) {
-            // Legacy behavior: RPCs do not really have a YangInstanceIdentifier, but the rest of the code expects it
-            path = YangInstanceIdentifier.of(qname);
-            stack = SchemaInferenceStack.of(modelContext);
+    @NonNullByDefault
+    public sealed interface OperationPath extends Path {
 
-            final var stmt = stack.enterSchemaTree(qname);
-            verify(stmt instanceof RpcDefinition, "Unexpected statement %s", stmt);
-            node = (RpcDefinition) stmt;
+        InputEffectiveStatement inputStatement();
+
+        record Action(Inference inference, YangInstanceIdentifier instance, ActionEffectiveStatement action)
+                implements OperationPath, InstanceReference {
+            public Action {
+                requireNonNull(inference);
+                requireNonNull(action);
+                requireNonNull(instance);
+            }
+
+            @Override
+            public InputEffectiveStatement inputStatement() {
+                return action.input();
+            }
         }
 
-        Result(final List<PathArgument> steps, final SchemaInferenceStack stack, final SchemaNode node) {
-            path = YangInstanceIdentifier.of(steps);
-            this.stack = requireNonNull(stack);
-            this.node = requireNonNull(node);
+        record Rpc(Inference inference, RpcEffectiveStatement rpc) implements OperationPath {
+            public Rpc {
+                requireNonNull(inference);
+                requireNonNull(rpc);
+            }
+
+            @Override
+            public InputEffectiveStatement inputStatement() {
+                return rpc.input();
+            }
         }
     }
 
@@ -90,23 +119,11 @@ public final class ApiPathNormalizer implements PointNormalizer {
         instanceIdentifierCodec = new ApiPathInstanceIdentifierCodec(databind);
     }
 
-    @Override
-    public PathArgument normalizePoint(final ApiPath value) {
-        return normalizePath(value).path.getLastPathArgument();
-    }
-
-    // FIXME: NETCONF-818: this method really needs to report an Inference and optionally a YangInstanceIdentifier
-    // - we need the inference for discerning the correct context
-    // - RPCs do not have a YangInstanceIdentifier
-    // - Actions always have a YangInstanceIdentifier, but it points to their parent
-    // - we need to discern the cases RPC invocation, Action invocation and data tree access quickly
-    //
-    // All of this really is an utter mess because we end up calling into this code from various places which,
-    // for example, should not allow RPCs to be valid targets
-    public @NonNull Result normalizePath(final ApiPath apiPath) {
+    public @NonNull Path normalizePath(final ApiPath apiPath) {
         final var it = apiPath.steps().iterator();
         if (!it.hasNext()) {
-            return new Result(modelContext);
+            return new DataPath(Inference.ofDataTreePath(modelContext), YangInstanceIdentifier.of(),
+                databind.schemaTree().getRoot());
         }
 
         // First step is somewhat special:
@@ -129,6 +146,8 @@ public final class ApiPathNormalizer implements PointNormalizer {
         final var optRpc = modelContext.findModuleStatement(namespace).orElseThrow()
             .findSchemaTreeNode(RpcEffectiveStatement.class, qname);
         if (optRpc.isPresent()) {
+            final var rpc = optRpc.orElseThrow();
+
             // We have found an RPC match,
             if (it.hasNext()) {
                 throw new RestconfDocumentedException("First step in the path resolves to RPC '" + qname + "' and "
@@ -139,19 +158,22 @@ public final class ApiPathNormalizer implements PointNormalizer {
                     + "therefore it must not contain key values", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
             }
 
-            return new Result(modelContext, optRpc.orElseThrow().argument());
+            final var stack = SchemaInferenceStack.of(modelContext);
+            final var stmt = stack.enterSchemaTree(rpc.argument());
+            verify(rpc.equals(stmt), "Expecting %s, inferred %s", rpc, stmt);
+            return new OperationPath.Rpc(stack.toInference(), rpc);
         }
 
         final var stack = SchemaInferenceStack.of(modelContext);
         final var path = new ArrayList<PathArgument>();
-        final SchemaNode node;
-
         DataSchemaContext parentNode = databind.schemaTree().getRoot();
         while (true) {
             final var parentSchema = parentNode.dataSchemaNode();
             if (parentSchema instanceof ActionNodeContainer actionParent) {
                 final var optAction = actionParent.findAction(qname);
                 if (optAction.isPresent()) {
+                    final var action = optAction.orElseThrow();
+
                     if (it.hasNext()) {
                         throw new RestconfDocumentedException("Request path resolves to action '" + qname + "' and "
                             + "therefore it must not continue past it", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
@@ -162,11 +184,11 @@ public final class ApiPathNormalizer implements PointNormalizer {
                             ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
                     }
 
-                    // Legacy behavior: Action's path should not include its path, but the rest of the code expects it
-                    path.add(new NodeIdentifier(qname));
-                    stack.enterSchemaTree(qname);
-                    node = optAction.orElseThrow();
-                    break;
+                    final var stmt = stack.enterSchemaTree(qname);
+                    final var actionStmt = action.asEffectiveStatement();
+                    verify(actionStmt.equals(stmt), "Expecting %s, inferred %s", actionStmt, stmt);
+
+                    return new OperationPath.Action(stack.toInference(), YangInstanceIdentifier.of(path), actionStmt);
                 }
             }
 
@@ -205,8 +227,7 @@ public final class ApiPathNormalizer implements PointNormalizer {
             path.add(pathArg);
 
             if (!it.hasNext()) {
-                node = childNode.dataSchemaNode();
-                break;
+                return new DataPath(stack.toInference(), YangInstanceIdentifier.of(path), childNode);
             }
 
             parentNode = childNode;
@@ -218,8 +239,78 @@ public final class ApiPathNormalizer implements PointNormalizer {
 
             qname = step.identifier().bindTo(namespace);
         }
+    }
+
+    public @NonNull DataPath normalizeDataPath(final ApiPath apiPath) {
+        final var path = normalizePath(apiPath);
+        if (path instanceof DataPath dataPath) {
+            return dataPath;
+        }
+        throw new RestconfDocumentedException("Point '" + apiPath + "' resolves to non-data " + path,
+            ErrorType.PROTOCOL, ErrorTag.DATA_MISSING);
+    }
+
+    @Override
+    public PathArgument normalizePoint(final ApiPath value) {
+        final var path = normalizePath(value);
+        if (path instanceof DataPath dataPath) {
+            final var lastArg = dataPath.instance().getLastPathArgument();
+            if (lastArg != null) {
+                return lastArg;
+            }
+            throw new IllegalArgumentException("Point '" + value + "' resolves to an empty path");
+        }
+        throw new IllegalArgumentException("Point '" + value + "' resolves to non-data " + path);
+    }
+
+    public @NonNull Rpc normalizeRpcPath(final ApiPath apiPath) {
+        final var steps = apiPath.steps();
+        return switch (steps.size()) {
+            case 0 -> throw new RestconfDocumentedException("RPC name must be present", ErrorType.PROTOCOL,
+                ErrorTag.DATA_MISSING);
+            case 1 -> normalizeRpcPath(steps.get(0));
+            default -> throw new RestconfDocumentedException(apiPath + " does not refer to an RPC", ErrorType.PROTOCOL,
+                ErrorTag.DATA_MISSING);
+        };
+    }
+
+    public @NonNull Rpc normalizeRpcPath(final ApiPath.Step step) {
+        final var firstModule = step.module();
+        if (firstModule == null) {
+            throw new RestconfDocumentedException(
+                "First member must use namespace-qualified form, '" + step.identifier() + "' does not",
+                ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
+        }
 
-        return new Result(path, stack, node);
+        final var namespace = resolveNamespace(firstModule);
+        final var qname = step.identifier().bindTo(namespace);
+        final var stack = SchemaInferenceStack.of(modelContext);
+        final SchemaTreeEffectiveStatement<?> stmt;
+        try {
+            stmt = stack.enterSchemaTree(qname);
+        } catch (IllegalArgumentException e) {
+            throw new RestconfDocumentedException(qname + " does not refer to an RPC", ErrorType.PROTOCOL,
+                ErrorTag.DATA_MISSING, e);
+        }
+        if (stmt instanceof RpcEffectiveStatement rpc) {
+            return new Rpc(stack.toInference(), rpc);
+        }
+        throw new RestconfDocumentedException(qname + " does not refer to an RPC", ErrorType.PROTOCOL,
+            ErrorTag.DATA_MISSING);
+    }
+
+    public @NonNull InstanceReference normalizeDataOrActionPath(final ApiPath apiPath) {
+
+
+        // FIXME: optimize this
+        final var path = normalizePath(apiPath);
+        if (path instanceof DataPath dataPath) {
+            return dataPath;
+        }
+        if (path instanceof OperationPath.Action actionPath) {
+            return actionPath;
+        }
+        throw new RestconfDocumentedException("Unexpected path " + path, ErrorType.PROTOCOL, ErrorTag.DATA_MISSING);
     }
 
     private NodeIdentifierWithPredicates prepareNodeWithPredicates(final SchemaInferenceStack stack, final QName qname,
index a8f64d06d6ee51cf92e74b823a6ab7c5e1b58947..1091df5486931a7b10e248022551b8cb6b9353a1 100644 (file)
@@ -23,8 +23,10 @@ import org.mockito.Mock;
 import org.mockito.junit.jupiter.MockitoExtension;
 import org.opendaylight.mdsal.common.api.CommitInfo;
 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+import org.opendaylight.mdsal.dom.api.DOMActionService;
 import org.opendaylight.mdsal.dom.api.DOMDataBroker;
 import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction;
+import org.opendaylight.mdsal.dom.api.DOMMountPointService;
 import org.opendaylight.mdsal.dom.api.DOMRpcService;
 import org.opendaylight.mdsal.dom.api.DOMSchemaService;
 import org.opendaylight.mdsal.dom.spi.FixedDOMSchemaService;
@@ -74,6 +76,8 @@ class RestconfDataDeleteTest extends AbstractRestconfTest {
             .getService(DOMSchemaService.class);
         doReturn(Optional.of(dataBroker)).when(mountPoint).getService(DOMDataBroker.class);
         doReturn(Optional.of(rpcService)).when(mountPoint).getService(DOMRpcService.class);
+        doReturn(Optional.empty()).when(mountPoint).getService(DOMActionService.class);
+        doReturn(Optional.empty()).when(mountPoint).getService(DOMMountPointService.class);
         doReturn(Optional.empty()).when(mountPoint).getService(NetconfDataTreeService.class);
 
         doNothing().when(tx).delete(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
index 77b6e531458b3d867375da90a1245e2527d47f0b..b6a9d7fc5fc4b2bb241283e2a092c3b2e0b7247f 100644 (file)
@@ -23,8 +23,10 @@ import org.junit.jupiter.api.extension.ExtendWith;
 import org.mockito.Mock;
 import org.mockito.junit.jupiter.MockitoExtension;
 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+import org.opendaylight.mdsal.dom.api.DOMActionService;
 import org.opendaylight.mdsal.dom.api.DOMDataBroker;
 import org.opendaylight.mdsal.dom.api.DOMDataTreeReadTransaction;
+import org.opendaylight.mdsal.dom.api.DOMMountPointService;
 import org.opendaylight.mdsal.dom.api.DOMRpcService;
 import org.opendaylight.mdsal.dom.api.DOMSchemaService;
 import org.opendaylight.mdsal.dom.spi.FixedDOMSchemaService;
@@ -118,6 +120,8 @@ class RestconfDataGetTest extends AbstractRestconfTest {
             .getService(DOMSchemaService.class);
         doReturn(Optional.of(dataBroker)).when(mountPoint).getService(DOMDataBroker.class);
         doReturn(Optional.of(rpcService)).when(mountPoint).getService(DOMRpcService.class);
+        doReturn(Optional.empty()).when(mountPoint).getService(DOMActionService.class);
+        doReturn(Optional.empty()).when(mountPoint).getService(DOMMountPointService.class);
         doReturn(Optional.empty()).when(mountPoint).getService(NetconfDataTreeService.class);
 
         // response must contain all child nodes from config and operational containers merged in one container
index fc8cf582b8a367ef7b0b7152adafff25827aa2a4..49269eb778d80f0f49d626ff2c77cff1dafc6f42 100644 (file)
@@ -27,9 +27,11 @@ import org.mockito.Mock;
 import org.mockito.junit.jupiter.MockitoExtension;
 import org.opendaylight.mdsal.common.api.CommitInfo;
 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+import org.opendaylight.mdsal.dom.api.DOMActionService;
 import org.opendaylight.mdsal.dom.api.DOMDataBroker;
 import org.opendaylight.mdsal.dom.api.DOMDataTreeReadTransaction;
 import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction;
+import org.opendaylight.mdsal.dom.api.DOMMountPointService;
 import org.opendaylight.mdsal.dom.api.DOMRpcService;
 import org.opendaylight.mdsal.dom.api.DOMSchemaService;
 import org.opendaylight.mdsal.dom.spi.FixedDOMSchemaService;
@@ -78,6 +80,8 @@ class RestconfDataPutTest extends AbstractRestconfTest {
         .getService(DOMSchemaService.class);
         doReturn(Optional.of(dataBroker)).when(mountPoint).getService(DOMDataBroker.class);
         doReturn(Optional.of(rpcService)).when(mountPoint).getService(DOMRpcService.class);
+        doReturn(Optional.empty()).when(mountPoint).getService(DOMActionService.class);
+        doReturn(Optional.empty()).when(mountPoint).getService(DOMMountPointService.class);
         doReturn(Optional.empty()).when(mountPoint).getService(NetconfDataTreeService.class);
 
         assertNull(assertEntity(204, ar -> restconf.dataXmlPUT(
index 08410815c2df6a22abe4ae24e9f6be531543dbb0..8fec02ba8c224c5dab93b98a31b2e7ca3f1d5133 100644 (file)
@@ -21,7 +21,9 @@ import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.mockito.Mock;
 import org.mockito.junit.jupiter.MockitoExtension;
+import org.opendaylight.mdsal.dom.api.DOMActionService;
 import org.opendaylight.mdsal.dom.api.DOMDataBroker;
+import org.opendaylight.mdsal.dom.api.DOMMountPointService;
 import org.opendaylight.mdsal.dom.api.DOMRpcService;
 import org.opendaylight.mdsal.dom.api.DOMSchemaService;
 import org.opendaylight.mdsal.dom.api.DOMYangTextSourceProvider;
@@ -175,7 +177,9 @@ class RestconfModulesGetTest extends AbstractRestconfTest {
         doReturn(Optional.of(FixedDOMSchemaService.of(MODEL_CONTEXT_ON_MOUNT_POINT))).when(mountPoint)
             .getService(DOMSchemaService.class);
         doReturn(Optional.of(mountPoint)).when(mountPointService).getMountPoint(MOUNT_IID);
+        doReturn(Optional.of(mountPointService)).when(mountPoint).getService(DOMMountPointService.class);
         doReturn(Optional.of(rpcService)).when(mountPoint).getService(DOMRpcService.class);
+        doReturn(Optional.empty()).when(mountPoint).getService(DOMActionService.class);
         doReturn(Optional.of(dataBroker)).when(mountPoint).getService(DOMDataBroker.class);
         doReturn(Optional.empty()).when(mountPoint).getService(NetconfDataTreeService.class);
     }
index 0504229a86a5055450f8e27fdbdf0941233ad554..6fbf06e6a3a68886c4cd2cd37c8d6cc2a2f0d89a 100644 (file)
@@ -16,8 +16,13 @@ import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.mockito.junit.jupiter.MockitoExtension;
 import org.opendaylight.mdsal.binding.runtime.spi.BindingRuntimeHelpers;
+import org.opendaylight.mdsal.dom.api.DOMActionService;
+import org.opendaylight.mdsal.dom.api.DOMDataBroker;
+import org.opendaylight.mdsal.dom.api.DOMMountPointService;
+import org.opendaylight.mdsal.dom.api.DOMRpcService;
 import org.opendaylight.mdsal.dom.api.DOMSchemaService;
 import org.opendaylight.mdsal.dom.spi.FixedDOMSchemaService;
+import org.opendaylight.netconf.dom.api.NetconfDataTreeService;
 import org.opendaylight.restconf.api.ApiPath;
 import org.opendaylight.yang.gen.v1.module._1.rev140101.Module1Data;
 import org.opendaylight.yang.gen.v1.module._2.rev140102.Module2Data;
@@ -68,6 +73,11 @@ class RestconfOperationsGetTest extends AbstractRestconfTest {
     private void mockMountPoint() {
         doReturn(Optional.of(FixedDOMSchemaService.of(MODEL_CONTEXT))).when(mountPoint)
             .getService(DOMSchemaService.class);
+        doReturn(Optional.empty()).when(mountPoint).getService(DOMRpcService.class);
+        doReturn(Optional.empty()).when(mountPoint).getService(DOMActionService.class);
+        doReturn(Optional.empty()).when(mountPoint).getService(DOMMountPointService.class);
+        doReturn(Optional.empty()).when(mountPoint).getService(NetconfDataTreeService.class);
+        doReturn(Optional.of(dataBroker)).when(mountPoint).getService(DOMDataBroker.class);
         doReturn(Optional.of(mountPoint)).when(mountPointService).getMountPoint(any());
     }
 
index b3a022ef7463a8613c0071ee22e3ff36cc7e887c..222c3c73b57b85bef486f4e3ed140689107e65f1 100644 (file)
@@ -22,7 +22,9 @@ import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.mockito.Mock;
 import org.mockito.junit.jupiter.MockitoExtension;
+import org.opendaylight.mdsal.dom.api.DOMActionService;
 import org.opendaylight.mdsal.dom.api.DOMDataBroker;
+import org.opendaylight.mdsal.dom.api.DOMMountPointService;
 import org.opendaylight.mdsal.dom.api.DOMNotificationService;
 import org.opendaylight.mdsal.dom.api.DOMRpcImplementationNotAvailableException;
 import org.opendaylight.mdsal.dom.api.DOMRpcResult;
@@ -132,6 +134,8 @@ class RestconfOperationsPostTest extends AbstractRestconfTest {
         doReturn(Optional.of(FixedDOMSchemaService.of(MODEL_CONTEXT))).when(mountPoint)
             .getService(DOMSchemaService.class);
         doReturn(Optional.of(rpcService)).when(mountPoint).getService(DOMRpcService.class);
+        doReturn(Optional.empty()).when(mountPoint).getService(DOMActionService.class);
+        doReturn(Optional.empty()).when(mountPoint).getService(DOMMountPointService.class);
         doReturn(Optional.empty()).when(mountPoint).getService(NetconfDataTreeService.class);
         doReturn(Optional.of(dataBroker)).when(mountPoint).getService(DOMDataBroker.class);
         doReturn(Optional.of(mountPoint)).when(mountPointService).getMountPoint(YangInstanceIdentifier.of(
@@ -157,6 +161,8 @@ class RestconfOperationsPostTest extends AbstractRestconfTest {
         doReturn(Optional.of(FixedDOMSchemaService.of(MODEL_CONTEXT))).when(mountPoint)
             .getService(DOMSchemaService.class);
         doReturn(Optional.empty()).when(mountPoint).getService(DOMRpcService.class);
+        doReturn(Optional.empty()).when(mountPoint).getService(DOMActionService.class);
+        doReturn(Optional.empty()).when(mountPoint).getService(DOMMountPointService.class);
         doReturn(Optional.empty()).when(mountPoint).getService(NetconfDataTreeService.class);
         doReturn(Optional.of(dataBroker)).when(mountPoint).getService(DOMDataBroker.class);
         doReturn(Optional.of(mountPoint)).when(mountPointService).getMountPoint(YangInstanceIdentifier.of(
index 8226f9a2dd6f0fd4d44f87bd91d59f879a5c3d77..55fbe8ab2f4bc3d543fe80a8f197f8f0622cb7d2 100644 (file)
@@ -23,14 +23,18 @@ import org.junit.Before;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnitRunner;
+import org.opendaylight.mdsal.dom.api.DOMActionService;
+import org.opendaylight.mdsal.dom.api.DOMDataBroker;
 import org.opendaylight.mdsal.dom.api.DOMMountPoint;
 import org.opendaylight.mdsal.dom.api.DOMMountPointService;
+import org.opendaylight.mdsal.dom.api.DOMRpcService;
 import org.opendaylight.mdsal.dom.api.DOMSchemaService;
 import org.opendaylight.mdsal.dom.spi.FixedDOMSchemaService;
+import org.opendaylight.netconf.dom.api.NetconfDataTreeService;
 import org.opendaylight.restconf.api.ApiPath;
 import org.opendaylight.restconf.common.patch.PatchContext;
 import org.opendaylight.restconf.nb.rfc8040.AbstractInstanceIdentifierTest;
-import org.opendaylight.restconf.nb.rfc8040.legacy.InstanceIdentifierContext;
+import org.opendaylight.restconf.nb.rfc8040.rests.transactions.MdsalRestconfStrategy;
 import org.opendaylight.restconf.server.api.DataPatchPath;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 
@@ -38,6 +42,8 @@ import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 abstract class AbstractPatchBodyTest extends AbstractInstanceIdentifierTest {
     private final Function<InputStream, PatchBody> bodyConstructor;
 
+    @Mock
+    private DOMDataBroker dataBroker;
     @Mock
     DOMMountPointService mountPointService;
     @Mock
@@ -51,6 +57,11 @@ abstract class AbstractPatchBodyTest extends AbstractInstanceIdentifierTest {
     public final void before() {
         doReturn(Optional.of(mountPoint)).when(mountPointService).getMountPoint(any(YangInstanceIdentifier.class));
         doReturn(Optional.of(FixedDOMSchemaService.of(IID_SCHEMA))).when(mountPoint).getService(DOMSchemaService.class);
+        doReturn(Optional.of(dataBroker)).when(mountPoint).getService(DOMDataBroker.class);
+        doReturn(Optional.empty()).when(mountPoint).getService(DOMActionService.class);
+        doReturn(Optional.empty()).when(mountPoint).getService(DOMRpcService.class);
+        doReturn(Optional.empty()).when(mountPoint).getService(DOMMountPointService.class);
+        doReturn(Optional.empty()).when(mountPoint).getService(NetconfDataTreeService.class);
     }
 
     @NonNull String mountPrefix() {
@@ -83,10 +94,12 @@ abstract class AbstractPatchBodyTest extends AbstractInstanceIdentifierTest {
             throw new AssertionError(e);
         }
 
-        final var iid = InstanceIdentifierContext.ofApiPath(apiPath, IID_DATABIND, mountPointService);
+        final var strategy = new MdsalRestconfStrategy(IID_DATABIND, dataBroker, null, null, null, mountPointService);
+        final var stratAndPath = strategy.resolveStrategyPath(apiPath);
 
         try (var body = bodyConstructor.apply(stringInputStream(patchBody))) {
-            return body.toPatchContext(new DataPatchPath(iid.databind(), iid.getInstanceIdentifier()));
+            return body.toPatchContext(new DataPatchPath(stratAndPath.strategy().databind(),
+                stratAndPath.path().instance()));
         }
     }
 }
index ad545be65cd180b28e88692beda79b908bd71259..fc4da82c502f86b1ca493649a0696e983cfcef56 100644 (file)
@@ -21,12 +21,13 @@ import org.junit.jupiter.api.extension.ExtendWith;
 import org.junit.jupiter.api.function.Executable;
 import org.mockito.Mock;
 import org.mockito.junit.jupiter.MockitoExtension;
+import org.opendaylight.mdsal.dom.api.DOMDataBroker;
 import org.opendaylight.mdsal.dom.api.DOMMountPoint;
 import org.opendaylight.mdsal.dom.api.DOMMountPointService;
 import org.opendaylight.restconf.api.ApiPath;
 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
 import org.opendaylight.restconf.common.errors.RestconfError;
-import org.opendaylight.restconf.nb.rfc8040.legacy.InstanceIdentifierContext;
+import org.opendaylight.restconf.nb.rfc8040.rests.transactions.MdsalRestconfStrategy;
 import org.opendaylight.restconf.server.api.DataPutPath;
 import org.opendaylight.restconf.server.api.DatabindContext;
 import org.opendaylight.yangtools.yang.common.ErrorTag;
@@ -53,6 +54,8 @@ abstract class AbstractResourceBodyTest extends AbstractBodyTest {
 
     private static DatabindContext DATABIND;
 
+    @Mock
+    DOMDataBroker dataBroker;
     @Mock
     DOMMountPointService mountPointService;
     @Mock
@@ -81,10 +84,12 @@ abstract class AbstractResourceBodyTest extends AbstractBodyTest {
             throw new AssertionError(e);
         }
 
+        final var strategy = new MdsalRestconfStrategy(DATABIND, dataBroker, null, null, null, mountPointService);
+        final var stratAndPath = strategy.resolveStrategyPath(apiPath);
+
         try (var body = bodyConstructor.apply(stringInputStream(patchBody))) {
-            final var context = InstanceIdentifierContext.ofApiPath(apiPath, DATABIND, mountPointService);
-            return body.toNormalizedNode(
-                new DataPutPath(context.databind(), context.inference(), context.getInstanceIdentifier()));
+            return body.toNormalizedNode(new DataPutPath(stratAndPath.strategy().databind(),
+                stratAndPath.path().inference(), stratAndPath.path().instance()));
         }
     }
 
index be7e9baefa8a74235cfc6f91640928eb2d26a462..50db4a3db3abdd0b0dae296b9c536867512f6f07 100644 (file)
@@ -16,8 +16,13 @@ import static org.mockito.Mockito.doReturn;
 import java.util.Map;
 import java.util.Optional;
 import org.junit.jupiter.api.Test;
+import org.opendaylight.mdsal.dom.api.DOMActionService;
+import org.opendaylight.mdsal.dom.api.DOMDataBroker;
+import org.opendaylight.mdsal.dom.api.DOMMountPointService;
+import org.opendaylight.mdsal.dom.api.DOMRpcService;
 import org.opendaylight.mdsal.dom.api.DOMSchemaService;
 import org.opendaylight.mdsal.dom.spi.FixedDOMSchemaService;
+import org.opendaylight.netconf.dom.api.NetconfDataTreeService;
 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
 import org.opendaylight.yangtools.yang.common.ErrorTag;
 import org.opendaylight.yangtools.yang.common.ErrorType;
@@ -40,6 +45,11 @@ class XmlResourceBodyTest extends AbstractResourceBodyTest {
         doReturn(Optional.of(mountPoint)).when(mountPointService).getMountPoint(any(YangInstanceIdentifier.class));
         doReturn(Optional.of(FixedDOMSchemaService.of(IID_SCHEMA))).when(mountPoint)
             .getService(DOMSchemaService.class);
+        doReturn(Optional.of(dataBroker)).when(mountPoint).getService(DOMDataBroker.class);
+        doReturn(Optional.empty()).when(mountPoint).getService(DOMActionService.class);
+        doReturn(Optional.empty()).when(mountPoint).getService(DOMRpcService.class);
+        doReturn(Optional.empty()).when(mountPoint).getService(DOMMountPointService.class);
+        doReturn(Optional.empty()).when(mountPoint).getService(NetconfDataTreeService.class);
     }
 
     /**
index 8cfd79b3e1b3f238dd5604ca28d39856280f07fc..d8b3da942e6034a88d81aa71dae201d03c1914a4 100644 (file)
@@ -17,7 +17,6 @@ import static org.mockito.Mockito.withSettings;
 
 import java.util.List;
 import java.util.Map;
-import java.util.Optional;
 import java.util.Set;
 import java.util.function.Function;
 import javax.ws.rs.core.MultivaluedHashMap;
@@ -33,7 +32,6 @@ import org.opendaylight.restconf.api.query.RestconfQueryParam;
 import org.opendaylight.restconf.api.query.WithDefaultsParam;
 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
 import org.opendaylight.restconf.nb.rfc8040.Insert;
-import org.opendaylight.restconf.nb.rfc8040.legacy.InstanceIdentifierContext;
 import org.opendaylight.restconf.nb.rfc8040.legacy.QueryParameters;
 import org.opendaylight.restconf.nb.rfc8040.utils.parser.WriterFieldsTranslator;
 import org.opendaylight.restconf.server.api.DatabindContext;
@@ -41,13 +39,11 @@ import org.opendaylight.restconf.server.api.EventStreamGetParams;
 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.data.util.DataSchemaContext;
 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.stmt.ContainerEffectiveStatement;
-import org.opendaylight.yangtools.yang.model.api.stmt.ModuleEffectiveStatement;
-import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
 
 @RunWith(MockitoJUnitRunner.StrictStubs.class)
 public class QueryParamsTest {
@@ -175,17 +171,8 @@ public class QueryParamsTest {
         doReturn(containerChild).when(containerChildSchema).getQName();
         doReturn(containerChildSchema).when(containerSchema).dataChildByName(containerChild);
 
-        final var module = mock(ModuleEffectiveStatement.class);
-        doReturn(Optional.of(containerSchema)).when(module).findSchemaTreeNode(containerQName);
-        final var context = mock(EffectiveModelContext.class);
-        doReturn(Map.of(containerQName.getModule(), module)).when(context).getModuleStatements();
-
-        final var stack = SchemaInferenceStack.of(context);
-        stack.enterSchemaTree(containerQName);
-        final var iid = InstanceIdentifierContext.ofStack(DatabindContext.ofModel(context), stack);
-
-        final var queryParameters = QueryParameters.ofFields(params, WriterFieldsTranslator.translate(
-            iid.databind().modelContext(), (DataSchemaNode) iid.getSchemaNode(), paramsFields));
+        final var queryParameters = QueryParameters.of(params, WriterFieldsTranslator.translate(
+            mock(EffectiveModelContext.class), DataSchemaContext.of(containerSchema), paramsFields));
         final var fields = queryParameters.fields();
         assertNotNull(fields);
         assertEquals(1, fields.size());
diff --git a/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/legacy/InstanceIdentifierContextTest.java b/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/legacy/InstanceIdentifierContextTest.java
deleted file mode 100644 (file)
index 37e4f38..0000000
+++ /dev/null
@@ -1,294 +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.legacy;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNull;
-import static org.junit.jupiter.api.Assertions.assertSame;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-
-import java.text.ParseException;
-import org.eclipse.jdt.annotation.Nullable;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.opendaylight.mdsal.dom.api.DOMMountPoint;
-import org.opendaylight.mdsal.dom.api.DOMMountPointService;
-import org.opendaylight.mdsal.dom.api.DOMSchemaService;
-import org.opendaylight.mdsal.dom.broker.DOMMountPointServiceImpl;
-import org.opendaylight.mdsal.dom.spi.FixedDOMSchemaService;
-import org.opendaylight.restconf.api.ApiPath;
-import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
-import org.opendaylight.restconf.common.errors.RestconfError;
-import org.opendaylight.restconf.server.api.DatabindContext;
-import org.opendaylight.restconf.server.spi.ApiPathNormalizer;
-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.data.api.YangInstanceIdentifier;
-import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
-import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
-
-class InstanceIdentifierContextTest {
-    // mount point identifier
-    private static final String MOUNT_POINT_IDENT = "mount-point:mount-container/point-number/yang-ext:mount";
-
-    // test identifier + expected result
-    private static final String TEST_IDENT =
-            "parser-identifier:cont1/cont2/listTest/list-in-grouping=name/leaf-A.B";
-
-    private static final String TEST_IDENT_RESULT = """
-            /(parser:identifier?revision=2016-06-02)cont1/cont2/listTest/listTest/list-in-grouping/\
-            list-in-grouping[{(parser:identifier?revision=2016-06-02)name=name}]/leaf-A.B""";
-
-    // test identifier with nodes defined in other modules using augmentation + expected result
-    private static final String TEST_IDENT_OTHERS =
-            "parser-identifier-included:list-1=name,2016-06-02/parser-identifier:augment-leaf";
-
-    private static final String TEST_IDENT_OTHERS_RESULT = """
-        /(parser:identifier:included?revision=2016-06-02)list-1/list-1\
-        [{(parser:identifier:included?revision=2016-06-02)name=name, \
-        (parser:identifier:included?revision=2016-06-02)revision=2016-06-02}]\
-        /(parser:identifier?revision=2016-06-02)augment-leaf""";
-
-    // invalid test identifier
-    private static final String INVALID_TEST_IDENT =
-            "parser-identifier:cont2/listTest/list-in-grouping=name/leaf-A.B";
-
-    private static final String INVOKE_RPC = "invoke-rpc-module:rpc-test";
-    private static final String INVOKE_ACTION = "example-actions:interfaces/interface=eth0/reset";
-
-    // schema context with test modules
-    private static final EffectiveModelContext MODEL_CONTEXT =
-        YangParserTestUtils.parseYangResourceDirectory("/parser-identifier");
-    // contains the same modules but it is different object (it can be compared with equals)
-    // FIXME: we really should use a different context of mount point
-    private static final EffectiveModelContext MODEL_CONTEXT_ON_MOUNT_POINT =
-        YangParserTestUtils.parseYangResourceDirectory("/parser-identifier");
-
-    // mount point and mount point service
-    private final DOMMountPointService mountPointService = new DOMMountPointServiceImpl();
-    private DOMMountPoint mountPoint;
-
-    @BeforeEach
-    void beforeEach() {
-        // create and register mount point
-        final var mountPointId = YangInstanceIdentifier.of(
-            QName.create("mount:point", "2016-06-02", "mount-container"),
-            QName.create("mount:point", "2016-06-02", "point-number"));
-
-        mountPoint = mountPointService.createMountPoint(mountPointId)
-            .addService(DOMSchemaService.class, FixedDOMSchemaService.of(MODEL_CONTEXT_ON_MOUNT_POINT))
-            .register()
-            .getInstance();
-    }
-
-    /**
-     * {@link ParserIdentifier#toInstanceIdentifier(String, SchemaContext)} tests.
-     */
-
-    /**
-     * Positive test of creating <code>InstanceIdentifierContext</code> from identifier when all nodes are defined
-     * in one module.
-     */
-    @Test
-    void toInstanceIdentifierTest() {
-        final var context = assertContext(TEST_IDENT, MODEL_CONTEXT, null);
-        assertEquals(TEST_IDENT_RESULT, context.getInstanceIdentifier().toString());
-    }
-
-    /**
-     * Positive test of creating <code>InstanceIdentifierContext</code> from identifier when nodes are defined in
-     * multiple modules.
-     */
-    @Test
-    void toInstanceIdentifierOtherModulesTest() {
-        final var context = assertContext(TEST_IDENT_OTHERS, MODEL_CONTEXT, null);
-        assertEquals(TEST_IDENT_OTHERS_RESULT, context.getInstanceIdentifier().toString());
-    }
-
-    /**
-     * Positive test of creating {@code InstanceIdentifierContext} from identifier containing {@code yang-ext:mount}.
-     */
-    @Test
-    void toInstanceIdentifierMountPointTest() {
-        final var context = assertContext(MOUNT_POINT_IDENT + "/" + TEST_IDENT, MODEL_CONTEXT,
-            mountPointService);
-        assertEquals(TEST_IDENT_RESULT.toString(), context.getInstanceIdentifier().toString());
-        assertEquals(mountPoint, context.getMountPoint());
-        assertSame(MODEL_CONTEXT_ON_MOUNT_POINT, context.databind().modelContext());
-    }
-
-    /**
-     * Negative test of creating <code>InstanceIdentifierContext</code> when <code>SchemaContext</code> is
-     * <code>null</code>. Test fails expecting <code>NullPointerException</code>.
-     */
-    @Test
-    void toInstanceIdentifierNullSchemaContextNegativeTest() {
-        assertThrows(NullPointerException.class, () -> assertContext(TEST_IDENT, null, null));
-    }
-
-    /**
-     * Api path can be empty. <code>YangInstanceIdentifier.EMPTY</code> is expected to be returned.
-     */
-    @Test
-    void toInstanceIdentifierEmptyIdentifierTest() {
-        final var context = assertContext("", MODEL_CONTEXT, null);
-        assertEquals(YangInstanceIdentifier.of(), context.getInstanceIdentifier());
-    }
-
-    /**
-     * Negative test with invalid test identifier. Test should fail with <code>RestconfDocumentedException</code>.
-     */
-    @Test
-    void toInstanceIdentifierInvalidIdentifierNegativeTest() {
-        final var error = assertError(INVALID_TEST_IDENT, MODEL_CONTEXT, null);
-        assertEquals("Schema for '(parser:identifier?revision=2016-06-02)cont2' not found", error.getErrorMessage());
-        assertEquals(ErrorType.PROTOCOL, error.getErrorType());
-        assertEquals(ErrorTag.DATA_MISSING, error.getErrorTag());
-    }
-
-    /**
-     * Negative test when identifier contains {@code yang-ext:mount} but identifier part is not valid. Test
-     * should fail with {@link RestconfDocumentedException}.
-     */
-    @Test
-    void toInstanceIdentifierMountPointInvalidIdentifierNegativeTest() {
-        final var error = assertError("mount-point:point-number/yang-ext:mount", MODEL_CONTEXT, mountPointService);
-        assertEquals("Schema for '(mount:point?revision=2016-06-02)point-number' not found", error.getErrorMessage());
-        assertEquals(ErrorType.PROTOCOL, error.getErrorType());
-        assertEquals(ErrorTag.DATA_MISSING, error.getErrorTag());
-    }
-
-    /**
-     * Negative test when <code>DOMMountPoint</code> cannot be found. Test is expected to fail with
-     * <code>RestconfDocumentedException</code> error type, error tag and error status code are
-     * compared to expected values.
-     */
-    @Test
-    void toInstanceIdentifierMissingMountPointNegativeTest() {
-        final var error = assertError("yang-ext:mount", MODEL_CONTEXT, mountPointService);
-        assertEquals("Mount point '' does not exist", error.getErrorMessage());
-        assertEquals(ErrorType.PROTOCOL, error.getErrorType());
-        assertEquals(ErrorTags.RESOURCE_DENIED_TRANSPORT, error.getErrorTag());
-    }
-
-    /**
-     * Negative test when <code>{@link DOMMountPointService}</code> is absent. Test is expected to fail with
-     * <code>RestconfDocumentedException</code> error type, error tag and error status code are
-     * compared to expected values.
-     */
-    @Test
-    void toInstanceIdentifierMissingMountPointServiceNegativeTest() {
-        final var error = assertError("yang-ext:mount", MODEL_CONTEXT, null);
-        assertEquals("Mount point service is not available", error.getErrorMessage());
-        assertEquals(ErrorType.APPLICATION, error.getErrorType());
-        assertEquals(ErrorTag.OPERATION_FAILED, error.getErrorTag());
-    }
-
-    /**
-     * Test invoke RPC. Verify if RPC schema node was found.
-     */
-    @Test
-    void invokeRpcTest() throws Exception {
-        final var result = assertContext(INVOKE_RPC, MODEL_CONTEXT, null);
-
-        // RPC schema node
-        final QName rpcQName = result.getSchemaNode().getQName();
-        assertEquals("invoke:rpc:module", rpcQName.getModule().getNamespace().toString());
-        assertEquals("rpc-test", rpcQName.getLocalName());
-
-        // other fields
-        assertEquals(assertPath(INVOKE_RPC), result.getInstanceIdentifier());
-        assertEquals(null, result.getMountPoint());
-        assertSame(MODEL_CONTEXT, result.databind().modelContext());
-    }
-
-    /**
-     * Test invoke RPC on mount point. Verify if RPC schema node was found.
-     */
-    @Test
-    void invokeRpcOnMountPointTest() throws Exception {
-        final var result = assertContext(MOUNT_POINT_IDENT + "/" + INVOKE_RPC, MODEL_CONTEXT, mountPointService);
-
-        // RPC schema node
-        final var rpcQName = result.getSchemaNode().getQName();
-        assertEquals("invoke:rpc:module", rpcQName.getModule().getNamespace().toString());
-        assertEquals("rpc-test", rpcQName.getLocalName());
-
-        // other fields
-        assertEquals(assertPath(INVOKE_RPC), result.getInstanceIdentifier());
-        assertEquals(mountPoint, result.getMountPoint());
-        assertSame(MODEL_CONTEXT_ON_MOUNT_POINT, result.databind().modelContext());
-    }
-
-    /**
-     * Test Action. Verify if Action schema node was found.
-     */
-    @Test
-    void invokeActionTest() throws Exception {
-        final var result = assertContext(INVOKE_ACTION, MODEL_CONTEXT, null);
-
-        // Action schema node
-        final var actionQName = result.getSchemaNode().getQName();
-        assertEquals("https://example.com/ns/example-actions", actionQName.getModule().getNamespace().toString());
-        assertEquals("reset", actionQName.getLocalName());
-
-        // other fields
-        assertEquals(assertPath(INVOKE_ACTION), result.getInstanceIdentifier());
-        assertNull(result.getMountPoint());
-        assertSame(MODEL_CONTEXT, result.databind().modelContext());
-    }
-
-    /**
-     * Test invoke Action on mount point. Verify if Action schema node was found.
-     */
-    @Test
-    void invokeActionOnMountPointTest() throws Exception {
-        final var result = assertContext(MOUNT_POINT_IDENT + "/" + INVOKE_ACTION, MODEL_CONTEXT,
-            mountPointService);
-
-        // Action schema node
-        final var actionQName = result.getSchemaNode().getQName();
-        assertEquals("https://example.com/ns/example-actions", actionQName.getModule().getNamespace().toString());
-        assertEquals("reset", actionQName.getLocalName());
-
-        // other fields
-        assertEquals(assertPath(INVOKE_ACTION), result.getInstanceIdentifier());
-        assertEquals(mountPoint, result.getMountPoint());
-        assertSame(MODEL_CONTEXT_ON_MOUNT_POINT, result.databind().modelContext());
-    }
-
-    private static InstanceIdentifierContext assertContext(final String identifier,
-            final EffectiveModelContext schemaContext, final @Nullable DOMMountPointService mountPointService) {
-        return InstanceIdentifierContext.ofApiPath(assertApiPath(identifier), DatabindContext.ofModel(schemaContext),
-            mountPointService);
-    }
-
-    private static RestconfError assertError(final String identifier, final EffectiveModelContext schemaContext,
-            final @Nullable DOMMountPointService mountPointService) {
-        final var apiPath = assertApiPath(identifier);
-        final var databind = DatabindContext.ofModel(schemaContext);
-        final var ex = assertThrows(RestconfDocumentedException.class,
-            () -> InstanceIdentifierContext.ofApiPath(apiPath, databind, mountPointService));
-        final var errors = ex.getErrors();
-        assertEquals(1, errors.size());
-        return errors.get(0);
-    }
-
-    private static YangInstanceIdentifier assertPath(final String path) {
-        return new ApiPathNormalizer(DatabindContext.ofModel(MODEL_CONTEXT)).normalizePath(assertApiPath(path)).path;
-    }
-
-    private static ApiPath assertApiPath(final String str) {
-        try {
-            return ApiPath.parseUrl(str);
-        } catch (ParseException e) {
-            throw new AssertionError(e);
-        }
-    }
-}
index 1c52f527eb9abd8d3907c692ab122d86dcaf5cd5..7ddeaaf5adb393f45f1a844acb13f2bf0d5fa1f6 100644 (file)
@@ -24,6 +24,7 @@ import java.util.concurrent.ExecutionException;
 import javax.ws.rs.core.UriInfo;
 import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
+import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Mock;
 import org.opendaylight.restconf.api.query.ContentParam;
@@ -32,6 +33,7 @@ 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.server.api.DatabindContext;
 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;
@@ -208,10 +210,27 @@ abstract class AbstractRestconfStrategyTest extends AbstractJukeboxTest {
         new NodeIdentifier(QName.create("ns", "2016-02-28", "container"));
 
     @Mock
-    EffectiveModelContext mockSchemaContext;
+    private EffectiveModelContext mockSchemaContext;
     @Mock
     private UriInfo uriInfo;
 
+    private DatabindContext mockDatabind;
+
+    @Before
+    public void initMockDatabind() {
+        mockDatabind = DatabindContext.ofModel(mockSchemaContext);
+    }
+
+    abstract @NonNull RestconfStrategy newStrategy(DatabindContext databind);
+
+    final @NonNull RestconfStrategy jukeboxStrategy() {
+        return newStrategy(JUKEBOX_DATABIND);
+    }
+
+    final @NonNull RestconfStrategy mockStrategy() {
+        return newStrategy(mockDatabind);
+    }
+
     /**
      * Test of successful DELETE operation.
      */
index b9541064bc6083e1ba1d7f7565a2b88d660d0ce0..7900a577cb1c7b4bd688a61ab7726a29faf30460 100644 (file)
@@ -7,7 +7,10 @@
  */
 package org.opendaylight.restconf.nb.rfc8040.rests.transactions;
 
-import static org.junit.Assert.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.verify;
@@ -18,6 +21,7 @@ import static org.opendaylight.yangtools.util.concurrent.FluentFutures.immediate
 import static org.opendaylight.yangtools.util.concurrent.FluentFutures.immediateTrueFluentFuture;
 
 import java.util.Optional;
+import org.eclipse.jdt.annotation.NonNull;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -25,13 +29,22 @@ import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnitRunner;
 import org.opendaylight.mdsal.common.api.CommitInfo;
 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+import org.opendaylight.mdsal.dom.api.DOMActionService;
 import org.opendaylight.mdsal.dom.api.DOMDataBroker;
 import org.opendaylight.mdsal.dom.api.DOMDataTreeReadTransaction;
 import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction;
+import org.opendaylight.mdsal.dom.api.DOMMountPoint;
+import org.opendaylight.mdsal.dom.api.DOMMountPointService;
 import org.opendaylight.mdsal.dom.api.DOMRpcService;
+import org.opendaylight.mdsal.dom.api.DOMSchemaService;
+import org.opendaylight.mdsal.dom.spi.FixedDOMSchemaService;
+import org.opendaylight.netconf.dom.api.NetconfDataTreeService;
+import org.opendaylight.restconf.api.ApiPath;
 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.common.patch.PatchStatusContext;
+import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfStrategy.StrategyAndTail;
 import org.opendaylight.restconf.server.api.DatabindContext;
 import org.opendaylight.yangtools.yang.common.ErrorTag;
 import org.opendaylight.yangtools.yang.common.ErrorType;
@@ -52,19 +65,33 @@ public final class MdsalRestconfStrategyTest extends AbstractRestconfStrategyTes
     @Mock
     private DOMDataTreeReadWriteTransaction readWrite;
     @Mock
-    private DOMDataBroker mockDataBroker;
+    private DOMDataBroker dataBroker;
     @Mock
     private DOMDataTreeReadTransaction read;
     @Mock
     private DOMRpcService rpcService;
-
-    private DatabindContext mockDatabind;
+    @Mock
+    private DOMSchemaService schemaService;
+    @Mock
+    private DOMMountPointService mountPointService;
+    @Mock
+    private DOMMountPoint mountPoint;
+    @Mock
+    private NetconfDataTreeService netconfService;
 
     @Before
     public void before() {
         doReturn(CommitInfo.emptyFluentFuture()).when(readWrite).commit();
-        doReturn(readWrite).when(mockDataBroker).newReadWriteTransaction();
-        mockDatabind = DatabindContext.ofModel(mockSchemaContext);
+        doReturn(readWrite).when(dataBroker).newReadWriteTransaction();
+    }
+
+    @Override
+    RestconfStrategy newStrategy(final DatabindContext databind) {
+        return new MdsalRestconfStrategy(databind, dataBroker, rpcService, null, null, mountPointService);
+    }
+
+    private @NonNull RestconfStrategy modulesStrategy() {
+        return newStrategy(MODULES_DATABIND);
     }
 
     @Override
@@ -72,7 +99,7 @@ public final class MdsalRestconfStrategyTest extends AbstractRestconfStrategyTes
         // assert that data to delete exists
         when(readWrite.exists(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.of()))
             .thenReturn(immediateTrueFluentFuture());
-        return new MdsalRestconfStrategy(JUKEBOX_DATABIND, mockDataBroker, null, null);
+        return jukeboxStrategy();
     }
 
     @Override
@@ -80,7 +107,7 @@ public final class MdsalRestconfStrategyTest extends AbstractRestconfStrategyTes
         // assert that data to delete does NOT exist
         when(readWrite.exists(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.of()))
             .thenReturn(immediateFalseFluentFuture());
-        return new MdsalRestconfStrategy(JUKEBOX_DATABIND, mockDataBroker, null, null);
+        return jukeboxStrategy();
     }
 
     @Override
@@ -88,7 +115,7 @@ public final class MdsalRestconfStrategyTest extends AbstractRestconfStrategyTes
         doReturn(immediateFalseFluentFuture()).when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, node);
         doNothing().when(readWrite).put(LogicalDatastoreType.CONFIGURATION, node, entryNode);
         doReturn(CommitInfo.emptyFluentFuture()).when(readWrite).commit();
-        return new MdsalRestconfStrategy(JUKEBOX_DATABIND, mockDataBroker, null, null);
+        return jukeboxStrategy();
     }
 
     @Override
@@ -96,32 +123,31 @@ public final class MdsalRestconfStrategyTest extends AbstractRestconfStrategyTes
         doReturn(immediateFalseFluentFuture()).when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
         doReturn(immediateFailedFluentFuture(domException)).when(readWrite).commit();
         doNothing().when(readWrite).put(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID, EMPTY_JUKEBOX);
-        return new MdsalRestconfStrategy(JUKEBOX_DATABIND, mockDataBroker, null, null);
+        return jukeboxStrategy();
     }
 
     @Test
     public void testPutContainerData() {
-        doReturn(readWrite).when(mockDataBroker).newReadWriteTransaction();
-        doReturn(read).when(mockDataBroker).newReadOnlyTransaction();
+        doReturn(readWrite).when(dataBroker).newReadWriteTransaction();
+        doReturn(read).when(dataBroker).newReadOnlyTransaction();
         doReturn(immediateFalseFluentFuture()).when(read).exists(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
         doNothing().when(readWrite).put(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID, EMPTY_JUKEBOX);
         doReturn(CommitInfo.emptyFluentFuture()).when(readWrite).commit();
 
-        new MdsalRestconfStrategy(JUKEBOX_DATABIND, mockDataBroker, null, null)
-            .putData(JUKEBOX_IID, EMPTY_JUKEBOX, null);
+        jukeboxStrategy().putData(JUKEBOX_IID, EMPTY_JUKEBOX, null);
         verify(read).exists(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
         verify(readWrite).put(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID, EMPTY_JUKEBOX);
     }
 
     @Test
     public void testPutLeafData() {
-        doReturn(readWrite).when(mockDataBroker).newReadWriteTransaction();
-        doReturn(read).when(mockDataBroker).newReadOnlyTransaction();
+        doReturn(readWrite).when(dataBroker).newReadWriteTransaction();
+        doReturn(read).when(dataBroker).newReadOnlyTransaction();
         doReturn(immediateFalseFluentFuture()).when(read).exists(LogicalDatastoreType.CONFIGURATION, GAP_IID);
         doNothing().when(readWrite).put(LogicalDatastoreType.CONFIGURATION, GAP_IID, GAP_LEAF);
         doReturn(CommitInfo.emptyFluentFuture()).when(readWrite).commit();
 
-        new MdsalRestconfStrategy(JUKEBOX_DATABIND, mockDataBroker, null, null).putData(GAP_IID, GAP_LEAF, null);
+        jukeboxStrategy().putData(GAP_IID, GAP_LEAF, null);
         verify(read).exists(LogicalDatastoreType.CONFIGURATION, GAP_IID);
         verify(readWrite).put(LogicalDatastoreType.CONFIGURATION, GAP_IID, GAP_LEAF);
     }
@@ -129,15 +155,14 @@ public final class MdsalRestconfStrategyTest extends AbstractRestconfStrategyTes
 
     @Test
     public void testPutListData() {
-        doReturn(readWrite).when(mockDataBroker).newReadWriteTransaction();
-        doReturn(read).when(mockDataBroker).newReadOnlyTransaction();
+        doReturn(readWrite).when(dataBroker).newReadWriteTransaction();
+        doReturn(read).when(dataBroker).newReadOnlyTransaction();
         doReturn(immediateFalseFluentFuture())
                 .when(read).exists(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
         doNothing().when(readWrite).put(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID, JUKEBOX_WITH_BANDS);
         doReturn(CommitInfo.emptyFluentFuture()).when(readWrite).commit();
 
-        new MdsalRestconfStrategy(JUKEBOX_DATABIND, mockDataBroker, null, null)
-            .putData(JUKEBOX_IID, JUKEBOX_WITH_BANDS, null);
+        jukeboxStrategy().putData(JUKEBOX_IID, JUKEBOX_WITH_BANDS, null);
         verify(read).exists(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
         verify(readWrite).put(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID, JUKEBOX_WITH_BANDS);
     }
@@ -147,33 +172,33 @@ public final class MdsalRestconfStrategyTest extends AbstractRestconfStrategyTes
         doReturn(immediateFalseFluentFuture()).when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
         doNothing().when(readWrite).put(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID, EMPTY_JUKEBOX);
         doReturn(CommitInfo.emptyFluentFuture()).when(readWrite).commit();
-        return new MdsalRestconfStrategy(JUKEBOX_DATABIND, mockDataBroker, null, null);
+        return jukeboxStrategy();
     }
 
     @Override
     RestconfStrategy testPatchContainerDataStrategy() {
-        doReturn(readWrite).when(mockDataBroker).newReadWriteTransaction();
+        doReturn(readWrite).when(dataBroker).newReadWriteTransaction();
         doReturn(CommitInfo.emptyFluentFuture()).when(readWrite).commit();
-        return new MdsalRestconfStrategy(JUKEBOX_DATABIND, mockDataBroker, null, null);
+        return jukeboxStrategy();
     }
 
     @Override
     RestconfStrategy testPatchLeafDataStrategy() {
-        doReturn(readWrite).when(mockDataBroker).newReadWriteTransaction();
+        doReturn(readWrite).when(dataBroker).newReadWriteTransaction();
         doReturn(CommitInfo.emptyFluentFuture()).when(readWrite).commit();
-        return new MdsalRestconfStrategy(JUKEBOX_DATABIND, mockDataBroker, null, null);
+        return jukeboxStrategy();
     }
 
     @Override
     RestconfStrategy testPatchListDataStrategy() {
-        doReturn(readWrite).when(mockDataBroker).newReadWriteTransaction();
+        doReturn(readWrite).when(dataBroker).newReadWriteTransaction();
         doReturn(CommitInfo.emptyFluentFuture()).when(readWrite).commit();
-        return new MdsalRestconfStrategy(JUKEBOX_DATABIND, mockDataBroker, null, null);
+        return jukeboxStrategy();
     }
 
     @Override
     RestconfStrategy testPatchDataReplaceMergeAndRemoveStrategy() {
-        return new MdsalRestconfStrategy(JUKEBOX_DATABIND, mockDataBroker, null, null);
+        return jukeboxStrategy();
     }
 
     @Override
@@ -181,19 +206,19 @@ public final class MdsalRestconfStrategyTest extends AbstractRestconfStrategyTes
         doReturn(immediateFalseFluentFuture()).when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, PLAYER_IID);
         doReturn(immediateTrueFluentFuture()).when(readWrite).exists(LogicalDatastoreType.CONFIGURATION,
             CREATE_AND_DELETE_TARGET);
-        return new MdsalRestconfStrategy(JUKEBOX_DATABIND, mockDataBroker, null, null);
+        return jukeboxStrategy();
     }
 
     @Override
     RestconfStrategy testPatchMergePutContainerStrategy() {
-        return new MdsalRestconfStrategy(JUKEBOX_DATABIND, mockDataBroker, null, null);
+        return jukeboxStrategy();
     }
 
     @Override
     RestconfStrategy deleteNonexistentDataTestStrategy() {
         doReturn(immediateFalseFluentFuture()).when(readWrite).exists(LogicalDatastoreType.CONFIGURATION,
             CREATE_AND_DELETE_TARGET);
-        return new MdsalRestconfStrategy(JUKEBOX_DATABIND, mockDataBroker, null, null);
+        return jukeboxStrategy();
     }
 
     @Override
@@ -209,115 +234,115 @@ public final class MdsalRestconfStrategyTest extends AbstractRestconfStrategyTes
 
     @Override
     RestconfStrategy readDataConfigTestStrategy() {
-        doReturn(read).when(mockDataBroker).newReadOnlyTransaction();
+        doReturn(read).when(dataBroker).newReadOnlyTransaction();
         doReturn(immediateFluentFuture(Optional.of(DATA_3))).when(read)
             .read(LogicalDatastoreType.CONFIGURATION, PATH);
-        return new MdsalRestconfStrategy(mockDatabind, mockDataBroker, null, null);
+        return mockStrategy();
     }
 
     @Override
     RestconfStrategy readAllHavingOnlyConfigTestStrategy() {
-        doReturn(read).when(mockDataBroker).newReadOnlyTransaction();
+        doReturn(read).when(dataBroker).newReadOnlyTransaction();
         doReturn(immediateFluentFuture(Optional.of(DATA_3))).when(read)
             .read(LogicalDatastoreType.CONFIGURATION, PATH);
         doReturn(immediateFluentFuture(Optional.empty())).when(read)
             .read(LogicalDatastoreType.OPERATIONAL, PATH);
-        return new MdsalRestconfStrategy(mockDatabind, mockDataBroker, null, null);
+        return mockStrategy();
     }
 
     @Override
     RestconfStrategy readAllHavingOnlyNonConfigTestStrategy() {
-        doReturn(read).when(mockDataBroker).newReadOnlyTransaction();
+        doReturn(read).when(dataBroker).newReadOnlyTransaction();
         doReturn(immediateFluentFuture(Optional.of(DATA_2))).when(read)
             .read(LogicalDatastoreType.OPERATIONAL, PATH_2);
         doReturn(immediateFluentFuture(Optional.empty())).when(read)
             .read(LogicalDatastoreType.CONFIGURATION, PATH_2);
-        return new MdsalRestconfStrategy(mockDatabind, mockDataBroker, null, null);
+        return mockStrategy();
     }
 
     @Override
     RestconfStrategy readDataNonConfigTestStrategy() {
-        doReturn(read).when(mockDataBroker).newReadOnlyTransaction();
+        doReturn(read).when(dataBroker).newReadOnlyTransaction();
         doReturn(immediateFluentFuture(Optional.of(DATA_2))).when(read)
             .read(LogicalDatastoreType.OPERATIONAL, PATH_2);
-        return new MdsalRestconfStrategy(mockDatabind, mockDataBroker, null, null);
+        return mockStrategy();
     }
 
     @Override
     RestconfStrategy readContainerDataAllTestStrategy() {
-        doReturn(read).when(mockDataBroker).newReadOnlyTransaction();
+        doReturn(read).when(dataBroker).newReadOnlyTransaction();
         doReturn(immediateFluentFuture(Optional.of(DATA_3))).when(read)
             .read(LogicalDatastoreType.CONFIGURATION, PATH);
         doReturn(immediateFluentFuture(Optional.of(DATA_4))).when(read)
             .read(LogicalDatastoreType.OPERATIONAL, PATH);
-        return new MdsalRestconfStrategy(mockDatabind, mockDataBroker, null, null);
+        return mockStrategy();
     }
 
     @Override
     RestconfStrategy readContainerDataConfigNoValueOfContentTestStrategy() {
-        doReturn(read).when(mockDataBroker).newReadOnlyTransaction();
+        doReturn(read).when(dataBroker).newReadOnlyTransaction();
         doReturn(immediateFluentFuture(Optional.of(DATA_3))).when(read)
             .read(LogicalDatastoreType.CONFIGURATION, PATH);
         doReturn(immediateFluentFuture(Optional.of(DATA_4))).when(read)
             .read(LogicalDatastoreType.OPERATIONAL, PATH);
-        return new MdsalRestconfStrategy(mockDatabind, mockDataBroker, null, null);
+        return mockStrategy();
     }
 
     @Override
     RestconfStrategy readListDataAllTestStrategy() {
-        doReturn(read).when(mockDataBroker).newReadOnlyTransaction();
+        doReturn(read).when(dataBroker).newReadOnlyTransaction();
         doReturn(immediateFluentFuture(Optional.of(LIST_DATA))).when(read)
             .read(LogicalDatastoreType.OPERATIONAL, PATH_3);
         doReturn(immediateFluentFuture(Optional.of(LIST_DATA_2))).when(read)
             .read(LogicalDatastoreType.CONFIGURATION, PATH_3);
-        return new MdsalRestconfStrategy(mockDatabind, mockDataBroker, null, null);
+        return mockStrategy();
     }
 
     @Override
     RestconfStrategy readOrderedListDataAllTestStrategy() {
-        doReturn(read).when(mockDataBroker).newReadOnlyTransaction();
+        doReturn(read).when(dataBroker).newReadOnlyTransaction();
         doReturn(immediateFluentFuture(Optional.of(ORDERED_MAP_NODE_1))).when(read)
             .read(LogicalDatastoreType.OPERATIONAL, PATH_3);
         doReturn(immediateFluentFuture(Optional.of(ORDERED_MAP_NODE_2))).when(read)
             .read(LogicalDatastoreType.CONFIGURATION, PATH_3);
-        return new MdsalRestconfStrategy(mockDatabind, mockDataBroker, null, null);
+        return mockStrategy();
     }
 
     @Override
     RestconfStrategy readUnkeyedListDataAllTestStrategy() {
-        doReturn(read).when(mockDataBroker).newReadOnlyTransaction();
+        doReturn(read).when(dataBroker).newReadOnlyTransaction();
         doReturn(immediateFluentFuture(Optional.of(UNKEYED_LIST_NODE_1))).when(read)
             .read(LogicalDatastoreType.OPERATIONAL, PATH_3);
         doReturn(immediateFluentFuture(Optional.of(UNKEYED_LIST_NODE_2))).when(read)
             .read(LogicalDatastoreType.CONFIGURATION, PATH_3);
-        return new MdsalRestconfStrategy(mockDatabind, mockDataBroker, null, null);
+        return mockStrategy();
     }
 
     @Override
     RestconfStrategy readLeafListDataAllTestStrategy() {
-        doReturn(read).when(mockDataBroker).newReadOnlyTransaction();
+        doReturn(read).when(dataBroker).newReadOnlyTransaction();
         doReturn(immediateFluentFuture(Optional.of(LEAF_SET_NODE_1))).when(read)
             .read(LogicalDatastoreType.OPERATIONAL, LEAF_SET_NODE_PATH);
         doReturn(immediateFluentFuture(Optional.of(LEAF_SET_NODE_2))).when(read)
             .read(LogicalDatastoreType.CONFIGURATION, LEAF_SET_NODE_PATH);
-        return new MdsalRestconfStrategy(mockDatabind, mockDataBroker, null, null);
+        return mockStrategy();
     }
 
     @Override
     RestconfStrategy readOrderedLeafListDataAllTestStrategy() {
-        doReturn(read).when(mockDataBroker).newReadOnlyTransaction();
+        doReturn(read).when(dataBroker).newReadOnlyTransaction();
         doReturn(immediateFluentFuture(Optional.of(ORDERED_LEAF_SET_NODE_1))).when(read)
             .read(LogicalDatastoreType.OPERATIONAL, LEAF_SET_NODE_PATH);
         doReturn(immediateFluentFuture(Optional.of(ORDERED_LEAF_SET_NODE_2))).when(read)
             .read(LogicalDatastoreType.CONFIGURATION, LEAF_SET_NODE_PATH);
-        return new MdsalRestconfStrategy(mockDatabind, mockDataBroker, null, null);
+        return mockStrategy();
     }
 
     @Override
     RestconfStrategy readDataWrongPathOrNoContentTestStrategy() {
-        doReturn(read).when(mockDataBroker).newReadOnlyTransaction();
+        doReturn(read).when(dataBroker).newReadOnlyTransaction();
         doReturn(immediateFluentFuture(Optional.empty())).when(read).read(LogicalDatastoreType.CONFIGURATION, PATH_2);
-        return new MdsalRestconfStrategy(mockDatabind, mockDataBroker, null, null);
+        return mockStrategy();
     }
 
     @Test
@@ -327,14 +352,13 @@ public final class MdsalRestconfStrategyTest extends AbstractRestconfStrategyTes
             .withChild(ImmutableNodes.leafNode(QName.create(BASE, "exampleLeaf"), "i am leaf"))
             .build();
         final var path = YangInstanceIdentifier.of(CONT_QNAME);
-        doReturn(read).when(mockDataBroker).newReadOnlyTransaction();
+        doReturn(read).when(dataBroker).newReadOnlyTransaction();
         doReturn(immediateFluentFuture(Optional.of(data))).when(read)
                 .read(LogicalDatastoreType.CONFIGURATION, path);
         doReturn(immediateFluentFuture(Optional.of(data))).when(read)
                 .read(LogicalDatastoreType.OPERATIONAL, path);
 
-        assertEquals(data, new MdsalRestconfStrategy(MODULES_DATABIND, mockDataBroker, null, null)
-            .readData(ContentParam.ALL, path, WithDefaultsParam.TRIM));
+        assertEquals(data, modulesStrategy().readData(ContentParam.ALL, path, WithDefaultsParam.TRIM));
     }
 
     @Test
@@ -358,14 +382,13 @@ public final class MdsalRestconfStrategyTest extends AbstractRestconfStrategyTes
                 .build())
             .build();
         final var path = YangInstanceIdentifier.of(CONT_QNAME);
-        doReturn(read).when(mockDataBroker).newReadOnlyTransaction();
+        doReturn(read).when(dataBroker).newReadOnlyTransaction();
         doReturn(immediateFluentFuture(Optional.of(data))).when(read)
                 .read(LogicalDatastoreType.CONFIGURATION, path);
         doReturn(immediateFluentFuture(Optional.of(data))).when(read)
                 .read(LogicalDatastoreType.OPERATIONAL, path);
 
-        assertEquals(data, new MdsalRestconfStrategy(MODULES_DATABIND, mockDataBroker, null, null)
-            .readData(ContentParam.ALL, path, WithDefaultsParam.TRIM));
+        assertEquals(data, modulesStrategy().readData(ContentParam.ALL, path, WithDefaultsParam.TRIM));
     }
 
     @Test
@@ -382,13 +405,76 @@ public final class MdsalRestconfStrategyTest extends AbstractRestconfStrategyTes
                 .build())
             .build();
         final var path = YangInstanceIdentifier.of(CONT_QNAME);
-        doReturn(read).when(mockDataBroker).newReadOnlyTransaction();
+        doReturn(read).when(dataBroker).newReadOnlyTransaction();
         doReturn(immediateFluentFuture(Optional.of(content))).when(read)
                 .read(LogicalDatastoreType.CONFIGURATION, path);
         doReturn(immediateFluentFuture(Optional.of(content))).when(read)
                 .read(LogicalDatastoreType.OPERATIONAL, path);
 
-        assertEquals(content, new MdsalRestconfStrategy(MODULES_DATABIND, mockDataBroker, null, null)
-            .readData(ContentParam.ALL, path, WithDefaultsParam.TRIM));
+        assertEquals(content, modulesStrategy().readData(ContentParam.ALL, path, WithDefaultsParam.TRIM));
+    }
+
+    @Test
+    public void testGetRestconfStrategyLocal() {
+        final var strategy = jukeboxStrategy();
+        assertEquals(new StrategyAndTail(strategy, ApiPath.empty()), strategy.resolveStrategy(ApiPath.empty()));
+    }
+
+    @Test
+    public void testGetRestconfStrategyMountDataBroker() throws Exception {
+        doReturn(Optional.empty()).when(mountPoint).getService(NetconfDataTreeService.class);
+        doReturn(Optional.of(dataBroker)).when(mountPoint).getService(DOMDataBroker.class);
+        doReturn(Optional.of(rpcService)).when(mountPoint).getService(DOMRpcService.class);
+        doReturn(Optional.of(FixedDOMSchemaService.of(JUKEBOX_SCHEMA))).when(mountPoint)
+            .getService(DOMSchemaService.class);
+        doReturn(Optional.empty()).when(mountPoint).getService(DOMMountPointService.class);
+        doReturn(Optional.empty()).when(mountPoint).getService(DOMActionService.class);
+        doReturn(Optional.of(mountPoint)).when(mountPointService).getMountPoint(YangInstanceIdentifier.of());
+
+        final var strategy = jukeboxStrategy();
+        final var result = strategy.resolveStrategy(ApiPath.parse("yang-ext:mount"));
+        assertEquals(ApiPath.empty(), result.tail());
+        assertNotSame(strategy, assertInstanceOf(MdsalRestconfStrategy.class, result.strategy()));
+    }
+
+    @Test
+    public void testGetRestconfStrategyMountNetconfService() throws Exception {
+        doReturn(Optional.of(netconfService)).when(mountPoint).getService(NetconfDataTreeService.class);
+        doReturn(Optional.of(rpcService)).when(mountPoint).getService(DOMRpcService.class);
+        doReturn(Optional.of(FixedDOMSchemaService.of(JUKEBOX_SCHEMA))).when(mountPoint)
+            .getService(DOMSchemaService.class);
+        doReturn(Optional.empty()).when(mountPoint).getService(DOMMountPointService.class);
+        doReturn(Optional.empty()).when(mountPoint).getService(DOMActionService.class);
+        doReturn(Optional.of(mountPoint)).when(mountPointService).getMountPoint(YangInstanceIdentifier.of());
+
+        final var strategy = jukeboxStrategy();
+        final var result = strategy.resolveStrategy(ApiPath.parse("yang-ext:mount"));
+        assertEquals(ApiPath.empty(), result.tail());
+        assertInstanceOf(NetconfRestconfStrategy.class, result.strategy());
+    }
+
+    @Test
+    public void testGetRestconfStrategyMountNone() throws Exception {
+        doReturn(JUKEBOX_IID).when(mountPoint).getIdentifier();
+        doReturn(Optional.empty()).when(mountPoint).getService(NetconfDataTreeService.class);
+        doReturn(Optional.empty()).when(mountPoint).getService(DOMDataBroker.class);
+        doReturn(Optional.empty()).when(mountPoint).getService(DOMMountPointService.class);
+        doReturn(Optional.empty()).when(mountPoint).getService(DOMActionService.class);
+        doReturn(Optional.of(rpcService)).when(mountPoint).getService(DOMRpcService.class);
+        doReturn(Optional.of(FixedDOMSchemaService.of(JUKEBOX_SCHEMA))).when(mountPoint)
+            .getService(DOMSchemaService.class);
+        doReturn(Optional.of(mountPoint)).when(mountPointService).getMountPoint(YangInstanceIdentifier.of());
+
+        final var strategy = jukeboxStrategy();
+        final var mountPath = ApiPath.parse("yang-ext:mount");
+
+        final var ex = assertThrows(RestconfDocumentedException.class, () -> strategy.resolveStrategy(mountPath));
+        final var errors = ex.getErrors();
+        assertEquals(1, errors.size());
+        final var error = errors.get(0);
+        assertEquals(ErrorType.APPLICATION, error.getErrorType());
+        assertEquals(ErrorTag.OPERATION_FAILED, error.getErrorTag());
+        assertEquals("Could not find a supported access interface in mount point", error.getErrorMessage());
+        assertEquals(JUKEBOX_IID, error.getErrorPath());
     }
 }
index 950e0989ff80b48a53ca924f8c1290991700730d..cf53b656a57ae102d73e5eed44044a01ea3b150e 100644 (file)
@@ -43,8 +43,6 @@ public final class NetconfRestconfStrategyTest extends AbstractRestconfStrategyT
     @Mock
     private NetconfDataTreeService netconfService;
 
-    private DatabindContext mockDatabind;
-
     @Before
     public void before() {
         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService).commit();
@@ -55,12 +53,16 @@ public final class NetconfRestconfStrategyTest extends AbstractRestconfStrategyT
             .delete(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.of());
         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService).merge(any(), any(),
             any(), any());
-        mockDatabind = DatabindContext.ofModel(mockSchemaContext);
+    }
+
+    @Override
+    RestconfStrategy newStrategy(final DatabindContext databind) {
+        return new NetconfRestconfStrategy(databind, netconfService, null, null, null, null);
     }
 
     @Override
     RestconfStrategy testDeleteDataStrategy() {
-        return new NetconfRestconfStrategy(JUKEBOX_DATABIND, netconfService, null, null);
+        return jukeboxStrategy();
     }
 
     @Override
@@ -68,7 +70,7 @@ public final class NetconfRestconfStrategyTest extends AbstractRestconfStrategyT
         doReturn(Futures.immediateFailedFuture(new TransactionCommitFailedException(
             "Commit of transaction " + this + " failed", new NetconfDocumentedException("id",
                 ErrorType.RPC, ErrorTag.DATA_MISSING, ErrorSeverity.ERROR)))).when(netconfService).commit();
-        return new NetconfRestconfStrategy(JUKEBOX_DATABIND, netconfService, null, null);
+        return jukeboxStrategy();
     }
 
     @Override
@@ -76,7 +78,7 @@ public final class NetconfRestconfStrategyTest extends AbstractRestconfStrategyT
         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService).commit();
         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService)
             .create(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID, EMPTY_JUKEBOX, Optional.empty());
-        return new NetconfRestconfStrategy(JUKEBOX_DATABIND, netconfService, null, null);
+        return jukeboxStrategy();
     }
 
     @Override
@@ -87,7 +89,7 @@ public final class NetconfRestconfStrategyTest extends AbstractRestconfStrategyT
         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService).commit();
         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService).create(
             LogicalDatastoreType.CONFIGURATION, node, entryNode, Optional.empty());
-        return new NetconfRestconfStrategy(JUKEBOX_DATABIND, netconfService, null, null);
+        return jukeboxStrategy();
     }
 
     @Override
@@ -97,14 +99,14 @@ public final class NetconfRestconfStrategyTest extends AbstractRestconfStrategyT
             .create(any(), any(), any(), any());
         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService).discardChanges();
         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService).unlock();
-        return new NetconfRestconfStrategy(JUKEBOX_DATABIND, netconfService, null, null);
+        return jukeboxStrategy();
     }
 
     @Override
     RestconfStrategy testPatchContainerDataStrategy() {
         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService).merge(any(), any(),any(),
             any());
-        return new NetconfRestconfStrategy(JUKEBOX_DATABIND, netconfService, null, null);
+        return jukeboxStrategy();
     }
 
     @Override
@@ -112,7 +114,7 @@ public final class NetconfRestconfStrategyTest extends AbstractRestconfStrategyT
         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService)
             .merge(any(), any(), any(), any());
         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService).commit();
-        return new NetconfRestconfStrategy(JUKEBOX_DATABIND, netconfService, null, null);
+        return jukeboxStrategy();
     }
 
     @Override
@@ -120,7 +122,7 @@ public final class NetconfRestconfStrategyTest extends AbstractRestconfStrategyT
         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService).commit();
         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService)
             .merge(any(), any(),any(),any());
-        return new NetconfRestconfStrategy(JUKEBOX_DATABIND, netconfService, null, null);
+        return jukeboxStrategy();
     }
 
     @Test
@@ -130,8 +132,7 @@ public final class NetconfRestconfStrategyTest extends AbstractRestconfStrategyT
         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService)
             .replace(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID, EMPTY_JUKEBOX, Optional.empty());
 
-        new NetconfRestconfStrategy(JUKEBOX_DATABIND, netconfService, null, null)
-            .putData(JUKEBOX_IID, EMPTY_JUKEBOX, null);
+        jukeboxStrategy().putData(JUKEBOX_IID, EMPTY_JUKEBOX, null);
         verify(netconfService).lock();
         verify(netconfService).getConfig(JUKEBOX_IID);
         verify(netconfService).replace(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID, EMPTY_JUKEBOX,
@@ -146,8 +147,7 @@ public final class NetconfRestconfStrategyTest extends AbstractRestconfStrategyT
         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService)
             .replace(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID, EMPTY_JUKEBOX, Optional.empty());
 
-        new NetconfRestconfStrategy(JUKEBOX_DATABIND, netconfService, null, null)
-            .putData(JUKEBOX_IID, EMPTY_JUKEBOX, null);
+        jukeboxStrategy().putData(JUKEBOX_IID, EMPTY_JUKEBOX, null);
         verify(netconfService).getConfig(JUKEBOX_IID);
         verify(netconfService).replace(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID, EMPTY_JUKEBOX,
             Optional.empty());
@@ -160,7 +160,7 @@ public final class NetconfRestconfStrategyTest extends AbstractRestconfStrategyT
         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService)
             .replace(LogicalDatastoreType.CONFIGURATION, GAP_IID, GAP_LEAF, Optional.empty());
 
-        new NetconfRestconfStrategy(JUKEBOX_DATABIND, netconfService, null, null).putData(GAP_IID, GAP_LEAF, null);
+        jukeboxStrategy().putData(GAP_IID, GAP_LEAF, null);
         verify(netconfService).getConfig(GAP_IID);
         verify(netconfService).replace(LogicalDatastoreType.CONFIGURATION, GAP_IID, GAP_LEAF, Optional.empty());
     }
@@ -173,7 +173,7 @@ public final class NetconfRestconfStrategyTest extends AbstractRestconfStrategyT
         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService)
             .replace(LogicalDatastoreType.CONFIGURATION, GAP_IID, GAP_LEAF, Optional.empty());
 
-        new NetconfRestconfStrategy(JUKEBOX_DATABIND, netconfService, null, null).putData(GAP_IID, GAP_LEAF, null);
+        jukeboxStrategy().putData(GAP_IID, GAP_LEAF, null);
         verify(netconfService).getConfig(GAP_IID);
         verify(netconfService).replace(LogicalDatastoreType.CONFIGURATION, GAP_IID, GAP_LEAF, Optional.empty());
     }
@@ -185,8 +185,7 @@ public final class NetconfRestconfStrategyTest extends AbstractRestconfStrategyT
         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService)
             .replace(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID, JUKEBOX_WITH_BANDS, Optional.empty());
 
-        new NetconfRestconfStrategy(JUKEBOX_DATABIND, netconfService, null, null)
-            .putData(JUKEBOX_IID, JUKEBOX_WITH_BANDS, null);
+        jukeboxStrategy().putData(JUKEBOX_IID, JUKEBOX_WITH_BANDS, null);
         verify(netconfService).getConfig(JUKEBOX_IID);
         verify(netconfService).replace(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID, JUKEBOX_WITH_BANDS,
             Optional.empty());
@@ -200,8 +199,7 @@ public final class NetconfRestconfStrategyTest extends AbstractRestconfStrategyT
         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService)
             .replace(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID, JUKEBOX_WITH_BANDS, Optional.empty());
 
-        new NetconfRestconfStrategy(JUKEBOX_DATABIND, netconfService, null, null)
-            .putData(JUKEBOX_IID, JUKEBOX_WITH_BANDS, null);
+        jukeboxStrategy().putData(JUKEBOX_IID, JUKEBOX_WITH_BANDS, null);
         verify(netconfService).getConfig(JUKEBOX_IID);
         verify(netconfService).replace(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID, JUKEBOX_WITH_BANDS,
             Optional.empty());
@@ -214,7 +212,7 @@ public final class NetconfRestconfStrategyTest extends AbstractRestconfStrategyT
         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService)
             // FIXME: exact match
             .replace(any(), any(), any(), any());
-        return new NetconfRestconfStrategy(JUKEBOX_DATABIND, netconfService, null, null);
+        return jukeboxStrategy();
     }
 
     @Override
@@ -223,12 +221,12 @@ public final class NetconfRestconfStrategyTest extends AbstractRestconfStrategyT
             .create(LogicalDatastoreType.CONFIGURATION, PLAYER_IID, EMPTY_JUKEBOX, Optional.empty());
         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService)
             .delete(LogicalDatastoreType.CONFIGURATION, CREATE_AND_DELETE_TARGET);
-        return new NetconfRestconfStrategy(JUKEBOX_DATABIND, netconfService, null, null);
+        return jukeboxStrategy();
     }
 
     @Override
     RestconfStrategy testPatchMergePutContainerStrategy() {
-        return new NetconfRestconfStrategy(JUKEBOX_DATABIND, netconfService, null, null);
+        return jukeboxStrategy();
     }
 
     @Override
@@ -239,7 +237,7 @@ public final class NetconfRestconfStrategyTest extends AbstractRestconfStrategyT
             .when(netconfService).commit();
         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService)
             .delete(LogicalDatastoreType.CONFIGURATION, CREATE_AND_DELETE_TARGET);
-        return new NetconfRestconfStrategy(JUKEBOX_DATABIND, netconfService, null, null);
+        return jukeboxStrategy();
     }
 
     @Override
@@ -255,62 +253,62 @@ public final class NetconfRestconfStrategyTest extends AbstractRestconfStrategyT
     @Override
     RestconfStrategy readDataConfigTestStrategy() {
         doReturn(immediateFluentFuture(Optional.of(DATA_3))).when(netconfService).getConfig(PATH);
-        return new NetconfRestconfStrategy(mockDatabind, netconfService, null, null);
+        return mockStrategy();
     }
 
     @Override
     RestconfStrategy readAllHavingOnlyConfigTestStrategy() {
         doReturn(immediateFluentFuture(Optional.of(DATA_3))).when(netconfService).getConfig(PATH);
         doReturn(immediateFluentFuture(Optional.empty())).when(netconfService).get(PATH);
-        return new NetconfRestconfStrategy(mockDatabind, netconfService, null, null);
+        return mockStrategy();
     }
 
     @Override
     RestconfStrategy readAllHavingOnlyNonConfigTestStrategy() {
         doReturn(immediateFluentFuture(Optional.of(DATA_2))).when(netconfService).get(PATH_2);
         doReturn(immediateFluentFuture(Optional.empty())).when(netconfService).getConfig(PATH_2);
-        return new NetconfRestconfStrategy(mockDatabind, netconfService, null, null);
+        return mockStrategy();
     }
 
     @Override
     RestconfStrategy readDataNonConfigTestStrategy() {
         doReturn(immediateFluentFuture(Optional.of(DATA_2))).when(netconfService).get(PATH_2);
-        return new NetconfRestconfStrategy(mockDatabind, netconfService, null, null);
+        return mockStrategy();
     }
 
     @Override
     RestconfStrategy readContainerDataAllTestStrategy() {
         doReturn(immediateFluentFuture(Optional.of(DATA_3))).when(netconfService).getConfig(PATH);
         doReturn(immediateFluentFuture(Optional.of(DATA_4))).when(netconfService).get(PATH);
-        return new NetconfRestconfStrategy(mockDatabind, netconfService, null, null);
+        return mockStrategy();
     }
 
     @Override
     RestconfStrategy readContainerDataConfigNoValueOfContentTestStrategy() {
         doReturn(immediateFluentFuture(Optional.of(DATA_3))).when(netconfService).getConfig(PATH);
         doReturn(immediateFluentFuture(Optional.of(DATA_4))).when(netconfService).get(PATH);
-        return new NetconfRestconfStrategy(mockDatabind, netconfService, null, null);
+        return mockStrategy();
     }
 
     @Override
     RestconfStrategy readListDataAllTestStrategy() {
         doReturn(immediateFluentFuture(Optional.of(LIST_DATA))).when(netconfService).get(PATH_3);
         doReturn(immediateFluentFuture(Optional.of(LIST_DATA_2))).when(netconfService).getConfig(PATH_3);
-        return new NetconfRestconfStrategy(mockDatabind, netconfService, null, null);
+        return mockStrategy();
     }
 
     @Override
     RestconfStrategy readOrderedListDataAllTestStrategy() {
         doReturn(immediateFluentFuture(Optional.of(ORDERED_MAP_NODE_1))).when(netconfService).get(PATH_3);
         doReturn(immediateFluentFuture(Optional.of(ORDERED_MAP_NODE_2))).when(netconfService).getConfig(PATH_3);
-        return new NetconfRestconfStrategy(mockDatabind, netconfService, null, null);
+        return mockStrategy();
     }
 
     @Override
     RestconfStrategy readUnkeyedListDataAllTestStrategy() {
         doReturn(immediateFluentFuture(Optional.of(UNKEYED_LIST_NODE_1))).when(netconfService).get(PATH_3);
         doReturn(immediateFluentFuture(Optional.of(UNKEYED_LIST_NODE_2))).when(netconfService).getConfig(PATH_3);
-        return new NetconfRestconfStrategy(mockDatabind, netconfService, null, null);
+        return mockStrategy();
     }
 
     @Override
@@ -319,7 +317,7 @@ public final class NetconfRestconfStrategyTest extends AbstractRestconfStrategyT
             .get(LEAF_SET_NODE_PATH);
         doReturn(immediateFluentFuture(Optional.of(LEAF_SET_NODE_2))).when(netconfService)
             .getConfig(LEAF_SET_NODE_PATH);
-        return new NetconfRestconfStrategy(mockDatabind, netconfService, null, null);
+        return mockStrategy();
     }
 
     @Override
@@ -328,12 +326,12 @@ public final class NetconfRestconfStrategyTest extends AbstractRestconfStrategyT
             .get(LEAF_SET_NODE_PATH);
         doReturn(immediateFluentFuture(Optional.of(ORDERED_LEAF_SET_NODE_2))).when(netconfService)
             .getConfig(LEAF_SET_NODE_PATH);
-        return new NetconfRestconfStrategy(mockDatabind, netconfService, null, null);
+        return mockStrategy();
     }
 
     @Override
     RestconfStrategy readDataWrongPathOrNoContentTestStrategy() {
         doReturn(immediateFluentFuture(Optional.empty())).when(netconfService).getConfig(PATH_2);
-        return new NetconfRestconfStrategy(mockDatabind, netconfService, null, null);
+        return mockStrategy();
     }
 }
index 6ab29989332375be8b9f945a8c7ab0c846ff6bf8..09038c50562907497bc10548bd1f47146fe577dc 100644 (file)
@@ -8,7 +8,6 @@
 package org.opendaylight.restconf.nb.rfc8040.utils.parser;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertInstanceOf;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 
@@ -20,17 +19,15 @@ import org.junit.Test;
 import org.opendaylight.restconf.api.query.FieldsParam;
 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
 import org.opendaylight.restconf.nb.rfc8040.AbstractJukeboxTest;
-import org.opendaylight.restconf.nb.rfc8040.legacy.InstanceIdentifierContext;
-import org.opendaylight.restconf.server.api.DatabindContext;
 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.common.Revision;
 import org.opendaylight.yangtools.yang.common.XMLNamespace;
-import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+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.util.SchemaInferenceStack;
 import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
 
 public abstract class AbstractFieldsTranslatorTest<T> extends AbstractJukeboxTest {
@@ -43,14 +40,12 @@ public abstract class AbstractFieldsTranslatorTest<T> extends AbstractJukeboxTes
 
     private static final EffectiveModelContext TEST_SERVICES_SCHEMA =
         YangParserTestUtils.parseYangResourceDirectory("/test-services");
-    private static final DatabindContext TEST_SERVICES_DATABIND = DatabindContext.ofModel(TEST_SERVICES_SCHEMA);
     private static final EffectiveModelContext FOO_SCHEMA =
         YangParserTestUtils.parseYangResourceDirectory("/same-qname-nodes");
-    private static final DatabindContext FOO_DATABIND = DatabindContext.ofModel(FOO_SCHEMA);
 
-    private DataSchemaNode jukeboxSchemaNode;
-    private DataSchemaNode testServices;
-    private DataSchemaNode foo;
+    private DataSchemaContext jukeboxSchemaNode;
+    private DataSchemaContext testServices;
+    private DataSchemaContext foo;
 
     // container augmented library
     protected static final QName AUGMENTED_LIBRARY_Q_NAME = QName.create(Q_NAME_MODULE_AUGMENTED_JUKEBOX,
@@ -112,16 +107,16 @@ public abstract class AbstractFieldsTranslatorTest<T> extends AbstractJukeboxTes
 
     @Before
     public void setUp() {
-        jukeboxSchemaNode = assertInstanceOf(DataSchemaNode.class, InstanceIdentifierContext.ofStack(JUKEBOX_DATABIND,
-            SchemaInferenceStack.ofDataTreePath(JUKEBOX_SCHEMA, JUKEBOX_QNAME)).getSchemaNode());
-        testServices = assertInstanceOf(DataSchemaNode.class, InstanceIdentifierContext.ofStack(TEST_SERVICES_DATABIND,
-            SchemaInferenceStack.ofDataTreePath(TEST_SERVICES_SCHEMA, TEST_DATA_Q_NAME)).getSchemaNode());
-        foo = assertInstanceOf(DataSchemaNode.class, InstanceIdentifierContext.ofStack(FOO_DATABIND,
-            SchemaInferenceStack.ofDataTreePath(FOO_SCHEMA, FOO_Q_NAME)).getSchemaNode());
+        jukeboxSchemaNode = DataSchemaContextTree.from(JUKEBOX_SCHEMA).getRoot().childByQName(JUKEBOX_QNAME);
+        assertNotNull(jukeboxSchemaNode);
+        testServices = DataSchemaContextTree.from(TEST_SERVICES_SCHEMA).getRoot().childByQName(TEST_DATA_Q_NAME);
+        assertNotNull(testServices);
+        foo = DataSchemaContextTree.from(FOO_SCHEMA).getRoot().childByQName(FOO_Q_NAME);
+        assertNotNull(foo);
     }
 
     protected abstract List<T> translateFields(@NonNull EffectiveModelContext modelContext,
-        @NonNull DataSchemaNode schemaNode, @NonNull FieldsParam fields);
+        @NonNull DataSchemaContext startNode, @NonNull FieldsParam fields);
 
     /**
      * Test parse fields parameter containing only one child selected.
index 38c4717918b51693fdbe335f18e42af89bdd4c65..26583eaac356ab600619d5887b6661af60642bf4 100644 (file)
@@ -20,7 +20,7 @@ import org.opendaylight.yangtools.yang.common.QName;
 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.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.data.util.DataSchemaContext;
 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
 
 /**
@@ -30,8 +30,8 @@ import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
 public class NetconfFieldsTranslatorTest extends AbstractFieldsTranslatorTest<YangInstanceIdentifier> {
     @Override
     protected List<YangInstanceIdentifier> translateFields(final EffectiveModelContext modelContext,
-            final DataSchemaNode schemaNode, final FieldsParam fields) {
-        return NetconfFieldsTranslator.translate(modelContext, schemaNode, fields);
+            final DataSchemaContext startNode, final FieldsParam fields) {
+        return NetconfFieldsTranslator.translate(modelContext, startNode, fields);
     }
 
     @Override
index 1194cd62ea675b7adbdda8c9dc0ae7bd36be47c2..988c1c23420b950149e1b767c80e9e18346d279e 100644 (file)
@@ -16,7 +16,7 @@ import org.junit.runner.RunWith;
 import org.mockito.junit.MockitoJUnitRunner;
 import org.opendaylight.restconf.api.query.FieldsParam;
 import org.opendaylight.yangtools.yang.common.QName;
-import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.data.util.DataSchemaContext;
 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
 
 /**
@@ -26,8 +26,8 @@ import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
 public class WriterFieldsTranslatorTest extends AbstractFieldsTranslatorTest<Set<QName>> {
     @Override
     protected List<Set<QName>> translateFields(final EffectiveModelContext modelContext,
-            final DataSchemaNode schemaNode, final FieldsParam fields) {
-        return WriterFieldsTranslator.translate(modelContext, schemaNode, fields);
+            final DataSchemaContext startNode, final FieldsParam fields) {
+        return WriterFieldsTranslator.translate(modelContext, startNode, fields);
     }
 
     @Override
index 00e3a3c8efd337b6710cca8efcf3a3adfba2c122..a1ce8506a720f0ef0b106a5157a7db77ce106905 100644 (file)
@@ -8,6 +8,7 @@
 package org.opendaylight.restconf.nb.rfc8040.utils.parser;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 
 import com.google.common.collect.ImmutableMap;
@@ -20,6 +21,7 @@ import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
 import org.opendaylight.restconf.common.errors.RestconfError;
 import org.opendaylight.restconf.server.api.DatabindContext;
 import org.opendaylight.restconf.server.spi.ApiPathNormalizer;
+import org.opendaylight.restconf.server.spi.ApiPathNormalizer.DataPath;
 import org.opendaylight.yangtools.yang.common.ErrorTag;
 import org.opendaylight.yangtools.yang.common.ErrorType;
 import org.opendaylight.yangtools.yang.common.QName;
@@ -334,7 +336,8 @@ class YangInstanceIdentifierSerializerTest {
 
     private static YangInstanceIdentifier assertNormalized(final String str) {
         try {
-            return new ApiPathNormalizer(DATABIND).normalizePath(ApiPath.parse(str)).path;
+            return assertInstanceOf(DataPath.class, new ApiPathNormalizer(DATABIND).normalizePath(ApiPath.parse(str)))
+                .instance();
         } catch (ParseException e) {
             throw new AssertionError(e);
         }
diff --git a/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/server/mdsal/MdsalRestconfServerTest.java b/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/server/mdsal/MdsalRestconfServerTest.java
deleted file mode 100644 (file)
index 7962a98..0000000
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (c) 2023 PANTHEON.tech, s.r.o. 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.server.mdsal;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertInstanceOf;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.mockito.Mockito.doReturn;
-
-import com.google.common.collect.ImmutableClassToInstanceMap;
-import java.util.Optional;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
-import org.mockito.Mock;
-import org.mockito.junit.jupiter.MockitoExtension;
-import org.opendaylight.mdsal.dom.api.DOMActionService;
-import org.opendaylight.mdsal.dom.api.DOMDataBroker;
-import org.opendaylight.mdsal.dom.api.DOMMountPoint;
-import org.opendaylight.mdsal.dom.api.DOMMountPointService;
-import org.opendaylight.mdsal.dom.api.DOMRpcService;
-import org.opendaylight.mdsal.dom.api.DOMSchemaService;
-import org.opendaylight.mdsal.dom.spi.FixedDOMSchemaService;
-import org.opendaylight.netconf.dom.api.NetconfDataTreeService;
-import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
-import org.opendaylight.restconf.nb.rfc8040.AbstractJukeboxTest;
-import org.opendaylight.restconf.nb.rfc8040.rests.transactions.MdsalRestconfStrategy;
-import org.opendaylight.restconf.nb.rfc8040.rests.transactions.NetconfRestconfStrategy;
-import org.opendaylight.yangtools.yang.common.ErrorTag;
-import org.opendaylight.yangtools.yang.common.ErrorType;
-
-@ExtendWith(MockitoExtension.class)
-class MdsalRestconfServerTest extends AbstractJukeboxTest {
-    @Mock
-    private DOMMountPointService mountPointService;
-    @Mock
-    private DOMMountPoint mountPoint;
-    @Mock
-    private DOMDataBroker dataBroker;
-    @Mock
-    private NetconfDataTreeService netconfService;
-    @Mock
-    private DOMRpcService rpcService;
-    @Mock
-    private DOMActionService actionService;
-    @Mock
-    private DOMSchemaService schemaService;
-
-    private MdsalRestconfServer server;
-
-    @BeforeEach
-    void before() {
-        server = new MdsalRestconfServer(FixedDOMSchemaService.of(JUKEBOX_SCHEMA), dataBroker, rpcService,
-            actionService, mountPointService);
-    }
-
-    @Test
-    void testGetRestconfStrategyLocal() {
-        assertInstanceOf(MdsalRestconfStrategy.class, server.getRestconfStrategy(JUKEBOX_DATABIND, null));
-    }
-
-    @Test
-    void testGetRestconfStrategyMountDataBroker() {
-        doReturn(Optional.empty()).when(mountPoint).getService(NetconfDataTreeService.class);
-        doReturn(Optional.of(dataBroker)).when(mountPoint).getService(DOMDataBroker.class);
-        doReturn(Optional.of(rpcService)).when(mountPoint).getService(DOMRpcService.class);
-        doReturn(Optional.of(schemaService)).when(mountPoint).getService(DOMSchemaService.class);
-        doReturn(ImmutableClassToInstanceMap.of()).when(schemaService).getExtensions();
-        assertInstanceOf(MdsalRestconfStrategy.class, server.getRestconfStrategy(JUKEBOX_DATABIND, mountPoint));
-    }
-
-    @Test
-    void testGetRestconfStrategyMountNetconfService() {
-        doReturn(Optional.of(netconfService)).when(mountPoint).getService(NetconfDataTreeService.class);
-        doReturn(Optional.of(rpcService)).when(mountPoint).getService(DOMRpcService.class);
-        doReturn(Optional.of(schemaService)).when(mountPoint).getService(DOMSchemaService.class);
-        doReturn(ImmutableClassToInstanceMap.of()).when(schemaService).getExtensions();
-        assertInstanceOf(NetconfRestconfStrategy.class, server.getRestconfStrategy(JUKEBOX_DATABIND, mountPoint));
-    }
-
-    @Test
-    void testGetRestconfStrategyMountNone() {
-        doReturn(JUKEBOX_IID).when(mountPoint).getIdentifier();
-        doReturn(Optional.empty()).when(mountPoint).getService(NetconfDataTreeService.class);
-        doReturn(Optional.empty()).when(mountPoint).getService(DOMDataBroker.class);
-        doReturn(Optional.of(rpcService)).when(mountPoint).getService(DOMRpcService.class);
-        doReturn(Optional.of(schemaService)).when(mountPoint).getService(DOMSchemaService.class);
-        doReturn(ImmutableClassToInstanceMap.of()).when(schemaService).getExtensions();
-        final var ex = assertThrows(RestconfDocumentedException.class,
-            () -> server.getRestconfStrategy(JUKEBOX_DATABIND, mountPoint));
-        final var errors = ex.getErrors();
-        assertEquals(1, errors.size());
-        final var error = errors.get(0);
-        assertEquals(ErrorType.APPLICATION, error.getErrorType());
-        assertEquals(ErrorTag.OPERATION_FAILED, error.getErrorTag());
-        assertEquals("Could not find a supported access interface in mount point", error.getErrorMessage());
-        assertEquals(JUKEBOX_IID, error.getErrorPath());
-    }
-}
index d519f7c18c594c2834b8220c13da74e754874238..e2502b1d2c29425cc1e2ddf5ddba055678ec696a 100644 (file)
@@ -10,7 +10,6 @@ package org.opendaylight.restconf.server.spi;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertInstanceOf;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 
 import com.google.common.collect.ImmutableMap;
@@ -20,7 +19,8 @@ import org.opendaylight.restconf.api.ApiPath;
 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
 import org.opendaylight.restconf.common.errors.RestconfError;
 import org.opendaylight.restconf.server.api.DatabindContext;
-import org.opendaylight.restconf.server.spi.ApiPathNormalizer.Result;
+import org.opendaylight.restconf.server.spi.ApiPathNormalizer.DataPath;
+import org.opendaylight.restconf.server.spi.ApiPathNormalizer.OperationPath.Action;
 import org.opendaylight.yangtools.yang.common.ErrorTag;
 import org.opendaylight.yangtools.yang.common.ErrorType;
 import org.opendaylight.yangtools.yang.common.QName;
@@ -49,7 +49,7 @@ class ApiPathNormalizerTest {
      */
     @Test
     void deserializeContainerTest() {
-        final var result = assertNormalizedPath("deserializer-test:contA").path.getPathArguments();
+        final var result = assertNormalizedPath("deserializer-test:contA").getPathArguments();
         assertEquals(1, result.size());
         assertEquals(NodeIdentifier.create(QName.create("deserializer:test", "2016-06-06", "contA")), result.get(0));
     }
@@ -60,7 +60,7 @@ class ApiPathNormalizerTest {
      */
     @Test
     void deserializeContainerWithLeafTest() {
-        final var result = assertNormalizedPath("deserializer-test:contA/leaf-A").path.getPathArguments();
+        final var result = assertNormalizedPath("deserializer-test:contA/leaf-A").getPathArguments();
         assertEquals(2, result.size());
         assertEquals(NodeIdentifier.create(QName.create("deserializer:test", "2016-06-06", "contA")), result.get(0));
         assertEquals(NodeIdentifier.create(QName.create("deserializer:test", "2016-06-06", "leaf-A")), result.get(1));
@@ -72,7 +72,7 @@ class ApiPathNormalizerTest {
      */
     @Test
     void deserializeContainerWithListWithLeafListTest() {
-        final var result = assertNormalizedPath("deserializer-test:contA/list-A=100/leaf-list-AA=instance").path
+        final var result = assertNormalizedPath("deserializer-test:contA/list-A=100/leaf-list-AA=instance")
             .getPathArguments();
         assertEquals(5, result.size());
 
@@ -95,17 +95,16 @@ class ApiPathNormalizerTest {
      */
     @Test
     void deserializeContainerWithListWithActionTest() {
-        final var result = assertNormalizedPath("example-actions:interfaces/interface=eth0/reset").path
-            .getPathArguments();
-        assertEquals(4, result.size());
-        // container
-        assertEquals(NodeIdentifier.create(ACTIONS_INTERFACES), result.get(0));
-        // list
+        final var result = assertNormalizedAction("example-actions:interfaces/interface=eth0/reset");
         final var list = QName.create(ACTIONS_INTERFACES, "interface");
-        assertEquals(NodeIdentifier.create(list), result.get(1));
-        assertEquals(NodeIdentifierWithPredicates.of(list, QName.create(list, "name"), "eth0"), result.get(2));
-        // action
-        assertEquals(NodeIdentifier.create(QName.create(ACTIONS_INTERFACES, "reset")), result.get(3));
+        assertEquals(YangInstanceIdentifier.builder()
+            // container
+            .node(ACTIONS_INTERFACES)
+            // list
+            .node(list)
+            .nodeWithKey(list, QName.create(list, "name"), "eth0")
+            .build(), result.instance());
+        assertEquals(QName.create(ACTIONS_INTERFACES, "reset"), result.action().argument());
     }
 
     /**
@@ -114,24 +113,20 @@ class ApiPathNormalizerTest {
      */
     @Test
     void deserializeContainerWithChoiceSchemaNodeWithActionTest() {
-        final var result = assertNormalizedPath("example-actions:interfaces/typeA-gigabyte/interface=eth0/reboot").path
-            .getPathArguments();
-        assertEquals(6, result.size());
-
-        // container
-        assertEquals(NodeIdentifier.create(ACTIONS_INTERFACES), result.get(0));
-        // choice
-        assertEquals(NodeIdentifier.create(QName.create(ACTIONS_INTERFACES, "interface-type")), result.get(1));
-        // container
-        assertEquals(NodeIdentifier.create(QName.create(ACTIONS_INTERFACES, "typeA-gigabyte")), result.get(2));
-
-        // list
+        final var result = assertNormalizedAction("example-actions:interfaces/typeA-gigabyte/interface=eth0/reboot");
         final var list = QName.create(ACTIONS_INTERFACES, "interface");
-        assertEquals(NodeIdentifier.create(list), result.get(3));
-        assertEquals(NodeIdentifierWithPredicates.of(list, QName.create(list, "name"), "eth0"), result.get(4));
-
-        // action QName
-        assertEquals(NodeIdentifier.create(QName.create(ACTIONS_INTERFACES, "reboot")), result.get(5));
+        assertEquals(YangInstanceIdentifier.builder()
+            // container
+            .node(ACTIONS_INTERFACES)
+            // choice
+            .node(QName.create(ACTIONS_INTERFACES, "interface-type"))
+            // container
+            .node(QName.create(ACTIONS_INTERFACES, "typeA-gigabyte"))
+            // list
+            .node(list)
+            .nodeWithKey(list, QName.create(list, "name"), "eth0")
+            .build(), result.instance());
+        assertEquals(QName.create(ACTIONS_INTERFACES, "reboot"), result.action().argument());
     }
 
     /**
@@ -140,16 +135,15 @@ class ApiPathNormalizerTest {
      */
     @Test
     void deserializeContainerWithChoiceCaseSchemaNodeWithActionTest() {
-        final var result = assertNormalizedPath("example-actions:interfaces/udp/reboot").path.getPathArguments();
-        assertEquals(4, result.size());
-        // container
-        assertEquals(NodeIdentifier.create(ACTIONS_INTERFACES), result.get(0));
-        // choice
-        assertEquals(NodeIdentifier.create(QName.create(ACTIONS_INTERFACES, "protocol")), result.get(1));
-        // choice container
-        assertEquals(NodeIdentifier.create(QName.create(ACTIONS_INTERFACES, "udp")), result.get(2));
-        // action QName
-        assertEquals(NodeIdentifier.create(QName.create(ACTIONS_INTERFACES, "reboot")), result.get(3));
+        final var result = assertNormalizedAction("example-actions:interfaces/udp/reboot");
+        assertEquals(YangInstanceIdentifier.of(
+            // container
+            ACTIONS_INTERFACES,
+            // choice
+            QName.create(ACTIONS_INTERFACES, "protocol"),
+            // choice container
+            QName.create(ACTIONS_INTERFACES, "udp")),
+            result.instance());
     }
 
     /**
@@ -158,7 +152,7 @@ class ApiPathNormalizerTest {
      */
     @Test
     void deserializeListWithNoKeysTest() {
-        final var result = assertNormalizedPath("deserializer-test:list-no-key").path.getPathArguments();
+        final var result = assertNormalizedPath("deserializer-test:list-no-key").getPathArguments();
         assertEquals(2, result.size());
         final var list = QName.create("deserializer:test", "2016-06-06", "list-no-key");
         assertEquals(NodeIdentifier.create(list), result.get(0));
@@ -171,7 +165,7 @@ class ApiPathNormalizerTest {
      */
     @Test
     void deserializeListWithOneKeyTest() {
-        final var result = assertNormalizedPath("deserializer-test:list-one-key=value").path.getPathArguments();
+        final var result = assertNormalizedPath("deserializer-test:list-one-key=value").getPathArguments();
         assertEquals(2, result.size());
         final QName list = QName.create("deserializer:test", "2016-06-06", "list-one-key");
         assertEquals(NodeIdentifier.create(list), result.get(0));
@@ -190,7 +184,7 @@ class ApiPathNormalizerTest {
             QName.create(list, "number"), Uint8.valueOf(100),
             QName.create(list, "enabled"), false);
 
-        final var result = assertNormalizedPath("deserializer-test:list-multiple-keys=value,100,false").path
+        final var result = assertNormalizedPath("deserializer-test:list-multiple-keys=value,100,false")
             .getPathArguments();
         assertEquals(2, result.size());
         assertEquals(NodeIdentifier.create(list), result.get(0));
@@ -203,7 +197,7 @@ class ApiPathNormalizerTest {
      */
     @Test
     void deserializeLeafListTest() {
-        final var result = assertNormalizedPath("deserializer-test:leaf-list-0=true").path.getPathArguments();
+        final var result = assertNormalizedPath("deserializer-test:leaf-list-0=true").getPathArguments();
         assertEquals(2, result.size());
 
         final QName leafList = QName.create("deserializer:test", "2016-06-06", "leaf-list-0");
@@ -216,7 +210,7 @@ class ApiPathNormalizerTest {
      */
     @Test
     void deserializeEmptyDataTest() {
-        assertEquals(YangInstanceIdentifier.of(), assertNormalizedPath("").path);
+        assertEquals(YangInstanceIdentifier.of(), assertNormalizedPath(""));
     }
 
     /**
@@ -319,7 +313,7 @@ class ApiPathNormalizerTest {
             QName.create(list, "enabled"), false);
 
         final var result = assertNormalizedPath("deserializer-test:list-multiple-keys=%3Afoo,1,false/string-value")
-            .path.getPathArguments();
+            .getPathArguments();
         assertEquals(3, result.size());
         // list
         assertEquals(NodeIdentifier.create(list), result.get(0));
@@ -351,7 +345,7 @@ class ApiPathNormalizerTest {
     @Test
     void percentEncodedKeyEndsWithNoPercentEncodedChars() {
         final var URI = "deserializer-test:list-multiple-keys=%3Afoo,1,true";
-        final var result = assertNormalizedPath(URI).path;
+        final var result = assertNormalizedPath(URI);
 
         final var resultListKeys = assertInstanceOf(NodeIdentifierWithPredicates.class, result.getLastPathArgument())
             .entrySet().iterator();
@@ -371,7 +365,7 @@ class ApiPathNormalizerTest {
             QName.create(list, "number"), Uint8.ZERO,
             QName.create(list, "enabled"), true);
 
-        final var result = assertNormalizedPath("deserializer-test:list-multiple-keys=,0,true").path.getPathArguments();
+        final var result = assertNormalizedPath("deserializer-test:list-multiple-keys=,0,true").getPathArguments();
         assertEquals(2, result.size());
         assertEquals(NodeIdentifier.create(list), result.get(0));
         assertEquals(NodeIdentifierWithPredicates.of(list, values), result.get(1));
@@ -397,7 +391,7 @@ class ApiPathNormalizerTest {
     @Test
     void deserializePartInOtherModuleTest() {
         final var result = assertNormalizedPath(
-            "deserializer-test-included:augmented-list=100/deserializer-test:augmented-leaf").path.getPathArguments();
+            "deserializer-test-included:augmented-list=100/deserializer-test:augmented-leaf").getPathArguments();
         assertEquals(3, result.size());
 
         // list
@@ -414,8 +408,7 @@ class ApiPathNormalizerTest {
     @Test
     void deserializeListInOtherModuleTest() {
         final var result = assertNormalizedPath(
-            "deserializer-test-included:augmented-list=100/deserializer-test:augmenting-list=0")
-            .path.getPathArguments();
+            "deserializer-test-included:augmented-list=100/deserializer-test:augmenting-list=0").getPathArguments();
         assertEquals(4, result.size());
 
         // list
@@ -456,10 +449,12 @@ class ApiPathNormalizerTest {
         return errors.get(0);
     }
 
-    private static Result assertNormalizedPath(final String path) {
-        final var result = NORMALIZER.normalizePath(assertApiPath(path));
-        assertNotNull(result);
-        return result;
+    private static Action assertNormalizedAction(final String path) {
+        return assertInstanceOf(Action.class, NORMALIZER.normalizePath(assertApiPath(path)));
+    }
+
+    private static YangInstanceIdentifier assertNormalizedPath(final String path) {
+        return assertInstanceOf(DataPath.class, NORMALIZER.normalizePath(assertApiPath(path))).instance();
     }
 
     private static ApiPath assertApiPath(final String path) {
@@ -471,7 +466,7 @@ class ApiPathNormalizerTest {
     }
 
     private static void assertIdentityrefKeyValue(final String path) {
-        final var pathArgs = assertNormalizedPath(path).path.getPathArguments();
+        final var pathArgs = assertNormalizedPath(path).getPathArguments();
         assertEquals(4, pathArgs.size());
 
         assertEquals("refs", pathArgs.get(0).getNodeType().getLocalName());