Permalink
Browse files

Merge branch 'validations-enhancements' into master, fixing #137

  • Loading branch information...
fcamblor committed Jan 22, 2015
2 parents 3a25964 + 6bff1cc commit a516b4ea58b35460da7d0dcd91fa1966ebe2479c
Showing with 500 additions and 37 deletions.
  1. +2 −1 README.md
  2. +1 −0 pom.xml
  3. +38 −0 restx-core-annotation-processor/src/main/java/restx/annotations/Annotations.java
  4. +38 −3 ...core-annotation-processor/src/main/java/restx/annotations/processor/RestxAnnotationProcessor.java
  5. +1 −1 restx-core-annotation-processor/src/main/resources/restx/annotations/processor/RestxRouter.mustache
  6. +4 −4 restx-core/pom.xml
  7. +13 −0 restx-core/src/main/java/restx/validation/ValidatedFor.java
  8. +21 −3 restx-core/src/main/java/restx/validation/Validations.java
  9. +0 −25 restx-core/src/main/java/restx/validation/ValidatorFactory.java
  10. +14 −0 restx-core/src/main/java/restx/validation/stereotypes/FormValidations.java
  11. +19 −0 restx-samplest/pom.xml
  12. +52 −0 restx-samplest/src/main/java/samplest/validation/POJO.java
  13. +16 −0 restx-samplest/src/main/java/samplest/validation/SubPOJO.java
  14. +41 −0 restx-samplest/src/main/java/samplest/validation/ValidationResource.java
  15. +11 −0 restx-samplest/src/main/resources/specs/core/Hello_msg_20140427_170121.spec.yaml
  16. +34 −0 restx-samplest/src/main/resources/specs/validation/createPOJO_ko.spec.yaml
  17. +14 −0 restx-samplest/src/main/resources/specs/validation/createPOJO_ok.spec.yaml
  18. +24 −0 restx-samplest/src/main/resources/specs/validation/updatePOJO_ko.spec.yaml
  19. +9 −0 restx-samplest/src/main/resources/specs/validation/updatePOJO_ok.spec.yaml
  20. +13 −0 restx-samplest/src/test/java/samplest/validation/ValidationResourceTest.java
  21. +19 −0 restx-validation/md.restx.json
  22. +22 −0 restx-validation/module.ivy
  23. +55 −0 restx-validation/pom.xml
  24. +38 −0 restx-validation/src/main/java/restx/validation/ValidatorFactory.java
  25. +1 −0 restx.build.properties.json
