Add OperationsPostPath 87/109087/3
authorRobert Varga <robert.varga@pantheon.tech>
Tue, 28 Nov 2023 03:11:13 +0000 (04:11 +0100)
committerRobert Varga <robert.varga@pantheon.tech>
Tue, 28 Nov 2023 03:45:05 +0000 (04:45 +0100)
This is a nice capture of DatabindContext and the logic we need to
decode OperationInputBody -- using which we can reuse JSON/XML codecs.

JIRA: NETCONF-1157
Change-Id: I6a7136c1c54293be22a85ef015992cc2fb349971
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/databind/JsonOperationInputBody.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/databind/OperationInputBody.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/databind/XmlOperationInputBody.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/OperationsPostPath.java [new file with mode: 0644]
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/OperationsPostResult.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/mdsal/MdsalRestconfServer.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/databind/AbstractOperationInputBodyTest.java

index 674cada08d867a14ee6e37f0bc6520b874ef0286..ad92b553567a0439aef1324e871dbde5b53e0982 100644 (file)
@@ -14,12 +14,11 @@ import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.nio.charset.StandardCharsets;
 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.server.api.OperationsPostPath;
 import org.opendaylight.yangtools.yang.common.ErrorTag;
 import org.opendaylight.yangtools.yang.common.ErrorType;
 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
-import org.opendaylight.yangtools.yang.data.codec.gson.JSONCodecFactorySupplier;
 import org.opendaylight.yangtools.yang.data.codec.gson.JsonParserStream;
-import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -31,11 +30,10 @@ public final class JsonOperationInputBody extends OperationInputBody {
     }
 
     @Override
