ISIS-233: argument validation when invoking actions

* mostly 'invalidReason' and 'x-ro-invalidReason'
* also fix for ObjectActionParameter.isValid, which only worked for strings;
  now works for other types too (in backward compatible fashion).


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

Branch: refs/heads/master
Commit: b4dd583ad354fb52bf27bd71499aaed7120631b1
Parents: f980329
Author: Dan Haywood <[email protected]>
Authored: Wed Mar 13 07:33:55 2013 +0000
Committer: Dan Haywood <[email protected]>
Committed: Sat Apr 27 19:01:18 2013 +0100

----------------------------------------------------------------------
 .../server/resources/DomainResourceHelper.java     |   82 +++++-----
 ...DomainServiceTest_req_safe_noarg_resp_list.java |   22 ++-
 .../DomainServiceTest_req_safe_refarg_bad.java     |   57 ++-----
 ...ainServiceTest_req_safe_refarg_resp_scalar.java |   58 ++++---
 ...eq_safe_simplearg_fail_all_args_validation.java |  133 ++++++++++++++
 ...e_simplearg_fail_individual_arg_validation.java |  134 +++++++++++++++
 ...inServiceTest_req_safe_simplearg_resp_list.java |   21 ++-
 .../specimpl/ObjectActionParameterParseable.java   |   34 +++--
 .../tck/dom/actions/ActionsEntityRepository.java   |   26 +++-
 9 files changed, 430 insertions(+), 137 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/isis/blob/b4dd583a/component/viewer/restfulobjects/server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/DomainResourceHelper.java
----------------------------------------------------------------------
diff --git 
a/component/viewer/restfulobjects/server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/DomainResourceHelper.java
 
