From 6b440af88d98aeea9b34fdf9ddf2e81dacca02c2 Mon Sep 17 00:00:00 2001 From: fcamblor Date: Mon, 22 Dec 2014 14:15:17 +0100 Subject: [PATCH 01/15] validation - Considering Validator components as optional components [BREAKING] --- .../restx/annotations/processor/RestxRouter.mustache | 2 +- .../src/main/java/restx/validation/Validations.java | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/restx-core-annotation-processor/src/main/resources/restx/annotations/processor/RestxRouter.mustache b/restx-core-annotation-processor/src/main/resources/restx/annotations/processor/RestxRouter.mustache index b07e7c539..4b2ea2183 100644 --- a/restx-core-annotation-processor/src/main/resources/restx/annotations/processor/RestxRouter.mustache +++ b/restx-core-annotation-processor/src/main/resources/restx/annotations/processor/RestxRouter.mustache @@ -32,7 +32,7 @@ public class {{router}} extends RestxRouter { final EntityRequestBodyReaderRegistry readerRegistry, final EntityResponseWriterRegistry writerRegistry, final MainStringConverter converter, - final Validator validator, + final Optional validator, final RestxSecurityManager securityManager) { super( "{{routerGroup}}", "{{router}}", new RestxRoute[] { diff --git a/restx-core/src/main/java/restx/validation/Validations.java b/restx-core/src/main/java/restx/validation/Validations.java index 65f4f74ab..e2a484b8b 100644 --- a/restx-core/src/main/java/restx/validation/Validations.java +++ b/restx-core/src/main/java/restx/validation/Validations.java @@ -1,6 +1,7 @@ package restx.validation; import com.google.common.base.Joiner; +import com.google.common.base.Optional; import javax.validation.ConstraintViolation; import javax.validation.Validator; @@ -12,11 +13,14 @@ * Time: 9:57 PM */ public class Validations { - public static T checkValid(Validator validator, T o) { - Set> violations = validator.validate(o); - if (!violations.isEmpty()) { - throw new IllegalArgumentException(Joiner.on(", ").join(violations)); + public static T checkValid(Optional validator, T o) { + if(validator.isPresent()) { + Set> violations = validator.get().validate(o); + if (!violations.isEmpty()) { + throw new IllegalArgumentException(Joiner.on(", ").join(violations)); + } } + return o; } } From 6989675109786048da04efe1beeaa745df5ec852 Mon Sep 17 00:00:00 2001 From: fcamblor Date: Mon, 22 Dec 2014 14:22:10 +0100 Subject: [PATCH 02/15] validation - Extracting hibernate validator dependency from restx-core to new restx-validation module. [BREAKING] It means that to activate bean validation, you will need to explicitely add a dependency on restx-validation module on your project. Startup time on project not using restx-validation will then be improved (no hibernate validator will be loaded) for ~250ms --- pom.xml | 1 + restx-core/pom.xml | 8 ++-- restx-validation/pom.xml | 38 +++++++++++++++++++ .../restx/validation/ValidatorFactory.java | 4 +- 4 files changed, 46 insertions(+), 5 deletions(-) create mode 100644 restx-validation/pom.xml rename {restx-core => restx-validation}/src/main/java/restx/validation/ValidatorFactory.java (88%) diff --git a/pom.xml b/pom.xml index 43d9267bc..e0389f76f 100644 --- a/pom.xml +++ b/pom.xml @@ -158,6 +158,7 @@ restx-shell-manager restx-build-shell restx-specs-shell + restx-validation diff --git a/restx-core/pom.xml b/restx-core/pom.xml index 9e497b88b..83cb31b4e 100644 --- a/restx-core/pom.xml +++ b/restx-core/pom.xml @@ -25,7 +25,7 @@ 2.3 2.3.3 1.13 - 5.0.1.Final + 1.1.0.Final 0.9.9-RC1 1 4.11 @@ -94,9 +94,9 @@ ${slf4j-api.version} - org.hibernate - hibernate-validator - ${hibernate-validator.version} + javax.validation + validation-api + ${validation-api.version} org.reflections diff --git a/restx-validation/pom.xml b/restx-validation/pom.xml new file mode 100644 index 000000000..850472fb4 --- /dev/null +++ b/restx-validation/pom.xml @@ -0,0 +1,38 @@ + + + + io.restx + restx-parent + 0.34-SNAPSHOT + + 4.0.0 + + io.restx + restx-validation + 0.34-SNAPSHOT + jar + restx-validation + + + 0.34-SNAPSHOT + 1.7 + 1.7 + 5.0.1.Final + + + + + io.restx + restx-factory + ${restx.version} + + + org.hibernate + hibernate-validator + ${hibernate-validator.version} + + + + \ No newline at end of file diff --git a/restx-core/src/main/java/restx/validation/ValidatorFactory.java b/restx-validation/src/main/java/restx/validation/ValidatorFactory.java similarity index 88% rename from restx-core/src/main/java/restx/validation/ValidatorFactory.java rename to restx-validation/src/main/java/restx/validation/ValidatorFactory.java index 1ff510ff8..7280a6b9f 100644 --- a/restx-core/src/main/java/restx/validation/ValidatorFactory.java +++ b/restx-validation/src/main/java/restx/validation/ValidatorFactory.java @@ -1,7 +1,9 @@ package restx.validation; import org.hibernate.validator.HibernateValidator; -import restx.factory.*; +import restx.factory.Module; +import restx.factory.Name; +import restx.factory.Provides; import javax.inject.Named; import javax.validation.Validation; From 91891fe810bd5c4b2a8bdb5b531b5756caa65f66 Mon Sep 17 00:00:00 2001 From: fcamblor Date: Mon, 22 Dec 2014 14:52:25 +0100 Subject: [PATCH 03/15] validation - Updated name for default Validator (from "validator" to "hibernate.validator") [BREAKING] --- .../src/main/java/restx/validation/ValidatorFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/restx-validation/src/main/java/restx/validation/ValidatorFactory.java b/restx-validation/src/main/java/restx/validation/ValidatorFactory.java index 7280a6b9f..e1c600043 100644 --- a/restx-validation/src/main/java/restx/validation/ValidatorFactory.java +++ b/restx-validation/src/main/java/restx/validation/ValidatorFactory.java @@ -16,7 +16,7 @@ */ @Module public class ValidatorFactory { - public static final String VALIDATOR_NAME = "validator"; + public static final String VALIDATOR_NAME = "hibernate.validator"; public static final Name VALIDATOR = Name.of(Validator.class, VALIDATOR_NAME); @Provides @Named(VALIDATOR_NAME) From 616a005e84893e11cd53f81f8b133b046691219d Mon Sep 17 00:00:00 2001 From: fcamblor Date: Mon, 22 Dec 2014 15:10:15 +0100 Subject: [PATCH 04/15] validation - Providing el api & impl dependencies in restx-validation, in order to make interpolation work in validation error messages (like @Size) --- restx-validation/pom.xml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/restx-validation/pom.xml b/restx-validation/pom.xml index 850472fb4..fba93c314 100644 --- a/restx-validation/pom.xml +++ b/restx-validation/pom.xml @@ -20,6 +20,7 @@ 1.7 1.7 5.0.1.Final + 2.2 @@ -32,6 +33,22 @@ org.hibernate hibernate-validator ${hibernate-validator.version} + + compile + + + javax.el + el-api + ${el.api.version} + + + org.glassfish.web + el-impl + ${el.api.version} + runtime From 4d90301242a530e548001c5684e87d6b84395223 Mon Sep 17 00:00:00 2001 From: fcamblor Date: Mon, 22 Dec 2014 15:18:28 +0100 Subject: [PATCH 05/15] validation - added restx-validation dep on restx-samplest --- restx-samplest/pom.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/restx-samplest/pom.xml b/restx-samplest/pom.xml index 235031d93..3045f5022 100644 --- a/restx-samplest/pom.xml +++ b/restx-samplest/pom.xml @@ -30,6 +30,11 @@ restx-core ${restx.version} + + io.restx + restx-validation + ${restx.version} + io.restx restx-core-annotation-processor From 0ad8d57b37e84ca1d83e0b67f6df495770cdc32f Mon Sep 17 00:00:00 2001 From: fcamblor Date: Fri, 26 Dec 2014 12:03:27 +0100 Subject: [PATCH 06/15] validation - Preparing ResourceMethodParameter to allow to pass it validation groups which will be used during bean validation --- .../processor/RestxAnnotationProcessor.java | 26 ++++++++++++++++--- .../java/restx/validation/Validations.java | 9 +++++-- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/restx-core-annotation-processor/src/main/java/restx/annotations/processor/RestxAnnotationProcessor.java b/restx-core-annotation-processor/src/main/java/restx/annotations/processor/RestxAnnotationProcessor.java index 5c2aa30ae..d18ba6e09 100644 --- a/restx-core-annotation-processor/src/main/java/restx/annotations/processor/RestxAnnotationProcessor.java +++ b/restx-core-annotation-processor/src/main/java/restx/annotations/processor/RestxAnnotationProcessor.java @@ -1,6 +1,7 @@ package restx.annotations.processor; import com.google.common.base.CaseFormat; +import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.base.Optional; import com.google.common.collect.*; @@ -167,7 +168,8 @@ private void buildResourceMethodParams(ResourceMethodAnnotation annotation, Reso p.asType().toString(), paramName, reqParamName, - parameterKind)); + parameterKind, + new String[0])); } if (!pathParamNamesToMatch.isEmpty()) { error( @@ -492,8 +494,16 @@ private static class ResourceMethodParameter { final String name; final String reqParamName; final ResourceMethodParameterKind kind; + final List validationGroupsFQNs; - private ResourceMethodParameter(String type, String name, String reqParamName, ResourceMethodParameterKind kind) { + private static final Function CLASS_APPENDER_FCT = new Function() { + @Override + public String apply(String fqn) { + return fqn+".class"; + } + }; + + private ResourceMethodParameter(String type, String name, String reqParamName, ResourceMethodParameterKind kind, String[] validationGroupsFQNs) { Matcher guavaOptionalMatcher = guavaOptionalPattern.matcher(type); Matcher java8OptionalMatcher = java8OptionalPattern.matcher(type); this.realType = type; @@ -513,6 +523,15 @@ private ResourceMethodParameter(String type, String name, String reqParamName, R this.name = name; this.reqParamName = reqParamName; this.kind = kind; + this.validationGroupsFQNs = Arrays.asList(validationGroupsFQNs); + } + + public Optional joinedValidationGroupFQNExpression(){ + if(this.validationGroupsFQNs == null || this.validationGroupsFQNs.isEmpty()) { + return Optional.absent(); + } else { + return Optional.of(Joiner.on(", ").join(Iterables.transform(this.validationGroupsFQNs, CLASS_APPENDER_FCT))); + } } } @@ -539,7 +558,8 @@ public String fetchFromReqCode(ResourceMethodParameter parameter) { }, BODY { public String fetchFromReqCode(ResourceMethodParameter parameter) { - return String.format("checkValid(validator, body)", parameter.type); + Optional validationGroupsExpr = parameter.joinedValidationGroupFQNExpression(); + return String.format("checkValid(validator, body%s)", validationGroupsExpr.isPresent()?","+validationGroupsExpr.get():""); } }, CONTEXT { diff --git a/restx-core/src/main/java/restx/validation/Validations.java b/restx-core/src/main/java/restx/validation/Validations.java index e2a484b8b..a24a57ff4 100644 --- a/restx-core/src/main/java/restx/validation/Validations.java +++ b/restx-core/src/main/java/restx/validation/Validations.java @@ -5,6 +5,7 @@ import javax.validation.ConstraintViolation; import javax.validation.Validator; +import javax.validation.groups.Default; import java.util.Set; /** @@ -13,9 +14,13 @@ * Time: 9:57 PM */ public class Validations { - public static T checkValid(Optional validator, T o) { + public static T checkValid(Optional validator, T o, Class... groups) { if(validator.isPresent()) { - Set> violations = validator.get().validate(o); + if(groups == null || groups.length==0) { + groups = new Class[]{ Default.class }; + } + + Set> violations = validator.get().validate(o, groups); if (!violations.isEmpty()) { throw new IllegalArgumentException(Joiner.on(", ").join(violations)); } From 84eab2dda0aa31f9b14700b909be759fc2e16fba Mon Sep 17 00:00:00 2001 From: fcamblor Date: Wed, 24 Dec 2014 18:38:48 +0100 Subject: [PATCH 07/15] Introduced @ValidatedFor annotation which will be used (soon) to validate POJO against bean validation groups --- .../main/java/restx/validation/ValidatedFor.java | 13 +++++++++++++ .../validation/stereotypes/FormValidations.java | 11 +++++++++++ 2 files changed, 24 insertions(+) create mode 100644 restx-core/src/main/java/restx/validation/ValidatedFor.java create mode 100644 restx-core/src/main/java/restx/validation/stereotypes/FormValidations.java diff --git a/restx-core/src/main/java/restx/validation/ValidatedFor.java b/restx-core/src/main/java/restx/validation/ValidatedFor.java new file mode 100644 index 000000000..d38952583 --- /dev/null +++ b/restx-core/src/main/java/restx/validation/ValidatedFor.java @@ -0,0 +1,13 @@ +package restx.validation; + +import java.lang.annotation.*; + +/** + * @author fcamblor + */ +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface ValidatedFor { + Class[] value(); +} diff --git a/restx-core/src/main/java/restx/validation/stereotypes/FormValidations.java b/restx-core/src/main/java/restx/validation/stereotypes/FormValidations.java new file mode 100644 index 000000000..034860616 --- /dev/null +++ b/restx-core/src/main/java/restx/validation/stereotypes/FormValidations.java @@ -0,0 +1,11 @@ +package restx.validation.stereotypes; + +import javax.validation.groups.Default; + +/** + * @author fcamblor + */ +public interface FormValidations { + public static interface Create extends Default{} + public static interface Update extends Default{} +} From 6d9e8f23c33ad6c28bc867fdf4a7dcfedcc1dcec Mon Sep 17 00:00:00 2001 From: fcamblor Date: Fri, 26 Dec 2014 12:17:41 +0100 Subject: [PATCH 08/15] validation - Providing @ValidatedFor validation groups to the bean validation validator --- .../processor/RestxAnnotationProcessor.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/restx-core-annotation-processor/src/main/java/restx/annotations/processor/RestxAnnotationProcessor.java b/restx-core-annotation-processor/src/main/java/restx/annotations/processor/RestxAnnotationProcessor.java index d18ba6e09..295c5833d 100644 --- a/restx-core-annotation-processor/src/main/java/restx/annotations/processor/RestxAnnotationProcessor.java +++ b/restx-core-annotation-processor/src/main/java/restx/annotations/processor/RestxAnnotationProcessor.java @@ -15,6 +15,7 @@ import restx.http.HttpStatus; import restx.security.PermitAll; import restx.security.RolesAllowed; +import restx.validation.ValidatedFor; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; @@ -39,6 +40,13 @@ public class RestxAnnotationProcessor extends RestxAbstractProcessor { final Template routerTpl; + private static final Function FQN_EXTRACTOR = new Function(){ + @Override + public String apply(Class clazz) { + return clazz.getCanonicalName(); + } + }; + public RestxAnnotationProcessor() { routerTpl = Mustaches.compile(RestxAnnotationProcessor.class, "RestxRouter.mustache"); } @@ -164,12 +172,19 @@ private void buildResourceMethodParams(ResourceMethodAnnotation annotation, Reso } } + ValidatedFor validatedFor = p.getAnnotation(ValidatedFor.class); + + String[] validationGroups = new String[0]; + if(validatedFor != null) { + validationGroups = Collections2.transform(Arrays.asList(validatedFor.value()), FQN_EXTRACTOR).toArray(new String[0]); + } + resourceMethod.parameters.add(new ResourceMethodParameter( p.asType().toString(), paramName, reqParamName, parameterKind, - new String[0])); + validationGroups)); } if (!pathParamNamesToMatch.isEmpty()) { error( From 4ab21068fb8ea6e6468657d6c8cb0c6e2bf2a933 Mon Sep 17 00:00:00 2001 From: fcamblor Date: Fri, 26 Dec 2014 12:33:22 +0100 Subject: [PATCH 09/15] validation - Provided samplest test cases for validation groups --- .../main/java/samplest/validation/POJO.java | 52 +++++++++++++++++++ .../java/samplest/validation/SubPOJO.java | 16 ++++++ .../validation/ValidationResource.java | 41 +++++++++++++++ .../core/Hello_msg_20140427_170121.spec.yaml | 11 ++++ .../specs/validation/createPOJO_ko.spec.yaml | 34 ++++++++++++ .../specs/validation/createPOJO_ok.spec.yaml | 14 +++++ .../specs/validation/updatePOJO_ko.spec.yaml | 24 +++++++++ .../specs/validation/updatePOJO_ok.spec.yaml | 9 ++++ .../validation/ValidationResourceTest.java | 13 +++++ 9 files changed, 214 insertions(+) create mode 100644 restx-samplest/src/main/java/samplest/validation/POJO.java create mode 100644 restx-samplest/src/main/java/samplest/validation/SubPOJO.java create mode 100644 restx-samplest/src/main/java/samplest/validation/ValidationResource.java create mode 100644 restx-samplest/src/main/resources/specs/core/Hello_msg_20140427_170121.spec.yaml create mode 100644 restx-samplest/src/main/resources/specs/validation/createPOJO_ko.spec.yaml create mode 100644 restx-samplest/src/main/resources/specs/validation/createPOJO_ok.spec.yaml create mode 100644 restx-samplest/src/main/resources/specs/validation/updatePOJO_ko.spec.yaml create mode 100644 restx-samplest/src/main/resources/specs/validation/updatePOJO_ok.spec.yaml create mode 100644 restx-samplest/src/test/java/samplest/validation/ValidationResourceTest.java diff --git a/restx-samplest/src/main/java/samplest/validation/POJO.java b/restx-samplest/src/main/java/samplest/validation/POJO.java new file mode 100644 index 000000000..f9b567670 --- /dev/null +++ b/restx-samplest/src/main/java/samplest/validation/POJO.java @@ -0,0 +1,52 @@ +package samplest.validation; + +import org.hibernate.validator.constraints.Email; +import restx.validation.stereotypes.FormValidations; + +import javax.validation.Valid; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +public class POJO { + @NotNull(groups={FormValidations.Update.class}) + Long id; + @NotNull + @Size(min=10, groups={ValidationResource.MyCustomValidationGroup.class}) + String name; + @Valid + SubPOJO subPOJO; + @Email + String email; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public SubPOJO getSubPOJO() { + return subPOJO; + } + + public void setSubPOJO(SubPOJO subPOJO) { + this.subPOJO = subPOJO; + } +} diff --git a/restx-samplest/src/main/java/samplest/validation/SubPOJO.java b/restx-samplest/src/main/java/samplest/validation/SubPOJO.java new file mode 100644 index 000000000..dd72ac8fa --- /dev/null +++ b/restx-samplest/src/main/java/samplest/validation/SubPOJO.java @@ -0,0 +1,16 @@ +package samplest.validation; + +import javax.validation.constraints.Size; + +public class SubPOJO { + @Size(min=10) + String label; + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } +} diff --git a/restx-samplest/src/main/java/samplest/validation/ValidationResource.java b/restx-samplest/src/main/java/samplest/validation/ValidationResource.java new file mode 100644 index 000000000..c702f2695 --- /dev/null +++ b/restx-samplest/src/main/java/samplest/validation/ValidationResource.java @@ -0,0 +1,41 @@ +package samplest.validation; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import restx.annotations.POST; +import restx.annotations.PUT; +import restx.annotations.Param; +import restx.annotations.RestxResource; +import restx.factory.Component; +import restx.security.PermitAll; +import restx.validation.ValidatedFor; +import restx.validation.stereotypes.FormValidations; + +/** + * @author fcamblor + */ +@RestxResource @Component +public class ValidationResource { + + private static final Logger LOG = LoggerFactory.getLogger(ValidationResource.class); + + public static interface MyCustomValidationGroup{} + + @PermitAll + @POST("/valid/pojos") + public void createPOJOWithoutAnnotation(POJO myPojo) { + LOG.info("Pojo {} {} created !", myPojo.getName(), myPojo.getSubPOJO().getLabel()); + } + + @PermitAll + @POST("/valid/pojos2") + public void createPOJOWithAnnotation(@ValidatedFor(FormValidations.Create.class) POJO myPojo) { + LOG.info("Pojo {} {} created !", myPojo.getName(), myPojo.getSubPOJO().getLabel()); + } + + @PermitAll + @PUT("/valid/pojos/{id}") + public void createPOJOWithoutAnnotation(Long id, @ValidatedFor({MyCustomValidationGroup.class, FormValidations.Update.class}) POJO myPojo) { + LOG.info("Pojo {} {} updated !", myPojo.getName(), myPojo.getSubPOJO().getLabel()); + } +} diff --git a/restx-samplest/src/main/resources/specs/core/Hello_msg_20140427_170121.spec.yaml b/restx-samplest/src/main/resources/specs/core/Hello_msg_20140427_170121.spec.yaml new file mode 100644 index 000000000..595683e91 --- /dev/null +++ b/restx-samplest/src/main/resources/specs/core/Hello_msg_20140427_170121.spec.yaml @@ -0,0 +1,11 @@ +title: Hello msg 20140427 170121 +given: + - time: 2014-04-27T17:01:21.795+02:00 +wts: + - when: | + GET core/hellomsg?who=xavier + Cookie: RestxSession={"_expires":"2014-05-27T17:01:21.795+02:00","principal":"admin","sessionKey":"81b92e9b-49eb-4c54-9c78-79a4a79feae5"}; RestxSessionSignature=g3KnRfPE2SAaoXbR3Iq7XHv5L3M= + then: | + { + "msg" : "hello xavier" + } diff --git a/restx-samplest/src/main/resources/specs/validation/createPOJO_ko.spec.yaml b/restx-samplest/src/main/resources/specs/validation/createPOJO_ko.spec.yaml new file mode 100644 index 000000000..13fa51d4b --- /dev/null +++ b/restx-samplest/src/main/resources/specs/validation/createPOJO_ko.spec.yaml @@ -0,0 +1,34 @@ +title: Create Validation POJO - KO +given: + - time: 2014-12-24T17:01:21.795+02:00 +wts: + - when: | + POST valid/pojos + {"email": "toto@acme.com", "subPOJO": { "label": "a very long label" }} + then: | + 400 + - when: | + POST valid/pojos + {"name": "blah", "email": "toto", "subPOJO": { "label": "a very long label" }} + then: | + 400 + - when: | + POST valid/pojos + {"name": "blah", "email": "toto@acme.com", "subPOJO": { "label": "a label" }} + then: | + 400 + - when: | + POST valid/pojos2 + {"email": "toto@acme.com", "subPOJO": { "label": "a very long label" }} + then: | + 400 + - when: | + POST valid/pojos2 + {"name": "blah", "email": "toto", "subPOJO": { "label": "a very long label" }} + then: | + 400 + - when: | + POST valid/pojos2 + {"name": "blah", "email": "toto@acme.com", "subPOJO": { "label": "a label" }} + then: | + 400 diff --git a/restx-samplest/src/main/resources/specs/validation/createPOJO_ok.spec.yaml b/restx-samplest/src/main/resources/specs/validation/createPOJO_ok.spec.yaml new file mode 100644 index 000000000..82ecd04f9 --- /dev/null +++ b/restx-samplest/src/main/resources/specs/validation/createPOJO_ok.spec.yaml @@ -0,0 +1,14 @@ +title: Create Validation POJO - OK +given: + - time: 2014-12-24T17:01:21.795+02:00 +wts: + - when: | + POST valid/pojos + {"name": "blah", "email": "toto@acme.com", "subPOJO": { "label": "a very long label" }} + then: | + 204 + - when: | + POST valid/pojos2 + {"name": "blah", "email": "toto@acme.com", "subPOJO": { "label": "a very long label" }} + then: | + 204 diff --git a/restx-samplest/src/main/resources/specs/validation/updatePOJO_ko.spec.yaml b/restx-samplest/src/main/resources/specs/validation/updatePOJO_ko.spec.yaml new file mode 100644 index 000000000..8cde5c8e4 --- /dev/null +++ b/restx-samplest/src/main/resources/specs/validation/updatePOJO_ko.spec.yaml @@ -0,0 +1,24 @@ +title: Update Validation POJO - KO +given: + - time: 2014-12-24T17:01:21.795+02:00 +wts: + - when: | + PUT valid/pojos/123 + {"name": "a very long name", "email": "toto@acme.com", "subPOJO": { "label": "a very long label" }} + then: | + 400 + - when: | + PUT valid/pojos/123 + {"id": 123, "name": "a name", "email": "toto@acme.com", "subPOJO": { "label": "a very long label" }} + then: | + 400 + - when: | + PUT valid/pojos/123 + {"id": 123, "name": "a very long name", "email": "toto", "subPOJO": { "label": "a very long label" }} + then: | + 400 + - when: | + PUT valid/pojos/123 + {"id": 123, "name": "a very long name", "email": "toto@acme.com", "subPOJO": { "label": "a label" }} + then: | + 400 diff --git a/restx-samplest/src/main/resources/specs/validation/updatePOJO_ok.spec.yaml b/restx-samplest/src/main/resources/specs/validation/updatePOJO_ok.spec.yaml new file mode 100644 index 000000000..286f73326 --- /dev/null +++ b/restx-samplest/src/main/resources/specs/validation/updatePOJO_ok.spec.yaml @@ -0,0 +1,9 @@ +title: Create Validation POJO - OK +given: + - time: 2014-12-24T17:01:21.795+02:00 +wts: + - when: | + PUT valid/pojos/123 + {"id": 123, "name": "a very long name", "email": "toto@acme.com", "subPOJO": { "label": "a very long label" }} + then: | + 204 diff --git a/restx-samplest/src/test/java/samplest/validation/ValidationResourceTest.java b/restx-samplest/src/test/java/samplest/validation/ValidationResourceTest.java new file mode 100644 index 000000000..e1d6d7c6e --- /dev/null +++ b/restx-samplest/src/test/java/samplest/validation/ValidationResourceTest.java @@ -0,0 +1,13 @@ +package samplest.validation; + +import org.junit.runner.RunWith; +import restx.tests.FindSpecsIn; +import restx.tests.RestxSpecTestsRunner; + +/** + * @author fcamblor + */ +@RunWith(RestxSpecTestsRunner.class) +@FindSpecsIn("specs/validation") +public class ValidationResourceTest { +} From 4afdbce86f5775d7452c938dd49a55c2090ae55b Mon Sep 17 00:00:00 2001 From: fcamblor Date: Fri, 26 Dec 2014 12:45:33 +0100 Subject: [PATCH 10/15] Provided some plugin commented code to debug annotation processing on samplest module --- restx-samplest/pom.xml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/restx-samplest/pom.xml b/restx-samplest/pom.xml index 3045f5022..ce89aefee 100644 --- a/restx-samplest/pom.xml +++ b/restx-samplest/pom.xml @@ -133,6 +133,20 @@ -restx-target-dir ${project.basedir}/target/classes + From d65fd018bd487ca1cf49e1e67809fb2fa2757953 Mon Sep 17 00:00:00 2001 From: fcamblor Date: Wed, 24 Dec 2014 18:53:31 +0100 Subject: [PATCH 11/15] validation - Fixing some ClassNotFoundException when retrieving @ValidatedFor.value() since in most of cases, plain Class instance won't be available yet at annotation processing time Note that current impl has a drawback by relying on com.sun.tools.javac.code hidden package --- .../java/restx/annotations/Annotations.java | 38 +++++++++++++++++++ .../processor/RestxAnnotationProcessor.java | 2 +- .../stereotypes/FormValidations.java | 3 ++ 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 restx-core-annotation-processor/src/main/java/restx/annotations/Annotations.java diff --git a/restx-core-annotation-processor/src/main/java/restx/annotations/Annotations.java b/restx-core-annotation-processor/src/main/java/restx/annotations/Annotations.java new file mode 100644 index 000000000..eae7b200e --- /dev/null +++ b/restx-core-annotation-processor/src/main/java/restx/annotations/Annotations.java @@ -0,0 +1,38 @@ +package restx.annotations; + +import com.sun.tools.javac.code.Attribute; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.DeclaredType; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * @author fcamblor + */ +public class Annotations { + public static String[] getAnnotationClassValuesAsFQCN(VariableElement p, Class annotationClazz, String methodName) { + List annotationMirrors = p.getAnnotationMirrors(); + for(AnnotationMirror annotationMirror : annotationMirrors){ + if(annotationMirror.getAnnotationType().toString().equals(annotationClazz.getCanonicalName())){ + for(Map.Entry entry : annotationMirror.getElementValues().entrySet()){ + if(entry.getKey().getSimpleName().contentEquals(methodName)){ + Attribute.Array array = (Attribute.Array) entry.getValue(); + + List fqcns = new ArrayList<>(); + for(Attribute attribute : array.getValue()) { + DeclaredType type = (DeclaredType) attribute.getValue(); + fqcns.add(type.toString()); + } + return fqcns.toArray(new String[fqcns.size()]); + } + } + } + } + return null; + } +} diff --git a/restx-core-annotation-processor/src/main/java/restx/annotations/processor/RestxAnnotationProcessor.java b/restx-core-annotation-processor/src/main/java/restx/annotations/processor/RestxAnnotationProcessor.java index 295c5833d..af8859f2b 100644 --- a/restx-core-annotation-processor/src/main/java/restx/annotations/processor/RestxAnnotationProcessor.java +++ b/restx-core-annotation-processor/src/main/java/restx/annotations/processor/RestxAnnotationProcessor.java @@ -176,7 +176,7 @@ private void buildResourceMethodParams(ResourceMethodAnnotation annotation, Reso String[] validationGroups = new String[0]; if(validatedFor != null) { - validationGroups = Collections2.transform(Arrays.asList(validatedFor.value()), FQN_EXTRACTOR).toArray(new String[0]); + validationGroups = Annotations.getAnnotationClassValuesAsFQCN(p, ValidatedFor.class, "value"); } resourceMethod.parameters.add(new ResourceMethodParameter( diff --git a/restx-core/src/main/java/restx/validation/stereotypes/FormValidations.java b/restx-core/src/main/java/restx/validation/stereotypes/FormValidations.java index 034860616..4aaec3864 100644 --- a/restx-core/src/main/java/restx/validation/stereotypes/FormValidations.java +++ b/restx-core/src/main/java/restx/validation/stereotypes/FormValidations.java @@ -6,6 +6,9 @@ * @author fcamblor */ public interface FormValidations { + public static final String DefaultFQN = "javax.validation.groups.Default"; + public static final String CreateFQN = "restx.validation.stereotypes.FormValidations.Create"; public static interface Create extends Default{} + public static final String UpdateFQN = "restx.validation.stereotypes.FormValidations.Update"; public static interface Update extends Default{} } From 333ea6a1164eed43be6192fbbb72f6aa1cf81b21 Mon Sep 17 00:00:00 2001 From: fcamblor Date: Fri, 2 Jan 2015 12:24:28 +0100 Subject: [PATCH 12/15] validation - provided named component to ignore the ignoreXmlConfiguration() call if needed --- .../java/restx/validation/ValidatorFactory.java | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/restx-validation/src/main/java/restx/validation/ValidatorFactory.java b/restx-validation/src/main/java/restx/validation/ValidatorFactory.java index e1c600043..b8cd83b0a 100644 --- a/restx-validation/src/main/java/restx/validation/ValidatorFactory.java +++ b/restx-validation/src/main/java/restx/validation/ValidatorFactory.java @@ -1,6 +1,7 @@ package restx.validation; import org.hibernate.validator.HibernateValidator; +import org.hibernate.validator.HibernateValidatorConfiguration; import restx.factory.Module; import restx.factory.Name; import restx.factory.Provides; @@ -17,11 +18,21 @@ @Module public class ValidatorFactory { public static final String VALIDATOR_NAME = "hibernate.validator"; + public static final String IGNORE_XML_CONFIGURATION_NAME = "hibernate.validator.ignore.xml.configuration"; public static final Name VALIDATOR = Name.of(Validator.class, VALIDATOR_NAME); @Provides @Named(VALIDATOR_NAME) - public Validator validator() { - return Validation.byProvider(HibernateValidator.class).configure() - .ignoreXmlConfiguration().buildValidatorFactory().getValidator(); + public Validator validator(@Named(IGNORE_XML_CONFIGURATION_NAME) Boolean ignoreXmlConfiguration) { + HibernateValidatorConfiguration config = Validation.byProvider(HibernateValidator.class).configure(); + if(ignoreXmlConfiguration) { + config.ignoreXmlConfiguration(); + } + return config.buildValidatorFactory().getValidator(); + } + + // Perf improvement to greatly fasten startup time + @Provides @Named(IGNORE_XML_CONFIGURATION_NAME) + public Boolean ignoreXmlConfigurationFlag(){ + return Boolean.TRUE; } } From 28a7379262358ff924d6fcf56f45d1326a39f54b Mon Sep 17 00:00:00 2001 From: fcamblor Date: Fri, 2 Jan 2015 12:43:40 +0100 Subject: [PATCH 13/15] validation - Provided md.restx.json file and corresponding module.ivy file --- restx-validation/md.restx.json | 19 +++++++++++++++++++ restx-validation/module.ivy | 22 ++++++++++++++++++++++ restx.build.properties.json | 1 + 3 files changed, 42 insertions(+) create mode 100644 restx-validation/md.restx.json create mode 100644 restx-validation/module.ivy diff --git a/restx-validation/md.restx.json b/restx-validation/md.restx.json new file mode 100644 index 000000000..1489f5671 --- /dev/null +++ b/restx-validation/md.restx.json @@ -0,0 +1,19 @@ +{ + "parent": "io.restx:restx-parent:${restx.version}", + "module": "io.restx:restx-validation:${restx.version}", + + "properties": { + "@files": ["../restx.build.properties.json"] + }, + + "dependencies": { + "compile": [ + "io.restx:restx-factory:${restx.version}", + "org.hibernate:hibernate-validator:${hibernate-validator.version}", + "javax.el:el-api:${el.api.version}" + ], + "runtime": [ + "org.glassfish.web:el-impl:${el.api.version}" + ] + } +} diff --git a/restx-validation/module.ivy b/restx-validation/module.ivy new file mode 100644 index 000000000..d04220028 --- /dev/null +++ b/restx-validation/module.ivy @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + diff --git a/restx.build.properties.json b/restx.build.properties.json index d134bf213..762e5b273 100644 --- a/restx.build.properties.json +++ b/restx.build.properties.json @@ -16,6 +16,7 @@ "snakeyaml.version": "1.13", "hibernate-validator.version": "5.0.1.Final", + "el.api.version": "2.2", "reflections.version": "0.9.9-RC1", "janino.version": "2.6.1", From 8be49651b08d58b00a824b41624f1dfb748a1ba5 Mon Sep 17 00:00:00 2001 From: fcamblor Date: Sat, 3 Jan 2015 14:49:28 +0100 Subject: [PATCH 14/15] validation - Provided deprecated Validations.checkValid(Validator,T) to avoid some backward incompatibilities on existing generated code --- .../src/main/java/restx/validation/Validations.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/restx-core/src/main/java/restx/validation/Validations.java b/restx-core/src/main/java/restx/validation/Validations.java index a24a57ff4..bef2dcb4f 100644 --- a/restx-core/src/main/java/restx/validation/Validations.java +++ b/restx-core/src/main/java/restx/validation/Validations.java @@ -14,6 +14,15 @@ * Time: 9:57 PM */ public class Validations { + + /** + * @deprecated Kept for backward compat. Use checkValid(Optional<Validator>, T, Class...) instead + */ + @Deprecated + public static T checkValid(Validator validator, T o) { + return checkValid(Optional.of(validator), o); + } + public static T checkValid(Optional validator, T o, Class... groups) { if(validator.isPresent()) { if(groups == null || groups.length==0) { From 6bff1cc05b43a7b0d215808569d49847071d826f Mon Sep 17 00:00:00 2001 From: fcamblor Date: Sat, 3 Jan 2015 14:59:40 +0100 Subject: [PATCH 15/15] validation - Added restx-validation module description in README --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1e1151f4a..b90040240 100644 --- a/README.md +++ b/README.md @@ -48,12 +48,13 @@ Main modules: - `restx-factory-testing`: A module dedicated to test `restx-factory` features involving annotation processing. - `restx-classloader`: Hot reload / hot compile support - `restx-build`: The very simple tool which generates POM / Ivy files from `md.restx.json` files -- `restx-core`: Core module, includes the REST framework, base security, JSON support, Validation, ... +- `restx-core`: Core module, includes the REST framework, base security, JSON support, ... - `restx-core-annotation-processor`: Annotation processing to generate routers based on RESTX core annotations. Needed at build time only. - `restx-security-basic`: A basic implementation of security, still enough in many cases. - `restx-specs-tests`: Enables using RESTX specs as JUnit tests. - `restx-specs-server`: Enables using RESTX specs as HTTP mocks. - `restx-i18n`: I18n Support +- `restx-validation`: Bean validation support (based on `hibernate-validator` implementation) for POJOs BODY parameters & query parameters Admin console modules: