From 2bb148284126ce63c972814689bd3da46c5cd348 Mon Sep 17 00:00:00 2001 From: Robert Varga Date: Mon, 8 Apr 2024 23:03:35 +0200 Subject: [PATCH] Rework body formatting wiring Output formatting is a policy decision of a particular HTTP endpoint, not of a RestconfServer implementation. This patch performs some very invasive surgery to rehost this information which minimizing method churn to express prepare the code for fitting in access control. FormatParameters are now a simple record encapsulating PrettyPrintParam. These get routed by JaxRsRestconf through interpretation and communicated to body writers without passing them directly to RestconfServer. ServerRequest is introduced and passed instead of QueryParameters or QueryParams -- it exposes post-processed QueryParameters. QueryParameters is turned into an immutable class instead of an interface + record. This is a tad more convenient and easier to understand. Most notably we disconnect WriterParametes from FormatParameters, as they serve different domains: - FormatParameters are about formatting any particular content - WriterParameters are about controlling how we iterate over a NormalizedNode (which in turn has two parts, but that's for later) JIRA: NETCONF-773 Change-Id: I0848ff5deb5dc55ff3b45ef7155f04cfe3ab50f7 Signed-off-by: Robert Varga --- .../restconf/api/FormatParameters.java | 15 +- .../restconf/api/FormattableBody.java | 29 +--- .../api/ImmutableQueryParameters.java | 55 -------- .../restconf/api/QueryParameters.java | 83 ++++++++--- .../restconf/api/query/ContentParam.java | 2 +- .../restconf/api/query/FieldsParam.java | 3 +- .../restconf/api/query/PrettyPrintParam.java | 3 +- .../restconf/api/query/WithDefaultsParam.java | 6 +- .../nb/jaxrs/FormattableBodyCallback.java | 11 +- .../nb/jaxrs/JaxRsFormattableBody.java | 25 ++++ ...r.java => JaxRsFormattableBodyWriter.java} | 14 +- .../restconf/nb/jaxrs/JaxRsRestconf.java | 97 +++++++------ ...va => JsonJaxRsFormattableBodyWriter.java} | 7 +- ...ava => XmlJaxRsFormattableBodyWriter.java} | 7 +- .../restconf/nb/rfc8040/Insert.java | 4 +- .../restconf/nb/rfc8040/JaxRsNorthbound.java | 8 +- .../restconf/nb/rfc8040/OSGiNorthbound.java | 11 +- .../AbstractNormalizedNodeBodyWriter.java | 12 +- .../JsonNormalizedNodeBodyWriter.java | 7 +- .../XmlNormalizedNodeBodyWriter.java | 7 +- .../rfc8040/legacy/NormalizedNodePayload.java | 8 +- .../nb/rfc8040/legacy/WriterParameters.java | 20 +-- .../transactions/MdsalRestconfStrategy.java | 17 +-- .../transactions/NetconfRestconfStrategy.java | 8 +- .../rests/transactions/RestconfStrategy.java | 113 ++++++--------- .../DefaultRestconfStreamServletFactory.java | 18 ++- .../streams/RestconfStreamServletFactory.java | 3 + .../nb/rfc8040/streams/SSEStreamService.java | 6 +- .../restconf/server/api/DataGetParams.java | 34 ++--- .../server/api/DataYangPatchParams.java | 39 ------ .../server/api/DataYangPatchResult.java | 6 +- .../server/api/DatabindFormattableBody.java | 22 ++- .../api/DatabindPathFormattableBody.java | 6 +- .../server/api/EventStreamGetParams.java | 7 +- .../server/api/FormatParametersHelper.java | 36 ----- .../restconf/server/api/InvokeParams.java | 40 ------ .../restconf/server/api/ModulesGetResult.java | 2 +- .../restconf/server/api/QueryParams.java | 47 ------- .../restconf/server/api/RestconfServer.java | 66 ++++----- .../restconf/server/api/ServerRequest.java | 47 +++++++ .../server/mdsal/MdsalRestconfServer.java | 131 +++++++----------- .../restconf/server/spi/AllOperations.java | 6 + .../server/spi/FailedHttpGetResource.java | 6 +- .../restconf/server/spi/HttpGetResource.java | 6 +- .../server/spi/NormalizedFormattableBody.java | 11 +- .../restconf/server/spi/OneOperation.java | 2 +- .../server/spi/OperationOutputBody.java | 9 +- .../restconf/server/spi/OperationsBody.java | 6 +- .../server/spi/OperationsResource.java | 6 +- .../spi/RestconfServerConfiguration.java | 24 ---- .../spi/YangLibraryVersionResource.java | 8 +- .../server/spi/YangPatchStatusBody.java | 13 +- .../nb/jaxrs/AbstractRestconfTest.java | 42 ++---- .../restconf/nb/jaxrs/Netconf799Test.java | 49 ++++--- .../restconf/nb/jaxrs/Netconf822Test.java | 12 +- .../nb/jaxrs/RestconfDataPatchTest.java | 6 +- .../nb/jaxrs/RestconfOperationsGetTest.java | 18 +-- .../jaxrs/RestconfSchemaServiceMountTest.java | 9 +- .../nb/jaxrs/RestconfSchemaServiceTest.java | 9 +- .../RestconfYangLibraryVersionGetTest.java | 6 +- .../nb/rfc8040/AbstractJukeboxTest.java | 11 +- .../XmlNormalizedNodeBodyWriterTest.java | 5 +- .../AbstractRestconfStrategyTest.java | 14 +- .../NetconfRestconfStrategyTest.java | 20 +-- .../restconf/server/api/ParamsTest.java | 15 +- .../server/spi/YangPatchStatusBodyTest.java | 17 ++- 66 files changed, 621 insertions(+), 791 deletions(-) delete mode 100644 protocol/restconf-api/src/main/java/org/opendaylight/restconf/api/ImmutableQueryParameters.java create mode 100644 restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/JaxRsFormattableBody.java rename restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/{FormattableBodyWriter.java => JaxRsFormattableBodyWriter.java} (65%) rename restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/{JsonFormattableBody.java => JsonJaxRsFormattableBodyWriter.java} (70%) rename restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/{XmlFormattableBody.java => XmlJaxRsFormattableBodyWriter.java} (71%) delete mode 100644 restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/DataYangPatchParams.java delete mode 100644 restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/FormatParametersHelper.java delete mode 100644 restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/InvokeParams.java delete mode 100644 restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/QueryParams.java create mode 100644 restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/ServerRequest.java delete mode 100644 restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/RestconfServerConfiguration.java diff --git a/protocol/restconf-api/src/main/java/org/opendaylight/restconf/api/FormatParameters.java b/protocol/restconf-api/src/main/java/org/opendaylight/restconf/api/FormatParameters.java index 9a3e59d636..7a1738ce41 100644 --- a/protocol/restconf-api/src/main/java/org/opendaylight/restconf/api/FormatParameters.java +++ b/protocol/restconf-api/src/main/java/org/opendaylight/restconf/api/FormatParameters.java @@ -7,6 +7,8 @@ */ package org.opendaylight.restconf.api; +import static java.util.Objects.requireNonNull; + import org.eclipse.jdt.annotation.NonNullByDefault; import org.opendaylight.restconf.api.query.PrettyPrintParam; import org.opendaylight.restconf.api.query.RestconfQueryParam; @@ -14,13 +16,18 @@ import org.opendaylight.yangtools.concepts.Immutable; /** * The set of {@link RestconfQueryParam}s governing output formatting. + * + * @param prettyPrint the {@link PrettyPrintParam} parameter */ @NonNullByDefault -public interface FormatParameters extends Immutable { +public record FormatParameters(PrettyPrintParam prettyPrint) implements Immutable { + public static final FormatParameters COMPACT = new FormatParameters(PrettyPrintParam.FALSE); + public static final FormatParameters PRETTY = new FormatParameters(PrettyPrintParam.TRUE); + /** * Return the {@link PrettyPrintParam} parameter. - * - * @return the {@link PrettyPrintParam} parameter */ - PrettyPrintParam prettyPrint(); + public FormatParameters { + requireNonNull(prettyPrint); + } } diff --git a/protocol/restconf-api/src/main/java/org/opendaylight/restconf/api/FormattableBody.java b/protocol/restconf-api/src/main/java/org/opendaylight/restconf/api/FormattableBody.java index ae277a23ef..870bcd2d28 100644 --- a/protocol/restconf-api/src/main/java/org/opendaylight/restconf/api/FormattableBody.java +++ b/protocol/restconf-api/src/main/java/org/opendaylight/restconf/api/FormattableBody.java @@ -7,8 +7,6 @@ */ package org.opendaylight.restconf.api; -import static java.util.Objects.requireNonNull; - import com.google.common.base.MoreObjects; import com.google.common.base.MoreObjects.ToStringHelper; import java.io.IOException; @@ -17,46 +15,33 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.opendaylight.yangtools.concepts.Immutable; /** - * A body which is capable of being formatted to an {@link OutputStream} in either JSON or XML format. + * A body which is capable of being formatted to an {@link OutputStream} in either JSON or XML format using particular + * {@link FormatParameters}. */ @NonNullByDefault public abstract class FormattableBody implements Immutable { - private final FormatParameters format; - - protected FormattableBody(final FormatParameters format) { - this.format = requireNonNull(format); - } - /** * Write the content of this body as a JSON document. * + * @param format {@link FormatParameters} * @param out output stream * @throws IOException if an IO error occurs. */ - public final void formatToJSON(final OutputStream out) throws IOException { - formatToJSON(requireNonNull(out), format); - } - - protected abstract void formatToJSON(OutputStream out, FormatParameters format) throws IOException; + public abstract void formatToJSON(FormatParameters format, OutputStream out) throws IOException; /** * Write the content of this body as an XML document. * + * @param format {@link FormatParameters} * @param out output stream * @throws IOException if an IO error occurs. */ - public final void formatToXML(final OutputStream out) throws IOException { - formatToXML(requireNonNull(out), format); - } + public abstract void formatToXML(FormatParameters format, OutputStream out) throws IOException; - protected abstract void formatToXML(OutputStream out, FormatParameters format) throws IOException; + protected abstract ToStringHelper addToStringAttributes(ToStringHelper helper); @Override public final String toString() { return addToStringAttributes(MoreObjects.toStringHelper(this).omitNullValues()).toString(); } - - protected ToStringHelper addToStringAttributes(final ToStringHelper helper) { - return helper.add("prettyPrint", format.prettyPrint().value()); - } } diff --git a/protocol/restconf-api/src/main/java/org/opendaylight/restconf/api/ImmutableQueryParameters.java b/protocol/restconf-api/src/main/java/org/opendaylight/restconf/api/ImmutableQueryParameters.java deleted file mode 100644 index 89bad2bd9a..0000000000 --- a/protocol/restconf-api/src/main/java/org/opendaylight/restconf/api/ImmutableQueryParameters.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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.api; - -import static java.util.Objects.requireNonNull; - -import com.google.common.collect.ImmutableMap; -import java.util.Collection; -import java.util.Map.Entry; -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.opendaylight.restconf.api.query.RestconfQueryParam; - -/** - * Default implementation of {@link QueryParameters}. - */ -@NonNullByDefault -record ImmutableQueryParameters(ImmutableMap params) implements QueryParameters { - static final ImmutableQueryParameters EMPTY = new ImmutableQueryParameters(ImmutableMap.of()); - - ImmutableQueryParameters { - requireNonNull(params); - } - - ImmutableQueryParameters(final Collection> params) { - // TODO: consider caching common request parameter combinations - this(params.stream() - .collect(ImmutableMap.toImmutableMap(RestconfQueryParam::paramName, RestconfQueryParam::paramValue))); - } - - @Override - public boolean isEmpty() { - return params.isEmpty(); - } - - @Override - public Collection> asCollection() { - return params.entrySet(); - } - - @Override - public @Nullable String lookup(final String paramName) { - return params.get(requireNonNull(paramName)); - } - - @Override - public String toString() { - return QueryParameters.class.getSimpleName() + "(" + params + ")"; - } -} diff --git a/protocol/restconf-api/src/main/java/org/opendaylight/restconf/api/QueryParameters.java b/protocol/restconf-api/src/main/java/org/opendaylight/restconf/api/QueryParameters.java index 732bfcd6f6..91ef6e2d92 100644 --- a/protocol/restconf-api/src/main/java/org/opendaylight/restconf/api/QueryParameters.java +++ b/protocol/restconf-api/src/main/java/org/opendaylight/restconf/api/QueryParameters.java @@ -7,12 +7,16 @@ */ package org.opendaylight.restconf.api; +import static java.util.Objects.requireNonNull; + import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.function.Function; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.opendaylight.restconf.api.query.RestconfQueryParam; @@ -25,56 +29,63 @@ import org.opendaylight.yangtools.concepts.Immutable; * once. */ @NonNullByDefault -public interface QueryParameters extends Immutable { +public final class QueryParameters implements Immutable { + static final QueryParameters EMPTY = new QueryParameters(ImmutableMap.of()); - boolean isEmpty(); + private final ImmutableMap params; - Collection> asCollection(); + private QueryParameters(final ImmutableMap params) { + this.params = requireNonNull(params); + } - @Nullable String lookup(String paramName); + private QueryParameters(final Collection> params) { + // TODO: consider caching common request parameter combinations + this(params.stream() + .collect(ImmutableMap.toImmutableMap(RestconfQueryParam::paramName, RestconfQueryParam::paramValue))); + } - static QueryParameters of() { - return ImmutableQueryParameters.EMPTY; + public static QueryParameters of() { + return EMPTY; } - static QueryParameters of(final String paramName, final String paramValue) { - return new ImmutableQueryParameters(ImmutableMap.of(paramName, paramValue)); + public static QueryParameters of(final String paramName, final String paramValue) { + return new QueryParameters(ImmutableMap.of(paramName, paramValue)); } - static QueryParameters of(final Entry entry) { + public static QueryParameters of(final Entry entry) { return of(entry.getKey(), entry.getValue()); } - static QueryParameters of(final RestconfQueryParam param) { + public static QueryParameters of(final RestconfQueryParam param) { return of(param.paramName(), param.paramValue()); } - static QueryParameters of(final RestconfQueryParam... params) { + public static QueryParameters of(final RestconfQueryParam... params) { return switch (params.length) { case 0 -> of(); case 1 -> of(params[0]); - default -> new ImmutableQueryParameters(Arrays.asList(params)); + default -> new QueryParameters(Arrays.asList(params)); }; } - static QueryParameters of(final Collection> params) { + public static QueryParameters of(final Collection> params) { return params instanceof List ? of((List>) params) : switch (params.size()) { case 0 -> of(); case 1 -> of(params.iterator().next()); - default -> new ImmutableQueryParameters(params); + default -> new QueryParameters(params); }; } - static QueryParameters of(final List> params) { + public static QueryParameters of(final List> params) { return switch (params.size()) { case 0 -> of(); case 1 -> of(params.get(0)); - default -> new ImmutableQueryParameters(params); + default -> new QueryParameters(params); }; } - static QueryParameters of(final Map params) { - return params.isEmpty() ? of() : new ImmutableQueryParameters(ImmutableMap.copyOf(params)); + public static QueryParameters of(final Map params) { + return params.isEmpty() ? of() : new QueryParameters(ImmutableMap.copyOf(params)); } /** @@ -82,11 +93,11 @@ public interface QueryParameters extends Immutable { * from JAX-RS's {@code UriInfo}. * * @param multiParams Input map - * @return An {@link ImmutableQueryParameters} instance + * @return A {@link QueryParameters} instance * @throws NullPointerException if {@code uriInfo} is {@code null} * @throws IllegalArgumentException if there are multiple values for a parameter */ - static QueryParameters ofMultiValue(final Map> multiParams) { + public static QueryParameters ofMultiValue(final Map> multiParams) { if (multiParams.isEmpty()) { return of(); } @@ -105,6 +116,36 @@ public interface QueryParameters extends Immutable { } final var params = builder.build(); - return params.isEmpty() ? of() : new ImmutableQueryParameters(params); + return params.isEmpty() ? of() : new QueryParameters(params); + } + + public boolean isEmpty() { + return params.isEmpty(); + } + + public Collection> asCollection() { + return params.entrySet(); + } + + public @Nullable String lookup(final String paramName) { + return params.get(requireNonNull(paramName)); } + + public @Nullable T lookup(final String paramName, final Function parseFunction) { + final var str = lookup(paramName); + if (str != null) { + return parseFunction.apply(str); + } + return null; + } + + public QueryParameters withoutParam(final String paramName) { + return params.containsKey(paramName) ? of(Maps.filterKeys(params, key -> !key.equals(paramName))) : this; + } + + @Override + public String toString() { + return QueryParameters.class.getSimpleName() + "(" + params + ")"; + } + } diff --git a/protocol/restconf-api/src/main/java/org/opendaylight/restconf/api/query/ContentParam.java b/protocol/restconf-api/src/main/java/org/opendaylight/restconf/api/query/ContentParam.java index 3f95f311f2..1c0354ea8a 100644 --- a/protocol/restconf-api/src/main/java/org/opendaylight/restconf/api/query/ContentParam.java +++ b/protocol/restconf-api/src/main/java/org/opendaylight/restconf/api/query/ContentParam.java @@ -60,7 +60,7 @@ public enum ContentParam implements RestconfQueryParam { case "config" -> CONFIG; case "nonconfig" -> NONCONFIG; default -> throw new IllegalArgumentException( - "Value can be 'all', 'config' or 'nonconfig', not '" + uriValue + "'"); + "Invalid " + uriName + " value: Value can be 'all', 'config' or 'nonconfig', not '" + uriValue + "'"); }; } } diff --git a/protocol/restconf-api/src/main/java/org/opendaylight/restconf/api/query/FieldsParam.java b/protocol/restconf-api/src/main/java/org/opendaylight/restconf/api/query/FieldsParam.java index 2a0cd640ba..d81d8000b4 100644 --- a/protocol/restconf-api/src/main/java/org/opendaylight/restconf/api/query/FieldsParam.java +++ b/protocol/restconf-api/src/main/java/org/opendaylight/restconf/api/query/FieldsParam.java @@ -114,7 +114,8 @@ public final class FieldsParam implements RestconfQueryParam { try { return parse(uriValue); } catch (ParseException e) { - throw new IllegalArgumentException(e.getMessage() + " [at offset " + e.getErrorOffset() + "]", e); + throw new IllegalArgumentException("Invalid " + uriName + " value: " + e.getMessage() + + " [at offset " + e.getErrorOffset() + "]", e); } } diff --git a/protocol/restconf-api/src/main/java/org/opendaylight/restconf/api/query/PrettyPrintParam.java b/protocol/restconf-api/src/main/java/org/opendaylight/restconf/api/query/PrettyPrintParam.java index 6a70d37883..9031fbd1d6 100644 --- a/protocol/restconf-api/src/main/java/org/opendaylight/restconf/api/query/PrettyPrintParam.java +++ b/protocol/restconf-api/src/main/java/org/opendaylight/restconf/api/query/PrettyPrintParam.java @@ -39,7 +39,8 @@ public final class PrettyPrintParam implements RestconfQueryParam FALSE; case "true" -> TRUE; - default -> throw new IllegalArgumentException("Value can be 'false' or 'true', not '" + uriValue + "'"); + default -> throw new IllegalArgumentException( + "Invalid " + uriName + " value: Value can be 'false' or 'true', not '" + uriValue + "'"); }; } diff --git a/protocol/restconf-api/src/main/java/org/opendaylight/restconf/api/query/WithDefaultsParam.java b/protocol/restconf-api/src/main/java/org/opendaylight/restconf/api/query/WithDefaultsParam.java index c2a30c62d4..147fada3a8 100644 --- a/protocol/restconf-api/src/main/java/org/opendaylight/restconf/api/query/WithDefaultsParam.java +++ b/protocol/restconf-api/src/main/java/org/opendaylight/restconf/api/query/WithDefaultsParam.java @@ -60,7 +60,11 @@ public enum WithDefaultsParam implements RestconfQueryParam { } public static @NonNull WithDefaultsParam forUriValue(final String uriValue) { - return of(WithDefaultsMode.ofName(uriValue)); + try { + return of(WithDefaultsMode.ofName(uriValue)); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("Invalid " + uriName + " value: " + e.getMessage(), e); + } } @Override diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/FormattableBodyCallback.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/FormattableBodyCallback.java index 045875293f..8d5aebb00b 100644 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/FormattableBodyCallback.java +++ b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/FormattableBodyCallback.java @@ -7,8 +7,12 @@ */ package org.opendaylight.restconf.nb.jaxrs; +import static java.util.Objects.requireNonNull; + import javax.ws.rs.container.AsyncResponse; import javax.ws.rs.core.Response; +import org.eclipse.jdt.annotation.NonNull; +import org.opendaylight.restconf.api.FormatParameters; import org.opendaylight.restconf.api.FormattableBody; import org.opendaylight.restconf.common.errors.RestconfDocumentedException; @@ -16,12 +20,15 @@ import org.opendaylight.restconf.common.errors.RestconfDocumentedException; * A {@link JaxRsRestconfCallback} producing a {@link FormattableBody}. */ final class FormattableBodyCallback extends JaxRsRestconfCallback { - FormattableBodyCallback(final AsyncResponse ar) { + private final @NonNull FormatParameters format; + + FormattableBodyCallback(final AsyncResponse ar, final FormatParameters format) { super(ar); + this.format = requireNonNull(format); } @Override Response transform(final FormattableBody result) throws RestconfDocumentedException { - return Response.ok().entity(result).build(); + return Response.ok().entity(new JaxRsFormattableBody(result, format)).build(); } } diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/JaxRsFormattableBody.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/JaxRsFormattableBody.java new file mode 100644 index 0000000000..2ebe205864 --- /dev/null +++ b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/JaxRsFormattableBody.java @@ -0,0 +1,25 @@ +/* + * 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 org.eclipse.jdt.annotation.NonNullByDefault; +import org.opendaylight.restconf.api.FormatParameters; +import org.opendaylight.restconf.api.FormattableBody; + +/** + * A bridge capturing a {@link FormattableBody} and {@link FormatParameters}./ + */ +@NonNullByDefault +record JaxRsFormattableBody(FormattableBody body, FormatParameters format) { + JaxRsFormattableBody { + requireNonNull(body); + requireNonNull(format); + } +} diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/FormattableBodyWriter.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/JaxRsFormattableBodyWriter.java similarity index 65% rename from restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/FormattableBodyWriter.java rename to restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/JaxRsFormattableBodyWriter.java index 10aa2fccdc..f92e09ba17 100644 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/FormattableBodyWriter.java +++ b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/JaxRsFormattableBodyWriter.java @@ -16,11 +16,12 @@ 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.eclipse.jdt.annotation.NonNullByDefault; +import org.opendaylight.restconf.api.FormatParameters; import org.opendaylight.restconf.api.FormattableBody; -abstract sealed class FormattableBodyWriter implements MessageBodyWriter - permits JsonFormattableBody, XmlFormattableBody { +abstract sealed class JaxRsFormattableBodyWriter implements MessageBodyWriter + permits JsonJaxRsFormattableBodyWriter, XmlJaxRsFormattableBodyWriter { @Override public final boolean isWriteable(final Class type, final Type genericType, final Annotation[] annotations, final MediaType mediaType) { @@ -28,11 +29,12 @@ abstract sealed class FormattableBodyWriter implements MessageBodyWriter type, final Type genericType, + public final void writeTo(final JaxRsFormattableBody entity, final Class type, final Type genericType, final Annotation[] annotations, final MediaType mediaType, final MultivaluedMap httpHeaders, final OutputStream entityStream) throws IOException { - writeTo(requireNonNull(body), requireNonNull(entityStream)); + writeTo(entity.body(), entity.format(), requireNonNull(entityStream)); } - abstract void writeTo(@NonNull FormattableBody body, @NonNull OutputStream out) throws IOException; + @NonNullByDefault + abstract void writeTo(FormattableBody body, FormatParameters format, OutputStream out) throws IOException; } diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/JaxRsRestconf.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/JaxRsRestconf.java index 69803105dc..d0fb429523 100644 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/JaxRsRestconf.java +++ b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/JaxRsRestconf.java @@ -45,8 +45,10 @@ import javax.ws.rs.ext.ParamConverterProvider; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; import org.opendaylight.restconf.api.ApiPath; +import org.opendaylight.restconf.api.FormatParameters; import org.opendaylight.restconf.api.MediaTypes; import org.opendaylight.restconf.api.QueryParameters; +import org.opendaylight.restconf.api.query.PrettyPrintParam; import org.opendaylight.restconf.common.errors.RestconfDocumentedException; import org.opendaylight.restconf.common.errors.RestconfError; import org.opendaylight.restconf.common.errors.RestconfFuture; @@ -70,6 +72,7 @@ import org.opendaylight.restconf.server.api.ModulesGetResult; import org.opendaylight.restconf.server.api.OperationInputBody; import org.opendaylight.restconf.server.api.PatchStatusContext; import org.opendaylight.restconf.server.api.RestconfServer; +import org.opendaylight.restconf.server.api.ServerRequest; import org.opendaylight.restconf.server.api.XmlChildBody; import org.opendaylight.restconf.server.api.XmlDataPostBody; import org.opendaylight.restconf.server.api.XmlOperationInputBody; @@ -116,9 +119,23 @@ public final class JaxRsRestconf implements ParamConverterProvider { }; private final @NonNull RestconfServer server; + private final @NonNull ServerRequest emptyRequest; + private final @NonNull PrettyPrintParam prettyPrint; - public JaxRsRestconf(final RestconfServer server) { + public JaxRsRestconf(final RestconfServer server, final PrettyPrintParam prettyPrint) { this.server = requireNonNull(server); + this.prettyPrint = requireNonNull(prettyPrint); + emptyRequest = ServerRequest.of(QueryParameters.of(), prettyPrint); + } + + private @NonNull ServerRequest requestOf(final UriInfo uriInfo) { + final QueryParameters params; + try { + params = QueryParameters.ofMultiValue(uriInfo.getQueryParameters()); + } catch (IllegalArgumentException e) { + throw new BadRequestException(e.getMessage(), e); + } + return params.isEmpty() ? emptyRequest : ServerRequest.of(params, prettyPrint); } @Override @@ -128,14 +145,6 @@ public final class JaxRsRestconf implements ParamConverterProvider { return ApiPath.class.equals(rawType) ? (ParamConverter) API_PATH_CONVERTER : null; } - private static @NonNull QueryParameters queryParams(final UriInfo uriInfo) { - try { - return QueryParameters.ofMultiValue(uriInfo.getQueryParameters()); - } catch (IllegalArgumentException e) { - throw new BadRequestException(e.getMessage(), e); - } - } - /** * Delete the target data resource. * @@ -147,7 +156,7 @@ public final class JaxRsRestconf implements ParamConverterProvider { @SuppressWarnings("checkstyle:abbreviationAsWordInName") public void dataDELETE(@Encoded @PathParam("identifier") final ApiPath identifier, @Suspended final AsyncResponse ar) { - server.dataDELETE(identifier).addCallback(new JaxRsRestconfCallback<>(ar) { + server.dataDELETE(emptyRequest, identifier).addCallback(new JaxRsRestconfCallback<>(ar) { @Override Response transform(final Empty result) { return Response.noContent().build(); @@ -171,7 +180,7 @@ public final class JaxRsRestconf implements ParamConverterProvider { MediaType.TEXT_XML }) public void dataGET(@Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) { - completeDataGET(server.dataGET(queryParams(uriInfo)), ar); + completeDataGET(server.dataGET(requestOf(uriInfo)), ar); } /** @@ -192,7 +201,7 @@ public final class JaxRsRestconf implements ParamConverterProvider { }) public void dataGET(@Encoded @PathParam("identifier") final ApiPath identifier, @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) { - completeDataGET(server.dataGET(identifier, queryParams(uriInfo)), ar); + completeDataGET(server.dataGET(requestOf(uriInfo), identifier), ar); } private static void completeDataGET(final RestconfFuture future, final AsyncResponse ar) { @@ -233,7 +242,7 @@ public final class JaxRsRestconf implements ParamConverterProvider { }) public void dataXmlPATCH(final InputStream body, @Suspended final AsyncResponse ar) { try (var xmlBody = new XmlResourceBody(body)) { - completeDataPATCH(server.dataPATCH(xmlBody), ar); + completeDataPATCH(server.dataPATCH(emptyRequest, xmlBody), ar); } } @@ -255,7 +264,7 @@ public final class JaxRsRestconf implements ParamConverterProvider { public void dataXmlPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body, @Suspended final AsyncResponse ar) { try (var xmlBody = new XmlResourceBody(body)) { - completeDataPATCH(server.dataPATCH(identifier, xmlBody), ar); + completeDataPATCH(server.dataPATCH(emptyRequest, identifier, xmlBody), ar); } } @@ -274,7 +283,7 @@ public final class JaxRsRestconf implements ParamConverterProvider { }) public void dataJsonPATCH(final InputStream body, @Suspended final AsyncResponse ar) { try (var jsonBody = new JsonResourceBody(body)) { - completeDataPATCH(server.dataPATCH(jsonBody), ar); + completeDataPATCH(server.dataPATCH(emptyRequest, jsonBody), ar); } } @@ -295,7 +304,7 @@ public final class JaxRsRestconf implements ParamConverterProvider { public void dataJsonPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body, @Suspended final AsyncResponse ar) { try (var jsonBody = new JsonResourceBody(body)) { - completeDataPATCH(server.dataPATCH(identifier, jsonBody), ar); + completeDataPATCH(server.dataPATCH(emptyRequest, identifier, jsonBody), ar); } } @@ -328,7 +337,7 @@ public final class JaxRsRestconf implements ParamConverterProvider { public void dataYangJsonPATCH(final InputStream body, @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) { try (var jsonBody = new JsonPatchBody(body)) { - completeDataYangPATCH(server.dataPATCH(queryParams(uriInfo), jsonBody), ar); + completeDataYangPATCH(server.dataPATCH(requestOf(uriInfo), jsonBody), ar); } } @@ -351,7 +360,7 @@ public final class JaxRsRestconf implements ParamConverterProvider { public void dataYangJsonPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body, @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) { try (var jsonBody = new JsonPatchBody(body)) { - completeDataYangPATCH(server.dataPATCH(identifier, queryParams(uriInfo), jsonBody), ar); + completeDataYangPATCH(server.dataPATCH(requestOf(uriInfo), identifier, jsonBody), ar); } } @@ -373,7 +382,7 @@ public final class JaxRsRestconf implements ParamConverterProvider { public void dataYangXmlPATCH(final InputStream body, @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) { try (var xmlBody = new XmlPatchBody(body)) { - completeDataYangPATCH(server.dataPATCH(queryParams(uriInfo), xmlBody), ar); + completeDataYangPATCH(server.dataPATCH(requestOf(uriInfo), xmlBody), ar); } } @@ -396,7 +405,7 @@ public final class JaxRsRestconf implements ParamConverterProvider { public void dataYangXmlPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body, @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) { try (var xmlBody = new XmlPatchBody(body)) { - completeDataYangPATCH(server.dataPATCH(identifier, queryParams(uriInfo), xmlBody), ar); + completeDataYangPATCH(server.dataPATCH(requestOf(uriInfo), identifier, xmlBody), ar); } } @@ -407,7 +416,7 @@ public final class JaxRsRestconf implements ParamConverterProvider { Response transform(final DataYangPatchResult result) { final var status = result.status(); final var builder = Response.status(statusOf(status)) - .entity(new YangPatchStatusBody(result.params(), status)); + .entity(new YangPatchStatusBody(status)); fillConfigurationMetadata(builder, result); return builder.build(); } @@ -453,7 +462,8 @@ public final class JaxRsRestconf implements ParamConverterProvider { public void postDataJSON(final InputStream body, @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) { try (var jsonBody = new JsonChildBody(body)) { - completeDataPOST(server.dataPOST(queryParams(uriInfo), jsonBody), uriInfo, ar); + final var request = requestOf(uriInfo); + completeDataPOST(server.dataPOST(request, jsonBody), request.format(), uriInfo, ar); } } @@ -473,7 +483,9 @@ public final class JaxRsRestconf implements ParamConverterProvider { }) public void postDataJSON(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body, @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) { - completeDataPOST(server.dataPOST(identifier, queryParams(uriInfo), new JsonDataPostBody(body)), uriInfo, ar); + final var request = requestOf(uriInfo); + completeDataPOST(server.dataPOST(request, identifier, new JsonDataPostBody(body)), request.format(), uriInfo, + ar); } /** @@ -492,7 +504,8 @@ public final class JaxRsRestconf implements ParamConverterProvider { }) public void postDataXML(final InputStream body, @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) { try (var xmlBody = new XmlChildBody(body)) { - completeDataPOST(server.dataPOST(queryParams(uriInfo), xmlBody), uriInfo, ar); + final var request = requestOf(uriInfo); + completeDataPOST(server.dataPOST(request, xmlBody), request.format(), uriInfo, ar); } } @@ -513,11 +526,13 @@ public final class JaxRsRestconf implements ParamConverterProvider { }) public void postDataXML(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body, @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) { - completeDataPOST(server.dataPOST(identifier, queryParams(uriInfo), new XmlDataPostBody(body)), uriInfo, ar); + final var request = requestOf(uriInfo); + completeDataPOST(server.dataPOST(request, identifier, new XmlDataPostBody(body)), request.format(), uriInfo, + ar); } - private static void completeDataPOST(final RestconfFuture future, final UriInfo uriInfo, - final AsyncResponse ar) { + private static void completeDataPOST(final RestconfFuture future, + final FormatParameters format, final UriInfo uriInfo, final AsyncResponse ar) { future.addCallback(new JaxRsRestconfCallback(ar) { @Override Response transform(final DataPostResult result) { @@ -531,7 +546,8 @@ public final class JaxRsRestconf implements ParamConverterProvider { } if (result instanceof InvokeResult invokeOperation) { final var output = invokeOperation.output(); - return output == null ? Response.noContent().build() : Response.ok().entity(output).build(); + return output == null ? Response.noContent().build() + : Response.ok().entity(new JaxRsFormattableBody(output, format)).build(); } LOG.error("Unhandled result {}", result); return Response.serverError().build(); @@ -554,7 +570,7 @@ public final class JaxRsRestconf implements ParamConverterProvider { }) public void dataJsonPUT(@Context final UriInfo uriInfo, final InputStream body, @Suspended final AsyncResponse ar) { try (var jsonBody = new JsonResourceBody(body)) { - completeDataPUT(server.dataPUT(queryParams(uriInfo), jsonBody), ar); + completeDataPUT(server.dataPUT(requestOf(uriInfo), jsonBody), ar); } } @@ -575,7 +591,7 @@ public final class JaxRsRestconf implements ParamConverterProvider { public void dataJsonPUT(@Encoded @PathParam("identifier") final ApiPath identifier, @Context final UriInfo uriInfo, final InputStream body, @Suspended final AsyncResponse ar) { try (var jsonBody = new JsonResourceBody(body)) { - completeDataPUT(server.dataPUT(identifier, queryParams(uriInfo), jsonBody), ar); + completeDataPUT(server.dataPUT(requestOf(uriInfo), identifier, jsonBody), ar); } } @@ -595,7 +611,7 @@ public final class JaxRsRestconf implements ParamConverterProvider { }) public void dataXmlPUT(@Context final UriInfo uriInfo, final InputStream body, @Suspended final AsyncResponse ar) { try (var xmlBody = new XmlResourceBody(body)) { - completeDataPUT(server.dataPUT(queryParams(uriInfo), xmlBody), ar); + completeDataPUT(server.dataPUT(requestOf(uriInfo), xmlBody), ar); } } @@ -617,7 +633,7 @@ public final class JaxRsRestconf implements ParamConverterProvider { public void dataXmlPUT(@Encoded @PathParam("identifier") final ApiPath identifier, @Context final UriInfo uriInfo, final InputStream body, @Suspended final AsyncResponse ar) { try (var xmlBody = new XmlResourceBody(body)) { - completeDataPUT(server.dataPUT(identifier, queryParams(uriInfo), xmlBody), ar); + completeDataPUT(server.dataPUT(requestOf(uriInfo), identifier, xmlBody), ar); } } @@ -645,7 +661,7 @@ public final class JaxRsRestconf implements ParamConverterProvider { MediaTypes.APPLICATION_YANG_DATA_JSON, MediaType.APPLICATION_JSON }) public void operationsGET(@Suspended final AsyncResponse ar) { - server.operationsGET().addCallback(new FormattableBodyCallback(ar)); + server.operationsGET(emptyRequest).addCallback(new FormattableBodyCallback(ar, emptyRequest.format())); } /** @@ -661,7 +677,8 @@ public final class JaxRsRestconf implements ParamConverterProvider { MediaTypes.APPLICATION_YANG_DATA_JSON, MediaType.APPLICATION_JSON }) public void operationsGET(@PathParam("operation") final ApiPath operation, @Suspended final AsyncResponse ar) { - server.operationsGET(operation).addCallback(new FormattableBodyCallback(ar)); + server.operationsGET(emptyRequest, operation) + .addCallback(new FormattableBodyCallback(ar, emptyRequest.format())); } /** @@ -725,7 +742,7 @@ public final class JaxRsRestconf implements ParamConverterProvider { private void operationsPOST(final ApiPath identifier, final UriInfo uriInfo, final AsyncResponse ar, final OperationInputBody body) { - server.operationsPOST(uriInfo.getBaseUri(), identifier, queryParams(uriInfo), body) + server.operationsPOST(requestOf(uriInfo), uriInfo.getBaseUri(), identifier, body) .addCallback(new JaxRsRestconfCallback<>(ar) { @Override Response transform(final InvokeResult result) { @@ -751,7 +768,7 @@ public final class JaxRsRestconf implements ParamConverterProvider { MediaType.TEXT_XML }) public void yangLibraryVersionGET(@Suspended final AsyncResponse ar) { - server.yangLibraryVersionGET().addCallback(new FormattableBodyCallback(ar)); + server.yangLibraryVersionGET(emptyRequest).addCallback(new FormattableBodyCallback(ar, emptyRequest.format())); } // FIXME: References to these resources are generated by our yang-library implementation. That means: @@ -781,7 +798,7 @@ public final class JaxRsRestconf implements ParamConverterProvider { @Path("/" + URLConstants.MODULES_SUBPATH + "/{fileName : [^/]+}") public void modulesYangGET(@PathParam("fileName") final String fileName, @QueryParam("revision") final String revision, @Suspended final AsyncResponse ar) { - completeModulesGET(server.modulesYangGET(fileName, revision), ar); + completeModulesGET(server.modulesYangGET(emptyRequest, fileName, revision), ar); } /** @@ -798,7 +815,7 @@ public final class JaxRsRestconf implements ParamConverterProvider { public void modulesYangGET(@Encoded @PathParam("mountPath") final ApiPath mountPath, @PathParam("fileName") final String fileName, @QueryParam("revision") final String revision, @Suspended final AsyncResponse ar) { - completeModulesGET(server.modulesYangGET(mountPath, fileName, revision), ar); + completeModulesGET(server.modulesYangGET(emptyRequest, mountPath, fileName, revision), ar); } /** @@ -813,7 +830,7 @@ public final class JaxRsRestconf implements ParamConverterProvider { @Path("/" + URLConstants.MODULES_SUBPATH + "/{fileName : [^/]+}") public void modulesYinGET(@PathParam("fileName") final String fileName, @QueryParam("revision") final String revision, @Suspended final AsyncResponse ar) { - completeModulesGET(server.modulesYinGET(fileName, revision), ar); + completeModulesGET(server.modulesYinGET(emptyRequest, fileName, revision), ar); } /** @@ -830,7 +847,7 @@ public final class JaxRsRestconf implements ParamConverterProvider { public void modulesYinGET(@Encoded @PathParam("mountPath") final ApiPath mountPath, @PathParam("fileName") final String fileName, @QueryParam("revision") final String revision, @Suspended final AsyncResponse ar) { - completeModulesGET(server.modulesYinGET(mountPath, fileName, revision), ar); + completeModulesGET(server.modulesYinGET(emptyRequest, mountPath, fileName, revision), ar); } private static void completeModulesGET(final RestconfFuture future, final AsyncResponse ar) { diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/JsonFormattableBody.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/JsonJaxRsFormattableBodyWriter.java similarity index 70% rename from restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/JsonFormattableBody.java rename to restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/JsonJaxRsFormattableBodyWriter.java index 91884746a8..f012e0d013 100644 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/JsonFormattableBody.java +++ b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/JsonJaxRsFormattableBodyWriter.java @@ -12,14 +12,15 @@ 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.FormatParameters; import org.opendaylight.restconf.api.FormattableBody; import org.opendaylight.restconf.api.MediaTypes; @Provider @Produces({ MediaTypes.APPLICATION_YANG_DATA_JSON, MediaType.APPLICATION_JSON }) -public final class JsonFormattableBody extends FormattableBodyWriter { +public final class JsonJaxRsFormattableBodyWriter extends JaxRsFormattableBodyWriter { @Override - void writeTo(final FormattableBody body, final OutputStream out) throws IOException { - body.formatToJSON(out); + void writeTo(final FormattableBody body, final FormatParameters format, final OutputStream out) throws IOException { + body.formatToJSON(format, out); } } diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/XmlFormattableBody.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/XmlJaxRsFormattableBodyWriter.java similarity index 71% rename from restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/XmlFormattableBody.java rename to restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/XmlJaxRsFormattableBodyWriter.java index 055abe0aee..5f047a31d6 100644 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/XmlFormattableBody.java +++ b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/XmlJaxRsFormattableBodyWriter.java @@ -12,14 +12,15 @@ 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.FormatParameters; import org.opendaylight.restconf.api.FormattableBody; import org.opendaylight.restconf.api.MediaTypes; @Provider @Produces({ MediaTypes.APPLICATION_YANG_DATA_XML, MediaType.APPLICATION_XML, MediaType.TEXT_XML }) -public final class XmlFormattableBody extends FormattableBodyWriter { +public final class XmlJaxRsFormattableBodyWriter extends JaxRsFormattableBodyWriter { @Override - void writeTo(final FormattableBody body, final OutputStream out) throws IOException { - body.formatToXML(out); + void writeTo(final FormattableBody body, final FormatParameters format, final OutputStream out) throws IOException { + body.formatToXML(format, out); } } diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/Insert.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/Insert.java index 22e2272da5..bd550434b7 100644 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/Insert.java +++ b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/Insert.java @@ -17,10 +17,10 @@ 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.QueryParameters; import org.opendaylight.restconf.api.query.InsertParam; import org.opendaylight.restconf.api.query.PointParam; import org.opendaylight.restconf.server.api.DatabindContext; -import org.opendaylight.restconf.server.api.QueryParams; import org.opendaylight.restconf.server.spi.ApiPathNormalizer; import org.opendaylight.yangtools.concepts.Immutable; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument; @@ -58,7 +58,7 @@ public final class Insert implements Immutable { * @throws NullPointerException if any argument is {@code null} * @throws IllegalArgumentException if the parameters are invalid */ - public static @Nullable Insert of(final DatabindContext databind, final QueryParams params) { + public static @Nullable Insert of(final DatabindContext databind, final QueryParameters params) { InsertParam insert = null; PointParam point = null; diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/JaxRsNorthbound.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/JaxRsNorthbound.java index 41615f1010..af81a9dbe3 100644 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/JaxRsNorthbound.java +++ b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/JaxRsNorthbound.java @@ -21,8 +21,8 @@ import org.opendaylight.aaa.web.WebServer; 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.JsonFormattableBody; -import org.opendaylight.restconf.nb.jaxrs.XmlFormattableBody; +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; @@ -66,9 +66,9 @@ public final class JaxRsNorthbound implements AutoCloseable { @Override public Set getSingletons() { return Set.of( - new JsonFormattableBody(), new XmlFormattableBody(), + new JsonJaxRsFormattableBodyWriter(), new XmlJaxRsFormattableBodyWriter(), new RestconfDocumentedExceptionMapper(databindProvider), - new JaxRsRestconf(server)); + new JaxRsRestconf(server, servletFactory.prettyPrint())); } }).build()) .asyncSupported(true) diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/OSGiNorthbound.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/OSGiNorthbound.java index a77cf17a0b..3cdef59ba0 100644 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/OSGiNorthbound.java +++ b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/OSGiNorthbound.java @@ -10,6 +10,7 @@ package org.opendaylight.restconf.nb.rfc8040; import static java.util.Objects.requireNonNull; import java.util.Map; +import org.opendaylight.restconf.api.query.PrettyPrintParam; import org.opendaylight.restconf.nb.rfc8040.streams.DefaultPingExecutor; import org.opendaylight.restconf.nb.rfc8040.streams.DefaultRestconfStreamServletFactory; import org.opendaylight.restconf.nb.rfc8040.streams.StreamsConfiguration; @@ -60,6 +61,11 @@ public final class OSGiNorthbound { @AttributeDefinition(name = "{+restconf}", description = """ The value of RFC8040 {+restconf} URI template, pointing to the root resource. Must not end with '/'.""") String restconf() default "rests"; + + @AttributeDefinition( + name = "default pretty-print", + description = "Control the default value of the '" + PrettyPrintParam.uriName + "' query parameter.") + boolean pretty$_$print() default false; } private static final Logger LOG = LoggerFactory.getLogger(OSGiNorthbound.class); @@ -87,7 +93,7 @@ public final class OSGiNorthbound { registry = registryFactory.newInstance(FrameworkUtil.asDictionary(MdsalRestconfStreamRegistry.props(useSSE))); servletProps = DefaultRestconfStreamServletFactory.props(configuration.restconf(), registry.getInstance(), - useSSE, + PrettyPrintParam.of(configuration.pretty$_$print()), useSSE, new StreamsConfiguration(configuration.maximum$_$fragment$_$length(), configuration.idle$_$timeout(), configuration.heartbeat$_$interval()), configuration.ping$_$executor$_$name$_$prefix(), configuration.max$_$thread$_$count()); @@ -106,9 +112,8 @@ public final class OSGiNorthbound { MdsalRestconfStreamRegistry.props(useSSE))); LOG.debug("ListenersBroker restarted with {}", newUseSSE ? "SSE" : "Websockets"); } - final var newServletProps = DefaultRestconfStreamServletFactory.props(configuration.restconf(), - registry.getInstance(), useSSE, + registry.getInstance(), PrettyPrintParam.of(configuration.pretty$_$print()), useSSE, new StreamsConfiguration(configuration.maximum$_$fragment$_$length(), configuration.idle$_$timeout(), configuration.heartbeat$_$interval()), configuration.ping$_$executor$_$name$_$prefix(), configuration.max$_$thread$_$count()); diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/AbstractNormalizedNodeBodyWriter.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/AbstractNormalizedNodeBodyWriter.java index b94a575962..872be8f01d 100644 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/AbstractNormalizedNodeBodyWriter.java +++ b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/AbstractNormalizedNodeBodyWriter.java @@ -16,7 +16,8 @@ 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.eclipse.jdt.annotation.NonNullByDefault; +import org.opendaylight.restconf.api.FormatParameters; 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; @@ -33,10 +34,11 @@ abstract class AbstractNormalizedNodeBodyWriter implements MessageBodyWriter type, final Type genericType, final Annotation[] annotations, final MediaType mediaType, final MultivaluedMap httpHeaders, final OutputStream entityStream) throws IOException { - writeData(context.inference().toSchemaInferenceStack(), context.writerParameters(), context.data(), - requireNonNull(entityStream)); + writeData(context.inference().toSchemaInferenceStack(), context.data(), context.writerParameters(), + context.format(), requireNonNull(entityStream)); } - abstract void writeData(@NonNull SchemaInferenceStack stack, @NonNull WriterParameters writerParameters, - @NonNull NormalizedNode data, @NonNull OutputStream entityStream) throws IOException; + @NonNullByDefault + abstract void writeData(SchemaInferenceStack stack, NormalizedNode data, WriterParameters writerParameters, + FormatParameters format, OutputStream out) throws IOException; } diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/JsonNormalizedNodeBodyWriter.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/JsonNormalizedNodeBodyWriter.java index fa56013fe0..bcb65ee12c 100644 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/JsonNormalizedNodeBodyWriter.java +++ b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/JsonNormalizedNodeBodyWriter.java @@ -14,6 +14,7 @@ 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.FormatParameters; import org.opendaylight.restconf.api.MediaTypes; import org.opendaylight.restconf.nb.rfc8040.jersey.providers.api.RestconfNormalizedNodeWriter; import org.opendaylight.restconf.nb.rfc8040.legacy.WriterParameters; @@ -32,8 +33,8 @@ import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference @Produces({ MediaTypes.APPLICATION_YANG_DATA_JSON, MediaType.APPLICATION_JSON }) public final class JsonNormalizedNodeBodyWriter extends AbstractNormalizedNodeBodyWriter { @Override - void writeData(final SchemaInferenceStack stack, final WriterParameters writerParameters, final NormalizedNode data, - final OutputStream entityStream) throws IOException { + void writeData(final SchemaInferenceStack stack, final NormalizedNode data, final WriterParameters writerParameters, + final FormatParameters format, final OutputStream out) throws IOException { if (!stack.isEmpty()) { stack.exit(); } @@ -46,7 +47,7 @@ public final class JsonNormalizedNodeBodyWriter extends AbstractNormalizedNodeBo .build() : data; - try (var jsonWriter = FormattableBodySupport.createJsonWriter(entityStream, writerParameters)) { + try (var jsonWriter = FormattableBodySupport.createJsonWriter(out, format)) { jsonWriter.beginObject(); final var nnWriter = createNormalizedNodeWriter(stack.toInference(), jsonWriter, writerParameters, null); diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/XmlNormalizedNodeBodyWriter.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/XmlNormalizedNodeBodyWriter.java index f6fa2ef07b..40092e5637 100644 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/XmlNormalizedNodeBodyWriter.java +++ b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/XmlNormalizedNodeBodyWriter.java @@ -15,6 +15,7 @@ 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.FormatParameters; import org.opendaylight.restconf.api.MediaTypes; import org.opendaylight.restconf.nb.rfc8040.jersey.providers.api.RestconfNormalizedNodeWriter; import org.opendaylight.restconf.nb.rfc8040.legacy.WriterParameters; @@ -33,8 +34,8 @@ import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference @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 WriterParameters writerParameters, final NormalizedNode data, - final OutputStream entityStream) throws IOException { + void writeData(final SchemaInferenceStack stack, final NormalizedNode data, final WriterParameters writerParameters, + final FormatParameters format, final OutputStream out) throws IOException { final boolean isRoot; if (!stack.isEmpty()) { stack.exit(); @@ -43,7 +44,7 @@ public final class XmlNormalizedNodeBodyWriter extends AbstractNormalizedNodeBod isRoot = true; } - final var xmlWriter = FormattableBodySupport.createXmlWriter(entityStream, writerParameters); + final var xmlWriter = FormattableBodySupport.createXmlWriter(out, format); final var nnWriter = createNormalizedNodeWriter(xmlWriter, stack.toInference(), writerParameters); if (data instanceof MapEntryNode mapEntry) { // Restconf allows returning one list item. We need to wrap it diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/legacy/NormalizedNodePayload.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/legacy/NormalizedNodePayload.java index fc392fb758..c8c9d7221a 100644 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/legacy/NormalizedNodePayload.java +++ b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/legacy/NormalizedNodePayload.java @@ -10,6 +10,7 @@ package org.opendaylight.restconf.nb.rfc8040.legacy; import static java.util.Objects.requireNonNull; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.opendaylight.restconf.api.FormatParameters; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference; @@ -18,10 +19,15 @@ import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference * messy details needed to deal with the payload. */ @NonNullByDefault -public record NormalizedNodePayload(Inference inference, NormalizedNode data, WriterParameters writerParameters) { +public record NormalizedNodePayload( + Inference inference, + NormalizedNode data, + WriterParameters writerParameters, + FormatParameters format) { public NormalizedNodePayload { requireNonNull(inference); requireNonNull(data); requireNonNull(writerParameters); + requireNonNull(format); } } diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/legacy/WriterParameters.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/legacy/WriterParameters.java index 21876baa4b..3e8a8b7c89 100644 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/legacy/WriterParameters.java +++ b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/legacy/WriterParameters.java @@ -7,16 +7,12 @@ */ package org.opendaylight.restconf.nb.rfc8040.legacy; -import static java.util.Objects.requireNonNull; - 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.FormatParameters; import org.opendaylight.restconf.api.query.DepthParam; -import org.opendaylight.restconf.api.query.PrettyPrintParam; import org.opendaylight.yangtools.yang.common.QName; /** @@ -25,18 +21,10 @@ import org.opendaylight.yangtools.yang.common.QName; * needs to be processed (for example filtered). */ @Beta -public record WriterParameters( - @NonNull PrettyPrintParam prettyPrint, - @Nullable DepthParam depth, - @Nullable List> fields) implements FormatParameters { - public static final @NonNull WriterParameters EMPTY = new WriterParameters(PrettyPrintParam.FALSE, null, null); - - public WriterParameters { - requireNonNull(prettyPrint); - } +public record WriterParameters(@Nullable DepthParam depth, @Nullable List> fields) { + public static final @NonNull WriterParameters EMPTY = new WriterParameters(null, null); - public static @NonNull WriterParameters of(final @NonNull PrettyPrintParam prettyPrint, - final @Nullable DepthParam depth) { - return depth == null && !prettyPrint.value() ? EMPTY : new WriterParameters(prettyPrint, depth, null); + public static @NonNull WriterParameters of(final @Nullable DepthParam depth) { + return depth == null ? EMPTY : new WriterParameters(depth, null); } } diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/transactions/MdsalRestconfStrategy.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/transactions/MdsalRestconfStrategy.java index 6ee59fc26d..9c65daff08 100644 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/transactions/MdsalRestconfStrategy.java +++ b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/transactions/MdsalRestconfStrategy.java @@ -44,7 +44,7 @@ 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.QueryParams; +import org.opendaylight.restconf.server.api.ServerRequest; import org.opendaylight.restconf.server.spi.HttpGetResource; import org.opendaylight.restconf.server.spi.RpcImplementation; import org.opendaylight.restconf.server.spi.YangLibraryVersionResource; @@ -79,8 +79,8 @@ public final class MdsalRestconfStrategy extends RestconfStrategy { } @NonNullByDefault - public RestconfFuture yangLibraryVersionGET(final QueryParams params) { - return yangLibraryVersion.httpGET(params); + public RestconfFuture yangLibraryVersionGET(final ServerRequest request) { + return yangLibraryVersion.httpGET(request); } @Override @@ -89,7 +89,8 @@ public final class MdsalRestconfStrategy extends RestconfStrategy { } @Override - void delete(final SettableRestconfFuture future, final YangInstanceIdentifier path) { + void delete(final SettableRestconfFuture future, final ServerRequest request, + final YangInstanceIdentifier path) { final var tx = dataBroker.newReadWriteTransaction(); tx.exists(CONFIGURATION, path).addCallback(new FutureCallback<>() { @Override @@ -128,12 +129,12 @@ public final class MdsalRestconfStrategy extends RestconfStrategy { } @Override - RestconfFuture dataGET(final Data path, final DataGetParams params) { + RestconfFuture dataGET(final ServerRequest request, final Data path, final DataGetParams params) { final var inference = path.inference(); final var fields = params.fields(); - return completeDataGET(inference, - fields == null ? WriterParameters.of(params.prettyPrint(), params.depth()) - : new WriterParameters(params.prettyPrint(), params.depth(), + return completeDataGET(request.format(), 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); } diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/transactions/NetconfRestconfStrategy.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/transactions/NetconfRestconfStrategy.java index b3259b1d92..00adede4ea 100644 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/transactions/NetconfRestconfStrategy.java +++ b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/transactions/NetconfRestconfStrategy.java @@ -45,6 +45,7 @@ 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.yangtools.yang.common.Empty; import org.opendaylight.yangtools.yang.common.ErrorTag; import org.opendaylight.yangtools.yang.common.ErrorType; @@ -83,7 +84,8 @@ public final class NetconfRestconfStrategy extends RestconfStrategy { } @Override - void delete(final SettableRestconfFuture future, final YangInstanceIdentifier path) { + void delete(final SettableRestconfFuture future, final ServerRequest request, + final YangInstanceIdentifier path) { final var tx = prepareWriteExecution(); tx.delete(path); Futures.addCallback(tx.commit(), new FutureCallback() { @@ -100,7 +102,7 @@ public final class NetconfRestconfStrategy extends RestconfStrategy { } @Override - RestconfFuture dataGET(final Data path, final DataGetParams params) { + RestconfFuture dataGET(final ServerRequest request, final Data path, final DataGetParams params) { final var inference = path.inference(); final var fields = params.fields(); final List fieldPaths; @@ -123,7 +125,7 @@ public final class NetconfRestconfStrategy extends RestconfStrategy { node = readData(params.content(), path.instance(), params.withDefaults()); } - return completeDataGET(inference, WriterParameters.of(params.prettyPrint(), params.depth()), node, null); + return completeDataGET(request.format(), inference, WriterParameters.of(params.depth()), node, null); } @Override diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/transactions/RestconfStrategy.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/transactions/RestconfStrategy.java index d62fd8e18c..7cad275e02 100644 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/transactions/RestconfStrategy.java +++ b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/transactions/RestconfStrategy.java @@ -50,6 +50,7 @@ import org.opendaylight.mdsal.dom.api.DOMTransactionChain; import org.opendaylight.mdsal.dom.spi.SimpleDOMActionResult; import org.opendaylight.netconf.dom.api.NetconfDataTreeService; import org.opendaylight.restconf.api.ApiPath; +import org.opendaylight.restconf.api.FormatParameters; import org.opendaylight.restconf.api.FormattableBody; import org.opendaylight.restconf.api.query.ContentParam; import org.opendaylight.restconf.api.query.WithDefaultsParam; @@ -71,7 +72,6 @@ import org.opendaylight.restconf.server.api.DataPatchResult; import org.opendaylight.restconf.server.api.DataPostBody; import org.opendaylight.restconf.server.api.DataPostResult; import org.opendaylight.restconf.server.api.DataPutResult; -import org.opendaylight.restconf.server.api.DataYangPatchParams; import org.opendaylight.restconf.server.api.DataYangPatchResult; import org.opendaylight.restconf.server.api.DatabindContext; import org.opendaylight.restconf.server.api.DatabindPath; @@ -80,14 +80,13 @@ import org.opendaylight.restconf.server.api.DatabindPath.Data; import org.opendaylight.restconf.server.api.DatabindPath.InstanceReference; import org.opendaylight.restconf.server.api.DatabindPath.OperationPath; import org.opendaylight.restconf.server.api.DatabindPath.Rpc; -import org.opendaylight.restconf.server.api.InvokeParams; import org.opendaylight.restconf.server.api.InvokeResult; import org.opendaylight.restconf.server.api.OperationInputBody; import org.opendaylight.restconf.server.api.PatchBody; import org.opendaylight.restconf.server.api.PatchStatusContext; import org.opendaylight.restconf.server.api.PatchStatusEntity; -import org.opendaylight.restconf.server.api.QueryParams; import org.opendaylight.restconf.server.api.ResourceBody; +import org.opendaylight.restconf.server.api.ServerRequest; import org.opendaylight.restconf.server.spi.ApiPathCanonizer; import org.opendaylight.restconf.server.spi.ApiPathNormalizer; import org.opendaylight.restconf.server.spi.DefaultResourceContext; @@ -329,7 +328,7 @@ public abstract class RestconfStrategy { }, MoreExecutors.directExecutor()); } - public @NonNull RestconfFuture dataPUT(final ApiPath apiPath, final QueryParams params, + public @NonNull RestconfFuture dataPUT(final ServerRequest request, final ApiPath apiPath, final ResourceBody body) { final Data path; try { @@ -340,7 +339,7 @@ public abstract class RestconfStrategy { final Insert insert; try { - insert = Insert.of(databind, params); + insert = Insert.of(databind, request.queryParameters()); } catch (IllegalArgumentException e) { return RestconfFuture.failed(new RestconfDocumentedException(e.getMessage(), ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE, e)); @@ -594,16 +593,7 @@ public abstract class RestconfStrategy { return merge(path.instance(), data); } - public final @NonNull RestconfFuture dataPATCH(final ApiPath apiPath, final QueryParams params, - final PatchBody body) { - final DataYangPatchParams patchParams; - try { - patchParams = DataYangPatchParams.of(params); - } catch (IllegalArgumentException e) { - return RestconfFuture.failed(new RestconfDocumentedException(e.getMessage(), - ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE, e)); - } - + public final @NonNull RestconfFuture dataPATCH(final ApiPath apiPath, final PatchBody body) { final Data path; try { path = pathNormalizer.normalizeDataPath(apiPath); @@ -619,7 +609,7 @@ public abstract class RestconfStrategy { return RestconfFuture.failed(new RestconfDocumentedException("Error parsing input: " + e.getMessage(), ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE, e)); } - return patchData(patchParams, patch); + return patchData(patch); } /** @@ -629,8 +619,7 @@ public abstract class RestconfStrategy { * @return {@link PatchStatusContext} */ @VisibleForTesting - public final @NonNull RestconfFuture patchData(final DataYangPatchParams params, - final PatchContext patch) { + public final @NonNull RestconfFuture patchData(final PatchContext patch) { final var editCollection = new ArrayList(); final var tx = prepareWriteExecution(); @@ -703,7 +692,7 @@ public abstract class RestconfStrategy { // We have errors if (!noError) { tx.cancel(); - ret.set(new DataYangPatchResult(params, + ret.set(new DataYangPatchResult( new PatchStatusContext(databind(), patch.patchId(), List.copyOf(editCollection), false, null))); return ret; } @@ -711,14 +700,14 @@ public abstract class RestconfStrategy { Futures.addCallback(tx.commit(), new FutureCallback() { @Override public void onSuccess(final CommitInfo result) { - ret.set(new DataYangPatchResult(params, + ret.set(new DataYangPatchResult( new PatchStatusContext(databind(), patch.patchId(), List.copyOf(editCollection), true, null))); } @Override public void onFailure(final Throwable cause) { // if errors occurred during transaction commit then patch failed and global errors are reported - ret.set(new DataYangPatchResult(params, + ret.set(new DataYangPatchResult( new PatchStatusContext(databind(), patch.patchId(), List.copyOf(editCollection), false, TransactionUtil.decodeException(cause, "PATCH", null, modelContext()).getErrors()))); } @@ -814,8 +803,9 @@ public abstract class RestconfStrategy { * @return A {@link RestconfFuture} * @throws NullPointerException if {@code apiPath} is {@code null} */ + @NonNullByDefault @SuppressWarnings("checkstyle:abbreviationAsWordInName") - public final @NonNull RestconfFuture dataDELETE(final ApiPath apiPath) { + public final RestconfFuture dataDELETE(final ServerRequest request, final ApiPath apiPath) { final Data path; try { path = pathNormalizer.normalizeDataPath(apiPath); @@ -825,13 +815,14 @@ public abstract class RestconfStrategy { // FIXME: reject empty YangInstanceIdentifier, as datastores may not be deleted final var ret = new SettableRestconfFuture(); - delete(ret, path.instance()); + delete(ret, request, path.instance()); return ret; } - abstract void delete(@NonNull SettableRestconfFuture future, @NonNull YangInstanceIdentifier path); + @NonNullByDefault + abstract void delete(SettableRestconfFuture future, ServerRequest request, YangInstanceIdentifier path); - public final @NonNull RestconfFuture dataGET(final ApiPath apiPath, final QueryParams params) { + public final @NonNull RestconfFuture dataGET(final ServerRequest request, final ApiPath apiPath) { final Data path; try { path = pathNormalizer.normalizeDataPath(apiPath); @@ -841,19 +832,19 @@ public abstract class RestconfStrategy { final DataGetParams getParams; try { - getParams = DataGetParams.of(params); + getParams = DataGetParams.of(request.queryParameters()); } catch (IllegalArgumentException e) { return RestconfFuture.failed(new RestconfDocumentedException(e, new RestconfError(ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE, "Invalid GET /data parameters", null, e.getMessage()))); } - return dataGET(path, getParams); + return dataGET(request, path, getParams); } - abstract @NonNull RestconfFuture dataGET(Data path, DataGetParams params); + abstract @NonNull RestconfFuture dataGET(ServerRequest request, Data path, DataGetParams params); - static final @NonNull RestconfFuture completeDataGET(final Inference inference, - final WriterParameters writerParams, final @Nullable NormalizedNode node, + static final @NonNull RestconfFuture completeDataGET(final FormatParameters format, + final Inference inference, final WriterParameters writerParams, final @Nullable NormalizedNode node, final @Nullable ConfigurationMetadata metadata) { if (node == null) { return RestconfFuture.failed(new RestconfDocumentedException( @@ -861,7 +852,7 @@ public abstract class RestconfStrategy { ErrorType.PROTOCOL, ErrorTag.DATA_MISSING)); } - final var payload = new NormalizedNodePayload(inference, node, writerParams); + final var payload = new NormalizedNodePayload(inference, node, writerParams, format); return RestconfFuture.of(metadata == null ? new DataGetResult(payload) : new DataGetResult(payload, metadata.entityTag(), metadata.lastModified())); } @@ -1257,17 +1248,17 @@ public abstract class RestconfStrategy { } @NonNullByDefault - public RestconfFuture operationsGET(final QueryParams params) { - return operations.httpGET(params); + public RestconfFuture operationsGET(final ServerRequest request) { + return operations.httpGET(request); } @NonNullByDefault - public RestconfFuture operationsGET(final ApiPath apiPath, final QueryParams params) { - return operations.httpGET(apiPath, params); + public RestconfFuture operationsGET(final ServerRequest request, final ApiPath apiPath) { + return operations.httpGET(request, apiPath); } - public @NonNull RestconfFuture operationsPOST(final URI restconfURI, final ApiPath apiPath, - final QueryParams params, final OperationInputBody body) { + public @NonNull RestconfFuture operationsPOST(final ServerRequest request, final URI restconfURI, + final ApiPath apiPath, final OperationInputBody body) { final Rpc path; try { path = pathNormalizer.normalizeRpcPath(apiPath); @@ -1275,14 +1266,6 @@ public abstract class RestconfStrategy { return RestconfFuture.failed(e); } - final InvokeParams invokeParams; - try { - invokeParams = InvokeParams.of(params); - } catch (IllegalArgumentException e) { - return RestconfFuture.failed(new RestconfDocumentedException(e.getMessage(), - ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE, e)); - } - final ContainerNode data; try { data = body.toContainerNode(path); @@ -1296,7 +1279,7 @@ public abstract class RestconfStrategy { final var local = localRpcs.get(type); if (local != null) { return local.invoke(restconfURI, new OperationInput(path, data)) - .transform(output -> outputToInvokeResult(path, invokeParams, output)); + .transform(output -> outputToInvokeResult(path, output)); } if (rpcService == null) { LOG.debug("RPC invocation is not available"); @@ -1310,7 +1293,7 @@ public abstract class RestconfStrategy { public void onSuccess(final DOMRpcResult response) { final var errors = response.errors(); if (errors.isEmpty()) { - ret.set(outputToInvokeResult(path, invokeParams, response.value())); + ret.set(outputToInvokeResult(path, response.value())); } else { LOG.debug("RPC invocation reported {}", response.errors()); ret.setFailure(new RestconfDocumentedException("RPC implementation reported errors", null, @@ -1334,9 +1317,9 @@ public abstract class RestconfStrategy { } private static @NonNull InvokeResult outputToInvokeResult(final @NonNull OperationPath path, - final @NonNull InvokeParams params, final @Nullable ContainerNode value) { + final @Nullable ContainerNode value) { return value == null || value.isEmpty() ? InvokeResult.EMPTY - : new InvokeResult(new OperationOutputBody(params, path, value)); + : new InvokeResult(new OperationOutputBody(path, value)); } public @NonNull RestconfFuture resolveSource(final SourceIdentifier source, @@ -1397,10 +1380,10 @@ public abstract class RestconfStrategy { ErrorType.APPLICATION, ErrorTag.DATA_MISSING)); } - public final @NonNull RestconfFuture dataPOST(final ApiPath apiPath, - final QueryParams params, final DataPostBody body) { + public final @NonNull RestconfFuture dataPOST(final ServerRequest request, + final ApiPath apiPath, final DataPostBody body) { if (apiPath.isEmpty()) { - return dataCreatePOST(params, body.toResource()); + return dataCreatePOST(request, body.toResource()); } final InstanceReference path; try { @@ -1410,12 +1393,12 @@ public abstract class RestconfStrategy { } if (path instanceof Data dataPath) { try (var resourceBody = body.toResource()) { - return dataCreatePOST(dataPath, params, resourceBody); + return dataCreatePOST(request, dataPath, resourceBody); } } if (path instanceof Action actionPath) { try (var inputBody = body.toOperationInput()) { - return dataInvokePOST(actionPath, params, inputBody); + return dataInvokePOST(actionPath, inputBody); } } // Note: this should never happen @@ -1423,16 +1406,16 @@ public abstract class RestconfStrategy { return RestconfFuture.failed(new RestconfDocumentedException("Unhandled path " + path)); } - public @NonNull RestconfFuture dataCreatePOST(final QueryParams params, + public @NonNull RestconfFuture dataCreatePOST(final ServerRequest request, final ChildBody body) { - return dataCreatePOST(new DatabindPath.Data(databind), params, body); + return dataCreatePOST(request, new DatabindPath.Data(databind), body); } - private @NonNull RestconfFuture dataCreatePOST(final DatabindPath.Data path, - final QueryParams params, final ChildBody body) { + private @NonNull RestconfFuture dataCreatePOST(final ServerRequest request, + final DatabindPath.Data path, final ChildBody body) { final Insert insert; try { - insert = Insert.of(path.databind(), params); + insert = Insert.of(path.databind(), request.queryParameters()); } catch (IllegalArgumentException e) { return RestconfFuture.failed(new RestconfDocumentedException(e.getMessage(), ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE, e)); @@ -1451,15 +1434,7 @@ public abstract class RestconfStrategy { } private @NonNull RestconfFuture dataInvokePOST(final @NonNull Action path, - final @NonNull QueryParams params, final @NonNull OperationInputBody body) { - final InvokeParams invokeParams; - try { - invokeParams = InvokeParams.of(params); - } catch (IllegalArgumentException e) { - return RestconfFuture.failed(new RestconfDocumentedException(e.getMessage(), - ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE, e)); - } - + final @NonNull OperationInputBody body) { final ContainerNode input; try { input = body.toContainerNode(path); @@ -1474,7 +1449,7 @@ public abstract class RestconfStrategy { } return dataInvokePOST(actionService, path, input) - .transform(result -> outputToInvokeResult(path, invokeParams, result.getOutput().orElse(null))); + .transform(result -> outputToInvokeResult(path, result.getOutput().orElse(null))); } /** diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/streams/DefaultRestconfStreamServletFactory.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/streams/DefaultRestconfStreamServletFactory.java index a909a76cc4..5c02131a36 100644 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/streams/DefaultRestconfStreamServletFactory.java +++ b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/streams/DefaultRestconfStreamServletFactory.java @@ -15,6 +15,7 @@ import javax.servlet.http.HttpServlet; import javax.ws.rs.core.Application; import org.eclipse.jdt.annotation.NonNull; import org.opendaylight.aaa.web.servlet.ServletSupport; +import org.opendaylight.restconf.api.query.PrettyPrintParam; import org.opendaylight.restconf.server.spi.RestconfStream; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; @@ -43,8 +44,10 @@ public final class DefaultRestconfStreamServletFactory implements RestconfStream private static final String PROP_USE_WEBSOCKETS = ".useWebsockets"; private static final String PROP_STREAMS_CONFIGURATION = ".streamsConfiguration"; private static final String PROP_RESTCONF = ".restconf"; + private static final String PROP_PRETTY_PRINT = ".prettyPrint"; private final @NonNull String restconf; + private final @NonNull PrettyPrintParam prettyPrint; private final RestconfStream.Registry streamRegistry; private final ServletSupport servletSupport; @@ -54,7 +57,8 @@ public final class DefaultRestconfStreamServletFactory implements RestconfStream public DefaultRestconfStreamServletFactory(final ServletSupport servletSupport, final String restconf, final RestconfStream.Registry streamRegistry, final StreamsConfiguration streamsConfiguration, - final String namePrefix, final int corePoolSize, final boolean useWebsockets) { + final PrettyPrintParam prettyPrint, final String namePrefix, final int corePoolSize, + final boolean useWebsockets) { this.servletSupport = requireNonNull(servletSupport); this.restconf = requireNonNull(restconf); if (restconf.endsWith("/")) { @@ -62,6 +66,7 @@ public final class DefaultRestconfStreamServletFactory implements RestconfStream } this.streamRegistry = requireNonNull(streamRegistry); this.streamsConfiguration = requireNonNull(streamsConfiguration); + this.prettyPrint = requireNonNull(prettyPrint); pingExecutor = new DefaultPingExecutor(namePrefix, corePoolSize); this.useWebsockets = useWebsockets; if (useWebsockets) { @@ -77,6 +82,7 @@ public final class DefaultRestconfStreamServletFactory implements RestconfStream this(servletSupport, (String) props.get(PROP_RESTCONF), (RestconfStream.Registry) props.get(PROP_STREAM_REGISTRY), (StreamsConfiguration) props.get(PROP_STREAMS_CONFIGURATION), + (PrettyPrintParam) props.get(PROP_PRETTY_PRINT), (String) props.get(PROP_NAME_PREFIX), (int) requireNonNull(props.get(PROP_CORE_POOL_SIZE)), (boolean) requireNonNull(props.get(PROP_USE_WEBSOCKETS))); } @@ -98,6 +104,11 @@ public final class DefaultRestconfStreamServletFactory implements RestconfStream }).build(); } + @Override + public PrettyPrintParam prettyPrint() { + return prettyPrint; + } + @Override @Deactivate public void close() { @@ -105,11 +116,12 @@ public final class DefaultRestconfStreamServletFactory implements RestconfStream } public static Map props(final String restconf, final RestconfStream.Registry streamRegistry, - final boolean useSSE, final StreamsConfiguration streamsConfiguration, final String namePrefix, - final int corePoolSize) { + final PrettyPrintParam prettyPrint, final boolean useSSE, final StreamsConfiguration streamsConfiguration, + final String namePrefix, final int corePoolSize) { return Map.of( PROP_RESTCONF, restconf, PROP_STREAM_REGISTRY, streamRegistry, + PROP_PRETTY_PRINT, prettyPrint, PROP_USE_WEBSOCKETS, !useSSE, PROP_STREAMS_CONFIGURATION, streamsConfiguration, PROP_NAME_PREFIX, namePrefix, diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/streams/RestconfStreamServletFactory.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/streams/RestconfStreamServletFactory.java index 14848ad48c..0ec164a1a3 100644 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/streams/RestconfStreamServletFactory.java +++ b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/streams/RestconfStreamServletFactory.java @@ -9,6 +9,7 @@ package org.opendaylight.restconf.nb.rfc8040.streams; import javax.servlet.http.HttpServlet; import org.eclipse.jdt.annotation.NonNull; +import org.opendaylight.restconf.api.query.PrettyPrintParam; import org.opendaylight.restconf.server.spi.RestconfStream; /** @@ -27,4 +28,6 @@ public interface RestconfStreamServletFactory { @NonNull String restconf(); @NonNull HttpServlet newStreamServlet(); + + @NonNull PrettyPrintParam prettyPrint(); } diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/streams/SSEStreamService.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/streams/SSEStreamService.java index de1cc95b4a..0fc05b824f 100644 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/streams/SSEStreamService.java +++ b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/streams/SSEStreamService.java @@ -23,9 +23,7 @@ import javax.ws.rs.sse.Sse; import javax.ws.rs.sse.SseEventSink; import javax.xml.xpath.XPathExpressionException; import org.opendaylight.restconf.api.QueryParameters; -import org.opendaylight.restconf.api.query.PrettyPrintParam; import org.opendaylight.restconf.server.api.EventStreamGetParams; -import org.opendaylight.restconf.server.api.QueryParams; import org.opendaylight.restconf.server.spi.RestconfStream; import org.opendaylight.restconf.server.spi.RestconfStream.EncodingName; import org.slf4j.Logger; @@ -71,9 +69,7 @@ final class SSEStreamService { final EventStreamGetParams getParams; try { - getParams = EventStreamGetParams.of(new QueryParams( - // FIXME: figure this out - QueryParameters.ofMultiValue(uriInfo.getQueryParameters()), PrettyPrintParam.FALSE)); + getParams = EventStreamGetParams.of(QueryParameters.ofMultiValue(uriInfo.getQueryParameters())); } catch (IllegalArgumentException e) { throw new BadRequestException(e.getMessage(), e); } diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/DataGetParams.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/DataGetParams.java index d32cab3a46..ee7994af6e 100644 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/DataGetParams.java +++ b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/DataGetParams.java @@ -9,14 +9,12 @@ package org.opendaylight.restconf.server.api; import static java.util.Objects.requireNonNull; -import java.util.function.Function; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; -import org.opendaylight.restconf.api.FormatParameters; +import org.opendaylight.restconf.api.QueryParameters; import org.opendaylight.restconf.api.query.ContentParam; import org.opendaylight.restconf.api.query.DepthParam; import org.opendaylight.restconf.api.query.FieldsParam; -import org.opendaylight.restconf.api.query.PrettyPrintParam; import org.opendaylight.restconf.api.query.WithDefaultsParam; /** @@ -27,14 +25,11 @@ public record DataGetParams( @NonNull ContentParam content, @Nullable DepthParam depth, @Nullable FieldsParam fields, - @Nullable WithDefaultsParam withDefaults, - @NonNull PrettyPrintParam prettyPrint) implements FormatParameters { - public static final @NonNull DataGetParams EMPTY = - new DataGetParams(ContentParam.ALL, null, null, null, PrettyPrintParam.FALSE); + @Nullable WithDefaultsParam withDefaults) { + public static final @NonNull DataGetParams EMPTY = new DataGetParams(ContentParam.ALL, null, null, null); public DataGetParams { requireNonNull(content); - requireNonNull(prettyPrint); } /** @@ -45,12 +40,11 @@ public record DataGetParams( * @throws NullPointerException if {@code queryParameters} is {@code null} * @throws IllegalArgumentException if the parameters are invalid */ - public static @NonNull DataGetParams of(final QueryParams params) { + public static @NonNull DataGetParams of(final QueryParameters params) { ContentParam content = ContentParam.ALL; DepthParam depth = null; FieldsParam fields = null; WithDefaultsParam withDefaults = null; - PrettyPrintParam prettyPrint = params.prettyPrint(); for (var entry : params.asCollection()) { final var name = entry.getKey(); @@ -58,34 +52,22 @@ public record DataGetParams( switch (name) { case ContentParam.uriName: - content = parseParam(ContentParam::forUriValue, name, value); + content = ContentParam.forUriValue(value); break; case DepthParam.uriName: depth = DepthParam.forUriValue(value); break; case FieldsParam.uriName: - fields = parseParam(FieldsParam::forUriValue, name, value); + fields = FieldsParam.forUriValue(value); break; case WithDefaultsParam.uriName: - withDefaults = parseParam(WithDefaultsParam::forUriValue, name, value); - break; - case PrettyPrintParam.uriName: - prettyPrint = parseParam(PrettyPrintParam::forUriValue, name, value); + withDefaults = WithDefaultsParam.forUriValue(value); break; default: throw new IllegalArgumentException("Unknown parameter in /data GET: " + name); } } - return new DataGetParams(content, depth, fields, withDefaults, prettyPrint); - } - - private static @NonNull T parseParam(final Function<@NonNull String, @NonNull T> method, final String name, - final @NonNull String value) { - try { - return method.apply(value); - } catch (IllegalArgumentException e) { - throw new IllegalArgumentException("Invalid " + name + " value: " + e.getMessage(), e); - } + return new DataGetParams(content, depth, fields, withDefaults); } } diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/DataYangPatchParams.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/DataYangPatchParams.java deleted file mode 100644 index b94e92a4d4..0000000000 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/DataYangPatchParams.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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 org.eclipse.jdt.annotation.NonNull; -import org.opendaylight.restconf.api.FormatParameters; -import org.opendaylight.restconf.api.query.PrettyPrintParam; - -/** - * Supported query parameters of {@code PATCH} HTTP operation when the request is to invoke a YANG Patch operation. - * There is no such thing in RFC8073, but we support pretty-printing of the resulting {@code yang-patch-status}. - */ -public record DataYangPatchParams(@NonNull PrettyPrintParam prettyPrint) implements FormatParameters { - public static final @NonNull DataYangPatchParams COMPACT = new DataYangPatchParams(PrettyPrintParam.FALSE); - public static final @NonNull DataYangPatchParams PRETTY = new DataYangPatchParams(PrettyPrintParam.TRUE); - - public DataYangPatchParams { - requireNonNull(prettyPrint); - } - - /** - * Return {@link DataYangPatchParams} for specified query parameters. - * - * @param params Parameters and their values - * @return A {@link DataYangPatchParams} - * @throws NullPointerException if {@code queryParameters} is {@code null} - * @throws IllegalArgumentException if the parameters are invalid - */ - public static @NonNull DataYangPatchParams of(final QueryParams params) { - return FormatParametersHelper.of(params, COMPACT, PRETTY); - } -} diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/DataYangPatchResult.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/DataYangPatchResult.java index 513d5c9ca2..13d1543f72 100644 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/DataYangPatchResult.java +++ b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/DataYangPatchResult.java @@ -17,13 +17,11 @@ import org.eclipse.jdt.annotation.Nullable; * Result of a {@code PATCH} request as defined in * RFC8072, section 2. * - * @param params A {@link DataYangPatchParams} * @param status A {@link PatchStatusContext} * @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 DataYangPatchResult( - @NonNull DataYangPatchParams params, @NonNull PatchStatusContext status, @Nullable EntityTag entityTag, @Nullable Instant lastModified) implements ConfigurationMetadata { @@ -31,7 +29,7 @@ public record DataYangPatchResult( requireNonNull(status); } - public DataYangPatchResult(final @NonNull DataYangPatchParams params, final @NonNull PatchStatusContext status) { - this(params, status, null, null); + public DataYangPatchResult(final @NonNull PatchStatusContext status) { + this(status, null, null); } } diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/DatabindFormattableBody.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/DatabindFormattableBody.java index 71daa7645e..d1aaf2d090 100644 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/DatabindFormattableBody.java +++ b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/DatabindFormattableBody.java @@ -19,32 +19,26 @@ import org.opendaylight.restconf.api.FormattableBody; * A {@link FormattableBody} which has an attached {@link DatabindContext}. */ @NonNullByDefault -public abstract class DatabindFormattableBody extends FormattableBody implements DatabindAware { +public abstract class DatabindFormattableBody extends FormattableBody { private final DatabindContext databind; - protected DatabindFormattableBody(final FormatParameters format, final DatabindContext databind) { - super(format); + protected DatabindFormattableBody(final DatabindContext databind) { this.databind = requireNonNull(databind); } @Override - public final DatabindContext databind() { - return databind; + public final void formatToJSON(final FormatParameters format, final OutputStream out) throws IOException { + formatToJSON(databind, format, out); } - @Override - protected final void formatToJSON(final OutputStream out, final FormatParameters format) throws IOException { - formatToJSON(out, format, databind()); - } - - protected abstract void formatToJSON(OutputStream out, FormatParameters format, DatabindContext databind) + protected abstract void formatToJSON(DatabindContext databind, FormatParameters format, OutputStream out) throws IOException; @Override - protected final void formatToXML(final OutputStream out, final FormatParameters format) throws IOException { - formatToXML(out, format, databind()); + public final void formatToXML(final FormatParameters format, final OutputStream out) throws IOException { + formatToXML(databind, format, out); } - protected abstract void formatToXML(OutputStream out, FormatParameters format, DatabindContext databind) + protected abstract void formatToXML(DatabindContext databind, FormatParameters format, OutputStream out) throws IOException; } diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/DatabindPathFormattableBody.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/DatabindPathFormattableBody.java index 2a09b34f61..4b113505c8 100644 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/DatabindPathFormattableBody.java +++ b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/DatabindPathFormattableBody.java @@ -13,7 +13,6 @@ import com.google.common.base.MoreObjects.ToStringHelper; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.opendaylight.restconf.api.FormatParameters; import org.opendaylight.restconf.api.FormattableBody; /** @@ -23,8 +22,7 @@ import org.opendaylight.restconf.api.FormattableBody; public abstract class DatabindPathFormattableBody

extends FormattableBody { private final @NonNull P path; - protected DatabindPathFormattableBody(final FormatParameters format, final P path) { - super(format); + protected DatabindPathFormattableBody(final P path) { this.path = requireNonNull(path); } @@ -34,7 +32,7 @@ public abstract class DatabindPathFormattableBody

extend @Override protected ToStringHelper addToStringAttributes(final ToStringHelper helper) { - return super.addToStringAttributes(helper.add("path", path).add("body", bodyAttribute())); + return helper.add("path", path).add("body", bodyAttribute()); } protected abstract @Nullable Object bodyAttribute(); diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/EventStreamGetParams.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/EventStreamGetParams.java index 21795c9be9..2f10ca3848 100644 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/EventStreamGetParams.java +++ b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/EventStreamGetParams.java @@ -13,6 +13,7 @@ import static java.util.Objects.requireNonNull; import com.google.common.base.MoreObjects; import java.util.function.Function; import org.eclipse.jdt.annotation.NonNull; +import org.opendaylight.restconf.api.QueryParameters; import org.opendaylight.restconf.api.query.ChangedLeafNodesOnlyParam; import org.opendaylight.restconf.api.query.ChildNodesOnlyParam; import org.opendaylight.restconf.api.query.FilterParam; @@ -53,12 +54,12 @@ public record EventStreamGetParams( /** * Return {@link EventStreamGetParams} for specified query parameters. * - * @param parameters Parameters and their values + * @param parames Parameters and their values * @return A {@link EventStreamGetParams} * @throws NullPointerException if {@code queryParameters} is {@code null} * @throws IllegalArgumentException if the parameters are invalid */ - public static @NonNull EventStreamGetParams of(final QueryParams parameters) { + public static @NonNull EventStreamGetParams of(final QueryParameters parames) { StartTimeParam startTime = null; StopTimeParam stopTime = null; FilterParam filter = null; @@ -67,7 +68,7 @@ public record EventStreamGetParams( ChangedLeafNodesOnlyParam changedLeafNodesOnly = null; ChildNodesOnlyParam childNodesOnly = null; - for (var entry : parameters.asCollection()) { + for (var entry : parames.asCollection()) { final var paramName = entry.getKey(); final var paramValue = entry.getValue(); diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/FormatParametersHelper.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/FormatParametersHelper.java deleted file mode 100644 index 1cdd567fec..0000000000 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/FormatParametersHelper.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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 org.eclipse.jdt.annotation.NonNull; -import org.opendaylight.restconf.api.FormatParameters; -import org.opendaylight.restconf.api.query.PrettyPrintParam; - -/** - * Helper utilities for structures which contain plain {@link FormatParameters}. - */ -final class FormatParametersHelper { - private FormatParametersHelper() { - // Hidden on purpose - } - - static @NonNull T of(final QueryParams params, final @NonNull T compact, - final @NonNull T pretty) { - var prettyPrint = params.prettyPrint(); - for (var entry : params.asCollection()) { - final var paramName = entry.getKey(); - - prettyPrint = switch (paramName) { - case PrettyPrintParam.uriName -> EventStreamGetParams.mandatoryParam(PrettyPrintParam::forUriValue, - paramName, entry.getValue()); - default -> throw new IllegalArgumentException("Invalid parameter: " + paramName); - }; - } - return prettyPrint.value() ? pretty : compact; - } -} diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/InvokeParams.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/InvokeParams.java deleted file mode 100644 index 012b291367..0000000000 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/InvokeParams.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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.server.api; - -import static java.util.Objects.requireNonNull; - -import org.eclipse.jdt.annotation.NonNull; -import org.opendaylight.restconf.api.FormatParameters; -import org.opendaylight.restconf.api.query.PrettyPrintParam; - -/** - * Supported query parameters of {@code POST} HTTP operation when the request is to invoke a YANG operation, be it - * {@code rpc} or {@code action}. There is no such thing in RFC8040, but we support pretty-printing of the resulting - * {@code output} container. - */ -public record InvokeParams(@NonNull PrettyPrintParam prettyPrint) implements FormatParameters { - public static final @NonNull InvokeParams COMPACT = new InvokeParams(PrettyPrintParam.FALSE); - public static final @NonNull InvokeParams PRETTY = new InvokeParams(PrettyPrintParam.TRUE); - - public InvokeParams { - requireNonNull(prettyPrint); - } - - /** - * Return {@link InvokeParams} for specified query parameters. - * - * @param params Parameters and their values - * @return A {@link InvokeParams} - * @throws NullPointerException if {@code queryParameters} is {@code null} - * @throws IllegalArgumentException if the parameters are invalid - */ - public static @NonNull InvokeParams of(final QueryParams params) { - return FormatParametersHelper.of(params, COMPACT, PRETTY); - } -} diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/ModulesGetResult.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/ModulesGetResult.java index 941b442e56..d51d9800df 100644 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/ModulesGetResult.java +++ b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/ModulesGetResult.java @@ -13,7 +13,7 @@ import com.google.common.io.CharSource; import org.eclipse.jdt.annotation.NonNullByDefault; /** - * Result of an {@link RestconfServer#modulesYangGET(String, String)} invocation. + * Result of an {@link RestconfServer#modulesYangGET(ServerRequest, String, String)} invocation. * * @param source A {@link CharSource} containing the body */ diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/QueryParams.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/QueryParams.java deleted file mode 100644 index 9ba0f37d5e..0000000000 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/QueryParams.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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 java.util.Collection; -import java.util.Map.Entry; -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.opendaylight.restconf.api.QueryParameters; -import org.opendaylight.restconf.api.query.PrettyPrintParam; - -/** - * A {@link QueryParameters} implementation which is congnizant of a default {@link PrettyPrintParam}. - */ -@NonNullByDefault -public record QueryParams(QueryParameters delegate, PrettyPrintParam prettyPrint) implements QueryParameters { - public QueryParams { - requireNonNull(delegate); - requireNonNull(prettyPrint); - } - - @Override - public boolean isEmpty() { - return delegate.isEmpty(); - } - - @Override - public Collection> asCollection() { - return delegate.asCollection(); - } - - @Override - public @Nullable String lookup(final String paramName) { - final var ret = delegate.lookup(paramName); - if (ret != null) { - return ret; - } - return PrettyPrintParam.uriName.equals(paramName) ? prettyPrint.paramValue() : null; - } -} diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/RestconfServer.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/RestconfServer.java index 0586fd20e5..c4d637d95a 100644 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/RestconfServer.java +++ b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/RestconfServer.java @@ -12,7 +12,6 @@ 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.QueryParameters; import org.opendaylight.restconf.common.errors.RestconfFuture; import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload; import org.opendaylight.yangtools.yang.common.Empty; @@ -30,107 +29,110 @@ public interface RestconfServer { * @return A {@link RestconfFuture} of the operation */ @SuppressWarnings("checkstyle:abbreviationAsWordInName") - RestconfFuture dataDELETE(ApiPath identifier); + RestconfFuture dataDELETE(ServerRequest request, ApiPath identifier); /** * Return the content of the datastore. * - * @param params {@link DataGetParams} for this request + * @param request {@link ServerRequest} for this request * @return A {@link RestconfFuture} of the {@link DataGetResult} content */ - RestconfFuture dataGET(QueryParameters params); + RestconfFuture dataGET(ServerRequest request); /** * Return the content of a data resource. * + * @param request {@link ServerRequest} for this request * @param identifier resource identifier - * @param params {@link DataGetParams} for this request * @return A {@link RestconfFuture} of the {@link DataGetResult} content */ - RestconfFuture dataGET(ApiPath identifier, QueryParameters params); + RestconfFuture dataGET(ServerRequest request, ApiPath identifier); /** * Partially modify the target data resource, as defined in * RFC8040, section 4.6.1. * + * @param request {@link ServerRequest} for this request * @param body data node for put to config DS * @return A {@link RestconfFuture} of the operation */ - RestconfFuture dataPATCH(ResourceBody body); + RestconfFuture dataPATCH(ServerRequest request, ResourceBody body); /** * Partially modify the target data resource, as defined in * RFC8040, section 4.6.1. * + * @param request {@link ServerRequest} for this request * @param identifier resource identifier * @param body data node for put to config DS * @return A {@link RestconfFuture} of the operation */ - RestconfFuture dataPATCH(ApiPath identifier, ResourceBody body); + RestconfFuture dataPATCH(ServerRequest request, ApiPath identifier, ResourceBody body); /** * Ordered list of edits that are applied to the datastore by the server, as defined in * RFC8072, section 2. * - * @param params query parameters + * @param request {@link ServerRequest} for this request * @param body YANG Patch body * @return A {@link RestconfFuture} of the {@link DataYangPatchResult} content */ - RestconfFuture dataPATCH(QueryParameters params, PatchBody body); + RestconfFuture dataPATCH(ServerRequest request, PatchBody body); /** * Ordered list of edits that are applied to the datastore by the server, as defined in * RFC8072, section 2. * + * @param request {@link ServerRequest} for this request * @param identifier path to target - * @param params query parameters * @param body YANG Patch body * @return A {@link RestconfFuture} of the {@link DataYangPatchResult} content */ - RestconfFuture dataPATCH(ApiPath identifier, QueryParameters params, PatchBody body); + RestconfFuture dataPATCH(ServerRequest request, ApiPath identifier, PatchBody body); - RestconfFuture dataPOST(QueryParameters params, ChildBody body); + RestconfFuture dataPOST(ServerRequest request, ChildBody body); /** * Create or invoke a operation, as described in * RFC8040 section 4.4. * + * @param request {@link ServerRequest} for this request * @param identifier path to target - * @param params query parameters * @param body body of the post request */ - RestconfFuture dataPOST(ApiPath identifier, QueryParameters params, DataPostBody body); + RestconfFuture dataPOST(ServerRequest request, ApiPath identifier, DataPostBody body); /** * Replace the data store, as described in * RFC8040 section 4.5. * + * @param request {@link ServerRequest} for this request * @param body data node for put to config DS - * @param params query parameters * @return A {@link RestconfFuture} completing with {@link DataPutResult} */ - RestconfFuture dataPUT(QueryParameters params, ResourceBody body); + RestconfFuture dataPUT(ServerRequest request, ResourceBody body); /** * Create or replace a data store resource, as described in * RFC8040 section 4.5. * + * @param request {@link ServerRequest} for this request * @param identifier resource identifier - * @param params query parameters * @param body data node for put to config DS * @return A {@link RestconfFuture} completing with {@link DataPutResult} */ - RestconfFuture dataPUT(ApiPath identifier, QueryParameters params, ResourceBody body); + RestconfFuture dataPUT(ServerRequest request, ApiPath identifier, ResourceBody body); /** * Return the set of supported RPCs supported by - * {@link #operationsPOST(URI, ApiPath, QueryParameters, OperationInputBody)}, + * {@link #operationsPOST(ServerRequest, URI, ApiPath, OperationInputBody)}, * as expressed in the ietf-restconf.yang * {@code container operations} statement. * + * @param request {@link ServerRequest} for this request * @return A {@link RestconfFuture} completing with an {@link FormattableBody} */ - RestconfFuture operationsGET(); + RestconfFuture operationsGET(ServerRequest request); /* * Return the details about a particular operation supported by @@ -138,42 +140,44 @@ public interface RestconfServer { * ietf-restconfig.yang * {@code container operations} statement. * + * @param request {@link ServerRequest} for this request * @param operation An operation * @return A {@link RestconfFuture} completing with an {@link FormattableBody} */ - RestconfFuture operationsGET(ApiPath operation); + RestconfFuture operationsGET(ServerRequest request, ApiPath operation); /** * Invoke an RPC operation, as defined in * RFC8040 Operation Resource. * + * @param request {@link ServerRequest} for this request * @param restconfURI Base URI of the request * @param operation {@code } path, really an {@link ApiPath} to an {@code rpc} - * @param params query parameters * @param body RPC operation * @return A {@link RestconfFuture} completing with {@link InvokeResult} */ // FIXME: 'operation' should really be an ApiIdentifier with non-null module, but we also support yang-ext:mount, // and hence it is a path right now - RestconfFuture operationsPOST(URI restconfURI, ApiPath operation, QueryParameters params, + RestconfFuture operationsPOST(ServerRequest request, URI restconfURI, ApiPath operation, OperationInputBody body); /** * Return the revision of {@code ietf-yang-library} module implemented by this server, as defined in * RFC8040 {+restconf}/yang-library-version. * + * @param request {@link ServerRequest} for this request * @return A {@link RestconfFuture} completing with {@link NormalizedNodePayload} containing a single * {@code yang-library-version} leaf element. */ - // FIXME: this is a simple encoding-variadic return, similar to how OperationsContent is handled use a common - // construct for both cases -- in this case it carries a yang.common.Revision - RestconfFuture yangLibraryVersionGET(); + RestconfFuture yangLibraryVersionGET(ServerRequest request); - RestconfFuture modulesYangGET(String fileName, @Nullable String revision); + RestconfFuture modulesYangGET(ServerRequest request, String fileName, @Nullable String revision); - RestconfFuture modulesYangGET(ApiPath mountPath, String fileName, @Nullable String revision); + RestconfFuture modulesYangGET(ServerRequest request, ApiPath mountPath, String fileName, + @Nullable String revision); - RestconfFuture modulesYinGET(String fileName, @Nullable String revision); + RestconfFuture modulesYinGET(ServerRequest request, String fileName, @Nullable String revision); - RestconfFuture modulesYinGET(ApiPath mountPath, String fileName, @Nullable String revision); + RestconfFuture modulesYinGET(ServerRequest request, ApiPath mountPath, String fileName, + @Nullable String revision); } diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/ServerRequest.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/ServerRequest.java new file mode 100644 index 0000000000..a675189950 --- /dev/null +++ b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/ServerRequest.java @@ -0,0 +1,47 @@ +/* + * 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 org.eclipse.jdt.annotation.NonNullByDefault; +import org.opendaylight.restconf.api.FormatParameters; +import org.opendaylight.restconf.api.QueryParameters; +import org.opendaylight.restconf.api.query.PrettyPrintParam; + +/** + * A request to {@link RestconfServer}. It contains state and binding established by whoever is performing binding to + * HTTP transport layer. This includes: + *

    + *
  • HTTP request {@link #queryParameters() query parameters},
  • + *
  • {@link #format() format parameters}, including those affected by query parameters
  • + *
+ * It notably does not hold the HTTP request path, nor the request body. Those are passed as separate arguments + * to server methods as implementations of those methods are expected to act on them. + */ +@NonNullByDefault +public record ServerRequest(QueryParameters queryParameters, FormatParameters format) { + // TODO: this is where a binding to security principal and access control should be: + // - we would like to be able to have java.security.Principal#name() for logging purposes + // - we need to have a NACM-capable interface, through which we can check permissions (such as data PUT) and + // establish output filters (i.e. excluding paths inaccessible path to user from a data GET a ContainerNode) + public ServerRequest { + requireNonNull(queryParameters); + requireNonNull(format); + } + + private ServerRequest(final QueryParameters queryParameters, final PrettyPrintParam prettyPrint) { + this(queryParameters, prettyPrint.value() ? FormatParameters.PRETTY : FormatParameters.COMPACT); + } + + public static ServerRequest of(final QueryParameters queryParameters, final PrettyPrintParam defaultPrettyPrint) { + final var prettyPrint = queryParameters.lookup(PrettyPrintParam.uriName, PrettyPrintParam::forUriValue); + return prettyPrint == null ? new ServerRequest(queryParameters, defaultPrettyPrint) + : new ServerRequest(queryParameters.withoutParam(PrettyPrintParam.uriName), prettyPrint); + } +} diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/mdsal/MdsalRestconfServer.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/mdsal/MdsalRestconfServer.java index 0eaaca945e..3fc09ce4b8 100644 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/mdsal/MdsalRestconfServer.java +++ b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/mdsal/MdsalRestconfServer.java @@ -30,8 +30,6 @@ import org.opendaylight.mdsal.dom.api.DOMMountPointService; import org.opendaylight.mdsal.dom.api.DOMRpcService; import org.opendaylight.restconf.api.ApiPath; import org.opendaylight.restconf.api.FormattableBody; -import org.opendaylight.restconf.api.QueryParameters; -import org.opendaylight.restconf.api.query.PrettyPrintParam; import org.opendaylight.restconf.common.errors.RestconfDocumentedException; import org.opendaylight.restconf.common.errors.RestconfFuture; import org.opendaylight.restconf.nb.rfc8040.rests.transactions.MdsalRestconfStrategy; @@ -50,10 +48,9 @@ import org.opendaylight.restconf.server.api.InvokeResult; import org.opendaylight.restconf.server.api.ModulesGetResult; import org.opendaylight.restconf.server.api.OperationInputBody; import org.opendaylight.restconf.server.api.PatchBody; -import org.opendaylight.restconf.server.api.QueryParams; import org.opendaylight.restconf.server.api.ResourceBody; import org.opendaylight.restconf.server.api.RestconfServer; -import org.opendaylight.restconf.server.spi.RestconfServerConfiguration; +import org.opendaylight.restconf.server.api.ServerRequest; import org.opendaylight.restconf.server.spi.RpcImplementation; import org.opendaylight.yangtools.yang.common.Empty; import org.opendaylight.yangtools.yang.common.ErrorTag; @@ -70,14 +67,12 @@ import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Deactivate; import org.osgi.service.component.annotations.Reference; import org.osgi.service.component.annotations.ReferencePolicyOption; -import org.osgi.service.metatype.annotations.Designate; /** * A RESTCONF server implemented on top of MD-SAL. */ @Singleton -@Component(service = RestconfServer.class, configurationPid = "org.opendaylight.restconf.server") -@Designate(ocd = RestconfServerConfiguration.class) +@Component(service = RestconfServer.class) public final class MdsalRestconfServer implements RestconfServer, AutoCloseable { private static final VarHandle LOCAL_STRATEGY; @@ -96,59 +91,31 @@ public final class MdsalRestconfServer implements RestconfServer, AutoCloseable private final @NonNull DOMDataBroker dataBroker; private final @Nullable DOMRpcService rpcService; private final @Nullable DOMActionService actionService; - private final @NonNull QueryParams emptyQueryParams; @SuppressFBWarnings(value = "URF_UNREAD_FIELD", justification = "https://github.com/spotbugs/spotbugs/issues/2749") private volatile MdsalRestconfStrategy localStrategy; - public MdsalRestconfServer(final MdsalDatabindProvider databindProvider, final DOMDataBroker dataBroker, - final DOMRpcService rpcService, final DOMActionService actionService, - final DOMMountPointService mountPointService, final List localRpcs, - final PrettyPrintParam prettyPrint) { + @Inject + @Activate + public MdsalRestconfServer(@Reference final MdsalDatabindProvider databindProvider, + @Reference final DOMDataBroker dataBroker, @Reference final DOMRpcService rpcService, + @Reference final DOMActionService actionService, + @Reference final DOMMountPointService mountPointService, + @Reference(policyOption = ReferencePolicyOption.GREEDY) final List localRpcs) { this.databindProvider = requireNonNull(databindProvider); this.dataBroker = requireNonNull(dataBroker); this.rpcService = requireNonNull(rpcService); this.actionService = requireNonNull(actionService); this.mountPointService = requireNonNull(mountPointService); - emptyQueryParams = new QueryParams(QueryParameters.of(), prettyPrint); this.localRpcs = Maps.uniqueIndex(localRpcs, RpcImplementation::qname); localStrategy = createLocalStrategy(databindProvider.currentDatabind()); } - @Inject - public MdsalRestconfServer(final MdsalDatabindProvider databindProvider, final DOMDataBroker dataBroker, - final DOMRpcService rpcService, final DOMActionService actionService, - final DOMMountPointService mountPointService, final List localRpcs) { - this(databindProvider, dataBroker, rpcService, actionService, mountPointService, localRpcs, - PrettyPrintParam.FALSE); - } - - @Activate - public MdsalRestconfServer(@Reference final MdsalDatabindProvider databindProvider, - @Reference final DOMDataBroker dataBroker, @Reference final DOMRpcService rpcService, - @Reference final DOMActionService actionService, - @Reference final DOMMountPointService mountPointService, - @Reference(policyOption = ReferencePolicyOption.GREEDY) final List localRpcs, - // FIXME: dynamic at some point - final RestconfServerConfiguration configuration) { - this(databindProvider, dataBroker, rpcService, actionService, mountPointService, localRpcs, - PrettyPrintParam.of(configuration.pretty$_$print())); - } - - public MdsalRestconfServer(final MdsalDatabindProvider databindProvider, final DOMDataBroker dataBroker, - final DOMRpcService rpcService, final DOMActionService actionService, - final DOMMountPointService mountPointService, final PrettyPrintParam prettyPrint, - final RpcImplementation... localRpcs) { - this(databindProvider, dataBroker, rpcService, actionService, mountPointService, List.of(localRpcs), - prettyPrint); - } - public MdsalRestconfServer(final MdsalDatabindProvider databindProvider, final DOMDataBroker dataBroker, final DOMRpcService rpcService, final DOMActionService actionService, final DOMMountPointService mountPointService, final RpcImplementation... localRpcs) { - this(databindProvider, dataBroker, rpcService, actionService, mountPointService, PrettyPrintParam.FALSE, - localRpcs); + this(databindProvider, dataBroker, rpcService, actionService, mountPointService, List.of(localRpcs)); } private @NonNull MdsalRestconfStrategy createLocalStrategy(final DatabindContext databind) { @@ -175,44 +142,41 @@ public final class MdsalRestconfServer implements RestconfServer, AutoCloseable localStrategy = null; } - private @NonNull QueryParams queryParams(final @NonNull QueryParameters params) { - return params.isEmpty() ? emptyQueryParams : new QueryParams(params, emptyQueryParams.prettyPrint()); - } - @Override - public RestconfFuture dataDELETE(final ApiPath identifier) { + public RestconfFuture dataDELETE(final ServerRequest request, final ApiPath identifier) { final StrategyAndTail stratAndTail; try { stratAndTail = localStrategy().resolveStrategy(identifier); } catch (RestconfDocumentedException e) { return RestconfFuture.failed(e); } - return stratAndTail.strategy().dataDELETE(stratAndTail.tail()); + return stratAndTail.strategy().dataDELETE(request, stratAndTail.tail()); } @Override - public RestconfFuture dataGET(final QueryParameters params) { - return localStrategy().dataGET(ApiPath.empty(), queryParams(params)); + public RestconfFuture dataGET(final ServerRequest request) { + return localStrategy().dataGET(request, ApiPath.empty()); } @Override - public RestconfFuture dataGET(final ApiPath identifier, final QueryParameters params) { + public RestconfFuture dataGET(final ServerRequest request, final ApiPath identifier) { final StrategyAndTail stratAndTail; try { stratAndTail = localStrategy().resolveStrategy(identifier); } catch (RestconfDocumentedException e) { return RestconfFuture.failed(e); } - return stratAndTail.strategy().dataGET(stratAndTail.tail(), queryParams(params)); + return stratAndTail.strategy().dataGET(request, stratAndTail.tail()); } @Override - public RestconfFuture dataPATCH(final ResourceBody body) { + public RestconfFuture dataPATCH(final ServerRequest request, final ResourceBody body) { return localStrategy().dataPATCH(ApiPath.empty(), body); } @Override - public RestconfFuture dataPATCH(final ApiPath identifier, final ResourceBody body) { + public RestconfFuture dataPATCH(final ServerRequest request, final ApiPath identifier, + final ResourceBody body) { final StrategyAndTail strategyAndTail; try { strategyAndTail = localStrategy().resolveStrategy(identifier); @@ -223,12 +187,12 @@ public final class MdsalRestconfServer implements RestconfServer, AutoCloseable } @Override - public RestconfFuture dataPATCH(final QueryParameters params, final PatchBody body) { - return localStrategy().dataPATCH(ApiPath.empty(), queryParams(params), body); + public RestconfFuture dataPATCH(final ServerRequest request, final PatchBody body) { + return localStrategy().dataPATCH(ApiPath.empty(), body); } @Override - public RestconfFuture dataPATCH(final ApiPath identifier, final QueryParameters params, + public RestconfFuture dataPATCH(final ServerRequest request, final ApiPath identifier, final PatchBody body) { final StrategyAndTail strategyAndTail; try { @@ -236,16 +200,16 @@ public final class MdsalRestconfServer implements RestconfServer, AutoCloseable } catch (RestconfDocumentedException e) { return RestconfFuture.failed(e); } - return strategyAndTail.strategy().dataPATCH(strategyAndTail.tail(), queryParams(params), body); + return strategyAndTail.strategy().dataPATCH(strategyAndTail.tail(), body); } @Override - public RestconfFuture dataPOST(final QueryParameters params, final ChildBody body) { - return localStrategy().dataCreatePOST(queryParams(params), body); + public RestconfFuture dataPOST(final ServerRequest request, final ChildBody body) { + return localStrategy().dataCreatePOST(request, body); } @Override - public RestconfFuture dataPOST(final ApiPath identifier, final QueryParameters params, + public RestconfFuture dataPOST(final ServerRequest request, final ApiPath identifier, final DataPostBody body) { final StrategyAndTail strategyAndTail; try { @@ -253,16 +217,16 @@ public final class MdsalRestconfServer implements RestconfServer, AutoCloseable } catch (RestconfDocumentedException e) { return RestconfFuture.failed(e); } - return strategyAndTail.strategy().dataPOST(strategyAndTail.tail(), queryParams(params), body); + return strategyAndTail.strategy().dataPOST(request, strategyAndTail.tail(), body); } @Override - public RestconfFuture dataPUT(final QueryParameters params, final ResourceBody body) { - return localStrategy().dataPUT(ApiPath.empty(), queryParams(params), body); + public RestconfFuture dataPUT(final ServerRequest request, final ResourceBody body) { + return localStrategy().dataPUT(request, ApiPath.empty(), body); } @Override - public RestconfFuture dataPUT(final ApiPath identifier, final QueryParameters params, + public RestconfFuture dataPUT(final ServerRequest request, final ApiPath identifier, final ResourceBody body) { final StrategyAndTail strategyAndTail; try { @@ -270,28 +234,30 @@ public final class MdsalRestconfServer implements RestconfServer, AutoCloseable } catch (RestconfDocumentedException e) { return RestconfFuture.failed(e); } - return strategyAndTail.strategy().dataPUT(strategyAndTail.tail(), queryParams(params), body); + return strategyAndTail.strategy().dataPUT(request, strategyAndTail.tail(), body); } @Override - public RestconfFuture modulesYangGET(final String fileName, final String revision) { + public RestconfFuture modulesYangGET(final ServerRequest request, final String fileName, + final String revision) { return modulesGET(fileName, revision, YangTextSource.class); } @Override - public RestconfFuture modulesYangGET(final ApiPath mountPath, final String fileName, - final String revision) { + public RestconfFuture modulesYangGET(final ServerRequest request, final ApiPath mountPath, + final String fileName, final String revision) { return modulesGET(mountPath, fileName, revision, YangTextSource.class); } @Override - public RestconfFuture modulesYinGET(final String fileName, final String revision) { + public RestconfFuture modulesYinGET(final ServerRequest request, final String fileName, + final String revision) { return modulesGET(fileName, revision, YinTextSource.class); } @Override - public RestconfFuture modulesYinGET(final ApiPath mountPath, final String fileName, - final String revision) { + public RestconfFuture modulesYinGET(final ServerRequest request, final ApiPath mountPath, + final String fileName, final String revision) { return modulesGET(mountPath, fileName, revision, YinTextSource.class); } @@ -352,12 +318,12 @@ public final class MdsalRestconfServer implements RestconfServer, AutoCloseable } @Override - public RestconfFuture operationsGET() { - return localStrategy().operationsGET(emptyQueryParams); + public RestconfFuture operationsGET(final ServerRequest request) { + return localStrategy().operationsGET(request); } @Override - public RestconfFuture operationsGET(final ApiPath operation) { + public RestconfFuture operationsGET(final ServerRequest request, final ApiPath operation) { final StrategyAndTail strategyAndTail; try { strategyAndTail = localStrategy().resolveStrategy(operation); @@ -367,13 +333,12 @@ public final class MdsalRestconfServer implements RestconfServer, AutoCloseable final var strategy = strategyAndTail.strategy(); final var tail = strategyAndTail.tail(); - return tail.isEmpty() ? strategy.operationsGET(emptyQueryParams) - : strategy.operationsGET(tail, emptyQueryParams); + return tail.isEmpty() ? strategy.operationsGET(request) : strategy.operationsGET(request, tail); } @Override - public RestconfFuture operationsPOST(final URI restconfURI, final ApiPath apiPath, - final QueryParameters params, final OperationInputBody body) { + public RestconfFuture operationsPOST(final ServerRequest request, final URI restconfURI, + final ApiPath apiPath, final OperationInputBody body) { final StrategyAndTail strategyAndTail; try { strategyAndTail = localStrategy().resolveStrategy(apiPath); @@ -381,11 +346,11 @@ public final class MdsalRestconfServer implements RestconfServer, AutoCloseable return RestconfFuture.failed(e); } final var strategy = strategyAndTail.strategy(); - return strategy.operationsPOST(restconfURI, strategyAndTail.tail(), queryParams(params), body); + return strategy.operationsPOST(request, restconfURI, strategyAndTail.tail(), body); } @Override - public RestconfFuture yangLibraryVersionGET() { - return localStrategy().yangLibraryVersionGET(emptyQueryParams); + public RestconfFuture yangLibraryVersionGET(final ServerRequest request) { + return localStrategy().yangLibraryVersionGET(request); } } diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/AllOperations.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/AllOperations.java index 5266ecf7ca..ee3926023a 100644 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/AllOperations.java +++ b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/AllOperations.java @@ -9,6 +9,7 @@ package org.opendaylight.restconf.server.spi; import static java.util.Objects.requireNonNull; +import com.google.common.base.MoreObjects.ToStringHelper; import com.google.common.collect.ImmutableSetMultimap; import java.io.IOException; import java.io.Writer; @@ -82,4 +83,9 @@ final class AllOperations extends OperationsBody { } out.write("\n"); } + + @Override + protected ToStringHelper addToStringAttributes(final ToStringHelper helper) { + return helper.add("rpcs", rpcs); + } } \ No newline at end of file diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/FailedHttpGetResource.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/FailedHttpGetResource.java index 09cabde2fa..e7c4ec9203 100644 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/FailedHttpGetResource.java +++ b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/FailedHttpGetResource.java @@ -14,7 +14,7 @@ import org.opendaylight.restconf.api.ApiPath; import org.opendaylight.restconf.api.FormattableBody; import org.opendaylight.restconf.common.errors.RestconfDocumentedException; import org.opendaylight.restconf.common.errors.RestconfFuture; -import org.opendaylight.restconf.server.api.QueryParams; +import org.opendaylight.restconf.server.api.ServerRequest; @NonNullByDefault public record FailedHttpGetResource(RestconfDocumentedException cause) implements HttpGetResource { @@ -23,12 +23,12 @@ public record FailedHttpGetResource(RestconfDocumentedException cause) implement } @Override - public RestconfFuture httpGET(final QueryParams params) { + public RestconfFuture httpGET(final ServerRequest request) { return RestconfFuture.failed(cause); } @Override - public RestconfFuture httpGET(final ApiPath apiPath, final QueryParams params) { + public RestconfFuture httpGET(final ServerRequest request, final ApiPath apiPath) { throw new UnsupportedOperationException(); } } diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/HttpGetResource.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/HttpGetResource.java index a616a0a629..86b57b9c5b 100644 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/HttpGetResource.java +++ b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/HttpGetResource.java @@ -11,7 +11,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.opendaylight.restconf.api.ApiPath; import org.opendaylight.restconf.api.FormattableBody; import org.opendaylight.restconf.common.errors.RestconfFuture; -import org.opendaylight.restconf.server.api.QueryParams; +import org.opendaylight.restconf.server.api.ServerRequest; /** * A resource which supports HTTP GET and produces a {@link FormattableBody}. @@ -19,7 +19,7 @@ import org.opendaylight.restconf.server.api.QueryParams; @NonNullByDefault public interface HttpGetResource { - RestconfFuture httpGET(QueryParams params); + RestconfFuture httpGET(ServerRequest request); - RestconfFuture httpGET(ApiPath apiPath, QueryParams params); + RestconfFuture httpGET(ServerRequest request, ApiPath apiPath); } diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/NormalizedFormattableBody.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/NormalizedFormattableBody.java index 2088ed7a7e..ae06d5e013 100644 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/NormalizedFormattableBody.java +++ b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/NormalizedFormattableBody.java @@ -39,9 +39,8 @@ public final class NormalizedFormattableBody extends D private final Inference parent; private final N data; - public NormalizedFormattableBody(final FormatParameters format, final DatabindContext databind, - final Inference parent, final N data) { - super(format, databind); + public NormalizedFormattableBody(final DatabindContext databind, final Inference parent, final N data) { + super(databind); this.parent = requireNonNull(parent); this.data = requireNonNull(data); // RESTCONF allows returning one list item. We need to wrap it in map node in order to serialize it properly, @@ -62,14 +61,14 @@ public final class NormalizedFormattableBody extends D } @Override - protected void formatToJSON(final OutputStream out, final FormatParameters format, final DatabindContext databind) + protected void formatToJSON(final DatabindContext databind, final FormatParameters format, final OutputStream out) throws IOException { writeTo(JSONNormalizedNodeStreamWriter.createExclusiveWriter(databind.jsonCodecs(), parent, null, FormattableBodySupport.createJsonWriter(out, format))); } @Override - protected void formatToXML(final OutputStream out, final FormatParameters format, final DatabindContext databind) + protected void formatToXML(final DatabindContext databind, final FormatParameters format, final OutputStream out) throws IOException { final var xmlWriter = FormattableBodySupport.createXmlWriter(out, format); writeTo(XMLStreamNormalizedNodeStreamWriter.create(xmlWriter, parent)); @@ -82,7 +81,7 @@ public final class NormalizedFormattableBody extends D @Override protected ToStringHelper addToStringAttributes(final ToStringHelper helper) { - return super.addToStringAttributes(helper.add("body", data.prettyTree())); + return helper.add("body", data.prettyTree()); } private void writeTo(final NormalizedNodeStreamWriter streamWriter) throws IOException { diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/OneOperation.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/OneOperation.java index 1d8ad60efc..f80cd6d32c 100644 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/OneOperation.java +++ b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/OneOperation.java @@ -48,6 +48,6 @@ final class OneOperation extends OperationsBody { @Override protected ToStringHelper addToStringAttributes(final ToStringHelper helper) { - return super.addToStringAttributes(helper.add("rpc", rpc)); + return helper.add("rpc", rpc); } } \ No newline at end of file diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/OperationOutputBody.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/OperationOutputBody.java index eb348307c9..a80724ba5d 100644 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/OperationOutputBody.java +++ b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/OperationOutputBody.java @@ -35,8 +35,8 @@ import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack; public final class OperationOutputBody extends DatabindPathFormattableBody { private final ContainerNode output; - public OperationOutputBody(final FormatParameters format, final OperationPath path, final ContainerNode output) { - super(format, path); + public OperationOutputBody(final OperationPath path, final ContainerNode output) { + super(path); this.output = requireNonNull(output); if (output.isEmpty()) { throw new IllegalArgumentException("output may not be empty"); @@ -49,8 +49,7 @@ public final class OperationOutputBody extends DatabindPathFormattableBody PrettyPrintParam.TRUE); this.modelContext = requireNonNull(modelContext); } @Override - protected final void formatToJSON(final OutputStream out, final FormatParameters format) throws IOException { + public final void formatToJSON(final FormatParameters format, final OutputStream out) throws IOException { try (var writer = new OutputStreamWriter(out, StandardCharsets.UTF_8)) { formatToJSON(writer); } @@ -43,7 +41,7 @@ abstract sealed class OperationsBody extends FormattableBody permits AllOperatio abstract void formatToJSON(@NonNull Writer out) throws IOException; @Override - protected final void formatToXML(final OutputStream out, final FormatParameters format) throws IOException { + public final void formatToXML(final FormatParameters format, final OutputStream out) throws IOException { try (var writer = new OutputStreamWriter(out, StandardCharsets.UTF_8)) { formatToXML(writer); } diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/OperationsResource.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/OperationsResource.java index 7a041d798a..553cb57add 100644 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/OperationsResource.java +++ b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/OperationsResource.java @@ -21,7 +21,7 @@ import org.opendaylight.restconf.api.FormattableBody; import org.opendaylight.restconf.common.errors.RestconfDocumentedException; import org.opendaylight.restconf.common.errors.RestconfFuture; import org.opendaylight.restconf.server.api.DatabindPath.Rpc; -import org.opendaylight.restconf.server.api.QueryParams; +import org.opendaylight.restconf.server.api.ServerRequest; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.common.QNameModule; import org.opendaylight.yangtools.yang.common.Revision; @@ -41,7 +41,7 @@ public final class OperationsResource implements HttpGetResource { } @Override - public RestconfFuture httpGET(final QueryParams params) { + public RestconfFuture httpGET(final ServerRequest request) { // RPC QNames by their XMLNamespace/Revision. This should be a Table, but Revision can be null, which wrecks us. final var table = new HashMap>>(); final var modelContext = pathNormalizer.databind().modelContext(); @@ -69,7 +69,7 @@ public final class OperationsResource implements HttpGetResource { } @Override - public RestconfFuture httpGET(final ApiPath apiPath, final QueryParams params) { + public RestconfFuture httpGET(final ServerRequest request, final ApiPath apiPath) { final Rpc path; try { path = pathNormalizer.normalizeRpcPath(apiPath); diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/RestconfServerConfiguration.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/RestconfServerConfiguration.java deleted file mode 100644 index 4b060ee18f..0000000000 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/RestconfServerConfiguration.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * 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 org.opendaylight.restconf.api.query.PrettyPrintParam; -import org.opendaylight.restconf.server.api.RestconfServer; -import org.osgi.service.metatype.annotations.AttributeDefinition; -import org.osgi.service.metatype.annotations.ObjectClassDefinition; - -/** - * OSGi configuration of a typical {@link RestconfServer}. - */ -@ObjectClassDefinition -public @interface RestconfServerConfiguration { - @AttributeDefinition( - name = "default pretty-print", - description = "Control the default value of the '" + PrettyPrintParam.uriName + "' query parameter.") - boolean pretty$_$print() default false; -} \ No newline at end of file diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/YangLibraryVersionResource.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/YangLibraryVersionResource.java index f6944c2d95..3b567fbe03 100644 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/YangLibraryVersionResource.java +++ b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/YangLibraryVersionResource.java @@ -15,7 +15,7 @@ import org.opendaylight.restconf.api.FormattableBody; import org.opendaylight.restconf.common.errors.RestconfDocumentedException; import org.opendaylight.restconf.common.errors.RestconfFuture; import org.opendaylight.restconf.server.api.DatabindContext; -import org.opendaylight.restconf.server.api.QueryParams; +import org.opendaylight.restconf.server.api.ServerRequest; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.restconf.rev170126.YangApi; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.restconf.rev170126.restconf.Restconf; import org.opendaylight.yangtools.yang.common.QName; @@ -70,12 +70,12 @@ public record YangLibraryVersionResource(DatabindContext databind, Inference res } @Override - public RestconfFuture httpGET(final QueryParams params) { - return RestconfFuture.of(new NormalizedFormattableBody<>(params::prettyPrint, databind, restconf, leaf)); + public RestconfFuture httpGET(final ServerRequest request) { + return RestconfFuture.of(new NormalizedFormattableBody<>(databind, restconf, leaf)); } @Override - public RestconfFuture httpGET(final ApiPath apiPath, final QueryParams params) { + public RestconfFuture httpGET(final ServerRequest request, final ApiPath apiPath) { throw new UnsupportedOperationException(); } } diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/YangPatchStatusBody.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/YangPatchStatusBody.java index fb443d0957..10f0f8e0e4 100644 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/YangPatchStatusBody.java +++ b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/YangPatchStatusBody.java @@ -9,6 +9,7 @@ package org.opendaylight.restconf.server.spi; import static java.util.Objects.requireNonNull; +import com.google.common.base.MoreObjects.ToStringHelper; import com.google.gson.stream.JsonWriter; import java.io.IOException; import java.io.OutputStream; @@ -31,13 +32,12 @@ public final class YangPatchStatusBody extends FormattableBody { private final PatchStatusContext status; - public YangPatchStatusBody(final FormatParameters format, final PatchStatusContext status) { - super(format); + public YangPatchStatusBody(final PatchStatusContext status) { this.status = requireNonNull(status); } @Override - protected void formatToJSON(final OutputStream out, final FormatParameters format) throws IOException { + public void formatToJSON(final FormatParameters format, final OutputStream out) throws IOException { try (var writer = FormattableBodySupport.createJsonWriter(out, format)) { writer.beginObject().name("ietf-yang-patch:yang-patch-status") .beginObject().name("patch-id").value(status.patchId()); @@ -70,7 +70,7 @@ public final class YangPatchStatusBody extends FormattableBody { } @Override - protected void formatToXML(final OutputStream out, final FormatParameters format) throws IOException { + public void formatToXML(final FormatParameters format, final OutputStream out) throws IOException { final var writer = FormattableBodySupport.createXmlWriter(out, format); try { formatToXML(writer); @@ -187,4 +187,9 @@ public final class YangPatchStatusBody extends FormattableBody { writer.writeEndElement(); } + + @Override + protected ToStringHelper addToStringAttributes(final ToStringHelper helper) { + return helper.add("status", status); + } } diff --git a/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/AbstractRestconfTest.java b/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/AbstractRestconfTest.java index 9bf0a0d25e..c9efc3211d 100644 --- a/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/AbstractRestconfTest.java +++ b/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/AbstractRestconfTest.java @@ -22,10 +22,8 @@ import java.text.ParseException; import java.util.List; import java.util.function.Consumer; import javax.ws.rs.container.AsyncResponse; -import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; -import javax.ws.rs.ext.MessageBodyWriter; import org.eclipse.jdt.annotation.NonNull; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.extension.ExtendWith; @@ -39,13 +37,12 @@ import org.opendaylight.mdsal.dom.api.DOMMountPointService; import org.opendaylight.mdsal.dom.api.DOMRpcService; import org.opendaylight.mdsal.dom.spi.FixedDOMSchemaService; import org.opendaylight.restconf.api.ApiPath; +import org.opendaylight.restconf.api.FormatParameters; import org.opendaylight.restconf.api.FormattableBody; -import org.opendaylight.restconf.api.MediaTypes; +import org.opendaylight.restconf.api.query.PrettyPrintParam; import org.opendaylight.restconf.common.errors.RestconfDocumentedException; import org.opendaylight.restconf.common.errors.RestconfError; import org.opendaylight.restconf.nb.rfc8040.AbstractJukeboxTest; -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.mdsal.MdsalDatabindProvider; import org.opendaylight.restconf.server.mdsal.MdsalRestconfServer; @@ -75,57 +72,38 @@ abstract class AbstractRestconfTest extends AbstractJukeboxTest { @BeforeEach final void setupRestconf() { - restconf = new JaxRsRestconf(new MdsalRestconfServer( - new MdsalDatabindProvider(new FixedDOMSchemaService(modelContext())), dataBroker, rpcService, actionService, - mountPointService)); + restconf = new JaxRsRestconf( + new MdsalRestconfServer(new MdsalDatabindProvider(new FixedDOMSchemaService(modelContext())), + dataBroker, rpcService, actionService, mountPointService), + PrettyPrintParam.FALSE); } EffectiveModelContext modelContext() { return JUKEBOX_SCHEMA; } - static final void assertJson(final String expectedJson, final NormalizedNodePayload payload) { - assertPayload(expectedJson, payload, new JsonNormalizedNodeBodyWriter(), - MediaTypes.APPLICATION_YANG_DATA_JSON); - } - static final void assertJson(final String expectedJson, final OperationOutputBody payload) { final var baos = new ByteArrayOutputStream(); try { - payload.formatToJSON(baos); + payload.formatToJSON(FormatParameters.COMPACT, 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.formatToXML(baos); + payload.formatToXML(FormatParameters.COMPACT, 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 writer, final String mediaType) { - final var baos = new ByteArrayOutputStream(); - try { - writer.writeTo(payload, null, null, null, MediaType.valueOf(mediaType), null, baos); - } catch (IOException e) { - throw new AssertionError(e); - } - assertEquals(expected, baos.toString(StandardCharsets.UTF_8)); - } - - static final FormattableBody assertFormatableBody(final int status, final Consumer invocation) { - return assertEntity(FormattableBody.class, status, invocation); + static final FormattableBody assertFormattableBody(final int status, final Consumer invocation) { + return assertEntity(JaxRsFormattableBody.class, status, invocation).body(); } static final ContainerNode assertOperationOutput(final int status, final Consumer invocation) { diff --git a/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/Netconf799Test.java b/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/Netconf799Test.java index 0cc1959259..a7f4a4b6f5 100644 --- a/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/Netconf799Test.java +++ b/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/Netconf799Test.java @@ -8,7 +8,6 @@ package org.opendaylight.restconf.nb.jaxrs; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; @@ -32,10 +31,11 @@ import org.opendaylight.mdsal.dom.api.DOMRpcService; import org.opendaylight.mdsal.dom.spi.FixedDOMSchemaService; import org.opendaylight.mdsal.dom.spi.SimpleDOMActionResult; import org.opendaylight.restconf.api.ApiPath; +import org.opendaylight.restconf.api.query.PrettyPrintParam; import org.opendaylight.restconf.nb.rfc8040.AbstractInstanceIdentifierTest; +import org.opendaylight.restconf.nb.rfc8040.AbstractJukeboxTest; import org.opendaylight.restconf.server.mdsal.MdsalDatabindProvider; import org.opendaylight.restconf.server.mdsal.MdsalRestconfServer; -import org.opendaylight.restconf.server.spi.OperationOutputBody; 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; @@ -68,9 +68,10 @@ class Netconf799Test extends AbstractInstanceIdentifierTest { .build()))) .when(actionService).invokeAction(eq(RESET_PATH), any(), any()); - final var restconf = new JaxRsRestconf(new MdsalRestconfServer( - new MdsalDatabindProvider(new FixedDOMSchemaService(IID_SCHEMA)), dataBroker, rpcService, actionService, - mountPointService)); + final var restconf = new JaxRsRestconf( + new MdsalRestconfServer(new MdsalDatabindProvider(new FixedDOMSchemaService(IID_SCHEMA)), + dataBroker, rpcService, actionService, mountPointService), + PrettyPrintParam.FALSE); doReturn(new MultivaluedHashMap<>()).when(uriInfo).getQueryParameters(); doReturn(true).when(asyncResponse).resume(captor.capture()); restconf.postDataJSON(ApiPath.parse("instance-identifier-module:cont/cont1/reset"), @@ -93,25 +94,27 @@ class Netconf799Test extends AbstractInstanceIdentifierTest { .build()))) .when(actionService).invokeAction(eq(RESET_PATH), any(), any()); - final var restconf = new JaxRsRestconf(new MdsalRestconfServer( - new MdsalDatabindProvider(new FixedDOMSchemaService(IID_SCHEMA)), dataBroker, rpcService, actionService, - mountPointService)); + final var restconf = new JaxRsRestconf( + new MdsalRestconfServer(new MdsalDatabindProvider(new FixedDOMSchemaService(IID_SCHEMA)), + dataBroker, rpcService, actionService, mountPointService), + PrettyPrintParam.FALSE); doReturn(new MultivaluedHashMap<>()).when(uriInfo).getQueryParameters(); - doReturn(true).when(asyncResponse).resume(captor.capture()); - restconf.postDataJSON(ApiPath.parse("instance-identifier-module:cont/cont1/reset"), - stringInputStream(""" - { - "instance-identifier-module:input": { - "delay": 600 - } - }"""), uriInfo, asyncResponse); - final var response = captor.getValue(); - assertEquals(200, response.getStatus()); - final var payload = assertInstanceOf(OperationOutputBody.class, response.getEntity()); - AbstractRestconfTest.assertJson(""" - {"instance-identifier-module:output":{"timestamp":"somevalue"}}""", payload); - AbstractRestconfTest.assertXml(""" - somevalue""", payload); + final var apiPath = ApiPath.parse("instance-identifier-module:cont/cont1/reset"); + final var body = AbstractRestconfTest.assertFormattableBody(200, ar -> { + restconf.postDataJSON(apiPath, + stringInputStream(""" + { + "instance-identifier-module:input": { + "delay": 600 + } + }"""), uriInfo, ar); + }); + + AbstractJukeboxTest.assertFormat(""" + {"instance-identifier-module:output":{"timestamp":"somevalue"}}""", body::formatToJSON, false); + AbstractJukeboxTest.assertFormat(""" + somevalue""", body::formatToXML, + false); } } diff --git a/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/Netconf822Test.java b/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/Netconf822Test.java index a112e4a7b7..38a1e1ae77 100644 --- a/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/Netconf822Test.java +++ b/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/Netconf822Test.java @@ -25,7 +25,7 @@ class Netconf822Test extends AbstractRestconfTest { @Test void testOperationsContent() { - final var body = assertFormatableBody(200, ar -> restconf.operationsGET(ar)); + final var body = assertFormattableBody(200, ar -> restconf.operationsGET(ar)); assertFormat(""" { @@ -33,21 +33,21 @@ class Netconf822Test extends AbstractRestconfTest { "foo:new" : [null], "foo:new1" : [null] } - }""", body::formatToJSON); + }""", body::formatToJSON, true); assertFormat(""" - """, body::formatToXML); + """, body::formatToXML, true); } @Test void testOperationsContentByIdentifier() { - final var body = assertFormatableBody(200, ar -> restconf.operationsGET(apiPath("foo:new1"), ar)); + final var body = assertFormattableBody(200, ar -> restconf.operationsGET(apiPath("foo:new1"), ar)); assertFormat(""" - { "foo:new1" : [null] }""", body::formatToJSON); + { "foo:new1" : [null] }""", body::formatToJSON, false); assertFormat(""" - """, body::formatToXML); + """, body::formatToXML, false); } } diff --git a/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/RestconfDataPatchTest.java b/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/RestconfDataPatchTest.java index 84f4e00a77..b9df9bd979 100644 --- a/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/RestconfDataPatchTest.java +++ b/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/RestconfDataPatchTest.java @@ -93,7 +93,7 @@ class RestconfDataPatchTest extends AbstractRestconfTest { null ] } - }""", body::formatToJSON); + }""", body::formatToJSON, true); } @Test @@ -169,7 +169,7 @@ class RestconfDataPatchTest extends AbstractRestconfTest { ] } } - }""", body::formatToJSON); + }""", body::formatToJSON, true); } @Test @@ -218,6 +218,6 @@ class RestconfDataPatchTest extends AbstractRestconfTest { test patch id - """, body::formatToXML); + """, body::formatToXML, true); } } diff --git a/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/RestconfOperationsGetTest.java b/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/RestconfOperationsGetTest.java index 1cf3ad3ff3..cf01fd7bc4 100644 --- a/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/RestconfOperationsGetTest.java +++ b/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/RestconfOperationsGetTest.java @@ -61,10 +61,10 @@ class RestconfOperationsGetTest extends AbstractRestconfTest { @Test void testOperations() { - final var body = assertFormatableBody(200, ar -> restconf.operationsGET(ar)); + final var body = assertFormattableBody(200, ar -> restconf.operationsGET(ar)); - assertFormat(EXPECTED_JSON, body::formatToJSON); - assertFormat(EXPECTED_XML, body::formatToXML); + assertFormat(EXPECTED_JSON, body::formatToJSON, true); + assertFormat(EXPECTED_XML, body::formatToXML, true); } private void mockMountPoint() { @@ -82,19 +82,19 @@ class RestconfOperationsGetTest extends AbstractRestconfTest { void testMountPointOperations() { mockMountPoint(); - final var body = assertFormatableBody(200, ar -> restconf.operationsGET(DEVICE_ID, ar)); - assertFormat(EXPECTED_JSON, body::formatToJSON); - assertFormat(EXPECTED_XML, body::formatToXML); + final var body = assertFormattableBody(200, ar -> restconf.operationsGET(DEVICE_ID, ar)); + assertFormat(EXPECTED_JSON, body::formatToJSON, true); + assertFormat(EXPECTED_XML, body::formatToXML, true); } @Test void testMountPointSpecificOperationsJson() { mockMountPoint(); - final var body = assertFormatableBody(200, ar -> restconf.operationsGET(DEVICE_RPC1_MODULE1_ID, ar)); + final var body = assertFormattableBody(200, ar -> restconf.operationsGET(DEVICE_RPC1_MODULE1_ID, ar)); assertFormat(""" - { "module1:dummy-rpc1-module1" : [null] }""", body::formatToJSON); + { "module1:dummy-rpc1-module1" : [null] }""", body::formatToJSON, false); assertFormat(""" - """, body::formatToXML); + """, body::formatToXML, false); } } diff --git a/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/RestconfSchemaServiceMountTest.java b/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/RestconfSchemaServiceMountTest.java index 36f8d96964..43213f3f5b 100644 --- a/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/RestconfSchemaServiceMountTest.java +++ b/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/RestconfSchemaServiceMountTest.java @@ -25,6 +25,7 @@ import org.opendaylight.mdsal.dom.api.DOMSchemaService; import org.opendaylight.mdsal.dom.broker.DOMMountPointServiceImpl; import org.opendaylight.mdsal.dom.spi.FixedDOMSchemaService; import org.opendaylight.restconf.api.ApiPath; +import org.opendaylight.restconf.api.query.PrettyPrintParam; import org.opendaylight.restconf.nb.rfc8040.legacy.ErrorTags; import org.opendaylight.restconf.server.mdsal.MdsalDatabindProvider; import org.opendaylight.restconf.server.mdsal.MdsalRestconfServer; @@ -81,9 +82,11 @@ public class RestconfSchemaServiceMountTest { .createMountPoint(YangInstanceIdentifier.of(QName.create("mount:point:2", "2016-01-01", "cont"))) .register(); - restconf = new JaxRsRestconf(new MdsalRestconfServer( - new MdsalDatabindProvider(new FixedDOMSchemaService(SCHEMA_CONTEXT_WITH_MOUNT_POINTS)), dataBroker, - rpcService, actionService, mountPointService)); + restconf = new JaxRsRestconf( + new MdsalRestconfServer(new MdsalDatabindProvider( + new FixedDOMSchemaService(SCHEMA_CONTEXT_WITH_MOUNT_POINTS)), dataBroker, rpcService, actionService, + mountPointService), + PrettyPrintParam.FALSE); } /** diff --git a/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/RestconfSchemaServiceTest.java b/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/RestconfSchemaServiceTest.java index 5bd3a28de4..d6c466f64f 100644 --- a/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/RestconfSchemaServiceTest.java +++ b/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/RestconfSchemaServiceTest.java @@ -27,6 +27,7 @@ import org.opendaylight.mdsal.dom.api.DOMMountPointService; import org.opendaylight.mdsal.dom.api.DOMRpcService; import org.opendaylight.mdsal.dom.api.DOMSchemaService.YangTextSourceExtension; import org.opendaylight.mdsal.dom.spi.FixedDOMSchemaService; +import org.opendaylight.restconf.api.query.PrettyPrintParam; import org.opendaylight.restconf.server.mdsal.MdsalDatabindProvider; import org.opendaylight.restconf.server.mdsal.MdsalRestconfServer; import org.opendaylight.yangtools.yang.common.ErrorTag; @@ -66,9 +67,11 @@ public class RestconfSchemaServiceTest { @Before public void setup() throws Exception { - restconf = new JaxRsRestconf(new MdsalRestconfServer( - new MdsalDatabindProvider(new FixedDOMSchemaService(() -> MODEL_CONTEXT, sourceProvider)), dataBroker, - rpcService, actionService, mountPointService)); + restconf = new JaxRsRestconf( + new MdsalRestconfServer(new MdsalDatabindProvider( + new FixedDOMSchemaService(() -> MODEL_CONTEXT, sourceProvider)), dataBroker, rpcService, actionService, + mountPointService), + PrettyPrintParam.FALSE); } /** diff --git a/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/RestconfYangLibraryVersionGetTest.java b/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/RestconfYangLibraryVersionGetTest.java index 1dac52090f..df77098206 100644 --- a/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/RestconfYangLibraryVersionGetTest.java +++ b/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/RestconfYangLibraryVersionGetTest.java @@ -22,12 +22,12 @@ class RestconfYangLibraryVersionGetTest extends AbstractRestconfTest { @Test void testLibraryVersion() { - final var body = assertFormatableBody(200, ar -> restconf.yangLibraryVersionGET(ar)); + final var body = assertFormattableBody(200, ar -> restconf.yangLibraryVersionGET(ar)); assertFormat(""" - {"ietf-restconf:yang-library-version":"2019-01-04"}""", body::formatToJSON); + {"ietf-restconf:yang-library-version":"2019-01-04"}""", body::formatToJSON, false); assertFormat(""" 2019-01-04\ - """, body::formatToXML); + """, body::formatToXML, false); } } diff --git a/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/AbstractJukeboxTest.java b/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/AbstractJukeboxTest.java index bf7545cf9a..cd0453594b 100644 --- a/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/AbstractJukeboxTest.java +++ b/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/AbstractJukeboxTest.java @@ -16,6 +16,8 @@ import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.StandardCharsets; import org.eclipse.jdt.annotation.NonNull; +import org.opendaylight.restconf.api.FormatParameters; +import org.opendaylight.restconf.api.query.PrettyPrintParam; import org.opendaylight.restconf.server.api.DatabindContext; import org.opendaylight.yangtools.yang.common.Decimal64; import org.opendaylight.yangtools.yang.common.QName; @@ -33,9 +35,9 @@ import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils; public abstract class AbstractJukeboxTest { @FunctionalInterface - protected interface FormatMethod { + public interface FormatMethod { - void invoke(@NonNull OutputStream out) throws IOException; + void invoke(@NonNull FormatParameters format, @NonNull OutputStream out) throws IOException; } // container jukebox @@ -110,10 +112,11 @@ public abstract class AbstractJukeboxTest { return new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8)); } - protected static void assertFormat(final String expected, final FormatMethod formatMethod) { + public static void assertFormat(final String expected, final FormatMethod formatMethod, + final boolean prettyPrint) { final var baos = new ByteArrayOutputStream(); try { - formatMethod.invoke(baos); + formatMethod.invoke(new FormatParameters(PrettyPrintParam.of(prettyPrint)), baos); } catch (IOException e) { throw new AssertionError(e); } diff --git a/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/XmlNormalizedNodeBodyWriterTest.java b/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/XmlNormalizedNodeBodyWriterTest.java index 2174fdd745..8901055266 100644 --- a/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/XmlNormalizedNodeBodyWriterTest.java +++ b/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/XmlNormalizedNodeBodyWriterTest.java @@ -17,6 +17,7 @@ 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.FormatParameters; import org.opendaylight.restconf.nb.rfc8040.AbstractInstanceIdentifierTest; import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload; import org.opendaylight.restconf.nb.rfc8040.legacy.WriterParameters; @@ -35,7 +36,7 @@ public class XmlNormalizedNodeBodyWriterTest extends AbstractInstanceIdentifierT final NormalizedNodePayload nodePayload = new NormalizedNodePayload(Inference.ofDataTreePath(schemaContext), ImmutableNodes.newContainerBuilder().withNodeIdentifier(new NodeIdentifier(SchemaContext.NAME)).build(), - WriterParameters.EMPTY); + WriterParameters.EMPTY, FormatParameters.COMPACT); final ByteArrayOutputStream output = new ByteArrayOutputStream(); final XmlNormalizedNodeBodyWriter xmlWriter = new XmlNormalizedNodeBodyWriter(); @@ -60,7 +61,7 @@ public class XmlNormalizedNodeBodyWriterTest extends AbstractInstanceIdentifierT .withNodeIdentifier(new NodeIdentifier( QName.create("bar:module", "2016-09-29", "foo-bar-container"))) .build()) - .build(), WriterParameters.EMPTY); + .build(), WriterParameters.EMPTY, FormatParameters.COMPACT); final ByteArrayOutputStream output = new ByteArrayOutputStream(); final XmlNormalizedNodeBodyWriter xmlWriter = new XmlNormalizedNodeBodyWriter(); diff --git a/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/rests/transactions/AbstractRestconfStrategyTest.java b/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/rests/transactions/AbstractRestconfStrategyTest.java index 09ecdad38e..17209541fc 100644 --- a/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/rests/transactions/AbstractRestconfStrategyTest.java +++ b/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/rests/transactions/AbstractRestconfStrategyTest.java @@ -28,15 +28,17 @@ import org.junit.Before; import org.junit.Test; import org.mockito.Mock; import org.opendaylight.restconf.api.ApiPath; +import org.opendaylight.restconf.api.QueryParameters; import org.opendaylight.restconf.api.query.ContentParam; +import org.opendaylight.restconf.api.query.PrettyPrintParam; import org.opendaylight.restconf.common.errors.RestconfDocumentedException; import org.opendaylight.restconf.common.patch.PatchContext; import org.opendaylight.restconf.common.patch.PatchEntity; import org.opendaylight.restconf.nb.rfc8040.AbstractJukeboxTest; -import org.opendaylight.restconf.server.api.DataYangPatchParams; import org.opendaylight.restconf.server.api.DatabindContext; import org.opendaylight.restconf.server.api.PatchStatusContext; import org.opendaylight.restconf.server.api.PatchStatusEntity; +import org.opendaylight.restconf.server.api.ServerRequest; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.patch.rev170222.yang.patch.yang.patch.Edit.Operation; import org.opendaylight.yangtools.yang.common.ErrorTag; import org.opendaylight.yangtools.yang.common.ErrorType; @@ -210,6 +212,7 @@ abstract class AbstractRestconfStrategyTest extends AbstractJukeboxTest { .build(); private static final NodeIdentifier NODE_IDENTIFIER = new NodeIdentifier(QName.create("ns", "2016-02-28", "container")); + private static final ServerRequest REQUEST = ServerRequest.of(QueryParameters.of(), PrettyPrintParam.TRUE); @Mock private EffectiveModelContext mockSchemaContext; @@ -238,7 +241,7 @@ abstract class AbstractRestconfStrategyTest extends AbstractJukeboxTest { */ @Test public final void testDeleteData() throws Exception { - final var future = testDeleteDataStrategy().dataDELETE(ApiPath.empty()); + final var future = testDeleteDataStrategy().dataDELETE(REQUEST, ApiPath.empty()); assertNotNull(Futures.getDone(future)); } @@ -249,7 +252,7 @@ abstract class AbstractRestconfStrategyTest extends AbstractJukeboxTest { */ @Test public final void testNegativeDeleteData() { - final var future = testNegativeDeleteDataStrategy().dataDELETE(ApiPath.empty()); + final var future = testNegativeDeleteDataStrategy().dataDELETE(REQUEST, ApiPath.empty()); final var ex = assertThrows(ExecutionException.class, () -> Futures.getDone(future)).getCause(); assertThat(ex, instanceOf(RestconfDocumentedException.class)); final var errors = ((RestconfDocumentedException) ex).getErrors(); @@ -350,7 +353,7 @@ abstract class AbstractRestconfStrategyTest extends AbstractJukeboxTest { @Test public final void testDeleteNonexistentData() { - final var status = deleteNonexistentDataTestStrategy().patchData(DataYangPatchParams.COMPACT, + final var status = deleteNonexistentDataTestStrategy().patchData( new PatchContext("patchD", List.of(new PatchEntity("edit", Operation.Delete, CREATE_AND_DELETE_TARGET)))) .getOrThrow().status(); assertEquals("patchD", status.patchId()); @@ -496,8 +499,7 @@ abstract class AbstractRestconfStrategyTest extends AbstractJukeboxTest { } private static void patch(final PatchContext patchContext, final RestconfStrategy strategy, final boolean failed) { - final var patchStatusContext = strategy.patchData(DataYangPatchParams.COMPACT, patchContext).getOrThrow() - .status(); + final var patchStatusContext = strategy.patchData(patchContext).getOrThrow().status(); for (var entity : patchStatusContext.editCollection()) { if (failed) { assertTrue("Edit " + entity.getEditId() + " failed", entity.isOk()); diff --git a/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/rests/transactions/NetconfRestconfStrategyTest.java b/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/rests/transactions/NetconfRestconfStrategyTest.java index 37c7625432..974ce64c1e 100644 --- a/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/rests/transactions/NetconfRestconfStrategyTest.java +++ b/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/rests/transactions/NetconfRestconfStrategyTest.java @@ -45,7 +45,7 @@ import org.opendaylight.restconf.server.api.JsonDataPostBody; import org.opendaylight.restconf.server.api.JsonResourceBody; import org.opendaylight.restconf.server.api.PatchStatusContext; import org.opendaylight.restconf.server.api.PatchStatusEntity; -import org.opendaylight.restconf.server.api.QueryParams; +import org.opendaylight.restconf.server.api.ServerRequest; import org.opendaylight.yangtools.yang.common.ErrorSeverity; import org.opendaylight.yangtools.yang.common.ErrorTag; import org.opendaylight.yangtools.yang.common.ErrorType; @@ -113,7 +113,7 @@ public final class NetconfRestconfStrategyTest extends AbstractRestconfStrategyT doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService) .delete(LogicalDatastoreType.CONFIGURATION, song2Path); - jukeboxStrategy().delete(new SettableRestconfFuture<>(), songListPath); + jukeboxStrategy().delete(new SettableRestconfFuture<>(), null, songListPath); verify(netconfService).getConfig(songListWildcardPath, songKeyFields); verify(netconfService).delete(LogicalDatastoreType.CONFIGURATION, song1Path); verify(netconfService).delete(LogicalDatastoreType.CONFIGURATION, song2Path); @@ -257,11 +257,11 @@ public final class NetconfRestconfStrategyTest extends AbstractRestconfStrategyT doReturn(immediateFluentFuture(Optional.of(PLAYLIST_WITH_SONGS))).when(spyTx).read(songListPath); // Inserting new song at 3rd position (aka as last element) - spyStrategy.dataPUT(ApiPath.parse("example-jukebox:jukebox/playlist=0/song=3"), - new QueryParams(QueryParameters.of( - // insert new item after last existing item in list - InsertParam.AFTER, PointParam.forUriValue("example-jukebox:jukebox/playlist=0/song=2")), - PrettyPrintParam.TRUE), + spyStrategy.dataPUT(ServerRequest.of(QueryParameters.of( + // insert new item after last existing item in list + InsertParam.AFTER, PointParam.forUriValue("example-jukebox:jukebox/playlist=0/song=2")), + PrettyPrintParam.TRUE), + ApiPath.parse("example-jukebox:jukebox/playlist=0/song=3"), new JsonResourceBody(stringInputStream(""" { "example-jukebox:song" : [ @@ -295,10 +295,10 @@ public final class NetconfRestconfStrategyTest extends AbstractRestconfStrategyT doReturn(immediateFluentFuture(Optional.of(PLAYLIST_WITH_SONGS))).when(spyTx).read(songListPath); // Inserting new song at 3rd position (aka as last element) - spyStrategy.dataPOST(ApiPath.parse("example-jukebox:jukebox/playlist=0"), + spyStrategy.dataPOST(ServerRequest.of(QueryParameters.of(InsertParam.AFTER, + PointParam.forUriValue("example-jukebox:jukebox/playlist=0/song=2")), PrettyPrintParam.FALSE), + ApiPath.parse("example-jukebox:jukebox/playlist=0"), // insert new item after last existing item in list - new QueryParams(QueryParameters.of(InsertParam.AFTER, - PointParam.forUriValue("example-jukebox:jukebox/playlist=0/song=2")), PrettyPrintParam.FALSE), new JsonDataPostBody(stringInputStream(""" { "example-jukebox:song" : [ diff --git a/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/server/api/ParamsTest.java b/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/server/api/ParamsTest.java index ffad45e11a..3073912813 100644 --- a/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/server/api/ParamsTest.java +++ b/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/server/api/ParamsTest.java @@ -24,7 +24,6 @@ import org.opendaylight.restconf.api.QueryParameters; import org.opendaylight.restconf.api.query.ContentParam; import org.opendaylight.restconf.api.query.DepthParam; import org.opendaylight.restconf.api.query.FieldsParam; -import org.opendaylight.restconf.api.query.PrettyPrintParam; import org.opendaylight.restconf.api.query.RestconfQueryParam; import org.opendaylight.restconf.api.query.WithDefaultsParam; import org.opendaylight.restconf.nb.rfc8040.Insert; @@ -156,32 +155,32 @@ public class ParamsTest { assertEquals(Set.of(containerChild), fields.get(0)); } - private static void assertInvalidIAE(final Function paramsMethod, + private static void assertInvalidIAE(final Function paramsMethod, final RestconfQueryParam param) { assertParamsThrows("Invalid parameter: " + param.paramName(), paramsMethod, param.paramName(), "odl-test-value"); } - private static void assertInvalidIAE(final Function paramsMethod) { + private static void assertInvalidIAE(final Function paramsMethod) { assertParamsThrows("Invalid parameter: odl-unknown-param", paramsMethod, "odl-unknown-param", "odl-test-value"); } private static void assertParamsThrows(final String expectedMessage, - final Function paramsMethod, final String name, final String value) { + final Function paramsMethod, final String name, final String value) { assertParamsThrows(expectedMessage, paramsMethod, QueryParameters.of(name, value)); } private static void assertParamsThrows(final String expectedMessage, - final Function paramsMethod, final QueryParameters params) { + final Function paramsMethod, final QueryParameters params) { final var ex = assertThrows(IllegalArgumentException.class, () -> assertParams(paramsMethod, params)); assertEquals(expectedMessage, ex.getMessage()); } - private static T assertParams(final Function paramsMethod, final QueryParameters params) { - return paramsMethod.apply(new QueryParams(params, PrettyPrintParam.FALSE)); + private static T assertParams(final Function paramsMethod, final QueryParameters params) { + return paramsMethod.apply(params); } - private static T assertParams(final Function paramsMethod, final String name, + private static T assertParams(final Function paramsMethod, final String name, final String value) { return assertParams(paramsMethod, QueryParameters.of(name, value)); } diff --git a/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/server/spi/YangPatchStatusBodyTest.java b/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/server/spi/YangPatchStatusBodyTest.java index 0b2f24f34d..c88577b103 100644 --- a/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/server/spi/YangPatchStatusBodyTest.java +++ b/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/server/spi/YangPatchStatusBodyTest.java @@ -12,7 +12,6 @@ import static org.mockito.Mockito.mock; import java.io.IOException; import java.util.List; import org.junit.Test; -import org.opendaylight.restconf.api.query.PrettyPrintParam; import org.opendaylight.restconf.common.errors.RestconfError; import org.opendaylight.restconf.nb.rfc8040.AbstractJukeboxTest; import org.opendaylight.restconf.server.api.DatabindContext; @@ -34,8 +33,8 @@ public class YangPatchStatusBodyTest extends AbstractJukeboxTest { */ @Test public void testOutputWithGlobalError() throws IOException { - final var body = new YangPatchStatusBody(() -> PrettyPrintParam.TRUE, - new PatchStatusContext(databind, "patch", List.of(statusEntity), false, List.of(error))); + final var body = new YangPatchStatusBody(new PatchStatusContext(databind, "patch", List.of(statusEntity), false, + List.of(error))); assertFormat(""" { @@ -51,7 +50,7 @@ public class YangPatchStatusBodyTest extends AbstractJukeboxTest { ] } } - }""", body::formatToJSON); + }""", body::formatToJSON, true); assertFormat(""" patch @@ -60,7 +59,7 @@ public class YangPatchStatusBodyTest extends AbstractJukeboxTest { data-exists Data already exists - """, body::formatToXML); + """, body::formatToXML, true); } /** @@ -68,8 +67,8 @@ public class YangPatchStatusBodyTest extends AbstractJukeboxTest { */ @Test public void testOutputWithoutGlobalError() throws IOException { - final var body = new YangPatchStatusBody(() -> PrettyPrintParam.TRUE, - new PatchStatusContext(databind,"patch", List.of(statusEntityError), false, null)); + final var body = new YangPatchStatusBody(new PatchStatusContext(databind,"patch", List.of(statusEntityError), + false, null)); assertFormat(""" { @@ -92,7 +91,7 @@ public class YangPatchStatusBodyTest extends AbstractJukeboxTest { ] } } - }""", body::formatToJSON); + }""", body::formatToJSON, true); assertFormat(""" patch @@ -106,6 +105,6 @@ public class YangPatchStatusBodyTest extends AbstractJukeboxTest { - """, body::formatToXML); + """, body::formatToXML, true); } } -- 2.36.6