import org.opendaylight.restconf.server.api.JsonResourceBody;
import org.opendaylight.restconf.server.api.ModulesGetResult;
import org.opendaylight.restconf.server.api.OperationInputBody;
+import org.opendaylight.restconf.server.api.OperationOutputBody;
import org.opendaylight.restconf.server.api.OperationsGetResult;
import org.opendaylight.restconf.server.api.OperationsPostResult;
import org.opendaylight.restconf.server.api.RestconfServer;
Response transform(final OperationsPostResult result) {
final var body = result.output();
return body == null ? Response.noContent().build()
- : Response.ok().entity(new NormalizedNodePayload(result.path().inference(), body)).build();
+ : Response.ok().entity(new OperationOutputBody(result.path(), body, false)).build();
}
});
}
--- /dev/null
+/*
+ * Copyright (c) 2024 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.nb.jaxrs;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.ext.Provider;
+import org.opendaylight.restconf.api.MediaTypes;
+import org.opendaylight.restconf.server.api.ReplyBody;
+
+@Provider
+@Produces({ MediaTypes.APPLICATION_YANG_DATA_JSON, MediaType.APPLICATION_JSON })
+public final class JsonReplyBodyWriter extends ReplyBodyWriter {
+ @Override
+ void writeTo(final ReplyBody body, final OutputStream out) throws IOException {
+ body.writeJSON(out);
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2024 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.nb.jaxrs;
+
+import static java.util.Objects.requireNonNull;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyWriter;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.restconf.server.api.ReplyBody;
+
+abstract sealed class ReplyBodyWriter implements MessageBodyWriter<ReplyBody>
+ permits JsonReplyBodyWriter, XmlReplyBodyWriter {
+ @Override
+ public final boolean isWriteable(final Class<?> type, final Type genericType, final Annotation[] annotations,
+ final MediaType mediaType) {
+ return ReplyBody.class.isAssignableFrom(type);
+ }
+
+ @Override
+ public final void writeTo(final ReplyBody body, final Class<?> type, final Type genericType,
+ final Annotation[] annotations, final MediaType mediaType, final MultivaluedMap<String, Object> httpHeaders,
+ final OutputStream entityStream) throws IOException {
+ writeTo(requireNonNull(body), requireNonNull(entityStream));
+ }
+
+ abstract void writeTo(@NonNull ReplyBody body, @NonNull OutputStream out) throws IOException;
+}
--- /dev/null
+/*
+ * Copyright (c) 2024 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.nb.jaxrs;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.ext.Provider;
+import org.opendaylight.restconf.api.MediaTypes;
+import org.opendaylight.restconf.server.api.ReplyBody;
+
+@Provider
+@Produces({ MediaTypes.APPLICATION_YANG_DATA_XML, MediaType.APPLICATION_XML, MediaType.TEXT_XML })
+public final class XmlReplyBodyWriter extends ReplyBodyWriter {
+ @Override
+ void writeTo(final ReplyBody body, final OutputStream out) throws IOException {
+ body.writeXML(out);
+ }
+}
import org.opendaylight.aaa.web.servlet.ServletSupport;
import org.opendaylight.restconf.nb.jaxrs.JaxRsRestconf;
import org.opendaylight.restconf.nb.jaxrs.JaxRsWebHostMetadata;
+import org.opendaylight.restconf.nb.jaxrs.JsonReplyBodyWriter;
+import org.opendaylight.restconf.nb.jaxrs.XmlReplyBodyWriter;
import org.opendaylight.restconf.nb.rfc8040.jersey.providers.JsonNormalizedNodeBodyWriter;
import org.opendaylight.restconf.nb.rfc8040.jersey.providers.JsonPatchStatusBodyWriter;
import org.opendaylight.restconf.nb.rfc8040.jersey.providers.XmlNormalizedNodeBodyWriter;
@Override
public Set<Object> getSingletons() {
return Set.of(
+ new JsonReplyBodyWriter(), new XmlReplyBodyWriter(),
new RestconfDocumentedExceptionMapper(databindProvider),
new JaxRsRestconf(server));
}
import org.eclipse.jdt.annotation.NonNull;
import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
import org.opendaylight.restconf.nb.rfc8040.legacy.QueryParameters;
-import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
-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;
abstract class AbstractNormalizedNodeBodyWriter implements MessageBodyWriter<NormalizedNodePayload> {
public final void writeTo(final NormalizedNodePayload context, final Class<?> type, final Type genericType,
final Annotation[] annotations, final MediaType mediaType, final MultivaluedMap<String, Object> httpHeaders,
final OutputStream entityStream) throws IOException {
- final var output = requireNonNull(entityStream);
- final var stack = context.inference().toSchemaInferenceStack();
- // FIXME: this dispatch is here to handle codec transition to 'output', but that should be completely okay with
- // the instantiation path we are using (based in Inference).
- if (!stack.isEmpty()) {
- final var stmt = stack.currentStatement();
- if (stmt instanceof RpcEffectiveStatement rpc) {
- stack.enterSchemaTree(rpc.output().argument());
- writeOperationOutput(stack, context.writerParameters(), (ContainerNode) context.data(), output);
- return;
- } else if (stmt instanceof ActionEffectiveStatement action) {
- stack.enterSchemaTree(action.output().argument());
- writeOperationOutput(stack, context.writerParameters(), (ContainerNode) context.data(), output);
- return;
- }
- }
- writeData(stack, context.writerParameters(), context.data(), output);
+ writeData(context.inference().toSchemaInferenceStack(), context.writerParameters(), context.data(),
+ requireNonNull(entityStream));
}
- abstract void writeOperationOutput(@NonNull SchemaInferenceStack stack, @NonNull QueryParameters writerParameters,
- @NonNull ContainerNode output, @NonNull OutputStream entityStream) throws IOException;
-
abstract void writeData(@NonNull SchemaInferenceStack stack, @NonNull QueryParameters writerParameters,
@NonNull NormalizedNode data, @NonNull OutputStream entityStream) throws IOException;
}
import org.opendaylight.restconf.nb.rfc8040.legacy.QueryParameters;
import org.opendaylight.yangtools.yang.common.XMLNamespace;
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.MapEntryNode;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
import org.opendaylight.yangtools.yang.data.codec.gson.JSONCodecFactorySupplier;
public final class JsonNormalizedNodeBodyWriter extends AbstractNormalizedNodeBodyWriter {
private static final int DEFAULT_INDENT_SPACES_NUM = 2;
- @Override
- void writeOperationOutput(final SchemaInferenceStack stack, final QueryParameters writerParameters,
- final ContainerNode output, final OutputStream entityStream) throws IOException {
- // RpcDefinition/ActionDefinition is not supported as initial codec in JSONStreamWriter, so we need to emit
- // initial output declaration
- try (var jsonWriter = createJsonWriter(entityStream, writerParameters.prettyPrint())) {
- final var module = stack.currentModule();
- jsonWriter.beginObject().name(module.argument().getLocalName() + ":output").beginObject();
-
- final var nnWriter = createNormalizedNodeWriter(stack.toInference(), jsonWriter, writerParameters,
- module.namespace().argument());
- writeChildren(nnWriter, output);
- nnWriter.flush();
-
- jsonWriter.endObject().endObject();
- }
- }
-
@Override
void writeData(final SchemaInferenceStack stack, final QueryParameters writerParameters, final NormalizedNode data,
final OutputStream entityStream) throws IOException {
}
}
- private static void writeChildren(final RestconfNormalizedNodeWriter nnWriter, final ContainerNode data)
- throws IOException {
- for (var child : data.body()) {
- nnWriter.write(child);
- }
- }
-
private static RestconfNormalizedNodeWriter createNormalizedNodeWriter(final Inference inference,
final JsonWriter jsonWriter, final QueryParameters writerParameters,
final @Nullable XMLNamespace initialNamespace) {
import java.util.Map.Entry;
import java.util.Set;
import javax.xml.transform.dom.DOMSource;
+import org.eclipse.jdt.annotation.NonNull;
import org.opendaylight.restconf.api.query.DepthParam;
import org.opendaylight.restconf.nb.rfc8040.jersey.providers.api.RestconfNormalizedNodeWriter;
import org.opendaylight.yangtools.yang.common.Ordering;
* @param fields Selected child nodes to write
* @return A new instance.
*/
- public static ParameterAwareNormalizedNodeWriter forStreamWriter(
+ public static @NonNull ParameterAwareNormalizedNodeWriter forStreamWriter(
final NormalizedNodeStreamWriter writer, final DepthParam maxDepth, final List<Set<QName>> fields) {
return forStreamWriter(writer, true, maxDepth, fields);
}
* @param fields Selected child nodes to write
* @return A new instance.
*/
- public static ParameterAwareNormalizedNodeWriter forStreamWriter(final NormalizedNodeStreamWriter writer,
- final boolean orderKeyLeaves,
- final DepthParam depth,
- final List<Set<QName>> fields) {
+ public static @NonNull ParameterAwareNormalizedNodeWriter forStreamWriter(final NormalizedNodeStreamWriter writer,
+ final boolean orderKeyLeaves, final DepthParam depth, final List<Set<QName>> fields) {
return orderKeyLeaves ? new OrderedParameterAwareNormalizedNodeWriter(writer, depth, fields)
: new ParameterAwareNormalizedNodeWriter(writer, depth, fields);
}
XML_FACTORY.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, true);
}
- @Override
- void writeOperationOutput(final SchemaInferenceStack stack, final QueryParameters writerParameters,
- final ContainerNode output, final OutputStream entityStream) throws IOException {
- // RpcDefinition/ActionDefinition is not supported as initial codec in XMLStreamWriter, so we need to emit
- // initial output declaration.
- final var xmlWriter = createXmlWriter(entityStream, writerParameters.prettyPrint());
- final var nnWriter = createNormalizedNodeWriter(xmlWriter, stack.toInference(), writerParameters);
- writeElements(xmlWriter, nnWriter, output);
- nnWriter.flush();
- }
-
@Override
void writeData(final SchemaInferenceStack stack, final QueryParameters writerParameters, final NormalizedNode data,
final OutputStream entityStream) throws IOException {
throw new IOException("Failed to write elements", e);
}
}
-
- private static void writeElements(final XMLStreamWriter xmlWriter, final RestconfNormalizedNodeWriter nnWriter,
- final ContainerNode data) throws IOException {
- final QName nodeType = data.name().getNodeType();
- final String namespace = nodeType.getNamespace().toString();
- try {
- xmlWriter.writeStartElement(XMLConstants.DEFAULT_NS_PREFIX, nodeType.getLocalName(), namespace);
- xmlWriter.writeDefaultNamespace(namespace);
- for (var child : data.body()) {
- nnWriter.write(child);
- }
- nnWriter.flush();
- xmlWriter.writeEndElement();
- xmlWriter.flush();
- } catch (final XMLStreamException e) {
- throw new IOException("Failed to write elements", e);
- }
- }
}
import org.opendaylight.restconf.server.api.DatabindPath.InstanceReference;
import org.opendaylight.restconf.server.api.DatabindPath.Rpc;
import org.opendaylight.restconf.server.api.OperationInputBody;
+import org.opendaylight.restconf.server.api.OperationOutputBody;
import org.opendaylight.restconf.server.api.OperationsGetResult;
import org.opendaylight.restconf.server.api.OperationsPostResult;
import org.opendaylight.restconf.server.api.PatchBody;
return ret;
}
- private @NonNull RestconfFuture<InvokeOperation> dataInvokePOST(final Action path, final OperationInputBody body) {
+ private @NonNull RestconfFuture<InvokeOperation> dataInvokePOST(final @NonNull Action path,
+ final @NonNull OperationInputBody body) {
final ContainerNode input;
try {
input = body.toContainerNode(path);
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(path.inference(), output))))
+ : Optional.of(new InvokeOperation(new OperationOutputBody(path, output, false))))
.orElse(InvokeOperation.EMPTY));
}
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
-public abstract sealed class ChildBody extends AbstractBody permits JsonChildBody, XmlChildBody {
+public abstract sealed class ChildBody extends RequestBody permits JsonChildBody, XmlChildBody {
public record PrefixAndBody(@NonNull ImmutableList<PathArgument> prefix, @NonNull NormalizedNode body) {
public PrefixAndBody {
requireNonNull(prefix);
* <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.4">RFC8040 section 4.4</a>.
*/
@NonNullByDefault
-public abstract sealed class DataPostBody extends AbstractBody permits JsonDataPostBody, XmlDataPostBody {
+public abstract sealed class DataPostBody extends RequestBody permits JsonDataPostBody, XmlDataPostBody {
DataPostBody(final InputStream inputStream) {
super(inputStream);
}
import java.time.Instant;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
-import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
/**
* Result of a {@code POST} request as defined in
*
* @param output Non-empty operation output, or {@code null}
*/
- record InvokeOperation(@Nullable NormalizedNodePayload output) implements DataPostResult {
- public static final InvokeOperation EMPTY = new InvokeOperation(null);
+ record InvokeOperation(@Nullable OperationOutputBody output) implements DataPostResult {
+ public static final @NonNull InvokeOperation EMPTY = new InvokeOperation(null);
}
}
/**
* Access to an {code rpc}'s or an {@code action}'s input.
*/
-public abstract sealed class OperationInputBody extends AbstractBody
+public abstract sealed class OperationInputBody extends RequestBody
permits JsonOperationInputBody, XmlOperationInputBody {
OperationInputBody(final InputStream inputStream) {
super(inputStream);
--- /dev/null
+/*
+ * Copyright (c) 2024 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 com.google.common.annotations.VisibleForTesting;
+import java.io.IOException;
+import java.io.OutputStream;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.opendaylight.restconf.nb.rfc8040.jersey.providers.ParameterAwareNormalizedNodeWriter;
+import org.opendaylight.restconf.server.api.DatabindPath.OperationPath;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.codec.gson.JSONNormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.codec.xml.XMLStreamNormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
+
+/**
+ * A {@link ReplyBody} corresponding to a {@code rpc} or {@code action} invocation.
+ */
+@NonNullByDefault
+public final class OperationOutputBody extends ReplyBody {
+ private final OperationPath path;
+ private final ContainerNode output;
+
+ public OperationOutputBody(final OperationPath path, final ContainerNode output, final boolean prettyPrint) {
+ super(prettyPrint);
+ this.path = requireNonNull(path);
+ this.output = requireNonNull(output);
+ if (output.isEmpty()) {
+ throw new IllegalArgumentException("output may not be empty");
+ }
+ }
+
+ @VisibleForTesting
+ public ContainerNode output() {
+ return output;
+ }
+
+ @Override
+ void writeJSON(final OutputStream out, final boolean prettyPrint) throws IOException {
+ final var stack = prepareStack();
+
+ // RpcDefinition/ActionDefinition is not supported as initial codec in JSONStreamWriter, so we need to emit
+ // initial output declaration
+ try (var jsonWriter = createJsonWriter(out, prettyPrint)) {
+ final var module = stack.currentModule();
+ jsonWriter.beginObject().name(module.argument().getLocalName() + ":output").beginObject();
+
+ final var nnWriter = ParameterAwareNormalizedNodeWriter.forStreamWriter(
+ JSONNormalizedNodeStreamWriter.createNestedWriter(path.databind().jsonCodecs(), stack.toInference(),
+ module.namespace().argument(), jsonWriter), null, null);
+ for (var child : output.body()) {
+ nnWriter.write(child);
+ }
+ nnWriter.flush();
+
+ jsonWriter.endObject().endObject();
+ }
+ }
+
+ @Override
+ void writeXML(final OutputStream out, final boolean prettyPrint) throws IOException {
+ final var stack = prepareStack();
+
+ // RpcDefinition/ActionDefinition is not supported as initial codec in XMLStreamWriter, so we need to emit
+ // initial output declaration.
+ final var xmlWriter = createXmlWriter(out, prettyPrint);
+ final var nnWriter = ParameterAwareNormalizedNodeWriter.forStreamWriter(
+ XMLStreamNormalizedNodeStreamWriter.create(xmlWriter, stack.toInference()), null, null);
+
+ writeElements(xmlWriter, nnWriter, output);
+ nnWriter.flush();
+ }
+
+ private SchemaInferenceStack prepareStack() {
+ final var stack = path.inference().toSchemaInferenceStack();
+ stack.enterSchemaTree(path.outputStatement().argument());
+ return stack;
+ }
+}
/**
* A YANG Patch body.
*/
-public abstract sealed class PatchBody extends AbstractBody permits JsonPatchBody, XmlPatchBody {
+public abstract sealed class PatchBody extends RequestBody permits JsonPatchBody, XmlPatchBody {
/**
* Resource context needed to completely resolve a {@link PatchBody}.
*/
--- /dev/null
+/*
+ * Copyright (c) 2024 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 com.google.common.base.MoreObjects;
+import com.google.common.base.MoreObjects.ToStringHelper;
+import com.google.gson.stream.JsonWriter;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.nio.charset.StandardCharsets;
+import javanet.staxutils.IndentingXMLStreamWriter;
+import javax.xml.XMLConstants;
+import javax.xml.stream.FactoryConfigurationError;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.opendaylight.restconf.nb.rfc8040.jersey.providers.api.RestconfNormalizedNodeWriter;
+import org.opendaylight.yangtools.concepts.Immutable;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.codec.gson.JsonWriterFactory;
+
+/**
+ * A response counterpart to {@link RequestBody}. It is inherently immutable and exposes methods to write the content
+ * to an {@link OutputStream}.
+ */
+@NonNullByDefault
+public abstract class ReplyBody implements Immutable {
+ private static final XMLOutputFactory XML_FACTORY = XMLOutputFactory.newFactory();
+ private static final String PRETTY_PRINT_INDENT = " ";
+
+ private final boolean prettyPrint;
+
+ ReplyBody(final boolean prettyPrint) {
+ this.prettyPrint = prettyPrint;
+ }
+
+ /**
+ * Write the content of this body as a JSON document.
+ *
+ * @param out output stream
+ * @throws IOException if an IO error occurs.
+ */
+ public final void writeJSON(final OutputStream out) throws IOException {
+ writeJSON(requireNonNull(out), prettyPrint);
+ }
+
+ abstract void writeJSON(OutputStream out, boolean prettyPrint) throws IOException;
+
+ /**
+ * Write the content of this body as an XML document.
+ *
+ * @param out output stream
+ * @throws IOException if an IO error occurs.
+ */
+ public final void writeXML(final OutputStream out) throws IOException {
+ writeXML(requireNonNull(out), prettyPrint);
+ }
+
+ abstract void writeXML(OutputStream out, boolean prettyPrint) throws IOException;
+
+ @Override
+ public final String toString() {
+ return addToStringAttributes(MoreObjects.toStringHelper(this)).toString();
+ }
+
+ ToStringHelper addToStringAttributes(final ToStringHelper helper) {
+ return helper.add("prettyPrint", prettyPrint);
+ }
+
+ static final JsonWriter createJsonWriter(final OutputStream out, final boolean prettyPrint) {
+ final var ret = JsonWriterFactory.createJsonWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8));
+ ret.setIndent(prettyPrint ? PRETTY_PRINT_INDENT : "");
+ return ret;
+ }
+
+ static final XMLStreamWriter createXmlWriter(final OutputStream out, final boolean prettyPrint) throws IOException {
+ final var xmlWriter = createXmlWriter(out);
+ return prettyPrint ? new IndentingXMLStreamWriter(xmlWriter) : xmlWriter;
+ }
+
+ private static XMLStreamWriter createXmlWriter(final OutputStream out) throws IOException {
+ try {
+ return XML_FACTORY.createXMLStreamWriter(out, StandardCharsets.UTF_8.name());
+ } catch (XMLStreamException | FactoryConfigurationError e) {
+ throw new IOException(e);
+ }
+ }
+
+ static final void writeElements(final XMLStreamWriter xmlWriter, final RestconfNormalizedNodeWriter nnWriter,
+ final ContainerNode data) throws IOException {
+ final QName nodeType = data.name().getNodeType();
+ final String namespace = nodeType.getNamespace().toString();
+ try {
+ xmlWriter.writeStartElement(XMLConstants.DEFAULT_NS_PREFIX, nodeType.getLocalName(), namespace);
+ xmlWriter.writeDefaultNamespace(namespace);
+ for (var child : data.body()) {
+ nnWriter.write(child);
+ }
+ nnWriter.flush();
+ xmlWriter.writeEndElement();
+ xmlWriter.flush();
+ } catch (final XMLStreamException e) {
+ throw new IOException("Failed to write elements", e);
+ }
+ }
+}
import org.eclipse.jdt.annotation.Nullable;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
import org.opendaylight.restconf.common.errors.RestconfError;
+import org.opendaylight.yangtools.concepts.Mutable;
import org.opendaylight.yangtools.yang.data.api.YangNetconfError;
import org.opendaylight.yangtools.yang.data.api.YangNetconfErrorAware;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
- * An abstract request body backed by an {@link InputStream}.
+ * An abstract request body backed by an {@link InputStream}. In controls the access to input stream, so that it can
+ * only be taken once.
*/
-public abstract sealed class AbstractBody implements AutoCloseable
+public abstract sealed class RequestBody implements AutoCloseable, Mutable
permits ChildBody, DataPostBody, OperationInputBody, PatchBody, ResourceBody {
- private static final Logger LOG = LoggerFactory.getLogger(AbstractBody.class);
+ private static final Logger LOG = LoggerFactory.getLogger(RequestBody.class);
private static final VarHandle INPUT_STREAM;
static {
try {
- INPUT_STREAM = MethodHandles.lookup().findVarHandle(AbstractBody.class, "inputStream", InputStream.class);
+ INPUT_STREAM = MethodHandles.lookup().findVarHandle(RequestBody.class, "inputStream", InputStream.class);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new ExceptionInInitializerError(e);
}
@SuppressFBWarnings(value = "URF_UNREAD_FIELD", justification = "https://github.com/spotbugs/spotbugs/issues/2749")
private volatile InputStream inputStream;
- AbstractBody(final InputStream inputStream) {
+ RequestBody(final InputStream inputStream) {
this.inputStream = requireNonNull(inputStream);
}
return is;
}
-
/**
* Throw a {@link RestconfDocumentedException} if the specified exception has a {@link YangNetconfError} attachment.
*
* The body of a resource identified in the request URL, i.e. a {@code PUT} or a plain {@code PATCH} request on RESTCONF
* data service.
*/
-public abstract sealed class ResourceBody extends AbstractBody permits JsonResourceBody, XmlResourceBody {
+public abstract sealed class ResourceBody extends RequestBody permits JsonResourceBody, XmlResourceBody {
private static final Logger LOG = LoggerFactory.getLogger(ResourceBody.class);
private static final NodeIdentifier DATA_NID = NodeIdentifier.create(Data.QNAME);
import org.opendaylight.restconf.nb.rfc8040.jersey.providers.JsonNormalizedNodeBodyWriter;
import org.opendaylight.restconf.nb.rfc8040.jersey.providers.XmlNormalizedNodeBodyWriter;
import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
+import org.opendaylight.restconf.server.api.OperationOutputBody;
import org.opendaylight.restconf.server.mdsal.MdsalDatabindProvider;
import org.opendaylight.restconf.server.mdsal.MdsalRestconfServer;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
MediaTypes.APPLICATION_YANG_DATA_JSON);
}
+ static final void assertJson(final String expectedJson, final OperationOutputBody payload) {
+ final var baos = new ByteArrayOutputStream();
+ try {
+ payload.writeJSON(baos);
+ } catch (IOException e) {
+ throw new AssertionError(e);
+ }
+ assertEquals(expectedJson, baos.toString(StandardCharsets.UTF_8));
+ }
+
static final void assertXml(final String expectedXml, final NormalizedNodePayload payload) {
assertPayload(expectedXml, payload, new XmlNormalizedNodeBodyWriter(), MediaTypes.APPLICATION_YANG_DATA_XML);
}
+ static final void assertXml(final String expectedXml, final OperationOutputBody payload) {
+ final var baos = new ByteArrayOutputStream();
+ try {
+ payload.writeXML(baos);
+ } catch (IOException e) {
+ throw new AssertionError(e);
+ }
+ assertEquals(expectedXml, baos.toString(StandardCharsets.UTF_8));
+ }
+
private static void assertPayload(final String expected, final NormalizedNodePayload payload,
final MessageBodyWriter<NormalizedNodePayload> writer, final String mediaType) {
final var baos = new ByteArrayOutputStream();
assertEquals(expected, baos.toString(StandardCharsets.UTF_8));
}
+ static final ContainerNode assertOperationOutput(final int status, final Consumer<AsyncResponse> invocation) {
+ return assertOperationOutputBody(status, invocation).output();
+ }
+
+ static final OperationOutputBody assertOperationOutputBody(final int status,
+ final Consumer<AsyncResponse> invocation) {
+ return assertEntity(OperationOutputBody.class, status, invocation);
+ }
+
static final NormalizedNode assertNormalizedNode(final int status, final Consumer<AsyncResponse> invocation) {
return assertNormalizedNodePayload(status, invocation).data();
}
import org.opendaylight.mdsal.dom.spi.SimpleDOMActionResult;
import org.opendaylight.restconf.api.ApiPath;
import org.opendaylight.restconf.nb.rfc8040.AbstractInstanceIdentifierTest;
-import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
+import org.opendaylight.restconf.server.api.OperationOutputBody;
import org.opendaylight.restconf.server.mdsal.MdsalDatabindProvider;
import org.opendaylight.restconf.server.mdsal.MdsalRestconfServer;
import org.opendaylight.yangtools.yang.common.QName;
final var response = captor.getValue();
assertEquals(200, response.getStatus());
- final var payload = assertInstanceOf(NormalizedNodePayload.class, response.getEntity());
+ final var payload = assertInstanceOf(OperationOutputBody.class, response.getEntity());
AbstractRestconfTest.assertJson("""
{"instance-identifier-module:output":{"timestamp":"somevalue"}}""", payload);
AbstractRestconfTest.assertXml("""
doReturn(false).when(result).isEmpty();
prepNNC(result);
- assertSame(result, assertNormalizedNode(200, ar -> restconf.operationsXmlPOST(
+ assertSame(result, assertOperationOutput(200, ar -> restconf.operationsXmlPOST(
apiPath("invoke-rpc-module:rpc-test"), stringInputStream("""
<input xmlns="invoke:rpc:module"/>"""), uriInfo, ar)));
}
doReturn(Futures.immediateFuture(new DefaultDOMRpcResult(OUTPUT, List.of()))).when(rpcService)
.invokeRpc(RPC, INPUT);
- assertEquals(OUTPUT, assertNormalizedNode(200, ar -> restconf.operationsXmlPOST(
+ assertEquals(OUTPUT, assertOperationOutput(200, ar -> restconf.operationsXmlPOST(
apiPath("invoke-rpc-module:rpc-test"), stringInputStream("""
<input xmlns="invoke:rpc:module">
<cont>
doReturn(Futures.immediateFuture(new DefaultDOMRpcResult(OUTPUT, List.of()))).when(rpcService)
.invokeRpc(RPC, INPUT);
- assertEquals(OUTPUT, assertNormalizedNode(200,
+ assertEquals(OUTPUT, assertOperationOutput(200,
ar -> restconf.operationsJsonPOST(
apiPath("ietf-yang-library:modules-state/yang-ext:mount/invoke-rpc-module:rpc-test"),
stringInputStream("""
doReturn(Futures.immediateFuture(new DefaultDOMRpcResult(OUTPUT, List.of())))
.when(rpcService).invokeRpc(RPC, INPUT);
- final var payload = assertNormalizedNodePayload(200, ar -> restconf.operationsJsonPOST(
+ final var body = assertOperationOutputBody(200, ar -> restconf.operationsJsonPOST(
apiPath("invoke-rpc-module:rpc-test"),
stringInputStream("""
{
}
}
}"""), uriInfo, ar));
- assertEquals(OUTPUT, payload.data());
+ assertEquals(OUTPUT, body.output());
assertJson("""
- {"invoke-rpc-module:output":{"cont-out":{"lf-out":"operation result"}}}""", payload);
+ {"invoke-rpc-module:output":{"cont-out":{"lf-out":"operation result"}}}""", body);
assertXml("""
- <output xmlns="invoke:rpc:module"><cont-out><lf-out>operation result</lf-out></cont-out></output>""",
- payload);
+ <output xmlns="invoke:rpc:module"><cont-out><lf-out>operation result</lf-out></cont-out></output>""", body);
}
private void prepNNC(final ContainerNode result) {