You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@causeway.apache.org by ah...@apache.org on 2023/03/28 08:52:12 UTC

[causeway] branch master updated: CAUSEWAY-3129: [RO] encapsul. request param utils within their own type

This is an automated email from the ASF dual-hosted git repository.

ahuber pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/causeway.git


The following commit(s) were added to refs/heads/master by this push:
     new 5666c5c7f1 CAUSEWAY-3129: [RO] encapsul. request param utils within their own type
5666c5c7f1 is described below

commit 5666c5c7f1c0494a2110446d1d65f27fa80a9f4a
Author: Andi Huber <ah...@apache.org>
AuthorDate: Tue Mar 28 10:52:07 2023 +0200

    CAUSEWAY-3129: [RO] encapsul. request param utils within their own type
---
 .../util/{Util.java => RequestParams.java}         | 82 +++++++++++++---------
 .../viewer/context/ResourceContext.java            | 16 ++---
 .../resources/DomainObjectResourceServerside.java  | 16 ++---
 .../resources/DomainServiceResourceServerside.java |  7 +-
 .../resources/DomainTypeResourceServerside.java    | 10 +--
 .../viewer/resources/JsonParserHelper.java         |  6 +-
 .../viewer/resources/ResourceAbstract.java         | 12 ++--
 .../context/ResourceContext_getArg_Test.java       |  5 +-
 .../DomainResourceHelper_readBodyAsMap_Test.java   | 25 ++++---
 9 files changed, 103 insertions(+), 76 deletions(-)

diff --git a/viewers/restfulobjects/rendering/src/main/java/org/apache/causeway/viewer/restfulobjects/rendering/util/Util.java b/viewers/restfulobjects/rendering/src/main/java/org/apache/causeway/viewer/restfulobjects/rendering/util/RequestParams.java
similarity index 54%
rename from viewers/restfulobjects/rendering/src/main/java/org/apache/causeway/viewer/restfulobjects/rendering/util/Util.java
rename to viewers/restfulobjects/rendering/src/main/java/org/apache/causeway/viewer/restfulobjects/rendering/util/RequestParams.java
index 84d2a5d295..b93572ac25 100644
--- a/viewers/restfulobjects/rendering/src/main/java/org/apache/causeway/viewer/restfulobjects/rendering/util/Util.java
+++ b/viewers/restfulobjects/rendering/src/main/java/org/apache/causeway/viewer/restfulobjects/rendering/util/RequestParams.java
@@ -25,6 +25,8 @@ import java.nio.charset.StandardCharsets;
 import com.fasterxml.jackson.core.JsonParseException;
 import com.fasterxml.jackson.databind.JsonMappingException;
 
+import org.springframework.lang.Nullable;
+
 import org.apache.causeway.commons.internal.base._Bytes;
 import org.apache.causeway.commons.internal.base._Strings;
 import org.apache.causeway.viewer.restfulobjects.applib.JsonRepresentation;
@@ -32,68 +34,82 @@ import org.apache.causeway.viewer.restfulobjects.applib.RestfulResponse;
 import org.apache.causeway.viewer.restfulobjects.applib.util.JsonMapper;
 import org.apache.causeway.viewer.restfulobjects.rendering.RestfulObjectsApplicationException;
 
