Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

@ValidatedFor annotation support for validation groups #137

Closed
wants to merge 15 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
6b440af
validation - Considering Validator components as optional components …
fcamblor Dec 22, 2014
6989675
validation - Extracting hibernate validator dependency from restx-cor…
fcamblor Dec 22, 2014
91891fe
validation - Updated name for default Validator (from "validator" to …
fcamblor Dec 22, 2014
616a005
validation - Providing el api & impl dependencies in restx-validation…
fcamblor Dec 22, 2014
4d90301
validation - added restx-validation dep on restx-samplest
fcamblor Dec 22, 2014
0ad8d57
validation - Preparing ResourceMethodParameter to allow to pass it
fcamblor Dec 26, 2014
84eab2d
Introduced @ValidatedFor annotation which will be used (soon) to vali…
fcamblor Dec 24, 2014
6d9e8f2
validation - Providing @ValidatedFor validation groups to the bean va…
fcamblor Dec 26, 2014
4ab2106
validation - Provided samplest test cases for validation groups
fcamblor Dec 26, 2014
4afdbce
Provided some plugin commented code to debug annotation processing on…
fcamblor Dec 26, 2014
d65fd01
validation - Fixing some ClassNotFoundException when retrieving @Vali…
fcamblor Dec 24, 2014
333ea6a
validation - provided named component to ignore the ignoreXmlConfigur…
fcamblor Jan 2, 2015
28a7379
validation - Provided md.restx.json file and corresponding module.ivy…
fcamblor Jan 2, 2015
8be4965
validation - Provided deprecated Validations.checkValid(Validator,T) …
fcamblor Jan 3, 2015
6bff1cc
validation - Added restx-validation module description in README
fcamblor Jan 3, 2015
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@
<module>restx-shell-manager</module>
<module>restx-build-shell</module>
<module>restx-specs-shell</module>
<module>restx-validation</module>
</modules>


Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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.*;
Expand All @@ -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;
Expand All @@ -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");
}
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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;
Expand All @@ -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)));
}
}
}

Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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[] {
Expand Down
8 changes: 4 additions & 4 deletions restx-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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>
Expand Down
13 changes: 13 additions & 0 deletions restx-core/src/main/java/restx/validation/ValidatedFor.java
Original file line number Diff line number Diff line change
@@ -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();
}
24 changes: 21 additions & 3 deletions restx-core/src/main/java/restx/validation/Validations.java
Original file line number Diff line number Diff line change
@@ -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;

/**
Expand All @@ -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;
}
}
25 changes: 0 additions & 25 deletions restx-core/src/main/java/restx/validation/ValidatorFactory.java

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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{}
}
19 changes: 19 additions & 0 deletions restx-samplest/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -128,6 +133,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>
52 changes: 52 additions & 0 deletions restx-samplest/src/main/java/samplest/validation/POJO.java
Original file line number Diff line number Diff line change
@@ -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;
}
}
16 changes: 16 additions & 0 deletions restx-samplest/src/main/java/samplest/validation/SubPOJO.java
Original file line number Diff line number Diff line change
@@ -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;
}
}