Rework body formatting wiring 39/111339/3
authorRobert Varga <robert.varga@pantheon.tech>
Mon, 8 Apr 2024 21:03:35 +0000 (23:03 +0200)
committerRobert Varga <robert.varga@pantheon.tech>
Tue, 9 Apr 2024 19:26:57 +0000 (21:26 +0200)
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 <robert.varga@pantheon.tech>
66 files changed:
protocol/restconf-api/src/main/java/org/opendaylight/restconf/api/FormatParameters.java
protocol/restconf-api/src/main/java/org/opendaylight/restconf/api/FormattableBody.java
protocol/restconf-api/src/main/java/org/opendaylight/restconf/api/ImmutableQueryParameters.java [deleted file]
protocol/restconf-api/src/main/java/org/opendaylight/restconf/api/QueryParameters.java
protocol/restconf-api/src/main/java/org/opendaylight/restconf/api/query/ContentParam.java
protocol/restconf-api/src/main/java/org/opendaylight/restconf/api/query/FieldsParam.java
protocol/restconf-api/src/main/java/org/opendaylight/restconf/api/query/PrettyPrintParam.java
protocol/restconf-api/src/main/java/org/opendaylight/restconf/api/query/WithDefaultsParam.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/FormattableBodyCallback.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/JaxRsFormattableBody.java [new file with mode: 0644]
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/JaxRsFormattableBodyWriter.java [moved from restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/FormattableBodyWriter.java with 65% similarity]
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/JaxRsRestconf.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/JsonJaxRsFormattableBodyWriter.java [moved from restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/JsonFormattableBody.java with 70% similarity]
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/XmlJaxRsFormattableBodyWriter.java [moved from restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/XmlFormattableBody.java with 71% similarity]
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/Insert.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/JaxRsNorthbound.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/OSGiNorthbound.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/AbstractNormalizedNodeBodyWriter.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/JsonNormalizedNodeBodyWriter.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/XmlNormalizedNodeBodyWriter.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/legacy/NormalizedNodePayload.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/legacy/WriterParameters.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/transactions/MdsalRestconfStrategy.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/transactions/NetconfRestconfStrategy.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/transactions/RestconfStrategy.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/streams/DefaultRestconfStreamServletFactory.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/streams/RestconfStreamServletFactory.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/streams/SSEStreamService.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/DataGetParams.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/DataYangPatchParams.java [deleted file]
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/DataYangPatchResult.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/DatabindFormattableBody.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/DatabindPathFormattableBody.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/EventStreamGetParams.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/FormatParametersHelper.java [deleted file]
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/InvokeParams.java [deleted file]
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/ModulesGetResult.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/QueryParams.java [deleted file]
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/RestconfServer.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/ServerRequest.java [new file with mode: 0644]
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/mdsal/MdsalRestconfServer.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/AllOperations.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/FailedHttpGetResource.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/HttpGetResource.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/NormalizedFormattableBody.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/OneOperation.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/OperationOutputBody.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/OperationsBody.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/OperationsResource.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/RestconfServerConfiguration.java [deleted file]
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/YangLibraryVersionResource.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/YangPatchStatusBody.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/AbstractRestconfTest.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/Netconf799Test.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/Netconf822Test.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/RestconfDataPatchTest.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/RestconfOperationsGetTest.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/RestconfSchemaServiceMountTest.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/RestconfSchemaServiceTest.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/RestconfYangLibraryVersionGetTest.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/AbstractJukeboxTest.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/XmlNormalizedNodeBodyWriterTest.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/rests/transactions/AbstractRestconfStrategyTest.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/rests/transactions/NetconfRestconfStrategyTest.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/server/api/ParamsTest.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/server/spi/YangPatchStatusBodyTest.java

index 9a3e59d636776716486774cb328648ad3d60e0bd..7a1738ce41fa1236d347382f064003cc568fb124 100644 (file)
@@ -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);
+    }
 }
index ae277a23efcea042eb88631516e94eee70d92fd9..870bcd2d28df8a9e3dbdedd95feb65c13d11092a 100644 (file)
@@ -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 (file)
index 89bad2b..0000000
+++ /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<String, String> params) implements QueryParameters {
-    static final ImmutableQueryParameters EMPTY = new ImmutableQueryParameters(ImmutableMap.of());
-
-    ImmutableQueryParameters {
-        requireNonNull(params);
-    }
-
-    ImmutableQueryParameters(final Collection<RestconfQueryParam<?>> 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<Entry<String, String>> 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 + ")";
-    }
-}
index 732bfcd6f60a8b96cc45a63251ac677ed62c8c08..91ef6e2d9278696bf2724da77c271cedafe091b7 100644 (file)
@@ -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<String, String> params;
 
-    Collection<? extends Entry<String, String>> asCollection();
+    private QueryParameters(final ImmutableMap<String, String> params) {
+        this.params = requireNonNull(params);
+    }
 
