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 2018/11/26 23:27:33 UTC

[isis] 01/01: ISIS-2045, ISIS-2046: RO validations now supported

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

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

commit 3c4daf4acdc8957522ede387be07319de29ee0ad
Author: danhaywood <da...@haywood-associates.co.uk>
AuthorDate: Mon Nov 26 23:19:17 2018 +0000

    ISIS-2045, ISIS-2046: RO validations now supported
    
    if validation fails, renders correct body in 422 client-side exception
    x-ro-validate-only flag supported; returns 204 no content if validation passes.
---
 .../core/metamodel/spec/feature/ObjectAction.java  | 20 ++++++-
 .../specimpl/ObjectActionContributee.java          | 27 +++++++--
 .../specloader/specimpl/ObjectActionDefault.java   | 65 +++++++++++++++++++++-
 .../viewer/restfulobjects/applib/util/Parser.java  |  8 ++-
 ...sHttpStatusCode.java => ExceptionWithBody.java} |  6 +-
 ...sCode.java => ExceptionWithHttpStatusCode.java} |  2 +-
 .../restfulobjects/rendering/RendererContext.java  |  1 +
 .../RestfulObjectsApplicationException.java        | 34 ++++++++---
 .../restfulobjects/server/ResourceContext.java     |  6 ++
 .../server/mappers/ExceptionMapperAbstract.java    | 36 ++++++++++--
 ...xceptionMapperForRestfulObjectsApplication.java |  4 +-
 .../resources/DomainObjectResourceServerside.java  |  8 +++
 .../server/resources/DomainResourceHelper.java     | 11 ++++
 .../server/resources/ObjectActionArgHelper.java    | 12 ++--
 .../modules/simple/dom/impl/SimpleObject.java      |  1 -
 15 files changed, 209 insertions(+), 32 deletions(-)

diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/ObjectAction.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/ObjectAction.java
index 7f37494..60be7d1 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/ObjectAction.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/ObjectAction.java
@@ -128,16 +128,34 @@ public interface ObjectAction extends ObjectMember {
 
     //endregion
 
-    //region > isProposedArgumentSetValid
+    //region > isProposedArgumentSetValid, isEachIndividualArgumentValid, isArgumentSetValid
 
     /**
      * Whether the provided argument set is valid, represented as a {@link Consent}.
+     *
+     * <p>
+     *     Basically just calls (the helper methods also called by) first
+     *     {@link #isEachIndividualArgumentValid(ObjectAdapter, ObjectAdapter[], InteractionInitiatedBy)} and then
+     *     {@link #isArgumentSetValid(ObjectAdapter, ObjectAdapter[], InteractionInitiatedBy)}.  Those methods are
+     *     separated out so that viewers have more fine-grained control.
+     * </p>
      */
     Consent isProposedArgumentSetValid(
             ObjectAdapter object,
             ObjectAdapter[] proposedArguments,
             final InteractionInitiatedBy interactionInitiatedBy);
 
+    Consent isEachIndividualArgumentValid(
+            ObjectAdapter objectAdapter,
+            ObjectAdapter[] proposedArguments,
+            InteractionInitiatedBy interactionInitiatedBy);
+
+    Consent isArgumentSetValid(
+            ObjectAdapter objectAdapter,
+            ObjectAdapter[] proposedArguments,
+            InteractionInitiatedBy interactionInitiatedBy);
+
+
     //endregion
 
     //region > Parameters (declarative)
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionContributee.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionContributee.java
index b0156e9..e47d74d 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionContributee.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionContributee.java
@@ -176,16 +176,35 @@ public class ObjectActionContributee extends ObjectActionDefault implements Cont
                 interactionInitiatedBy);
         return removeElementFromArray(serviceChoices, contributeeParam, new ObjectAdapter[][]{});
     }
