You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by da...@apache.org on 2014/10/09 13:26:44 UTC

[2/6] git commit: ISIS-917: first cut at a pluggable representations service for RO.

ISIS-917: first cut at a pluggable representations service for RO.

The app runs fine, but tests are failing.


Project: http://git-wip-us.apache.org/repos/asf/isis/repo
Commit: http://git-wip-us.apache.org/repos/asf/isis/commit/bfece661
Tree: http://git-wip-us.apache.org/repos/asf/isis/tree/bfece661
Diff: http://git-wip-us.apache.org/repos/asf/isis/diff/bfece661

Branch: refs/heads/master
Commit: bfece661b326a070f5d25cc814a539dbd11d97a8
Parents: b0e31f6
Author: Dan Haywood <da...@haywood-associates.co.uk>
Authored: Wed Oct 8 08:21:46 2014 +0100
Committer: Dan Haywood <da...@haywood-associates.co.uk>
Committed: Wed Oct 8 08:21:46 2014 +0100

----------------------------------------------------------------------
 .../ServicesInstallerFromAnnotation.java        |   1 +
 .../restfulobjects/server/ResourceContext.java  |   4 +-
 .../DomainObjectResourceServerside.java         | 118 ++--
 .../server/resources/DomainResourceHelper.java  | 635 ++-----------------
 .../DomainServiceResourceServerside.java        |  12 +-
 .../resources/DomainTypeResourceServerside.java |  18 +-
 .../resources/HomePageResourceServerside.java   |   2 +-
 .../server/resources/JsonParserHelper.java      | 141 ++++
 .../server/resources/ObjectActionArgHelper.java | 122 ++++
 .../resources/ObjectAdapterAccessHelper.java    | 134 ++++
 .../resources/ObjectAdapterUpdateHelper.java    | 134 ++++
 .../server/resources/ResourceAbstract.java      |  57 +-
 .../resources/ResponseGeneratorService.java     | 273 ++++++++
 .../server/resources/Responses.java             |  64 ++
 .../resources/UserResourceServerside.java       |   2 +-
 .../restfulobjects/server/resources/Util.java   | 124 ++++
 .../resources/VersionResourceServerside.java    |   2 +-
 .../DomainResourceHelperTest_readBodyAsMap.java |  12 +-
 .../main/java/webapp/CustomMementoService.java  |  61 ++
 .../webapp/CustomResponseGeneratorService.java  |  32 +
 .../src/main/java/webapp/MyMementoService.java  |  61 --
 .../src/main/webapp/WEB-INF/isis.properties     |   3 +-
 22 files changed, 1228 insertions(+), 784 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/isis/blob/bfece661/core/runtime/src/main/java/org/apache/isis/core/runtime/services/ServicesInstallerFromAnnotation.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/services/ServicesInstallerFromAnnotation.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/ServicesInstallerFromAnnotation.java
index 5bd15a2..e325c06 100644
--- a/core/runtime/src/main/java/org/apache/isis/core/runtime/services/ServicesInstallerFromAnnotation.java
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/ServicesInstallerFromAnnotation.java
@@ -64,6 +64,7 @@ public class ServicesInstallerFromAnnotation extends InstallerAbstract implement
                                          ",org.apache.isis.core.metamodel.services" +
                                          ",org.apache.isis.core.runtime.services" +
                                          ",org.apache.isis.objectstore.jdo.applib.service" +
+                                         ",org.apache.isis.viewer.restfulobjects.server.resources" +
                                          ",org.apache.isis.viewer.restfulobjects.rendering.eventserializer" +
                                          ",org.apache.isis.objectstore.jdo.datanucleus.service.support" +
                                          ",org.apache.isis.objectstore.jdo.datanucleus.service.eventbus";

http://git-wip-us.apache.org/repos/asf/isis/blob/bfece661/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/ResourceContext.java
----------------------------------------------------------------------
diff --git a/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/ResourceContext.java b/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/ResourceContext.java
index 31a3996..efa2d7a 100644
--- a/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/ResourceContext.java
+++ b/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/ResourceContext.java
@@ -41,7 +41,7 @@ import org.apache.isis.viewer.restfulobjects.applib.client.RestfulRequest.Domain
 import org.apache.isis.viewer.restfulobjects.applib.client.RestfulRequest.RequestParameter;
 import org.apache.isis.viewer.restfulobjects.applib.client.RestfulResponse.HttpStatusCode;
 import org.apache.isis.viewer.restfulobjects.rendering.RendererContext;
-import org.apache.isis.viewer.restfulobjects.server.resources.DomainResourceHelper;
+import org.apache.isis.viewer.restfulobjects.server.resources.Util;
 
 public class ResourceContext implements RendererContext {
 
@@ -190,7 +190,7 @@ public class ResourceContext implements RendererContext {
             return map;
         } else {
             final String queryString = getUrlUnencodedQueryString();
-            return DomainResourceHelper.readQueryStringAsMap(queryString);
+            return Util.readQueryStringAsMap(queryString);
         }
     }
 

http://git-wip-us.apache.org/repos/asf/isis/blob/bfece661/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/DomainObjectResourceServerside.java
----------------------------------------------------------------------
diff --git a/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/DomainObjectResourceServerside.java b/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/DomainObjectResourceServerside.java
index d985c8a..9d63296 100644
--- a/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/DomainObjectResourceServerside.java
+++ b/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/DomainObjectResourceServerside.java
@@ -17,21 +17,10 @@
 package org.apache.isis.viewer.restfulobjects.server.resources;
 
 import java.io.InputStream;
-
-import javax.ws.rs.Consumes;
-import javax.ws.rs.DELETE;
-import javax.ws.rs.GET;
-import javax.ws.rs.POST;
-import javax.ws.rs.PUT;
-import javax.ws.rs.Path;
-import javax.ws.rs.PathParam;
-import javax.ws.rs.Produces;
-import javax.ws.rs.QueryParam;
+import javax.ws.rs.*;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
-
 import org.jboss.resteasy.annotations.ClientResponseType;
-
 import org.apache.isis.applib.annotation.Where;
 import org.apache.isis.core.commons.url.UrlEncodingUtils;
 import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
@@ -47,8 +36,6 @@ import org.apache.isis.viewer.restfulobjects.applib.client.RestfulResponse;
 import org.apache.isis.viewer.restfulobjects.applib.client.RestfulResponse.HttpStatusCode;
 import org.apache.isis.viewer.restfulobjects.applib.domainobjects.DomainObjectResource;
 import org.apache.isis.viewer.restfulobjects.server.RestfulObjectsApplicationException;
-import org.apache.isis.viewer.restfulobjects.server.resources.DomainResourceHelper.Intent;
-import org.apache.isis.viewer.restfulobjects.server.resources.DomainResourceHelper.MemberMode;
 
 @Path("/objects")
 public class DomainObjectResourceServerside extends ResourceAbstract implements DomainObjectResource {
@@ -67,8 +54,8 @@ public class DomainObjectResourceServerside extends ResourceAbstract implements
 
         init(RepresentationType.DOMAIN_OBJECT, Where.OBJECT_FORMS);
 
-        final String objectStr = DomainResourceHelper.asStringUtf8(object);
-        final JsonRepresentation objectRepr = DomainResourceHelper.readAsMap(objectStr);
+        final String objectStr = Util.asStringUtf8(object);
+        final JsonRepresentation objectRepr = Util.readAsMap(objectStr);
         if (!objectRepr.isMap()) {
             throw RestfulObjectsApplicationException.createWithMessage(HttpStatusCode.BAD_REQUEST, "Body is not a map; got %s", objectRepr);
         }
@@ -80,11 +67,14 @@ public class DomainObjectResourceServerside extends ResourceAbstract implements
 
         final ObjectAdapter objectAdapter = getResourceContext().getPersistenceSession().createTransientInstance(domainTypeSpec);
 
+        final ObjectAdapterUpdateHelper updateHelper = new ObjectAdapterUpdateHelper(getResourceContext(), objectAdapter);
+
         final JsonRepresentation propertiesList = objectRepr.getArrayEnsured("members[objectMemberType=property]");
         if (propertiesList == null) {
             throw RestfulObjectsApplicationException.createWithMessage(HttpStatusCode.BAD_REQUEST, "Could not find properties list (no members[objectMemberType=property]); got %s", objectRepr);
         }
-        if (!DomainResourceHelper.copyOverProperties(getResourceContext(), objectAdapter, propertiesList)) {
+
+        if (!updateHelper.copyOverProperties(propertiesList)) {
             throw RestfulObjectsApplicationException.createWithBody(HttpStatusCode.BAD_REQUEST, objectRepr, "Illegal property value");
         }
 
@@ -94,7 +84,7 @@ public class DomainObjectResourceServerside extends ResourceAbstract implements
         }
         getResourceContext().getPersistenceSession().makePersistent(objectAdapter);
 
-        return new DomainResourceHelper(getResourceContext(), objectAdapter).objectRepresentation();
+        return getDomainResourceHelper(objectAdapter).objectRepresentation();
     }
 
     // //////////////////////////////////////////////////////////
@@ -110,10 +100,14 @@ public class DomainObjectResourceServerside extends ResourceAbstract implements
 
         final ObjectAdapter objectAdapter = getObjectAdapterElseThrowNotFound(domainType, instanceId);
 
-        final DomainResourceHelper helper = new DomainResourceHelper(getResourceContext(), objectAdapter);
+        final DomainResourceHelper helper = getDomainResourceHelper(objectAdapter);
         return helper.objectRepresentation();
     }
 