-public final class Util {
+import lombok.NonNull;
+import lombok.experimental.Accessors;
+
+@lombok.Value(staticConstructor = "of")
+@Accessors(fluent=true) //XXX record candidate
+public class RequestParams {
+
+    static enum Nature {
+        REQUEST_BODY,
+        QUERY_STRING
+    }
+
+    private final @NonNull RequestParams.Nature nature;
+    private final @Nullable String raw;
+
+    public static RequestParams ofRequestBody(final InputStream is) {
+        return of(Nature.REQUEST_BODY, asStringUtf8(is));
+    }
+
+    public static RequestParams ofQueryString(final String queryString) {
+        return of(Nature.QUERY_STRING, queryString);
+    }
+
+    public static RequestParams ofEmptyQueryString() {
+        return of(Nature.QUERY_STRING, "");
+    }
 
-    private Util(){}
+    public JsonRepresentation asMap() {
+        return readAsMap(raw, nature.name());
+    }
 
-    // //////////////////////////////////////////////////////////////
-    // parsing
-    // //////////////////////////////////////////////////////////////
+    // -- HELPER
 
     /**
      * Parse {@link java.io.InputStream} to String, else throw exception
      */
-    public static String asStringUtf8(final InputStream body) {
+    private static String asStringUtf8(final InputStream body) {
         try {
             return _Strings.ofBytes(_Bytes.of(body), StandardCharsets.UTF_8);
         } catch (final IOException e) {
-            throw RestfulObjectsApplicationException.createWithCauseAndMessage(RestfulResponse.HttpStatusCode.BAD_REQUEST, e, "could not read body");
+            throw RestfulObjectsApplicationException
+                .createWithCauseAndMessage(RestfulResponse.HttpStatusCode.BAD_REQUEST, e, "could not read body");
         }
     }
 
     /**
      * Parse (body) string to {@link org.apache.causeway.viewer.restfulobjects.applib.JsonRepresentation}, else throw exception
      */
-    public static JsonRepresentation readAsMap(final String body) {
-        if (body == null) {
+    private static JsonRepresentation readAsMap(final String rawArgs, final String argsNature) {
+        if (rawArgs == null) {
             return JsonRepresentation.newMap();
         }
-        final String bodyTrimmed = body.trim();
+        final String bodyTrimmed = rawArgs.trim();
         if (bodyTrimmed.isEmpty()) {
             return JsonRepresentation.newMap();
         }
-        return read(bodyTrimmed, "body");
-    }
 
-    /**
-     * Parse (query) string to {@link org.apache.causeway.viewer.restfulobjects.applib.JsonRepresentation}, else throw exception
-     */
-    public static JsonRepresentation readQueryStringAsMap(final String queryString) {
-        if (queryString == null) {
-            return JsonRepresentation.newMap();
-        }
-        final String queryStringTrimmed = queryString.trim();
-        if (queryStringTrimmed.isEmpty()) {
-            return JsonRepresentation.newMap();
-        }
-        return read(queryStringTrimmed, "query string");
-    }
 
-    private static JsonRepresentation read(final String args, final String argsNature) {
         try {
-            final JsonRepresentation jsonRepr = JsonMapper.instance().read(args);
+            final JsonRepresentation jsonRepr = JsonMapper.instance().read(rawArgs);
             if (!jsonRepr.isMap()) {
-                throw RestfulObjectsApplicationException.createWithMessage(RestfulResponse.HttpStatusCode.BAD_REQUEST, "could not read %s as a JSON map", argsNature);
+                throw RestfulObjectsApplicationException
+                .createWithMessage(RestfulResponse.HttpStatusCode.BAD_REQUEST, "could not read %s as a JSON map", argsNature);
             }
             return jsonRepr;
         } catch (final JsonParseException e) {
-            throw RestfulObjectsApplicationException.createWithCauseAndMessage(RestfulResponse.HttpStatusCode.BAD_REQUEST, e, "could not parse %s", argsNature);
+            throw RestfulObjectsApplicationException
+                .createWithCauseAndMessage(RestfulResponse.HttpStatusCode.BAD_REQUEST, e, "could not parse %s", argsNature);
         } catch (final JsonMappingException e) {
-            throw RestfulObjectsApplicationException.createWithCauseAndMessage(RestfulResponse.HttpStatusCode.BAD_REQUEST, e, "could not read %s as JSON", argsNature);
+            throw RestfulObjectsApplicationException
+                .createWithCauseAndMessage(RestfulResponse.HttpStatusCode.BAD_REQUEST, e, "could not read %s as JSON", argsNature);
         } catch (final IOException e) {
-            throw RestfulObjectsApplicationException.createWithCauseAndMessage(RestfulResponse.HttpStatusCode.BAD_REQUEST, e, "could not parse %s", argsNature);
+            throw RestfulObjectsApplicationException
+                .createWithCauseAndMessage(RestfulResponse.HttpStatusCode.BAD_REQUEST, e, "could not parse %s", argsNature);
         }
-    }
 
+    }
 
-}
+}
\ No newline at end of file
diff --git a/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/context/ResourceContext.java b/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/context/ResourceContext.java
index 9ed35a1ee1..7927a2e212 100644
--- a/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/context/ResourceContext.java
+++ b/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/context/ResourceContext.java
@@ -21,6 +21,7 @@ package org.apache.causeway.viewer.restfulobjects.viewer.context;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 
 import javax.servlet.http.HttpServletRequest;
@@ -49,7 +50,7 @@ import org.apache.causeway.viewer.restfulobjects.rendering.IResourceContext;
 import org.apache.causeway.viewer.restfulobjects.rendering.RestfulObjectsApplicationException;
 import org.apache.causeway.viewer.restfulobjects.rendering.domainobjects.ObjectAdapterLinkTo;
 import org.apache.causeway.viewer.restfulobjects.rendering.service.RepresentationService;
-import org.apache.causeway.viewer.restfulobjects.rendering.util.Util;
+import org.apache.causeway.viewer.restfulobjects.rendering.util.RequestParams;
 import org.apache.causeway.viewer.restfulobjects.viewer.resources.ResourceDescriptor;
 import org.apache.causeway.viewer.restfulobjects.viewer.resources.serialization.SerializationStrategy;
 
@@ -78,7 +79,7 @@ implements IResourceContext {
     private final Where where;
     private final RepresentationService.Intent intent;
     @Getter private final InteractionInitiatedBy interactionInitiatedBy;
-    private final String urlUnencodedQueryString;
+    private final @NonNull RequestParams urlUnencodedQueryString;
     private final JsonRepresentation readQueryStringAsMap;
 
     // -- constructor and init
@@ -90,7 +91,7 @@ implements IResourceContext {
             final Request request,
             final String applicationAbsoluteBase,
             final String restfulAbsoluteBase,
-            final String urlUnencodedQueryStringIfAny,
+            final RequestParams urlUnencodedQueryString,
             final HttpServletRequest httpServletRequest,
             final HttpServletResponse httpServletResponse,
             final SecurityContext securityContext,
@@ -105,7 +106,8 @@ implements IResourceContext {
         this.request = request;
         this.where = resourceDescriptor.getWhere();
         this.intent = resourceDescriptor.getIntent();
-        this.urlUnencodedQueryString = urlUnencodedQueryStringIfAny;
+        this.urlUnencodedQueryString = Optional.ofNullable(urlUnencodedQueryString)
+                .orElseGet(RequestParams::ofEmptyQueryString);
         this.httpServletRequest = httpServletRequest;
         this.httpServletResponse = httpServletResponse;
         this.securityContext = securityContext;
@@ -119,7 +121,6 @@ implements IResourceContext {
         init(resourceDescriptor.getRepresentationType());
     }
 
-
     void init(final RepresentationType representationType) {
 
         // previously we checked for compatible accept headers here.
@@ -142,7 +143,7 @@ implements IResourceContext {
      * Note that this can return non-null for all HTTP methods; will be either the
      * query string (GET, DELETE) or read out of the input stream (PUT, POST).
      */
-    public String getUrlUnencodedQueryString() {
+    public RequestParams getUrlUnencodedQueryString() {
         return urlUnencodedQueryString;
     }
 
@@ -171,8 +172,7 @@ implements IResourceContext {
             }
             return map;
         } else {
-            final String queryString = getUrlUnencodedQueryString();
-            return Util.readQueryStringAsMap(queryString);
+            return getUrlUnencodedQueryString().asMap();
         }
     }
 
diff --git a/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/DomainObjectResourceServerside.java b/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/DomainObjectResourceServerside.java
index 24d53bec54..17ccb46409 100644
--- a/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/DomainObjectResourceServerside.java
+++ b/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/DomainObjectResourceServerside.java
@@ -63,7 +63,7 @@ import org.apache.causeway.viewer.restfulobjects.applib.domainobjects.DomainObje
 import org.apache.causeway.viewer.restfulobjects.rendering.Responses;
 import org.apache.causeway.viewer.restfulobjects.rendering.RestfulObjectsApplicationException;
 import org.apache.causeway.viewer.restfulobjects.rendering.service.RepresentationService;
-import org.apache.causeway.viewer.restfulobjects.rendering.util.Util;
+import org.apache.causeway.viewer.restfulobjects.rendering.util.RequestParams;
 import org.apache.causeway.viewer.restfulobjects.viewer.context.ResourceContext;
 
 import lombok.NonNull;
@@ -101,8 +101,7 @@ implements DomainObjectResource {
         val resourceContext = createResourceContext(
                 RepresentationType.DOMAIN_OBJECT, Where.OBJECT_FORMS, RepresentationService.Intent.JUST_CREATED);
 
-        final String objectStr = Util.asStringUtf8(object);
-        final JsonRepresentation objectRepr = Util.readAsMap(objectStr);
+        final JsonRepresentation objectRepr = RequestParams.ofRequestBody(object).asMap();
         if (!objectRepr.isMap()) {
             throw _EndpointLogging.error(log, "POST /objects/{}", domainType,
                     RestfulObjectsApplicationException
@@ -195,8 +194,7 @@ implements DomainObjectResource {
         val resourceContext = createResourceContext(
                 RepresentationType.DOMAIN_OBJECT, Where.OBJECT_FORMS, RepresentationService.Intent.ALREADY_PERSISTENT);
 
-        final String objectStr = Util.asStringUtf8(object);
-        final JsonRepresentation argRepr = Util.readAsMap(objectStr);
+        final JsonRepresentation argRepr = RequestParams.ofRequestBody(object).asMap();
         if (!argRepr.isMap()) {
             throw _EndpointLogging.error(log, "PUT /objects/{}/{}", domainType, instanceId,
                     RestfulObjectsApplicationException
@@ -448,7 +446,7 @@ implements DomainObjectResource {
         .checkUsability(AccessIntent.MUTATE)
         .modifyProperty(property->{
             val proposedNewValue = new JsonParserHelper(resourceContext, property.getElementType())
-                    .parseAsMapWithSingleValue(Util.asStringUtf8(body));
+                    .parseAsMapWithSingleValue(RequestParams.ofRequestBody(body));
 
             return proposedNewValue;
         })
@@ -692,10 +690,12 @@ implements DomainObjectResource {
             final @QueryParam("x-causeway-querystring") String xCausewayUrlEncodedQueryString) {
 
         final String urlUnencodedQueryString = _UrlDecoderUtil
-                .urlDecodeNullSafe(xCausewayUrlEncodedQueryString != null? xCausewayUrlEncodedQueryString: httpServletRequest.getQueryString());
+                .urlDecodeNullSafe(xCausewayUrlEncodedQueryString != null
+                    ? xCausewayUrlEncodedQueryString
+                    : httpServletRequest.getQueryString());
         val resourceContext = createResourceContext(
                 ResourceDescriptor.of(RepresentationType.ACTION_RESULT, Where.STANDALONE_TABLES, RepresentationService.Intent.NOT_APPLICABLE),
-                urlUnencodedQueryString);
+                RequestParams.ofQueryString(urlUnencodedQueryString));
 
         final JsonRepresentation arguments = resourceContext.getQueryStringAsJsonRepr();
 
diff --git a/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/DomainServiceResourceServerside.java b/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/DomainServiceResourceServerside.java
index 03159affc1..447648e132 100644
--- a/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/DomainServiceResourceServerside.java
+++ b/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/DomainServiceResourceServerside.java
@@ -51,6 +51,7 @@ import org.apache.causeway.viewer.restfulobjects.rendering.RestfulObjectsApplica
 import org.apache.causeway.viewer.restfulobjects.rendering.domainobjects.DomainObjectReprRenderer;
 import org.apache.causeway.viewer.restfulobjects.rendering.domainobjects.DomainServiceLinkTo;
 import org.apache.causeway.viewer.restfulobjects.rendering.service.RepresentationService;
+import org.apache.causeway.viewer.restfulobjects.rendering.util.RequestParams;
 
 import lombok.val;
 import lombok.extern.log4j.Log4j2;
@@ -268,10 +269,12 @@ implements DomainServiceResource {
             final @QueryParam("x-causeway-querystring") String xCausewayUrlEncodedQueryString) {
 
         final String urlUnencodedQueryString = _UrlDecoderUtil
-                .urlDecodeNullSafe(xCausewayUrlEncodedQueryString != null? xCausewayUrlEncodedQueryString: httpServletRequest.getQueryString());
+                .urlDecodeNullSafe(xCausewayUrlEncodedQueryString != null
+                    ? xCausewayUrlEncodedQueryString
+                    : httpServletRequest.getQueryString());
         val resourceContext = createResourceContext(
                 ResourceDescriptor.of(RepresentationType.ACTION_RESULT, Where.STANDALONE_TABLES, RepresentationService.Intent.NOT_APPLICABLE),
-                urlUnencodedQueryString);
+                RequestParams.ofQueryString(urlUnencodedQueryString));
 
         final JsonRepresentation arguments = resourceContext.getQueryStringAsJsonRepr();
 
diff --git a/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/DomainTypeResourceServerside.java b/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/DomainTypeResourceServerside.java
index b984707942..4646dacbbb 100644
--- a/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/DomainTypeResourceServerside.java
+++ b/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/DomainTypeResourceServerside.java
@@ -59,7 +59,7 @@ import org.apache.causeway.viewer.restfulobjects.rendering.domaintypes.PropertyD
 import org.apache.causeway.viewer.restfulobjects.rendering.domaintypes.TypeActionResultReprRenderer;
 import org.apache.causeway.viewer.restfulobjects.rendering.domaintypes.TypeListReprRenderer;
 import org.apache.causeway.viewer.restfulobjects.rendering.service.RepresentationService;
-import org.apache.causeway.viewer.restfulobjects.rendering.util.Util;
+import org.apache.causeway.viewer.restfulobjects.rendering.util.RequestParams;
 import org.apache.causeway.viewer.restfulobjects.viewer.util.UrlParserUtils;
 
 import lombok.NonNull;
@@ -393,16 +393,16 @@ implements DomainTypeResource {
         }
 
         // formal style; must parse from args that has a link with an href to the domain type
-        final String argsAsQueryString = UrlEncodingUtils.urlDecode(argsAsUrlEncodedQueryString);
-        final String href = linkFromFormalArgs(argsAsQueryString, argsParamName, onRoException);
+        val requestParams = RequestParams.ofQueryString(UrlEncodingUtils.urlDecode(argsAsUrlEncodedQueryString));
+        final String href = linkFromFormalArgs(requestParams, argsParamName, onRoException);
         return UrlParserUtils.domainTypeFrom(href);
     }
 
     private static String linkFromFormalArgs(
-            final String argumentsAsQueryString,
+            final RequestParams requestParams,
             final String paramName,
             final @NonNull UnaryOperator<RestfulObjectsApplicationException> onRoException) {
-        final JsonRepresentation arguments = Util.readQueryStringAsMap(argumentsAsQueryString);
+        final JsonRepresentation arguments = requestParams.asMap();
         if (!arguments.isLink(paramName)) {
             throw onRoException.apply(RestfulObjectsApplicationException
                     .createWithMessage(HttpStatusCode.BAD_REQUEST, "Args should contain a link '%s'", paramName));
diff --git a/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/JsonParserHelper.java b/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/JsonParserHelper.java
index fb20cc1e92..9c817a4454 100644
--- a/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/JsonParserHelper.java
+++ b/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/JsonParserHelper.java
@@ -29,7 +29,7 @@ import org.apache.causeway.viewer.restfulobjects.applib.RestfulResponse;
 import org.apache.causeway.viewer.restfulobjects.rendering.IResourceContext;
 import org.apache.causeway.viewer.restfulobjects.rendering.RestfulObjectsApplicationException;
 import org.apache.causeway.viewer.restfulobjects.rendering.service.valuerender.JsonValueEncoderService;
-import org.apache.causeway.viewer.restfulobjects.rendering.util.Util;
+import org.apache.causeway.viewer.restfulobjects.rendering.util.RequestParams;
 
 import lombok.val;
 
@@ -59,8 +59,8 @@ public class JsonParserHelper {
      *            - as per {@link org.apache.causeway.viewer.restfulobjects.rendering.util.Util#asStringUtf8(java.io.InputStream)}
      * @return
      */
-    ManagedObject parseAsMapWithSingleValue(final String bodyAsString) {
-        final JsonRepresentation arguments = Util.readAsMap(bodyAsString);
+    ManagedObject parseAsMapWithSingleValue(final RequestParams body) {
+        final JsonRepresentation arguments = body.asMap();
         return parseAsMapWithSingleValue(arguments);
     }
 
diff --git a/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/ResourceAbstract.java b/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/ResourceAbstract.java
index 3154cbe0ce..5f7abd08bc 100644
--- a/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/ResourceAbstract.java
+++ b/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/ResourceAbstract.java
@@ -48,7 +48,7 @@ import org.apache.causeway.viewer.restfulobjects.applib.RestfulResponse.HttpStat
 import org.apache.causeway.viewer.restfulobjects.rendering.RestfulObjectsApplicationException;
 import org.apache.causeway.viewer.restfulobjects.rendering.UrlDecoderUtils;
 import org.apache.causeway.viewer.restfulobjects.rendering.service.RepresentationService;
-import org.apache.causeway.viewer.restfulobjects.rendering.util.Util;
+import org.apache.causeway.viewer.restfulobjects.rendering.util.RequestParams;
 import org.apache.causeway.viewer.restfulobjects.viewer.context.ResourceContext;
 
 import lombok.Getter;
@@ -85,20 +85,20 @@ implements HasMetaModelContext {
 
     protected ResourceContext createResourceContext(final ResourceDescriptor resourceDescriptor) {
         String queryStringIfAny = getUrlDecodedQueryStringIfAny();
-        return createResourceContext(resourceDescriptor, queryStringIfAny);
+        return createResourceContext(resourceDescriptor, RequestParams.ofQueryString(queryStringIfAny));
     }
 
     protected ResourceContext createResourceContext(
             final ResourceDescriptor resourceDescriptor,
             final InputStream arguments) {
 
-        final String urlDecodedQueryString = Util.asStringUtf8(arguments);
+        val urlDecodedQueryString = RequestParams.ofRequestBody(arguments);
         return createResourceContext(resourceDescriptor, urlDecodedQueryString);
     }
 
     protected ResourceContext createResourceContext(
             final ResourceDescriptor resourceDescriptor,
-            final String urlUnencodedQueryString) {
+            final RequestParams requestParams) {
 
         if (!getInteractionService().isInInteraction()) {
             throw RestfulObjectsApplicationException.create(HttpStatusCode.UNAUTHORIZED);
@@ -127,7 +127,7 @@ implements HasMetaModelContext {
                 resourceDescriptor,
                 applicationAbsoluteBase,
                 restfulAbsoluteBase,
-                urlUnencodedQueryString,
+                requestParams,
                 httpServletRequest.getParameterMap());
     }
 
@@ -167,7 +167,7 @@ implements HasMetaModelContext {
             final ResourceDescriptor resourceDescriptor,
             final String applicationAbsoluteBase,
             final String restfulAbsoluteBase,
-            final String urlUnencodedQueryString,
+            final RequestParams urlUnencodedQueryString,
             final Map<String, String[]> requestParams) {
 
         return new ResourceContext(
diff --git a/viewers/restfulobjects/viewer/src/test/java/org/apache/causeway/viewer/restfulobjects/viewer/context/ResourceContext_getArg_Test.java b/viewers/restfulobjects/viewer/src/test/java/org/apache/causeway/viewer/restfulobjects/viewer/context/ResourceContext_getArg_Test.java
index 04d191b765..8513b283f8 100644
--- a/viewers/restfulobjects/viewer/src/test/java/org/apache/causeway/viewer/restfulobjects/viewer/context/ResourceContext_getArg_Test.java
+++ b/viewers/restfulobjects/viewer/src/test/java/org/apache/causeway/viewer/restfulobjects/viewer/context/ResourceContext_getArg_Test.java
@@ -40,6 +40,7 @@ import org.apache.causeway.viewer.restfulobjects.applib.JsonRepresentation;
 import org.apache.causeway.viewer.restfulobjects.applib.RepresentationType;
 import org.apache.causeway.viewer.restfulobjects.applib.RestfulRequest.RequestParameter;
 import org.apache.causeway.viewer.restfulobjects.applib.util.UrlEncodingUtils;
+import org.apache.causeway.viewer.restfulobjects.rendering.util.RequestParams;
 import org.apache.causeway.viewer.restfulobjects.viewer.resources.ResourceDescriptor;
 
 class ResourceContext_getArg_Test {
@@ -96,7 +97,7 @@ class ResourceContext_getArg_Test {
         final String queryString = UrlEncodingUtils.urlEncode(JsonRepresentation.newMap("x-ro-page", "123").asJsonNode());
 
         resourceContext = new ResourceContext(ResourceDescriptor.empty(), null, null, null, null, null,
-                _UrlDecoderUtil.urlDecodeNullSafe(queryString),
+                RequestParams.ofQueryString(_UrlDecoderUtil.urlDecodeNullSafe(queryString)),
                 mockHttpServletRequest, null, null,
                 metaModelContext, null, null) {
             @Override
@@ -113,7 +114,7 @@ class ResourceContext_getArg_Test {
         final String queryString = UrlEncodingUtils.urlEncode(JsonRepresentation.newMap("xxx", "123").asJsonNode());
 
         resourceContext = new ResourceContext(ResourceDescriptor.empty(), null, null, null, null, null,
-                _UrlDecoderUtil.urlDecodeNullSafe(queryString),
+                RequestParams.ofQueryString(_UrlDecoderUtil.urlDecodeNullSafe(queryString)),
                 mockHttpServletRequest, null, null,
                 metaModelContext, null, null) {
             @Override
diff --git a/viewers/restfulobjects/viewer/src/test/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/DomainResourceHelper_readBodyAsMap_Test.java b/viewers/restfulobjects/viewer/src/test/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/DomainResourceHelper_readBodyAsMap_Test.java
index e3cdfe501f..51024066a9 100644
--- a/viewers/restfulobjects/viewer/src/test/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/DomainResourceHelper_readBodyAsMap_Test.java
+++ b/viewers/restfulobjects/viewer/src/test/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/DomainResourceHelper_readBodyAsMap_Test.java
@@ -26,15 +26,15 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import org.apache.causeway.viewer.restfulobjects.applib.JsonRepresentation;
 import org.apache.causeway.viewer.restfulobjects.rendering.RestfulObjectsApplicationException;
-import org.apache.causeway.viewer.restfulobjects.rendering.util.Util;
+import org.apache.causeway.viewer.restfulobjects.rendering.util.RequestParams;
 
-class DomainResourceHelper_readBodyAsMap_Test {
+import lombok.val;
 
-    private JsonRepresentation representation;
+class DomainResourceHelper_readBodyAsMap_Test {
 
     @Test
     void whenNull() throws Exception {
-        representation = Util.readAsMap(null);
+        val representation = representationFor(null);
 
         assertTrue(representation.isMap());
         assertEquals(0, representation.size());
@@ -42,7 +42,7 @@ class DomainResourceHelper_readBodyAsMap_Test {
 
     @Test
     void whenEmptyString() throws Exception {
-        representation = Util.readAsMap("");
+        val representation = representationFor("");
 
         assertTrue(representation.isMap());
         assertEquals(0, representation.size());
@@ -50,7 +50,7 @@ class DomainResourceHelper_readBodyAsMap_Test {
 
     @Test
     void whenWhitespaceOnlyString() throws Exception {
-        representation = Util.readAsMap(" \t ");
+        val representation = representationFor(" \t ");
 
         assertTrue(representation.isMap());
         assertEquals(0, representation.size());
@@ -58,7 +58,7 @@ class DomainResourceHelper_readBodyAsMap_Test {
 
     @Test
     void emptyMap() throws Exception {
-        representation = Util.readAsMap("{}");
+        val representation = representationFor("{}");
 
         assertTrue(representation.isMap());
         assertEquals(0, representation.size());
@@ -66,7 +66,7 @@ class DomainResourceHelper_readBodyAsMap_Test {
 
     @Test
     void map() throws Exception {
-        representation = Util.readAsMap("{\"foo\":\"bar\"}");
+        val representation = representationFor("{\"foo\":\"bar\"}");
 
         assertTrue(representation.isMap());
         assertEquals(1, representation.size());
@@ -75,8 +75,15 @@ class DomainResourceHelper_readBodyAsMap_Test {
     @Test
     void whenArray() throws Exception {
         assertThrows(RestfulObjectsApplicationException.class, ()->{
-            Util.readAsMap("[]");
+            representationFor("[]");
         });
     }
 
+    // -- HELPER
+
+    private JsonRepresentation representationFor(final String input) {
+        return RequestParams.ofQueryString(input).asMap();
+    }
+
+
 }