b/component/viewer/restfulobjects/server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/DomainResourceHelper.java
index 87cd55a..67d546d 100644
--- 
a/component/viewer/restfulobjects/server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/DomainResourceHelper.java
+++ 
b/component/viewer/restfulobjects/server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/DomainResourceHelper.java
@@ -273,36 +273,11 @@ public final class DomainResourceHelper {
 
     private Response invokeActionUsingAdapters(final ObjectAction action, 
final JsonRepresentation arguments) {
 
-        final List<ObjectAdapter> argAdapters = parseArguments(action, 
arguments);
-
-        // validate individual args
-        final List<ObjectActionParameter> parameters = action.getParameters();
-        for (int i = 0; i < parameters.size(); i++) {
-            final ObjectActionParameter parameter = parameters.get(i);
-            final ObjectAdapter argAdapter = argAdapters.get(i);
-            if (argAdapter == null) {
-                // can only happen if this is an optional parameter; nothing to
-                // do
-                continue;
-            }
-            if (argAdapter.getSpecification().containsFacet(ValueFacet.class)) 
{
-                final Object arg = argAdapter.getObject();
-                final String reasonNotValid = parameter.isValid(objectAdapter, 
arg, null);
-                if (reasonNotValid != null) {
-                    throw 
RestfulObjectsApplicationException.createWithMessage(HttpStatusCode.NOT_ACCEPTABLE,
 reasonNotValid);
-                }
-            }
-        }
-
-        // validate all args
-        final ObjectAdapter[] argArray = argAdapters.toArray(new 
ObjectAdapter[0]);
-        final Consent consent = 
action.isProposedArgumentSetValid(objectAdapter, argArray);
-        if (consent.isVetoed()) {
-            throw 
RestfulObjectsApplicationException.createWithMessage(HttpStatusCode.NOT_ACCEPTABLE,
 consent.getReason());
-        }
+        final List<ObjectAdapter> argAdapters = 
parseAndValidateArguments(action, arguments);
 
         // invoke
-        final ObjectAdapter returnedAdapter = action.execute(objectAdapter, 
argArray);
+        final ObjectAdapter[] argArray2 = argAdapters.toArray(new 
ObjectAdapter[0]);
+        final ObjectAdapter returnedAdapter = action.execute(objectAdapter, 
argArray2);
 
         // response (void)
         final ActionResultReprRenderer renderer = new 
ActionResultReprRenderer(resourceContext, null, JsonRepresentation.newMap());
@@ -465,35 +440,62 @@ public final class DomainResourceHelper {
         return objectAdapterFor(resourceContext, objectSpec, representation);
     }
 
-    private List<ObjectAdapter> parseArguments(final ObjectAction action, 
final JsonRepresentation arguments) {
-        return parseArguments(resourceContext, action, arguments);
-    }
-
-    public static List<ObjectAdapter> parseArguments(final RendererContext 
resourceContext, final ObjectAction action, final JsonRepresentation 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();
         final StringBuilder invalidReasonBuf = new StringBuilder();
         for (int i = 0; i < argList.size(); i++) {
-            final JsonRepresentation arg = argList.get(i);
+            final JsonRepresentation argRepr = argList.get(i);
             final ObjectSpecification paramSpec = 
parameters.get(i).getSpecification();
             try {
-                final ObjectAdapter objectAdapter = 
objectAdapterFor(resourceContext, paramSpec, arg);
-                argAdapters.add(objectAdapter);
-            } catch (final IllegalArgumentException e) {
-                if(invalidReasonBuf.length()>0) {
-                    invalidReasonBuf.append("; ");
+                final ObjectAdapter argAdapter = 
objectAdapterFor(resourceContext, paramSpec, argRepr);
+                argAdapters.add(argAdapter);
+
+                // validate individual arg
+                final ObjectActionParameter parameter = parameters.get(i);
+                if (argAdapter == null) {
+                    // can only happen if this is an optional parameter; 
nothing to
+                    // do
+                    continue;
                 }
-                invalidReasonBuf.append(e.getMessage());
+                if 
(argAdapter.getSpecification().containsFacet(ValueFacet.class)) {
+                    final Object argPojo = argAdapter.getObject();
+                    final String reasonNotValid = 
parameter.isValid(objectAdapter, argPojo, null);
+                    if (reasonNotValid != null) {
+                        argRepr.mapPut("invalidReason", reasonNotValid);
+                        appendReasonTo(invalidReasonBuf, "Validation failed, 
see body for details");
+                    }
+                }
+            } catch (final IllegalArgumentException e) {
+                String reason = e.getMessage();
+                appendReasonTo(invalidReasonBuf, reason);
             }
         }
+        
+        // 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());
+            appendReasonTo(invalidReasonBuf, "Validation failed, see body for 
details");
+        }
+
         if(invalidReasonBuf.length()>0) {
             throw 
RestfulObjectsApplicationException.createWithBody(HttpStatusCode.VALIDATION_FAILED,
 arguments, invalidReasonBuf.toString());
         }
+        
         return argAdapters;
     }
 
+    private void appendReasonTo(final StringBuilder buf, String reason) {
+        if(buf.length()>0) {
+            buf.append("; ");
+        }
+        buf.append(reason);
+    }
+
     private static List<JsonRepresentation> argListFor(final ObjectAction 
action, final JsonRepresentation arguments) {
         final List<JsonRepresentation> argList = Lists.newArrayList();
 

http://git-wip-us.apache.org/repos/asf/isis/blob/b4dd583a/component/viewer/restfulobjects/tck/src/test/java/org/apache/isis/viewer/restfulobjects/tck/domainservice/serviceId/action/invoke/DomainServiceTest_req_safe_noarg_resp_list.java
----------------------------------------------------------------------
diff --git 
a/component/viewer/restfulobjects/tck/src/test/java/org/apache/isis/viewer/restfulobjects/tck/domainservice/serviceId/action/invoke/DomainServiceTest_req_safe_noarg_resp_list.java
 
b/component/viewer/restfulobjects/tck/src/test/java/org/apache/isis/viewer/restfulobjects/tck/domainservice/serviceId/action/invoke/DomainServiceTest_req_safe_noarg_resp_list.java
index 1470fb4..612f32b 100644
--- 
a/component/viewer/restfulobjects/tck/src/test/java/org/apache/isis/viewer/restfulobjects/tck/domainservice/serviceId/action/invoke/DomainServiceTest_req_safe_noarg_resp_list.java
+++ 
b/component/viewer/restfulobjects/tck/src/test/java/org/apache/isis/viewer/restfulobjects/tck/domainservice/serviceId/action/invoke/DomainServiceTest_req_safe_noarg_resp_list.java
@@ -18,18 +18,23 @@
  */
 package 
org.apache.isis.viewer.restfulobjects.tck.domainservice.serviceId.action.invoke;
 
+import static 
org.apache.isis.viewer.restfulobjects.tck.RestfulMatchers.hasProfile;
 import static org.apache.isis.viewer.restfulobjects.tck.RestfulMatchers.isLink;
 import static org.hamcrest.CoreMatchers.is;
 import static org.junit.Assert.assertThat;
 
+import java.io.IOException;
+
 import javax.ws.rs.core.Response;
 
 import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation;
 import org.apache.isis.viewer.restfulobjects.applib.LinkRepresentation;
 import org.apache.isis.viewer.restfulobjects.applib.Rel;
 import org.apache.isis.viewer.restfulobjects.applib.RestfulHttpMethod;
+import org.apache.isis.viewer.restfulobjects.applib.RestfulMediaType;
 import org.apache.isis.viewer.restfulobjects.applib.client.RestfulClient;
 import org.apache.isis.viewer.restfulobjects.applib.client.RestfulResponse;
+import 
org.apache.isis.viewer.restfulobjects.applib.client.RestfulResponse.Header;
 import 
org.apache.isis.viewer.restfulobjects.applib.domainobjects.ActionResultRepresentation;
 import 
org.apache.isis.viewer.restfulobjects.applib.domainobjects.ActionResultRepresentation.ResultType;
 import 
org.apache.isis.viewer.restfulobjects.applib.domainobjects.DomainServiceResource;
@@ -37,6 +42,8 @@ import 
org.apache.isis.viewer.restfulobjects.applib.domainobjects.ListRepresenta
 import 
org.apache.isis.viewer.restfulobjects.applib.domainobjects.ObjectActionRepresentation;
 import org.apache.isis.viewer.restfulobjects.tck.IsisWebServerRule;
 import org.apache.isis.viewer.restfulobjects.tck.Util;
+import org.codehaus.jackson.JsonParseException;
+import org.codehaus.jackson.map.JsonMappingException;
 import org.hamcrest.Matchers;
 import org.junit.Before;
 import org.junit.Rule;
@@ -78,6 +85,12 @@ public class DomainServiceTest_req_safe_noarg_resp_list {
         final RestfulResponse<ActionResultRepresentation> restfulResponse = 
client.followT(invokeLink);
         
         // then
+        then(restfulResponse);
+    }
+
+    private static void then(final RestfulResponse<ActionResultRepresentation> 
restfulResponse) throws JsonParseException, JsonMappingException, IOException {
+        
+        assertThat(restfulResponse.getHeader(Header.CONTENT_TYPE), 
hasProfile(RestfulMediaType.APPLICATION_JSON_ACTION_RESULT));
         final ActionResultRepresentation actionResultRepr = 
restfulResponse.getEntity();
         
         assertThat(actionResultRepr.getResultType(), is(ResultType.LIST));
@@ -93,14 +106,7 @@ public class DomainServiceTest_req_safe_noarg_resp_list {
         Response response = 
serviceResource.invokeActionQueryOnly("ActionsEntities", "list", null);
         RestfulResponse<ActionResultRepresentation> restfulResponse = 
RestfulResponse.ofT(response);
         
-        // then
-        final ActionResultRepresentation actionResultRepr = 
restfulResponse.getEntity();
-        
-        assertThat(actionResultRepr.getResultType(), is(ResultType.LIST));
-        
-        final ListRepresentation listRepr = 
actionResultRepr.getResult().as(ListRepresentation.class);
-
-        assertThat(listRepr.getValue().size(), is(5));
+        then(restfulResponse);
     }
 
 }

http://git-wip-us.apache.org/repos/asf/isis/blob/b4dd583a/component/viewer/restfulobjects/tck/src/test/java/org/apache/isis/viewer/restfulobjects/tck/domainservice/serviceId/action/invoke/DomainServiceTest_req_safe_refarg_bad.java
----------------------------------------------------------------------
diff --git 
a/component/viewer/restfulobjects/tck/src/test/java/org/apache/isis/viewer/restfulobjects/tck/domainservice/serviceId/action/invoke/DomainServiceTest_req_safe_refarg_bad.java
 
b/component/viewer/restfulobjects/tck/src/test/java/org/apache/isis/viewer/restfulobjects/tck/domainservice/serviceId/action/invoke/DomainServiceTest_req_safe_refarg_bad.java
index 3bcb277..d31ca0e 100644
--- 
a/component/viewer/restfulobjects/tck/src/test/java/org/apache/isis/viewer/restfulobjects/tck/domainservice/serviceId/action/invoke/DomainServiceTest_req_safe_refarg_bad.java
+++ 
b/component/viewer/restfulobjects/tck/src/test/java/org/apache/isis/viewer/restfulobjects/tck/domainservice/serviceId/action/invoke/DomainServiceTest_req_safe_refarg_bad.java
@@ -20,31 +20,29 @@ package 
org.apache.isis.viewer.restfulobjects.tck.domainservice.serviceId.action
 
 import static 
org.apache.isis.viewer.restfulobjects.tck.RestfulMatchers.hasProfile;
 import static 
org.apache.isis.viewer.restfulobjects.tck.RestfulMatchers.hasStatus;
-import static org.hamcrest.CoreMatchers.*;
 import static org.hamcrest.CoreMatchers.is;
 import static org.junit.Assert.assertThat;
 
+import java.io.IOException;
+
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 
 import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation;
 import org.apache.isis.viewer.restfulobjects.applib.LinkRepresentation;
-import org.apache.isis.viewer.restfulobjects.applib.RestfulMediaType;
 import org.apache.isis.viewer.restfulobjects.applib.client.RestfulClient;
 import org.apache.isis.viewer.restfulobjects.applib.client.RestfulResponse;
 import 
org.apache.isis.viewer.restfulobjects.applib.client.RestfulResponse.Header;
 import 
org.apache.isis.viewer.restfulobjects.applib.client.RestfulResponse.HttpStatusCode;
 import 
org.apache.isis.viewer.restfulobjects.applib.domainobjects.ActionResultRepresentation;
-import 
org.apache.isis.viewer.restfulobjects.applib.domainobjects.ActionResultRepresentation.ResultType;
 import 
org.apache.isis.viewer.restfulobjects.applib.domainobjects.DomainServiceResource;
-import 
org.apache.isis.viewer.restfulobjects.applib.domainobjects.ListRepresentation;
 import 
org.apache.isis.viewer.restfulobjects.applib.domainobjects.ObjectActionRepresentation;
-import 
org.apache.isis.viewer.restfulobjects.applib.domainobjects.ScalarValueRepresentation;
 import org.apache.isis.viewer.restfulobjects.applib.util.UrlEncodingUtils;
 import org.apache.isis.viewer.restfulobjects.tck.IsisWebServerRule;
 import org.apache.isis.viewer.restfulobjects.tck.Util;
+import org.codehaus.jackson.JsonParseException;
+import org.codehaus.jackson.map.JsonMappingException;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 
@@ -68,7 +66,7 @@ public class DomainServiceTest_req_safe_refarg_bad {
 
         // given a reference to a non-existent entity
         LinkRepresentation nonExistentEntityLink = new LinkRepresentation()
-        .withHref("http://localhost:39393/objects/NONEXISTENT/123";);
+            .withHref("http://localhost:39393/objects/NONEXISTENT/123";);
         
         // and given a representation of the 'contains' action accepting a 
entity href
         final JsonRepresentation containsAction = Util.givenAction(client, 
"ActionsEntities", "contains");
@@ -84,6 +82,10 @@ public class DomainServiceTest_req_safe_refarg_bad {
         
         RestfulResponse<ActionResultRepresentation> restfulResponse = 
client.followT(invokeLink, args);
 
+        then(args, restfulResponse);
+    }
+
+    private static void then(final JsonRepresentation args, 
RestfulResponse<ActionResultRepresentation> restfulResponse) throws 
JsonParseException, JsonMappingException, IOException {
         // then the response is an error
         assertThat(restfulResponse, 
hasStatus(HttpStatusCode.VALIDATION_FAILED));
 
@@ -95,53 +97,28 @@ public class DomainServiceTest_req_safe_refarg_bad {
         RestfulResponse<JsonRepresentation> restfulResponseOfError = 
restfulResponse.wraps(JsonRepresentation.class);
         JsonRepresentation repr = restfulResponseOfError.getEntity();
         
+        assertThat(repr.getString("searchFor.value.href"), 
is(args.getString("searchFor.value.href")));
         assertThat(repr.getString("searchFor.invalidReason"), is("'href' does 
not reference a known entity"));
     }
 
-    @Ignore("still to update according to above test...")
+
     @Test
     public void usingResourceProxy() throws Exception {
 
-        // given a reference to the first entity
-        final ListRepresentation subListRepr = givenSublistActionInvoked(0, 1);
-        LinkRepresentation firstEntityLink = 
subListRepr.getValue().arrayGet(0).asLink();
+        // given a reference to a non-existent entity
+        LinkRepresentation nonExistentEntityLink = new LinkRepresentation()
+            .withHref("http://localhost:39393/objects/NONEXISTENT/123";);
 
-        // when query the 'contains' action passing in the entity 
-        // (for a range where the entity is contained in the range)
+        // when query the 'contains' action passing in the reference to the 
non-existent entity 
         JsonRepresentation args = JsonRepresentation.newMap();
-        args.mapPut("searchFor.value", firstEntityLink);
+        args.mapPut("searchFor.value", nonExistentEntityLink);
         args.mapPut("from.value", 0);
         args.mapPut("to.value", 3);
         Response response = 
serviceResource.invokeActionQueryOnly("ActionsEntities", "contains", 
UrlEncodingUtils.urlEncode(args));
         RestfulResponse<ActionResultRepresentation> restfulResponse = 
RestfulResponse.ofT(response);
         
-        // then
-        final ActionResultRepresentation actionResultRepr = 
restfulResponse.getEntity();
-        JsonRepresentation resultRepr = actionResultRepr.getResult();
-        
-        assertThat(actionResultRepr.getResultType(), 
is(ResultType.SCALAR_VALUE));
-        ScalarValueRepresentation scalarValueRepr = 
resultRepr.as(ScalarValueRepresentation.class);
-
-        assertThat(scalarValueRepr.getValue().asBoolean(), is(true));
+        then(args, restfulResponse);
     }
 
     
-
-    private ListRepresentation givenSublistActionInvoked(int from, int to) 
throws Exception {
-        final JsonRepresentation givenSubListAction = Util.givenAction(client, 
"ActionsEntities", "subList");
-        final ObjectActionRepresentation actionRepr = 
givenSubListAction.as(ObjectActionRepresentation.class);
-        
-        final LinkRepresentation invokeLink = actionRepr.getInvoke();
-        final JsonRepresentation args = invokeLink.getArguments();
-        
-        // when
-        args.mapPut("from", from);
-        args.mapPut("to", to);
-        
-        final RestfulResponse<ActionResultRepresentation> restfulResponse = 
client.followT(invokeLink, args);
-        
-        final ActionResultRepresentation actionResultRepr = 
restfulResponse.getEntity();
-        return actionResultRepr.getResult().as(ListRepresentation.class);
-    }
-
 }

http://git-wip-us.apache.org/repos/asf/isis/blob/b4dd583a/component/viewer/restfulobjects/tck/src/test/java/org/apache/isis/viewer/restfulobjects/tck/domainservice/serviceId/action/invoke/DomainServiceTest_req_safe_refarg_resp_scalar.java
----------------------------------------------------------------------
diff --git 
a/component/viewer/restfulobjects/tck/src/test/java/org/apache/isis/viewer/restfulobjects/tck/domainservice/serviceId/action/invoke/DomainServiceTest_req_safe_refarg_resp_scalar.java
 
b/component/viewer/restfulobjects/tck/src/test/java/org/apache/isis/viewer/restfulobjects/tck/domainservice/serviceId/action/invoke/DomainServiceTest_req_safe_refarg_resp_scalar.java
index f7cd2c0..d707510 100644
--- 
a/component/viewer/restfulobjects/tck/src/test/java/org/apache/isis/viewer/restfulobjects/tck/domainservice/serviceId/action/invoke/DomainServiceTest_req_safe_refarg_resp_scalar.java
+++ 
b/component/viewer/restfulobjects/tck/src/test/java/org/apache/isis/viewer/restfulobjects/tck/domainservice/serviceId/action/invoke/DomainServiceTest_req_safe_refarg_resp_scalar.java
@@ -18,19 +18,24 @@
  */
 package 
org.apache.isis.viewer.restfulobjects.tck.domainservice.serviceId.action.invoke;
 
+import static 
org.apache.isis.viewer.restfulobjects.tck.RestfulMatchers.hasProfile;
 import static org.apache.isis.viewer.restfulobjects.tck.RestfulMatchers.isLink;
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.CoreMatchers.nullValue;
 import static org.junit.Assert.assertThat;
 
+import java.io.IOException;
+
 import javax.ws.rs.core.Response;
 
 import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation;
 import org.apache.isis.viewer.restfulobjects.applib.LinkRepresentation;
 import org.apache.isis.viewer.restfulobjects.applib.Rel;
+import org.apache.isis.viewer.restfulobjects.applib.RestfulMediaType;
 import org.apache.isis.viewer.restfulobjects.applib.client.RestfulClient;
 import org.apache.isis.viewer.restfulobjects.applib.client.RestfulResponse;
+import 
org.apache.isis.viewer.restfulobjects.applib.client.RestfulResponse.Header;
 import 
org.apache.isis.viewer.restfulobjects.applib.client.RestfulResponse.HttpStatusCode;
 import 
org.apache.isis.viewer.restfulobjects.applib.domainobjects.ActionResultRepresentation;
 import 
org.apache.isis.viewer.restfulobjects.applib.domainobjects.ActionResultRepresentation.ResultType;
@@ -41,6 +46,8 @@ import 
org.apache.isis.viewer.restfulobjects.applib.domainobjects.ScalarValueRep
 import org.apache.isis.viewer.restfulobjects.applib.util.UrlEncodingUtils;
 import org.apache.isis.viewer.restfulobjects.tck.IsisWebServerRule;
 import org.apache.isis.viewer.restfulobjects.tck.Util;
+import org.codehaus.jackson.JsonParseException;
+import org.codehaus.jackson.map.JsonMappingException;
 import org.hamcrest.Matchers;
 import org.junit.Before;
 import org.junit.Rule;
@@ -86,23 +93,8 @@ public class DomainServiceTest_req_safe_refarg_resp_scalar {
         
         RestfulResponse<ActionResultRepresentation> restfulResponse = 
client.followT(invokeLink, args);
 
-        // then the response is a scalar value of 'true'
-        ActionResultRepresentation actionResultRepr = 
restfulResponse.getEntity();
-        assertThat(actionResultRepr.getResultType(), 
is(ResultType.SCALAR_VALUE));
-        JsonRepresentation resultRepr = actionResultRepr.getResult();
-        assertThat(resultRepr, is(not(nullValue())));
-        
-        ScalarValueRepresentation scalarValueRepr = 
resultRepr.as(ScalarValueRepresentation.class);
-        
-        LinkRepresentation returnTypeLink = 
scalarValueRepr.getLinkWithRel(Rel.RETURN_TYPE);
-        assertThat(returnTypeLink, is(not(nullValue())));
-        assertThat(returnTypeLink, isLink(client)
-                                        .rel(Rel.RETURN_TYPE)
-                                        
.href(Matchers.endsWith(":39393/domain-types/boolean"))
-                                        .returning(HttpStatusCode.OK)
-                                        .build());
-        
-        assertThat(scalarValueRepr.getValue().asBoolean(), is(true));
+        // then 
+        thenResponseIsScalarValueOf(restfulResponse, true);
         
         
         // and when query the 'contains' action for a different range which 
does not
@@ -113,14 +105,11 @@ public class 
DomainServiceTest_req_safe_refarg_resp_scalar {
         
         restfulResponse = client.followT(invokeLink, args);
 
-        // then the response is a scalar value of 'false'
-        actionResultRepr = restfulResponse.getEntity();
-        resultRepr = actionResultRepr.getResult();
-        
-        scalarValueRepr = resultRepr.as(ScalarValueRepresentation.class);
-        assertThat(scalarValueRepr.getValue().asBoolean(), is(false));
+        // then 
+        thenResponseIsScalarValueOf(restfulResponse, false);
     }
 
+
     
     @Test
     public void usingResourceProxy() throws Exception {
@@ -139,16 +128,29 @@ public class 
DomainServiceTest_req_safe_refarg_resp_scalar {
         RestfulResponse<ActionResultRepresentation> restfulResponse = 
RestfulResponse.ofT(response);
         
         // then
-        final ActionResultRepresentation actionResultRepr = 
restfulResponse.getEntity();
+        thenResponseIsScalarValueOf(restfulResponse, true);
+    }
+
+    private void 
thenResponseIsScalarValueOf(RestfulResponse<ActionResultRepresentation> 
restfulResponse, boolean value) throws JsonParseException, 
JsonMappingException, IOException {
+        assertThat(restfulResponse.getHeader(Header.CONTENT_TYPE), 
hasProfile(RestfulMediaType.APPLICATION_JSON_ACTION_RESULT));
+        ActionResultRepresentation actionResultRepr = 
restfulResponse.getEntity();
+        assertThat(actionResultRepr.getResultType(), 
is(ResultType.SCALAR_VALUE));
         JsonRepresentation resultRepr = actionResultRepr.getResult();
+        assertThat(resultRepr, is(not(nullValue())));
         
-        assertThat(actionResultRepr.getResultType(), 
is(ResultType.SCALAR_VALUE));
         ScalarValueRepresentation scalarValueRepr = 
resultRepr.as(ScalarValueRepresentation.class);
-
-        assertThat(scalarValueRepr.getValue().asBoolean(), is(true));
+        
+        LinkRepresentation returnTypeLink = 
scalarValueRepr.getLinkWithRel(Rel.RETURN_TYPE);
+        assertThat(returnTypeLink, is(not(nullValue())));
+        assertThat(returnTypeLink, isLink(client)
+                                        .rel(Rel.RETURN_TYPE)
+                                        
.href(Matchers.endsWith(":39393/domain-types/boolean"))
+                                        .returning(HttpStatusCode.OK)
+                                        .build());
+        
+        assertThat(scalarValueRepr.getValue().asBoolean(), is(value));
     }
 
-    
 
     private ListRepresentation givenSublistActionInvoked(int from, int to) 
throws Exception {
         final JsonRepresentation givenSubListAction = Util.givenAction(client, 
"ActionsEntities", "subList");

http://git-wip-us.apache.org/repos/asf/isis/blob/b4dd583a/component/viewer/restfulobjects/tck/src/test/java/org/apache/isis/viewer/restfulobjects/tck/domainservice/serviceId/action/invoke/DomainServiceTest_req_safe_simplearg_fail_all_args_validation.java
----------------------------------------------------------------------
diff --git 
a/component/viewer/restfulobjects/tck/src/test/java/org/apache/isis/viewer/restfulobjects/tck/domainservice/serviceId/action/invoke/DomainServiceTest_req_safe_simplearg_fail_all_args_validation.java
 
b/component/viewer/restfulobjects/tck/src/test/java/org/apache/isis/viewer/restfulobjects/tck/domainservice/serviceId/action/invoke/DomainServiceTest_req_safe_simplearg_fail_all_args_validation.java
new file mode 100644
index 0000000..93ab7b1
--- /dev/null
+++ 
b/component/viewer/restfulobjects/tck/src/test/java/org/apache/isis/viewer/restfulobjects/tck/domainservice/serviceId/action/invoke/DomainServiceTest_req_safe_simplearg_fail_all_args_validation.java
@@ -0,0 +1,133 @@
+/*
+ *  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.tck.domainservice.serviceId.action.invoke;
+
+import static 
org.apache.isis.viewer.restfulobjects.tck.RestfulMatchers.hasProfile;
+import static 
org.apache.isis.viewer.restfulobjects.tck.RestfulMatchers.hasStatus;
+import static org.apache.isis.viewer.restfulobjects.tck.RestfulMatchers.isLink;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+import java.io.IOException;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation;
+import org.apache.isis.viewer.restfulobjects.applib.LinkRepresentation;
+import org.apache.isis.viewer.restfulobjects.applib.Rel;
+import org.apache.isis.viewer.restfulobjects.applib.RestfulHttpMethod;
+import org.apache.isis.viewer.restfulobjects.applib.RestfulMediaType;
+import org.apache.isis.viewer.restfulobjects.applib.client.RestfulClient;
+import org.apache.isis.viewer.restfulobjects.applib.client.RestfulResponse;
+import 
org.apache.isis.viewer.restfulobjects.applib.client.RestfulResponse.Header;
+import 
org.apache.isis.viewer.restfulobjects.applib.client.RestfulResponse.HttpStatusCode;
+import 
org.apache.isis.viewer.restfulobjects.applib.domainobjects.ActionResultRepresentation;
+import 
org.apache.isis.viewer.restfulobjects.applib.domainobjects.ActionResultRepresentation.ResultType;
+import 
org.apache.isis.viewer.restfulobjects.applib.domainobjects.DomainServiceResource;
+import 
org.apache.isis.viewer.restfulobjects.applib.domainobjects.ListRepresentation;
+import 
org.apache.isis.viewer.restfulobjects.applib.domainobjects.ObjectActionRepresentation;
+import org.apache.isis.viewer.restfulobjects.applib.util.UrlEncodingUtils;
+import org.apache.isis.viewer.restfulobjects.tck.IsisWebServerRule;
+import org.apache.isis.viewer.restfulobjects.tck.RestfulMatchers;
+import org.apache.isis.viewer.restfulobjects.tck.Util;
+import org.codehaus.jackson.JsonParseException;
+import org.codehaus.jackson.map.JsonMappingException;
+import org.hamcrest.Matchers;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class DomainServiceTest_req_safe_simplearg_fail_all_args_validation {
+
+    @Rule
+    public IsisWebServerRule webServerRule = new IsisWebServerRule();
+
+    private RestfulClient client;
+
+    private DomainServiceResource serviceResource;
+
+    @Before
+    public void setUp() throws Exception {
+        client = webServerRule.getClient();
+
+        serviceResource = client.getDomainServiceResource();
+    }
+
+    @Test
+    public void usingClientFollow() throws Exception {
+
+        // given
+        final JsonRepresentation givenAction = Util.givenAction(client, 
"ActionsEntities", "subList");
+        final ObjectActionRepresentation actionRepr = 
givenAction.as(ObjectActionRepresentation.class);
+
+        final LinkRepresentation invokeLink = actionRepr.getInvoke();
+
+        assertThat(invokeLink, isLink(client)
+                                    .rel(Rel.INVOKE)
+                                    .httpMethod(RestfulHttpMethod.GET)
+                                    
.href(Matchers.endsWith(":39393/services/ActionsEntities/actions/subList/invoke"))
+                                    .build());
+        
+        JsonRepresentation args =invokeLink.getArguments();
+        assertThat(args.size(), is(2));
+        assertThat(args, RestfulMatchers.mapHas("from"));
+        assertThat(args, RestfulMatchers.mapHas("to"));
+        
+        // when
+        args.mapPut("from.value", 1);
+        args.mapPut("to.value", 0);
+
+        final RestfulResponse<ActionResultRepresentation> restfulResponse = 
client.followT(invokeLink, args);
+        
+        // then
+        thenResponseIsErrorWithInvalidReason(restfulResponse);
+    }
+
+    @Test
+    public void usingResourceProxy() throws Exception {
+
+        // given, when
+        JsonRepresentation args = JsonRepresentation.newMap();
+        args.mapPut("from.value", 1);
+        args.mapPut("to.value", 0);
+        Response response = 
serviceResource.invokeActionQueryOnly("ActionsEntities", "subList", 
UrlEncodingUtils.urlEncode(args));
+        RestfulResponse<ActionResultRepresentation> restfulResponse = 
RestfulResponse.ofT(response);
+        
+        // then
+        thenResponseIsErrorWithInvalidReason(restfulResponse);
+    }
+
+    private static void thenResponseIsErrorWithInvalidReason(final 
RestfulResponse<ActionResultRepresentation> restfulResponse) throws 
JsonParseException, JsonMappingException, IOException {
+        assertThat(restfulResponse, 
hasStatus(HttpStatusCode.VALIDATION_FAILED));
+        assertThat(restfulResponse.getHeader(Header.WARNING), is("Validation 
failed, see body for details"));
+
+        // hmmm... what is the media type, though?  the spec doesn't say.  
testing for a generic one.
+        assertThat(restfulResponse.getHeader(Header.CONTENT_TYPE), 
hasProfile(MediaType.APPLICATION_JSON));
+
+        RestfulResponse<JsonRepresentation> restfulResponseOfError = 
restfulResponse.wraps(JsonRepresentation.class);
+        JsonRepresentation repr = restfulResponseOfError.getEntity();
+        
+        assertThat(repr.getString("x-ro-invalidReason"), is("'from' cannot be 
larger than 'to'"));
+    }
+
+
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/b4dd583a/component/viewer/restfulobjects/tck/src/test/java/org/apache/isis/viewer/restfulobjects/tck/domainservice/serviceId/action/invoke/DomainServiceTest_req_safe_simplearg_fail_individual_arg_validation.java
----------------------------------------------------------------------
diff --git 
a/component/viewer/restfulobjects/tck/src/test/java/org/apache/isis/viewer/restfulobjects/tck/domainservice/serviceId/action/invoke/DomainServiceTest_req_safe_simplearg_fail_individual_arg_validation.java
 
b/component/viewer/restfulobjects/tck/src/test/java/org/apache/isis/viewer/restfulobjects/tck/domainservice/serviceId/action/invoke/DomainServiceTest_req_safe_simplearg_fail_individual_arg_validation.java
new file mode 100644
index 0000000..7393abb
--- /dev/null
+++ 
b/component/viewer/restfulobjects/tck/src/test/java/org/apache/isis/viewer/restfulobjects/tck/domainservice/serviceId/action/invoke/DomainServiceTest_req_safe_simplearg_fail_individual_arg_validation.java
@@ -0,0 +1,134 @@
+/*
+ *  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.tck.domainservice.serviceId.action.invoke;
+
+import static 
org.apache.isis.viewer.restfulobjects.tck.RestfulMatchers.hasProfile;
+import static 
org.apache.isis.viewer.restfulobjects.tck.RestfulMatchers.hasStatus;
+import static org.apache.isis.viewer.restfulobjects.tck.RestfulMatchers.isLink;
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+import java.io.IOException;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation;
+import org.apache.isis.viewer.restfulobjects.applib.LinkRepresentation;
+import org.apache.isis.viewer.restfulobjects.applib.Rel;
+import org.apache.isis.viewer.restfulobjects.applib.RestfulHttpMethod;
+import org.apache.isis.viewer.restfulobjects.applib.RestfulMediaType;
+import org.apache.isis.viewer.restfulobjects.applib.client.RestfulClient;
+import org.apache.isis.viewer.restfulobjects.applib.client.RestfulResponse;
+import 
org.apache.isis.viewer.restfulobjects.applib.client.RestfulResponse.Header;
+import 
org.apache.isis.viewer.restfulobjects.applib.client.RestfulResponse.HttpStatusCode;
+import 
org.apache.isis.viewer.restfulobjects.applib.domainobjects.ActionResultRepresentation;
+import 
org.apache.isis.viewer.restfulobjects.applib.domainobjects.ActionResultRepresentation.ResultType;
+import 
org.apache.isis.viewer.restfulobjects.applib.domainobjects.DomainServiceResource;
+import 
org.apache.isis.viewer.restfulobjects.applib.domainobjects.ListRepresentation;
+import 
org.apache.isis.viewer.restfulobjects.applib.domainobjects.ObjectActionRepresentation;
+import org.apache.isis.viewer.restfulobjects.applib.util.UrlEncodingUtils;
+import org.apache.isis.viewer.restfulobjects.tck.IsisWebServerRule;
+import org.apache.isis.viewer.restfulobjects.tck.RestfulMatchers;
+import org.apache.isis.viewer.restfulobjects.tck.Util;
+import org.codehaus.jackson.JsonParseException;
+import org.codehaus.jackson.map.JsonMappingException;
+import org.hamcrest.Matchers;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class 
DomainServiceTest_req_safe_simplearg_fail_individual_arg_validation {
+
+    @Rule
+    public IsisWebServerRule webServerRule = new IsisWebServerRule();
+
+    private RestfulClient client;
+
+    private DomainServiceResource serviceResource;
+
+    @Before
+    public void setUp() throws Exception {
+        client = webServerRule.getClient();
+
+        serviceResource = client.getDomainServiceResource();
+    }
+
+    @Test
+    public void usingClientFollow() throws Exception {
+
+        // given
+        final JsonRepresentation givenAction = Util.givenAction(client, 
"ActionsEntities", "subList");
+        final ObjectActionRepresentation actionRepr = 
givenAction.as(ObjectActionRepresentation.class);
+
+        final LinkRepresentation invokeLink = actionRepr.getInvoke();
+
+        assertThat(invokeLink, isLink(client)
+                                    .rel(Rel.INVOKE)
+                                    .httpMethod(RestfulHttpMethod.GET)
+                                    
.href(Matchers.endsWith(":39393/services/ActionsEntities/actions/subList/invoke"))
+                                    .build());
+        
+        JsonRepresentation args =invokeLink.getArguments();
+        assertThat(args.size(), is(2));
+        assertThat(args, RestfulMatchers.mapHas("from"));
+        assertThat(args, RestfulMatchers.mapHas("to"));
+        
+        // when
+        args.mapPut("from.value", -1);
+        args.mapPut("to.value", 2);
+
+        final RestfulResponse<ActionResultRepresentation> restfulResponse = 
client.followT(invokeLink, args);
+        
+        // then
+        thenResponseIsAValidationError(restfulResponse);
+    }
+
+
+    @Test
+    public void usingResourceProxy() throws Exception {
+
+        // given, when
+        JsonRepresentation args = JsonRepresentation.newMap();
+        args.mapPut("from.value", -1);
+        args.mapPut("to.value", 2);
+        Response response = 
serviceResource.invokeActionQueryOnly("ActionsEntities", "subList", 
UrlEncodingUtils.urlEncode(args));
+        RestfulResponse<ActionResultRepresentation> restfulResponse = 
RestfulResponse.ofT(response);
+        
+        // then
+        thenResponseIsAValidationError(restfulResponse);
+    }
+
+    private static void thenResponseIsAValidationError(final 
RestfulResponse<ActionResultRepresentation> restfulResponse) throws 
JsonParseException, JsonMappingException, IOException {
+        assertThat(restfulResponse, 
hasStatus(HttpStatusCode.VALIDATION_FAILED));
+
+        assertThat(restfulResponse.getHeader(Header.WARNING), 
containsString("Validation failed, see body for details"));
+
+        // hmmm... what is the media type, though?  the spec doesn't say.  
testing for a generic one.
+        assertThat(restfulResponse.getHeader(Header.CONTENT_TYPE), 
hasProfile(MediaType.APPLICATION_JSON));
+
+        RestfulResponse<JsonRepresentation> restfulResponseOfError = 
restfulResponse.wraps(JsonRepresentation.class);
+        JsonRepresentation repr = restfulResponseOfError.getEntity();
+        
+        assertThat(repr.getString("from.invalidReason"), is("Cannot be less 
than zero"));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/b4dd583a/component/viewer/restfulobjects/tck/src/test/java/org/apache/isis/viewer/restfulobjects/tck/domainservice/serviceId/action/invoke/DomainServiceTest_req_safe_simplearg_resp_list.java
----------------------------------------------------------------------
diff --git 
a/component/viewer/restfulobjects/tck/src/test/java/org/apache/isis/viewer/restfulobjects/tck/domainservice/serviceId/action/invoke/DomainServiceTest_req_safe_simplearg_resp_list.java
 
b/component/viewer/restfulobjects/tck/src/test/java/org/apache/isis/viewer/restfulobjects/tck/domainservice/serviceId/action/invoke/DomainServiceTest_req_safe_simplearg_resp_list.java
index 0e5c67c..cdcaa2e 100644
--- 
a/component/viewer/restfulobjects/tck/src/test/java/org/apache/isis/viewer/restfulobjects/tck/domainservice/serviceId/action/invoke/DomainServiceTest_req_safe_simplearg_resp_list.java
+++ 
b/component/viewer/restfulobjects/tck/src/test/java/org/apache/isis/viewer/restfulobjects/tck/domainservice/serviceId/action/invoke/DomainServiceTest_req_safe_simplearg_resp_list.java
@@ -18,18 +18,24 @@
  */
 package 
org.apache.isis.viewer.restfulobjects.tck.domainservice.serviceId.action.invoke;
 
+import static 
org.apache.isis.viewer.restfulobjects.tck.RestfulMatchers.hasProfile;
 import static org.apache.isis.viewer.restfulobjects.tck.RestfulMatchers.isLink;
 import static org.hamcrest.CoreMatchers.is;
 import static org.junit.Assert.assertThat;
 
+import java.io.IOException;
+
+import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 
 import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation;
 import org.apache.isis.viewer.restfulobjects.applib.LinkRepresentation;
 import org.apache.isis.viewer.restfulobjects.applib.Rel;
 import org.apache.isis.viewer.restfulobjects.applib.RestfulHttpMethod;
+import org.apache.isis.viewer.restfulobjects.applib.RestfulMediaType;
 import org.apache.isis.viewer.restfulobjects.applib.client.RestfulClient;
 import org.apache.isis.viewer.restfulobjects.applib.client.RestfulResponse;
+import 
org.apache.isis.viewer.restfulobjects.applib.client.RestfulResponse.Header;
 import 
org.apache.isis.viewer.restfulobjects.applib.domainobjects.ActionResultRepresentation;
 import 
org.apache.isis.viewer.restfulobjects.applib.domainobjects.ActionResultRepresentation.ResultType;
 import 
org.apache.isis.viewer.restfulobjects.applib.domainobjects.DomainServiceResource;
@@ -39,6 +45,8 @@ import 
org.apache.isis.viewer.restfulobjects.applib.util.UrlEncodingUtils;
 import org.apache.isis.viewer.restfulobjects.tck.IsisWebServerRule;
 import org.apache.isis.viewer.restfulobjects.tck.RestfulMatchers;
 import org.apache.isis.viewer.restfulobjects.tck.Util;
+import org.codehaus.jackson.JsonParseException;
+import org.codehaus.jackson.map.JsonMappingException;
 import org.hamcrest.Matchers;
 import org.junit.Before;
 import org.junit.Rule;
@@ -86,15 +94,9 @@ public class DomainServiceTest_req_safe_simplearg_resp_list {
 
         final RestfulResponse<ActionResultRepresentation> restfulResponse = 
client.followT(invokeLink, args);
         
-        // then
-        final ActionResultRepresentation actionResultRepr = 
restfulResponse.getEntity();
-        
-        assertThat(actionResultRepr.getResultType(), is(ResultType.LIST));
-        final ListRepresentation listRepr = 
actionResultRepr.getResult().as(ListRepresentation.class);
-        assertThat(listRepr.getValue().size(), is(2));
+        then(restfulResponse);
     }
 
-
     @Test
     public void usingResourceProxy() throws Exception {
 
@@ -106,6 +108,11 @@ public class 
DomainServiceTest_req_safe_simplearg_resp_list {
         RestfulResponse<ActionResultRepresentation> restfulResponse = 
RestfulResponse.ofT(response);
         
         // then
+        then(restfulResponse);
+    }
+
+    private static void then(RestfulResponse<ActionResultRepresentation> 
restfulResponse) throws JsonParseException, JsonMappingException, IOException {
+        assertThat(restfulResponse.getHeader(Header.CONTENT_TYPE), 
hasProfile(RestfulMediaType.APPLICATION_JSON_ACTION_RESULT));
         final ActionResultRepresentation actionResultRepr = 
restfulResponse.getEntity();
         
         assertThat(actionResultRepr.getResultType(), is(ResultType.LIST));

http://git-wip-us.apache.org/repos/asf/isis/blob/b4dd583a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionParameterParseable.java
----------------------------------------------------------------------
diff --git 
a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionParameterParseable.java
 
b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionParameterParseable.java
index e05a1d8..44de1c9 100644
--- 
a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionParameterParseable.java
+++ 
b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionParameterParseable.java
@@ -77,22 +77,32 @@ public class ObjectActionParameterParseable extends 
ObjectActionParameterAbstrac
     @Override
     public String isValid(final ObjectAdapter adapter, final Object 
proposedValue, final Localization localization) {
 
-        if (!(proposedValue instanceof String)) {
-            return null;
-        }
-        final String proposedString = (String) proposedValue;
 
-        final ObjectActionParameter objectActionParameter = 
getAction().getParameters().get(getNumber());
-        if (!(objectActionParameter instanceof ParseableEntryActionParameter)) 
{
+        final ObjectActionParameter parameter = 
getAction().getParameters().get(getNumber());
+        final ObjectSpecification parameterSpecification = 
parameter.getSpecification();
+        if(proposedValue == null) {
             return null;
         }
-        final ParseableEntryActionParameter parameter = 
(ParseableEntryActionParameter) objectActionParameter;
-
-        final ObjectSpecification parameterSpecification = 
parameter.getSpecification();
-        final ParseableFacet p = 
parameterSpecification.getFacet(ParseableFacet.class);
-        final ObjectAdapter newValue = p.parseTextEntry(null, proposedString, 
localization);
+        
+        ObjectAdapter proposedValueAdapter = 
getAdapterMap().adapterFor(proposedValue);
+        final ObjectSpecification proposedValueSpec = 
proposedValueAdapter.getSpecification();
+        if(proposedValueSpec.isOfType(proposedValueSpec)) {
+            // nothing to do
+        } else {
+            // try to parse
+            if (!(parameter instanceof ParseableEntryActionParameter)) {
+                return null;
+            }
+            if (!(proposedValue instanceof String)) {
+                return null;
+            }
+            final String proposedString = (String) proposedValue;
+
+            final ParseableFacet p = 
parameterSpecification.getFacet(ParseableFacet.class);
+            proposedValueAdapter = p.parseTextEntry(null, proposedString, 
localization);
+        }
 
-        final ValidityContext<?> ic = 
parameter.createProposedArgumentInteractionContext(getAuthenticationSession(), 
InteractionInvocationMethod.BY_USER, adapter, arguments(newValue), getNumber());
+        final ValidityContext<?> ic = 
parameter.createProposedArgumentInteractionContext(getAuthenticationSession(), 
InteractionInvocationMethod.BY_USER, adapter, arguments(proposedValueAdapter), 
getNumber());
 
         final InteractionResultSet buf = new InteractionResultSet();
         InteractionUtils.isValidResultSet(parameter, ic, buf);

http://git-wip-us.apache.org/repos/asf/isis/blob/b4dd583a/core/tck/tck-dom/src/main/java/org/apache/isis/core/tck/dom/actions/ActionsEntityRepository.java
----------------------------------------------------------------------
diff --git 
a/core/tck/tck-dom/src/main/java/org/apache/isis/core/tck/dom/actions/ActionsEntityRepository.java
 
b/core/tck/tck-dom/src/main/java/org/apache/isis/core/tck/dom/actions/ActionsEntityRepository.java
index b14a73f..1c89a73 100644
--- 
a/core/tck/tck-dom/src/main/java/org/apache/isis/core/tck/dom/actions/ActionsEntityRepository.java
+++ 
b/core/tck/tck-dom/src/main/java/org/apache/isis/core/tck/dom/actions/ActionsEntityRepository.java
@@ -24,10 +24,12 @@ import java.util.List;
 import org.apache.isis.applib.annotation.ActionSemantics;
 import org.apache.isis.applib.annotation.ActionSemantics.Of;
 import org.apache.isis.applib.annotation.MemberOrder;
+import org.apache.isis.applib.annotation.MustSatisfy;
 import org.apache.isis.applib.annotation.Named;
 import org.apache.isis.applib.annotation.ObjectType;
 import org.apache.isis.applib.query.Query;
 import org.apache.isis.applib.query.QueryDefault;
+import org.apache.isis.applib.spec.Specification;
 import org.apache.isis.core.tck.dom.AbstractEntityRepository;
 
 @Named("ActionsEntities")
@@ -48,12 +50,22 @@ public class ActionsEntityRepository extends 
AbstractEntityRepository<ActionsEnt
 
     @ActionSemantics(Of.SAFE)
     @MemberOrder(sequence = "1")
-    public List<ActionsEntity> subList(@Named("from") int from, @Named("to") 
int to) {
+    public List<ActionsEntity> subList(
+            @MustSatisfy(IntegerCannotBeNegative.class)
+            @Named("from") int from, 
+            @MustSatisfy(IntegerCannotBeNegative.class)
+            @Named("to") int to) {
         List<ActionsEntity> list = list();
         int toChecked = Math.min(to, list.size());
         int fromChecked = Math.min(from, toChecked);
         return list.subList(fromChecked, toChecked);
     }
+    public String validateSubList(final int from, final int to) {
+        if(from > to) {
+            return "'from' cannot be larger than 'to'";
+        }
+        return null;
+    }
 
     @ActionSemantics(Of.SAFE)
     @MemberOrder(sequence = "1")
@@ -61,5 +73,15 @@ public class ActionsEntityRepository extends 
AbstractEntityRepository<ActionsEnt
         List<ActionsEntity> list = subList(from, to);
         return list.contains(entity);
     }
-    
+
+    public static class IntegerCannotBeNegative implements Specification {
+        @Override
+        public String satisfies(Object obj) {
+            if(!(obj instanceof Integer)) {
+                return null;
+            } 
+            Integer integer = (Integer) obj;
+            return integer.intValue() < 0? "Cannot be less than zero": null;
+        }
+    }
 }

Reply via email to