+    private DomainResourceHelper getDomainResourceHelper(ObjectAdapter objectAdapter) {
+        return new DomainResourceHelper(getResourceContext(), objectAdapter);
+    }
+
     @Override
     @PUT
     @Path("/{domainType}/{instanceId}")
@@ -123,15 +117,16 @@ public class DomainObjectResourceServerside extends ResourceAbstract implements
 
         init(RepresentationType.DOMAIN_OBJECT, Where.OBJECT_FORMS);
 
-        final String objectStr = DomainResourceHelper.asStringUtf8(object);
-        final JsonRepresentation argRepr = DomainResourceHelper.readAsMap(objectStr);
+        final String objectStr = Util.asStringUtf8(object);
+        final JsonRepresentation argRepr = Util.readAsMap(objectStr);
         if (!argRepr.isMap()) {
             throw RestfulObjectsApplicationException.createWithMessage(HttpStatusCode.BAD_REQUEST, "Body is not a map; got %s", argRepr);
         }
 
         final ObjectAdapter objectAdapter = getObjectAdapterElseThrowNotFound(domainType, instanceId);
+        final ObjectAdapterUpdateHelper updateHelper = new ObjectAdapterUpdateHelper(getResourceContext(), objectAdapter);
 
-        if (!DomainResourceHelper.copyOverProperties(getResourceContext(), objectAdapter, argRepr)) {
+        if (!updateHelper.copyOverProperties(argRepr)) {
             throw RestfulObjectsApplicationException.createWithBody(HttpStatusCode.BAD_REQUEST, argRepr, "Illegal property value");
         }
 
@@ -140,7 +135,7 @@ public class DomainObjectResourceServerside extends ResourceAbstract implements
             throw RestfulObjectsApplicationException.createWithBody(HttpStatusCode.BAD_REQUEST, argRepr, validity.getReason());
         }
 
-        final DomainResourceHelper helper = new DomainResourceHelper(getResourceContext(), objectAdapter);
+        final DomainResourceHelper helper = getDomainResourceHelper(objectAdapter);
         return helper.objectRepresentation();
     }
 
@@ -166,9 +161,9 @@ public class DomainObjectResourceServerside extends ResourceAbstract implements
         init(RepresentationType.OBJECT_PROPERTY, Where.OBJECT_FORMS);
 
         final ObjectAdapter objectAdapter = getObjectAdapterElseThrowNotFound(domainType, instanceId);
-        final DomainResourceHelper helper = new DomainResourceHelper(getResourceContext(), objectAdapter);
+        final DomainResourceHelper helper = getDomainResourceHelper(objectAdapter);
 
-        return helper.propertyDetails(propertyId, MemberMode.NOT_MUTATING, Caching.NONE, getResourceContext().getWhere());
+        return helper.propertyDetails(propertyId, ResponseGeneratorService.MemberMode.NOT_MUTATING, Caching.NONE);
     }
 
     @Override
@@ -180,14 +175,15 @@ public class DomainObjectResourceServerside extends ResourceAbstract implements
         init(Where.OBJECT_FORMS);
 
         final ObjectAdapter objectAdapter = getObjectAdapterElseThrowNotFound(domainType, instanceId);
-        final DomainResourceHelper helper = new DomainResourceHelper(getResourceContext(), objectAdapter);
+        final DomainResourceHelper helper = getDomainResourceHelper(objectAdapter);
+        final ObjectAdapterAccessHelper accessHelper = new ObjectAdapterAccessHelper(getResourceContext(), objectAdapter);
 
-        final OneToOneAssociation property = helper.getPropertyThatIsVisibleForIntent(propertyId, Intent.MUTATE, getResourceContext().getWhere());
+        final OneToOneAssociation property = accessHelper.getPropertyThatIsVisibleForIntent(propertyId, ObjectAdapterAccessHelper.Intent.MUTATE);
 
         final ObjectSpecification propertySpec = property.getSpecification();
-        final String bodyAsString = DomainResourceHelper.asStringUtf8(body);
+        final String bodyAsString = Util.asStringUtf8(body);
 
-        final ObjectAdapter argAdapter = helper.parseAsMapWithSingleValue(propertySpec, bodyAsString);
+        final ObjectAdapter argAdapter = new JsonParserHelper(getResourceContext(), propertySpec).parseAsMapWithSingleValue(bodyAsString);
 
         final Consent consent = property.isAssociationValid(objectAdapter, argAdapter);
         if (consent.isVetoed()) {
@@ -196,7 +192,7 @@ public class DomainObjectResourceServerside extends ResourceAbstract implements
 
         property.set(objectAdapter, argAdapter);
 
-        return helper.propertyDetails(propertyId, MemberMode.MUTATING, Caching.NONE, getResourceContext().getWhere());
+        return helper.propertyDetails(propertyId, ResponseGeneratorService.MemberMode.MUTATING, Caching.NONE);
     }
 
     @Override
@@ -207,9 +203,11 @@ public class DomainObjectResourceServerside extends ResourceAbstract implements
         init(Where.OBJECT_FORMS);
 
         final ObjectAdapter objectAdapter = getObjectAdapterElseThrowNotFound(domainType, instanceId);
-        final DomainResourceHelper helper = new DomainResourceHelper(getResourceContext(), objectAdapter);
+        final DomainResourceHelper helper = getDomainResourceHelper(objectAdapter);
+        final ObjectAdapterAccessHelper accessHelper = new ObjectAdapterAccessHelper(getResourceContext(), objectAdapter);
 
-        final OneToOneAssociation property = helper.getPropertyThatIsVisibleForIntent(propertyId, Intent.MUTATE, getResourceContext().getWhere());
+        final OneToOneAssociation property = accessHelper.getPropertyThatIsVisibleForIntent(
+                propertyId, ObjectAdapterAccessHelper.Intent.MUTATE);
 
         final Consent consent = property.isAssociationValid(objectAdapter, null);
         if (consent.isVetoed()) {
@@ -218,7 +216,7 @@ public class DomainObjectResourceServerside extends ResourceAbstract implements
 
         property.set(objectAdapter, null);
 
-        return helper.propertyDetails(propertyId, MemberMode.MUTATING, Caching.NONE, getResourceContext().getWhere());
+        return helper.propertyDetails(propertyId, ResponseGeneratorService.MemberMode.MUTATING, Caching.NONE);
     }
 
     @Override
@@ -238,9 +236,9 @@ public class DomainObjectResourceServerside extends ResourceAbstract implements
         init(RepresentationType.OBJECT_COLLECTION, Where.PARENTED_TABLES);
 
         final ObjectAdapter objectAdapter = getObjectAdapterElseThrowNotFound(domainType, instanceId);
-        final DomainResourceHelper helper = new DomainResourceHelper(getResourceContext(), objectAdapter);
+        final DomainResourceHelper helper = getDomainResourceHelper(objectAdapter);
 
-        return helper.collectionDetails(collectionId, MemberMode.NOT_MUTATING, Caching.NONE, getResourceContext().getWhere());
+        return helper.collectionDetails(collectionId, ResponseGeneratorService.MemberMode.NOT_MUTATING, Caching.NONE);
     }
 
     @Override
@@ -252,17 +250,19 @@ public class DomainObjectResourceServerside extends ResourceAbstract implements
         init(Where.PARENTED_TABLES);
 
         final ObjectAdapter objectAdapter = getObjectAdapterElseThrowNotFound(domainType, instanceId);
-        final DomainResourceHelper helper = new DomainResourceHelper(getResourceContext(), objectAdapter);
+        final DomainResourceHelper helper = getDomainResourceHelper(objectAdapter);
+        final ObjectAdapterAccessHelper accessHelper = new ObjectAdapterAccessHelper(getResourceContext(), objectAdapter);
 
-        final OneToManyAssociation collection = helper.getCollectionThatIsVisibleForIntent(collectionId, Intent.MUTATE, getResourceContext().getWhere());
+        final OneToManyAssociation collection = accessHelper.getCollectionThatIsVisibleForIntent(
+                collectionId, ObjectAdapterAccessHelper.Intent.MUTATE);
 
         if (!collection.getCollectionSemantics().isSet()) {
             throw RestfulObjectsApplicationException.createWithMessage(HttpStatusCode.BAD_REQUEST, "Collection '%s' does not have set semantics", collectionId);
         }
 
         final ObjectSpecification collectionSpec = collection.getSpecification();
-        final String bodyAsString = DomainResourceHelper.asStringUtf8(body);
-        final ObjectAdapter argAdapter = helper.parseAsMapWithSingleValue(collectionSpec, bodyAsString);
+        final String bodyAsString = Util.asStringUtf8(body);
+        final ObjectAdapter argAdapter = new JsonParserHelper(getResourceContext(), collectionSpec).parseAsMapWithSingleValue(bodyAsString);
 
         final Consent consent = collection.isValidToAdd(objectAdapter, argAdapter);
         if (consent.isVetoed()) {
@@ -271,7 +271,7 @@ public class DomainObjectResourceServerside extends ResourceAbstract implements
 
         collection.addElement(objectAdapter, argAdapter);
 
-        return helper.collectionDetails(collectionId, MemberMode.MUTATING, Caching.NONE, getResourceContext().getWhere());
+        return helper.collectionDetails(collectionId, ResponseGeneratorService.MemberMode.MUTATING, Caching.NONE);
     }
 
     @Override
@@ -283,17 +283,19 @@ public class DomainObjectResourceServerside extends ResourceAbstract implements
         init(Where.PARENTED_TABLES);
 
         final ObjectAdapter objectAdapter = getObjectAdapterElseThrowNotFound(domainType, instanceId);
