Skip to content

Commit a516b4e

Browse files
committed
Merge branch 'validations-enhancements' into master, fixing #137
2 parents 3a25964 + 6bff1cc commit a516b4e

25 files changed

Lines changed: 500 additions & 37 deletions

File tree

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ Here is a brief summary of each module:
4747
- `restx-factory`: RESTX Dependency Injection (DI) container. Brought as transitive dependency from restx-core.
4848
- `restx-classloader`: Hot reload / hot compile support
4949
- `restx-apidocs-doclet`: Some javadoc doclets used when generating apidocs
50-
- `restx-core`: Core module, includes the REST framework, base security, JSON support, Validation, ...
50+
- `restx-core`: Core module, includes the REST framework, base security, JSON support, ...
5151

5252
*By relying on `restx-core` module, every modules described above will be retrieved as transitive dependencies*
5353

@@ -63,6 +63,7 @@ Here is a brief summary of each module:
6363
- `restx-specs-tests-java8`: Support for java 8 time API during specs tests
6464
- `restx-specs-server`: Enables using RESTX specs as HTTP mocks (running a server serving spec files responses given spec files requests).
6565
- `restx-factory-testing`: A module dedicated to test `restx-factory` features involving annotation processing.
66+
- `restx-validation`: Bean validation support (based on `hibernate-validator` implementation) for POJOs BODY parameters
6667

6768

6869
#### MongoDB support through Jongo API:

pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@
158158
<module>restx-shell-manager</module>
159159
<module>restx-build-shell</module>
160160
<module>restx-specs-shell</module>
161+
<module>restx-validation</module>
161162
</modules>
162163

163164

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package restx.annotations;
2+
3+
import com.sun.tools.javac.code.Attribute;
4+
5+
import javax.lang.model.element.AnnotationMirror;
6+
import javax.lang.model.element.AnnotationValue;
7+
import javax.lang.model.element.ExecutableElement;
8+
import javax.lang.model.element.VariableElement;
9+
import javax.lang.model.type.DeclaredType;
10+
import java.util.ArrayList;
11+
import java.util.List;
12+
import java.util.Map;
13+
14+
/**
15+
* @author fcamblor
16+
*/
17+
public class Annotations {
18+
public static String[] getAnnotationClassValuesAsFQCN(VariableElement p, Class annotationClazz, String methodName) {
19+
List<? extends AnnotationMirror> annotationMirrors = p.getAnnotationMirrors();
20+
for(AnnotationMirror annotationMirror : annotationMirrors){
21+
if(annotationMirror.getAnnotationType().toString().equals(annotationClazz.getCanonicalName())){
22+
for(Map.Entry<? extends ExecutableElement,? extends AnnotationValue> entry : annotationMirror.getElementValues().entrySet()){
23+
if(entry.getKey().getSimpleName().contentEquals(methodName)){
24+
Attribute.Array array = (Attribute.Array) entry.getValue();
25+
26+
List<String> fqcns = new ArrayList<>();
27+
for(Attribute attribute : array.getValue()) {
28+
DeclaredType type = (DeclaredType) attribute.getValue();
29+
fqcns.add(type.toString());
30+
}
31+
return fqcns.toArray(new String[fqcns.size()]);
32+
}
33+
}
34+
}
35+
}
36+
return null;
37+
}
38+
}

restx-core-annotation-processor/src/main/java/restx/annotations/processor/RestxAnnotationProcessor.java

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package restx.annotations.processor;
22

33
import com.google.common.base.CaseFormat;
4+
import com.google.common.base.Function;
45
import com.google.common.base.Joiner;
56
import com.google.common.base.Optional;
67
import com.google.common.collect.*;
@@ -14,6 +15,7 @@
1415
import restx.http.HttpStatus;
1516
import restx.security.PermitAll;
1617
import restx.security.RolesAllowed;
18+
import restx.validation.ValidatedFor;
1719

