Skip to content

Commit a516b4e

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

File tree

25 files changed

+500
-37
lines changed

25 files changed

+500
-37
lines changed

Diff for: README.md

+2-1
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:

Diff for: pom.xml

+1
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

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+
}

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

+38-3
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 {

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

+1-1
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[] {

Diff for: restx-core/pom.xml

+4-4
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>
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+
}
+21-3
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
}

Diff for: restx-core/src/main/java/restx/validation/ValidatorFactory.java

-25
This file was deleted.
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+
}

Diff for: restx-samplest/pom.xml

+19
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@
3030
<artifactId>restx-core</artifactId>
3131
<version>${restx.version}</version>
3232
</dependency>
33+
<dependency>
34+
<groupId>io.restx</groupId>
35+
<artifactId>restx-validation</artifactId>
36+
<version>${restx.version}</version>
37+
</dependency>
3338
<dependency>
3439
<groupId>io.restx</groupId>
3540
<artifactId>restx-core-annotation-processor</artifactId>
@@ -123,6 +128,20 @@
123128
<additionalparam>-restx-target-dir ${project.basedir}/target/classes</additionalparam>
124129
</configuration>
125130
</plugin>
131+
<!-- Uncomment to debug annotation processing on samplest...
132+
<plugin>
133+
<groupId>org.apache.maven.plugins</groupId>
134+
<artifactId>maven-compiler-plugin</artifactId>
135+
<configuration>
136+
<verbose>true</verbose>
137+
<fork>true</fork>
138+
<compilerArgs>
139+
<arg>-J-Xdebug</arg>
140+
<arg>-J-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000</arg>
141+
</compilerArgs>
142+
</configuration>
143+
</plugin>
144+
-->
126145
</plugins>
127146
</build>
128147
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package samplest.validation;
2+
3+
import org.hibernate.validator.constraints.Email;
4+
import restx.validation.stereotypes.FormValidations;
5+
6+
import javax.validation.Valid;
7+
import javax.validation.constraints.NotNull;
8+
import javax.validation.constraints.Size;
9+
10+
public class POJO {
11+
@NotNull(groups={FormValidations.Update.class})
12+
Long id;
13+
@NotNull
14+
@Size(min=10, groups={ValidationResource.MyCustomValidationGroup.class})
15+
String name;
16+
@Valid
17+
SubPOJO subPOJO;
18+
@Email
19+
String email;
20+
21+
public Long getId() {
22+
return id;
23+
}
24+
25+
public void setId(Long id) {
26+
this.id = id;
27+
}
28+
29+
public String getEmail() {
30+
return email;
31+
}
32+
33+
public void setEmail(String email) {
34+
this.email = email;
35+
}
36+
37+
public String getName() {
38+
return name;
39+
}
40+
41+
public void setName(String name) {
42+
this.name = name;
43+
}
44+
45+
public SubPOJO getSubPOJO() {
46+
return subPOJO;
47+
}
48+
49+
public void setSubPOJO(SubPOJO subPOJO) {
50+
this.subPOJO = subPOJO;
51+
}
52+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package samplest.validation;
2+
3+
import javax.validation.constraints.Size;
4+
5+
public class SubPOJO {
6+
@Size(min=10)
7+
String label;
8+
9+
public String getLabel() {
10+
return label;
11+
}
12+
13+
public void setLabel(String label) {
14+
this.label = label;
15+
}
16+
}

0 commit comments

Comments
 (0)