-        final DomainResourceHelper helper = new DomainResourceHelper(getResourceContext(), objectAdapter);
+        final DomainResourceHelper helper = getDomainResourceHelper(objectAdapter);
+        final ObjectAdapterAccessHelper accessHelper = new ObjectAdapterAccessHelper(getResourceContext(), objectAdapter);
 
-        final OneToManyAssociation collection = helper.getCollectionThatIsVisibleForIntent(collectionId, Intent.MUTATE, getResourceContext().getWhere());
+        final OneToManyAssociation collection = accessHelper.getCollectionThatIsVisibleForIntent(
+                collectionId, ObjectAdapterAccessHelper.Intent.MUTATE);
 
         if (!collection.getCollectionSemantics().isListOrArray()) {
             throw RestfulObjectsApplicationException.createWithMessage(HttpStatusCode.METHOD_NOT_ALLOWED, "Collection '%s' does not have list or array semantics", collectionId);
         }
 
         final ObjectSpecification collectionSpec = collection.getSpecification();
-        final String bodyAsString = DomainResourceHelper.asStringUtf8(body);
-        final ObjectAdapter argAdapter = helper.parseAsMapWithSingleValue(collectionSpec, bodyAsString);
+        final String bodyAsString = Util.asStringUtf8(body);
+        final ObjectAdapter argAdapter = new JsonParserHelper(getResourceContext(), collectionSpec).parseAsMapWithSingleValue(bodyAsString);
 
         final Consent consent = collection.isValidToAdd(objectAdapter, argAdapter);
         if (consent.isVetoed()) {
@@ -302,7 +304,7 @@ public class DomainObjectResourceServerside extends ResourceAbstract implements
 
         collection.addElement(objectAdapter, argAdapter);
 
-        return helper.collectionDetails(collectionId, MemberMode.MUTATING, Caching.NONE, getResourceContext().getWhere());
+        return helper.collectionDetails(collectionId, ResponseGeneratorService.MemberMode.MUTATING, Caching.NONE);
     }
 
     @Override
@@ -313,12 +315,14 @@ public class DomainObjectResourceServerside extends ResourceAbstract implements
         init(Where.PARENTED_TABLES);
 
         final ObjectAdapter objectAdapter = getObjectAdapterElseThrowNotFound(domainType, instanceId);
-        final DomainResourceHelper helper = new DomainResourceHelper(getResourceContext(), objectAdapter);
+        final DomainResourceHelper helper = getDomainResourceHelper(objectAdapter);
+        final ObjectAdapterAccessHelper accessHelper = new ObjectAdapterAccessHelper(getResourceContext(), objectAdapter);
 
-        final OneToManyAssociation collection = helper.getCollectionThatIsVisibleForIntent(collectionId, Intent.MUTATE, getResourceContext().getWhere());
+        final OneToManyAssociation collection = accessHelper.getCollectionThatIsVisibleForIntent(
+                collectionId, ObjectAdapterAccessHelper.Intent.MUTATE);
 
         final ObjectSpecification collectionSpec = collection.getSpecification();
-        final ObjectAdapter argAdapter = helper.parseAsMapWithSingleValue(collectionSpec, getResourceContext().getUrlUnencodedQueryString());
+        final ObjectAdapter argAdapter = new JsonParserHelper(getResourceContext(), collectionSpec).parseAsMapWithSingleValue(getResourceContext().getUrlUnencodedQueryString());
 
         final Consent consent = collection.isValidToRemove(objectAdapter, argAdapter);
         if (consent.isVetoed()) {
@@ -327,7 +331,7 @@ public class DomainObjectResourceServerside extends ResourceAbstract implements
 
         collection.removeElement(objectAdapter, argAdapter);
 
-        return helper.collectionDetails(collectionId, MemberMode.MUTATING, Caching.NONE, getResourceContext().getWhere());
+        return helper.collectionDetails(collectionId, ResponseGeneratorService.MemberMode.MUTATING, Caching.NONE);
     }
 
     // //////////////////////////////////////////////////////////
@@ -342,9 +346,9 @@ public class DomainObjectResourceServerside extends ResourceAbstract implements
         init(RepresentationType.OBJECT_ACTION, Where.OBJECT_FORMS);
 
         final ObjectAdapter objectAdapter = getObjectAdapterElseThrowNotFound(domainType, instanceId);
-        final DomainResourceHelper helper = new DomainResourceHelper(getResourceContext(), objectAdapter);
+        final DomainResourceHelper helper = getDomainResourceHelper(objectAdapter);
 
-        return helper.actionPrompt(actionId, getResourceContext().getWhere());
+        return helper.actionPrompt(actionId);
     }
 
     @Override
@@ -382,9 +386,9 @@ public class DomainObjectResourceServerside extends ResourceAbstract implements
         final JsonRepresentation arguments = getResourceContext().getQueryStringAsJsonRepr();
 
         final ObjectAdapter objectAdapter = getObjectAdapterElseThrowNotFound(domainType, instanceId);
-        final DomainResourceHelper helper = new DomainResourceHelper(getResourceContext(), objectAdapter);
+        final DomainResourceHelper helper = getDomainResourceHelper(objectAdapter);
 
-        return helper.invokeActionQueryOnly(actionId, arguments, getResourceContext().getWhere());
+        return helper.invokeActionQueryOnly(actionId, arguments);
     }
 
     @Override
@@ -403,9 +407,9 @@ public class DomainObjectResourceServerside extends ResourceAbstract implements
         final JsonRepresentation arguments = getResourceContext().getQueryStringAsJsonRepr();
         
         final ObjectAdapter objectAdapter = getObjectAdapterElseThrowNotFound(domainType, instanceId);
-        final DomainResourceHelper helper = new DomainResourceHelper(getResourceContext(), objectAdapter);
+        final DomainResourceHelper helper = getDomainResourceHelper(objectAdapter);
 
-        return helper.invokeActionIdempotent(actionId, arguments, getResourceContext().getWhere());
+        return helper.invokeActionIdempotent(actionId, arguments);
     }
 
     @Override
@@ -419,10 +423,10 @@ public class DomainObjectResourceServerside extends ResourceAbstract implements
         final JsonRepresentation arguments = getResourceContext().getQueryStringAsJsonRepr();
         
         final ObjectAdapter objectAdapter = getObjectAdapterElseThrowNotFound(domainType, instanceId);
-        final DomainResourceHelper helper = new DomainResourceHelper(getResourceContext(), objectAdapter);
+        final DomainResourceHelper helper = getDomainResourceHelper(objectAdapter);
 
         Where where = getResourceContext().getWhere();
-        return helper.invokeAction(actionId, arguments, where);
+        return helper.invokeAction(actionId, arguments);
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/isis/blob/bfece661/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/DomainResourceHelper.java
----------------------------------------------------------------------
diff --git a/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/DomainResourceHelper.java b/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/DomainResourceHelper.java
index 621f041..db04307 100644
--- a/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/DomainResourceHelper.java
+++ b/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/DomainResourceHelper.java
@@ -1,4 +1,3 @@
-
 /**
  *  Licensed to the Apache Software Foundation (ASF) under one or more
  *  contributor license agreements.  See the NOTICE file distributed with
@@ -17,79 +16,27 @@
  */
 package org.apache.isis.viewer.restfulobjects.server.resources;
 
-import java.io.IOException;
-import java.io.InputStream;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-
 import javax.ws.rs.core.Response;
-import javax.ws.rs.core.Response.ResponseBuilder;
-
-import com.google.common.base.Charsets;
-import com.google.common.collect.Lists;
-import com.google.common.io.ByteStreams;
-
-import org.codehaus.jackson.JsonParseException;
-import org.codehaus.jackson.map.JsonMappingException;
-
-import org.apache.isis.applib.annotation.ActionSemantics;
-import org.apache.isis.applib.annotation.Where;
-import org.apache.isis.core.commons.authentication.AuthenticationSession;
 import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
-import org.apache.isis.core.metamodel.adapter.version.Version;
-import org.apache.isis.core.metamodel.consent.Consent;
-import org.apache.isis.core.metamodel.facets.object.encodeable.EncodableFacet;
-import org.apache.isis.core.metamodel.spec.ObjectSpecification;
-import org.apache.isis.core.metamodel.spec.feature.Contributed;
-import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
-import org.apache.isis.core.metamodel.spec.feature.ObjectActionParameter;
-import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
-import org.apache.isis.core.metamodel.spec.feature.ObjectMember;
-import org.apache.isis.core.metamodel.spec.feature.OneToManyAssociation;
-import org.apache.isis.core.metamodel.spec.feature.OneToOneAssociation;
+import org.apache.isis.core.runtime.system.context.IsisContext;
+import org.apache.isis.core.runtime.system.persistence.PersistenceSession;
 import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation;
-import org.apache.isis.viewer.restfulobjects.applib.client.RestfulResponse.HttpStatusCode;
-import org.apache.isis.viewer.restfulobjects.applib.util.JsonMapper;
-import org.apache.isis.viewer.restfulobjects.applib.util.UrlEncodingUtils;
-import org.apache.isis.viewer.restfulobjects.rendering.RendererContext;
-import org.apache.isis.viewer.restfulobjects.rendering.domainobjects.AbstractObjectMemberReprRenderer;
-import org.apache.isis.viewer.restfulobjects.rendering.domainobjects.ActionResultReprRenderer;
-import org.apache.isis.viewer.restfulobjects.rendering.domainobjects.ActionResultReprRenderer.SelfLink;
 import org.apache.isis.viewer.restfulobjects.rendering.domainobjects.DomainObjectLinkTo;