View
@@ -47,7 +47,7 @@ Here is a brief summary of each module:
- `restx-factory`: RESTX Dependency Injection (DI) container. Brought as transitive dependency from restx-core.
- `restx-classloader`: Hot reload / hot compile support
- `restx-apidocs-doclet`: Some javadoc doclets used when generating apidocs
-- `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, ...
*By relying on `restx-core` module, every modules described above will be retrieved as transitive dependencies*
@@ -63,6 +63,7 @@ Here is a brief summary of each module:
- `restx-specs-tests-java8`: Support for java 8 time API during specs tests
- `restx-specs-server`: Enables using RESTX specs as HTTP mocks (running a server serving spec files responses given spec files requests).
- `restx-factory-testing`: A module dedicated to test `restx-factory` features involving annotation processing.
+- `restx-validation`: Bean validation support (based on `hibernate-validator` implementation) for POJOs BODY parameters
#### MongoDB support through Jongo API:
View
@@ -158,6 +158,7 @@
<module>restx-shell-manager</module>
<module>restx-build-shell</module>
<module>restx-specs-shell</module>
+ <module>restx-validation</module>
</modules>
@@ -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<? extends AnnotationMirror> annotationMirrors = p.getAnnotationMirrors();
+ for(AnnotationMirror annotationMirror : annotationMirrors){
+ if(annotationMirror.getAnnotationType().toString().equals(annotationClazz.getCanonicalName())){
+ for(Map.Entry<? extends ExecutableElement,? extends AnnotationValue> entry : annotationMirror.getElementValues().entrySet()){
+ if(entry.getKey().getSimpleName().contentEquals(methodName)){
+ Attribute.Array array = (Attribute.Array) entry.getValue();
+
+ List<String> 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;
+ }
+}
@@ -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.*;
@@ -14,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;
@@ -38,6 +40,13 @@
public class RestxAnnotationProcessor extends RestxAbstractProcessor {
final Template routerTpl;
+ private static final Function<Class,String> FQN_EXTRACTOR = new Function<Class,String>(){
+ @Override
+ public String apply(Class clazz) {
+ return clazz.getCanonicalName();
+ }
+ };
+
public RestxAnnotationProcessor() {
routerTpl = Mustaches.compile(RestxAnnotationProcessor.class, "RestxRouter.mustache");
}
@@ -163,11 +172,19 @@ private void buildResourceMethodParams(ResourceMethodAnnotation annotation, Reso
}
}
+ ValidatedFor validatedFor = p.getAnnotation(ValidatedFor.class);
+
+ String[] validationGroups = new String[0];
+ if(validatedFor != null) {
+ validationGroups = Annotations.getAnnotationClassValuesAsFQCN(p, ValidatedFor.class, "value");
+ }
+
resourceMethod.parameters.add(new ResourceMethodParameter(
p.asType().toString(),
paramName,
reqParamName,
- parameterKind));
+ parameterKind,
+ validationGroups));
}
if (!pathParamNamesToMatch.isEmpty()) {
error(
@@ -492,8 +509,16 @@ private ResourceMethodAnnotation(String httpMethod, Element methodElem, String p
final String name;
final String reqParamName;
final ResourceMethodParameterKind kind;
+ final List<String> validationGroupsFQNs;
+
+ private static final Function<String, String> CLASS_APPENDER_FCT = new Function<String, String>() {
+ @Override
+ public String apply(String fqn) {
+ return fqn+".class";
+ }
+ };
- private ResourceMethodParameter(String type, String name, String reqParamName, ResourceMethodParameterKind kind) {
+ 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 +538,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<String> 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 +573,8 @@ public String fetchFromReqCode(ResourceMethodParameter parameter) {
},
BODY {
public String fetchFromReqCode(ResourceMethodParameter parameter) {
- return String.format("checkValid(validator, body)", parameter.type);
+ Optional<String> validationGroupsExpr = parameter.joinedValidationGroupFQNExpression();
+ return String.format("checkValid(validator, body%s)", validationGroupsExpr.isPresent()?","+validationGroupsExpr.get():"");
}
},
CONTEXT {
@@ -32,7 +32,7 @@ public class {{router}} extends RestxRouter {
final EntityRequestBodyReaderRegistry readerRegistry,
final EntityResponseWriterRegistry writerRegistry,
final MainStringConverter converter,
- final Validator validator,
+ final Optional<Validator> validator,
final RestxSecurityManager securityManager) {
super(
"{{routerGroup}}", "{{router}}", new RestxRoute[] {
View
@@ -25,7 +25,7 @@
<joda-time.version>2.3</joda-time.version>
<jackson.version>2.3.3</jackson.version>
<snakeyaml.version>1.13</snakeyaml.version>
- <hibernate-validator.version>5.0.1.Final</hibernate-validator.version>
+ <validation-api.version>1.1.0.Final</validation-api.version>
<reflections.version>0.9.9-RC1</reflections.version>
<javax.inject.version>1</javax.inject.version>
<junit.version>4.11</junit.version>
@@ -94,9 +94,9 @@
<version>${slf4j-api.version}</version>
</dependency>
<dependency>
- <groupId>org.hibernate</groupId>
- <artifactId>hibernate-validator</artifactId>
- <version>${hibernate-validator.version}</version>
+ <groupId>javax.validation</groupId>
+ <artifactId>validation-api</artifactId>
+ <version>${validation-api.version}</version>
</dependency>
<dependency>
<groupId>org.reflections</groupId>
@@ -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();
+}
@@ -1,9 +1,11 @@
package restx.validation;
import com.google.common.base.Joiner;
+import com.google.common.base.Optional;
import javax.validation.ConstraintViolation;
import javax.validation.Validator;
+import javax.validation.groups.Default;
import java.util.Set;
/**
@@ -12,11 +14,27 @@
* Time: 9:57 PM
*/
public class Validations {
+
+ /**
+ * @deprecated Kept for backward compat. Use checkValid(Optional&lt;Validator&gt;, T, Class...) instead
+ */
+ @Deprecated
public static <T> T checkValid(Validator validator, T o) {
- Set<ConstraintViolation<T>> violations = validator.validate(o);
- if (!violations.isEmpty()) {
- throw new IllegalArgumentException(Joiner.on(", ").join(violations));
+ return checkValid(Optional.of(validator), o);
+ }
+
+ public static <T> T checkValid(Optional<Validator> validator, T o, Class... groups) {
+ if(validator.isPresent()) {
+ if(groups == null || groups.length==0) {
+ groups = new Class[]{ Default.class };
+ }
+
+ Set<ConstraintViolation<T>> violations = validator.get().validate(o, groups);
+ if (!violations.isEmpty()) {
+ throw new IllegalArgumentException(Joiner.on(", ").join(violations));
+ }
}
+
return o;
}
}
@@ -1,25 +0,0 @@
-package restx.validation;
-
-import org.hibernate.validator.HibernateValidator;
-import restx.factory.*;
-
-import javax.inject.Named;
-import javax.validation.Validation;
-import javax.validation.Validator;
-
-/**
- * User: xavierhanin
- * Date: 2/3/13
- * Time: 9:48 PM
- */
-@Module
-public class ValidatorFactory {
- public static final String VALIDATOR_NAME = "validator";
- public static final Name<Validator> VALIDATOR = Name.of(Validator.class, VALIDATOR_NAME);
-
- @Provides @Named(VALIDATOR_NAME)
- public Validator validator() {
- return Validation.byProvider(HibernateValidator.class).configure()
- .ignoreXmlConfiguration().buildValidatorFactory().getValidator();
- }
-}
@@ -0,0 +1,14 @@
+package restx.validation.stereotypes;
+
+import javax.validation.groups.Default;
+
+/**
+ * @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{}
+}
View
@@ -30,6 +30,11 @@
<artifactId>restx-core</artifactId>
<version>${restx.version}</version>
</dependency>
+ <dependency>
+ <groupId>io.restx</groupId>
+ <artifactId>restx-validation</artifactId>
+ <version>${restx.version}</version>
+ </dependency>
<dependency>
<groupId>io.restx</groupId>
<artifactId>restx-core-annotation-processor</artifactId>
@@ -123,6 +128,20 @@
<additionalparam>-restx-target-dir ${project.basedir}/target/classes</additionalparam>
</configuration>
</plugin>
+ <!-- Uncomment to debug annotation processing on samplest...
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <verbose>true</verbose>
+ <fork>true</fork>
+ <compilerArgs>
+ <arg>-J-Xdebug</arg>
+ <arg>-J-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000</arg>
+ </compilerArgs>
+ </configuration>
+ </plugin>
+ -->
</plugins>
</build>
</project>
@@ -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;
+ }
+}
@@ -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;
+ }
+}
Oops, something went wrong.

0 comments on commit a516b4e

Please sign in to comment.