import javax.ws.rs.ext.ParamConverter;
import javax.ws.rs.ext.ParamConverterProvider;
import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.opendaylight.restconf.api.ApiPath;
+import org.opendaylight.restconf.api.FormattableBody;
import org.opendaylight.restconf.api.HttpStatusCode;
import org.opendaylight.restconf.api.MediaTypes;
import org.opendaylight.restconf.api.QueryParameters;
import org.opendaylight.restconf.common.errors.RestconfFuture;
import org.opendaylight.restconf.nb.rfc8040.ErrorTagMapping;
import org.opendaylight.restconf.nb.rfc8040.URLConstants;
-import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
import org.opendaylight.restconf.server.api.ConfigurationMetadata;
import org.opendaylight.restconf.server.api.CreateResourceResult;
import org.opendaylight.restconf.server.api.DataGetResult;
MediaType.TEXT_XML
})
public void dataGET(@Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
- completeDataGET(server.dataGET(requestOf(uriInfo)), ar);
+ final var request = requestOf(uriInfo);
+ completeDataGET(server.dataGET(request), request.prettyPrint(), ar);
}
/**
})
public void dataGET(@Encoded @PathParam("identifier") final ApiPath identifier, @Context final UriInfo uriInfo,
@Suspended final AsyncResponse ar) {
- completeDataGET(server.dataGET(requestOf(uriInfo), identifier), ar);
+ final var request = requestOf(uriInfo);
+ completeDataGET(server.dataGET(request, identifier), request.prettyPrint(), ar);
}
- private static void completeDataGET(final RestconfFuture<DataGetResult> future, final AsyncResponse ar) {
+ @NonNullByDefault
+ private static void completeDataGET(final RestconfFuture<DataGetResult> future, final PrettyPrintParam prettyPrint,
+ final AsyncResponse ar) {
future.addCallback(new JaxRsRestconfCallback<>(ar) {
@Override
Response transform(final DataGetResult result) {
- final var builder = Response.ok().entity(result.payload()).cacheControl(NO_CACHE);
+ final var builder = Response.ok()
+ .entity(new JaxRsFormattableBody(result.body(), prettyPrint))
+ .cacheControl(NO_CACHE);
fillConfigurationMetadata(builder, result);
return builder.build();
}
* @param identifier module name and rpc identifier string for the desired operation
* @param body the body of the operation
* @param uriInfo URI info
- * @param ar {@link AsyncResponse} which needs to be completed with a {@link NormalizedNodePayload} output
+ * @param ar {@link AsyncResponse} which needs to be completed with a {@link FormattableBody} output
*/
@POST
// FIXME: identifier is just a *single* QName
* @param identifier module name and rpc identifier string for the desired operation
* @param body the body of the operation
* @param uriInfo URI info
- * @param ar {@link AsyncResponse} which needs to be completed with a {@link NormalizedNodePayload} output
+ * @param ar {@link AsyncResponse} which needs to be completed with a {@link FormattableBody} output
*/
@POST
// FIXME: identifier is just a *single* QName
import org.opendaylight.restconf.nb.jaxrs.JaxRsWebHostMetadata;
import org.opendaylight.restconf.nb.jaxrs.JsonJaxRsFormattableBodyWriter;
import org.opendaylight.restconf.nb.jaxrs.XmlJaxRsFormattableBodyWriter;
-import org.opendaylight.restconf.nb.rfc8040.jersey.providers.JsonNormalizedNodeBodyWriter;
-import org.opendaylight.restconf.nb.rfc8040.jersey.providers.XmlNormalizedNodeBodyWriter;
import org.opendaylight.restconf.nb.rfc8040.jersey.providers.errors.RestconfDocumentedExceptionMapper;
import org.opendaylight.restconf.nb.rfc8040.streams.RestconfStreamServletFactory;
import org.opendaylight.restconf.server.api.RestconfServer;
.addUrlPattern("/*")
.servlet(servletSupport.createHttpServletBuilder(
new Application() {
- @Override
- public Set<Class<?>> getClasses() {
- return Set.of(JsonNormalizedNodeBodyWriter.class, XmlNormalizedNodeBodyWriter.class);
- }
-
@Override
public Set<Object> getSingletons() {
final var errorTagMapping = servletFactory.errorTagMapping();
+++ /dev/null
-/*
- * Copyright (c) 2021 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.rfc8040.jersey.providers;
-
-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.NonNullByDefault;
-import org.opendaylight.restconf.api.query.PrettyPrintParam;
-import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
-import org.opendaylight.restconf.nb.rfc8040.legacy.WriterParameters;
-import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
-import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
-
-abstract class AbstractNormalizedNodeBodyWriter implements MessageBodyWriter<NormalizedNodePayload> {
- @Override
- public final boolean isWriteable(final Class<?> type, final Type genericType, final Annotation[] annotations,
- final MediaType mediaType) {
- return type.equals(NormalizedNodePayload.class);
- }
-
- @Override
- 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 {
- writeData(context.inference().toSchemaInferenceStack(), context.data(), context.writerParameters(),
- context.prettyPrint(), requireNonNull(entityStream));
- }
-
- @NonNullByDefault
- abstract void writeData(SchemaInferenceStack stack, NormalizedNode data, WriterParameters writerParameters,
- PrettyPrintParam prettyPrint, OutputStream out) throws IOException;
-}
+++ /dev/null
-/*
- * 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.jersey.providers;
-
-import com.google.gson.stream.JsonWriter;
-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.eclipse.jdt.annotation.Nullable;
-import org.opendaylight.restconf.api.MediaTypes;
-import org.opendaylight.restconf.api.query.PrettyPrintParam;
-import org.opendaylight.restconf.nb.rfc8040.legacy.WriterParameters;
-import org.opendaylight.restconf.server.spi.FormattableBodySupport;
-import org.opendaylight.yangtools.yang.common.XMLNamespace;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
-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;
-import org.opendaylight.yangtools.yang.data.codec.gson.JSONNormalizedNodeStreamWriter;
-import org.opendaylight.yangtools.yang.data.spi.node.ImmutableNodes;
-import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
-import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
-
-@Provider
-@Produces({ MediaTypes.APPLICATION_YANG_DATA_JSON, MediaType.APPLICATION_JSON })
-public final class JsonNormalizedNodeBodyWriter extends AbstractNormalizedNodeBodyWriter {
- @Override
- void writeData(final SchemaInferenceStack stack, final NormalizedNode data, final WriterParameters writerParameters,
- final PrettyPrintParam prettyPrint, final OutputStream out) throws IOException {
- if (!stack.isEmpty()) {
- stack.exit();
- }
-
- // RESTCONF allows returning one list item. We need to wrap it in map node in order to serialize it properly
- final var toSerialize = data instanceof MapEntryNode mapEntry
- ? ImmutableNodes.newSystemMapBuilder()
- .withNodeIdentifier(new NodeIdentifier(data.name().getNodeType()))
- .withChild(mapEntry)
- .build()
- : data;
-
- try (var jsonWriter = FormattableBodySupport.createJsonWriter(out, prettyPrint)) {
- jsonWriter.beginObject();
-
- final var nnWriter = createNormalizedNodeWriter(stack.toInference(), jsonWriter, writerParameters, null);
- nnWriter.write(toSerialize);
- nnWriter.flush();
-
- jsonWriter.endObject().flush();
- }
- }
-
- private static RestconfNormalizedNodeWriter createNormalizedNodeWriter(final Inference inference,
- final JsonWriter jsonWriter, final WriterParameters writerParameters,
- final @Nullable XMLNamespace initialNamespace) {
- // TODO: Performance: Cache JSON Codec factory and schema context
- final var codecs = JSONCodecFactorySupplier.RFC7951.getShared(inference.modelContext());
- return RestconfNormalizedNodeWriter.forStreamWriter(
- JSONNormalizedNodeStreamWriter.createNestedWriter(codecs, inference,
- initialNamespace, jsonWriter), writerParameters.depth(), writerParameters.fields());
- }
-}
+++ /dev/null
-/*
- * 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.jersey.providers;
-
-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 javax.xml.XMLConstants;
-import javax.xml.stream.XMLStreamException;
-import javax.xml.stream.XMLStreamWriter;
-import org.opendaylight.restconf.api.MediaTypes;
-import org.opendaylight.restconf.api.query.PrettyPrintParam;
-import org.opendaylight.restconf.nb.rfc8040.legacy.WriterParameters;
-import org.opendaylight.restconf.server.spi.FormattableBodySupport;
-import org.opendaylight.yangtools.yang.common.QName;
-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.xml.XMLStreamNormalizedNodeStreamWriter;
-import org.opendaylight.yangtools.yang.data.spi.node.ImmutableNodes;
-import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
-import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
-
-@Provider
-@Produces({ MediaTypes.APPLICATION_YANG_DATA_XML, MediaType.APPLICATION_XML, MediaType.TEXT_XML })
-public final class XmlNormalizedNodeBodyWriter extends AbstractNormalizedNodeBodyWriter {
- @Override
- void writeData(final SchemaInferenceStack stack, final NormalizedNode data, final WriterParameters writerParameters,
- final PrettyPrintParam prettyPrint, final OutputStream out) throws IOException {
- final boolean isRoot;
- if (!stack.isEmpty()) {
- stack.exit();
- isRoot = false;
- } else {
- isRoot = true;
- }
-
- final var xmlWriter = FormattableBodySupport.createXmlWriter(out, prettyPrint);
- final var nnWriter = createNormalizedNodeWriter(xmlWriter, stack.toInference(), writerParameters);
- if (data instanceof MapEntryNode mapEntry) {
- // Restconf allows returning one list item. We need to wrap it
- // in map node in order to serialize it properly
- nnWriter.write(ImmutableNodes.newSystemMapBuilder()
- .withNodeIdentifier(new NodeIdentifier(data.name().getNodeType()))
- .addChild(mapEntry)
- .build());
- } else if (isRoot) {
- if (data instanceof ContainerNode container && container.isEmpty()) {
- writeEmptyDataNode(xmlWriter, container);
- } else {
- writeAndWrapInDataNode(xmlWriter, nnWriter, data);
- }
- } else {
- nnWriter.write(data);
- }
- nnWriter.flush();
- }
-
- private static RestconfNormalizedNodeWriter createNormalizedNodeWriter(final XMLStreamWriter xmlWriter,
- final Inference inference, final WriterParameters writerParameters) {
- return RestconfNormalizedNodeWriter.forStreamWriter(
- XMLStreamNormalizedNodeStreamWriter.create(xmlWriter, inference),
- writerParameters.depth(), writerParameters.fields());
- }
-
- private static void writeAndWrapInDataNode(final XMLStreamWriter xmlWriter,
- final RestconfNormalizedNodeWriter nnWriter, final NormalizedNode 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);
- nnWriter.write(data);
- xmlWriter.writeEndElement();
- xmlWriter.flush();
- } catch (XMLStreamException e) {
- throw new IOException("Failed to write elements", e);
- }
- }
-
- private static void writeEmptyDataNode(final XMLStreamWriter xmlWriter, 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);
- xmlWriter.writeEndElement();
- xmlWriter.flush();
- } catch (XMLStreamException e) {
- throw new IOException("Failed to write elements", e);
- }
- }
-}
+++ /dev/null
-/*
- * Copyright (c) 2021 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.rfc8040.legacy;
-
-import static java.util.Objects.requireNonNull;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.opendaylight.restconf.api.query.PrettyPrintParam;
-import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
-import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
-
-/**
- * A RFC8040 overlay from our marriage to NormalizedNodeContext. This represents a NormalizedNode along with further
- * messy details needed to deal with the payload.
- */
-@NonNullByDefault
-public record NormalizedNodePayload(
- Inference inference,
- NormalizedNode data,
- WriterParameters writerParameters,
- PrettyPrintParam prettyPrint) {
- public NormalizedNodePayload {
- requireNonNull(inference);
- requireNonNull(data);
- requireNonNull(writerParameters);
- requireNonNull(prettyPrint);
- }
-}
+++ /dev/null
-/*
- * Copyright (c) 2021 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.rfc8040.legacy;
-
-import com.google.common.annotations.Beta;
-import java.util.List;
-import java.util.Set;
-import org.eclipse.jdt.annotation.NonNull;
-import org.eclipse.jdt.annotation.Nullable;
-import org.opendaylight.restconf.api.query.DepthParam;
-import org.opendaylight.yangtools.yang.common.QName;
-
-/**
- * This holds various options acquired from a requests's query part. This class needs to be further split up to make
- * sense of it, as parts of it pertain to how a {@link NormalizedNodePayload} should be created while others how it
- * needs to be processed (for example filtered).
- */
-@Beta
-public record WriterParameters(@Nullable DepthParam depth, @Nullable List<Set<QName>> fields) {
- public static final @NonNull WriterParameters EMPTY = new WriterParameters(null, null);
-
- public static @NonNull WriterParameters of(final @Nullable DepthParam depth) {
- return depth == null ? EMPTY : new WriterParameters(depth, null);
- }
-}
--- /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.rfc8040.rests.transactions;
+
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.base.MoreObjects.ToStringHelper;
+import java.util.List;
+import java.util.Set;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.restconf.api.query.DepthParam;
+import org.opendaylight.restconf.nb.rfc8040.jersey.providers.RestconfNormalizedNodeWriter;
+import org.opendaylight.restconf.server.spi.NormalizedNodeWriterFactory;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
+
+/**
+ * A {@link NormalizedNodeWriterFactory} which restricts output to specified fields and potentially the specified depth.
+ */
+@NonNullByDefault
+final class MdsalNormalizedNodeWriterFactory extends NormalizedNodeWriterFactory {
+ private final @Nullable DepthParam depth;
+ private final List<Set<QName>> fields;
+
+ MdsalNormalizedNodeWriterFactory(final List<Set<QName>> fields, final @Nullable DepthParam depth) {
+ this.fields = requireNonNull(fields);
+ this.depth = depth;
+ }
+
+ @Override
+ protected RestconfNormalizedNodeWriter newWriter(final NormalizedNodeStreamWriter streamWriter) {
+ return RestconfNormalizedNodeWriter.forStreamWriter(streamWriter, depth, fields);
+ }
+
+ @Override
+ protected ToStringHelper addToStringAttributes(final ToStringHelper helper) {
+ final var local = depth;
+ if (local != null) {
+ helper.add("depth", local.value());
+ }
+ return helper.add("fields", fields);
+ }
+}
import org.opendaylight.restconf.common.errors.RestconfFuture;
import org.opendaylight.restconf.common.errors.SettableRestconfFuture;
import org.opendaylight.restconf.nb.rfc8040.jersey.providers.RestconfNormalizedNodeWriter;
-import org.opendaylight.restconf.nb.rfc8040.legacy.WriterParameters;
import org.opendaylight.restconf.server.api.DataGetParams;
import org.opendaylight.restconf.server.api.DataGetResult;
import org.opendaylight.restconf.server.api.DatabindContext;
import org.opendaylight.restconf.server.api.DatabindPath.Data;
import org.opendaylight.restconf.server.api.ServerRequest;
import org.opendaylight.restconf.server.spi.HttpGetResource;
+import org.opendaylight.restconf.server.spi.NormalizedNodeWriterFactory;
import org.opendaylight.restconf.server.spi.RpcImplementation;
import org.opendaylight.restconf.server.spi.YangLibraryVersionResource;
import org.opendaylight.yangtools.yang.common.Empty;
@Override
RestconfFuture<DataGetResult> dataGET(final ServerRequest request, final Data path, final DataGetParams params) {
- final var inference = path.inference();
+ final var depth = params.depth();
final var fields = params.fields();
- return completeDataGET(request.prettyPrint(), inference,
- fields == null ? WriterParameters.of(params.depth())
- : new WriterParameters(params.depth(),
- translateFieldsParam(inference.modelContext(), path.schema(), fields)),
- readData(params.content(), path.instance(), params.withDefaults()), null);
+ final var writerFactory = fields == null ? NormalizedNodeWriterFactory.of(depth)
+ : new MdsalNormalizedNodeWriterFactory(
+ translateFieldsParam(path.inference().modelContext(), path.schema(), fields), depth);
+
+ return completeDataGET(readData(params.content(), path.instance(), params.withDefaults()), path, writerFactory,
+ null);
}
@Override
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.WriterParameters;
import org.opendaylight.restconf.server.api.DataGetParams;
import org.opendaylight.restconf.server.api.DataGetResult;
import org.opendaylight.restconf.server.api.DatabindContext;
import org.opendaylight.restconf.server.api.DatabindPath.Data;
import org.opendaylight.restconf.server.api.ServerRequest;
+import org.opendaylight.restconf.server.spi.NormalizedNodeWriterFactory;
import org.opendaylight.yangtools.yang.common.Empty;
import org.opendaylight.yangtools.yang.common.ErrorTag;
import org.opendaylight.yangtools.yang.common.ErrorType;
@Override
RestconfFuture<DataGetResult> dataGET(final ServerRequest request, final Data path, final DataGetParams params) {
- final var inference = path.inference();
final var fields = params.fields();
final List<YangInstanceIdentifier> fieldPaths;
if (fields != null) {
final List<YangInstanceIdentifier> tmp;
try {
- tmp = fieldsParamToPaths(inference.modelContext(), path.schema(), fields);
+ tmp = fieldsParamToPaths(path.inference().modelContext(), path.schema(), fields);
} catch (RestconfDocumentedException e) {
return RestconfFuture.failed(e);
}
} else {
node = readData(params.content(), path.instance(), params.withDefaults());
}
-
- return completeDataGET(request.prettyPrint(), inference, WriterParameters.of(params.depth()), node, null);
+ return completeDataGET(node, path, NormalizedNodeWriterFactory.of(params.depth()), null);
}
@Override
import org.opendaylight.restconf.api.ApiPath;
import org.opendaylight.restconf.api.FormattableBody;
import org.opendaylight.restconf.api.query.ContentParam;
-import org.opendaylight.restconf.api.query.PrettyPrintParam;
import org.opendaylight.restconf.api.query.WithDefaultsParam;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
import org.opendaylight.restconf.common.errors.RestconfError;
import org.opendaylight.restconf.common.patch.PatchContext;
import org.opendaylight.restconf.nb.rfc8040.Insert;
import org.opendaylight.restconf.nb.rfc8040.legacy.ErrorTags;
-import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
-import org.opendaylight.restconf.nb.rfc8040.legacy.WriterParameters;
import org.opendaylight.restconf.server.api.ChildBody;
import org.opendaylight.restconf.server.api.ConfigurationMetadata;
import org.opendaylight.restconf.server.api.CreateResourceResult;
import org.opendaylight.restconf.server.spi.ApiPathNormalizer;
import org.opendaylight.restconf.server.spi.DefaultResourceContext;
import org.opendaylight.restconf.server.spi.HttpGetResource;
+import org.opendaylight.restconf.server.spi.NormalizedFormattableBody;
+import org.opendaylight.restconf.server.spi.NormalizedNodeWriterFactory;
import org.opendaylight.restconf.server.spi.OperationInput;
import org.opendaylight.restconf.server.spi.OperationOutputBody;
import org.opendaylight.restconf.server.spi.OperationsResource;
import org.opendaylight.yangtools.yang.model.api.source.YinTextSource;
import org.opendaylight.yangtools.yang.model.api.stmt.ModuleEffectiveStatement;
import org.opendaylight.yangtools.yang.model.api.stmt.SubmoduleEffectiveStatement;
-import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
abstract @NonNull RestconfFuture<DataGetResult> dataGET(ServerRequest request, Data path, DataGetParams params);
- static final @NonNull RestconfFuture<DataGetResult> completeDataGET(final PrettyPrintParam prettyPrint,
- final Inference inference, final WriterParameters writerParams, final @Nullable NormalizedNode node,
- final @Nullable ConfigurationMetadata metadata) {
+ @NonNullByDefault
+ static final RestconfFuture<DataGetResult> completeDataGET(final @Nullable NormalizedNode node, final Data path,
+ final NormalizedNodeWriterFactory writerFactory, final @Nullable ConfigurationMetadata metadata) {
+ // Non-existing data
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));
}
- final var payload = new NormalizedNodePayload(inference, node, writerParams, prettyPrint);
- return RestconfFuture.of(metadata == null ? new DataGetResult(payload)
- : new DataGetResult(payload, metadata.entityTag(), metadata.lastModified()));
+ final var body = NormalizedFormattableBody.of(path, node, writerFactory);
+ return RestconfFuture.of(metadata == null ? new DataGetResult(body)
+ : new DataGetResult(body, metadata.entityTag(), metadata.lastModified()));
}
/**
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 GET} request as defined in
* <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.3">RFC8040 section 4.3</a>.
*
- * @param payload Resulting payload
+ * @param body Resulting body
* @param entityTag response {@code ETag} header, or {@code null} if not applicable
* @param lastModified response {@code Last-Modified} header, or {@code null} if not applicable
*/
public record DataGetResult(
- @NonNull NormalizedNodePayload payload,
+ @NonNull DatabindFormattableBody body,
@Nullable EntityTag entityTag,
@Nullable Instant lastModified) implements ConfigurationMetadata {
public DataGetResult {
- requireNonNull(payload);
+ requireNonNull(body);
}
- public DataGetResult(final @NonNull NormalizedNodePayload payload) {
- this(payload, null, null);
+ public DataGetResult(final @NonNull DatabindFormattableBody body) {
+ this(body, null, null);
}
}
import org.opendaylight.restconf.api.ApiPath;
import org.opendaylight.restconf.api.FormattableBody;
import org.opendaylight.restconf.common.errors.RestconfFuture;
-import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
import org.opendaylight.yangtools.yang.common.Empty;
/**
* <a href="https://www.rfc-editor.org/rfc/rfc8040#section-3.3.3">RFC8040 {+restconf}/yang-library-version</a>.
*
* @param request {@link ServerRequest} for this request
- * @return A {@link RestconfFuture} completing with {@link NormalizedNodePayload} containing a single
+ * @return A {@link RestconfFuture} completing with {@link FormattableBody} containing a single
* {@code yang-library-version} leaf element.
*/
RestconfFuture<FormattableBody> yangLibraryVersionGET(ServerRequest request);
--- /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.spi;
+
+import static com.google.common.base.Verify.verify;
+import static java.util.Objects.requireNonNull;
+
+import com.google.gson.stream.JsonWriter;
+import java.io.IOException;
+import javax.xml.stream.XMLStreamWriter;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.opendaylight.restconf.server.api.DatabindContext;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
+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.JSONCodecFactory;
+import org.opendaylight.yangtools.yang.data.codec.gson.JSONNormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.codec.xml.XMLStreamNormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.codec.xml.XmlCodecFactory;
+import org.opendaylight.yangtools.yang.model.api.stmt.DataTreeEffectiveStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.LeafListEffectiveStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.ListEffectiveStatement;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
+
+/**
+ * A {@link NormalizedFormattableBody} representing a data resource.
+ */
+@NonNullByDefault
+final class DataFormattableBody<N extends NormalizedNode> extends NormalizedFormattableBody<N> {
+ private final Inference parent;
+
+ DataFormattableBody(final DatabindContext databind, final Inference parent, final N data,
+ final NormalizedNodeWriterFactory writerFactory) {
+ super(databind, writerFactory, data);
+ this.parent = requireNonNull(parent);
+
+ // RESTCONF allows returning one list item. We need to wrap it in map node in order to serialize it properly,
+ // which is where the notion of "parent Inference" may be confusing. We mean
+ // 'inference to the parent NormalizedNode', which for *EntryNode ends up the corresponding statement
+ if (data instanceof MapEntryNode) {
+ verifyParent(parent, ListEffectiveStatement.class, data);
+ } else if (data instanceof LeafSetEntryNode) {
+ verifyParent(parent, LeafListEffectiveStatement.class, data);
+ }
+ }
+
+ DataFormattableBody(final DatabindContext databind, final Inference parent, final N data) {
+ this(databind, parent, data, NormalizedNodeWriterFactory.of());
+ }
+
+ private static void verifyParent(final Inference inference,
+ final Class<? extends DataTreeEffectiveStatement<?>> expectedStmt, final NormalizedNode data) {
+ // Let's not bother with niceties of error report -- if we trip here, the caller is doing the wrong
+ final var qname = expectedStmt.cast(inference.toSchemaInferenceStack().currentStatement()).argument();
+ verify(qname.equals(data.name().getNodeType()));
+ }
+
+ @Override
+ protected void formatToJSON(final JSONCodecFactory codecs, final N data, final JsonWriter writer)
+ throws IOException {
+ writeTo(data, JSONNormalizedNodeStreamWriter.createExclusiveWriter(codecs, parent, null, writer));
+ }
+
+ @Override
+ protected void formatToXML(final XmlCodecFactory codecs, final N data, final XMLStreamWriter xmlWriter)
+ throws IOException {
+ writeTo(data, XMLStreamNormalizedNodeStreamWriter.create(xmlWriter, parent));
+ }
+}
--- /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.spi;
+
+import com.google.common.base.MoreObjects.ToStringHelper;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.opendaylight.restconf.nb.rfc8040.jersey.providers.RestconfNormalizedNodeWriter;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
+
+/**
+ * Default {@link NormalizedNodeWriterFactory}.
+ */
+@NonNullByDefault
+final class DefaultNormalizedNodeWriterFactory extends NormalizedNodeWriterFactory {
+ static final DefaultNormalizedNodeWriterFactory INSTANCE = new DefaultNormalizedNodeWriterFactory();
+
+ private DefaultNormalizedNodeWriterFactory() {
+ // Hidden on purpose
+ }
+
+ @Override
+ protected RestconfNormalizedNodeWriter newWriter(final NormalizedNodeStreamWriter streamWriter) {
+ return RestconfNormalizedNodeWriter.forStreamWriter(streamWriter, null);
+ }
+
+ @Override
+ protected ToStringHelper addToStringAttributes(final ToStringHelper helper) {
+ return helper;
+ }
+}
\ No newline at end of file
--- /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.spi;
+
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.base.MoreObjects.ToStringHelper;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.opendaylight.restconf.api.query.DepthParam;
+import org.opendaylight.restconf.nb.rfc8040.jersey.providers.RestconfNormalizedNodeWriter;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
+
+/**
+ * A {@link NormalizedNodeWriterFactory} returning a {@link RestconfNormalizedNodeWriter} which emits the data only to
+ * a certain
+ * depth.
+ */
+@NonNullByDefault
+final class MaxDepthNormalizedNodeWriterFactory extends NormalizedNodeWriterFactory {
+ private final DepthParam depth;
+
+ MaxDepthNormalizedNodeWriterFactory(final DepthParam depth) {
+ this.depth = requireNonNull(depth);
+ }
+
+ @Override
+ protected RestconfNormalizedNodeWriter newWriter(final NormalizedNodeStreamWriter streamWriter) {
+ return RestconfNormalizedNodeWriter.forStreamWriter(streamWriter, depth);
+ }
+
+ @Override
+ protected ToStringHelper addToStringAttributes(final ToStringHelper helper) {
+ return helper.add("depth", depth.value());
+ }
+}
\ No newline at end of file
*/
package org.opendaylight.restconf.server.spi;
-import static com.google.common.base.Verify.verify;
import static java.util.Objects.requireNonNull;
import com.google.common.base.MoreObjects.ToStringHelper;
+import com.google.common.base.VerifyException;
+import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.io.OutputStream;
import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.opendaylight.restconf.api.FormattableBody;
import org.opendaylight.restconf.api.query.PrettyPrintParam;
+import org.opendaylight.restconf.nb.rfc8040.jersey.providers.RestconfNormalizedNodeWriter;
import org.opendaylight.restconf.server.api.DatabindContext;
import org.opendaylight.restconf.server.api.DatabindFormattableBody;
+import org.opendaylight.restconf.server.api.DatabindPath.Data;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListEntryNode;
import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
-import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeWriter;
-import org.opendaylight.yangtools.yang.data.codec.gson.JSONNormalizedNodeStreamWriter;
-import org.opendaylight.yangtools.yang.data.codec.xml.XMLStreamNormalizedNodeStreamWriter;
-import org.opendaylight.yangtools.yang.model.api.stmt.DataTreeEffectiveStatement;
-import org.opendaylight.yangtools.yang.model.api.stmt.LeafListEffectiveStatement;
-import org.opendaylight.yangtools.yang.model.api.stmt.ListEffectiveStatement;
+import org.opendaylight.yangtools.yang.data.codec.gson.JSONCodecFactory;
+import org.opendaylight.yangtools.yang.data.codec.xml.XmlCodecFactory;
import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
/**
* A {@link FormattableBody} representing a data resource.
*/
@NonNullByDefault
-public final class NormalizedFormattableBody<N extends NormalizedNode> extends DatabindFormattableBody {
- private final Inference parent;
+public abstract sealed class NormalizedFormattableBody<N extends NormalizedNode> extends DatabindFormattableBody
+ permits DataFormattableBody, RootFormattableBody {
+ private final NormalizedNodeWriterFactory writerFactory;
private final N data;
- public NormalizedFormattableBody(final DatabindContext databind, final Inference parent, final N data) {
+ NormalizedFormattableBody(final DatabindContext databind, final NormalizedNodeWriterFactory writerFactory,
+ final N data) {
super(databind);
- this.parent = requireNonNull(parent);
+ this.writerFactory = requireNonNull(writerFactory);
this.data = requireNonNull(data);
- // RESTCONF allows returning one list item. We need to wrap it in map node in order to serialize it properly,
- // which is where the notion of "parent Inference" may be confusing. We mean
- // 'inference to the parent NormalizedNode', which for *EntryNode ends up the corresponding statement
- if (data instanceof MapEntryNode) {
- verifyParent(parent, ListEffectiveStatement.class, data);
- } else if (data instanceof LeafSetEntryNode) {
- verifyParent(parent, LeafListEffectiveStatement.class, data);
+ }
+
+ public static NormalizedFormattableBody<?> of(final Data path, final NormalizedNode data,
+ final NormalizedNodeWriterFactory writerFactory) {
+ final var inference = path.inference();
+ if (inference.isEmpty()) {
+ // Read of the entire /data resource
+ if (data instanceof ContainerNode container) {
+ return new RootFormattableBody(path.databind(), writerFactory, container);
+ }
+ throw new VerifyException("Unexpected root data contract " + data.contract());
+ }
+
+ // Read of a sub-resource. We need to adjust the inference to point to the NormalizedNode parent of the node
+ // being output.
+ final Inference parentInference;
+ if (data instanceof MapEntryNode || data instanceof LeafSetEntryNode || data instanceof UnkeyedListEntryNode) {
+ parentInference = inference;
+ } else {
+ final var stack = inference.toSchemaInferenceStack();
+ stack.exitToDataTree();
+ parentInference = stack.toInference();
}
+
+ return new DataFormattableBody<>(path.databind(), parentInference, data, writerFactory);
}
- private static void verifyParent(final Inference inference,
- final Class<? extends DataTreeEffectiveStatement<?>> expectedStmt, final NormalizedNode data) {
- // Let's not bother with niceties of error report -- if we trip here, the caller is doing the wrong
- final var qname = expectedStmt.cast(inference.toSchemaInferenceStack().currentStatement()).argument();
- verify(qname.equals(data.name().getNodeType()));
+ /**
+ * Return data.
+ *
+ * @return data
+ */
+ public final N data() {
+ return data;
}
@Override
- protected void formatToJSON(final DatabindContext databind, final PrettyPrintParam prettyPrint,
+ protected final void formatToJSON(final DatabindContext databind, final PrettyPrintParam prettyPrint,
final OutputStream out) throws IOException {
- writeTo(JSONNormalizedNodeStreamWriter.createExclusiveWriter(databind.jsonCodecs(), parent, null,
- FormattableBodySupport.createJsonWriter(out, prettyPrint)));
+ try (var writer = FormattableBodySupport.createJsonWriter(out, prettyPrint)) {
+ formatToJSON(databind.jsonCodecs(), data, writer);
+ }
}
+ protected abstract void formatToJSON(JSONCodecFactory codecs, N data, JsonWriter writer) throws IOException;
+
@Override
- protected void formatToXML(final DatabindContext databind, final PrettyPrintParam prettyPrint,
+ protected final void formatToXML(final DatabindContext databind, final PrettyPrintParam prettyPrint,
final OutputStream out) throws IOException {
- final var xmlWriter = FormattableBodySupport.createXmlWriter(out, prettyPrint);
- writeTo(XMLStreamNormalizedNodeStreamWriter.create(xmlWriter, parent));
+ final var writer = FormattableBodySupport.createXmlWriter(out, prettyPrint);
try {
- xmlWriter.close();
+ formatToXML(databind.xmlCodecs(), data, writer);
+ writer.close();
} catch (XMLStreamException e) {
throw new IOException("Failed to write data", e);
}
}
+ protected abstract void formatToXML(XmlCodecFactory codecs, N data, XMLStreamWriter writer)
+ throws IOException, XMLStreamException;
+
+ protected final RestconfNormalizedNodeWriter newWriter(final NormalizedNodeStreamWriter streamWriter) {
+ return writerFactory.newWriter(streamWriter);
+ }
+
+ final void writeTo(final NormalizedNode toWrite, final NormalizedNodeStreamWriter streamWriter)
+ throws IOException {
+ try (var writer = newWriter(streamWriter)) {
+ writer.write(toWrite);
+ }
+ }
+
@Override
protected ToStringHelper addToStringAttributes(final ToStringHelper helper) {
return helper.add("body", data.prettyTree());
}
- private void writeTo(final NormalizedNodeStreamWriter streamWriter) throws IOException {
- try (var writer = NormalizedNodeWriter.forStreamWriter(streamWriter)) {
- writer.write(data);
- }
- }
+
}
--- /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.spi;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.MoreObjects.ToStringHelper;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.restconf.api.query.DepthParam;
+import org.opendaylight.restconf.nb.rfc8040.jersey.providers.RestconfNormalizedNodeWriter;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
+
+/**
+ * Interface for instantiating a {@link RestconfNormalizedNodeWriter} to handle the data writeout to a
+ * {@link NormalizedNodeStreamWriter}.
+ */
+@NonNullByDefault
+public abstract class NormalizedNodeWriterFactory {
+ /**
+ * Return the default {@link NormalizedNodeWriterFactory}.
+ *
+ * @return the default {@link NormalizedNodeWriterFactory}
+ */
+ public static final NormalizedNodeWriterFactory of() {
+ return DefaultNormalizedNodeWriterFactory.INSTANCE;
+ }
+
+ public static final NormalizedNodeWriterFactory of(final @Nullable DepthParam depth) {
+ return depth == null ? of() : new MaxDepthNormalizedNodeWriterFactory(depth);
+ }
+
+ /**
+ * Create a new {@link RestconfNormalizedNodeWriter} for specified {@link NormalizedNodeStreamWriter}.
+ *
+ * @param streamWriter target {@link NormalizedNodeStreamWriter}
+ * @return A {@link RestconfNormalizedNodeWriter}
+ */
+ protected abstract RestconfNormalizedNodeWriter newWriter(NormalizedNodeStreamWriter streamWriter);
+
+ protected abstract ToStringHelper addToStringAttributes(ToStringHelper helper);
+
+ @Override
+ public final String toString() {
+ return addToStringAttributes(MoreObjects.toStringHelper(this).omitNullValues()).toString();
+ }
+}
\ No newline at end of file
--- /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.spi;
+
+import com.google.gson.stream.JsonWriter;
+import java.io.IOException;
+import javax.xml.XMLConstants;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.opendaylight.restconf.server.api.DatabindContext;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.codec.gson.JSONCodecFactory;
+import org.opendaylight.yangtools.yang.data.codec.gson.JSONNormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.codec.xml.XMLStreamNormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.codec.xml.XmlCodecFactory;
+
+/**
+ * A {@link NormalizedFormattableBody} for a data root.
+ */
+@NonNullByDefault
+final class RootFormattableBody extends NormalizedFormattableBody<ContainerNode> {
+ RootFormattableBody(final DatabindContext databind, final NormalizedNodeWriterFactory writerFactory,
+ final ContainerNode data) {
+ super(databind, writerFactory, data);
+ }
+
+ @Override
+ protected void formatToJSON(final JSONCodecFactory codecs, final ContainerNode data, final JsonWriter writer)
+ throws IOException {
+ writer.beginObject();
+
+ final var nnWriter = newWriter(JSONNormalizedNodeStreamWriter.createNestedWriter(codecs, writer, null));
+ // FIXME: we should be remapping namespace here
+ nnWriter.write(data);
+ nnWriter.flush();
+
+ writer.endObject();
+ }
+
+ @Override
+ protected void formatToXML(final XmlCodecFactory codecs, final ContainerNode data, final XMLStreamWriter writer)
+ throws IOException, XMLStreamException {
+ // FIXME: we should be remapping namespace here
+ final QName nodeType = data.name().getNodeType();
+ final String namespace = nodeType.getNamespace().toString();
+
+ writer.writeStartElement(XMLConstants.DEFAULT_NS_PREFIX, nodeType.getLocalName(), namespace);
+ writer.writeDefaultNamespace(namespace);
+
+ if (!data.isEmpty()) {
+ final var nnWriter = newWriter(
+ XMLStreamNormalizedNodeStreamWriter.create(writer, codecs.modelContext()));
+ nnWriter.write(data);
+ nnWriter.flush();
+ }
+
+ writer.writeEndElement();
+ }
+}
@Override
public RestconfFuture<FormattableBody> httpGET(final ServerRequest request) {
- return RestconfFuture.of(new NormalizedFormattableBody<>(databind, restconf, leaf));
+ return RestconfFuture.of(new DataFormattableBody<>(databind, restconf, leaf));
}
@Override
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.opendaylight.restconf.common.errors.RestconfError;
import org.opendaylight.restconf.nb.rfc8040.AbstractJukeboxTest;
import org.opendaylight.restconf.nb.rfc8040.ErrorTagMapping;
-import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
import org.opendaylight.restconf.server.mdsal.MdsalDatabindProvider;
import org.opendaylight.restconf.server.mdsal.MdsalRestconfServer;
+import org.opendaylight.restconf.server.spi.NormalizedFormattableBody;
import org.opendaylight.restconf.server.spi.OperationOutputBody;
import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
assertEquals(expectedXml, baos.toString(StandardCharsets.UTF_8));
}
+ @NonNullByDefault
+ static final <N extends NormalizedNode> NormalizedFormattableBody<N> assertNormalizedBody(final int status,
+ final Consumer<AsyncResponse> invocation) {
+ return assertInstanceOf(NormalizedFormattableBody.class, assertFormattableBody(status, invocation));
+ }
+
static final FormattableBody assertFormattableBody(final int status, final Consumer<AsyncResponse> invocation) {
return assertEntity(JaxRsFormattableBody.class, status, invocation).body();
}
return assertEntity(OperationOutputBody.class, status, invocation);
}
- static final NormalizedNode assertNormalizedNode(final int status, final Consumer<AsyncResponse> invocation) {
- return assertNormalizedNodePayload(status, invocation).data();
- }
-
- static final NormalizedNodePayload assertNormalizedNodePayload(final int status,
- final Consumer<AsyncResponse> invocation) {
- return assertEntity(NormalizedNodePayload.class, status, invocation);
- }
-
static final <T> T assertEntity(final Class<T> expectedType, final int expectedStatus,
final Consumer<AsyncResponse> invocation) {
return assertInstanceOf(expectedType, assertEntity(expectedStatus, invocation));
doReturn(immediateFluentFuture(Optional.empty()))
.when(tx).read(LogicalDatastoreType.OPERATIONAL, JUKEBOX_IID);
- assertEquals(EMPTY_JUKEBOX, assertNormalizedNode(200, ar -> restconf.dataGET(JUKEBOX_API_PATH, uriInfo, ar)));
+ final var body = assertNormalizedBody(200, ar -> restconf.dataGET(JUKEBOX_API_PATH, uriInfo, ar));
+ assertEquals(EMPTY_JUKEBOX, body.data());
+ assertFormat("""
+ {
+ "example-jukebox:jukebox": {
+ "player": {
+ "gap": "0.2"
+ }
+ }
+ }""", body::formatToJSON, true);
+ assertFormat("""
+ <jukebox xmlns="http://example.com/ns/example-jukebox">
+ <player>
+ <gap>0.2</gap>
+ </player>
+ </jukebox>""", body::formatToXML, true);
}
@Test
.when(tx)
.read(LogicalDatastoreType.OPERATIONAL, YangInstanceIdentifier.of());
- final var data = assertInstanceOf(ContainerNode.class,
- assertNormalizedNode(200, ar -> restconf.dataGET(uriInfo, ar)));
+ final var body = assertNormalizedBody(200, ar -> restconf.dataGET(uriInfo, ar));
+ final var data = assertInstanceOf(ContainerNode.class, body.data());
final var rootNodes = data.body();
assertEquals(1, rootNodes.size());
final var allDataChildren = assertInstanceOf(ContainerNode.class, rootNodes.iterator().next()).body();
assertEquals(3, allDataChildren.size());
+
+ assertFormat("""
+ {
+ "example-jukebox:jukebox": {
+ "player": {
+ "gap": "0.2"
+ }
+ }
+ }""", body::formatToJSON, true);
+ assertFormat("""
+ <data xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <jukebox xmlns="http://example.com/ns/example-jukebox">
+ <library/>
+ <player>
+ <gap>0.2</gap>
+ </player>
+ </jukebox>
+ </data>""", body::formatToXML, true);
}
private static ContainerNode wrapNodeByDataRootContainer(final DataContainerChild data) {
doReturn(Optional.empty()).when(mountPoint).getService(NetconfDataTreeService.class);
// response must contain all child nodes from config and operational containers merged in one container
- final var data = assertInstanceOf(ContainerNode.class,
- assertNormalizedNode(200, ar -> restconf.dataGET(
- apiPath("example-jukebox:jukebox/yang-ext:mount/example-jukebox:jukebox"), uriInfo, ar)));
+ final var body = assertNormalizedBody(200, ar -> restconf.dataGET(
+ apiPath("example-jukebox:jukebox/yang-ext:mount/example-jukebox:jukebox"), uriInfo, ar));
+ final var data = assertInstanceOf(ContainerNode.class, body.data());
assertEquals(3, data.size());
assertNotNull(data.childByArg(CONT_PLAYER.name()));
assertNotNull(data.childByArg(LIBRARY_NID));
assertNotNull(data.childByArg(PLAYLIST_NID));
+
+ assertFormat("""
+ {
+ "example-jukebox:jukebox": {
+ "player": {
+ "gap": "0.2"
+ }
+ }
+ }""", body::formatToJSON, true);
+ assertFormat("""
+ <jukebox xmlns="http://example.com/ns/example-jukebox">
+ <library/>
+ <player>
+ <gap>0.2</gap>
+ </player>
+ </jukebox>""", body::formatToXML, true);
}
@Test
.read(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
// response must contain only config data
- final var data = assertInstanceOf(ContainerNode.class,
- assertNormalizedNode(200, ar -> restconf.dataGET(JUKEBOX_API_PATH, uriInfo, ar)));
+ final var body = assertNormalizedBody(200, ar -> restconf.dataGET(JUKEBOX_API_PATH, uriInfo, ar));
+ final var data = assertInstanceOf(ContainerNode.class, body.data());
// config data present
assertNotNull(data.childByArg(CONT_PLAYER.name()));
assertNotNull(data.childByArg(LIBRARY_NID));
// state data absent
assertNull(data.childByArg(PLAYLIST_NID));
+
+ assertFormat("""
+ {
+ "example-jukebox:jukebox": {
+ "player": {
+ "gap": "0.2"
+ }
+ }
+ }""", body::formatToJSON, true);
+ assertFormat("""
+ <jukebox xmlns="http://example.com/ns/example-jukebox">
+ <library/>
+ <player>
+ <gap>0.2</gap>
+ </player>
+ </jukebox>""", body::formatToXML, true);
}
/**
.read(LogicalDatastoreType.OPERATIONAL, JUKEBOX_IID);
// response must contain only operational data
- final var data = assertInstanceOf(ContainerNode.class, assertNormalizedNode(200,
- ar -> restconf.dataGET(JUKEBOX_API_PATH, uriInfo, ar)));
+ final var body = assertNormalizedBody(200, ar -> restconf.dataGET(JUKEBOX_API_PATH, uriInfo, ar));
+ final var data = assertInstanceOf(ContainerNode.class, body.data());
// state data present
assertNotNull(data.childByArg(CONT_PLAYER.name()));
assertNotNull(data.childByArg(PLAYLIST_NID));
// config data absent
assertNull(data.childByArg(LIBRARY_NID));
+
+ assertFormat("""
+ {
+ "example-jukebox:jukebox": {
+ "player": {
+ "gap": "0.2"
+ }
+ }
+ }""", body::formatToJSON, true);
+ assertFormat("""
+ <jukebox xmlns="http://example.com/ns/example-jukebox">
+ <player>
+ <gap>0.2</gap>
+ </player>
+ </jukebox>""", body::formatToXML, true);
}
}
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
+import org.eclipse.jdt.annotation.NonNull;
import org.opendaylight.restconf.server.api.DatabindContext;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.common.QNameModule;
protected static final QName MY_LEAF11_QNAME = QName.create(PATCH_CONT_QNAME, "my-leaf11");
protected static final QName MY_LEAF12_QNAME = QName.create(PATCH_CONT_QNAME, "my-leaf12");
- protected static final EffectiveModelContext IID_SCHEMA =
+ protected static final @NonNull EffectiveModelContext IID_SCHEMA =
YangParserTestUtils.parseYangResourceDirectory("/instanceidentifier/yang");
- protected static final DatabindContext IID_DATABIND = DatabindContext.ofModel(IID_SCHEMA);
+ protected static final @NonNull DatabindContext IID_DATABIND = DatabindContext.ofModel(IID_SCHEMA);
protected static final InputStream stringInputStream(final String str) {
return new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8));
+++ /dev/null
-/*
- * Copyright (c) 2022 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.rfc8040.jersey.providers;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.mock;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import javax.ws.rs.core.MediaType;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.junit.MockitoJUnitRunner;
-import org.opendaylight.restconf.api.query.PrettyPrintParam;
-import org.opendaylight.restconf.nb.rfc8040.AbstractInstanceIdentifierTest;
-import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
-import org.opendaylight.restconf.nb.rfc8040.legacy.WriterParameters;
-import org.opendaylight.yangtools.yang.common.QName;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
-import org.opendaylight.yangtools.yang.data.spi.node.ImmutableNodes;
-import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
-import org.opendaylight.yangtools.yang.model.api.SchemaContext;
-import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
-
-@RunWith(MockitoJUnitRunner.StrictStubs.class)
-public class XmlNormalizedNodeBodyWriterTest extends AbstractInstanceIdentifierTest {
- @Test
- public void testWriteEmptyRootContainer() throws IOException {
- final EffectiveModelContext schemaContext = mock(EffectiveModelContext.class);
-
- final NormalizedNodePayload nodePayload = new NormalizedNodePayload(Inference.ofDataTreePath(schemaContext),
- ImmutableNodes.newContainerBuilder().withNodeIdentifier(new NodeIdentifier(SchemaContext.NAME)).build(),
- WriterParameters.EMPTY, PrettyPrintParam.FALSE);
-
- final ByteArrayOutputStream output = new ByteArrayOutputStream();
- final XmlNormalizedNodeBodyWriter xmlWriter = new XmlNormalizedNodeBodyWriter();
- xmlWriter.writeTo(nodePayload, null, null, null, MediaType.APPLICATION_XML_TYPE, null, output);
-
- // FIXME: NETCONF-855: this is wrong, the namespace should be 'urn:ietf:params:xml:ns:yang:ietf-restconf'
- assertEquals("<data xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\"></data>",
- output.toString(StandardCharsets.UTF_8));
- }
-
- @Test
- public void testRootContainerWrite() throws IOException {
- final NormalizedNodePayload nodePayload = new NormalizedNodePayload(
- Inference.ofDataTreePath(IID_SCHEMA),
- ImmutableNodes.newContainerBuilder()
- .withNodeIdentifier(new NodeIdentifier(SchemaContext.NAME))
- .withChild(ImmutableNodes.newContainerBuilder()
- .withNodeIdentifier(new NodeIdentifier(
- QName.create("foo:module", "2016-09-29", "foo-bar-container")))
- .build())
- .withChild(ImmutableNodes.newContainerBuilder()
- .withNodeIdentifier(new NodeIdentifier(
- QName.create("bar:module", "2016-09-29", "foo-bar-container")))
- .build())
- .build(), WriterParameters.EMPTY, PrettyPrintParam.FALSE);
-
- final ByteArrayOutputStream output = new ByteArrayOutputStream();
- final XmlNormalizedNodeBodyWriter xmlWriter = new XmlNormalizedNodeBodyWriter();
- xmlWriter.writeTo(nodePayload, null, null, null, MediaType.APPLICATION_XML_TYPE, null, output);
-
- assertEquals("""
- <data xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">\
- <foo-bar-container xmlns="bar:module"></foo-bar-container>\
- <foo-bar-container xmlns="foo:module"></foo-bar-container>\
- </data>""", output.toString(StandardCharsets.UTF_8));
- }
-}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright (c) 2022 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.spi;
+
+import org.junit.jupiter.api.Test;
+import org.opendaylight.restconf.nb.rfc8040.AbstractInstanceIdentifierTest;
+import org.opendaylight.restconf.nb.rfc8040.AbstractJukeboxTest;
+import org.opendaylight.restconf.server.api.DatabindPath.Data;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.spi.node.ImmutableNodes;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+class NormalizedFormattableBodyTest extends AbstractInstanceIdentifierTest {
+ @Test
+ void testWriteEmptyRootContainer() throws Exception {
+ final var body = NormalizedFormattableBody.of(new Data(IID_DATABIND), ImmutableNodes.newContainerBuilder()
+ .withNodeIdentifier(new NodeIdentifier(SchemaContext.NAME))
+ .build(), NormalizedNodeWriterFactory.of());
+
+ // FIXME: NETCONF-855: this is wrong, the namespace should be 'urn:ietf:params:xml:ns:yang:ietf-restconf'
+ AbstractJukeboxTest.assertFormat("<data xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\"></data>",
+ body::formatToXML, false);
+ AbstractJukeboxTest.assertFormat("{}", body::formatToJSON, false);
+ }
+
+ @Test
+ void testRootContainerWrite() throws Exception {
+ final var body = NormalizedFormattableBody.of(new Data(IID_DATABIND), ImmutableNodes.newContainerBuilder()
+ .withNodeIdentifier(new NodeIdentifier(SchemaContext.NAME))
+ .withChild(ImmutableNodes.newContainerBuilder()
+ .withNodeIdentifier(new NodeIdentifier(
+ QName.create("foo:module", "2016-09-29", "foo-bar-container")))
+ .build())
+ .withChild(ImmutableNodes.newContainerBuilder()
+ .withNodeIdentifier(new NodeIdentifier(
+ QName.create("bar:module", "2016-09-29", "foo-bar-container")))
+ .build())
+ .build(), NormalizedNodeWriterFactory.of());
+
+ // FIXME: NETCONF-855: this is wrong, the namespace should be 'urn:ietf:params:xml:ns:yang:ietf-restconf'
+ AbstractJukeboxTest.assertFormat("""
+ <data xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">\
+ <foo-bar-container xmlns="bar:module"></foo-bar-container>\
+ <foo-bar-container xmlns="foo:module"></foo-bar-container>\
+ </data>""", body::formatToXML, false);
+ AbstractJukeboxTest.assertFormat("""
+ {"bar-module:foo-bar-container":{}}""", body::formatToJSON, false);
+ }
+}
\ No newline at end of file
/* This container has the same name as container in foo-module */
container foo-bar-container {
+ presence "has semantic meaning";
}
-}
\ No newline at end of file
+}
}
/* This container has the same name as container in bar-module */
- container foo-bar-container {
- }
-}
\ No newline at end of file
+ container foo-bar-container;
+}