-import org.apache.isis.viewer.restfulobjects.rendering.domainobjects.DomainObjectReprRenderer;
-import org.apache.isis.viewer.restfulobjects.rendering.domainobjects.JsonValueEncoder;
-import org.apache.isis.viewer.restfulobjects.rendering.domainobjects.MemberType;
-import org.apache.isis.viewer.restfulobjects.rendering.domainobjects.ObjectActionReprRenderer;
 import org.apache.isis.viewer.restfulobjects.rendering.domainobjects.ObjectAdapterLinkTo;
-import org.apache.isis.viewer.restfulobjects.rendering.domainobjects.ObjectAndAction;
-import org.apache.isis.viewer.restfulobjects.rendering.domainobjects.ObjectAndActionInvocation;
-import org.apache.isis.viewer.restfulobjects.rendering.domainobjects.ObjectAndCollection;
-import org.apache.isis.viewer.restfulobjects.rendering.domainobjects.ObjectAndProperty;
-import org.apache.isis.viewer.restfulobjects.rendering.domainobjects.ObjectCollectionReprRenderer;
-import org.apache.isis.viewer.restfulobjects.rendering.domainobjects.ObjectPropertyReprRenderer;
 import org.apache.isis.viewer.restfulobjects.server.ResourceContext;
-import org.apache.isis.viewer.restfulobjects.server.RestfulObjectsApplicationException;
 import org.apache.isis.viewer.restfulobjects.server.resources.ResourceAbstract.Caching;
