Refactor OperationInputBody 31/107731/4
authorRobert Varga <robert.varga@pantheon.tech>
Wed, 6 Sep 2023 11:23:38 +0000 (13:23 +0200)
committerRobert Varga <robert.varga@pantheon.tech>
Wed, 6 Sep 2023 14:26:28 +0000 (16:26 +0200)
Add utility to extract the ContainerNode rather than streaming them.
Also detect empty body.

JIRA: NETCONF-1128
Change-Id: I06b8366d15353785f0f859fb7d11b927a26c3b76
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
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/rests/services/impl/RestconfInvokeOperationsServiceImpl.java

index 1aab9abe4a5e54ba8ea4ee5833bde1a3f63d759e..ef6aa10f4d98fa1d43c5c4db13b51c3c59254ca8 100644 (file)
@@ -9,8 +9,17 @@ package org.opendaylight.restconf.nb.rfc8040.databind;
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.PushbackInputStream;
 import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+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.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.InputEffectiveStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.RpcEffectiveStatement;
 import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
 
 /**
@@ -26,15 +35,42 @@ 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 writer Target writer
+     * @return The document body, or an empty container node
      * @throws IOException when an I/O error occurs
      */
-    // TODO: pass down DatabindContext corresponding to inference
-    public final void streamTo(final @NonNull Inference inference, final @NonNull NormalizedNodeStreamWriter writer)
-            throws IOException {
-        streamTo(acquireStream(), inference, writer);
+    public @NonNull ContainerNode toContainerNode(final @NonNull Inference inference) throws IOException {
+        try (var is = new PushbackInputStream(acquireStream())) {
+            final var firstByte = is.read();
+            if (firstByte == -1) {
+                return emptyInput(inference);
+            }
+            is.unread(firstByte);
+
+            final var holder = new NormalizationResultHolder();
+            try (var streamWriter = ImmutableNormalizedNodeStreamWriter.from(holder)) {
+                streamTo(is, inference, streamWriter);
+            }
+            return (ContainerNode) holder.getResult().data();
+        }
     }
 
     abstract void streamTo(@NonNull InputStream inputStream, @NonNull Inference inference,
         @NonNull NormalizedNodeStreamWriter writer) throws IOException;
+
+    private static @NonNull ContainerNode emptyInput(final Inference inference) {
+        return Builders.containerBuilder()
+            .withNodeIdentifier(new NodeIdentifier(extractInput(inference).argument()))
+            .build();
+    }
+
+    private static @NonNull InputEffectiveStatement extractInput(final Inference inference) {
+        final var stmt = inference.toSchemaInferenceStack().currentStatement();
+        if (stmt instanceof RpcEffectiveStatement rpc) {
+            return rpc.input();
+        } else if (stmt instanceof ActionEffectiveStatement action) {
+            return action.streamEffectiveSubstatements(InputEffectiveStatement.class).findFirst().orElseThrow();
+        } else {
+            throw new IllegalStateException(inference + " does not identify an 'rpc' statement");
+        }
+    }
 }
index df3126aa4e329ad6f9bf6a8c5a93b286feeb3a4a..4f4543d101c6f353121f4497431fa6d7e561da79 100644 (file)
@@ -57,8 +57,6 @@ import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
 import org.opendaylight.yangtools.yang.common.YangConstants;
 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
 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.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -149,16 +147,15 @@ public final class RestconfInvokeOperationsServiceImpl {
         final var schemaContext = dataBind.modelContext();
         final var context = ParserIdentifier.toInstanceIdentifier(identifier, schemaContext, mountPointService);
 
-        final var holder = new NormalizationResultHolder();
-        try (var streamWriter = ImmutableNormalizedNodeStreamWriter.from(holder)) {
-            body.streamTo(context.inference(), streamWriter);
+        final ContainerNode input;
+        try {
+            input = body.toContainerNode(context.inference());
         } catch (IOException e) {
             LOG.debug("Error reading input", e);
             throw new RestconfDocumentedException("Error parsing input: " + e.getMessage(), ErrorType.PROTOCOL,
                     ErrorTag.MALFORMED_MESSAGE, e);
         }
         final var rpcName = context.getSchemaNode().getQName();
-        final var input = (ContainerNode) holder.getResult().data();
 
         final ListenableFuture<? extends DOMRpcResult> future;
         final var mountPoint = context.getMountPoint();