1820
import javax.annotation.processing.RoundEnvironment;
1921
import javax.annotation.processing.SupportedAnnotationTypes;
@@ -38,6 +40,13 @@
3840
public class RestxAnnotationProcessor extends RestxAbstractProcessor {
3941
final Template routerTpl;
4042

43+
private static final Function<Class,String> FQN_EXTRACTOR = new Function<Class,String>(){
44+
@Override
45+
public String apply(Class clazz) {
46+
return clazz.getCanonicalName();
47+
}
48+
};
49+
4150
public RestxAnnotationProcessor() {
4251
routerTpl = Mustaches.compile(RestxAnnotationProcessor.class, "RestxRouter.mustache");
4352
}
@@ -163,11 +172,19 @@ private void buildResourceMethodParams(ResourceMethodAnnotation annotation, Reso
163172
}
164173
}
165174

175+
ValidatedFor validatedFor = p.getAnnotation(ValidatedFor.class);
176+
177+
String[] validationGroups = new String[0];
178+
if(validatedFor != null) {
179+
validationGroups = Annotations.getAnnotationClassValuesAsFQCN(p, ValidatedFor.class, "value");
180+
}
181+
166182
resourceMethod.parameters.add(new ResourceMethodParameter(
167183
p.asType().toString(),
168184
paramName,
169185
reqParamName,
170-
parameterKind));
186+
parameterKind,
187+
validationGroups));
171188
}
172189
if (!pathParamNamesToMatch.isEmpty()) {
173190
error(
@@ -492,8 +509,16 @@ private static class ResourceMethodParameter {
492509
final String name;
493510
final String reqParamName;
494511
final ResourceMethodParameterKind kind;
512+
final List<String> validationGroupsFQNs;
513+
514+
private static final Function<String, String> CLASS_APPENDER_FCT = new Function<String, String>() {
515+
@Override
516+
public String apply(String fqn) {
517+
return fqn+".class";
518+
}
519+
};
495520

496-
private ResourceMethodParameter(String type, String name, String reqParamName, ResourceMethodParameterKind kind) {
521+
private ResourceMethodParameter(String type, String name, String reqParamName, ResourceMethodParameterKind kind, String[] validationGroupsFQNs) {
497522
Matcher guavaOptionalMatcher = guavaOptionalPattern.matcher(type);
498523
Matcher java8OptionalMatcher = java8OptionalPattern.matcher(type);
499524
this.realType = type;
@@ -513,6 +538,15 @@ private ResourceMethodParameter(String type, String name, String reqParamName, R
513538
this.name = name;
514539
this.reqParamName = reqParamName;
515540
this.kind = kind;
541+
this.validationGroupsFQNs = Arrays.asList(validationGroupsFQNs);
542+
}
543+
544+
public Optional<String> joinedValidationGroupFQNExpression(){
545+
if(this.validationGroupsFQNs == null || this.validationGroupsFQNs.isEmpty()) {
546+
return Optional.absent();
547+
} else {
548+
return Optional.of(Joiner.on(", ").join(Iterables.transform(this.validationGroupsFQNs, CLASS_APPENDER_FCT)));
549+
}
516550
}
517551
}
518552

@@ -539,7 +573,8 @@ public String fetchFromReqCode(ResourceMethodParameter parameter) {
539573
},
540574
BODY {
541575
public String fetchFromReqCode(ResourceMethodParameter parameter) {
542-
return String.format("checkValid(validator, body)", parameter.type);
576+
Optional<String> validationGroupsExpr = parameter.joinedValidationGroupFQNExpression();
577+
return String.format("checkValid(validator, body%s)", validationGroupsExpr.isPresent()?","+validationGroupsExpr.get():"");
543578
}
544579
},
545580
CONTEXT {

restx-core-annotation-processor/src/main/resources/restx/annotations/processor/RestxRouter.mustache

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public class {{router}} extends RestxRouter {
3232
final EntityRequestBodyReaderRegistry readerRegistry,
3333
final EntityResponseWriterRegistry writerRegistry,
3434
final MainStringConverter converter,
35-
final Validator validator,
35+
final Optional<Validator> validator,
3636
final RestxSecurityManager securityManager) {
3737
super(
3838
"{{routerGroup}}", "{{router}}", new RestxRoute[] {

restx-core/pom.xml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
<joda-time.version>2.3</joda-time.version>
2626
<jackson.version>2.3.3</jackson.version>
2727
<snakeyaml.version>1.13</snakeyaml.version>
28-
<hibernate-validator.version>5.0.1.Final</hibernate-validator.version>
28+
<validation-api.version>1.1.0.Final</validation-api.version>
2929
<reflections.version>0.9.9-RC1</reflections.version>
3030
<javax.inject.version>1</javax.inject.version>
3131
<junit.version>4.11</junit.version>
@@ -94,9 +94,9 @@
9494
<version>${slf4j-api.version}</version>
9595
</dependency>
9696
<dependency>
97-
<groupId>org.hibernate</groupId>
98-
<artifactId>hibernate-validator</artifactId>
99-
<version>${hibernate-validator.version}</version>
97+
<groupId>javax.validation</groupId>
98+
<artifactId>validation-api</artifactId>
99+
<version>${validation-api.version}</version>
100100
</dependency>
101101
<dependency>
102102
<groupId>org.reflections</groupId>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package restx.validation;
2+
3+
import java.lang.annotation.*;
4+
5+
/**
6+
* @author fcamblor
7+
*/
8+
@Target(ElementType.PARAMETER)
9+
@Retention(RetentionPolicy.RUNTIME)
10+
@Documented
11+
public @interface ValidatedFor {
12+
Class[] value();
13+
}
Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package restx.validation;
22

33
import com.google.common.base.Joiner;
4+
import com.google.common.base.Optional;
45

56
import javax.validation.ConstraintViolation;
67
import javax.validation.Validator;
8+
import javax.validation.groups.Default;
79
import java.util.Set;
810

911
/**
@@ -12,11 +14,27 @@
1214
* Time: 9:57 PM
1315
*/
1416
public class Validations {
17+
18+
/**
19+
* @deprecated Kept for backward compat. Use checkValid(Optional&lt;Validator&gt;, T, Class...) instead
20+
*/
21+
@Deprecated
1522
public static <T> T checkValid(Validator validator, T o) {
16-
Set<ConstraintViolation<T>> violations = validator.validate(o);
17-
if (!violations.isEmpty()) {
18-
throw new IllegalArgumentException(Joiner.on(", ").join(violations));
23+
return checkValid(Optional.of(validator), o);
24+
}
25+
26+
public static <T> T checkValid(Optional<Validator> validator, T o, Class... groups) {
27+
if(validator.isPresent()) {
28+
if(groups == null || groups.length==0) {
29+
groups = new Class[]{ Default.class };
30+
}
31+
32+
Set<ConstraintViolation<T>> violations = validator.get().validate(o, groups);
33+
if (!violations.isEmpty()) {
34+
throw new IllegalArgumentException(Joiner.on(", ").join(violations));
35+
}
1936
}
37+
2038
return o;
2139
}
2240
}

restx-core/src/main/java/restx/validation/ValidatorFactory.java

Lines changed: 0 additions & 25 deletions
This file was deleted.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package restx.validation.stereotypes;
2+
3+
import javax.validation.groups.Default;
4+
5+
/**
6+
* @author fcamblor
7+
*/
8+
public interface FormValidations {
9+
public static final String DefaultFQN = "javax.validation.groups.Default";
10+
public static final String CreateFQN = "restx.validation.stereotypes.FormValidations.Create";
11+
public static interface Create extends Default{}
12+
public static final String UpdateFQN = "restx.validation.stereotypes.FormValidations.Update";
13+
public static interface Update extends Default{}
14+
}

0 commit comments

Comments
 (0)