-    void streamTo(final InputStream inputStream, final Inference inference, final NormalizedNodeStreamWriter writer)
+    void streamTo(final OperationsPostPath path, final InputStream inputStream, final NormalizedNodeStreamWriter writer)
             throws IOException {
         try {
-            JsonParserStream.create(writer,
-                JSONCodecFactorySupplier.RFC7951.getShared(inference.getEffectiveModelContext()), inference)
+            JsonParserStream.create(writer, path.databind().jsonCodecs(), path.operation())
                 .parse(new JsonReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)));
         } catch (JsonParseException e) {
             LOG.debug("Error parsing JSON input", e);
index 7a831cd4b0634859f583d4f947dc14818b417fd4..c87b473c1c5c67cd0dc3a2264bf65e821f450cee 100644 (file)
@@ -11,17 +11,12 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.PushbackInputStream;
 import org.eclipse.jdt.annotation.NonNull;
-import org.opendaylight.yangtools.yang.common.QName;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.restconf.server.api.OperationsPostPath;
 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
-import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter;
 import org.opendaylight.yangtools.yang.data.impl.schema.NormalizationResultHolder;
-import org.opendaylight.yangtools.yang.model.api.stmt.ActionEffectiveStatement;
-import org.opendaylight.yangtools.yang.model.api.stmt.RpcEffectiveStatement;
-import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
-import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
 
 /**
  * Access to an {code rpc}'s or an {@code action}'s input.
@@ -35,39 +30,26 @@ public abstract sealed class OperationInputBody extends AbstractBody
     /**
      * Stream the {@code input} into a {@link NormalizedNodeStreamWriter}.
      *
-     * @param inference An {@link Inference} of parent {@code rpc} or {@code action} statement
+     * @param path The {@link OperationsPostPath} of the operation invocation
      * @return The document body, or an empty container node
      * @throws IOException when an I/O error occurs
      */
-    public @NonNull ContainerNode toContainerNode(final @NonNull Inference inference) throws IOException {
+    public @NonNull ContainerNode toContainerNode(final @NonNull OperationsPostPath path) throws IOException {
         try (var is = new PushbackInputStream(acquireStream())) {
             final var firstByte = is.read();
             if (firstByte == -1) {
-                return Builders.containerBuilder()
-                    .withNodeIdentifier(new NodeIdentifier(extractInputQName(inference.toSchemaInferenceStack())))
-                    .build();
+                return ImmutableNodes.containerNode(path.inputQName());
             }
             is.unread(firstByte);
 
             final var holder = new NormalizationResultHolder();
             try (var streamWriter = ImmutableNormalizedNodeStreamWriter.from(holder)) {
-                streamTo(is, inference, streamWriter);
+                streamTo(path, is, streamWriter);
             }
             return (ContainerNode) holder.getResult().data();
         }
     }
 
-    abstract void streamTo(@NonNull InputStream inputStream, @NonNull Inference inference,
+    abstract void streamTo(@NonNull OperationsPostPath path, @NonNull InputStream inputStream,
         @NonNull NormalizedNodeStreamWriter writer) throws IOException;
-
-    static final @NonNull QName extractInputQName(final SchemaInferenceStack stack) {
-        final var stmt = stack.currentStatement();
-        if (stmt instanceof RpcEffectiveStatement rpc) {
-            return rpc.input().argument();
-        } else if (stmt instanceof ActionEffectiveStatement action) {
-            return action.input().argument();
-        } else {
-            throw new IllegalStateException(stack + " does not identify an 'rpc' nor an 'action' statement");
-        }
-    }
 }
index 8d99dadeac0f357908a584e150a49cd8e24b1ee5..af8652537ba246939ebf72322b28fb0a6f48c1fb 100644 (file)
@@ -11,12 +11,12 @@ import java.io.IOException;
 import java.io.InputStream;
 import javax.xml.stream.XMLStreamException;
 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.server.api.OperationsPostPath;
 import org.opendaylight.yangtools.util.xml.UntrustedXML;
 import org.opendaylight.yangtools.yang.common.ErrorTag;
 import org.opendaylight.yangtools.yang.common.ErrorType;
 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
 import org.opendaylight.yangtools.yang.data.codec.xml.XmlParserStream;
-import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -28,14 +28,11 @@ public final class XmlOperationInputBody extends OperationInputBody {
     }
 
     @Override
-    void streamTo(final InputStream inputStream, final Inference inference, final NormalizedNodeStreamWriter writer)
+    void streamTo(final OperationsPostPath path, final InputStream inputStream, final NormalizedNodeStreamWriter writer)
             throws IOException {
-        // Adjust inference to point to input
-        final var stack = inference.toSchemaInferenceStack();
-        stack.enterDataTree(extractInputQName(stack));
-
         try {
-            XmlParserStream.create(writer, stack.toInference()).parse(UntrustedXML.createXMLStreamReader(inputStream));
+            XmlParserStream.create(writer, path.databind().xmlCodecs(), path.input())
+                .parse(UntrustedXML.createXMLStreamReader(inputStream));
         } catch (XMLStreamException e) {
             LOG.debug("Error parsing XML input", e);
             throwIfYangError(e);
diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/OperationsPostPath.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/OperationsPostPath.java
new file mode 100644 (file)
index 0000000..7237695
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * 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.api;
+
+import static java.util.Objects.requireNonNull;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.opendaylight.restconf.api.ApiPath;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.ActionEffectiveStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.RpcEffectiveStatement;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
+
+/**
+ * An {@link ApiPath} subpath of {@code /operations} {@code POST} HTTP operation, as defined in
+ * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-3.6">RFC8040 Operation Resource</a> and
+ * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.4.2">RFC8040 Invoke Operation Mode</a>.
+ *
+ * @param databind Associated {@link DatabindContext}
+ * @param operation Associated {@link Inference} pointing to the {@link EffectiveStatement} of an {@code rpc} or an
+ *                  {@code action} invocation, inference pointing to the statement
+ * @see OperationsPostResult
+ */
+@NonNullByDefault
+public record OperationsPostPath(DatabindContext databind, Inference operation) implements DatabindAware {
+    public OperationsPostPath {
+        requireNonNull(databind);
+        requireNonNull(operation);
+    }
+
+    public Inference input() {
+        final var stack = operation.toSchemaInferenceStack();
+        stack.enterDataTree(inputQName(stack));
+        return stack.toInference();
+    }
+
+    public QName inputQName() {
+        return inputQName(operation.toSchemaInferenceStack());
+    }
+
+    private static QName inputQName(final SchemaInferenceStack stack) {
+        final var stmt = stack.currentStatement();
+        if (stmt instanceof RpcEffectiveStatement rpc) {
+            return rpc.input().argument();
+        } else if (stmt instanceof ActionEffectiveStatement action) {
+            return action.input().argument();
+        } else {
+            throw new IllegalStateException(stack + " does not identify an 'rpc' nor an 'action' statement");
+        }
+    }
+}
index b6d6470b2ed729297c162b896391a176609424f0..8ce4b5a0c5118f42c35629abaaaada22ccaf38ae 100644 (file)
@@ -16,11 +16,13 @@ import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference
 
 /**
  * RESTCONF {@code /operations} content for a {@code POST} operation as per
- * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-3.6">RFC8040 Operation Resource</a>.
+ * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-3.6">RFC8040 Operation Resource</a> and
+ * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.4.2">RFC8040 Invoke Operation Mode</a>.
  *
  * @param databind Associated {@link DatabindContext}
  * @param operation An {@link Inference} pointing to the invoked operation
  * @param output Operation output, or {@code null} if output would be empty
+ * @see OperationsPostPath
  */
 public record OperationsPostResult(
         @NonNull DatabindContext databind,
index e969b9115e8a0f3c48a93c9d4e8f93de0e8a1e63..c8548f5e53353baa7fc4719bffa9fe581d1fc8f7 100644 (file)
@@ -77,6 +77,7 @@ 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;
@@ -357,11 +358,11 @@ public final class MdsalRestconfServer
 
     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 var inference = reqPath.inference();
         final ContainerNode input;
         try {
-            input = body.toContainerNode(inference);
+            input = body.toContainerNode(postPath);
         } catch (IOException e) {
             LOG.debug("Error reading input", e);
             return RestconfFuture.failed(new RestconfDocumentedException("Error parsing input: " + e.getMessage(),
@@ -369,7 +370,7 @@ public final class MdsalRestconfServer
         }
 
         final var mountPoint = reqPath.getMountPoint();
-        final var schemaPath = inference.toSchemaInferenceStack().toSchemaNodeIdentifier();
+        final var schemaPath = postPath.operation().toSchemaInferenceStack().toSchemaNodeIdentifier();
         final var future = mountPoint != null ? dataInvokePOST(input, schemaPath, yangIIdContext, mountPoint)
             : dataInvokePOST(input, schemaPath, yangIIdContext, actionService);
 
@@ -607,10 +608,11 @@ public final class MdsalRestconfServer
     public RestconfFuture<OperationsPostResult> operationsPOST(final URI restconfURI, final ApiPath apiPath,
             final OperationInputBody body) {
         final var reqPath = bindRequestPath(localStrategy(), apiPath);
-        final var inference = reqPath.inference();
+        final var postPath = new OperationsPostPath(reqPath.databind(), reqPath.inference());
+
         final ContainerNode input;
         try {
-            input = body.toContainerNode(inference);
+            input = body.toContainerNode(postPath);
         } catch (IOException e) {
             LOG.debug("Error reading input", e);
             return RestconfFuture.failed(new RestconfDocumentedException("Error parsing input: " + e.getMessage(),
@@ -619,7 +621,7 @@ public final class MdsalRestconfServer
 
         final var strategy = getRestconfStrategy(reqPath.databind(), reqPath.getMountPoint());
         return strategy.invokeRpc(restconfURI, reqPath.getSchemaNode().getQName(),
-                new OperationInput(strategy.databind(), inference, input));
+                new OperationInput(strategy.databind(), postPath.operation(), input));
     }
 
     @Override
index 2b91d58a50086fca697b6bc8f577441040f51465..86dd04e892782adaa067d18022578a816f1fb604 100644 (file)
@@ -12,25 +12,26 @@ import static org.junit.Assert.assertEquals;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.opendaylight.restconf.nb.rfc8040.AbstractInstanceIdentifierTest;
+import org.opendaylight.restconf.server.api.DatabindContext;
+import org.opendaylight.restconf.server.api.OperationsPostPath;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.common.Uint32;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
 import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
-import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
 import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
 
 abstract class AbstractOperationInputBodyTest extends AbstractInstanceIdentifierTest {
     private static final NodeIdentifier INPUT_NID = new NodeIdentifier(QName.create(CONT_QNAME, "input"));
 
-    private static Inference RESET_INFERENCE;
+    private static OperationsPostPath RESET_PATH;
 
     @BeforeClass
     public static final void setupInference() {
         final var stack = SchemaInferenceStack.ofDataTreePath(IID_SCHEMA, CONT_QNAME, CONT1_QNAME);
         stack.enterSchemaTree(RESET_QNAME);
-        RESET_INFERENCE = stack.toInference();
+        RESET_PATH = new OperationsPostPath(IID_DATABIND, stack.toInference());
     }
 
     @Test
@@ -40,7 +41,7 @@ abstract class AbstractOperationInputBodyTest extends AbstractInstanceIdentifier
         assertEquals(Builders.containerBuilder()
             .withNodeIdentifier(INPUT_NID)
             .withChild(ImmutableNodes.leafNode(DELAY_QNAME, Uint32.valueOf(600)))
-            .build(), body.toContainerNode(RESET_INFERENCE));
+            .build(), body.toContainerNode(RESET_PATH));
     }
 
     abstract OperationInputBody moduleSubContainerDataPostActionBody();
@@ -49,7 +50,7 @@ abstract class AbstractOperationInputBodyTest extends AbstractInstanceIdentifier
     public final void testEmpty() throws Exception {
         final var body = testEmptyBody();
         assertEquals(Builders.containerBuilder().withNodeIdentifier(INPUT_NID).build(),
-            body.toContainerNode(RESET_INFERENCE));
+            body.toContainerNode(RESET_PATH));
     }
 
     abstract OperationInputBody testEmptyBody();
@@ -57,7 +58,8 @@ abstract class AbstractOperationInputBodyTest extends AbstractInstanceIdentifier
     @Test
     public final void testRpcModuleInput() throws Exception {
         final var rpcTest = QName.create("invoke:rpc:module", "2013-12-03", "rpc-test");
-        final var stack = SchemaInferenceStack.of(YangParserTestUtils.parseYangResourceDirectory("/invoke-rpc"));
+        final var modelContext = YangParserTestUtils.parseYangResourceDirectory("/invoke-rpc");
+        final var stack = SchemaInferenceStack.of(modelContext);
         stack.enterSchemaTree(rpcTest);
 
         final var body = testRpcModuleInputBody();
@@ -68,7 +70,8 @@ abstract class AbstractOperationInputBodyTest extends AbstractInstanceIdentifier
                 .withNodeIdentifier(new NodeIdentifier(QName.create(rpcTest, "cont")))
                 .withChild(ImmutableNodes.leafNode(QName.create(rpcTest, "lf"), "lf-test"))
                 .build())
-            .build(), body.toContainerNode(stack.toInference()));
+            .build(),
+            body.toContainerNode(new OperationsPostPath(DatabindContext.ofModel(modelContext), stack.toInference())));
     }
 
     abstract OperationInputBody testRpcModuleInputBody();