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; + } + } }
