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;
}
@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);
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.
/**
* 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");
- }
- }
}
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;
}
@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);
--- /dev/null
+/*
+ * 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");
+ }
+ }
+}
/**
* 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,
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;
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(),
}
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);
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(),
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
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
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();
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();
@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();
.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();