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

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


The following commit(s) were added to refs/heads/v4 by this push:
     new 04c9e9b0d13 CAUSEWAY-3892: working on RO test resurrection
04c9e9b0d13 is described below

commit 04c9e9b0d13ef3a122ac585e800a4790a34cac53
Author: Andi Huber <[email protected]>
AuthorDate: Thu Jun 12 15:36:51 2025 +0200

    CAUSEWAY-3892: working on RO test resurrection
---
 .../restfulobjects/rendering/IResourceContext.java |  14 +-
 .../rendering/ReprRendererAbstract.java            |   4 +-
 .../domainobjects/DomainObjectReprRenderer.java    |  12 +-
 .../domainobjects/ObjectActionReprRenderer.java    |  18 +-
 .../ObjectCollectionReprRenderer.java              |  22 +--
 ...entNegotiationServiceForRestfulObjectsV1_0.java |  23 ++-
 ...ntentNegotiationServiceOrgApacheCausewayV2.java |  14 +-
 .../ContentNegotiationServiceOrgApacheIsisV1.java  |   2 +-
 .../ContentNegotiationServiceXRoDomainType.java    |   4 +-
 ...sewayViewerRestfulObjectsIntegTestAbstract.java |  43 +----
 ...sewayViewerRestfulObjectsIntegTestManifest.java |  52 ++++++
 .../test/scenarios/Abstract_IntegTest.java         |  19 +-
 .../test/scenarios/dept/Department_IntegTest.java  |  16 +-
 .../test/scenarios/home/HomePage_IntegTest.java    |  14 +-
 .../scenarios/staff/Staff_hilevel_IntegTest.java   |  41 ++---
 .../staff/Staff_lowlevel_v1_IntegTest.java         |  54 +++---
 .../staff/Staff_lowlevel_v2_IntegTest.java         |  29 ++-
 .../H2InMemory_withUniqueSchema.properties         |  20 ---
 .../src/test/resources/SilenceMetaModel.properties |  18 --
 .../resources/SilenceProgrammingModel.properties   |  22 ---
 .../src/test/resources/UseLog4j2Test.properties    |  18 --
 .../src/test/resources/junit-platform.propertes    |  21 ---
 ..._ensureCompatibleAcceptHeader_ContractTest.java |   3 +-
 .../viewer/context/ResourceContext.java            | 197 +++++++++++----------
 .../resources/DomainObjectResourceServerside.java  | 170 ++++++------------
 .../resources/DomainServiceResourceServerside.java |  61 +++----
 .../resources/DomainTypeResourceServerside.java    |  11 +-
 .../viewer/resources/JsonParserHelper.java         |   2 +-
 .../resources/ObjectAdapterAccessHelper.java       |   2 +-
 .../resources/ObjectAdapterUpdateHelper.java       |  10 +-
 .../viewer/resources/ResourceAbstract.java         |  11 +-
 .../viewer/resources/ResourceDescriptor.java       |  11 +-
 .../resources/VersionResourceServerside.java       |   2 +-
 .../viewer/resources/_DomainResourceHelper.java    |  93 +++-------
 .../context/ResourceContext_getArg_Test.java       |  30 +---
 35 files changed, 429 insertions(+), 654 deletions(-)

diff --git 
a/viewers/restfulobjects/rendering/src/main/java/org/apache/causeway/viewer/restfulobjects/rendering/IResourceContext.java
 