-        
+
+    @Override
     public Consent isProposedArgumentSetValid(
             final ObjectAdapter contributee,
             final ObjectAdapter[] proposedArguments,
             final InteractionInitiatedBy interactionInitiatedBy) {
-        ObjectAdapter[] serviceArguments = argsPlusContributee(contributee, proposedArguments);
+        final ObjectAdapter[] serviceArguments = argsPlusContributee(contributee, proposedArguments);
         return serviceAction.isProposedArgumentSetValid(getServiceAdapter(), serviceArguments, interactionInitiatedBy);
     }
 
     @Override
+    public Consent isEachIndividualArgumentValid(
+            final ObjectAdapter contributee,
+            final ObjectAdapter[] proposedArguments,
+            final InteractionInitiatedBy interactionInitiatedBy) {
+        final ObjectAdapter[] serviceArguments = argsPlusContributee(contributee, proposedArguments);
+        return serviceAction.isEachIndividualArgumentValid(getServiceAdapter(), serviceArguments, interactionInitiatedBy);
+    }
+
+    @Override
+    public Consent isArgumentSetValid(
+            final ObjectAdapter contributee,
+            final ObjectAdapter[] proposedArguments,
+            final InteractionInitiatedBy interactionInitiatedBy) {
+        final ObjectAdapter[] serviceArguments = argsPlusContributee(contributee, proposedArguments);
+        return serviceAction.isArgumentSetValid(getServiceAdapter(), serviceArguments, interactionInitiatedBy);
+    }
+
+    @Override
     public ObjectAdapter execute(
             final ObjectAdapter targetAdapter,
             final ObjectAdapter mixedInAdapter,
@@ -194,9 +213,9 @@ public class ObjectActionContributee extends ObjectActionDefault implements Cont
 
         setupCommand(targetAdapter, argumentAdapters);
 
+        final ObjectAdapter[] serviceArguments = argsPlusContributee(targetAdapter, argumentAdapters);
         return serviceAction.executeInternal(
-                getServiceAdapter(), mixedInAdapter, argsPlusContributee(targetAdapter, argumentAdapters),
-                interactionInitiatedBy);
+                getServiceAdapter(), mixedInAdapter, serviceArguments, interactionInitiatedBy);
     }
 
 
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionDefault.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionDefault.java
index bfc7097..8137b76 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionDefault.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionDefault.java
@@ -285,6 +285,18 @@ public class ObjectActionDefault extends ObjectMemberAbstract implements ObjectA
 
     //region > validate
 
+    /**
+     * The Validates all arguments individually (by calling same helper that
+     * {@link #isEachIndividualArgumentValid(ObjectAdapter, ObjectAdapter[], InteractionInitiatedBy)} delegates to)
+     * and if there are no validation errors, then validates the entire argument
+     * set (by calling same helper that
+     * {@link #isArgumentSetValid(ObjectAdapter, ObjectAdapter[], InteractionInitiatedBy)} delegates to).
+     *
+     * <p>
+     * The two other validation methods mentioned above are separated out to allow viewers (such as the RO viewer) to
+     * call the validation phases separately.
+     * </p>
+     */
     @Override
     public Consent isProposedArgumentSetValid(
             final ObjectAdapter targetObject,
@@ -302,7 +314,33 @@ public class ObjectActionDefault extends ObjectMemberAbstract implements ObjectA
         return resultSet.createConsent();
     }
 
-    protected void validateArgumentsIndividually(
+    /**
+     * Normally action validation is all performed by
+     * {@link #isProposedArgumentSetValid(ObjectAdapter, ObjectAdapter[], InteractionInitiatedBy)}, which calls
+     * {@link #isEachIndividualArgumentValid(ObjectAdapter, ObjectAdapter[], InteractionInitiatedBy) this method} to
+     * validate arguments individually, and then
+     * {@link #isArgumentSetValid(ObjectAdapter, ObjectAdapter[], InteractionInitiatedBy) validate argument set}
+     * afterwards.
+     *
+     * <p>
+     * This method is in the API to allow viewers (eg the RO viewer) to call the different phases of validation
+     * individually.
+     * </p>
+     */
+    @Override
+    public Consent isEachIndividualArgumentValid(
+            final ObjectAdapter objectAdapter,
+            final ObjectAdapter[] proposedArguments,
+            final InteractionInitiatedBy interactionInitiatedBy) {
+
+        final InteractionResultSet resultSet = new InteractionResultSet();
+
+        validateArgumentsIndividually(objectAdapter, proposedArguments, interactionInitiatedBy, resultSet);
+
+        return resultSet.createConsent();
+    }
+
+    private void validateArgumentsIndividually(
             final ObjectAdapter objectAdapter,
             final ObjectAdapter[] proposedArguments,
             final InteractionInitiatedBy interactionInitiatedBy,
@@ -319,6 +357,31 @@ public class ObjectActionDefault extends ObjectMemberAbstract implements ObjectA
         }
     }
 