-    @Nullable String lookup(String paramName);
+    private QueryParameters(final Collection<RestconfQueryParam<?>> 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<String, String> entry) {
+    public static QueryParameters of(final Entry<String, String> 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<RestconfQueryParam<?>> params) {
+    public static QueryParameters of(final Collection<RestconfQueryParam<?>> params) {
         return params instanceof List ? of((List<RestconfQueryParam<?>>) 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<RestconfQueryParam<?>> params) {
+    public static QueryParameters of(final List<RestconfQueryParam<?>> 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<String, String> params) {
-        return params.isEmpty() ? of() : new ImmutableQueryParameters(ImmutableMap.copyOf(params));
+    public static QueryParameters of(final Map<String, String> 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<String, List<String>> multiParams) {
+    public static QueryParameters ofMultiValue(final Map<String, List<String>> 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<Entry<String, String>> asCollection() {
+        return params.entrySet();
+    }
+
+    public @Nullable String lookup(final String paramName) {
+        return params.get(requireNonNull(paramName));
     }
+
+    public <T> @Nullable T lookup(final String paramName, final Function<String, T> 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 + ")";
+    }
+
 }
index 3f95f311f2441fe690ab1552fe00c6ada7dbfbc2..1c0354ea8a2ca50d038152e2477457be744b8c12 100644 (file)
@@ -60,7 +60,7 @@ public enum ContentParam implements RestconfQueryParam<ContentParam> {
             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 + "'");
         };
     }
 }
index 2a0cd640ba38b74a7d00bd346eae5f7b689b8d23..d81d8000b4f9de1aa3859f67409f58cadccf090d 100644 (file)
@@ -114,7 +114,8 @@ public final class FieldsParam implements RestconfQueryParam<FieldsParam> {
         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);
         }
     }
 
index 6a70d3788327a41ecac1a21fe82a9ff45c38c389..9031fbd1d62f6cfd8c22e66505e4e22242c82a29 100644 (file)
@@ -39,7 +39,8 @@ public final class PrettyPrintParam implements RestconfQueryParam<PrettyPrintPar
         return switch (uriValue) {
             case "false" -> 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 + "'");
         };
     }
 
index c2a30c62d48e4c94b169822e3efefdf73114a24b..147fada3a80da1a89cc228509738a882d07e4b0c 100644 (file)
@@ -60,7 +60,11 @@ public enum WithDefaultsParam implements RestconfQueryParam<WithDefaultsParam> {
     }
 
     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
index 045875293f11b7a3173ff5c8685e9a52a276a2b5..8d5aebb00be3ca6bc1ce35fa7fe5cc2abc4863ae 100644 (file)
@@ -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<FormattableBody> {
-    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 (file)
index 0000000..2ebe205
--- /dev/null
@@ -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);
+    }
+}
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 10aa2fccdc8e0504ed4842d54efb59558b70176c..f92e09ba175f5b679612bdf57149cc41cd009337 100644 (file)
@@ -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<FormattableBody>
-        permits JsonFormattableBody, XmlFormattableBody {
+abstract sealed class JaxRsFormattableBodyWriter implements MessageBodyWriter<JaxRsFormattableBody>
+        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<Formatt
     }
 
     @Override
-    public final void writeTo(final FormattableBody body, final Class<?> 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<String, Object> 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;
 }
index 69803105dcb0a81fa102634b16dbb999e1736b0a..d0fb42952352af081f8976243e338b0b8d42c663 100644 (file)
@@ -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<T>) 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<DataGetResult> 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<? extends DataPostResult> future, final UriInfo uriInfo,
-            final AsyncResponse ar) {
+    private static void completeDataPOST(final RestconfFuture<? extends DataPostResult> future,
+            final FormatParameters format, final UriInfo uriInfo, final AsyncResponse ar) {
         future.addCallback(new JaxRsRestconfCallback<DataPostResult>(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<ModulesGetResult> future, final AsyncResponse ar) {
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 91884746a8a81e565eadd7795b67760eeaf6ca82..f012e0d0134a29a0480429469e11ba58fbe4b49d 100644 (file)
@@ -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);
     }
 }
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 055abe0aee6349a9c265a05d7ace0461f7d5b43f..5f047a31d6fc00a5888f13445d1e0f93c2f6445a 100644 (file)
@@ -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);
     }
 }
index 22e2272da54d77d6f75ab09b42f7069978bad8ef..bd550434b7b4f7c10091d8ce1e19cc2fb3da9cb3 100644 (file)
@@ -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;
 
index 41615f101087fd23e3eeec7b9c299cbbf52166e1..af81a9dbe322abd3606eeb30a39d9f6ee8833776 100644 (file)
@@ -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<Object> 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)
index a77cf17a0b147c6e5c8193e9dff29cd6bfcb0ad7..3cdef59ba0514d4dbf0fc598a95f33871fba639c 100644 (file)
@@ -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());
index b94a575962d88becd93c068112033aa22f2e8600..872be8f01dfe18faaa228a0cc474d615e8227ae1 100644 (file)
@@ -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<Nor
     public final void writeTo(final NormalizedNodePayload context, final Class<?> type, final Type genericType,
             final Annotation[] annotations, final MediaType mediaType, final MultivaluedMap<String, Object> httpHeaders,
             final OutputStream entityStream) throws IOException {
-        writeData(context.inference().toSchemaInferenceStack(), context.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;
 }
index fa56013fe022592a55b45a70b78cec5295ff0382..bcb65ee12cf7cf49cf1ddadb1c1f733253045575 100644 (file)
@@ -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);
index f6fa2ef07b296f52a3e542c8820f451b936a0ab0..40092e5637fb8efa4e3fb227a0c1dece42bbce87 100644 (file)
@@ -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
index fc392fb758008a442aa69510cf60e300efc3f281..c8c9d7221a11d4b6d6099a8eb723d8ce104be528 100644 (file)
@@ -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);
     }
 }
