Skip to content
Permalink
Browse files
Merge branch 'validations-enhancements' into master, fixing #137
  • Loading branch information
fcamblor committed Jan 22, 2015
2 parents 3a25964 + 6bff1cc commit a516b4e
Show file tree
Hide file tree
Showing 25 changed files with 500 additions and 37 deletions.
@@ -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:
@@ -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 static class ResourceMethodParameter {
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[] {
@@ -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;
}
}

This file was deleted.

@@ -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{}
}
@@ -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;
}
}

0 comments on commit a516b4e

Please sign in to comment.