+    /**
+     * Normally action validation is all performed by
+     * {@link #isProposedArgumentSetValid(ObjectAdapter, ObjectAdapter[], InteractionInitiatedBy)}, which calls
+     * {@link #isEachIndividualArgumentValid(ObjectAdapter, ObjectAdapter[], InteractionInitiatedBy)} to
+     * validate arguments individually, and then
+     * {@link #isArgumentSetValid(ObjectAdapter, ObjectAdapter[], InteractionInitiatedBy) this method} to
+     * validate the entire argument set afterwards.
+     *
+     * <p>
+     * This method is in the API to allow viewers (eg the RO viewer) to call the different phases of validation
+     * individually.
+     * </p>
+     */
+    @Override
+    public Consent isArgumentSetValid(
+            final ObjectAdapter objectAdapter,
+            final ObjectAdapter[] proposedArguments,
+            final InteractionInitiatedBy interactionInitiatedBy) {
+
+        final InteractionResultSet resultSet = new InteractionResultSet();
+        validateArgumentSet(objectAdapter, proposedArguments, interactionInitiatedBy, resultSet);
+
+        return resultSet.createConsent();
+    }
+
     protected void validateArgumentSet(
             final ObjectAdapter objectAdapter,
             final ObjectAdapter[] proposedArguments,
diff --git a/core/viewer-restfulobjects-applib/src/main/java/org/apache/isis/viewer/restfulobjects/applib/util/Parser.java b/core/viewer-restfulobjects-applib/src/main/java/org/apache/isis/viewer/restfulobjects/applib/util/Parser.java
index d23e821..3e18b27 100644
--- a/core/viewer-restfulobjects-applib/src/main/java/org/apache/isis/viewer/restfulobjects/applib/util/Parser.java
+++ b/core/viewer-restfulobjects-applib/src/main/java/org/apache/isis/viewer/restfulobjects/applib/util/Parser.java
@@ -28,14 +28,14 @@ import java.util.List;
 import javax.ws.rs.core.CacheControl;
 import javax.ws.rs.core.MediaType;
 
-import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation;
-
 import com.google.common.base.Function;
 import com.google.common.base.Joiner;
 import com.google.common.base.Splitter;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 
+import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation;
+
 public abstract class Parser<T> {
     public T valueOf(final List<String> str) {
         if (str == null) {
@@ -173,7 +173,9 @@ public abstract class Parser<T> {
                 if (str == null) {
                     return null;
                 }
-                return str.equals("yes") ? Boolean.TRUE : Boolean.FALSE;
+                return "yes".equalsIgnoreCase(str) || "true".equalsIgnoreCase(str)
+                        ? Boolean.TRUE
+                        : Boolean.FALSE;
             }
 
             @Override
diff --git a/core/viewer-restfulobjects-rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/HasHttpStatusCode.java b/core/viewer-restfulobjects-rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/ExceptionWithBody.java
similarity index 84%
copy from core/viewer-restfulobjects-rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/HasHttpStatusCode.java
copy to core/viewer-restfulobjects-rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/ExceptionWithBody.java
index cb35092..821555f 100644
--- a/core/viewer-restfulobjects-rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/HasHttpStatusCode.java
+++ b/core/viewer-restfulobjects-rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/ExceptionWithBody.java
@@ -18,9 +18,9 @@
  */
 package org.apache.isis.viewer.restfulobjects.rendering;
 
-import org.apache.isis.viewer.restfulobjects.applib.client.RestfulResponse.HttpStatusCode;
+import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation;
 
-public interface HasHttpStatusCode {
+public interface ExceptionWithBody {
 
-    HttpStatusCode getHttpStatusCode();
+    JsonRepresentation getBody();
 }
diff --git a/core/viewer-restfulobjects-rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/HasHttpStatusCode.java b/core/viewer-restfulobjects-rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/ExceptionWithHttpStatusCode.java
similarity index 95%
rename from core/viewer-restfulobjects-rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/HasHttpStatusCode.java
rename to core/viewer-restfulobjects-rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/ExceptionWithHttpStatusCode.java
index cb35092..c88d08e 100644
--- a/core/viewer-restfulobjects-rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/HasHttpStatusCode.java
+++ b/core/viewer-restfulobjects-rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/ExceptionWithHttpStatusCode.java
@@ -20,7 +20,7 @@ package org.apache.isis.viewer.restfulobjects.rendering;
 
 import org.apache.isis.viewer.restfulobjects.applib.client.RestfulResponse.HttpStatusCode;
 
-public interface HasHttpStatusCode {
+public interface ExceptionWithHttpStatusCode {
 
     HttpStatusCode getHttpStatusCode();
 }
diff --git a/core/viewer-restfulobjects-rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/RendererContext.java b/core/viewer-restfulobjects-rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/RendererContext.java
index 460e0e4..55da602 100644
--- a/core/viewer-restfulobjects-rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/RendererContext.java
+++ b/core/viewer-restfulobjects-rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/RendererContext.java
@@ -48,6 +48,7 @@ public interface RendererContext {
     Where getWhere();
 
     List<List<String>> getFollowLinks();
+    boolean isValidateOnly();
 
     boolean honorUiHints();
 
diff --git a/core/viewer-restfulobjects-rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/RestfulObjectsApplicationException.java b/core/viewer-restfulobjects-rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/RestfulObjectsApplicationException.java
index 77e3765..1e8af8f 100644
--- a/core/viewer-restfulobjects-rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/RestfulObjectsApplicationException.java
+++ b/core/viewer-restfulobjects-rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/RestfulObjectsApplicationException.java
@@ -21,25 +21,39 @@ package org.apache.isis.viewer.restfulobjects.rendering;
 import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation;
 import org.apache.isis.viewer.restfulobjects.applib.client.RestfulResponse.HttpStatusCode;
 
-public class RestfulObjectsApplicationException extends RuntimeException implements HasHttpStatusCode {
+public class RestfulObjectsApplicationException
+        extends RuntimeException
+        implements ExceptionWithHttpStatusCode, ExceptionWithBody {
+
+    private static final long serialVersionUID = 1L;
 
     public static final RestfulObjectsApplicationException create(final HttpStatusCode httpStatusCode) {
         return createWithCause(httpStatusCode, null);
     }
 
-    public static RestfulObjectsApplicationException createWithMessage(final HttpStatusCode httpStatusCode, final String message, final Object... args) {
-        return createWithCauseAndMessage(httpStatusCode, (Exception) null, message, args);
+    public static RestfulObjectsApplicationException createWithMessage(
+            final HttpStatusCode httpStatusCode,
+            final String message, final Object... args) {
+        return createWithCauseAndMessage(httpStatusCode, null, message, args);
     }
 
-    public static RestfulObjectsApplicationException createWithCause(final HttpStatusCode httpStatusCode, final Exception cause) {
+    public static RestfulObjectsApplicationException createWithCause(
+            final HttpStatusCode httpStatusCode,
+            final Exception cause) {
         return createWithCauseAndMessage(httpStatusCode, cause, null);
     }
 
-    public static RestfulObjectsApplicationException createWithCauseAndMessage(final HttpStatusCode httpStatusCode, final Exception cause, final String message, final Object... args) {
+    public static RestfulObjectsApplicationException createWithCauseAndMessage(
+            final HttpStatusCode httpStatusCode,
+            final Exception cause,
+            final String message, final Object... args) {
         return new RestfulObjectsApplicationException(httpStatusCode, formatString(message, args), cause, null);
     }
 
-    public static RestfulObjectsApplicationException createWithBody(final HttpStatusCode httpStatusCode, final JsonRepresentation body, final String message, final Object... args) {
+    public static RestfulObjectsApplicationException createWithBody(
+            final HttpStatusCode httpStatusCode,
+            final JsonRepresentation body,
+            final String message, final Object... args) {
         return new RestfulObjectsApplicationException(httpStatusCode, formatString(message, args), null, body);
     }
 
@@ -47,11 +61,14 @@ public class RestfulObjectsApplicationException extends RuntimeException impleme
         return formatStr != null ? String.format(formatStr, args) : null;
     }
 
-    private static final long serialVersionUID = 1L;
     private final HttpStatusCode httpStatusCode;
     private final JsonRepresentation body;
 
-    protected RestfulObjectsApplicationException(final HttpStatusCode httpStatusCode, final String message, final Throwable cause, final JsonRepresentation body) {
+    protected RestfulObjectsApplicationException(
+            final HttpStatusCode httpStatusCode,
+            final String message,
+            final Throwable cause,
+            final JsonRepresentation body) {
         super(message, cause);
         this.httpStatusCode = httpStatusCode;
         this.body = body;
@@ -62,6 +79,7 @@ public class RestfulObjectsApplicationException extends RuntimeException impleme
         return httpStatusCode;
     }
 
+    @Override
     public JsonRepresentation getBody() {
         return body;
     }
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 415f243..7992611 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
@@ -77,6 +77,7 @@ public class ResourceContext implements RendererContext6 {
     private final PersistenceSession persistenceSession;
 
     private List<List<String>> followLinks;
+    private boolean validateOnly;
 
     private final Where where;
     private final RepresentationService.Intent intent;
@@ -138,6 +139,7 @@ public class ResourceContext implements RendererContext6 {
         ensureDomainModelQueryParamSupported();
         
         this.followLinks = Collections.unmodifiableList(getArg(RequestParameter.FOLLOW_LINKS));
+        this.validateOnly = getArg(RequestParameter.VALIDATE_ONLY);
     }
 
     private void ensureDomainModelQueryParamSupported() {
@@ -265,6 +267,10 @@ public class ResourceContext implements RendererContext6 {
     public List<List<String>> getFollowLinks() {
         return followLinks;
     }
+    @Override
+    public boolean isValidateOnly() {
+        return validateOnly;
+    }
 
     @Override
     public AuthenticationSession getAuthenticationSession() {
diff --git a/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/mappers/ExceptionMapperAbstract.java b/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/mappers/ExceptionMapperAbstract.java
index 0977835..1d73316 100644
--- a/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/mappers/ExceptionMapperAbstract.java
+++ b/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/mappers/ExceptionMapperAbstract.java
@@ -34,9 +34,11 @@ import com.google.common.collect.FluentIterable;
 import org.jboss.resteasy.spi.Failure;
 
 import org.apache.isis.applib.RecoverableException;
+import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation;
 import org.apache.isis.viewer.restfulobjects.applib.RepresentationType;
 import org.apache.isis.viewer.restfulobjects.applib.client.RestfulResponse;
-import org.apache.isis.viewer.restfulobjects.rendering.HasHttpStatusCode;
+import org.apache.isis.viewer.restfulobjects.rendering.ExceptionWithBody;
+import org.apache.isis.viewer.restfulobjects.rendering.ExceptionWithHttpStatusCode;
 import org.apache.isis.viewer.restfulobjects.server.mappers.entity.ExceptionDetail;
 import org.apache.isis.viewer.restfulobjects.server.mappers.entity.ExceptionPojo;
 import org.apache.isis.viewer.restfulobjects.server.resources.serialization.SerializationStrategy;
@@ -50,6 +52,14 @@ public abstract class ExceptionMapperAbstract<T extends Throwable> implements Ex
         final RestfulResponse.HttpStatusCode httpStatusCode = determineStatusCode(ex);
         final String message = messageFor(ex);
 
+        if(ex instanceof ExceptionWithBody) {
+            final ExceptionWithBody exceptionWithBody = (ExceptionWithBody) ex;
+            final JsonRepresentation body = exceptionWithBody.getBody();
+            if(body != null) {
+                return buildResponse(httpStatusCode, message, body);
+            }
+        }
+
         final ExceptionPojo exceptionPojo =
                 new ExceptionPojo(
                         httpStatusCode.getStatusCode(), message,
@@ -59,6 +69,24 @@ public abstract class ExceptionMapperAbstract<T extends Throwable> implements Ex
         return buildResponse(httpStatusCode, exceptionPojo);
     }
 
+    private Response buildResponse(
+            final RestfulResponse.HttpStatusCode httpStatusCode,
+            final String message,
+            final JsonRepresentation body) {
+        final ResponseBuilder builder = Response.status(httpStatusCode.getJaxrsStatusType());
+        if (message != null) {
+            builder.header(RestfulResponse.Header.WARNING.getName(), RestfulResponse.Header.WARNING.render(message));
+        }
+
+        final SerializationStrategy serializationStrategy = SerializationStrategy.JSON;
+
+        // hmm; the mediaType doesn't seem to be specified in the RO spec
+        builder.type(serializationStrategy.type(RepresentationType.GENERIC));
+        builder.entity(body.toString());
+
+        return builder.build();
+    }
+
     private RestfulResponse.HttpStatusCode determineStatusCode(final T ex) {
         final List<Throwable> chain = Throwables.getCausalChain(ex);
 
@@ -68,9 +96,9 @@ public abstract class ExceptionMapperAbstract<T extends Throwable> implements Ex
             statusCode = RestfulResponse.HttpStatusCode.statusFor(failure.getErrorCode());
         } else if(!FluentIterable.from(chain).filter(RecoverableException.class).isEmpty()) {
             statusCode = RestfulResponse.HttpStatusCode.OK;
-        } else if(ex instanceof HasHttpStatusCode) {
-            HasHttpStatusCode hasHttpStatusCode = (HasHttpStatusCode) ex;
-            statusCode = hasHttpStatusCode.getHttpStatusCode();
+        } else if(ex instanceof ExceptionWithHttpStatusCode) {
+            ExceptionWithHttpStatusCode exceptionWithHttpStatusCode = (ExceptionWithHttpStatusCode) ex;
+            statusCode = exceptionWithHttpStatusCode.getHttpStatusCode();
         } else {
             statusCode = RestfulResponse.HttpStatusCode.INTERNAL_SERVER_ERROR;
         }
diff --git a/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/mappers/ExceptionMapperForRestfulObjectsApplication.java b/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/mappers/ExceptionMapperForRestfulObjectsApplication.java
index da04ffe..7b49d00 100644
--- a/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/mappers/ExceptionMapperForRestfulObjectsApplication.java
+++ b/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/mappers/ExceptionMapperForRestfulObjectsApplication.java
@@ -21,14 +21,16 @@ package org.apache.isis.viewer.restfulobjects.server.mappers;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.ext.Provider;
 
+import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation;
 import org.apache.isis.viewer.restfulobjects.rendering.RestfulObjectsApplicationException;
 
-//@Path("/") // FIXME: workaround for TomEE ... but breaks the RestEasy TCK tests so commented out:-(
 @Provider
 public class ExceptionMapperForRestfulObjectsApplication extends ExceptionMapperAbstract<RestfulObjectsApplicationException> {
 
     @Override
     public Response toResponse(final RestfulObjectsApplicationException ex) {
+        final JsonRepresentation body = ex.getBody();
+        final String bodyStr = body != null ? body.toString() : null;
         return buildResponse(ex);
     }
 
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 853fcc9..5c7ee86 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
@@ -36,6 +36,8 @@ import javax.ws.rs.core.Response;
 import com.google.common.base.Strings;
 import com.google.common.io.Resources;
 
+import org.apache.log4j.Logger;
+
 import org.apache.isis.applib.annotation.Where;
 import org.apache.isis.applib.layout.component.ActionLayoutData;
 import org.apache.isis.applib.layout.component.CollectionLayoutData;
@@ -72,6 +74,12 @@ import org.apache.isis.viewer.restfulobjects.server.resources.serialization.Seri
 @Path("/objects")
 public class DomainObjectResourceServerside extends ResourceAbstract implements DomainObjectResource {
 
+    private final static Logger LOG = Logger.getLogger(DomainObjectResourceServerside.class);
+
+    public DomainObjectResourceServerside() {
+        LOG.debug("<init>");
+    }
+
     // //////////////////////////////////////////////////////////
     // persist
     // //////////////////////////////////////////////////////////
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 40385c4..a7474ff 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
@@ -113,6 +113,11 @@ public class DomainResourceHelper {
         }
 
         @Override
+        public boolean isValidateOnly() {
+            return rendererContext.isValidateOnly();
+        }
+
+        @Override
         public List<MediaType> getAcceptableMediaTypes() {
             return rendererContext.getAcceptableMediaTypes();
         }
@@ -364,6 +369,12 @@ public class DomainResourceHelper {
 
         final List<ObjectAdapter> argAdapters = argHelper.parseAndValidateArguments(arguments);
 
+        if(rendererContext.isValidateOnly()) {
+            // nothing more to do.
+            // if there had been a validation error, then an exception would have been thrown above.
+            return Response.noContent().build();
+        }
+
         // invoke
         final ObjectAdapter mixedInAdapter = null; // action will automatically fill in if a mixin
         final ObjectAdapter[] argAdapterArr = argAdapters.toArray(new ObjectAdapter[argAdapters.size()]);
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
index 82d15f3..6361b43 100644
--- 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
@@ -65,8 +65,7 @@ public class ObjectActionArgHelper {
                 // validate individual arg
                 final ObjectActionParameter parameter = parameters.get(i);
                 final Object argPojo = argAdapter!=null?argAdapter.getObject():null;
-                final String reasonNotValid = parameter.isValid(objectAdapter, argPojo, InteractionInitiatedBy.USER
-                );
+                final String reasonNotValid = parameter.isValid(objectAdapter, argPojo, InteractionInitiatedBy.USER);
                 if (reasonNotValid != null) {
                     argRepr.mapPut("invalidReason", reasonNotValid);
                     valid = false;
@@ -77,16 +76,19 @@ public class ObjectActionArgHelper {
             }
         }
 
-        // validate all args
+        // validate entire argument set
         final ObjectAdapter[] argArray = argAdapters.toArray(new ObjectAdapter[0]);
-        final Consent consent = action.isProposedArgumentSetValid(objectAdapter, argArray, InteractionInitiatedBy.USER);
+        final Consent consent = action.isArgumentSetValid(objectAdapter, argArray, InteractionInitiatedBy.USER);
         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");
+            throw RestfulObjectsApplicationException.createWithBody(
+                    RestfulResponse.HttpStatusCode.VALIDATION_FAILED,
+                    arguments,
+                    "Validation failed, see body for details");
         }
 
         return argAdapters;
diff --git a/example/application/simpleapp/module-simple/src/main/java/domainapp/modules/simple/dom/impl/SimpleObject.java b/example/application/simpleapp/module-simple/src/main/java/domainapp/modules/simple/dom/impl/SimpleObject.java
index 9c598d3..6b2b5b8 100644
--- a/example/application/simpleapp/module-simple/src/main/java/domainapp/modules/simple/dom/impl/SimpleObject.java
+++ b/example/application/simpleapp/module-simple/src/main/java/domainapp/modules/simple/dom/impl/SimpleObject.java
@@ -24,7 +24,6 @@ import javax.jdo.annotations.VersionStrategy;
 import com.google.common.collect.ComparisonChain;
 
 import org.apache.isis.applib.annotation.Action;
-import org.apache.isis.applib.annotation.ActionLayout;
 import org.apache.isis.applib.annotation.Auditing;
 import org.apache.isis.applib.annotation.CommandReification;
 import org.apache.isis.applib.annotation.DomainObject;