index 21876baa4b8a9ed229bfb6fb5fe59e67ee8ff234..3e8a8b7c891c13d353b47daa64697107aefa0ae1 100644 (file)
@@ -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<Set<QName>> 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<Set<QName>> 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);
     }
 }
index 6ee59fc26df44ccf16e7e96c257fe8fa3371c6d1..9c65daff0877c27ed9b91bf9e6de7917b7676c8a 100644 (file)
@@ -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<FormattableBody> yangLibraryVersionGET(final QueryParams params) {
-        return yangLibraryVersion.httpGET(params);
+    public RestconfFuture<FormattableBody> yangLibraryVersionGET(final ServerRequest request) {
+        return yangLibraryVersion.httpGET(request);
     }
 
     @Override
@@ -89,7 +89,8 @@ public final class MdsalRestconfStrategy extends RestconfStrategy {
     }
 
     @Override
-    void delete(final SettableRestconfFuture<Empty> future, final YangInstanceIdentifier path) {
+    void delete(final SettableRestconfFuture<Empty> 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<DataGetResult> dataGET(final Data path, final DataGetParams params) {
+    RestconfFuture<DataGetResult> 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);
     }
index b3259b1d923db938e18f2578c6a344b7b49bfcd9..00adede4ea308f83a77624e3fed3e0cadcfe0213 100644 (file)
@@ -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<Empty> future, final YangInstanceIdentifier path) {
+    void delete(final SettableRestconfFuture<Empty> future, final ServerRequest request,
+            final YangInstanceIdentifier path) {
         final var tx = prepareWriteExecution();
         tx.delete(path);
         Futures.addCallback(tx.commit(), new FutureCallback<CommitInfo>() {
@@ -100,7 +102,7 @@ public final class NetconfRestconfStrategy extends RestconfStrategy {
     }
 
     @Override
-    RestconfFuture<DataGetResult> dataGET(final Data path, final DataGetParams params) {
+    RestconfFuture<DataGetResult> dataGET(final ServerRequest request, final Data path, final DataGetParams params) {
         final var inference = path.inference();
         final var fields = params.fields();
         final List<YangInstanceIdentifier> fieldPaths;
@@ -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
index d62fd8e18c62a4114a5558a5581fff27f821d73a..7cad275e02443289850383813e0a7c2730694427 100644 (file)
@@ -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<DataPutResult> dataPUT(final ApiPath apiPath, final QueryParams params,
+    public @NonNull RestconfFuture<DataPutResult> 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<DataYangPatchResult> 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<DataYangPatchResult> 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<DataYangPatchResult> patchData(final DataYangPatchParams params,
-            final PatchContext patch) {
+    public final @NonNull RestconfFuture<DataYangPatchResult> patchData(final PatchContext patch) {
         final var editCollection = new ArrayList<PatchStatusEntity>();
         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<CommitInfo>() {
             @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<Empty> dataDELETE(final ApiPath apiPath) {
+    public final RestconfFuture<Empty> 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<Empty>();
-        delete(ret, path.instance());
+        delete(ret, request, path.instance());
         return ret;
     }
 
-    abstract void delete(@NonNull SettableRestconfFuture<Empty> future, @NonNull YangInstanceIdentifier path);
+    @NonNullByDefault
+    abstract void delete(SettableRestconfFuture<Empty> future, ServerRequest request, YangInstanceIdentifier path);
 
-    public final @NonNull RestconfFuture<DataGetResult> dataGET(final ApiPath apiPath, final QueryParams params) {
+    public final @NonNull RestconfFuture<DataGetResult> 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<DataGetResult> dataGET(Data path, DataGetParams params);
+    abstract @NonNull RestconfFuture<DataGetResult> dataGET(ServerRequest request, Data path, DataGetParams params);
 
-    static final @NonNull RestconfFuture<DataGetResult> completeDataGET(final Inference inference,
-            final WriterParameters writerParams, final @Nullable NormalizedNode node,
+    static final @NonNull RestconfFuture<DataGetResult> 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<FormattableBody> operationsGET(final QueryParams params) {
-        return operations.httpGET(params);
+    public RestconfFuture<FormattableBody> operationsGET(final ServerRequest request) {
+        return operations.httpGET(request);
     }
 
     @NonNullByDefault
-    public RestconfFuture<FormattableBody> operationsGET(final ApiPath apiPath, final QueryParams params) {
-        return operations.httpGET(apiPath, params);
+    public RestconfFuture<FormattableBody> operationsGET(final ServerRequest request, final ApiPath apiPath) {
+        return operations.httpGET(request, apiPath);
     }
 
-    public @NonNull RestconfFuture<InvokeResult> operationsPOST(final URI restconfURI, final ApiPath apiPath,
-            final QueryParams params, final OperationInputBody body) {
+    public @NonNull RestconfFuture<InvokeResult> 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<CharSource> resolveSource(final SourceIdentifier source,
@@ -1397,10 +1380,10 @@ public abstract class RestconfStrategy {
             ErrorType.APPLICATION, ErrorTag.DATA_MISSING));
     }
 
-    public final @NonNull RestconfFuture<? extends DataPostResult> dataPOST(final ApiPath apiPath,
-            final QueryParams params, final DataPostBody body) {
+    public final @NonNull RestconfFuture<? extends DataPostResult> 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<CreateResourceResult> dataCreatePOST(final QueryParams params,
+    public @NonNull RestconfFuture<CreateResourceResult> 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<CreateResourceResult> dataCreatePOST(final DatabindPath.Data path,
-            final QueryParams params, final ChildBody body) {
+    private @NonNull RestconfFuture<CreateResourceResult> 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<InvokeResult> 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)));
     }
 
     /**
index a909a76cc42c4abcc01e1342ae2e48fcafed1a91..5c02131a36ed73746f90d7fdc5ffc29b5a2d3ee9 100644 (file)
@@ -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<String, ?> 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,
index 14848ad48c2567f6b676bb2836d56be174f1f8f0..0ec164a1a3f5baf8857c1cfd7c1a651658a1f8d0 100644 (file)
@@ -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();
 }
index de1cc95b4a1b77de42249bf8c641f7b1cf7408a7..0fc05b824f9505b380affd9ed6c4a0ec078b50e1 100644 (file)
@@ -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);
         }
index d32cab3a4658a9204b5fa390d4e57db6fd1b1a6c..ee7994af6efd475e10936081b1a93913e9404e9f 100644 (file)
@@ -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 <T> @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 (file)
index b94e92a..0000000
+++ /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);
-    }
-}
index 513d5c9ca22515da6e8944269d92e164a5e04c99..13d1543f729d83f9aab136f662d479ba49a5177a 100644 (file)
@@ -17,13 +17,11 @@ import org.eclipse.jdt.annotation.Nullable;
  * Result of a {@code PATCH} request as defined in
  * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
  *
- * @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);
     }
 }
index 71daa7645ecb96d7094be287730ea5aa03b1f85e..d1aaf2d0902f70fa547bc3346ada517e9002be42 100644 (file)
@@ -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;
 }
index 2a09b34f61741fe412f2f85c4f0e0d6357f448f9..4b113505c8b9e267fb5f1b45bd3f0f36e63c48a9 100644 (file)
@@ -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<P extends DatabindPath> 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<P extends DatabindPath> 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();
index 21795c9be99813f2f5fd16e71be8af284e0ef589..2f10ca38485f6db04bd429778380bc8d229220cf 100644 (file)
@@ -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 (file)
index 1cdd567..0000000
+++ /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 <T extends FormatParameters> @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 (file)
index 012b291..0000000
+++ /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);
-    }
-}
index 941b442e5692998a460250e542cf33f3f384eca4..d51d9800dfa18472a18c6cf2909d49044b5a1833 100644 (file)
@@ -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 (file)
index 9ba0f37..0000000
+++ /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<? extends Entry<String, String>> 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;
-    }
-}
index 0586fd20e5bac14f0c27446d7de6ed520903c01a..c4d637d95ae760a037ddc70b3268a3a9b13abfbe 100644 (file)
@@ -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<Empty> dataDELETE(ApiPath identifier);
+    RestconfFuture<Empty> 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<DataGetResult> dataGET(QueryParameters params);
+    RestconfFuture<DataGetResult> 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<DataGetResult> dataGET(ApiPath identifier, QueryParameters params);
+    RestconfFuture<DataGetResult> dataGET(ServerRequest request, ApiPath identifier);
 
     /**
      * Partially modify the target data resource, as defined in
      * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
      *
+     * @param request {@link ServerRequest} for this request
      * @param body data node for put to config DS
      * @return A {@link RestconfFuture} of the operation
      */
-    RestconfFuture<DataPatchResult> dataPATCH(ResourceBody body);
+    RestconfFuture<DataPatchResult> dataPATCH(ServerRequest request, ResourceBody body);
 
     /**
      * Partially modify the target data resource, as defined in
      * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
      *
+     * @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<DataPatchResult> dataPATCH(ApiPath identifier, ResourceBody body);
+    RestconfFuture<DataPatchResult> dataPATCH(ServerRequest request, ApiPath identifier, ResourceBody body);
 
     /**
      * Ordered list of edits that are applied to the datastore by the server, as defined in
      * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
      *
-     * @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<DataYangPatchResult> dataPATCH(QueryParameters params, PatchBody body);
+    RestconfFuture<DataYangPatchResult> dataPATCH(ServerRequest request, PatchBody body);
 
     /**
      * Ordered list of edits that are applied to the datastore by the server, as defined in
      * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
      *
+     * @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<DataYangPatchResult> dataPATCH(ApiPath identifier, QueryParameters params, PatchBody body);
+    RestconfFuture<DataYangPatchResult> dataPATCH(ServerRequest request, ApiPath identifier, PatchBody body);
 
-    RestconfFuture<CreateResourceResult> dataPOST(QueryParameters params, ChildBody body);
+    RestconfFuture<CreateResourceResult> dataPOST(ServerRequest request, ChildBody body);
 
     /**
      * Create or invoke a operation, as described in
      * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.4">RFC8040 section 4.4</a>.
      *
+     * @param request {@link ServerRequest} for this request
      * @param identifier path to target
-     * @param params query parameters
      * @param body body of the post request
      */
-    RestconfFuture<? extends DataPostResult> dataPOST(ApiPath identifier, QueryParameters params, DataPostBody body);
+    RestconfFuture<? extends DataPostResult> dataPOST(ServerRequest request, ApiPath identifier, DataPostBody body);
 
     /**
      * Replace the data store, as described in
      * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.5">RFC8040 section 4.5</a>.
      *
+     * @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<DataPutResult> dataPUT(QueryParameters params, ResourceBody body);
+    RestconfFuture<DataPutResult> dataPUT(ServerRequest request, ResourceBody body);
 
     /**
      * Create or replace a data store resource, as described in
      * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.5">RFC8040 section 4.5</a>.
      *
+     * @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<DataPutResult> dataPUT(ApiPath identifier, QueryParameters params, ResourceBody body);
+    RestconfFuture<DataPutResult> 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 <a href="https://www.rfc-editor.org/rfc/rfc8040#page-84">ietf-restconf.yang</a>
      * {@code container operations} statement.
      *
+     * @param request {@link ServerRequest} for this request
      * @return A {@link RestconfFuture} completing with an {@link FormattableBody}
      */
-    RestconfFuture<FormattableBody> operationsGET();
+    RestconfFuture<FormattableBody> operationsGET(ServerRequest request);
 
     /*
      * Return the details about a particular operation supported by
@@ -138,42 +140,44 @@ public interface RestconfServer {
      * <a href="https://www.rfc-editor.org/rfc/rfc8040#page-84">ietf-restconfig.yang</a>
      * {@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<FormattableBody> operationsGET(ApiPath operation);
+    RestconfFuture<FormattableBody> operationsGET(ServerRequest request, ApiPath operation);
 
     /**
      * Invoke an RPC operation, as defined in
      * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-3.6">RFC8040 Operation Resource</a>.
      *
+     * @param request {@link ServerRequest} for this request
      * @param restconfURI Base URI of the request
      * @param operation {@code <operation>} 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<InvokeResult> operationsPOST(URI restconfURI, ApiPath operation, QueryParameters params,
+    RestconfFuture<InvokeResult> 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
      * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-3.3.3">RFC8040 {+restconf}/yang-library-version</a>.
      *
+     * @param request {@link ServerRequest} for this request
      * @return A {@link RestconfFuture} completing with {@link NormalizedNodePayload} containing a single
      *        {@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<FormattableBody> yangLibraryVersionGET();
+    RestconfFuture<FormattableBody> yangLibraryVersionGET(ServerRequest request);
 
-    RestconfFuture<ModulesGetResult> modulesYangGET(String fileName, @Nullable String revision);
+    RestconfFuture<ModulesGetResult> modulesYangGET(ServerRequest request, String fileName, @Nullable String revision);
 
-    RestconfFuture<ModulesGetResult> modulesYangGET(ApiPath mountPath, String fileName, @Nullable String revision);
+    RestconfFuture<ModulesGetResult> modulesYangGET(ServerRequest request, ApiPath mountPath, String fileName,
+        @Nullable String revision);
 
-    RestconfFuture<ModulesGetResult> modulesYinGET(String fileName, @Nullable String revision);
+    RestconfFuture<ModulesGetResult> modulesYinGET(ServerRequest request, String fileName, @Nullable String revision);
 
-    RestconfFuture<ModulesGetResult> modulesYinGET(ApiPath mountPath, String fileName, @Nullable String revision);
+    RestconfFuture<ModulesGetResult> 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 (file)
index 0000000..a675189
--- /dev/null
@@ -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:
+ * <ul>
+ *   <li>HTTP request {@link #queryParameters() query parameters},</li>
+ *   <li>{@link #format() format parameters}, including those affected by query parameters<li>
+ * </ul>
+ * It notably does <b>not</b> 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);
+    }
+}
index 0eaaca945efbf54a585150dde5ce5b0b839ba96c..3fc09ce4b82dd6693fd612f5bcf55dcee7f44568 100644 (file)
@@ -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<RpcImplementation> 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<RpcImplementation> 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<RpcImplementation> 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<RpcImplementation> 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<Empty> dataDELETE(final ApiPath identifier) {
+    public RestconfFuture<Empty> 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<DataGetResult> dataGET(final QueryParameters params) {
-        return localStrategy().dataGET(ApiPath.empty(), queryParams(params));
+    public RestconfFuture<DataGetResult> dataGET(final ServerRequest request) {
+        return localStrategy().dataGET(request, ApiPath.empty());
     }
 
     @Override
-    public RestconfFuture<DataGetResult> dataGET(final ApiPath identifier, final QueryParameters params) {
+    public RestconfFuture<DataGetResult> 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<DataPatchResult> dataPATCH(final ResourceBody body) {
+    public RestconfFuture<DataPatchResult> dataPATCH(final ServerRequest request, final ResourceBody body) {
         return localStrategy().dataPATCH(ApiPath.empty(), body);
     }
 
     @Override
-    public RestconfFuture<DataPatchResult> dataPATCH(final ApiPath identifier, final ResourceBody body) {
+    public RestconfFuture<DataPatchResult> 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<DataYangPatchResult> dataPATCH(final QueryParameters params, final PatchBody body) {
-        return localStrategy().dataPATCH(ApiPath.empty(), queryParams(params), body);
+    public RestconfFuture<DataYangPatchResult> dataPATCH(final ServerRequest request, final PatchBody body) {
+        return localStrategy().dataPATCH(ApiPath.empty(), body);
     }
 
     @Override
-    public RestconfFuture<DataYangPatchResult> dataPATCH(final ApiPath identifier, final QueryParameters params,
+    public RestconfFuture<DataYangPatchResult> 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<CreateResourceResult> dataPOST(final QueryParameters params, final ChildBody body) {
-        return localStrategy().dataCreatePOST(queryParams(params), body);
+    public RestconfFuture<CreateResourceResult> dataPOST(final ServerRequest request, final ChildBody body) {
+        return localStrategy().dataCreatePOST(request, body);
     }
 
     @Override
-    public RestconfFuture<? extends DataPostResult> dataPOST(final ApiPath identifier, final QueryParameters params,
+    public RestconfFuture<? extends DataPostResult> 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<DataPutResult> dataPUT(final QueryParameters params, final ResourceBody body) {
-        return localStrategy().dataPUT(ApiPath.empty(), queryParams(params), body);
+    public RestconfFuture<DataPutResult> dataPUT(final ServerRequest request, final ResourceBody body) {
+        return localStrategy().dataPUT(request, ApiPath.empty(), body);
     }
 
     @Override
-    public RestconfFuture<DataPutResult> dataPUT(final ApiPath identifier, final QueryParameters params,
+    public RestconfFuture<DataPutResult> 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<ModulesGetResult> modulesYangGET(final String fileName, final String revision) {
+    public RestconfFuture<ModulesGetResult> modulesYangGET(final ServerRequest request, final String fileName,
+            final String revision) {
         return modulesGET(fileName, revision, YangTextSource.class);
     }
 
     @Override
-    public RestconfFuture<ModulesGetResult> modulesYangGET(final ApiPath mountPath, final String fileName,
-            final String revision) {
+    public RestconfFuture<ModulesGetResult> modulesYangGET(final ServerRequest request, final ApiPath mountPath,
+            final String fileName, final String revision) {
         return modulesGET(mountPath, fileName, revision, YangTextSource.class);
     }
 
     @Override
-    public RestconfFuture<ModulesGetResult> modulesYinGET(final String fileName, final String revision) {
+    public RestconfFuture<ModulesGetResult> modulesYinGET(final ServerRequest request, final String fileName,
+            final String revision) {
         return modulesGET(fileName, revision, YinTextSource.class);
     }
 
     @Override
-    public RestconfFuture<ModulesGetResult> modulesYinGET(final ApiPath mountPath, final String fileName,
-            final String revision) {
+    public RestconfFuture<ModulesGetResult> 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<FormattableBody> operationsGET() {
-        return localStrategy().operationsGET(emptyQueryParams);
+    public RestconfFuture<FormattableBody> operationsGET(final ServerRequest request) {
+        return localStrategy().operationsGET(request);
     }
 
     @Override
-    public RestconfFuture<FormattableBody> operationsGET(final ApiPath operation) {
+    public RestconfFuture<FormattableBody> 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<InvokeResult> operationsPOST(final URI restconfURI, final ApiPath apiPath,
-            final QueryParameters params, final OperationInputBody body) {
+    public RestconfFuture<InvokeResult> 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<FormattableBody> yangLibraryVersionGET() {
-        return localStrategy().yangLibraryVersionGET(emptyQueryParams);
+    public RestconfFuture<FormattableBody> yangLibraryVersionGET(final ServerRequest request) {
+        return localStrategy().yangLibraryVersionGET(request);
     }
 }
index 5266ecf7ca693d8eaeaa2f75e9ae1a4ae1900118..ee3926023a4e9bb1e7f468684c29acc0c7e51280 100644 (file)
@@ -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</operations>");
     }
+
+    @Override
+    protected ToStringHelper addToStringAttributes(final ToStringHelper helper) {
+        return helper.add("rpcs", rpcs);
+    }
 }
\ No newline at end of file
index 09cabde2fa046e45a133d18219eb999e04c184d2..e7c4ec920375710ed921384bf073fd945e7cb977 100644 (file)
@@ -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<FormattableBody> httpGET(final QueryParams params) {
+    public RestconfFuture<FormattableBody> httpGET(final ServerRequest request) {
         return RestconfFuture.failed(cause);
     }
 
     @Override
-    public RestconfFuture<FormattableBody> httpGET(final ApiPath apiPath, final QueryParams params) {
+    public RestconfFuture<FormattableBody> httpGET(final ServerRequest request, final ApiPath apiPath) {
         throw new UnsupportedOperationException();
     }
 }
index a616a0a6297ab35e1cd3e44d58365f6825715d47..86b57b9c5b734b94f76d014f79628ecfff6c90b5 100644 (file)
@@ -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<FormattableBody> httpGET(QueryParams params);
+    RestconfFuture<FormattableBody> httpGET(ServerRequest request);
 
-    RestconfFuture<FormattableBody> httpGET(ApiPath apiPath, QueryParams params);
+    RestconfFuture<FormattableBody> httpGET(ServerRequest request, ApiPath apiPath);
 }
index 2088ed7a7e2ab81e35d5f4ef6873fc70b5fc7284..ae06d5e013f738ff620dcf4e8ec5e36b7e8a3267 100644 (file)
@@ -39,9 +39,8 @@ public final class NormalizedFormattableBody<N extends NormalizedNode> 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<N extends NormalizedNode> 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<N extends NormalizedNode> 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 {
index 1d8ad60efc59b4b2f099a3092f0da536eac1dcd0..f80cd6d32c1f711ecc651dc37fd576afe9a2eca6 100644 (file)
@@ -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
index eb348307c9caaf1424db211d71560d84ca9c8fce..a80724ba5da738be6faa7e5edfa3221f89420e04 100644 (file)
@@ -35,8 +35,8 @@ import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
 public final class OperationOutputBody extends DatabindPathFormattableBody<OperationPath> {
     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<Opera
     }
 
     @Override
-    protected void formatToJSON(final OutputStream out, final FormatParameters format)
-            throws IOException {
+    public void formatToJSON(final FormatParameters format, final OutputStream out) throws IOException {
         final var stack = prepareStack();
 
         // RpcDefinition/ActionDefinition is not supported as initial codec in JSONStreamWriter, so we need to emit
@@ -72,7 +71,7 @@ public final class OperationOutputBody extends DatabindPathFormattableBody<Opera
     }
 
     @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 stack = prepareStack();
 
         // RpcDefinition/ActionDefinition is not supported as initial codec in XMLStreamWriter, so we need to emit
index b7de003d67300d9869d34c188dd54d44f7c98038..d335eaef4182adeee97b6c5984d07c33efef3f5a 100644 (file)
@@ -17,7 +17,6 @@ import java.nio.charset.StandardCharsets;
 import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.restconf.api.FormatParameters;
 import org.opendaylight.restconf.api.FormattableBody;
-import org.opendaylight.restconf.api.query.PrettyPrintParam;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.common.QNameModule;
 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
@@ -29,12 +28,11 @@ abstract sealed class OperationsBody extends FormattableBody permits AllOperatio
     private final EffectiveModelContext modelContext;
 
     OperationsBody(final EffectiveModelContext modelContext) {
-        super(() -> 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);
         }
index 7a041d798a0abdcd9d241859b98a2e911376113a..553cb57add605555e9dc5628787d564f7ce5dd3a 100644 (file)
@@ -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<FormattableBody> httpGET(final QueryParams params) {
+    public RestconfFuture<FormattableBody> 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<XMLNamespace, Map<Revision, ImmutableSet<QName>>>();
         final var modelContext = pathNormalizer.databind().modelContext();
@@ -69,7 +69,7 @@ public final class OperationsResource implements HttpGetResource {
     }
 
     @Override
-    public RestconfFuture<FormattableBody> httpGET(final ApiPath apiPath, final QueryParams params) {
+    public RestconfFuture<FormattableBody> 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 (file)
index 4b060ee..0000000
+++ /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
index f6944c2d956ae13cc60bd7b71dadc04a74216a9b..3b567fbe039ffadbdebc8fc437497720cb283dc8 100644 (file)
@@ -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<FormattableBody> httpGET(final QueryParams params) {
-        return RestconfFuture.of(new NormalizedFormattableBody<>(params::prettyPrint, databind, restconf, leaf));
+    public RestconfFuture<FormattableBody> httpGET(final ServerRequest request) {
+        return RestconfFuture.of(new NormalizedFormattableBody<>(databind, restconf, leaf));
     }
 
     @Override
-    public RestconfFuture<FormattableBody> httpGET(final ApiPath apiPath, final QueryParams params) {
+    public RestconfFuture<FormattableBody> httpGET(final ServerRequest request, final ApiPath apiPath) {
         throw new UnsupportedOperationException();
     }
 }
index fb443d095774f4203edd1612b1be22d7356acb7a..10f0f8e0e4115dc492760b9aafaa34bc56a4e7f3 100644 (file)
@@ -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);
+    }
 }
index 9bf0a0d25ee6f29e27409276811ee00fb6320101..c9efc3211d20a179059882e45a12393132273c22 100644 (file)
@@ -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<NormalizedNodePayload> 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<AsyncResponse> invocation) {
-        return assertEntity(FormattableBody.class, status, invocation);
+    static final FormattableBody assertFormattableBody(final int status, final Consumer<AsyncResponse> invocation) {
+        return assertEntity(JaxRsFormattableBody.class, status, invocation).body();
     }
 
     static final ContainerNode assertOperationOutput(final int status, final Consumer<AsyncResponse> invocation) {
index 0cc19592595fccca012fdca72b93da498a9fb489..a7f4a4b6f5b17f5509882b3db44385467b936a4b 100644 (file)
@@ -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("""
-            <output xmlns="instance:identifier:module"><timestamp>somevalue</timestamp></output>""", 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("""
+            <output xmlns="instance:identifier:module"><timestamp>somevalue</timestamp></output>""", body::formatToXML,
+            false);
     }
 }
index a112e4a7b7a291148e760979d6870e3b4c83e4f2..38a1e1ae779daf0a3cee92efacde24e778d6f34c 100644 (file)
@@ -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("""
             <operations xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf">
               <new xmlns="foo"/>
               <new1 xmlns="foo"/>
-            </operations>""", body::formatToXML);
+            </operations>""", 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("""
-            <new1 xmlns="foo"/>""", body::formatToXML);
+            <new1 xmlns="foo"/>""", body::formatToXML, false);
     }
 }
index 84f4e00a7734cf507574e3336e0d8813adaa011f..b9df9bd9796cf78c5f97405a6faad66fa350c163 100644 (file)
@@ -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 {
             <yang-patch-status xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
               <patch-id>test patch id</patch-id>
               <ok/>
-            </yang-patch-status>""", body::formatToXML);
+            </yang-patch-status>""", body::formatToXML, true);
     }
 }
index 1cf3ad3ff31daac3859d27cf540983a7da60e0a1..cf01fd7bc4b58bab63e2d4a1e80b4072a7265d31 100644 (file)
@@ -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("""
-            <dummy-rpc1-module1 xmlns="module:1"/>""", body::formatToXML);
+            <dummy-rpc1-module1 xmlns="module:1"/>""", body::formatToXML, false);
     }
 }
index 36f8d96964ff45b9f43fda706d00e6ede39b67fa..43213f3f5b1dcda655e28dbf2664733da1eff976 100644 (file)
@@ -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);
     }
 
     /**
index 5bd3a28de41befa1545ae006a63efad55b555248..d6c466f64f39626d55e47ab073a53d4d395eccd2 100644 (file)
@@ -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);
     }
 
     /**
index 1dac52090f1b4ea62a1bbce0bb0abfe0ecd44432..df77098206ea31b637cd938bac553c96595d00bf 100644 (file)
@@ -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("""
             <yang-library-version xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf">2019-01-04\
-            </yang-library-version>""", body::formatToXML);
+            </yang-library-version>""", body::formatToXML, false);
     }
 }
index bf7545cf9aeaab96c3c9deffb301633ed61eda20..cd0453594bc6f3180dae146ad2bc5f6620876076 100644 (file)
@@ -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);
         }
index 2174fdd7455ccd1fa951b703b43f817c942e222c..8901055266b6c1b1afb5dc372252340ba365fa07 100644 (file)
@@ -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();
index 09ecdad38e0fe0f8b587478dee5791add36ddd38..17209541fca35e4c484e062b15c7cb3d1325f0bb 100644 (file)
@@ -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());
index 37c762543288f6e9234212398ca6effe9ade1d2d..974ce64c1ecad1d4a469314a9a3bee39798c121d 100644 (file)
@@ -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" : [
index ffad45e11af7e92d8501334c6b6c143f761ba08c..307391281348f4240d1f2b7d8561793fd56bdfd9 100644 (file)
@@ -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<QueryParams, ?> paramsMethod,
+    private static void assertInvalidIAE(final Function<QueryParameters, ?> paramsMethod,
             final RestconfQueryParam<?> param) {
         assertParamsThrows("Invalid parameter: " + param.paramName(), paramsMethod, param.paramName(),
             "odl-test-value");
     }
 
-    private static void assertInvalidIAE(final Function<QueryParams, ?> paramsMethod) {
+    private static void assertInvalidIAE(final Function<QueryParameters, ?> paramsMethod) {
         assertParamsThrows("Invalid parameter: odl-unknown-param", paramsMethod, "odl-unknown-param", "odl-test-value");
     }
 
     private static void assertParamsThrows(final String expectedMessage,
-            final Function<QueryParams, ?> paramsMethod, final String name, final String value) {
+            final Function<QueryParameters, ?> paramsMethod, final String name, final String value) {
         assertParamsThrows(expectedMessage, paramsMethod, QueryParameters.of(name, value));
     }
 
     private static void assertParamsThrows(final String expectedMessage,
-            final Function<QueryParams, ?> paramsMethod, final QueryParameters params) {
+            final Function<QueryParameters, ?> paramsMethod, final QueryParameters params) {
         final var ex = assertThrows(IllegalArgumentException.class, () -> assertParams(paramsMethod, params));
         assertEquals(expectedMessage, ex.getMessage());
     }
 
-    private static <T> T assertParams(final Function<QueryParams, T> paramsMethod, final QueryParameters params) {
-        return paramsMethod.apply(new QueryParams(params, PrettyPrintParam.FALSE));
+    private static <T> T assertParams(final Function<QueryParameters, T> paramsMethod, final QueryParameters params) {
+        return paramsMethod.apply(params);
     }
 
-    private static <T> T assertParams(final Function<QueryParams, T> paramsMethod, final String name,
+    private static <T> T assertParams(final Function<QueryParameters, T> paramsMethod, final String name,
             final String value) {
         return assertParams(paramsMethod, QueryParameters.of(name, value));
     }
index 0b2f24f34dc17d5f250667409f6368a01813daa8..c88577b1030636335ccd047194c292d003da6ca1 100644 (file)
@@ -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("""
             <yang-patch-status xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
               <patch-id>patch</patch-id>
@@ -60,7 +59,7 @@ public class YangPatchStatusBodyTest extends AbstractJukeboxTest {
                 <error-tag>data-exists</error-tag>
                 <error-message>Data already exists</error-message>
               </errors>
-            </yang-patch-status>""", body::formatToXML);
+            </yang-patch-status>""", 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("""
             <yang-patch-status xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
               <patch-id>patch</patch-id>
@@ -106,6 +105,6 @@ public class YangPatchStatusBodyTest extends AbstractJukeboxTest {
                   </errors>
                 </edit>
               </edit-status>
-            </yang-patch-status>""", body::formatToXML);
+            </yang-patch-status>""", body::formatToXML, true);
     }
 }