-import org.apache.isis.viewer.restfulobjects.server.util.OidUtils;
-import org.apache.isis.viewer.restfulobjects.server.util.UrlDecoderUtils;
-import org.apache.isis.viewer.restfulobjects.server.util.UrlParserUtils;
-
-public final class DomainResourceHelper {
-
-    private static final DateFormat ETAG_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
 
-    private final RendererContext resourceContext;
-    private ObjectAdapterLinkTo adapterLinkTo;
+public class DomainResourceHelper implements ResponseGeneratorService.ResponseContext {
 
-    private final ObjectAdapter objectAdapter;
+    private final ResponseGeneratorService generatorService;
 
-    public DomainResourceHelper(final RendererContext resourceContext, final ObjectAdapter objectAdapter) {
+    public DomainResourceHelper(final ResourceContext resourceContext, final ObjectAdapter objectAdapter) {
         this.resourceContext = resourceContext;
         this.objectAdapter = objectAdapter;
+
         using(new DomainObjectLinkTo());
+
+        generatorService = lookupService(ResponseGeneratorService.class);
     }
 
     public DomainResourceHelper using(final ObjectAdapterLinkTo linkTo) {
@@ -98,563 +45,83 @@ public final class DomainResourceHelper {
         return this;
     }
 
-    // //////////////////////////////////////////////////////////////
-    // multiple properties (persist or multi-property update)
-    // //////////////////////////////////////////////////////////////
-
-    static boolean copyOverProperties(final RendererContext resourceContext, final ObjectAdapter objectAdapter, final JsonRepresentation propertiesList) {
-        final ObjectSpecification objectSpec = objectAdapter.getSpecification();
-        final List<ObjectAssociation> properties = objectSpec.getAssociations(Contributed.EXCLUDED, ObjectAssociation.Filters.PROPERTIES);
-        boolean allOk = true;
-
-        for (final ObjectAssociation association : properties) {
-            final OneToOneAssociation property = (OneToOneAssociation) association;
-            final ObjectSpecification propertySpec = property.getSpecification();
-            final String id = property.getId();
-            final JsonRepresentation propertyRepr = propertiesList.getRepresentation(id);
-            final Consent visibility = property.isVisible(resourceContext.getAuthenticationSession() , objectAdapter, resourceContext.getWhere());
-            final Consent usability = property.isUsable(resourceContext.getAuthenticationSession() , objectAdapter, resourceContext.getWhere());
-
-            final boolean invisible = visibility.isVetoed();
-            final boolean disabled = usability.isVetoed();
-            final boolean valueProvided = propertyRepr != null;
+    //region > ResponseContext impl
 
-            if(!valueProvided) {
-                
-                // no value provided
-                
-                if(invisible || disabled) {
-                    // that's ok, indeed expected
-                    continue; 
-                }
-                if (!property.isMandatory()) {
-                    // optional, so also not a problem
-                    continue;
-                }
-
-                // otherwise, is an error.
-                final String invalidReason = propertiesList.getString("x-ro-invalidReason");
-                if(invalidReason != null) {
-                    propertiesList.mapPut("x-ro-invalidReason", invalidReason + "; " + property.getName());
-                } else {
-                    propertiesList.mapPut("x-ro-invalidReason", "Mandatory field(s) missing: " + property.getName());
-                }
-                allOk = false;
-                continue;
-                
-            } else {
-                
-                // value has been provided
-                if (invisible) {
-                    // silently ignore; don't want to acknowledge the existence of this property to the caller
-                    continue;
-                }
-                if (disabled) {
-                    // not allowed to update
-                    propertyRepr.mapPut("invalidReason", usability.getReason());
-                    allOk = false;
-                    continue;
-                }
-                
-                // ok, we have a value, and the property is not invisible, and is not disabled
-                final ObjectAdapter valueAdapter;
-                try {
-                    valueAdapter = objectAdapterFor(resourceContext, propertySpec, propertyRepr);
-                } catch(IllegalArgumentException ex) {
-                    propertyRepr.mapPut("invalidReason", ex.getMessage());
-                    allOk = false;
-                    continue;
-                }
-                // check if the proposed value is valid 
-                final Consent validity = property.isAssociationValid(objectAdapter, valueAdapter);
-                if (validity.isAllowed()) {
-                    try {
-                        property.set(objectAdapter, valueAdapter);
-                    } catch (final IllegalArgumentException ex) {
-                        propertyRepr.mapPut("invalidReason", ex.getMessage());
-                        allOk = false;
-                    }
-                } else {
-                    propertyRepr.mapPut("invalidReason", validity.getReason());
-                    allOk = false;
-                }
-            }
-            
-        }
-
-        return allOk;
-    }
-
-    // //////////////////////////////////////////////////////////////
-    // propertyDetails
-    // //////////////////////////////////////////////////////////////
-
-    public Response objectRepresentation() {
-        final DomainObjectReprRenderer renderer = new DomainObjectReprRenderer(resourceContext, null, JsonRepresentation.newMap());
-        renderer.with(objectAdapter).includesSelf();
-
-        final ResponseBuilder respBuilder = ResourceAbstract.responseOfOk(renderer, Caching.NONE);
-
-        final Version version = objectAdapter.getVersion();
-        if (version != null && version.getTime() != null) {
-            respBuilder.tag(ETAG_FORMAT.format(version.getTime()));
-        }
-        return respBuilder.build();
-    }
-
-    // //////////////////////////////////////////////////////////////
-    // propertyDetails
-    // //////////////////////////////////////////////////////////////
-
-    public enum MemberMode {
-        NOT_MUTATING {
-            @Override
-            public void apply(final AbstractObjectMemberReprRenderer<?, ?> renderer) {
-                renderer.asStandalone();
-            }
-        },
-        MUTATING {
-            @Override
-            public void apply(final AbstractObjectMemberReprRenderer<?, ?> renderer) {
-                renderer.asMutated();
-            }
-        };
-
-        public abstract void apply(AbstractObjectMemberReprRenderer<?, ?> renderer);
-    }
-
-    Response propertyDetails(final String propertyId, final MemberMode memberMode, final Caching caching, Where where) {
-
-        final OneToOneAssociation property = getPropertyThatIsVisibleForIntent(propertyId, Intent.ACCESS, where);
-
-        final ObjectPropertyReprRenderer renderer = new ObjectPropertyReprRenderer(resourceContext, null, null, JsonRepresentation.newMap());
-
-        renderer.with(new ObjectAndProperty(objectAdapter, property)).usingLinkTo(adapterLinkTo);
-
-        memberMode.apply(renderer);
-
-        return ResourceAbstract.responseOfOk(renderer, caching).build();
-    }
-
-    // //////////////////////////////////////////////////////////////
-    // collectionDetails
-    // //////////////////////////////////////////////////////////////
-
-    Response collectionDetails(final String collectionId, final MemberMode memberMode, final Caching caching, Where where) {
-
-        final OneToManyAssociation collection = getCollectionThatIsVisibleForIntent(collectionId, Intent.ACCESS, where);
-
-        final ObjectCollectionReprRenderer renderer = new ObjectCollectionReprRenderer(resourceContext, null, null, JsonRepresentation.newMap());
-
-        renderer.with(new ObjectAndCollection(objectAdapter, collection)).usingLinkTo(adapterLinkTo);
-
-        memberMode.apply(renderer);
-
-        return ResourceAbstract.responseOfOk(renderer, caching).build();
-    }
-
-    // //////////////////////////////////////////////////////////////
-    // action Prompt
-    // //////////////////////////////////////////////////////////////
-
-    Response actionPrompt(final String actionId, Where where) {
-        final ObjectAction action = getObjectActionThatIsVisibleForIntent(actionId, Intent.ACCESS, where);
-
-        final ObjectActionReprRenderer renderer = new ObjectActionReprRenderer(resourceContext, null, null, JsonRepresentation.newMap());
-
-        renderer.with(new ObjectAndAction(objectAdapter, action)).usingLinkTo(adapterLinkTo).asStandalone();
+    private final ResourceContext resourceContext;
+    private final ObjectAdapter objectAdapter;
+    private ObjectAdapterLinkTo adapterLinkTo;
 
-        return ResourceAbstract.responseOfOk(renderer, Caching.NONE).build();
+    @Override
+    public ResourceContext getResourceContext() {
+        return resourceContext;
     }
 
-    // //////////////////////////////////////////////////////////////
-    // invoke action
-    // //////////////////////////////////////////////////////////////
-
-    enum Intent {
-        ACCESS, MUTATE;
-
-        public boolean isMutate() {
-            return this == MUTATE;
-        }
+    @Override
+    public ObjectAdapter getObjectAdapter() {
+        return objectAdapter;
     }
 
-    Response invokeActionQueryOnly(final String actionId, final JsonRepresentation arguments, Where where) {
-        final ObjectAction action = getObjectActionThatIsVisibleForIntent(actionId, Intent.MUTATE, where);
-
-        final ActionSemantics.Of actionSemantics = action.getSemantics();
-        if (actionSemantics != ActionSemantics.Of.SAFE) {
-            throw RestfulObjectsApplicationException.createWithMessage(HttpStatusCode.METHOD_NOT_ALLOWED, "Method not allowed; action '%s' is not query only", action.getId());
-        }
-
-        return invokeActionUsingAdapters(action, arguments, SelfLink.INCLUDED);
+    @Override
+    public ObjectAdapterLinkTo getAdapterLinkTo() {
+        return adapterLinkTo;
     }
+    //endregion
 
-    Response invokeActionIdempotent(final String actionId, final JsonRepresentation arguments, Where where) {
 
-        final ObjectAction action = getObjectActionThatIsVisibleForIntent(actionId, Intent.MUTATE, where);
-
-        final ActionSemantics.Of actionSemantics = action.getSemantics();
-        if (!actionSemantics.isIdempotentInNature()) {
-            throw RestfulObjectsApplicationException.createWithMessage(HttpStatusCode.METHOD_NOT_ALLOWED, "Method not allowed; action '%s' is not idempotent", action.getId());
-        }
-        return invokeActionUsingAdapters(action, arguments, SelfLink.EXCLUDED);
+    public Response objectRepresentation() {
+        return generatorService.objectRepresentation(this);
     }
 
-    Response invokeAction(final String actionId, final JsonRepresentation arguments, Where where) {
-        final ObjectAction action = getObjectActionThatIsVisibleForIntent(actionId, Intent.MUTATE, where);
-
-        return invokeActionUsingAdapters(action, arguments, SelfLink.EXCLUDED);
+    public Response propertyDetails(
+            final String propertyId,
+            final ResponseGeneratorService.MemberMode memberMode,
+            final Caching caching) {
+        return generatorService.propertyDetails(this, propertyId, memberMode, caching);
     }
 
-    private Response invokeActionUsingAdapters(final ObjectAction action, final JsonRepresentation arguments, SelfLink selfLink) {
-
-        final List<ObjectAdapter> argAdapters = parseAndValidateArguments(action, arguments);
-
-        // invoke
-        final ObjectAdapter[] argArray2 = argAdapters.toArray(new ObjectAdapter[0]);
-        final ObjectAdapter returnedAdapter = action.execute(objectAdapter, argArray2);
-
-        // response (void)
-        final ActionResultReprRenderer renderer = new ActionResultReprRenderer(resourceContext, null, selfLink, JsonRepresentation.newMap());
-
-        renderer.with(new ObjectAndActionInvocation(objectAdapter, action, arguments, returnedAdapter)).using(adapterLinkTo);
+    public Response collectionDetails(
+            final String collectionId,
+            final ResponseGeneratorService.MemberMode memberMode,
+            final Caching caching) {
 
-        final ResponseBuilder respBuilder = ResourceAbstract.responseOfOk(renderer, Caching.NONE);
-
-        final Version version = objectAdapter.getVersion();
-        ResourceAbstract.addLastModifiedAndETagIfAvailable(respBuilder, version);
-
-        return respBuilder.build();
+        return generatorService.collectionDetails(this, collectionId, memberMode, caching);
     }
 
-    /**
-     *
-     * @param resourceContext
-     * @param objectSpec
-     *            - the {@link ObjectSpecification} to interpret the object as.
-     * @param argRepr
-     *            - expected to be either a String or a Map (ie from within a
-     *            List, built by parsing a JSON structure).
-     */
-    private static ObjectAdapter objectAdapterFor(final RendererContext resourceContext, final ObjectSpecification objectSpec, final JsonRepresentation argRepr) {
-
-        if (argRepr == null) {
-            return null;
-        }
-
-        if(!argRepr.mapHas("value")) {
-            String reason = "No 'value' key";
-            argRepr.mapPut("invalidReason", reason);
-            throw new IllegalArgumentException(reason);
-        }
+    public Response actionPrompt(final String actionId) {
 
-        if (objectSpec == null) {
-            String reason = "ObjectSpec is null, cannot validate";
-            argRepr.mapPut("invalidReason", reason);
-            throw new IllegalArgumentException(reason);
-        }
-        
-        final JsonRepresentation argValueRepr = argRepr.getRepresentation("value");
-
-        // value (encodable)
-        if (objectSpec.isEncodeable()) {
-            try {
-                return JsonValueEncoder.asAdapter(objectSpec, argValueRepr, null);
-            }catch(IllegalArgumentException ex) {
-                argRepr.mapPut("invalidReason", ex.getMessage());
-                throw ex;
-            }catch(Exception ex) {
-                StringBuilder buf = new StringBuilder("Failed to parse representation ");
-                try {
-                    final String reprStr = argRepr.getString("value");
-                    buf.append("'").append(reprStr).append("' ");
-                } catch(Exception ex2) {
-                }
-                buf.append("as value of type '").append(objectSpec.getShortIdentifier()).append("'");
-                String reason = buf.toString();
-                argRepr.mapPut("invalidReason", reason);
-                throw new IllegalArgumentException(reason);
-            }
-        }
-
-        // reference
-        if (!argValueRepr.isLink()) {
-            final String reason = "Expected a link (because this object's type is not a value) but found no 'href'";
-            argRepr.mapPut("invalidReason", reason);
-            throw new IllegalArgumentException(reason);
-        }
-        final String oidFromHref = UrlParserUtils.encodedOidFromLink(argValueRepr);
-        if (oidFromHref == null) {
-            final String reason = "Could not parse 'href' to identify the object's OID";
-            argRepr.mapPut("invalidReason", reason);
-            throw new IllegalArgumentException(reason);
-        }
-
-        final ObjectAdapter objectAdapter = OidUtils.getObjectAdapterElseNull(resourceContext, oidFromHref);
-        if (objectAdapter == null) {
-            final String reason = "'href' does not reference a known entity";
-            argRepr.mapPut("invalidReason", reason);
-            throw new IllegalArgumentException(reason);
-        }
-        return objectAdapter;
+        return generatorService.actionPrompt(this, actionId);
     }
 
-    /**
-     * Similar to
-     * {@link #objectAdapterFor(ResourceContext, ObjectSpecification, Object)},
-     * however the object being interpreted is a String holding URL encoded JSON
-     * (rather than having already been parsed into a Map representation).
-     *
-     * @throws IOException
-     * @throws JsonMappingException
-     * @throws JsonParseException
-     */
-    ObjectAdapter objectAdapterFor(final ObjectSpecification spec, final String urlEncodedJson) throws JsonParseException, JsonMappingException, IOException {
+    public Response invokeActionQueryOnly(final String actionId, final JsonRepresentation arguments) {
 
-        final String json = UrlDecoderUtils.urlDecode(urlEncodedJson);
-        final JsonRepresentation representation = JsonMapper.instance().read(json);
-        return objectAdapterFor(resourceContext, spec, representation);
+        return generatorService.invokeActionQueryOnly(this, actionId, arguments);
     }
 
+    public Response invokeActionIdempotent(final String actionId, final JsonRepresentation arguments) {
 
-    // ///////////////////////////////////////////////////////////////////
-    // get{MemberType}ThatIsVisibleAndUsable
-    // ///////////////////////////////////////////////////////////////////
-
-    protected OneToOneAssociation getPropertyThatIsVisibleForIntent(final String propertyId, final Intent intent, Where where) {
-
-        final ObjectAssociation association;
-        try {
-            final ObjectSpecification specification = objectAdapter.getSpecification();
-            association = specification.getAssociation(propertyId);
-        } catch(Exception ex) {
-            // fall through
-            throwNotFoundException(propertyId, MemberType.PROPERTY);
-            return null; // to keep compiler happy.
-        }
-
-        if (association == null || !association.isOneToOneAssociation()) {
-            throwNotFoundException(propertyId, MemberType.PROPERTY);
-        }
-        
-        final OneToOneAssociation property = (OneToOneAssociation) association;
-        return memberThatIsVisibleForIntent(property, MemberType.PROPERTY, intent, where);
+        return generatorService.invokeActionIdempotent(this, actionId, arguments);
     }
 
-    protected OneToManyAssociation getCollectionThatIsVisibleForIntent(final String collectionId, final Intent intent, Where where) {
+    public Response invokeAction(final String actionId, final JsonRepresentation arguments) {
 
-        final ObjectAssociation association;
-        try {
-            final ObjectSpecification specification = objectAdapter.getSpecification();
-            association = specification.getAssociation(collectionId);
-        } catch(Exception ex) {
-            // fall through
-            throwNotFoundException(collectionId, MemberType.COLLECTION);
-            return null; // to keep compiler happy.
-        }
-        if (association == null || !association.isOneToManyAssociation()) {
-            throwNotFoundException(collectionId, MemberType.COLLECTION);
-        } 
-        final OneToManyAssociation collection = (OneToManyAssociation) association;
-        return memberThatIsVisibleForIntent(collection, MemberType.COLLECTION, intent, where);
+        return generatorService.invokeAction(this, actionId, arguments);
     }
 
-    protected ObjectAction getObjectActionThatIsVisibleForIntent(final String actionId, final Intent intent, Where where) {
-
-        final ObjectAction action;
-        try {
-            final ObjectSpecification specification = objectAdapter.getSpecification();
-            action = specification.getObjectAction(actionId);
-        } catch(Exception ex) {
-            throwNotFoundException(actionId, MemberType.ACTION);
-            return null; // to keep compiler happy.
-        }
-        if (action == null) {
-            throwNotFoundException(actionId, MemberType.ACTION);
-        } 
-        return memberThatIsVisibleForIntent(action, MemberType.ACTION, intent, where);
-    }
 
-    protected <T extends ObjectMember> T memberThatIsVisibleForIntent(final T objectMember, final MemberType memberType, final Intent intent, Where where) {
-        final String memberId = objectMember.getId();
-        final AuthenticationSession authenticationSession = resourceContext.getAuthenticationSession();
-        if (objectMember.isVisible(authenticationSession, objectAdapter, where).isVetoed()) {
-            throwNotFoundException(memberId, memberType);
-        }
-        if (intent.isMutate()) {
-            final Consent usable = objectMember.isUsable(authenticationSession, objectAdapter, where);
-            if (usable.isVetoed()) {
-                throw RestfulObjectsApplicationException.createWithMessage(HttpStatusCode.FORBIDDEN, usable.getReason());
-            }
-        }
-        return objectMember;
-    }
+    // //////////////////////////////////////
+    // dependencies (from context)
+    // //////////////////////////////////////
 
-    protected static void throwNotFoundException(final String memberId, final MemberType memberType) {
-        final String memberTypeStr = memberType.name().toLowerCase();
-        throw RestfulObjectsApplicationException.createWithMessage(HttpStatusCode.NOT_FOUND, "%s '%s' either does not exist or is not visible", memberTypeStr, memberId);
+    private PersistenceSession getPersistenceSession() {
+        return IsisContext.getPersistenceSession();
     }
 
-    // ///////////////////////////////////////////////////////////////////
-    // parseBody
-    // ///////////////////////////////////////////////////////////////////
-
     /**
-     *
-     * @param objectSpec
-     * @param bodyAsString
-     *            - as per {@link #asStringUtf8(InputStream)}
-     * @return
+     * Service locator
      */
-    ObjectAdapter parseAsMapWithSingleValue(final ObjectSpecification objectSpec, final String bodyAsString) {
-        final JsonRepresentation arguments = readAsMap(bodyAsString);
-        return parseAsMapWithSingleValue(objectSpec, arguments);
-    }
-
-    ObjectAdapter parseAsMapWithSingleValue(final ObjectSpecification objectSpec, final JsonRepresentation arguments) {
-        final JsonRepresentation representation = arguments.getRepresentation("value");
-        if (arguments.size() != 1 || representation == null) {
-            throw RestfulObjectsApplicationException.createWithMessage(HttpStatusCode.BAD_REQUEST, "Body should be a map with a single key 'value' whose value represents an instance of type '%s'", resourceFor(objectSpec));
-        }
-
-        return objectAdapterFor(resourceContext, objectSpec, arguments);
-    }
-
-    private List<ObjectAdapter> parseAndValidateArguments(final ObjectAction action, final JsonRepresentation arguments) {
-        final List<JsonRepresentation> argList = argListFor(action, arguments);
-
-        final List<ObjectAdapter> argAdapters = Lists.newArrayList();
-        final List<ObjectActionParameter> parameters = action.getParameters();
-        boolean valid = true;
-        for (int i = 0; i < argList.size(); i++) {
-            final JsonRepresentation argRepr = argList.get(i);
-            final ObjectSpecification paramSpec = parameters.get(i).getSpecification();
-            try {
-                final ObjectAdapter argAdapter = objectAdapterFor(resourceContext, paramSpec, argRepr);
-                argAdapters.add(argAdapter);
-
-                // validate individual arg
-                final ObjectActionParameter parameter = parameters.get(i);
-                final Object argPojo = argAdapter!=null?argAdapter.getObject():null;
-                final String reasonNotValid = parameter.isValid(objectAdapter, argPojo, null);
-                if (reasonNotValid != null) {
-                    argRepr.mapPut("invalidReason", reasonNotValid);
-                    valid = false;
-                }
-            } catch (final IllegalArgumentException e) {
-                argAdapters.add(null);
-                valid = false;
-            }
-        }
-        
-        // validate all args
-        final ObjectAdapter[] argArray = argAdapters.toArray(new ObjectAdapter[0]);
-        final Consent consent = action.isProposedArgumentSetValid(objectAdapter, argArray);
-        if (consent.isVetoed()) {
-            arguments.mapPut("x-ro-invalidReason", consent.getReason());
-            valid = false;
-        }
-
-        if(!valid) {
-            throw RestfulObjectsApplicationException.createWithBody(HttpStatusCode.VALIDATION_FAILED, arguments, "Validation failed, see body for details");
-        }
-        
-        return argAdapters;
-    }
-
-    private static List<JsonRepresentation> argListFor(final ObjectAction action, final JsonRepresentation arguments) {
-        final List<JsonRepresentation> argList = Lists.newArrayList();
-
-        // ensure that we have no arguments that are not parameters
-        for (final Entry<String, JsonRepresentation> arg : arguments.mapIterable()) {
-            final String argName = arg.getKey();
-            if (action.getParameterById(argName) == null) {
-                String reason = String.format("Argument '%s' found but no such parameter", argName);
-                arguments.mapPut("x-ro-invalidReason", reason);
-                throw RestfulObjectsApplicationException.createWithBody(HttpStatusCode.BAD_REQUEST, arguments, reason);
-            }
-        }
-
-        // ensure that an argument value has been provided for all non-optional
-        // parameters
-        final List<ObjectActionParameter> parameters = action.getParameters();
-        for (final ObjectActionParameter param : parameters) {
-            final String paramId = param.getId();
-            final JsonRepresentation argRepr = arguments.getRepresentation(paramId);
-            if (argRepr == null && !param.isOptional()) {
-                String reason = String.format("No argument found for (mandatory) parameter '%s'", paramId);
-                arguments.mapPut("x-ro-invalidReason", reason);
-                throw RestfulObjectsApplicationException.createWithBody(HttpStatusCode.BAD_REQUEST, arguments, reason);
-            }
-            argList.add(argRepr);
-        }
-        return argList;
-    }
-
-    public static JsonRepresentation readParameterMapAsMap(final Map<String, String[]> parameterMap) {
-        final JsonRepresentation map = JsonRepresentation.newMap();
-        for (final Map.Entry<String, String[]> parameter : parameterMap.entrySet()) {
-            map.mapPut(parameter.getKey(), parameter.getValue()[0]);
-        }
-        return map;
-    }
-
-    public static JsonRepresentation readQueryStringAsMap(final String queryString) {
-        if (queryString == null) {
-            return JsonRepresentation.newMap();
-        }
-        final String queryStringTrimmed = queryString.trim();
-        if (queryStringTrimmed.isEmpty()) {
-            return JsonRepresentation.newMap();
-        }
-        return read(queryStringTrimmed, "query string");
-    }
-
-    public static JsonRepresentation readAsMap(final String body) {
-        if (body == null) {
-            return JsonRepresentation.newMap();
-        }
-        final String bodyTrimmed = body.trim();
-        if (bodyTrimmed.isEmpty()) {
-            return JsonRepresentation.newMap();
-        }
-        return read(bodyTrimmed, "body");
-    }
-
-    private static JsonRepresentation read(final String args, final String argsNature) {
-        try {
-            final JsonRepresentation jsonRepr = JsonMapper.instance().read(args);
-            if (!jsonRepr.isMap()) {
-                throw RestfulObjectsApplicationException.createWithMessage(HttpStatusCode.BAD_REQUEST, "could not read %s as a JSON map", argsNature);
-            }
-            return jsonRepr;
-        } catch (final JsonParseException e) {
-            throw RestfulObjectsApplicationException.createWithCauseAndMessage(HttpStatusCode.BAD_REQUEST, e, "could not parse %s", argsNature);
-        } catch (final JsonMappingException e) {
-            throw RestfulObjectsApplicationException.createWithCauseAndMessage(HttpStatusCode.BAD_REQUEST, e, "could not read %s as JSON", argsNature);
-        } catch (final IOException e) {
-            throw RestfulObjectsApplicationException.createWithCauseAndMessage(HttpStatusCode.BAD_REQUEST, e, "could not parse %s", argsNature);
-        }
-    }
-
-    public static String asStringUtf8(final InputStream body) {
-        try {
-            final byte[] byteArray = ByteStreams.toByteArray(body);
-            return new String(byteArray, Charsets.UTF_8);
-        } catch (final IOException e) {
-            throw RestfulObjectsApplicationException.createWithCauseAndMessage(HttpStatusCode.BAD_REQUEST, e, "could not read body");
-        }
-    }
-
-    // //////////////////////////////////////////////////////////////
-    // misc
-    // //////////////////////////////////////////////////////////////
-
-    private static String resourceFor(final ObjectSpecification objectSpec) {
-        // TODO: should return a string in the form
-        // http://localhost:8080/types/xxx
-        return objectSpec.getFullIdentifier();
+    private <T> T lookupService(Class<T> serviceType) {
+        return getPersistenceSession().getServiceOrNull(serviceType);
     }
 
 }
+

http://git-wip-us.apache.org/repos/asf/isis/blob/bfece661/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/DomainServiceResourceServerside.java
----------------------------------------------------------------------
diff --git a/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/DomainServiceResourceServerside.java b/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/DomainServiceResourceServerside.java
index 40a16f3..d0ecc93 100644
--- a/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/DomainServiceResourceServerside.java
+++ b/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/DomainServiceResourceServerside.java
@@ -50,7 +50,7 @@ public class DomainServiceResourceServerside extends ResourceAbstract implements
             .includesSelf()
             .with(serviceAdapters);
 
-        return responseOfOk(renderer, Caching.ONE_DAY).build();
+        return Responses.ofOk(renderer, Caching.ONE_DAY).build();
     }
 
     @Override
@@ -86,7 +86,7 @@ public class DomainServiceResourceServerside extends ResourceAbstract implements
             .with(serviceAdapter)
             .includesSelf();
 
-        return responseOfOk(renderer, Caching.ONE_DAY).build();
+        return Responses.ofOk(renderer, Caching.ONE_DAY).build();
     }
 
     @Override
@@ -119,7 +119,7 @@ public class DomainServiceResourceServerside extends ResourceAbstract implements
         final ObjectAdapter serviceAdapter = getServiceAdapter(serviceId);
         final DomainResourceHelper helper = new DomainResourceHelper(getResourceContext(), serviceAdapter).using(new DomainServiceLinkTo());
 
-        return helper.actionPrompt(actionId, getResourceContext().getWhere());
+        return helper.actionPrompt(actionId);
     }
 
     @Override
@@ -160,7 +160,7 @@ public class DomainServiceResourceServerside extends ResourceAbstract implements
         final ObjectAdapter serviceAdapter = getServiceAdapter(serviceId);
         final DomainResourceHelper helper = new DomainResourceHelper(getResourceContext(), serviceAdapter).using(new DomainServiceLinkTo());
 
-        return helper.invokeActionQueryOnly(actionId, arguments, getResourceContext().getWhere());
+        return helper.invokeActionQueryOnly(actionId, arguments);
     }
 
 
@@ -181,7 +181,7 @@ public class DomainServiceResourceServerside extends ResourceAbstract implements
         final ObjectAdapter serviceAdapter = getServiceAdapter(serviceId);
         final DomainResourceHelper helper = new DomainResourceHelper(getResourceContext(), serviceAdapter).using(new DomainServiceLinkTo());
 
-        return helper.invokeActionIdempotent(actionId, arguments, getResourceContext().getWhere());
+        return helper.invokeActionIdempotent(actionId, arguments);
     }
 
 
@@ -199,7 +199,7 @@ public class DomainServiceResourceServerside extends ResourceAbstract implements
         final ObjectAdapter serviceAdapter = getServiceAdapter(serviceId);
         final DomainResourceHelper helper = new DomainResourceHelper(getResourceContext(), serviceAdapter).using(new DomainServiceLinkTo());
 
-        return helper.invokeAction(actionId, arguments, getResourceContext().getWhere());
+        return helper.invokeAction(actionId, arguments);
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/isis/blob/bfece661/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/DomainTypeResourceServerside.java
----------------------------------------------------------------------
diff --git a/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/DomainTypeResourceServerside.java b/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/DomainTypeResourceServerside.java
index 6c2c48e..46700c3 100644
--- a/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/DomainTypeResourceServerside.java
+++ b/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/DomainTypeResourceServerside.java
@@ -81,7 +81,7 @@ public class DomainTypeResourceServerside extends ResourceAbstract implements Do
         final TypeListReprRenderer renderer = new TypeListReprRenderer(getResourceContext(), null, JsonRepresentation.newMap());
         renderer.with(allSpecifications).includesSelf();
 
-        return responseOfOk(renderer, Caching.ONE_DAY).build();
+        return Responses.ofOk(renderer, Caching.ONE_DAY).build();
     }
 
     @Override
@@ -97,7 +97,7 @@ public class DomainTypeResourceServerside extends ResourceAbstract implements Do
         final DomainTypeReprRenderer renderer = new DomainTypeReprRenderer(getResourceContext(), null, JsonRepresentation.newMap());
         renderer.with(objectSpec).includesSelf();
 
-        return responseOfOk(renderer, Caching.ONE_DAY).build();
+        return Responses.ofOk(renderer, Caching.ONE_DAY).build();
     }
 
     @Override
@@ -122,7 +122,7 @@ public class DomainTypeResourceServerside extends ResourceAbstract implements Do
         final PropertyDescriptionReprRenderer renderer = new PropertyDescriptionReprRenderer(getResourceContext(), null, JsonRepresentation.newMap());
         renderer.with(new ParentSpecAndProperty(parentSpec, property)).includesSelf();
 
-        return responseOfOk(renderer, Caching.ONE_DAY).build();
+        return Responses.ofOk(renderer, Caching.ONE_DAY).build();
     }
 
     @Override
@@ -147,7 +147,7 @@ public class DomainTypeResourceServerside extends ResourceAbstract implements Do
         final CollectionDescriptionReprRenderer renderer = new CollectionDescriptionReprRenderer(getResourceContext(), null, JsonRepresentation.newMap());
         renderer.with(new ParentSpecAndCollection(parentSpec, collection)).includesSelf();
 
-        return responseOfOk(renderer, Caching.ONE_DAY).build();
+        return Responses.ofOk(renderer, Caching.ONE_DAY).build();
     }
 
     @Override
@@ -172,7 +172,7 @@ public class DomainTypeResourceServerside extends ResourceAbstract implements Do
         final ActionDescriptionReprRenderer renderer = new ActionDescriptionReprRenderer(getResourceContext(), null, JsonRepresentation.newMap());
         renderer.with(new ParentSpecAndAction(parentSpec, action)).includesSelf();
 
-        return responseOfOk(renderer, Caching.ONE_DAY).build();
+        return Responses.ofOk(renderer, Caching.ONE_DAY).build();
     }
 
     @Override
@@ -199,7 +199,7 @@ public class DomainTypeResourceServerside extends ResourceAbstract implements Do
         final ActionParameterDescriptionReprRenderer renderer = new ActionParameterDescriptionReprRenderer(getResourceContext(), null, JsonRepresentation.newMap());
         renderer.with(new ParentSpecAndActionParam(parentSpec, actionParam)).includesSelf();
 
-        return responseOfOk(renderer, Caching.ONE_DAY).build();
+        return Responses.ofOk(renderer, Caching.ONE_DAY).build();
     }
 
     // //////////////////////////////////////////////////////////
@@ -232,7 +232,7 @@ public class DomainTypeResourceServerside extends ResourceAbstract implements Do
         final boolean value = domainTypeSpec.isOfType(supertypeSpec);
         renderer.with(domainTypeSpec).withSelf(selfLink).withValue(value);
 
-        return responseOfOk(renderer, Caching.ONE_DAY).build();
+        return Responses.ofOk(renderer, Caching.ONE_DAY).build();
     }
 
 
@@ -263,7 +263,7 @@ public class DomainTypeResourceServerside extends ResourceAbstract implements Do
         final boolean value = subtypeSpec.isOfType(domainTypeSpec);
         renderer.with(domainTypeSpec).withSelf(selfLink).withValue(value);
 
-        return responseOfOk(renderer, Caching.ONE_DAY).build();
+        return Responses.ofOk(renderer, Caching.ONE_DAY).build();
     }
 
     private static String domainTypeFor(
@@ -284,7 +284,7 @@ public class DomainTypeResourceServerside extends ResourceAbstract implements Do
     }
 
     private static String linkFromFormalArgs(final String argumentsAsQueryString, final String paramName) {
-        final JsonRepresentation arguments = DomainResourceHelper.readQueryStringAsMap(argumentsAsQueryString);
+        final JsonRepresentation arguments = Util.readQueryStringAsMap(argumentsAsQueryString);
         if (!arguments.isLink(paramName)) {
             throw RestfulObjectsApplicationException.createWithMessage(HttpStatusCode.BAD_REQUEST, "Args should contain a link '%s'", paramName);
         }

http://git-wip-us.apache.org/repos/asf/isis/blob/bfece661/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/HomePageResourceServerside.java
----------------------------------------------------------------------
diff --git a/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/HomePageResourceServerside.java b/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/HomePageResourceServerside.java
index b340730..72fef2b 100644
--- a/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/HomePageResourceServerside.java
+++ b/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/HomePageResourceServerside.java
@@ -43,7 +43,7 @@ public class HomePageResourceServerside extends ResourceAbstract implements Home
         final HomePageReprRenderer renderer = new HomePageReprRenderer(getResourceContext(), null, JsonRepresentation.newMap());
         renderer.includesSelf();
 
-        return responseOfOk(renderer, Caching.ONE_DAY).build();
+        return Responses.ofOk(renderer, Caching.ONE_DAY).build();
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/isis/blob/bfece661/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/JsonParserHelper.java
----------------------------------------------------------------------
diff --git a/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/JsonParserHelper.java b/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/JsonParserHelper.java
new file mode 100644
index 0000000..97cce01
--- /dev/null
+++ b/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/JsonParserHelper.java
@@ -0,0 +1,141 @@
+/**
+ *  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.isis.viewer.restfulobjects.server.resources;
+
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation;
+import org.apache.isis.viewer.restfulobjects.applib.client.RestfulResponse;
+import org.apache.isis.viewer.restfulobjects.rendering.domainobjects.JsonValueEncoder;
+import org.apache.isis.viewer.restfulobjects.server.ResourceContext;
+import org.apache.isis.viewer.restfulobjects.server.RestfulObjectsApplicationException;
+import org.apache.isis.viewer.restfulobjects.server.util.OidUtils;
+import org.apache.isis.viewer.restfulobjects.server.util.UrlParserUtils;
+
+/**
+ * Utility class that encapsulates the logic for parsing some content (JSON, or a simple string that is JSON)
+ * into an{@link org.apache.isis.core.metamodel.adapter.ObjectAdapter} of a specified
+ * {@link org.apache.isis.core.metamodel.spec.ObjectSpecification type}.
+ */
+public class JsonParserHelper {
+
+    static enum Intent {
+        ACCESS, MUTATE;
+
+        public boolean isMutate() {
+            return this == MUTATE;
+        }
+    }
+
+    private final ResourceContext resourceContext;
+    private final ObjectSpecification objectSpec;
+
+    public JsonParserHelper(ResourceContext resourceContext, ObjectSpecification objectSpecification) {
+        this.objectSpec = objectSpecification;
+        this.resourceContext = resourceContext;
+    }
+
+
+    /**
+     * @param bodyAsString
+     *            - as per {@link Util#asStringUtf8(java.io.InputStream)}
+     * @return
+     */
+    ObjectAdapter parseAsMapWithSingleValue(final String bodyAsString) {
+        final JsonRepresentation arguments = Util.readAsMap(bodyAsString);
+        return parseAsMapWithSingleValue(arguments);
+    }
+
+    ObjectAdapter parseAsMapWithSingleValue(final JsonRepresentation arguments) {
+        final JsonRepresentation representation = arguments.getRepresentation("value");
+        if (arguments.size() != 1 || representation == null) {
+            throw RestfulObjectsApplicationException.createWithMessage(RestfulResponse.HttpStatusCode.BAD_REQUEST, "Body should be a map with a single key 'value' whose value represents an instance of type '%s'", Util.resourceFor(objectSpec));
+        }
+
+        return objectAdapterFor(arguments);
+    }
+
+    /**
+     *
+     * @param argRepr
+     *            - expected to be either a String or a Map (ie from within a
+     *            List, built by parsing a JSON structure).
+     */
+    public ObjectAdapter objectAdapterFor(final JsonRepresentation argRepr) {
+
+        if (argRepr == null) {
+            return null;
+        }
+
+        if(!argRepr.mapHas("value")) {
+            String reason = "No 'value' key";
+            argRepr.mapPut("invalidReason", reason);
+            throw new IllegalArgumentException(reason);
+        }
+
+        if (objectSpec == null) {
+            String reason = "ObjectSpec is null, cannot validate";
+            argRepr.mapPut("invalidReason", reason);
+            throw new IllegalArgumentException(reason);
+        }
+
+        final JsonRepresentation argValueRepr = argRepr.getRepresentation("value");
+
+        // value (encodable)
+        if (objectSpec.isEncodeable()) {
+            try {
+                return JsonValueEncoder.asAdapter(objectSpec, argValueRepr, null);
+            }catch(IllegalArgumentException ex) {
+                argRepr.mapPut("invalidReason", ex.getMessage());
+                throw ex;
+            }catch(Exception ex) {
+                StringBuilder buf = new StringBuilder("Failed to parse representation ");
+                try {
+                    final String reprStr = argRepr.getString("value");
+                    buf.append("'").append(reprStr).append("' ");
+                } catch(Exception ex2) {
+                }
+                buf.append("as value of type '").append(objectSpec.getShortIdentifier()).append("'");
+                String reason = buf.toString();
+                argRepr.mapPut("invalidReason", reason);
+                throw new IllegalArgumentException(reason);
+            }
+        }
+
+        // reference
+        if (!argValueRepr.isLink()) {
+            final String reason = "Expected a link (because this object's type is not a value) but found no 'href'";
+            argRepr.mapPut("invalidReason", reason);
+            throw new IllegalArgumentException(reason);
+        }
+        final String oidFromHref = UrlParserUtils.encodedOidFromLink(argValueRepr);
+        if (oidFromHref == null) {
+            final String reason = "Could not parse 'href' to identify the object's OID";
+            argRepr.mapPut("invalidReason", reason);
+            throw new IllegalArgumentException(reason);
+        }
+
+        final ObjectAdapter objectAdapter = OidUtils.getObjectAdapterElseNull(resourceContext, oidFromHref);
+        if (objectAdapter == null) {
+            final String reason = "'href' does not reference a known entity";
+            argRepr.mapPut("invalidReason", reason);
+            throw new IllegalArgumentException(reason);
+        }
+        return objectAdapter;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/bfece661/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/ObjectActionArgHelper.java
----------------------------------------------------------------------
diff --git a/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/ObjectActionArgHelper.java b/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/ObjectActionArgHelper.java
new file mode 100644
index 0000000..1d807a5
--- /dev/null
+++ b/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/ObjectActionArgHelper.java
@@ -0,0 +1,122 @@
+/**
+ *  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.isis.viewer.restfulobjects.server.resources;
+
+import java.util.List;
+import java.util.Map;
+import com.google.common.collect.Lists;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.consent.Consent;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
+import org.apache.isis.core.metamodel.spec.feature.ObjectActionParameter;
+import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation;
+import org.apache.isis.viewer.restfulobjects.applib.client.RestfulResponse;
+import org.apache.isis.viewer.restfulobjects.server.ResourceContext;
+import org.apache.isis.viewer.restfulobjects.server.RestfulObjectsApplicationException;
+
+/**
+ * Utility class that encapsulates the logic for parsing arguments to be invoked by an 
+ * {@link org.apache.isis.core.metamodel.spec.feature.ObjectAction}.
+ */
+public class ObjectActionArgHelper {
+
+    private final ResourceContext resourceContext;
+    private final ObjectAdapter objectAdapter;
+    private final ObjectAction action;
+
+    public ObjectActionArgHelper(
+            final ResourceContext resourceContext,
+            final ObjectAdapter objectAdapter,
+            final ObjectAction action) {
+        this.resourceContext = resourceContext;
+        this.objectAdapter = objectAdapter;
+        this.action = action;
+    }
+
+    public List<ObjectAdapter> parseAndValidateArguments(final JsonRepresentation arguments) {
+        final List<JsonRepresentation> argList = argListFor(action, arguments);
+
+        final List<ObjectAdapter> argAdapters = Lists.newArrayList();
+        final List<ObjectActionParameter> parameters = action.getParameters();
+        boolean valid = true;
+        for (int i = 0; i < argList.size(); i++) {
+            final JsonRepresentation argRepr = argList.get(i);
+            final ObjectSpecification paramSpec = parameters.get(i).getSpecification();
+            try {
+                final ObjectAdapter argAdapter = new JsonParserHelper(resourceContext, paramSpec).objectAdapterFor(argRepr);
+                argAdapters.add(argAdapter);
+
+                // validate individual arg
+                final ObjectActionParameter parameter = parameters.get(i);
+                final Object argPojo = argAdapter!=null?argAdapter.getObject():null;
+                final String reasonNotValid = parameter.isValid(objectAdapter, argPojo, null);
+                if (reasonNotValid != null) {
+                    argRepr.mapPut("invalidReason", reasonNotValid);
+                    valid = false;
+                }
+            } catch (final IllegalArgumentException e) {
+                argAdapters.add(null);
+                valid = false;
+            }
+        }
+
+        // validate all args
+        final ObjectAdapter[] argArray = argAdapters.toArray(new ObjectAdapter[0]);
+        final Consent consent = action.isProposedArgumentSetValid(objectAdapter, argArray);
+        if (consent.isVetoed()) {
+            arguments.mapPut("x-ro-invalidReason", consent.getReason());
+            valid = false;
+        }
+
+        if(!valid) {
+            throw RestfulObjectsApplicationException.createWithBody(RestfulResponse.HttpStatusCode.VALIDATION_FAILED, arguments, "Validation failed, see body for details");
+        }
+
+        return argAdapters;
+    }
+
+    private static List<JsonRepresentation> argListFor(final ObjectAction action, final JsonRepresentation arguments) {
+        final List<JsonRepresentation> argList = Lists.newArrayList();
+
+        // ensure that we have no arguments that are not parameters
+        for (final Map.Entry<String, JsonRepresentation> arg : arguments.mapIterable()) {
+            final String argName = arg.getKey();
+            if (action.getParameterById(argName) == null) {
+                String reason = String.format("Argument '%s' found but no such parameter", argName);
+                arguments.mapPut("x-ro-invalidReason", reason);
+                throw RestfulObjectsApplicationException.createWithBody(RestfulResponse.HttpStatusCode.BAD_REQUEST, arguments, reason);
+            }
+        }
+
+        // ensure that an argument value has been provided for all non-optional
+        // parameters
+        final List<ObjectActionParameter> parameters = action.getParameters();
+        for (final ObjectActionParameter param : parameters) {
+            final String paramId = param.getId();
+            final JsonRepresentation argRepr = arguments.getRepresentation(paramId);
+            if (argRepr == null && !param.isOptional()) {
+                String reason = String.format("No argument found for (mandatory) parameter '%s'", paramId);
+                arguments.mapPut("x-ro-invalidReason", reason);
+                throw RestfulObjectsApplicationException.createWithBody(RestfulResponse.HttpStatusCode.BAD_REQUEST, arguments, reason);
+            }
+            argList.add(argRepr);
+        }
+        return argList;
+    }
+
+}