b/viewers/restfulobjects/rendering/src/main/java/org/apache/causeway/viewer/restfulobjects/rendering/IResourceContext.java
index 2abef630c8a..d403309556e 100644
--- 
a/viewers/restfulobjects/rendering/src/main/java/org/apache/causeway/viewer/restfulobjects/rendering/IResourceContext.java
+++ 
b/viewers/restfulobjects/rendering/src/main/java/org/apache/causeway/viewer/restfulobjects/rendering/IResourceContext.java
@@ -59,19 +59,19 @@ public interface IResourceContext extends 
HasMetaModelContext {
      * Returns the {@link HttpHeaders#getAcceptableMediaTypes() acceptable 
media types}
      * as obtained from {@link HttpHeaders}.
      */
-    List<MediaType> getAcceptableMediaTypes();
+    List<MediaType> acceptableMediaTypes();
 
     /**
      * Whether this interaction was initiated directly by a
      * {@link InteractionInitiatedBy#USER user} (or indirectly by the
      * {@link InteractionInitiatedBy#FRAMEWORK framework}.
      */
-    InteractionInitiatedBy getInteractionInitiatedBy();
+    InteractionInitiatedBy interactionInitiatedBy();
 
-    Where getWhere();
+    Where where();
 
-    ObjectAdapterLinkTo getObjectAdapterLinkTo();
-    List<List<String>> getFollowLinks();
+    ObjectAdapterLinkTo objectAdapterLinkTo();
+    List<List<String>> followLinks();
     boolean isValidateOnly();
 
     default Restfulobjects config() {
@@ -91,11 +91,11 @@ default Restfulobjects config() {
     /**
      * Applies only when rendering a domain object.
      */
-    RepresentationService.Intent getIntent();
+    RepresentationService.Intent intent();
 
     // -- UTILITY
 
-    default Optional<ManagedObject> getObjectAdapterForOidFromHref(final 
String oidFromHref) {
+    default Optional<ManagedObject> objectAdapterForOidFromHref(final String 
oidFromHref) {
         String oidStrUnencoded = UrlDecoderUtils.urlDecode(oidFromHref);
         return Bookmark.parse(oidStrUnencoded)
         .flatMap(getMetaModelContext().getObjectManager()::loadObject);
diff --git 
a/viewers/restfulobjects/rendering/src/main/java/org/apache/causeway/viewer/restfulobjects/rendering/ReprRendererAbstract.java
 
b/viewers/restfulobjects/rendering/src/main/java/org/apache/causeway/viewer/restfulobjects/rendering/ReprRendererAbstract.java
index 637c4a4286d..1df4f9232aa 100644
--- 
a/viewers/restfulobjects/rendering/src/main/java/org/apache/causeway/viewer/restfulobjects/rendering/ReprRendererAbstract.java
+++ 
b/viewers/restfulobjects/rendering/src/main/java/org/apache/causeway/viewer/restfulobjects/rendering/ReprRendererAbstract.java
@@ -71,7 +71,7 @@ public ReprRendererAbstract(
 
     private static InteractionInitiatedBy determineInteractionInitiatedByFrom(
             final IResourceContext resourceContext) {
-        return resourceContext.getInteractionInitiatedBy();
+        return resourceContext.interactionInitiatedBy();
     }
 
     protected InteractionInitiatedBy getInteractionInitiatedBy() {
@@ -86,7 +86,7 @@ private LinkFollowSpecs asProvidedElseCreate(final 
LinkFollowSpecs linkFollower)
         if (linkFollower != null) {
             return linkFollower;
         }
-        return LinkFollowSpecs.create(resourceContext.getFollowLinks());
+        return LinkFollowSpecs.create(resourceContext.followLinks());
     }
 
     @Override
diff --git 
a/viewers/restfulobjects/rendering/src/main/java/org/apache/causeway/viewer/restfulobjects/rendering/domainobjects/DomainObjectReprRenderer.java
 
b/viewers/restfulobjects/rendering/src/main/java/org/apache/causeway/viewer/restfulobjects/rendering/domainobjects/DomainObjectReprRenderer.java
index 939493b2f2e..c0a062348ff 100644
--- 
a/viewers/restfulobjects/rendering/src/main/java/org/apache/causeway/viewer/restfulobjects/rendering/domainobjects/DomainObjectReprRenderer.java
+++ 
b/viewers/restfulobjects/rendering/src/main/java/org/apache/causeway/viewer/restfulobjects/rendering/domainobjects/DomainObjectReprRenderer.java
@@ -309,7 +309,7 @@ private void addProperties(final ManagedObject 
objectAdapter, final JsonRepresen
         for (final ObjectAssociation assoc : associations) {
 
             if (mode.checkVisibility()) {
-                final Consent visibility = assoc.isVisible(objectAdapter, 
getInteractionInitiatedBy(), resourceContext.getWhere());
+                final Consent visibility = assoc.isVisible(objectAdapter, 
getInteractionInitiatedBy(), resourceContext.where());
                 if (!visibility.isAllowed()) {
                     continue;
                 }
@@ -323,7 +323,7 @@ private void addProperties(final ManagedObject 
objectAdapter, final JsonRepresen
             final JsonRepresentation propertyRepresentation = 
JsonRepresentation.newMap();
             final ObjectPropertyReprRenderer renderer =
                     new ObjectPropertyReprRenderer(getResourceContext(), 
linkFollowerForProp, property.getId(), propertyRepresentation);
-            renderer.with(ManagedProperty.of(objectAdapter, property, 
resourceContext.getWhere())).usingLinkTo(linkToBuilder);
+            renderer.with(ManagedProperty.of(objectAdapter, property, 
resourceContext.where())).usingLinkTo(linkToBuilder);
 
             if (mode.isArgs()) {
                 renderer.asArguments();
@@ -344,7 +344,7 @@ private void addCollections(final ManagedObject 
objectAdapter, final JsonReprese
         for (final ObjectAssociation assoc : associations) {
 
             if (mode.checkVisibility()) {
-                final Consent visibility = assoc.isVisible(objectAdapter, 
getInteractionInitiatedBy(), resourceContext.getWhere());
+                final Consent visibility = assoc.isVisible(objectAdapter, 
getInteractionInitiatedBy(), resourceContext.where());
                 if (!visibility.isAllowed()) {
                     continue;
                 }
@@ -362,7 +362,7 @@ private void addCollections(final ManagedObject 
objectAdapter, final JsonReprese
             final ObjectCollectionReprRenderer renderer =
                     new ObjectCollectionReprRenderer(getResourceContext(), 
linkFollowerForColl, collection.getId(), collectionRepresentation);
 
-            var where = resourceContext.getWhere();
+            var where = resourceContext.where();
 
             renderer.with(ManagedCollection.of(objectAdapter, collection, 
where)).usingLinkTo(linkToBuilder);
             if(mode.isEventSerialization()) {
@@ -380,7 +380,7 @@ private void addActions(
 
         actions
         .filter(action->{
-            final Consent visibility = action.isVisible(objectAdapter, 
getInteractionInitiatedBy(), resourceContext.getWhere());
+            final Consent visibility = action.isVisible(objectAdapter, 
getInteractionInitiatedBy(), resourceContext.where());
             return visibility.isAllowed();
         })
         .forEach(action->{
@@ -389,7 +389,7 @@ private void addActions(
                     new ObjectActionReprRenderer(getResourceContext(), 
linkFollowSpecs, action.getId(),
                             JsonRepresentation.newMap());
 
-            var where = resourceContext.getWhere();
+            var where = resourceContext.where();
 
             renderer.with(ManagedAction.of(objectAdapter, action, 
where)).usingLinkTo(linkToBuilder);
             members.mapPutJsonRepresentation(action.getId(), 
renderer.render());
diff --git 
a/viewers/restfulobjects/rendering/src/main/java/org/apache/causeway/viewer/restfulobjects/rendering/domainobjects/ObjectActionReprRenderer.java
 
b/viewers/restfulobjects/rendering/src/main/java/org/apache/causeway/viewer/restfulobjects/rendering/domainobjects/ObjectActionReprRenderer.java
index bc2788a8d97..f2321c4cab7 100644
--- 
a/viewers/restfulobjects/rendering/src/main/java/org/apache/causeway/viewer/restfulobjects/rendering/domainobjects/ObjectActionReprRenderer.java
+++ 
b/viewers/restfulobjects/rendering/src/main/java/org/apache/causeway/viewer/restfulobjects/rendering/domainobjects/ObjectActionReprRenderer.java
@@ -68,24 +68,20 @@ public JsonRepresentation render() {
         return representation;
     }
 
-    // ///////////////////////////////////////////////////
-    // details link
-    // ///////////////////////////////////////////////////
+    // DETAILS LINK
 
     /**
      * Mandatory hook method to support x-ro-follow-links
      */
     @Override
     protected void followDetailsLink(final JsonRepresentation detailsLink) {
-        var where = resourceContext.getWhere();
+        var where = resourceContext.where();
         final ObjectActionReprRenderer renderer = new 
ObjectActionReprRenderer(getResourceContext(), getLinkFollowSpecs(), null, 
JsonRepresentation.newMap());
         renderer.with(ManagedAction.of(objectAdapter, objectMember, 
where)).usingLinkTo(linkTo).asFollowed();
         detailsLink.mapPutJsonRepresentation("value", renderer.render());
     }
 
-    // ///////////////////////////////////////////////////
-    // mutators
-    // ///////////////////////////////////////////////////
+    // MUTATORS
 
     @Override
     protected void addMutatorLinksIfEnabled() {
@@ -121,9 +117,7 @@ private Object argValueFor(final int i) {
         return NullNode.getInstance();
     }
 
-    // ///////////////////////////////////////////////////
-    // parameter details
-    // ///////////////////////////////////////////////////
+    // PARAMETER DETAILS
 
     private ObjectActionReprRenderer addParameterDetails() {
         final Map<String,Object> parameters = _Maps.newLinkedHashMap();
@@ -186,9 +180,7 @@ private Object defaultFor(final ManagedParameter paramMod) {
         return DomainObjectReprRenderer.valueOrRef(resourceContext, paramMeta, 
getJsonValueEncoder(), defaultAdapter);
     }
 
-    // ///////////////////////////////////////////////////
-    // extensions and links
-    // ///////////////////////////////////////////////////
+    // EXTENSIONS AND LINKS
 
     @Override
     protected void addLinksToFormalDomainModel() {
diff --git 
a/viewers/restfulobjects/rendering/src/main/java/org/apache/causeway/viewer/restfulobjects/rendering/domainobjects/ObjectCollectionReprRenderer.java
 
b/viewers/restfulobjects/rendering/src/main/java/org/apache/causeway/viewer/restfulobjects/rendering/domainobjects/ObjectCollectionReprRenderer.java
index a36f8cb0de1..c320a98cc1a 100644
--- 
a/viewers/restfulobjects/rendering/src/main/java/org/apache/causeway/viewer/restfulobjects/rendering/domainobjects/ObjectCollectionReprRenderer.java
+++ 
b/viewers/restfulobjects/rendering/src/main/java/org/apache/causeway/viewer/restfulobjects/rendering/domainobjects/ObjectCollectionReprRenderer.java
@@ -84,9 +84,7 @@ public JsonRepresentation render() {
         return representation;
     }
 
-    // ///////////////////////////////////////////////////
-    // value
-    // ///////////////////////////////////////////////////
+    // VALUE
 
     private void addValue(final LinkFollowSpecs linkFollower) {
         var valueAdapter = objectMember.get(objectAdapter, 
getInteractionInitiatedBy());
@@ -123,36 +121,28 @@ private void addValue(final LinkFollowSpecs linkFollower) 
{
         representation.mapPut("value", list);
     }
 
-    // ///////////////////////////////////////////////////
-    // details link
-    // ///////////////////////////////////////////////////
+    // DETAILS LINK
 
     /**
      * Mandatory hook method to support x-ro-follow-links
      */
     @Override
     protected void followDetailsLink(final JsonRepresentation detailsLink) {
-        var where = resourceContext.getWhere();
-        var jsonRepresentation = JsonRepresentation.newMap();
         var objectCollectionReprRenderer =
-                new ObjectCollectionReprRenderer(getResourceContext(), 
getLinkFollowSpecs(), null, jsonRepresentation)
-                .with(ManagedCollection.of(objectAdapter, objectMember, where))
+            new ObjectCollectionReprRenderer(getResourceContext(), 
getLinkFollowSpecs(), null, JsonRepresentation.newMap())
+                .with(ManagedCollection.of(objectAdapter, objectMember, 
resourceContext.where()))
                 .asFollowed();
         detailsLink.mapPutJsonRepresentation("value", 
objectCollectionReprRenderer.render());
     }
 
-    // ///////////////////////////////////////////////////
-    // mutators
-    // ///////////////////////////////////////////////////
+    // MUTATORS
 
     @Override
     protected void addMutatorLinksIfEnabled() {
         // no-op
     }
 
-    // ///////////////////////////////////////////////////
-    // extensions and links
-    // ///////////////////////////////////////////////////
+    // EXTENSIONS AND LINKS
 
     @Override
     protected void addLinksToFormalDomainModel() {
diff --git 
a/viewers/restfulobjects/rendering/src/main/java/org/apache/causeway/viewer/restfulobjects/rendering/service/conneg/ContentNegotiationServiceForRestfulObjectsV1_0.java
 
b/viewers/restfulobjects/rendering/src/main/java/org/apache/causeway/viewer/restfulobjects/rendering/service/conneg/ContentNegotiationServiceForRestfulObjectsV1_0.java
index 7a1059dd1a5..f5d5018dc2f 100644
--- 
a/viewers/restfulobjects/rendering/src/main/java/org/apache/causeway/viewer/restfulobjects/rendering/service/conneg/ContentNegotiationServiceForRestfulObjectsV1_0.java
+++ 
b/viewers/restfulobjects/rendering/src/main/java/org/apache/causeway/viewer/restfulobjects/rendering/service/conneg/ContentNegotiationServiceForRestfulObjectsV1_0.java
@@ -115,7 +115,7 @@ ResponseBuilder buildResponseTo(
 
         final ResponseBuilder responseBuilder = Responses.ofOk(renderer, 
Caching.NONE, rootRepresentation);
 
-        if(resourceContext.getIntent() == 
RepresentationService.Intent.JUST_CREATED) {
+        if(resourceContext.intent() == 
RepresentationService.Intent.JUST_CREATED) {
             responseBuilder.status(Response.Status.CREATED);
         }
 
@@ -132,7 +132,7 @@ public ResponseBuilder buildResponse(
         var renderer =
                 new ObjectPropertyReprRenderer(resourceContext)
                 .with(objectAndProperty)
-                .usingLinkTo(resourceContext.getObjectAdapterLinkTo());
+                .usingLinkTo(resourceContext.objectAdapterLinkTo());
 
         var repMode = objectAndProperty.getRepresentationMode();
         if(repMode.isExplicit()) {
@@ -164,7 +164,7 @@ ResponseBuilder buildResponseTo(
         final ObjectCollectionReprRenderer renderer =
                 new ObjectCollectionReprRenderer(resourceContext, null, null, 
representation);
         renderer.with(objectAndCollection)
-        .usingLinkTo(resourceContext.getObjectAdapterLinkTo());
+        .usingLinkTo(resourceContext.objectAdapterLinkTo());
 
         if(objectAndCollection.getRepresentationMode().isExplicit()) {
             
renderer.withMemberMode(objectAndCollection.getRepresentationMode());
@@ -183,7 +183,7 @@ public ResponseBuilder buildResponse(
         var renderer =
                 new ObjectActionReprRenderer(resourceContext)
                 .with(objectAndAction)
-                .usingLinkTo(resourceContext.getObjectAdapterLinkTo())
+                .usingLinkTo(resourceContext.objectAdapterLinkTo())
                 .asStandalone();
 
         return responseBuilder(Responses.ofOk(renderer, Caching.NONE));
@@ -194,7 +194,7 @@ public ResponseBuilder buildResponse(
             final IResourceContext resourceContext,
             final ObjectAndActionInvocation objectAndActionInvocation) {
 
-        final List<MediaType> acceptableMediaTypes = 
resourceContext.getAcceptableMediaTypes();
+        final List<MediaType> acceptableMediaTypes = 
resourceContext.acceptableMediaTypes();
 
         var returnTypeCompileTimeSpecification = 
objectAndActionInvocation.getReturnTypeSpecification();
 
@@ -329,14 +329,13 @@ ResponseBuilder buildResponseTo(
             final ObjectAndActionInvocation objectAndActionInvocation,
             final JsonRepresentation representation,
             final JsonRepresentation rootRepresentation) {
-        final ActionResultReprRenderer renderer =
+        var renderer =
                 new ActionResultReprRenderer(resourceContext, null, 
objectAndActionInvocation.getSelfLink(), representation);
-        renderer.with(objectAndActionInvocation)
-        .using(resourceContext.getObjectAdapterLinkTo());
-
-        final ResponseBuilder responseBuilder = Responses.ofOk(renderer, 
Caching.NONE, rootRepresentation);
+        renderer
+            .with(objectAndActionInvocation)
+            .using(resourceContext.objectAdapterLinkTo());
 
-        return responseBuilder;
+        return Responses.ofOk(renderer, Caching.NONE, rootRepresentation);
     }
 
     private static enum AcceptChecking {
@@ -370,7 +369,7 @@ private void ensureCompatibleAcceptHeader(
         if (producedProfile == null) {
             return;
         }
-        if(!isAccepted(producedProfile, 
resourceContext.getAcceptableMediaTypes())) {
+        if(!isAccepted(producedProfile, 
resourceContext.acceptableMediaTypes())) {
             throw 
RestfulObjectsApplicationException.create(RestfulResponse.HttpStatusCode.NOT_ACCEPTABLE);
         }
     }
diff --git 
a/viewers/restfulobjects/rendering/src/main/java/org/apache/causeway/viewer/restfulobjects/rendering/service/conneg/ContentNegotiationServiceOrgApacheCausewayV2.java
 
b/viewers/restfulobjects/rendering/src/main/java/org/apache/causeway/viewer/restfulobjects/rendering/service/conneg/ContentNegotiationServiceOrgApacheCausewayV2.java
index 99ac5912d7c..6b0332c6e94 100644
--- 
a/viewers/restfulobjects/rendering/src/main/java/org/apache/causeway/viewer/restfulobjects/rendering/service/conneg/ContentNegotiationServiceOrgApacheCausewayV2.java
+++ 
b/viewers/restfulobjects/rendering/src/main/java/org/apache/causeway/viewer/restfulobjects/rendering/service/conneg/ContentNegotiationServiceOrgApacheCausewayV2.java
@@ -325,13 +325,13 @@ protected Response.ResponseBuilder responseBuilder(final 
Response.ResponseBuilde
     }
 
     boolean canAccept(final IResourceContext resourceContext) {
-        final List<MediaType> acceptableMediaTypes = 
resourceContext.getAcceptableMediaTypes();
+        final List<MediaType> acceptableMediaTypes = 
resourceContext.acceptableMediaTypes();
         return mediaTypeParameterMatches(acceptableMediaTypes, "profile", 
ACCEPT_PROFILE);
     }
 
     protected EnumSet<SuppressionType> suppress(
             final IResourceContext resourceContext) {
-        final List<MediaType> acceptableMediaTypes = 
resourceContext.getAcceptableMediaTypes();
+        final List<MediaType> acceptableMediaTypes = 
resourceContext.acceptableMediaTypes();
         return 
SuppressionType.ParseUtil.parse(mediaTypeParameterList(acceptableMediaTypes, 
"suppress"));
     }
 
@@ -343,7 +343,7 @@ private void appendObjectTo(
 
         appendPropertiesTo(resourceContext, owner, rootRepresentation, 
suppression);
 
-        var where = resourceContext.getWhere();
+        var where = resourceContext.where();
 
         owner.objSpec()
         .streamCollections(MixedIn.INCLUDED)
@@ -352,7 +352,7 @@ private void appendObjectTo(
             var collectionRepresentation = JsonRepresentation.newArray();
             rootRepresentation.mapPutJsonRepresentation(collection.getId(), 
collectionRepresentation);
 
-            var interactionInitiatedBy = 
resourceContext.getInteractionInitiatedBy();
+            var interactionInitiatedBy = 
resourceContext.interactionInitiatedBy();
             var visibilityConsent = collection.isVisible(owner, 
interactionInitiatedBy, where);
             if (!visibilityConsent.isAllowed()) {
                 return;
@@ -371,8 +371,8 @@ private void appendPropertiesTo(
             final JsonRepresentation rootRepresentation,
             final EnumSet<SuppressionType> suppression) {
 
-        var interactionInitiatedBy = 
resourceContext.getInteractionInitiatedBy();
-        var where = resourceContext.getWhere();
+        var interactionInitiatedBy = resourceContext.interactionInitiatedBy();
+        var where = resourceContext.where();
         final Stream<OneToOneAssociation> properties = objectAdapter.objSpec()
                 .streamProperties(MixedIn.INCLUDED);
 
@@ -427,7 +427,7 @@ private void appendCollectionTo(
             final JsonRepresentation representation,
             final EnumSet<SuppressionType> suppression) {
 
-        
managedCollection.streamElements(resourceContext.getInteractionInitiatedBy())
+        
managedCollection.streamElements(resourceContext.interactionInitiatedBy())
         .forEach(element->
             appendElementTo(resourceContext, element, representation, 
suppression));
     }
diff --git 
a/viewers/restfulobjects/rendering/src/main/java/org/apache/causeway/viewer/restfulobjects/rendering/service/conneg/ContentNegotiationServiceOrgApacheIsisV1.java
 
b/viewers/restfulobjects/rendering/src/main/java/org/apache/causeway/viewer/restfulobjects/rendering/service/conneg/ContentNegotiationServiceOrgApacheIsisV1.java
index 77d60f61f98..fdd4a8a3bdf 100644
--- 
a/viewers/restfulobjects/rendering/src/main/java/org/apache/causeway/viewer/restfulobjects/rendering/service/conneg/ContentNegotiationServiceOrgApacheIsisV1.java
+++ 
b/viewers/restfulobjects/rendering/src/main/java/org/apache/causeway/viewer/restfulobjects/rendering/service/conneg/ContentNegotiationServiceOrgApacheIsisV1.java
@@ -91,7 +91,7 @@ public Response.ResponseBuilder buildResponse(
     // -- HELPER
 
     private boolean canAccept(final IResourceContext resourceContext) {
-        final List<MediaType> acceptableMediaTypes = 
resourceContext.getAcceptableMediaTypes();
+        final List<MediaType> acceptableMediaTypes = 
resourceContext.acceptableMediaTypes();
         return mediaTypeParameterMatches(acceptableMediaTypes, "profile", 
ACCEPT_PROFILE);
     }
 
diff --git 
a/viewers/restfulobjects/rendering/src/main/java/org/apache/causeway/viewer/restfulobjects/rendering/service/conneg/ContentNegotiationServiceXRoDomainType.java
 
b/viewers/restfulobjects/rendering/src/main/java/org/apache/causeway/viewer/restfulobjects/rendering/service/conneg/ContentNegotiationServiceXRoDomainType.java
index f1dc67b9fd6..e4ab65a4404 100644
--- 
a/viewers/restfulobjects/rendering/src/main/java/org/apache/causeway/viewer/restfulobjects/rendering/service/conneg/ContentNegotiationServiceXRoDomainType.java
+++ 
b/viewers/restfulobjects/rendering/src/main/java/org/apache/causeway/viewer/restfulobjects/rendering/service/conneg/ContentNegotiationServiceXRoDomainType.java
@@ -86,7 +86,7 @@ public Response.ResponseBuilder buildResponse(
     protected MediaType mediaTypeFrom(
             final IResourceContext renderContext,
             final RepresentationType representationType) {
-        final List<MediaType> acceptableMediaTypes = 
renderContext.getAcceptableMediaTypes();
+        final List<MediaType> acceptableMediaTypes = 
renderContext.acceptableMediaTypes();
         MediaType mediaType =
                 
representationType.matchesXmlProfileWithParameter(acceptableMediaTypes, 
X_RO_DOMAIN_TYPE);
 
@@ -124,7 +124,7 @@ protected Response.ResponseBuilder buildResponse(
             final Object domainObject,
             final RepresentationType representationType) {
 
-        final List<MediaType> acceptableMediaTypes = 
renderContext.getAcceptableMediaTypes();
+        final List<MediaType> acceptableMediaTypes = 
renderContext.acceptableMediaTypes();
 
         final MediaType mediaType = mediaTypeFrom(renderContext, 
representationType);
         if (mediaType == null) {
diff --git 
a/viewers/restfulobjects/test/src/test/java/org/apache/causeway/viewer/restfulobjects/test/CausewayViewerRestfulObjectsIntegTestAbstract.java
 
b/viewers/restfulobjects/test/src/test/java/org/apache/causeway/viewer/restfulobjects/test/CausewayViewerRestfulObjectsIntegTestAbstract.java
index c8bd299b428..3062b707362 100644
--- 
a/viewers/restfulobjects/test/src/test/java/org/apache/causeway/viewer/restfulobjects/test/CausewayViewerRestfulObjectsIntegTestAbstract.java
+++ 
b/viewers/restfulobjects/test/src/test/java/org/apache/causeway/viewer/restfulobjects/test/CausewayViewerRestfulObjectsIntegTestAbstract.java
@@ -34,13 +34,9 @@
 import org.junit.jupiter.api.TestInstance;
 import org.junit.jupiter.api.TestMethodOrder;
 
-import org.springframework.boot.SpringBootConfiguration;
-import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.boot.test.web.server.LocalServerPort;
 import org.springframework.context.annotation.Import;
-import org.springframework.context.annotation.PropertySource;
-import org.springframework.context.annotation.PropertySources;
 import org.springframework.core.io.ClassPathResource;
 import org.jspecify.annotations.Nullable;
 import org.springframework.test.context.ActiveProfiles;
@@ -48,16 +44,10 @@
 import org.apache.causeway.applib.services.xactn.TransactionService;
 import org.apache.causeway.applib.value.Blob;
 import org.apache.causeway.core.config.environment.CausewaySystemEnvironment;
-import org.apache.causeway.core.config.presets.CausewayPresets;
 import org.apache.causeway.core.metamodel.specloader.SpecificationLoader;
-import 
org.apache.causeway.core.runtimeservices.CausewayModuleCoreRuntimeServices;
-import org.apache.causeway.security.bypass.CausewayModuleSecurityBypass;
-import 
org.apache.causeway.testing.fixtures.applib.CausewayModuleTestingFixturesApplib;
 import org.apache.causeway.viewer.restfulobjects.client.AuthenticationMode;
 import org.apache.causeway.viewer.restfulobjects.client.RestfulClient;
 import org.apache.causeway.viewer.restfulobjects.client.RestfulClientConfig;
-import 
org.apache.causeway.viewer.restfulobjects.jaxrsresteasy.CausewayModuleViewerRestfulObjectsJaxrsResteasy;
-
 import static 
org.apache.causeway.commons.internal.assertions._Assert.assertNotNull;
 
 import lombok.SneakyThrows;
@@ -79,10 +69,11 @@
  */
 @SpringBootTest(
         classes = {
-                CausewayViewerRestfulObjectsIntegTestAbstract.TestApp.class
+                CausewayViewerRestfulObjectsIntegTestManifest.class
         },
         webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT
 )
+
 @TestInstance(TestInstance.Lifecycle.PER_CLASS)
 @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
 @ActiveProfiles("test")
@@ -90,38 +81,15 @@ public abstract class 
CausewayViewerRestfulObjectsIntegTestAbstract {
 
     private final Class<?> resourceBaseClazz;
 
-    protected CausewayViewerRestfulObjectsIntegTestAbstract(
-            final Class<?> resourceBaseClazz
-    ) {
+    protected CausewayViewerRestfulObjectsIntegTestAbstract(final Class<?> 
resourceBaseClazz) {
         this.resourceBaseClazz = resourceBaseClazz;
     }
 
-    /**
-     * Compared to the production app manifest 
<code>domainapp.webapp.AppManifest</code>,
-     * here we in effect disable security checks, and we exclude any web/UI 
modules.
-     */
-    @SpringBootConfiguration
-    @EnableAutoConfiguration
-    @Import({
-            CausewayModuleCoreRuntimeServices.class,
-            CausewayModuleSecurityBypass.class,
-            CausewayModuleTestingFixturesApplib.class,
-            CausewayModuleViewerRestfulObjectsJaxrsResteasy.class,
-    })
-    @PropertySources({
-            @PropertySource(CausewayPresets.H2InMemory_withUniqueSchema),
-            @PropertySource(CausewayPresets.SilenceMetaModel),
-            @PropertySource(CausewayPresets.SilenceProgrammingModel),
-    })
-    public static class TestApp {
-    }
-
     @Inject protected CausewaySystemEnvironment causewaySystemEnvironment;
     @Inject protected SpecificationLoader specificationLoader;
     @Inject protected TransactionService transactionService;
 
-    @LocalServerPort
-    protected int port;
+    @LocalServerPort protected int port;
 
     @BeforeEach
     void init(final TestInfo testInfo) {
@@ -152,8 +120,7 @@ protected RestfulClient restfulClient() {
 
     public enum BookmarkOptions {
         SCRUB,
-        PRESERVE,
-        ;
+        PRESERVE
     }
 
     protected Options jsonOptions() {
diff --git 
a/viewers/restfulobjects/test/src/test/java/org/apache/causeway/viewer/restfulobjects/test/CausewayViewerRestfulObjectsIntegTestManifest.java
 
b/viewers/restfulobjects/test/src/test/java/org/apache/causeway/viewer/restfulobjects/test/CausewayViewerRestfulObjectsIntegTestManifest.java
new file mode 100644
index 00000000000..62a36585acc
--- /dev/null
+++ 
b/viewers/restfulobjects/test/src/test/java/org/apache/causeway/viewer/restfulobjects/test/CausewayViewerRestfulObjectsIntegTestManifest.java
@@ -0,0 +1,52 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.causeway.viewer.restfulobjects.test;
+
+import org.springframework.boot.SpringBootConfiguration;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.context.annotation.Import;
+import org.springframework.context.annotation.PropertySource;
+import org.springframework.context.annotation.PropertySources;
+
+import org.apache.causeway.core.config.presets.CausewayPresets;
+import 
org.apache.causeway.core.runtimeservices.CausewayModuleCoreRuntimeServices;
+import org.apache.causeway.security.bypass.CausewayModuleSecurityBypass;
+import 
org.apache.causeway.testing.fixtures.applib.CausewayModuleTestingFixturesApplib;
+import 
org.apache.causeway.viewer.restfulobjects.jaxrsresteasy.CausewayModuleViewerRestfulObjectsJaxrsResteasy;
+
+/**
+ * Compared to the production app manifest 
<code>domainapp.webapp.AppManifest</code>,
+ * here we in effect disable security checks, and we exclude any web/UI 
modules.
+ */
+@SpringBootConfiguration
+@EnableAutoConfiguration
+@Import({
+        CausewayModuleCoreRuntimeServices.class,
+        CausewayModuleSecurityBypass.class,
+        CausewayModuleTestingFixturesApplib.class,
+        CausewayModuleViewerRestfulObjectsJaxrsResteasy.class,
+})
+@PropertySources({
+        @PropertySource(CausewayPresets.H2InMemory_withUniqueSchema),
+        @PropertySource(CausewayPresets.SilenceMetaModel),
+        @PropertySource(CausewayPresets.SilenceProgrammingModel),
+})
+class CausewayViewerRestfulObjectsIntegTestManifest {
+
+}
diff --git 
a/viewers/restfulobjects/test/src/test/java/org/apache/causeway/viewer/restfulobjects/test/scenarios/Abstract_IntegTest.java
 
b/viewers/restfulobjects/test/src/test/java/org/apache/causeway/viewer/restfulobjects/test/scenarios/Abstract_IntegTest.java
index 6c069d73667..804a30befb2 100644
--- 
a/viewers/restfulobjects/test/src/test/java/org/apache/causeway/viewer/restfulobjects/test/scenarios/Abstract_IntegTest.java
+++ 
b/viewers/restfulobjects/test/src/test/java/org/apache/causeway/viewer/restfulobjects/test/scenarios/Abstract_IntegTest.java
@@ -19,17 +19,23 @@
 package org.apache.causeway.viewer.restfulobjects.test.scenarios;
 
 import jakarta.inject.Inject;
+import jakarta.ws.rs.core.Response;
 
-import org.apache.causeway.viewer.restfulobjects.client.RestfulClient;
-
+import org.jspecify.annotations.Nullable;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
 import org.springframework.context.annotation.Import;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.TestPropertySource;
 import org.springframework.transaction.annotation.Propagation;
 
 import org.apache.causeway.applib.services.bookmark.BookmarkService;
 import 
org.apache.causeway.persistence.jpa.eclipselink.CausewayModulePersistenceJpaEclipselink;
+import org.apache.causeway.viewer.restfulobjects.client.RestfulClient;
 import 
org.apache.causeway.viewer.restfulobjects.test.CausewayViewerRestfulObjectsIntegTestAbstract;
 import org.apache.causeway.viewer.restfulobjects.test.domain.UniversityModule;
 import org.apache.causeway.viewer.restfulobjects.test.domain.dom.Department;
@@ -41,6 +47,8 @@
         UniversityModule.class,
         CausewayModulePersistenceJpaEclipselink.class,
 })
+@TestPropertySource(properties = 
"logging.level.org.apache.causeway.core.runtimeservices.session.InteractionServiceDefault=DEBUG")
+@DirtiesContext
 public abstract class Abstract_IntegTest extends 
CausewayViewerRestfulObjectsIntegTestAbstract {
 
     @Inject protected DepartmentRepository departmentRepository;
@@ -50,7 +58,7 @@ public abstract class Abstract_IntegTest extends 
CausewayViewerRestfulObjectsInt
 
     protected RestfulClient restfulClient;
 
-    protected Abstract_IntegTest(Class<?> resourceBaseClazz) {
+    protected Abstract_IntegTest(final Class<?> resourceBaseClazz) {
         super(resourceBaseClazz);
     }
 
@@ -104,4 +112,9 @@ protected void afterEach(){
         });
     }
 
+    protected void assertResponseOK(@Nullable final Response response) {
+        assertNotNull(response);
+        assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
+    }
+
 }
diff --git 
a/viewers/restfulobjects/test/src/test/java/org/apache/causeway/viewer/restfulobjects/test/scenarios/dept/Department_IntegTest.java
 
b/viewers/restfulobjects/test/src/test/java/org/apache/causeway/viewer/restfulobjects/test/scenarios/dept/Department_IntegTest.java
index 32c659bb553..e238f0be153 100644
--- 
a/viewers/restfulobjects/test/src/test/java/org/apache/causeway/viewer/restfulobjects/test/scenarios/dept/Department_IntegTest.java
+++ 
b/viewers/restfulobjects/test/src/test/java/org/apache/causeway/viewer/restfulobjects/test/scenarios/dept/Department_IntegTest.java
@@ -34,11 +34,11 @@
 import org.apache.causeway.viewer.restfulobjects.test.domain.dom.Department;
 import 
org.apache.causeway.viewer.restfulobjects.test.scenarios.Abstract_IntegTest;
 
-public class Department_IntegTest extends Abstract_IntegTest {
+class Department_IntegTest extends Abstract_IntegTest {
 
     @Test
     @UseReporter(DiffReporter.class)
-    public void exists() {
+    void exists() {
 
         // given
         Bookmark bookmark = 
transactionService.callTransactional(Propagation.REQUIRED, () -> {
@@ -46,7 +46,7 @@ public void exists() {
             return bookmarkService.bookmarkFor(classics).orElseThrow();
         }).valueAsNonNullElseFail();
 
-        Invocation.Builder request = 
restfulClient.request(String.format("/objects/%s/%s", 
bookmark.getLogicalTypeName(), bookmark.getIdentifier()));
+        Invocation.Builder request = 
restfulClient.request(String.format("/objects/%s/%s", 
bookmark.logicalTypeName(), bookmark.identifier()));
 
         // when
         var response = request.get();
@@ -62,7 +62,7 @@ public void exists() {
 
     @Test
     @UseReporter(DiffReporter.class)
-    public void collection_with_staff_members() {
+    void collection_with_staff_members() {
 
         // given
         Bookmark bookmark = 
transactionService.callTransactional(Propagation.REQUIRED, () -> {
@@ -70,7 +70,7 @@ public void collection_with_staff_members() {
             return bookmarkService.bookmarkFor(classics).orElseThrow();
         }).valueAsNonNullElseFail();
 
-        Invocation.Builder request = 
restfulClient.request(String.format("/objects/%s/%s/collections/staffMembers", 
bookmark.getLogicalTypeName(), bookmark.getIdentifier()));
+        Invocation.Builder request = 
restfulClient.request(String.format("/objects/%s/%s/collections/staffMembers", 
bookmark.logicalTypeName(), bookmark.identifier()));
 
         // when
         var response = request.get();
@@ -86,7 +86,7 @@ public void collection_with_staff_members() {
 
     @Test
     @UseReporter(DiffReporter.class)
-    public void collection_with_no_staff_members() {
+    void collection_with_no_staff_members() {
 
         // given
         Bookmark bookmark = 
transactionService.callTransactional(Propagation.REQUIRED, () -> {
@@ -94,7 +94,7 @@ public void collection_with_no_staff_members() {
             return bookmarkService.bookmarkFor(classics).orElseThrow();
         }).valueAsNonNullElseFail();
 
-        Invocation.Builder request = 
restfulClient.request(String.format("/objects/%s/%s/collections/staffMembers", 
bookmark.getLogicalTypeName(), bookmark.getIdentifier()));
+        Invocation.Builder request = 
restfulClient.request(String.format("/objects/%s/%s/collections/staffMembers", 
bookmark.logicalTypeName(), bookmark.identifier()));
 
         // when
         var response = request.get();
@@ -110,7 +110,7 @@ public void collection_with_no_staff_members() {
 
     @Test
     @UseReporter(DiffReporter.class)
-    public void does_not_exist() {
+    void does_not_exist() {
 
         // given
         Invocation.Builder request = 
restfulClient.request("/objects/university.dept.Department/9999999");
diff --git 
a/viewers/restfulobjects/test/src/test/java/org/apache/causeway/viewer/restfulobjects/test/scenarios/home/HomePage_IntegTest.java
 
b/viewers/restfulobjects/test/src/test/java/org/apache/causeway/viewer/restfulobjects/test/scenarios/home/HomePage_IntegTest.java
index 4ad234d1e7d..833fa3de35b 100644
--- 
a/viewers/restfulobjects/test/src/test/java/org/apache/causeway/viewer/restfulobjects/test/scenarios/home/HomePage_IntegTest.java
+++ 
b/viewers/restfulobjects/test/src/test/java/org/apache/causeway/viewer/restfulobjects/test/scenarios/home/HomePage_IntegTest.java
@@ -19,22 +19,19 @@
 package org.apache.causeway.viewer.restfulobjects.test.scenarios.home;
 
 import jakarta.ws.rs.client.Invocation;
-import jakarta.ws.rs.core.Response;
-
-import 
org.apache.causeway.viewer.restfulobjects.test.scenarios.Abstract_IntegTest;
 
 import org.approvaltests.Approvals;
 import org.approvaltests.reporters.DiffReporter;
 import org.approvaltests.reporters.UseReporter;
 import org.junit.jupiter.api.Test;
 
-import static org.assertj.core.api.Assertions.assertThat;
+import 
org.apache.causeway.viewer.restfulobjects.test.scenarios.Abstract_IntegTest;
 
-public class HomePage_IntegTest extends Abstract_IntegTest {
+class HomePage_IntegTest extends Abstract_IntegTest {
 
     @Test
     @UseReporter(DiffReporter.class)
-    public void homePage() {
+    void homePage() {
 
         // given
         Invocation.Builder request = restfulClient.request("/");
@@ -43,13 +40,10 @@ public void homePage() {
         var response = request.get();
 
         // then
-        assertThat(response)
-                .extracting(Response::getStatus)
-                .isEqualTo(Response.Status.OK.getStatusCode());
+        assertResponseOK(response);
 
         var entity = response.readEntity(String.class);
 
         Approvals.verify(entity, jsonOptions());
-
     }
 }
diff --git 
a/viewers/restfulobjects/test/src/test/java/org/apache/causeway/viewer/restfulobjects/test/scenarios/staff/Staff_hilevel_IntegTest.java
 
b/viewers/restfulobjects/test/src/test/java/org/apache/causeway/viewer/restfulobjects/test/scenarios/staff/Staff_hilevel_IntegTest.java
index 5a2908d8010..b4af917eb38 100644
--- 
a/viewers/restfulobjects/test/src/test/java/org/apache/causeway/viewer/restfulobjects/test/scenarios/staff/Staff_hilevel_IntegTest.java
+++ 
b/viewers/restfulobjects/test/src/test/java/org/apache/causeway/viewer/restfulobjects/test/scenarios/staff/Staff_hilevel_IntegTest.java
@@ -28,6 +28,7 @@
 import org.junit.jupiter.api.Test;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
 
 import org.springframework.transaction.annotation.Propagation;
 
@@ -42,10 +43,10 @@
 
 class Staff_hilevel_IntegTest extends Abstract_IntegTest {
 
-    @SneakyThrows
     @Test
     @UseReporter(DiffReporter.class)
-    public void createStaffMemberWithPhoto() {
+    @SneakyThrows
+    void createStaffMemberWithPhoto() {
 
         // given
         final var staffName = "Fred Smith";
@@ -80,11 +81,10 @@ public void createStaffMemberWithPhoto() {
         
assertThat(response.getStatusInfo().getFamily()).isEqualTo(Response.Status.Family.SUCCESSFUL);
         
assertThat(response.getStatus()).isEqualTo(Response.Status.OK.getStatusCode());
 
-        // and also json response
+        // and also JSON response
+        assertResponseOK(response);
         var entity = response.readEntity(String.class);
-        assertThat(response)
-                .extracting(Response::getStatus)
-                .isEqualTo(Response.Status.OK.getStatusCode());
+        assertNotNull(entity);
 
         // and also object is created in database
         final var bookmarkAfterIfAny = 
transactionService.callTransactional(Propagation.REQUIRED, () -> {
@@ -94,10 +94,10 @@ public void createStaffMemberWithPhoto() {
         assertThat(bookmarkAfterIfAny).isNotEmpty();
     }
 
-    @SneakyThrows
     @Test
     @UseReporter(DiffReporter.class)
-    public void createStaffMemberWithPhoto_using_map() {
+    @SneakyThrows
+    void createStaffMemberWithPhoto_using_map() {
 
         // given
         final var staffName = "Fred Smith";
@@ -128,10 +128,9 @@ public void createStaffMemberWithPhoto_using_map() {
         
assertThat(response.getStatus()).isEqualTo(Response.Status.OK.getStatusCode());
 
         // and also json response
+        assertResponseOK(response);
         var entity = response.readEntity(String.class);
-        assertThat(response)
-                .extracting(Response::getStatus)
-                .isEqualTo(Response.Status.OK.getStatusCode());
+        assertNotNull(entity);
 
         // and also object is created in database
         final var bookmarkAfterIfAny = 
transactionService.callTransactional(Propagation.REQUIRED, () -> {
@@ -141,10 +140,10 @@ public void createStaffMemberWithPhoto_using_map() {
         assertThat(bookmarkAfterIfAny).isNotEmpty();
     }
 
-    @SneakyThrows
     @Test
     @UseReporter(DiffReporter.class)
-    public void createStaffMemberWithPhoto2() {
+    @SneakyThrows
+    void createStaffMemberWithPhoto2() {
 
         // given
         final var staffName = "Fred Smith";
@@ -183,10 +182,9 @@ public void createStaffMemberWithPhoto2() {
         
assertThat(response.getStatus()).isEqualTo(Response.Status.OK.getStatusCode());
 
         // and also json response
+        assertResponseOK(response);
         var entity = response.readEntity(String.class);
-        assertThat(response)
-                .extracting(Response::getStatus)
-                .isEqualTo(Response.Status.OK.getStatusCode());
+        assertNotNull(entity);
 
         // and also object is created in database
         final var bookmarkAfterIfAny = 
transactionService.callTransactional(Propagation.REQUIRED, () -> {
@@ -196,10 +194,10 @@ public void createStaffMemberWithPhoto2() {
         assertThat(bookmarkAfterIfAny).isNotEmpty();
     }
 
-    @SneakyThrows
     @Test
     @UseReporter(DiffReporter.class)
-    public void createStaffMemberWithPhoto2_using_map() {
+    @SneakyThrows
+    void createStaffMemberWithPhoto2_using_map() {
 
         // given
         final var staffName = "Fred Smith";
@@ -243,10 +241,9 @@ public void createStaffMemberWithPhoto2_using_map() {
         
assertThat(response.getStatus()).isEqualTo(Response.Status.OK.getStatusCode());
 
         // and also json response
+        assertResponseOK(response);
         var entity = response.readEntity(String.class);
-        assertThat(response)
-                .extracting(Response::getStatus)
-                .isEqualTo(Response.Status.OK.getStatusCode());
+        assertNotNull(entity);
 
         // and also object is created in database
         final var bookmarkAfterIfAny = 
transactionService.callTransactional(Propagation.REQUIRED, () -> {
@@ -267,7 +264,7 @@ private String asAbsoluteHref(final Bookmark bookmark) {
     }
 
     private String asRelativeHref(final Bookmark bookmark) {
-        return String.format("objects/%s/%s", bookmark.getLogicalTypeName(), 
bookmark.getIdentifier());
+        return String.format("objects/%s/%s", bookmark.logicalTypeName(), 
bookmark.identifier());
     }
 
 }
diff --git 
a/viewers/restfulobjects/test/src/test/java/org/apache/causeway/viewer/restfulobjects/test/scenarios/staff/Staff_lowlevel_v1_IntegTest.java
 
b/viewers/restfulobjects/test/src/test/java/org/apache/causeway/viewer/restfulobjects/test/scenarios/staff/Staff_lowlevel_v1_IntegTest.java
index 3926e49561d..d634d95e942 100644
--- 
a/viewers/restfulobjects/test/src/test/java/org/apache/causeway/viewer/restfulobjects/test/scenarios/staff/Staff_lowlevel_v1_IntegTest.java
+++ 
b/viewers/restfulobjects/test/src/test/java/org/apache/causeway/viewer/restfulobjects/test/scenarios/staff/Staff_lowlevel_v1_IntegTest.java
@@ -25,7 +25,6 @@
 
 import jakarta.activation.MimeType;
 import jakarta.activation.MimeTypeParseException;
-import jakarta.annotation.Priority;
 import jakarta.inject.Named;
 import jakarta.ws.rs.client.Entity;
 import jakarta.ws.rs.client.Invocation;
@@ -36,18 +35,17 @@
 import org.approvaltests.Approvals;
 import org.approvaltests.reporters.DiffReporter;
 import org.approvaltests.reporters.UseReporter;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Order;
 import org.junit.jupiter.api.Test;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
 
 import org.springframework.context.annotation.Import;
 import org.springframework.stereotype.Component;
 import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.annotation.DirtiesContext.MethodMode;
 import org.springframework.transaction.annotation.Propagation;
 
-import org.apache.causeway.applib.annotation.PriorityPrecedence;
 import org.apache.causeway.applib.services.bookmark.Bookmark;
 import org.apache.causeway.applib.value.Blob;
 import org.apache.causeway.applib.value.NamedWithMimeType;
@@ -58,7 +56,6 @@
 import org.apache.causeway.commons.internal.base._Bytes;
 import org.apache.causeway.commons.internal.base._Strings;
 import org.apache.causeway.commons.io.DataSource;
-import 
org.apache.causeway.core.internaltestsupport.annotations.DisabledIfRunningWithSurefire;
 import org.apache.causeway.core.metamodel.valuesemantics.BlobValueSemantics;
 import org.apache.causeway.schema.common.v2.ValueType;
 import 
org.apache.causeway.viewer.restfulobjects.test.scenarios.Abstract_IntegTest;
@@ -66,23 +63,16 @@
 import lombok.Getter;
 import lombok.SneakyThrows;
 
-@DisabledIfRunningWithSurefire //XXX surefire run broken since around 
2024-09-20
-@Order(value = Integer.MAX_VALUE)   // last
-@DirtiesContext
-@Import({Staff_lowlevel_v1_IntegTest.BlobValueSemanticsV1LegacyEncoding.class})
-public class Staff_lowlevel_v1_IntegTest extends Abstract_IntegTest {
+@Import({
+    Staff_lowlevel_v1_IntegTest.BlobValueSemanticsV1LegacyEncoding.class
+})
+class Staff_lowlevel_v1_IntegTest extends Abstract_IntegTest {
 
-    private GsonBuilder gsonBuilder;
-
-    @BeforeEach
-    void setup() {
-        gsonBuilder = new GsonBuilder();
-    }
-
-    @SneakyThrows
     @Test
     @UseReporter(DiffReporter.class)
-    public void createStaffMemberWithPhoto2() {
+    @DirtiesContext(methodMode = MethodMode.AFTER_METHOD)
+    @SneakyThrows
+    void createStaffMemberWithPhoto2() {
 
         // given
         final var staffName = "Fred Smith";
@@ -113,7 +103,7 @@ public void createStaffMemberWithPhoto2() {
         final var requestBuilder = 
restfulClient.request("services/university.dept.Staff/actions/createStaffMemberWithPhoto2/invoke");
 
         final var body = new Body(staffName, 
asAbsoluteHref(departmentBookmark), photoEncoded);
-        final var bodyJson = gsonBuilder.create().toJson(body);
+        final var bodyJson = new GsonBuilder().create().toJson(body);
 
         // then
         Approvals.verify(bodyJson, jsonOptions());
@@ -122,11 +112,9 @@ public void createStaffMemberWithPhoto2() {
         var response = requestBuilder.post(Entity.entity(bodyJson, 
"application/json"));
 
         // then
+        assertResponseOK(response);
         var entity = response.readEntity(String.class);
-        
assertThat(response.getStatusInfo().getFamily()).isEqualTo(Response.Status.Family.SUCCESSFUL);
-        
assertThat(response.getStatus()).isEqualTo(Response.Status.OK.getStatusCode());
-
-        // and also json response
+        assertNotNull(entity);
 
         // and also object is created in database
         final var bookmarkAfterIfAny = 
transactionService.callTransactional(Propagation.REQUIRED, () -> {
@@ -160,6 +148,10 @@ private String encodePdf(final String fileName, final 
byte[] pdfBytes) throws UR
     @Getter
     static class Body {
 
+        private Name name;
+        private Department department;
+        private Blob photo;
+
         /**
          * @param nameValue
          * @param departmentHrefValue
@@ -171,10 +163,6 @@ static class Body {
             department = new Department(new 
Department.Value(departmentHrefValue));
         }
 
-        private Name name;
-        private Department department;
-        private Blob photo;
-
         record Name(String value) {
         }
 
@@ -189,13 +177,11 @@ record Blob(String value) {
 
     @Component
     @Named("causeway.metamodel.value.BlobValueSemanticsV1LegacyEncoding")   // 
must have different name to original
-    @Priority(PriorityPrecedence.EARLY)                                     // 
and earlier precedence to be picked up
-    public static class BlobValueSemanticsV1LegacyEncoding
-            extends BlobValueSemantics
-            implements
-            Renderer<Blob> {
+    static class BlobValueSemanticsV1LegacyEncoding
+        extends BlobValueSemantics
+        implements Renderer<Blob> {
 
-        public BlobValueSemanticsV1LegacyEncoding(){
+        public BlobValueSemanticsV1LegacyEncoding() {
         }
 
         @Override
diff --git 
a/viewers/restfulobjects/test/src/test/java/org/apache/causeway/viewer/restfulobjects/test/scenarios/staff/Staff_lowlevel_v2_IntegTest.java
 
b/viewers/restfulobjects/test/src/test/java/org/apache/causeway/viewer/restfulobjects/test/scenarios/staff/Staff_lowlevel_v2_IntegTest.java
index 588115625c9..e5ee41e8fe6 100644
--- 
a/viewers/restfulobjects/test/src/test/java/org/apache/causeway/viewer/restfulobjects/test/scenarios/staff/Staff_lowlevel_v2_IntegTest.java
+++ 
b/viewers/restfulobjects/test/src/test/java/org/apache/causeway/viewer/restfulobjects/test/scenarios/staff/Staff_lowlevel_v2_IntegTest.java
@@ -30,10 +30,10 @@
 import org.approvaltests.Approvals;
 import org.approvaltests.reporters.DiffReporter;
 import org.approvaltests.reporters.UseReporter;
-import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
 
 import org.springframework.transaction.annotation.Propagation;
 
@@ -45,18 +45,11 @@
 import lombok.Getter;
 import lombok.SneakyThrows;
 
-public class Staff_lowlevel_v2_IntegTest extends Abstract_IntegTest {
+class Staff_lowlevel_v2_IntegTest extends Abstract_IntegTest {
 
-    private GsonBuilder gsonBuilder;
-
-    @BeforeEach
-    void setup() {
-        gsonBuilder = new GsonBuilder();
-    }
-
-    @SneakyThrows
     @Test
     @UseReporter(DiffReporter.class)
+    @SneakyThrows
     public void createStaffMemberWithPhoto2() {
 
         // given
@@ -88,7 +81,7 @@ public void createStaffMemberWithPhoto2() {
         final var requestBuilder = 
restfulClient.request("services/university.dept.Staff/actions/createStaffMemberWithPhoto2/invoke");
 
         final var body = new Body(staffName, 
asAbsoluteHref(departmentBookmark), photoEncoded);
-        final var bodyJson = gsonBuilder.create().toJson(body);
+        final var bodyJson = new GsonBuilder().create().toJson(body);
 
         // then
         Approvals.verify(bodyJson, jsonOptions());
@@ -97,11 +90,9 @@ public void createStaffMemberWithPhoto2() {
         var response = requestBuilder.post(Entity.entity(bodyJson, 
"application/json"));
 
         // then
+        assertResponseOK(response);
         var entity = response.readEntity(String.class);
-        
assertThat(response.getStatusInfo().getFamily()).isEqualTo(Response.Status.Family.SUCCESSFUL);
-        
assertThat(response.getStatus()).isEqualTo(Response.Status.OK.getStatusCode());
-
-        // and also json response
+        assertNotNull(entity);
 
         // and also object is created in database
         final var bookmarkAfterIfAny = 
transactionService.callTransactional(Propagation.REQUIRED, () -> {
@@ -128,6 +119,10 @@ private Blob readFileAsBlob(final String fileName) throws 
IOException {
     @Getter
     static class Body {
 
+        private Name name;
+        private Department department;
+        private Blob photo;
+
         /**
          * @param nameValue
          * @param departmentHrefValue
@@ -139,10 +134,6 @@ static class Body {
             photo = new Blob(new Blob.Value(blob.name(), 
blob.mimeType().toString(), Base64.getEncoder().encodeToString(blob.bytes())));
         }
 
-        private Name name;
-        private Department department;
-        private Blob photo;
-
         record Name(String value) {
         }
 
diff --git 
a/viewers/restfulobjects/test/src/test/resources/H2InMemory_withUniqueSchema.properties
 
b/viewers/restfulobjects/test/src/test/resources/H2InMemory_withUniqueSchema.properties
deleted file mode 100644
index 3e3ce263f97..00000000000
--- 
a/viewers/restfulobjects/test/src/test/resources/H2InMemory_withUniqueSchema.properties
+++ /dev/null
@@ -1,20 +0,0 @@
-#  Licensed to the Apache Software Foundation (ASF) under one
-#  or more contributor license agreements.  See the NOTICE file
-#  distributed with this work for additional information
-#  regarding copyright ownership.  The ASF licenses this file
-#  to you under the Apache License, Version 2.0 (the
-#  "License"); you may not use this file except in compliance
-#  with the License.  You may obtain a copy of the License at
-#  
-#         http://www.apache.org/licenses/LICENSE-2.0
-#         
-#  Unless required by applicable law or agreed to in writing,
-#  software distributed under the License is distributed on an
-#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-#  KIND, either express or implied.  See the License for the
-#  specific language governing permissions and limitations
-#  under the License.
-
-spring.sql.init.platform=h2
-spring.datasource.url=jdbc:h2:mem:T${random.long[1,9999999]};DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
-
diff --git 
a/viewers/restfulobjects/test/src/test/resources/SilenceMetaModel.properties 
b/viewers/restfulobjects/test/src/test/resources/SilenceMetaModel.properties
deleted file mode 100644
index 8c73de5f0c0..00000000000
--- a/viewers/restfulobjects/test/src/test/resources/SilenceMetaModel.properties
+++ /dev/null
@@ -1,18 +0,0 @@
-#  Licensed to the Apache Software Foundation (ASF) under one
-#  or more contributor license agreements.  See the NOTICE file
-#  distributed with this work for additional information
-#  regarding copyright ownership.  The ASF licenses this file
-#  to you under the Apache License, Version 2.0 (the
-#  "License"); you may not use this file except in compliance
-#  with the License.  You may obtain a copy of the License at
-#  
-#         http://www.apache.org/licenses/LICENSE-2.0
-#         
-#  Unless required by applicable law or agreed to in writing,
-#  software distributed under the License is distributed on an
-#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-#  KIND, either express or implied.  See the License for the
-#  specific language governing permissions and limitations
-#  under the License.
-
-logging.level.org.apache.causeway.core.metamodel.specloader.SpecificationLoaderDefault
 = WARN
diff --git 
a/viewers/restfulobjects/test/src/test/resources/SilenceProgrammingModel.properties
 
b/viewers/restfulobjects/test/src/test/resources/SilenceProgrammingModel.properties
deleted file mode 100644
index 086429e53df..00000000000
--- 
a/viewers/restfulobjects/test/src/test/resources/SilenceProgrammingModel.properties
+++ /dev/null
@@ -1,22 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#        http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-#
-#
-
-logging.level.org.apache.causeway.core.metamodel.specloader.ProgrammingModelServiceDefault
 = WARN
-logging.level.org.apache.causeway.core.metamodel.specloader.specimpl.FacetedMethodsBuilder
 = WARN
diff --git 
a/viewers/restfulobjects/test/src/test/resources/UseLog4j2Test.properties 
b/viewers/restfulobjects/test/src/test/resources/UseLog4j2Test.properties
deleted file mode 100644
index df0c635739f..00000000000
--- a/viewers/restfulobjects/test/src/test/resources/UseLog4j2Test.properties
+++ /dev/null
@@ -1,18 +0,0 @@
-#  Licensed to the Apache Software Foundation (ASF) under one
-#  or more contributor license agreements.  See the NOTICE file
-#  distributed with this work for additional information
-#  regarding copyright ownership.  The ASF licenses this file
-#  to you under the Apache License, Version 2.0 (the
-#  "License"); you may not use this file except in compliance
-#  with the License.  You may obtain a copy of the License at
-#  
-#         http://www.apache.org/licenses/LICENSE-2.0
-#         
-#  Unless required by applicable law or agreed to in writing,
-#  software distributed under the License is distributed on an
-#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-#  KIND, either express or implied.  See the License for the
-#  specific language governing permissions and limitations
-#  under the License.
-
-logging.config=log4j2-test.xml
\ No newline at end of file
diff --git 
a/viewers/restfulobjects/test/src/test/resources/junit-platform.propertes 
b/viewers/restfulobjects/test/src/test/resources/junit-platform.propertes
deleted file mode 100644
index 82e3e202a99..00000000000
--- a/viewers/restfulobjects/test/src/test/resources/junit-platform.propertes
+++ /dev/null
@@ -1,21 +0,0 @@
-#
-#  Licensed to the Apache Software Foundation (ASF) under one
-#  or more contributor license agreements.  See the NOTICE file
-#  distributed with this work for additional information
-#  regarding copyright ownership.  The ASF licenses this file
-#  to you under the Apache License, Version 2.0 (the
-#  "License"); you may not use this file except in compliance
-#  with the License.  You may obtain a copy of the License at
-#
-#        http://www.apache.org/licenses/LICENSE-2.0
-#
-#  Unless required by applicable law or agreed to in writing,
-#  software distributed under the License is distributed on an
-#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-#  KIND, either express or implied.  See the License for the
-#  specific language governing permissions and limitations
-#  under the License.
-#
-
-# ClassOrderer$OrderAnnotation sorts classes based on their @Order annotation
-junit.jupiter.testclass.order.default=org.junit.jupiter.api.ClassOrderer$OrderAnnotation
\ No newline at end of file
diff --git 
a/viewers/restfulobjects/testing/src/main/java/org/apache/causeway/viewer/restfulobjects/testing/ResourceContext_ensureCompatibleAcceptHeader_ContractTest.java
 
b/viewers/restfulobjects/testing/src/main/java/org/apache/causeway/viewer/restfulobjects/testing/ResourceContext_ensureCompatibleAcceptHeader_ContractTest.java
index 376fd0ae2b5..0bc4a92d28f 100644
--- 
a/viewers/restfulobjects/testing/src/main/java/org/apache/causeway/viewer/restfulobjects/testing/ResourceContext_ensureCompatibleAcceptHeader_ContractTest.java
+++ 
b/viewers/restfulobjects/testing/src/main/java/org/apache/causeway/viewer/restfulobjects/testing/ResourceContext_ensureCompatibleAcceptHeader_ContractTest.java
@@ -49,6 +49,7 @@
 import 
org.apache.causeway.viewer.restfulobjects.rendering.RestfulObjectsApplicationException;
 import 
org.apache.causeway.viewer.restfulobjects.viewer.context.ResourceContext;
 import 
org.apache.causeway.viewer.restfulobjects.viewer.resources.ResourceDescriptor;
+import 
org.apache.causeway.viewer.restfulobjects.viewer.resources.ResourceDescriptor.ResourceLink;
 
 public abstract class 
ResourceContext_ensureCompatibleAcceptHeader_ContractTest {
 
@@ -182,7 +183,7 @@ private void 
givenHttpHeadersGetAcceptableMediaTypesReturns(final List<MediaType
     private ResourceContext instantiateResourceContext(
             final RepresentationType representationType) {
 
-        var resourceDescriptor = new ResourceDescriptor(representationType, 
null, null);
+        var resourceDescriptor = new ResourceDescriptor(representationType, 
null, null, ResourceLink.NONE);
 
         return new ResourceContext(resourceDescriptor, mockHttpHeaders, null, 
null, null, null, null,
                 mockHttpServletRequest, null, null,
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 bf2ee6fee70..e43e524156f 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
@@ -19,6 +19,7 @@
 package org.apache.causeway.viewer.restfulobjects.viewer.context;
 
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
@@ -33,55 +34,55 @@
 import jakarta.ws.rs.ext.Providers;
 
 import org.apache.causeway.applib.annotation.Where;
+import org.apache.causeway.applib.id.LogicalType;
 import org.apache.causeway.applib.services.bookmark.Bookmark;
 import org.apache.causeway.commons.internal.base._Strings;
-import org.apache.causeway.commons.internal.collections._Sets;
 import org.apache.causeway.commons.internal.primitives._Ints;
+import org.apache.causeway.commons.io.UrlUtils;
 import org.apache.causeway.core.metamodel.consent.InteractionInitiatedBy;
 import org.apache.causeway.core.metamodel.context.MetaModelContext;
 import org.apache.causeway.core.metamodel.object.ManagedObject;
 import org.apache.causeway.core.metamodel.object.ManagedObjects;
 import org.apache.causeway.viewer.restfulobjects.applib.JsonRepresentation;
-import org.apache.causeway.viewer.restfulobjects.applib.RepresentationType;
 import 
org.apache.causeway.viewer.restfulobjects.applib.RestfulRequest.DomainModel;
 import 
org.apache.causeway.viewer.restfulobjects.applib.RestfulRequest.RequestParameter;
 import 
org.apache.causeway.viewer.restfulobjects.applib.RestfulResponse.HttpStatusCode;
 import org.apache.causeway.viewer.restfulobjects.rendering.IResourceContext;
 import 
org.apache.causeway.viewer.restfulobjects.rendering.RestfulObjectsApplicationException;
+import 
org.apache.causeway.viewer.restfulobjects.rendering.domainobjects.DomainObjectLinkTo;
+import 
org.apache.causeway.viewer.restfulobjects.rendering.domainobjects.DomainServiceLinkTo;
 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.service.RepresentationService.Intent;
 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;
 
-import lombok.Getter;
 import org.jspecify.annotations.NonNull;
-import lombok.Setter;
-
-public class ResourceContext
+import org.jspecify.annotations.Nullable;
+
+public record ResourceContext(
+    MetaModelContext metaModelContext,
+    ResourceDescriptor resourceDescriptor,
+    HttpHeaders httpHeaders,
+    Request request,
+    HttpServletRequest httpServletRequest,
+    HttpServletResponse httpServletResponse,
+    SecurityContext securityContext,
+    String applicationAbsoluteBase,
+    String restfulAbsoluteBase,
+
+    List<List<String>> followLinks,
+    boolean isValidateOnly,
+
+    InteractionInitiatedBy interactionInitiatedBy,
+
+    JsonRepresentation queryStringAsJsonRepr,
+    ObjectAdapterLinkTo objectAdapterLinkTo,
+    Set<Bookmark> rendered
+)
 implements IResourceContext {
 
-    @Getter(onMethod_={@Override})
-    private MetaModelContext metaModelContext;
-
-    @Getter private final HttpHeaders httpHeaders;
-    @Getter private final Request request;
-    @Getter private final HttpServletRequest httpServletRequest;
-    @Getter private final HttpServletResponse httpServletResponse;
-    @Getter private final SecurityContext securityContext;
-    private final String applicationAbsoluteBase;
-    private final String restfulAbsoluteBase;
-
-    @Getter private List<List<String>> followLinks;
-    @Getter private boolean validateOnly;
-
-    private final Where where;
-    private final RepresentationService.Intent intent;
-    @Getter private final InteractionInitiatedBy interactionInitiatedBy;
-    private final @NonNull RequestParams urlUnencodedQueryString;
-    private final JsonRepresentation readQueryStringAsMap;
-
-    // -- constructor and init
+    // -- NON CANONICAL CONSTRUCTORS
 
     public ResourceContext(
             final ResourceDescriptor resourceDescriptor,
@@ -98,60 +99,62 @@ public ResourceContext(
             final InteractionInitiatedBy interactionInitiatedBy,
             final Map<String, String[]> requestParams) {
 
-        this.metaModelContext = metaModelContext;
-
-        this.httpHeaders = httpHeaders;
-        //not used ... this.providers = providers;
-        this.request = request;
-        this.where = resourceDescriptor.where();
-        this.intent = resourceDescriptor.intent();
-        this.urlUnencodedQueryString = 
Optional.ofNullable(urlUnencodedQueryString)
-                .orElseGet(RequestParams::ofEmptyQueryString);
-        this.httpServletRequest = httpServletRequest;
-        this.httpServletResponse = httpServletResponse;
-        this.securityContext = securityContext;
-        this.interactionInitiatedBy = interactionInitiatedBy;
-
-        this.applicationAbsoluteBase = 
_Strings.suffix(applicationAbsoluteBase, "/");
-        this.restfulAbsoluteBase = _Strings.suffix(restfulAbsoluteBase, "/");
-
-        this.readQueryStringAsMap = requestArgsAsMap(requestParams);
-
-        init(resourceDescriptor.representationType());
+        this(resourceDescriptor, httpHeaders, providers, request, 
applicationAbsoluteBase, restfulAbsoluteBase,
+            httpServletRequest, httpServletResponse, securityContext, 
metaModelContext, interactionInitiatedBy,
+            requestArgsAsMap(requestParams, urlUnencodedQueryString));
     }
 
-    void init(final RepresentationType representationType) {
+    private ResourceContext(
+            final ResourceDescriptor resourceDescriptor,
+            final HttpHeaders httpHeaders,
+            final Providers providers,
+            final Request request,
+            final String applicationAbsoluteBase,
+            final String restfulAbsoluteBase,
+            final HttpServletRequest httpServletRequest,
+            final HttpServletResponse httpServletResponse,
+            final SecurityContext securityContext,
+            final MetaModelContext metaModelContext,
+            final InteractionInitiatedBy interactionInitiatedBy,
+            final JsonRepresentation requestArgsAsMap) {
+
+        this(metaModelContext, resourceDescriptor, httpHeaders, request, 
httpServletRequest, httpServletResponse, securityContext,
+            _Strings.suffix(applicationAbsoluteBase, "/"),
+            _Strings.suffix(restfulAbsoluteBase, "/"),
+            Collections.unmodifiableList(arg(requestArgsAsMap, 
RequestParameter.FOLLOW_LINKS)),
+            arg(requestArgsAsMap, RequestParameter.VALIDATE_ONLY),
+            interactionInitiatedBy,
+            requestArgsAsMap,
+            switch(resourceDescriptor.resourceLink()) {
+                case NONE -> null;
+                case OBJECT -> new DomainObjectLinkTo();
+                case SERVICE -> new DomainServiceLinkTo();
+            },
+            new HashSet<>());
 
-        // previously we checked for compatible accept headers here.
-        // now, though, this is a responsibility of the various 
ContentNegotiationService implementations
         ensureDomainModelQueryParamSupported();
-
-        this.followLinks = 
Collections.unmodifiableList(getArg(RequestParameter.FOLLOW_LINKS));
-        this.validateOnly = getArg(RequestParameter.VALIDATE_ONLY);
     }
 
-    private void ensureDomainModelQueryParamSupported() {
-        final DomainModel domainModel = getArg(RequestParameter.DOMAIN_MODEL);
-        if(domainModel != DomainModel.FORMAL) {
-            throw 
RestfulObjectsApplicationException.createWithMessage(HttpStatusCode.BAD_REQUEST,
-                    "x-ro-domain-model of '%s' is not supported", domainModel);
-        }
+    @Override public Where where() {
+        return resourceDescriptor().where();
     }
 
     /**
-     * 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).
+     * Only applies to rendering of objects
      */
-    public RequestParams getUrlUnencodedQueryString() {
-        return urlUnencodedQueryString;
+    @Override public Intent intent() {
+        return resourceDescriptor().intent();
     }
 
-    public JsonRepresentation getQueryStringAsJsonRepr() {
-        return readQueryStringAsMap;
+    private void ensureDomainModelQueryParamSupported() {
+        final DomainModel domainModel = arg(queryStringAsJsonRepr(), 
RequestParameter.DOMAIN_MODEL);
+        if(domainModel != DomainModel.FORMAL) {
+            throw 
RestfulObjectsApplicationException.createWithMessage(HttpStatusCode.BAD_REQUEST,
+                    "x-ro-domain-model of '%s' is not supported", domainModel);
+        }
     }
 
-    protected JsonRepresentation requestArgsAsMap(final Map<String, String[]> 
params) {
-
+    private static JsonRepresentation requestArgsAsMap(final Map<String, 
String[]> params, RequestParams urlUnencodedQueryString) {
         if(simpleQueryArgs(params)) {
             // try to process regular params and build up JSON repr
             final JsonRepresentation map = JsonRepresentation.newMap();
@@ -170,7 +173,9 @@ protected JsonRepresentation requestArgsAsMap(final 
Map<String, String[]> params
             }
             return map;
         } else {
-            return getUrlUnencodedQueryString().asMap();
+            return Optional.ofNullable(urlUnencodedQueryString)
+                .orElseGet(RequestParams::ofEmptyQueryString)
+                .asMap();
         }
     }
 
@@ -196,31 +201,14 @@ private static boolean simpleQueryArgs(final Map<String, 
String[]> params) {
         return true;
     }
 
-    public <Q> Q getArg(final RequestParameter<Q> requestParameter) {
-        final JsonRepresentation queryStringJsonRepr = 
getQueryStringAsJsonRepr();
-        return requestParameter.valueOf(queryStringJsonRepr);
-    }
-
-    @Override
-    public Where getWhere() {
-        return where;
-    }
-
-    /**
-     * Only applies to rendering of objects
-     */
-    @Override
-    public RepresentationService.Intent getIntent() {
-        return intent;
+    static <Q> Q arg(final JsonRepresentation queryStringAsJsonRepr, final 
RequestParameter<Q> requestParameter) {
+        return requestParameter.valueOf(queryStringAsJsonRepr);
     }
 
     public SerializationStrategy getSerializationStrategy() {
-        return SerializationStrategy.determineFrom(getAcceptableMediaTypes());
+        return SerializationStrategy.determineFrom(acceptableMediaTypes());
     }
 
-    // -- canEagerlyRender
-    private Set<Bookmark> rendered = _Sets.newHashSet();
-
     @Override
     public boolean canEagerlyRender(final ManagedObject objectAdapter) {
         return ManagedObjects.bookmark(objectAdapter)
@@ -242,12 +230,35 @@ public String applicationUrlFor(final @NonNull String 
url) {
     }
 
     @Override
-    public List<MediaType> getAcceptableMediaTypes() {
+    public List<MediaType> acceptableMediaTypes() {
         return httpHeaders.getAcceptableMediaTypes();
     }
 
-    @Getter(onMethod = @__(@Override))
-    @Setter //(onMethod = @__(@Override))
-    private ObjectAdapterLinkTo objectAdapterLinkTo;
+    // -- UTIL
+
+    public ManagedObject lookupServiceAdapterElseFail(
+            final @Nullable String serviceIdOrAlias) {
+
+        final ManagedObject serviceAdapter = getSpecificationLoader()
+                .lookupLogicalType(serviceIdOrAlias)
+                .map(LogicalType::logicalName)
+                .map(this::lookupServiceAdapterById)
+                .orElse(null);
+
+        if(serviceAdapter==null) {
+            throw 
RestfulObjectsApplicationException.createWithMessage(HttpStatusCode.NOT_FOUND,
+                    "Could not locate service '%s'", serviceIdOrAlias);
+        }
+        return serviceAdapter;
+    }
+
+    // -- JUNIT
+
+    static ResourceContext forTesting(String queryString, HttpServletRequest 
servletRequest) {
+        return new ResourceContext(ResourceDescriptor.empty(), null, null, 
null, null, null,
+            RequestParams.ofQueryString(UrlUtils.urlDecodeUtf8(queryString)),
+            servletRequest, null, null,
+            MetaModelContext.instanceElseFail(), null, (Map<String, 
String[]>)null);
+    }
 
 }
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 884d30910e9..1f3d699868f 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
@@ -67,6 +67,7 @@
 import 
org.apache.causeway.viewer.restfulobjects.rendering.service.RepresentationService;
 import org.apache.causeway.viewer.restfulobjects.rendering.util.RequestParams;
 import 
org.apache.causeway.viewer.restfulobjects.viewer.context.ResourceContext;
+import 
org.apache.causeway.viewer.restfulobjects.viewer.resources.ResourceDescriptor.ResourceLink;
 
 import lombok.extern.slf4j.Slf4j;
 
@@ -82,9 +83,7 @@ public DomainObjectResourceServerside() {
         log.debug("<init>");
     }
 
-    // //////////////////////////////////////////////////////////
-    // persist
-    // //////////////////////////////////////////////////////////
+    // PERSIST
 
     @Override
     @POST
@@ -98,8 +97,9 @@ public Response persist(
             @PathParam("domainType") final String domainType,
             final InputStream object) {
 
-        var resourceContext = createResourceContext(
-                RepresentationType.DOMAIN_OBJECT, Where.OBJECT_FORMS, 
RepresentationService.Intent.JUST_CREATED);
+        var resourceContext = createResourceContext(new ResourceDescriptor(
+                RepresentationType.DOMAIN_OBJECT, Where.OBJECT_FORMS,
+                RepresentationService.Intent.JUST_CREATED, 
ResourceLink.OBJECT));
 
         final JsonRepresentation objectRepr = 
RequestParams.ofRequestBody(object).asMap();
         if (!objectRepr.isMap()) {
@@ -149,9 +149,7 @@ public Response persist(
                 domainResourceHelper.objectRepresentation());
     }
 
-    // //////////////////////////////////////////////////////////
-    // domain object
-    // //////////////////////////////////////////////////////////
+    // DOMAIN OBJECT
 
     @Override
     @GET
@@ -165,8 +163,9 @@ public Response object(
             @PathParam("domainType") final String domainType,
             @PathParam("instanceId") final String instanceId) {
 
-        var resourceContext = createResourceContext(
-                RepresentationType.DOMAIN_OBJECT, Where.OBJECT_FORMS, 
RepresentationService.Intent.ALREADY_PERSISTENT);
+        var resourceContext = createResourceContext(new ResourceDescriptor(
+                RepresentationType.DOMAIN_OBJECT, Where.OBJECT_FORMS,
+                RepresentationService.Intent.ALREADY_PERSISTENT, 
ResourceLink.OBJECT));
 
         var objectAdapter = getObjectAdapterElseThrowNotFound(domainType, 
instanceId,
                 roEx->_EndpointLogging.error(log, "GET /objects/{}/{}", 
domainType, instanceId, roEx));
@@ -189,8 +188,9 @@ public Response object(
             @PathParam("instanceId") final String instanceId,
             final InputStream object) {
 
-        var resourceContext = createResourceContext(
-                RepresentationType.DOMAIN_OBJECT, Where.OBJECT_FORMS, 
RepresentationService.Intent.ALREADY_PERSISTENT);
+        var resourceContext = createResourceContext(new ResourceDescriptor(
+                RepresentationType.DOMAIN_OBJECT, Where.OBJECT_FORMS,
+                RepresentationService.Intent.ALREADY_PERSISTENT, 
ResourceLink.OBJECT));
 
         final JsonRepresentation argRepr = 
RequestParams.ofRequestBody(object).asMap();
         if (!argRepr.isMap()) {
@@ -249,9 +249,7 @@ public Response postMethodNotAllowed(
                         HttpStatusCode.METHOD_NOT_ALLOWED, "Posting to object 
resource is not allowed."));
     }
 
-    // //////////////////////////////////////////////////////////
-    // domain object layout
-    // //////////////////////////////////////////////////////////
+    // DOMAIN OBJECT LAYOUT
 
     @Override
     @GET
@@ -389,9 +387,7 @@ public void visit(final CollectionLayoutData 
collectionLayoutData) {
         });
     }
 
-    // //////////////////////////////////////////////////////////
-    // domain object property
-    // //////////////////////////////////////////////////////////
+    // DOMAIN OBJECT PROPERTY
 
     @Override
     @GET
@@ -406,8 +402,9 @@ public Response propertyDetails(
             @PathParam("instanceId") final String instanceId,
             @PathParam("propertyId") final String propertyId) {
 
-        var resourceContext = createResourceContext(
-                RepresentationType.OBJECT_PROPERTY, Where.OBJECT_FORMS, 
RepresentationService.Intent.NOT_APPLICABLE);
+        var resourceContext = createResourceContext(new ResourceDescriptor(
+                RepresentationType.OBJECT_PROPERTY, Where.OBJECT_FORMS,
+                RepresentationService.Intent.NOT_APPLICABLE, 
ResourceLink.OBJECT));
 
         var objectAdapter = getObjectAdapterElseThrowNotFound(domainType, 
instanceId,
                 roEx->_EndpointLogging.error(log, "GET 
/objects/{}/{}/properties/{}", domainType, instanceId, propertyId, roEx));
@@ -432,23 +429,25 @@ public Response modifyProperty(
             @PathParam("propertyId") final String propertyId,
             final InputStream body) {
 
-        var resourceContext = createResourceContext(
-                ResourceDescriptor.generic(Where.OBJECT_FORMS, 
RepresentationService.Intent.NOT_APPLICABLE));
+        var resourceContext = createResourceContext(new ResourceDescriptor(
+                RepresentationType.GENERIC, Where.OBJECT_FORMS,
+                RepresentationService.Intent.NOT_APPLICABLE, 
ResourceLink.OBJECT));
 
         var objectAdapter = getObjectAdapterElseThrowNotFound(domainType, 
instanceId,
                 roEx->_EndpointLogging.error(log, "PUT 
/objects/{}/{}/properties/{}", domainType, instanceId, propertyId, roEx));
 
-        PropertyInteraction.start(objectAdapter, propertyId, 
resourceContext.getWhere())
-        .checkVisibility()
-        .checkUsability(AccessIntent.MUTATE)
-        .modifyProperty(property->{
-            var proposedNewValue = new JsonParserHelper(resourceContext, 
property.getElementType())
-                    
.parseAsMapWithSingleValue(RequestParams.ofRequestBody(body));
+        PropertyInteraction
+            .start(objectAdapter, propertyId, resourceContext.where())
+            .checkVisibility()
+            .checkUsability(AccessIntent.MUTATE)
+            .modifyProperty(property->{
+                var proposedNewValue = new JsonParserHelper(resourceContext, 
property.getElementType())
+                        
.parseAsMapWithSingleValue(RequestParams.ofRequestBody(body));
 
-            return proposedNewValue;
-        })
-        .validateElseThrow(veto->
-            _EndpointLogging.error(log, "PUT /objects/{}/{}/properties/{}", 
domainType, instanceId, propertyId, InteractionFailureHandler.onFailure(veto)));
+                return proposedNewValue;
+            })
+            .validateElseThrow(veto->
+                _EndpointLogging.error(log, "PUT 
/objects/{}/{}/properties/{}", domainType, instanceId, propertyId, 
InteractionFailureHandler.onFailure(veto)));
 
         return _EndpointLogging.response(log, "PUT 
/objects/{}/{}/properties/{}", domainType, instanceId, propertyId,
                 _DomainResourceHelper
@@ -469,13 +468,14 @@ public Response clearProperty(
             @PathParam("instanceId") final String instanceId,
             @PathParam("propertyId") final String propertyId) {
 
-        var resourceContext = createResourceContext(
-                ResourceDescriptor.generic(Where.OBJECT_FORMS, 
RepresentationService.Intent.NOT_APPLICABLE));
+        var resourceContext = createResourceContext(new ResourceDescriptor(
+                RepresentationType.GENERIC, Where.OBJECT_FORMS,
+                RepresentationService.Intent.NOT_APPLICABLE, 
ResourceLink.OBJECT));
 
         var objectAdapter = getObjectAdapterElseThrowNotFound(domainType, 
instanceId,
                 roEx->_EndpointLogging.error(log, "DELETE 
/objects/{}/{}/properties/{}", domainType, instanceId, propertyId, roEx));
 
-        PropertyInteraction.start(objectAdapter, propertyId, 
resourceContext.getWhere())
+        PropertyInteraction.start(objectAdapter, propertyId, 
resourceContext.where())
         .checkVisibility()
         .checkUsability(AccessIntent.MUTATE)
         .modifyProperty(property->null)
@@ -503,9 +503,7 @@ public Response postPropertyNotAllowed(
                         "Posting to a property resource is not allowed."));
     }
 
-    // //////////////////////////////////////////////////////////
-    // domain object collection
-    // //////////////////////////////////////////////////////////
+    // DOMAIN OBJECT COLLECTION
 
     @Override
     @GET
@@ -520,8 +518,9 @@ public Response accessCollection(
             @PathParam("instanceId") final String instanceId,
             @PathParam("collectionId") final String collectionId) {
 
-        var resourceContext = createResourceContext(
-                RepresentationType.OBJECT_COLLECTION, Where.PARENTED_TABLES, 
RepresentationService.Intent.NOT_APPLICABLE);
+        var resourceContext = createResourceContext(new ResourceDescriptor(
+                RepresentationType.OBJECT_COLLECTION, Where.PARENTED_TABLES,
+                RepresentationService.Intent.NOT_APPLICABLE, 
ResourceLink.OBJECT));
 
         var objectAdapter = getObjectAdapterElseThrowNotFound(domainType, 
instanceId,
                 roEx->_EndpointLogging.error(log, "GET 
/objects/{}/{}/collections/{}", domainType, instanceId, collectionId, roEx));
@@ -532,72 +531,7 @@ public Response accessCollection(
                 domainResourceHelper.collectionDetails(collectionId, 
ManagedMember.RepresentationMode.READ));
     }
 
-    //XXX[CAUSEWAY-3084] - removal of (direct) collection modification - 
business logic should handle that via actions instead
-//    @Override
-//    @PUT
-//    @Path("/{domainType}/{instanceId}/collections/{collectionId}")
-//    @Consumes({ MediaType.WILDCARD })
-//    @Produces({
-//        MediaType.APPLICATION_JSON, 
RestfulMediaType.APPLICATION_JSON_OBJECT_COLLECTION, 
RestfulMediaType.APPLICATION_JSON_ERROR,
-//        MediaType.APPLICATION_XML, 
RestfulMediaType.APPLICATION_XML_OBJECT_COLLECTION, 
RestfulMediaType.APPLICATION_XML_ERROR
-//    })
-//    public Response addToSet(
-//            @PathParam("domainType") final String domainType,
-//            @PathParam("instanceId") final String instanceId,
-//            @PathParam("collectionId") final String collectionId,
-//            final InputStream body) {
-//
-//        throw _EndpointLogging.error(log, "POST 
/objects/{}/{}/collections/{}", domainType, instanceId, collectionId,
-//                RestfulObjectsApplicationException
-//                .createWithMessage(
-//                        HttpStatusCode.METHOD_NOT_ALLOWED,
-//                        "The framework no longer supports mutable 
collections."));
-//    }
-//
-//    @Override
-//    @POST
-//    @Path("/{domainType}/{instanceId}/collections/{collectionId}")
-//    @Consumes({ MediaType.WILDCARD })
-//    @Produces({
-//        MediaType.APPLICATION_JSON, 
RestfulMediaType.APPLICATION_JSON_OBJECT_COLLECTION, 
RestfulMediaType.APPLICATION_JSON_ERROR,
-//        MediaType.APPLICATION_XML, 
RestfulMediaType.APPLICATION_XML_OBJECT_COLLECTION, 
RestfulMediaType.APPLICATION_XML_ERROR
-//    })
-//    public Response addToList(
-//            @PathParam("domainType") final String domainType,
-//            @PathParam("instanceId") final String instanceId,
-//            @PathParam("collectionId") final String collectionId,
-//            final InputStream body) {
-//
-//        throw _EndpointLogging.error(log, "POST 
/objects/{}/{}/collections/{}", domainType, instanceId, collectionId,
-//                RestfulObjectsApplicationException
-//                .createWithMessage(
-//                        HttpStatusCode.METHOD_NOT_ALLOWED,
-//                        "The framework no longer supports mutable 
collections."));
-//    }
-//
-//    @Override
-//    @DELETE
-//    @Path("/{domainType}/{instanceId}/collections/{collectionId}")
-//    @Consumes({ MediaType.WILDCARD })
-//    @Produces({
-//        MediaType.APPLICATION_JSON, 
RestfulMediaType.APPLICATION_JSON_OBJECT_COLLECTION, 
RestfulMediaType.APPLICATION_JSON_ERROR,
-//        MediaType.APPLICATION_XML, 
RestfulMediaType.APPLICATION_XML_OBJECT_COLLECTION, 
RestfulMediaType.APPLICATION_XML_ERROR
-//    })
-//    public Response removeFromCollection(
-//            @PathParam("domainType") final String domainType,
-//            @PathParam("instanceId") final String instanceId,
-//            @PathParam("collectionId") final String collectionId) {
-//
-//        throw _EndpointLogging.error(log, "DELETE 
/objects/{}/{}/collections/{}", domainType, instanceId, collectionId,
-//                RestfulObjectsApplicationException
-//                .createWithMessage(
-//                        HttpStatusCode.METHOD_NOT_ALLOWED,
-//                        "The framework no longer supports mutable 
collections."));
-//    }
-
-    // //////////////////////////////////////////////////////////
-    // domain object action
-    // //////////////////////////////////////////////////////////
+    // DOMAIN OBJECT ACTION
 
     @Override
     @GET
@@ -612,8 +546,9 @@ public Response actionPrompt(
             @PathParam("instanceId") final String instanceId,
             @PathParam("actionId") final String actionId) {
 
-        var resourceContext = createResourceContext(
-                RepresentationType.OBJECT_ACTION, Where.OBJECT_FORMS, 
RepresentationService.Intent.NOT_APPLICABLE);
+        var resourceContext = createResourceContext(new ResourceDescriptor(
+                RepresentationType.OBJECT_ACTION, Where.OBJECT_FORMS,
+                RepresentationService.Intent.NOT_APPLICABLE, 
ResourceLink.OBJECT));
 
         var objectAdapter = getObjectAdapterElseThrowNotFound(domainType, 
instanceId,
                 roEx->_EndpointLogging.error(log, "GET 
/objects/{}/{}/actions/{}", domainType, instanceId, actionId, roEx));
@@ -668,9 +603,7 @@ public Response putActionPromptNotAllowed(
                         "Putting to an action prompt resource is not 
allowed."));
     }
 
-    // //////////////////////////////////////////////////////////
-    // domain object action invoke
-    // //////////////////////////////////////////////////////////
+    // DOMAIN OBJECT ACTION INVOKE
 
     @Override
     @GET
@@ -691,10 +624,11 @@ public Response invokeActionQueryOnly(
                     ? xCausewayUrlEncodedQueryString
                     : httpServletRequest.getQueryString());
         var resourceContext = createResourceContext(
-                new ResourceDescriptor(RepresentationType.ACTION_RESULT, 
Where.STANDALONE_TABLES, RepresentationService.Intent.NOT_APPLICABLE),
+                new ResourceDescriptor(RepresentationType.ACTION_RESULT, 
Where.STANDALONE_TABLES,
+                    RepresentationService.Intent.NOT_APPLICABLE, 
ResourceLink.OBJECT),
                 RequestParams.ofQueryString(urlUnencodedQueryString));
 
-        final JsonRepresentation arguments = 
resourceContext.getQueryStringAsJsonRepr();
+        final JsonRepresentation arguments = 
resourceContext.queryStringAsJsonRepr();
 
         var objectAdapter = getObjectAdapterElseThrowNotFound(domainType, 
instanceId,
                 roEx->_EndpointLogging.error(log, "GET 
/objects/{}/{}/actions/{}/invoke", domainType, instanceId, actionId, roEx));
@@ -719,10 +653,11 @@ public Response invokeActionIdempotent(
             final InputStream body) {
 
         var resourceContext = createResourceContext(
-                new ResourceDescriptor(RepresentationType.ACTION_RESULT, 
Where.STANDALONE_TABLES, RepresentationService.Intent.NOT_APPLICABLE),
+                new ResourceDescriptor(RepresentationType.ACTION_RESULT, 
Where.STANDALONE_TABLES,
+                    RepresentationService.Intent.NOT_APPLICABLE, 
ResourceLink.OBJECT),
                 body);
 
-        final JsonRepresentation arguments = 
resourceContext.getQueryStringAsJsonRepr();
+        final JsonRepresentation arguments = 
resourceContext.queryStringAsJsonRepr();
 
         var objectAdapter = getObjectAdapterElseThrowNotFound(domainType, 
instanceId,
                 roEx->_EndpointLogging.error(log, "PUT 
/objects/{}/{}/actions/{}/invoke", domainType, instanceId, actionId, roEx));
@@ -747,10 +682,11 @@ public Response invokeAction(
             final InputStream body) {
 
         var resourceContext = createResourceContext(
-                new ResourceDescriptor(RepresentationType.ACTION_RESULT, 
Where.STANDALONE_TABLES, RepresentationService.Intent.NOT_APPLICABLE),
+                new ResourceDescriptor(RepresentationType.ACTION_RESULT, 
Where.STANDALONE_TABLES,
+                    RepresentationService.Intent.NOT_APPLICABLE, 
ResourceLink.OBJECT),
                 body);
 
-        final JsonRepresentation arguments = 
resourceContext.getQueryStringAsJsonRepr();
+        final JsonRepresentation arguments = 
resourceContext.queryStringAsJsonRepr();
 
         var objectAdapter = getObjectAdapterElseThrowNotFound(domainType, 
instanceId,
                 roEx->_EndpointLogging.error(log, "POST 
/objects/{}/{}/actions/{}/invoke", domainType, instanceId, actionId, roEx));
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 7f7c4504fa4..dee4c5bdd71 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
@@ -19,8 +19,6 @@
 package org.apache.causeway.viewer.restfulobjects.viewer.resources;
 
 import java.io.InputStream;
-import java.util.stream.Stream;
-
 import jakarta.ws.rs.Consumes;
 import jakarta.ws.rs.DELETE;
 import jakarta.ws.rs.GET;
@@ -37,7 +35,6 @@
 
 import org.apache.causeway.applib.annotation.Where;
 import org.apache.causeway.commons.io.UrlUtils;
-import org.apache.causeway.core.metamodel.object.ManagedObject;
 import org.apache.causeway.viewer.restfulobjects.applib.JsonRepresentation;
 import org.apache.causeway.viewer.restfulobjects.applib.RepresentationType;
 import org.apache.causeway.viewer.restfulobjects.applib.RestfulMediaType;
@@ -50,6 +47,7 @@
 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 
org.apache.causeway.viewer.restfulobjects.viewer.resources.ResourceDescriptor.ResourceLink;
 
 import lombok.extern.slf4j.Slf4j;
 
@@ -77,13 +75,12 @@ public Response services() {
         var resourceContext = createResourceContext(
                 RepresentationType.LIST, Where.STANDALONE_TABLES, 
RepresentationService.Intent.NOT_APPLICABLE);
 
-        final Stream<ManagedObject> serviceAdapters = 
resourceContext.streamServiceAdapters();
-
-        final DomainServicesListReprRenderer renderer = new 
DomainServicesListReprRenderer(
+        var renderer = new DomainServicesListReprRenderer(
                 resourceContext, null, JsonRepresentation.newMap());
-        renderer.usingLinkToBuilder(new DomainServiceLinkTo())
-        .includesSelf()
-        .with(serviceAdapters);
+        renderer
+            .usingLinkToBuilder(new DomainServiceLinkTo())
+            .includesSelf()
+            .with(resourceContext.streamServiceAdapters());
 
         return _EndpointLogging.response(log, "GET /services/",
                 Responses.ofOk(renderer, Caching.ONE_DAY).build());
@@ -116,9 +113,7 @@ public Response postServicesNotAllowed() {
                         "Posting to the services resource is not allowed."));
     }
 
-    // //////////////////////////////////////////////////////////
-    // domain service
-    // //////////////////////////////////////////////////////////
+    // DOMAIN SERVICE
 
     @Override
     @GET
@@ -130,15 +125,17 @@ public Response postServicesNotAllowed() {
     public Response service(
             @PathParam("serviceId") final String serviceId) {
 
-        var resourceContext = createResourceContext(
-                RepresentationType.DOMAIN_OBJECT, Where.OBJECT_FORMS, 
RepresentationService.Intent.ALREADY_PERSISTENT);
+        var resourceContext = createResourceContext(new ResourceDescriptor(
+                RepresentationType.DOMAIN_OBJECT, Where.OBJECT_FORMS,
+                RepresentationService.Intent.ALREADY_PERSISTENT, 
ResourceLink.SERVICE));
 
-        var serviceAdapter = 
_DomainResourceHelper.getServiceAdapter(resourceContext, serviceId);
+        var serviceAdapter = 
resourceContext.lookupServiceAdapterElseFail(serviceId);
 
         var renderer = new DomainObjectReprRenderer(resourceContext, null, 
JsonRepresentation.newMap());
-        renderer.usingLinkToBuilder(new DomainServiceLinkTo())
-        .with(serviceAdapter)
-        .includesSelf();
+        renderer
+            .usingLinkToBuilder(new DomainServiceLinkTo())
+            .with(serviceAdapter)
+            .includesSelf();
 
         return _EndpointLogging.response(log, "GET /services/{}", serviceId,
                 Responses.ofOk(renderer, Caching.ONE_DAY).build());
@@ -180,9 +177,7 @@ public Response postServiceNotAllowed(
                         "Posting to a service resource is not allowed."));
     }
 
-    // //////////////////////////////////////////////////////////
-    // domain service action
-    // //////////////////////////////////////////////////////////
+    // DOMAIN SERVICE ACTION
 
     @Override
     @GET
@@ -195,8 +190,9 @@ public Response actionPrompt(
             @PathParam("serviceId") final String serviceId,
             @PathParam("actionId") final String actionId) {
 
-        var resourceContext = createResourceContext(
-                RepresentationType.OBJECT_ACTION, Where.OBJECT_FORMS, 
RepresentationService.Intent.ALREADY_PERSISTENT);
+        var resourceContext = createResourceContext(new ResourceDescriptor(
+                RepresentationType.OBJECT_ACTION, Where.OBJECT_FORMS,
+                RepresentationService.Intent.ALREADY_PERSISTENT, 
ResourceLink.SERVICE));
 
         var domainResourceHelper = 
_DomainResourceHelper.ofServiceResource(resourceContext, serviceId);
 
@@ -243,9 +239,7 @@ public Response postActionPromptNotAllowed(
                         "Posting to an action prompt resource is not 
allowed."));
     }
 
-    // //////////////////////////////////////////////////////////
-    // domain service action invoke
-    // //////////////////////////////////////////////////////////
+    // DOMAIN SERVICE ACTION INVOKE
 
     @Override
     @GET
@@ -264,10 +258,11 @@ public Response invokeActionQueryOnly(
                     ? xCausewayUrlEncodedQueryString
                     : httpServletRequest.getQueryString());
         var resourceContext = createResourceContext(
-                new ResourceDescriptor(RepresentationType.ACTION_RESULT, 
Where.STANDALONE_TABLES, RepresentationService.Intent.NOT_APPLICABLE),
+                new ResourceDescriptor(RepresentationType.ACTION_RESULT, 
Where.STANDALONE_TABLES,
+                    RepresentationService.Intent.NOT_APPLICABLE, 
ResourceLink.SERVICE),
                 RequestParams.ofQueryString(urlUnencodedQueryString));
 
-        final JsonRepresentation arguments = 
resourceContext.getQueryStringAsJsonRepr();
+        final JsonRepresentation arguments = 
resourceContext.queryStringAsJsonRepr();
 
         var domainResourceHelper = 
_DomainResourceHelper.ofServiceResource(resourceContext, serviceId);
 
@@ -289,10 +284,11 @@ public Response invokeActionIdempotent(
             final InputStream body) {
 
         var resourceContext = createResourceContext(
-                new ResourceDescriptor(RepresentationType.ACTION_RESULT, 
Where.STANDALONE_TABLES, RepresentationService.Intent.NOT_APPLICABLE),
+                new ResourceDescriptor(RepresentationType.ACTION_RESULT, 
Where.STANDALONE_TABLES,
+                    RepresentationService.Intent.NOT_APPLICABLE, 
ResourceLink.SERVICE),
                 body);
 
-        final JsonRepresentation arguments = 
resourceContext.getQueryStringAsJsonRepr();
+        final JsonRepresentation arguments = 
resourceContext.queryStringAsJsonRepr();
 
         var domainResourceHelper = 
_DomainResourceHelper.ofServiceResource(resourceContext, serviceId);
 
@@ -314,10 +310,11 @@ public Response invokeAction(
             final InputStream body) {
 
         var resourceContext = createResourceContext(
-                new ResourceDescriptor(RepresentationType.ACTION_RESULT, 
Where.STANDALONE_TABLES, RepresentationService.Intent.NOT_APPLICABLE),
+                new ResourceDescriptor(RepresentationType.ACTION_RESULT, 
Where.STANDALONE_TABLES,
+                    RepresentationService.Intent.NOT_APPLICABLE, 
ResourceLink.SERVICE),
                 body);
 
-        final JsonRepresentation arguments = 
resourceContext.getQueryStringAsJsonRepr();
+        final JsonRepresentation arguments = 
resourceContext.queryStringAsJsonRepr();
 
         var domainResourceHelper = 
_DomainResourceHelper.ofServiceResource(resourceContext, serviceId);
 
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 14f95a6d7b8..bf11a3c82df 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
@@ -62,6 +62,7 @@
 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.RequestParams;
+import 
org.apache.causeway.viewer.restfulobjects.viewer.resources.ResourceDescriptor.ResourceLink;
 import org.apache.causeway.viewer.restfulobjects.viewer.util.UrlParserUtils;
 
 import lombok.extern.slf4j.Slf4j;
@@ -311,8 +312,9 @@ public Response domainTypeIsSubtypeOf(
             @QueryParam("args") final String argsUrlEncoded // formal style
             ) {
 
-        var resourceContext = createResourceContext(
-                ResourceDescriptor.generic(Where.ANYWHERE, 
RepresentationService.Intent.NOT_APPLICABLE));
+        var resourceContext = createResourceContext(new ResourceDescriptor(
+                RepresentationType.GENERIC, Where.ANYWHERE,
+                RepresentationService.Intent.NOT_APPLICABLE, 
ResourceLink.NONE));
 
         final String supertype = domainTypeFor(superTypeStr, argsUrlEncoded, 
"supertype",
                 roEx->_EndpointLogging.error(log, "GET 
/domain-types/{}/type-actions/isSubtypeOf/invoke", domainType, roEx));
@@ -352,8 +354,9 @@ public Response domainTypeIsSupertypeOf(
             @QueryParam("args") final String argsUrlEncoded // formal style
             ) {
 
-        var resourceContext = createResourceContext(
-                ResourceDescriptor.generic(Where.ANYWHERE, 
RepresentationService.Intent.NOT_APPLICABLE));
+        var resourceContext = createResourceContext(new ResourceDescriptor(
+            RepresentationType.GENERIC, Where.ANYWHERE,
+            RepresentationService.Intent.NOT_APPLICABLE, ResourceLink.NONE));
 
         final String subtype = domainTypeFor(subTypeStr, argsUrlEncoded, 
"subtype",
                 roEx->_EndpointLogging.error(log, "GET 
/domain-types/{}/type-actions/isSupertypeOf/invoke", domainType, roEx));
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 15f95c5f8ab..ef1af167bfb 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
@@ -130,7 +130,7 @@ ManagedObject objectAdapterFor(final JsonRepresentation 
argRepr) {
             throw new IllegalArgumentException(reason);
         }
 
-        var objectAdapter = 
resourceContext.getObjectAdapterForOidFromHref(oidFromHref)
+        var objectAdapter = 
resourceContext.objectAdapterForOidFromHref(oidFromHref)
                 .orElseThrow(()->{
                     var reason = "'href' does not reference a known entity";
                     argRepr.mapPutString("invalidReason", reason);
diff --git 
a/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/ObjectAdapterAccessHelper.java
 
b/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/ObjectAdapterAccessHelper.java
index bf197a9953d..b46ecf98629 100644
--- 
a/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/ObjectAdapterAccessHelper.java
+++ 
b/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/ObjectAdapterAccessHelper.java
@@ -45,7 +45,7 @@ public static ObjectAdapterAccessHelper of(
             final ManagedObject managedObject) {
         return new ObjectAdapterAccessHelper(
                 managedObject,
-                resourceContext.getWhere());
+                resourceContext.where());
     }
 
     private final ManagedObject managedObject;
diff --git 
a/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/ObjectAdapterUpdateHelper.java
 
b/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/ObjectAdapterUpdateHelper.java
index dca3800a65d..d5d23d2477b 100644
--- 
a/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/ObjectAdapterUpdateHelper.java
+++ 
b/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/ObjectAdapterUpdateHelper.java
@@ -87,12 +87,12 @@ private boolean copyOverProperty(
         final JsonRepresentation propertyRepr = 
propertiesMap.getRepresentation(id);
         final Consent visibility = property.isVisible(
                 objectAdapter,
-                resourceContext.getInteractionInitiatedBy(),
-                resourceContext.getWhere());
+                resourceContext.interactionInitiatedBy(),
+                resourceContext.where());
         final Consent usability = property.isUsable(
                 objectAdapter,
-                resourceContext.getInteractionInitiatedBy(),
-                resourceContext.getWhere()
+                resourceContext.interactionInitiatedBy(),
+                resourceContext.where()
                 );
 
         final boolean invisible = visibility.isVetoed();
@@ -157,7 +157,7 @@ private boolean copyOverProperty(
                 try {
                     property.set(
                             objectAdapter, valueAdapter,
-                            resourceContext.getInteractionInitiatedBy());
+                            resourceContext.interactionInitiatedBy());
                 } catch (final IllegalArgumentException ex) {
                     propertyRepr.mapPutString("invalidReason", 
ex.getMessage());
                     allOk = false;
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 a0e637772f9..9860981e8ca 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
@@ -32,7 +32,6 @@
 import jakarta.ws.rs.ext.Providers;
 
 import org.springframework.beans.factory.annotation.Autowired;
-
 import org.apache.causeway.applib.annotation.Where;
 import org.apache.causeway.applib.services.bookmark.Bookmark;
 import org.apache.causeway.commons.internal.base._Strings;
@@ -52,6 +51,7 @@
 import 
org.apache.causeway.viewer.restfulobjects.rendering.service.RepresentationService;
 import org.apache.causeway.viewer.restfulobjects.rendering.util.RequestParams;
 import 
org.apache.causeway.viewer.restfulobjects.viewer.context.ResourceContext;
+import 
org.apache.causeway.viewer.restfulobjects.viewer.resources.ResourceDescriptor.ResourceLink;
 
 import lombok.Getter;
 import org.jspecify.annotations.NonNull;
@@ -80,11 +80,11 @@ protected ResourceContext createResourceContext(
             final RepresentationType representationType,
             final Where where,
             final RepresentationService.Intent intent) {
-
-        return createResourceContext(new 
ResourceDescriptor(representationType, where, intent));
+        return createResourceContext(new 
ResourceDescriptor(representationType, where, intent, ResourceLink.NONE));
     }
 
-    protected ResourceContext createResourceContext(final ResourceDescriptor 
resourceDescriptor) {
+    protected ResourceContext createResourceContext(
+            final ResourceDescriptor resourceDescriptor) {
         String queryStringIfAny = getUrlDecodedQueryStringIfAny();
         return createResourceContext(resourceDescriptor, 
RequestParams.ofQueryString(queryStringIfAny));
     }
@@ -92,7 +92,6 @@ protected ResourceContext createResourceContext(final 
ResourceDescriptor resourc
     protected ResourceContext createResourceContext(
             final ResourceDescriptor resourceDescriptor,
             final InputStream arguments) {
-
         var urlDecodedQueryString = RequestParams.ofRequestBody(arguments);
         return createResourceContext(resourceDescriptor, 
urlDecodedQueryString);
     }
@@ -107,7 +106,7 @@ protected ResourceContext createResourceContext(
 
         // eg. http://localhost:8080/ctx/restful/
         final String restfulAbsoluteBase = 
getConfiguration().getViewer().getRestfulobjects().getBaseUri()
-                                    
.orElseGet(()->uriInfo.getBaseUri().toString());
+            .orElseGet(()->uriInfo.getBaseUri().toString());
 
         // eg. /ctx/restful/
         var restfulRelativeBase = uriInfo.getBaseUri().getRawPath();
diff --git 
a/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/ResourceDescriptor.java
 
b/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/ResourceDescriptor.java
index cd0b9752a3f..bc6b3ecdcd6 100644
--- 
a/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/ResourceDescriptor.java
+++ 
b/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/ResourceDescriptor.java
@@ -28,15 +28,18 @@
 public record ResourceDescriptor(
         RepresentationType representationType,
         Where where,
-        RepresentationService.Intent intent) {
+        RepresentationService.Intent intent,
+        ResourceLink resourceLink) {
 
-    public static ResourceDescriptor generic(final Where where, final 
RepresentationService.Intent intent) {
-        return new ResourceDescriptor(RepresentationType.GENERIC, where, 
intent);
+    public enum ResourceLink {
+        NONE,
+        OBJECT,
+        SERVICE
     }
 
     public static ResourceDescriptor empty() {
         // in support of testing
-        return new ResourceDescriptor(null, null, null);
+        return new ResourceDescriptor(null, null, null, ResourceLink.NONE);
     }
 
 }
diff --git 
a/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/VersionResourceServerside.java
 
b/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/VersionResourceServerside.java
index c417ea173f3..23c2b241c4d 100644
--- 
a/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/VersionResourceServerside.java
+++ 
b/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/VersionResourceServerside.java
@@ -107,7 +107,7 @@ public Response postVersionNotAllowed() {
     }
 
     private void fakeRuntimeExceptionIfXFail(final ResourceContext 
resourceContext) {
-        final HttpHeaders httpHeaders = resourceContext.getHttpHeaders();
+        final HttpHeaders httpHeaders = resourceContext.httpHeaders();
         final List<String> requestHeader = 
httpHeaders.getRequestHeader("X-Fail");
         if (requestHeader != null && !requestHeader.isEmpty()) {
             throw _EndpointLogging.error(log, "GET /version",
diff --git 
a/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/_DomainResourceHelper.java
 
b/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/_DomainResourceHelper.java
index a8137bdbe81..2dc3a694f38 100644
--- 
a/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/_DomainResourceHelper.java
+++ 
b/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/_DomainResourceHelper.java
@@ -22,14 +22,11 @@
 
 import jakarta.ws.rs.core.Response;
 
-import org.jspecify.annotations.Nullable;
-
 import org.apache.causeway.applib.Identifier;
 import org.apache.causeway.applib.annotation.SemanticsOf;
-import org.apache.causeway.applib.id.LogicalType;
-import org.apache.causeway.applib.services.registry.ServiceRegistry;
 import org.apache.causeway.applib.services.xactn.TransactionService;
 import org.apache.causeway.commons.functional.Railway;
+import org.apache.causeway.commons.internal.assertions._Assert;
 import 
org.apache.causeway.core.metamodel.interactions.managed.ActionInteraction;
 import 
org.apache.causeway.core.metamodel.interactions.managed.ActionInteraction.Result;
 import 
org.apache.causeway.core.metamodel.interactions.managed.ActionInteraction.SemanticConstraint;
@@ -38,60 +35,56 @@
 import 
org.apache.causeway.core.metamodel.interactions.managed.MemberInteraction.AccessIntent;
 import org.apache.causeway.core.metamodel.object.ManagedObject;
 import org.apache.causeway.viewer.restfulobjects.applib.JsonRepresentation;
-import 
org.apache.causeway.viewer.restfulobjects.applib.RestfulResponse.HttpStatusCode;
-import org.apache.causeway.viewer.restfulobjects.rendering.IResourceContext;
-import 
org.apache.causeway.viewer.restfulobjects.rendering.RestfulObjectsApplicationException;
 import 
org.apache.causeway.viewer.restfulobjects.rendering.domainobjects.ActionResultReprRenderer;
 import 
org.apache.causeway.viewer.restfulobjects.rendering.domainobjects.DomainObjectLinkTo;
 import 
org.apache.causeway.viewer.restfulobjects.rendering.domainobjects.DomainServiceLinkTo;
-import 
org.apache.causeway.viewer.restfulobjects.rendering.domainobjects.ObjectAdapterLinkTo;
 import 
org.apache.causeway.viewer.restfulobjects.rendering.domainobjects.ObjectAndActionInvocation;
 import 
org.apache.causeway.viewer.restfulobjects.rendering.service.RepresentationService;
 import 
org.apache.causeway.viewer.restfulobjects.viewer.context.ResourceContext;
 
 import org.jspecify.annotations.NonNull;
 
-class _DomainResourceHelper {
+record _DomainResourceHelper(
+    ResourceContext resourceContext,
+    RepresentationService representationService,
+    TransactionService transactionService,
+    ManagedObject objectAdapter) {
 
-    private final IResourceContext resourceContext;
-    private final RepresentationService representationService;
-    private final TransactionService transactionService;
+    // -- FACTORIES
 
     public static _DomainResourceHelper ofObjectResource(
-            final IResourceContext resourceContext,
+            final ResourceContext resourceContext,
             final ManagedObject objectAdapter) {
-        return new _DomainResourceHelper(resourceContext, objectAdapter, new 
DomainObjectLinkTo());
+        _Assert.assertTrue(resourceContext.objectAdapterLinkTo() instanceof 
DomainObjectLinkTo);
+        return new _DomainResourceHelper(resourceContext, objectAdapter);
     }
 
     public static _DomainResourceHelper ofServiceResource(
-            final IResourceContext resourceContext,
+            final ResourceContext resourceContext,
             final String serviceIdOrAlias) {
+        _Assert.assertTrue(resourceContext.objectAdapterLinkTo() instanceof 
DomainServiceLinkTo);
         return new _DomainResourceHelper(resourceContext,
-                getServiceAdapter(resourceContext, serviceIdOrAlias), new 
DomainServiceLinkTo());
+            resourceContext.lookupServiceAdapterElseFail(serviceIdOrAlias));
     }
 
-    private _DomainResourceHelper(
-            final IResourceContext resourceContext,
-            final ManagedObject objectAdapter,
-            final ObjectAdapterLinkTo adapterLinkTo) {
-
-        
((ResourceContext)resourceContext).setObjectAdapterLinkTo(adapterLinkTo);
+    // -- NON CANONICAL CONSTRUCTOR
 
-        this.resourceContext = resourceContext;
-        this.objectAdapter = objectAdapter;
+    private _DomainResourceHelper(
+            final ResourceContext resourceContext,
+            final ManagedObject objectAdapter) {
 
-        adapterLinkTo.usingUrlBase(this.resourceContext)
-        .with(this.objectAdapter);
+        this(
+            resourceContext,
+            resourceContext.lookupServiceElseFail(RepresentationService.class),
+            resourceContext.lookupServiceElseFail(TransactionService.class),
+            objectAdapter);
 
-        representationService = lookupService(RepresentationService.class);
-        transactionService = lookupService(TransactionService.class);
+        resourceContext.objectAdapterLinkTo()
+            .usingUrlBase(this.resourceContext)
+            .with(this.objectAdapter);
     }
 
-    private final ManagedObject objectAdapter;
-
-    // //////////////////////////////////////
-    // Helpers (resource delegate here)
-    // //////////////////////////////////////
+    // resource delegate here ...
 
     /**
      * Simply delegates to the {@link 
org.apache.causeway.viewer.restfulobjects.rendering.service.RepresentationService}
 to
@@ -198,6 +191,8 @@ public Response invokeAction(final String actionId, final 
JsonRepresentation arg
                 arguments, ActionResultReprRenderer.SelfLink.EXCLUDED);
     }
 
+    // -- HELPER
+
     private Response invokeAction(
             final @NonNull String actionId,
             final @NonNull AccessIntent intent,
@@ -205,7 +200,7 @@ private Response invokeAction(
             final @NonNull JsonRepresentation arguments,
             final ActionResultReprRenderer.@NonNull SelfLink selfLink) {
 
-        var where = resourceContext.getWhere();
+        var where = resourceContext.where();
 
         // lombok issue, needs explicit cast here
         var actionInteraction = ActionInteraction.start(objectAdapter, 
actionId, where)
@@ -302,34 +297,4 @@ private Response invokeAction(
         return representationService.actionResult(resourceContext, 
objectAndActionInvocation);
     }
 
-    // -- DEPENDENCIES (FROM CONTEXT)
-
-    //TODO pretty low level stuff; maybe move the logic to metamodel module
-    static ManagedObject getServiceAdapter(
-            final IResourceContext resourceContext,
-            final @Nullable String serviceIdOrAlias) {
-
-        var mmc = resourceContext.getMetaModelContext();
-
-        final ManagedObject serviceAdapter = mmc.getSpecificationLoader()
-                .lookupLogicalType(serviceIdOrAlias)
-                .map(LogicalType::logicalName)
-                .map(mmc::lookupServiceAdapterById)
-                .orElse(null);
-
-        if(serviceAdapter==null) {
-            throw 
RestfulObjectsApplicationException.createWithMessage(HttpStatusCode.NOT_FOUND,
-                    "Could not locate service '%s'", serviceIdOrAlias);
-        }
-        return serviceAdapter;
-    }
-
-    private <T> T lookupService(final Class<T> serviceType) {
-        return getServiceRegistry().lookupServiceElseFail(serviceType);
-    }
-
-    private ServiceRegistry getServiceRegistry() {
-        return resourceContext.getMetaModelContext().getServiceRegistry();
-    }
-
 }
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 4e992fba558..d464b636583 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
@@ -32,17 +32,13 @@
 
 import org.apache.causeway.applib.services.iactnlayer.InteractionLayerTracker;
 import org.apache.causeway.applib.services.iactnlayer.InteractionService;
-import org.apache.causeway.commons.io.UrlUtils;
 import org.apache.causeway.core.metamodel._testing.MetaModelContext_forTesting;
 import org.apache.causeway.core.metamodel.context.MetaModelContext;
 import org.apache.causeway.core.metamodel.specloader.SpecificationLoader;
 import 
org.apache.causeway.core.security.authentication.manager.AuthenticationManager;
 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,34 +92,16 @@ void setUp() throws Exception {
     @Test
     void whenArgExists() throws Exception {
         final String queryString = 
UrlEncodingUtils.urlEncode(JsonRepresentation.newMap("x-ro-page", 
"123").asJsonNode());
-
-        resourceContext = new ResourceContext(ResourceDescriptor.empty(), 
null, null, null, null, null,
-                
RequestParams.ofQueryString(UrlUtils.urlDecodeUtf8(queryString)),
-                mockHttpServletRequest, null, null,
-                metaModelContext, null, null) {
-            @Override
-            void init(final RepresentationType representationType) {
-                //
-            }
-        };
-        final Integer arg = resourceContext.getArg(RequestParameter.PAGE);
+        resourceContext = ResourceContext.forTesting(queryString, 
mockHttpServletRequest);
+        final Integer arg = 
ResourceContext.arg(resourceContext.queryStringAsJsonRepr(), 
RequestParameter.PAGE);
         assertThat(arg, equalTo(123));
     }
 
     @Test
     void whenArgDoesNotExist() throws Exception {
         final String queryString = 
UrlEncodingUtils.urlEncode(JsonRepresentation.newMap("xxx", 
"123").asJsonNode());
-
-        resourceContext = new ResourceContext(ResourceDescriptor.empty(), 
null, null, null, null, null,
-                
RequestParams.ofQueryString(UrlUtils.urlDecodeUtf8(queryString)),
-                mockHttpServletRequest, null, null,
-                metaModelContext, null, null) {
-            @Override
-            void init(final RepresentationType representationType) {
-                //
-            }
-        };
-        final Integer arg = resourceContext.getArg(RequestParameter.PAGE);
+        resourceContext = ResourceContext.forTesting(queryString, 
mockHttpServletRequest);
+        final Integer arg = 
ResourceContext.arg(resourceContext.queryStringAsJsonRepr(), 
RequestParameter.PAGE);
         assertThat(arg, equalTo(RequestParameter.PAGE.getDefault()));
     }
 

Reply via email to