├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── settings.gradle ├── .github ├── dependabot.yml └── workflows │ └── CI.yml ├── src ├── test │ └── java │ │ └── ru │ │ └── vyarus │ │ └── guice │ │ └── validator │ │ ├── group │ │ ├── support │ │ │ ├── groups │ │ │ │ ├── FooGroup.java │ │ │ │ ├── FooGroup2.java │ │ │ │ ├── RootFooGroup.java │ │ │ │ └── ann │ │ │ │ │ ├── Group1.java │ │ │ │ │ ├── Group2.java │ │ │ │ │ └── RootGroup.java │ │ │ ├── simple │ │ │ │ ├── InheritedService.java │ │ │ │ ├── NotInheritedService.java │ │ │ │ ├── CompositeAnnService2.java │ │ │ │ ├── CompositeAnnService3.java │ │ │ │ ├── SimpleAnnService.java │ │ │ │ └── CompositeAnnService.java │ │ │ ├── PropFunction.java │ │ │ └── model │ │ │ │ └── Model.java │ │ ├── NoDefaultGroupTest.java │ │ ├── GroupCacheTest.java │ │ ├── AnnotationRecognitionTest.java │ │ └── GroupRecognitionTest.java │ │ ├── matcher │ │ ├── MatchedService.java │ │ ├── ExceptionalService.java │ │ ├── ExceptionalMethod.java │ │ └── SuppressValidation.java │ │ ├── customann │ │ ├── ExplicitMethod.java │ │ ├── SuppressedExplicitClass.java │ │ ├── ToValidate.java │ │ └── SuppressedExplicitMethod.java │ │ ├── crossparams │ │ ├── ComplexParamsService.java │ │ ├── CrossParamsCheck.java │ │ └── CrossParamsValidator.java │ │ ├── DeadCodeTest.java │ │ ├── customtype │ │ ├── CustomService.java │ │ ├── ComplexBeanValid.java │ │ ├── ComplexBean.java │ │ └── ComplexBeanValidator.java │ │ ├── script │ │ ├── ScriptedService.java │ │ └── ScriptedBean.java │ │ ├── compositeannotation │ │ ├── ComposedCheckService.java │ │ └── ComposedCheck.java │ │ ├── simple │ │ ├── SimpleBean.java │ │ └── SimpleService.java │ │ ├── CrossParamsValidationTest.java │ │ ├── AbstractParameterizedTest.java │ │ ├── CustomExplicitAnnotationTest.java │ │ ├── CustomTypeValidationTest.java │ │ ├── CheckCustomMethodMatcher.java │ │ ├── ComposedCheckValidationTest.java │ │ ├── CheckCustomMatcher.java │ │ ├── InstanceBindingTest.java │ │ ├── ScriptedValidationTest.java │ │ ├── CustomValidatorFactoryTest.java │ │ ├── CustomMatchersInExplicitModeTest.java │ │ ├── SingleMethodValidationTest.java │ │ ├── SimpleValidationTest.java │ │ └── GroupsTest.java └── main │ └── java │ └── ru │ └── vyarus │ └── guice │ └── validator │ ├── group │ ├── GroupAction.java │ ├── aop │ │ ├── ValidationGroupMatcher.java │ │ └── ValidationGroupInterceptor.java │ ├── annotation │ │ ├── GroupUtils.java │ │ ├── ValidationGroups.java │ │ └── MethodGroupsFactory.java │ └── ValidationContext.java │ ├── aop │ ├── DeclaredMethodMatcher.java │ ├── ValidatedMethodMatcher.java │ └── ValidationMethodInterceptor.java │ ├── constraint │ └── GuiceConstraintValidatorFactory.java │ └── ValidationModule.java ├── .yo-rc.json ├── .appveyor.yml ├── LICENSE ├── CHANGELOG.md ├── .gitignore ├── gradlew.bat ├── gradlew └── README.md /gradle.properties: -------------------------------------------------------------------------------- 1 | version=3.0.3-SNAPSHOT -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xvik/guice-validator/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | mavenLocal() 4 | gradlePluginPortal() 5 | } 6 | } 7 | 8 | rootProject.name = 'guice-validator' 9 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gradle 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "23:00" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/validator/group/support/groups/FooGroup.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator.group.support.groups; 2 | 3 | /** 4 | * @author Vyacheslav Rusakov 5 | * @since 09.03.2016 6 | */ 7 | public interface FooGroup { 8 | } 9 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/validator/group/support/groups/FooGroup2.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator.group.support.groups; 2 | 3 | /** 4 | * @author Vyacheslav Rusakov 5 | * @since 09.03.2016 6 | */ 7 | public interface FooGroup2 { 8 | } 9 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/validator/group/support/groups/RootFooGroup.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator.group.support.groups; 2 | 3 | /** 4 | * @author Vyacheslav Rusakov 5 | * @since 09.03.2016 6 | */ 7 | public interface RootFooGroup { 8 | } 9 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/validator/matcher/MatchedService.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator.matcher; 2 | 3 | import jakarta.validation.constraints.NotNull; 4 | 5 | /** 6 | * @author Vyacheslav Rusakov 7 | * @since 20.12.2014 8 | */ 9 | public class MatchedService { 10 | public void doSmth(@NotNull String val){ 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/validator/matcher/ExceptionalService.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator.matcher; 2 | 3 | import jakarta.validation.constraints.NotNull; 4 | 5 | /** 6 | * @author Vyacheslav Rusakov 7 | * @since 20.12.2014 8 | */ 9 | @SuppressValidation 10 | public class ExceptionalService { 11 | 12 | public void doSmth(@NotNull String val){ 13 | 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/validator/group/support/simple/InheritedService.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator.group.support.simple; 2 | 3 | /** 4 | * @author Vyacheslav Rusakov 5 | * @since 15.03.2016 6 | */ 7 | public class InheritedService extends CompositeAnnService2 { 8 | 9 | public void groupInherit(){ 10 | } 11 | 12 | @Override 13 | public void single() { 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/validator/group/support/simple/NotInheritedService.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator.group.support.simple; 2 | 3 | /** 4 | * @author Vyacheslav Rusakov 5 | * @since 15.03.2016 6 | */ 7 | public class NotInheritedService extends CompositeAnnService { 8 | 9 | public void noGroups(){ 10 | } 11 | 12 | @Override 13 | public void custom() { 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/validator/customann/ExplicitMethod.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator.customann; 2 | 3 | import jakarta.validation.constraints.NotNull; 4 | 5 | /** 6 | * @author Vyacheslav Rusakov 7 | * @since 24.12.2019 8 | */ 9 | public class ExplicitMethod { 10 | 11 | public void doSmth(@NotNull String val) { 12 | 13 | } 14 | 15 | @ToValidate 16 | public void doSmth2(@NotNull String val) { 17 | 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/validator/matcher/ExceptionalMethod.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator.matcher; 2 | 3 | import jakarta.validation.constraints.NotNull; 4 | 5 | /** 6 | * @author Vyacheslav Rusakov 7 | * @since 24.12.2019 8 | */ 9 | public class ExceptionalMethod { 10 | 11 | @SuppressValidation 12 | public void doSmth(@NotNull String val){ 13 | 14 | } 15 | 16 | public void doSmth2(@NotNull String val){ 17 | 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/validator/customann/SuppressedExplicitClass.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator.customann; 2 | 3 | import ru.vyarus.guice.validator.matcher.SuppressValidation; 4 | 5 | import jakarta.validation.constraints.NotNull; 6 | 7 | /** 8 | * @author Vyacheslav Rusakov 9 | * @since 24.12.2019 10 | */ 11 | @ToValidate 12 | @SuppressValidation 13 | public class SuppressedExplicitClass { 14 | 15 | public void doSmth(@NotNull String val) { 16 | 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/validator/customann/ToValidate.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator.customann; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * @author Vyacheslav Rusakov 10 | * @since 24.12.2019 11 | */ 12 | @Target({ElementType.TYPE, ElementType.METHOD}) 13 | @Retention(RetentionPolicy.RUNTIME) 14 | public @interface ToValidate { 15 | } 16 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/validator/crossparams/ComplexParamsService.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator.crossparams; 2 | 3 | import com.google.inject.Singleton; 4 | import jakarta.validation.executable.ValidateOnExecution; 5 | 6 | /** 7 | * Service with method cross parameters validation. 8 | * 9 | * @author Vyacheslav Rusakov 10 | * @since 24.06.2014 11 | */ 12 | @Singleton 13 | @ValidateOnExecution 14 | public class ComplexParamsService { 15 | 16 | @CrossParamsCheck 17 | public void action(Integer param1, Object param2) { 18 | 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/validator/customann/SuppressedExplicitMethod.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator.customann; 2 | 3 | import ru.vyarus.guice.validator.matcher.SuppressValidation; 4 | 5 | import jakarta.validation.constraints.NotNull; 6 | 7 | /** 8 | * @author Vyacheslav Rusakov 9 | * @since 24.12.2019 10 | */ 11 | @ToValidate 12 | public class SuppressedExplicitMethod { 13 | 14 | 15 | public void doSmth(@NotNull String val) { 16 | 17 | } 18 | 19 | @SuppressValidation 20 | public void doSmth2(@NotNull String val) { 21 | 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/validator/matcher/SuppressValidation.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator.matcher; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Sample annotation used to disable validation 10 | * 11 | * @author Vyacheslav Rusakov 12 | * @since 20.12.2014 13 | */ 14 | @Target({ElementType.TYPE, ElementType.METHOD}) 15 | @Retention(RetentionPolicy.RUNTIME) 16 | public @interface SuppressValidation { 17 | } 18 | -------------------------------------------------------------------------------- /.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "generator-lib-java": { 3 | "githubUser": "xvik", 4 | "authorName": "Vyacheslav Rusakov", 5 | "authorEmail": "vyarus@gmail.com", 6 | "libName": "guice-validator", 7 | "libGroup": "ru.vyarus", 8 | "libPackage": "ru.vyarus.guice.validator", 9 | "libVersion": "0.1.0", 10 | "libDesc": "Guice method validation through jakarta.validation implementation (hibernate-validator)", 11 | "enableQualityChecks": true, 12 | "usedGeneratorVersion": "3.0.0", 13 | "multiModule": false, 14 | "modulePrefix": "-", 15 | "moduleName": "-" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/validator/DeadCodeTest.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator; 2 | 3 | import com.pushtorefresh.private_constructor_checker.PrivateConstructorChecker; 4 | import org.junit.Test; 5 | import ru.vyarus.guice.validator.group.annotation.GroupUtils; 6 | 7 | /** 8 | * @author Vyacheslav Rusakov 9 | * @since 31.03.2016 10 | */ 11 | public class DeadCodeTest { 12 | 13 | @Test 14 | public void checkPrivateCtor() throws Exception { 15 | PrivateConstructorChecker 16 | .forClass(GroupUtils.class) 17 | .check(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/ru/vyarus/guice/validator/group/GroupAction.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator.group; 2 | 3 | /** 4 | * Callback used to execute logic under certain validation groups. Used from 5 | * {@link ValidationContext#doWithGroups(ru.vyarus.guice.validator.group.GroupAction, java.lang.Class[])}. 6 | * 7 | * @param return type 8 | * @author Vyacheslav Rusakov 9 | * @since 09.03.2016 10 | */ 11 | public interface GroupAction { 12 | 13 | /** 14 | * @return result object (or null) 15 | * @throws Throwable in case of errors 16 | */ 17 | T call() throws Throwable; 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/ru/vyarus/guice/validator/aop/DeclaredMethodMatcher.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator.aop; 2 | 3 | import com.google.inject.matcher.AbstractMatcher; 4 | 5 | import java.lang.reflect.Method; 6 | 7 | /** 8 | * Matcher filters synthetic and bridge methods from aop matching. Otherwise, guice aop will show warnings 9 | * on synthetic methods calls. 10 | * 11 | * @author Vyacheslav Rusakov 12 | * @since 24.12.2019 13 | */ 14 | public class DeclaredMethodMatcher extends AbstractMatcher { 15 | 16 | @Override 17 | public boolean matches(final Method method) { 18 | return !method.isSynthetic() || !method.isBridge(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/validator/group/support/groups/ann/Group1.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator.group.support.groups.ann; 2 | 3 | import ru.vyarus.guice.validator.group.annotation.ValidationGroups; 4 | 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | import static java.lang.annotation.ElementType.METHOD; 10 | import static java.lang.annotation.ElementType.TYPE; 11 | 12 | /** 13 | * @author Vyacheslav Rusakov 14 | * @since 09.03.2016 15 | */ 16 | @Target({TYPE, METHOD}) 17 | @Retention(RetentionPolicy.RUNTIME) 18 | @ValidationGroups(Group1.class) 19 | public @interface Group1 { 20 | } 21 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/validator/group/support/groups/ann/Group2.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator.group.support.groups.ann; 2 | 3 | import ru.vyarus.guice.validator.group.annotation.ValidationGroups; 4 | 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | import static java.lang.annotation.ElementType.METHOD; 10 | import static java.lang.annotation.ElementType.TYPE; 11 | 12 | /** 13 | * @author Vyacheslav Rusakov 14 | * @since 09.03.2016 15 | */ 16 | @Target({TYPE, METHOD}) 17 | @Retention(RetentionPolicy.RUNTIME) 18 | @ValidationGroups(Group2.class) 19 | public @interface Group2 { 20 | } 21 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/validator/group/support/groups/ann/RootGroup.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator.group.support.groups.ann; 2 | 3 | import ru.vyarus.guice.validator.group.annotation.ValidationGroups; 4 | 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | import static java.lang.annotation.ElementType.METHOD; 10 | import static java.lang.annotation.ElementType.TYPE; 11 | 12 | /** 13 | * @author Vyacheslav Rusakov 14 | * @since 09.03.2016 15 | */ 16 | @Target({TYPE, METHOD}) 17 | @Retention(RetentionPolicy.RUNTIME) 18 | @ValidationGroups(RootGroup.class) 19 | public @interface RootGroup { 20 | } 21 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/validator/customtype/CustomService.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator.customtype; 2 | 3 | import com.google.inject.Singleton; 4 | import jakarta.validation.Valid; 5 | import jakarta.validation.executable.ValidateOnExecution; 6 | 7 | /** 8 | * Service, used in custom validator, wired with guice. 9 | * 10 | * @author Vyacheslav Rusakov 11 | * @since 24.06.2014 12 | */ 13 | @Singleton 14 | public class CustomService { 15 | 16 | public String getRequiredValue() { 17 | return "perfect"; 18 | } 19 | 20 | /* @Valid will trigger bean validation with includes entire bean validation. */ 21 | @ValidateOnExecution 22 | public void doAction(@Valid ComplexBean bean) { 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/validator/group/support/simple/CompositeAnnService2.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator.group.support.simple; 2 | 3 | import ru.vyarus.guice.validator.group.annotation.ValidationGroups; 4 | import ru.vyarus.guice.validator.group.support.groups.FooGroup; 5 | import ru.vyarus.guice.validator.group.support.groups.ann.Group1; 6 | import ru.vyarus.guice.validator.group.support.groups.RootFooGroup; 7 | 8 | /** 9 | * @author Vyacheslav Rusakov 10 | * @since 09.03.2016 11 | */ 12 | @ValidationGroups(RootFooGroup.class) 13 | public class CompositeAnnService2 { 14 | 15 | public void nothing() { 16 | } 17 | 18 | @ValidationGroups(FooGroup.class) 19 | public void single() { 20 | } 21 | 22 | @Group1 23 | public void custom() { 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/validator/script/ScriptedService.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator.script; 2 | 3 | import org.hibernate.validator.constraints.ParameterScriptAssert; 4 | 5 | import com.google.inject.Singleton; 6 | import jakarta.validation.Valid; 7 | import jakarta.validation.executable.ValidateOnExecution; 8 | import java.util.List; 9 | 10 | /** 11 | * Service with scripted validations 12 | * 13 | * @author Vyacheslav Rusakov 14 | * @since 25.06.2014 15 | */ 16 | @Singleton 17 | @ValidateOnExecution 18 | public class ScriptedService { 19 | 20 | @ParameterScriptAssert(lang = "javascript", script = "arg0.size() == arg1") 21 | public void paramsValid(List list, int count) { 22 | } 23 | 24 | 25 | public void validBean(@Valid ScriptedBean bean) { 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/ru/vyarus/guice/validator/group/aop/ValidationGroupMatcher.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator.group.aop; 2 | 3 | import com.google.inject.matcher.AbstractMatcher; 4 | import ru.vyarus.guice.validator.group.annotation.GroupUtils; 5 | 6 | import java.lang.reflect.Method; 7 | 8 | /** 9 | * Matcher for {@link ValidationGroupInterceptor}. Applied for methods annotated with 10 | * {@link ru.vyarus.guice.validator.group.annotation.ValidationGroups} (or with annotation annotated with it) and 11 | * methods in annotated class. 12 | * 13 | * @author Vyacheslav Rusakov 14 | * @since 09.03.2016 15 | */ 16 | public class ValidationGroupMatcher extends AbstractMatcher { 17 | 18 | @Override 19 | public boolean matches(final Method method) { 20 | return !GroupUtils.findAnnotations(method).isEmpty(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/validator/compositeannotation/ComposedCheckService.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator.compositeannotation; 2 | 3 | import com.google.inject.Singleton; 4 | import jakarta.validation.executable.ValidateOnExecution; 5 | 6 | /** 7 | * Example of using composed annotation for parameter validation.(return value validation doesn't work, maybe bug.. investigation required) 8 | * 9 | * @author Vyacheslav Rusakov 10 | * @since 25.06.2014 11 | */ 12 | @Singleton 13 | @ValidateOnExecution 14 | public class ComposedCheckService { 15 | 16 | public String checkParam(@ComposedCheck String string) { 17 | return string; 18 | } 19 | 20 | // todo can't make composed annotation work for return value 21 | // @ComposedCheck 22 | // public String checkReturn(String string) { 23 | // return string; 24 | // } 25 | } 26 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/validator/customtype/ComplexBeanValid.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator.customtype; 2 | 3 | import jakarta.validation.Constraint; 4 | import jakarta.validation.Payload; 5 | import java.lang.annotation.*; 6 | 7 | /** 8 | * Annotation enables validation for entire {@code ComplexBean} 9 | * 10 | * @author Vyacheslav Rusakov 11 | * @since 24.06.2014 12 | */ 13 | @Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE}) 14 | @Retention(RetentionPolicy.RUNTIME) 15 | @Constraint(validatedBy = {ComplexBeanValidator.class}) 16 | @Documented 17 | public @interface ComplexBeanValid { 18 | /* ideally there should be just localization key, but for simplicity just message */ 19 | String message() default "Bean is not valid"; 20 | 21 | Class[] groups() default {}; 22 | 23 | Class[] payload() default {}; 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/validator/group/support/simple/CompositeAnnService3.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator.group.support.simple; 2 | 3 | import ru.vyarus.guice.validator.group.support.groups.FooGroup; 4 | import ru.vyarus.guice.validator.group.support.groups.ann.Group1; 5 | import ru.vyarus.guice.validator.group.support.groups.RootFooGroup; 6 | import ru.vyarus.guice.validator.group.support.groups.ann.RootGroup; 7 | import ru.vyarus.guice.validator.group.annotation.ValidationGroups; 8 | 9 | /** 10 | * @author Vyacheslav Rusakov 11 | * @since 09.03.2016 12 | */ 13 | @RootGroup 14 | @ValidationGroups(RootFooGroup.class) 15 | public class CompositeAnnService3 { 16 | 17 | public void nothing() { 18 | } 19 | 20 | @ValidationGroups(FooGroup.class) 21 | public void single() { 22 | } 23 | 24 | @Group1 25 | public void custom() { 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/validator/crossparams/CrossParamsCheck.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator.crossparams; 2 | 3 | import jakarta.validation.Constraint; 4 | import jakarta.validation.Payload; 5 | import java.lang.annotation.*; 6 | 7 | /** 8 | * Annotation for method parameters validation. 9 | * 10 | * @author Vyacheslav Rusakov 11 | * @since 24.06.2014 12 | */ 13 | @Constraint(validatedBy = CrossParamsValidator.class) 14 | @Target({ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.ANNOTATION_TYPE }) 15 | @Retention(RetentionPolicy.RUNTIME) 16 | @Documented 17 | public @interface CrossParamsCheck { 18 | /* ideally there should be just localization key, but for simplicity just message */ 19 | String message() default "Parameters are not valid"; 20 | 21 | Class[] groups() default { }; 22 | 23 | Class[] payload() default { }; 24 | } 25 | -------------------------------------------------------------------------------- /.appveyor.yml: -------------------------------------------------------------------------------- 1 | version: '{build}' 2 | image: Visual Studio 2019 3 | 4 | environment: 5 | matrix: 6 | - job_name: Java 8 7 | JAVA_HOME: C:\Program Files\Java\jdk1.8.0 8 | - job_name: Java 11 9 | JAVA_HOME: C:\Program Files\Java\jdk11 10 | - job_name: Java 17 11 | appveyor_build_worker_image: Visual Studio 2019 12 | JAVA_HOME: C:\Program Files\Java\jdk16 13 | 14 | build_script: 15 | - ./gradlew assemble --no-daemon 16 | test_script: 17 | - ./gradlew check --no-daemon 18 | 19 | on_success: 20 | - ./gradlew jacocoTestReport --no-daemon 21 | - ps: | 22 | $ProgressPreference = 'SilentlyContinue' 23 | Invoke-WebRequest -Uri https://uploader.codecov.io/latest/windows/codecov.exe -Outfile codecov.exe 24 | .\codecov.exe -f build\reports\jacoco\test\jacocoTestReport.xml -F windows 25 | 26 | cache: 27 | - C:\Users\appveyor\.gradle\caches 28 | - C:\Users\appveyor\.gradle\wrapper -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/validator/group/support/PropFunction.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator.group.support; 2 | 3 | import com.google.common.base.Function; 4 | import com.google.common.collect.Iterables; 5 | import com.google.common.collect.Lists; 6 | import com.google.common.collect.Sets; 7 | 8 | import jakarta.validation.ConstraintViolation; 9 | import java.util.Collection; 10 | import java.util.Set; 11 | 12 | /** 13 | * @author Vyacheslav Rusakov 14 | * @since 11.03.2016 15 | */ 16 | public class PropFunction implements Function { 17 | 18 | @Override 19 | public String apply(ConstraintViolation input) { 20 | return Lists.newLinkedList(input.getPropertyPath()).getLast().toString(); 21 | } 22 | 23 | public static Set convert(Collection> violations) { 24 | return Sets.newHashSet(Iterables.transform(violations, new PropFunction())); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/validator/customtype/ComplexBean.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator.customtype; 2 | 3 | import jakarta.validation.constraints.NotNull; 4 | 5 | /** 6 | * Bean with custom validator for entire class. 7 | * 8 | * @author Vyacheslav Rusakov 9 | * @since 24.06.2014 10 | */ 11 | @ComplexBeanValid 12 | public class ComplexBean { 13 | 14 | @NotNull 15 | private String user; 16 | private int value; 17 | 18 | public ComplexBean() { 19 | } 20 | 21 | public ComplexBean(String user, int value) { 22 | this.user = user; 23 | this.value = value; 24 | } 25 | 26 | public String getUser() { 27 | return user; 28 | } 29 | 30 | public void setUser(String user) { 31 | this.user = user; 32 | } 33 | 34 | public int getValue() { 35 | return value; 36 | } 37 | 38 | public void setValue(int value) { 39 | this.value = value; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/validator/simple/SimpleBean.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator.simple; 2 | 3 | 4 | import jakarta.validation.constraints.NotBlank; 5 | import jakarta.validation.constraints.NotNull; 6 | 7 | /** 8 | * @author Vyacheslav Rusakov 9 | * @since 24.06.2014 10 | */ 11 | public class SimpleBean { 12 | 13 | @NotBlank 14 | private String name; 15 | @NotNull 16 | private Integer value; 17 | 18 | public SimpleBean() { 19 | } 20 | 21 | public SimpleBean(String name, Integer value) { 22 | this.name = name; 23 | this.value = value; 24 | } 25 | 26 | public String getName() { 27 | return name; 28 | } 29 | 30 | public void setName(String name) { 31 | this.name = name; 32 | } 33 | 34 | public Integer getValue() { 35 | return value; 36 | } 37 | 38 | public void setValue(Integer value) { 39 | this.value = value; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/validator/group/support/simple/SimpleAnnService.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator.group.support.simple; 2 | 3 | import ru.vyarus.guice.validator.group.support.groups.FooGroup; 4 | import ru.vyarus.guice.validator.group.support.groups.FooGroup2; 5 | import ru.vyarus.guice.validator.group.support.groups.ann.Group1; 6 | import ru.vyarus.guice.validator.group.support.groups.ann.Group2; 7 | import ru.vyarus.guice.validator.group.annotation.ValidationGroups; 8 | 9 | /** 10 | * @author Vyacheslav Rusakov 11 | * @since 09.03.2016 12 | */ 13 | public class SimpleAnnService { 14 | 15 | public void nothing() { 16 | } 17 | 18 | @ValidationGroups(FooGroup.class) 19 | public void single() { 20 | } 21 | 22 | @ValidationGroups({FooGroup.class, FooGroup2.class}) 23 | public void multiple() { 24 | } 25 | 26 | @Group1 27 | public void custom() { 28 | } 29 | 30 | @Group1 31 | @Group2 32 | public void custom2() { 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/validator/simple/SimpleService.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator.simple; 2 | 3 | import com.google.inject.Singleton; 4 | import jakarta.validation.Valid; 5 | import jakarta.validation.constraints.NotNull; 6 | import jakarta.validation.executable.ValidateOnExecution; 7 | 8 | /** 9 | * Very basic method validations. 10 | * 11 | * @author Vyacheslav Rusakov 12 | * @since 24.06.2014. 13 | */ 14 | @Singleton 15 | @ValidateOnExecution // enables validation for all methods 16 | public class SimpleService { 17 | 18 | public SimpleBean beanRequired(@NotNull SimpleBean bean) { 19 | return null; 20 | } 21 | 22 | public SimpleBean validBeanRequired(@NotNull @Valid SimpleBean bean) { 23 | return null; 24 | } 25 | 26 | @NotNull 27 | public SimpleBean notNullReturn(SimpleBean bean) { 28 | return bean; 29 | } 30 | 31 | @NotNull 32 | @Valid 33 | public SimpleBean validReturn(SimpleBean bean) { 34 | return bean; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/validator/compositeannotation/ComposedCheck.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator.compositeannotation; 2 | 3 | import jakarta.validation.Constraint; 4 | import jakarta.validation.Payload; 5 | import jakarta.validation.ReportAsSingleViolation; 6 | import jakarta.validation.constraints.NotNull; 7 | import jakarta.validation.constraints.Size; 8 | import java.lang.annotation.*; 9 | 10 | /** 11 | * Example of validation annotation composed of other annotations 12 | * 13 | * @author Vyacheslav Rusakov 14 | * @since 25.06.2014 15 | */ 16 | @NotNull 17 | @Size(min = 2, max = 14) 18 | @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.PARAMETER}) 19 | @Retention(RetentionPolicy.RUNTIME) 20 | @Constraint(validatedBy = {}) 21 | @Documented 22 | @ReportAsSingleViolation //optional 23 | public @interface ComposedCheck { 24 | String message() default "Composed check failed"; 25 | 26 | Class[] groups() default {}; 27 | 28 | Class[] payload() default {}; 29 | } 30 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/validator/crossparams/CrossParamsValidator.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator.crossparams; 2 | 3 | import jakarta.validation.ConstraintValidator; 4 | import jakarta.validation.ConstraintValidatorContext; 5 | import jakarta.validation.constraintvalidation.SupportedValidationTarget; 6 | import jakarta.validation.constraintvalidation.ValidationTarget; 7 | 8 | /** 9 | * Validates method parameters. 10 | * 11 | * @author Vyacheslav Rusakov 12 | * @since 24.06.2014 13 | */ 14 | @SupportedValidationTarget(ValidationTarget.PARAMETERS) 15 | public class CrossParamsValidator implements ConstraintValidator { 16 | 17 | @Override 18 | public void initialize(CrossParamsCheck constraintAnnotation) { 19 | 20 | } 21 | 22 | @Override 23 | public boolean isValid(Object[] value, ConstraintValidatorContext context) { 24 | Integer param1 = (Integer) value[0]; 25 | Object param2 = value[1]; 26 | return param1 != null && param1 == 1 && param2 instanceof Integer; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/validator/script/ScriptedBean.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator.script; 2 | 3 | import org.hibernate.validator.constraints.ScriptAssert; 4 | 5 | import java.util.Date; 6 | 7 | /** 8 | * Example of script validation for bean state. 9 | * 10 | * @author Vyacheslav Rusakov 11 | * @since 25.06.2014 12 | */ 13 | @ScriptAssert(lang = "javascript", script = "it.start.before(it.finish)", alias = "it") 14 | public class ScriptedBean { 15 | 16 | private Date start; 17 | private Date finish; 18 | 19 | public ScriptedBean() { 20 | } 21 | 22 | public ScriptedBean(Date start, Date finish) { 23 | this.start = start; 24 | this.finish = finish; 25 | } 26 | 27 | public Date getStart() { 28 | return start; 29 | } 30 | 31 | public void setStart(Date start) { 32 | this.start = start; 33 | } 34 | 35 | public Date getFinish() { 36 | return finish; 37 | } 38 | 39 | public void setFinish(Date finish) { 40 | this.finish = finish; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/validator/group/support/simple/CompositeAnnService.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator.group.support.simple; 2 | 3 | import ru.vyarus.guice.validator.group.annotation.ValidationGroups; 4 | import ru.vyarus.guice.validator.group.support.groups.FooGroup; 5 | import ru.vyarus.guice.validator.group.support.groups.FooGroup2; 6 | import ru.vyarus.guice.validator.group.support.groups.ann.Group1; 7 | import ru.vyarus.guice.validator.group.support.groups.ann.Group2; 8 | import ru.vyarus.guice.validator.group.support.groups.ann.RootGroup; 9 | 10 | /** 11 | * @author Vyacheslav Rusakov 12 | * @since 09.03.2016 13 | */ 14 | @RootGroup 15 | public class CompositeAnnService { 16 | 17 | public void nothing() { 18 | } 19 | 20 | @ValidationGroups(FooGroup.class) 21 | public void single() { 22 | } 23 | 24 | @ValidationGroups({FooGroup.class, FooGroup2.class}) 25 | public void multiple() { 26 | } 27 | 28 | @Group1 29 | public void custom() { 30 | } 31 | 32 | @Group1 33 | @Group2 34 | public void custom2() { 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Vyacheslav Rusakov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/main/java/ru/vyarus/guice/validator/group/aop/ValidationGroupInterceptor.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator.group.aop; 2 | 3 | import com.google.inject.Inject; 4 | import org.aopalliance.intercept.MethodInterceptor; 5 | import org.aopalliance.intercept.MethodInvocation; 6 | import ru.vyarus.guice.validator.group.ValidationContext; 7 | import ru.vyarus.guice.validator.group.annotation.MethodGroupsFactory; 8 | 9 | /** 10 | * Intercepts methods under {@link ru.vyarus.guice.validator.group.annotation.ValidationGroups} annotation to declare 11 | * validation groups in scope of annotated method call. 12 | * 13 | * @author Vyacheslav Rusakov 14 | * @see ValidationContext 15 | * @since 07.03.2016 16 | */ 17 | public class ValidationGroupInterceptor implements MethodInterceptor { 18 | 19 | @Inject 20 | private ValidationContext context; 21 | @Inject 22 | private MethodGroupsFactory factory; 23 | 24 | @Override 25 | public Object invoke(final MethodInvocation invocation) { 26 | final Class[] groups = factory.create(invocation.getMethod()); 27 | return context.doWithGroups(invocation::proceed, groups); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/validator/customtype/ComplexBeanValidator.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator.customtype; 2 | 3 | import com.google.inject.Inject; 4 | import jakarta.validation.ConstraintValidator; 5 | import jakarta.validation.ConstraintValidatorContext; 6 | 7 | /** 8 | * Entire bean validator with service injection. 9 | * 10 | * @author Vyacheslav Rusakov 11 | * @since 24.06.2014 12 | */ 13 | public class ComplexBeanValidator implements ConstraintValidator { 14 | 15 | @Inject 16 | private CustomService customService; 17 | 18 | @Override 19 | public void initialize(ComplexBeanValid constraintAnnotation) { 20 | /* if annotation contains addition parameter it must be parsed here.. skipping for simplicity. 21 | NOTE: in such simple case we can make validator singleton, because of no internal state */ 22 | } 23 | 24 | @Override 25 | public boolean isValid(ComplexBean value, ConstraintValidatorContext context) { 26 | /* common convention is to treat null values as valid and explicitly check them with @NotNull */ 27 | return value == null || customService.getRequiredValue().equals(value.getUser()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/validator/CrossParamsValidationTest.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator; 2 | 3 | import org.junit.Test; 4 | import org.junit.runners.Parameterized; 5 | import ru.vyarus.guice.validator.crossparams.ComplexParamsService; 6 | 7 | import jakarta.validation.ConstraintViolationException; 8 | import java.util.Collection; 9 | 10 | /** 11 | * Checks custom cross parameters validator. 12 | * 13 | * @author Vyacheslav Rusakov 14 | * @since 25.06.2014 15 | */ 16 | public class CrossParamsValidationTest extends AbstractParameterizedTest { 17 | 18 | public CrossParamsValidationTest(String type, ComplexParamsService service) { 19 | super(type, service); 20 | } 21 | 22 | @Parameterized.Parameters(name = "{index}: {0}") 23 | public static Collection generateData() { 24 | return AbstractParameterizedTest.generateData(ComplexParamsService.class); 25 | } 26 | 27 | @Test(expected = ConstraintViolationException.class) 28 | public void testValidationFail() throws Exception { 29 | service.action(2, 12); 30 | } 31 | 32 | @Test 33 | public void testValidation() throws Exception { 34 | service.action(1, 12); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/validator/group/support/model/Model.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator.group.support.model; 2 | 3 | import ru.vyarus.guice.validator.group.support.groups.ann.Group1; 4 | import ru.vyarus.guice.validator.group.support.groups.ann.Group2; 5 | 6 | import jakarta.validation.constraints.NotNull; 7 | 8 | /** 9 | * @author Vyacheslav Rusakov 10 | * @since 11.03.2016 11 | */ 12 | public class Model { 13 | 14 | @NotNull(groups = Group1.class) 15 | private String foo; 16 | 17 | @NotNull(groups = Group2.class) 18 | private String bar; 19 | 20 | @NotNull 21 | private String def; 22 | 23 | public Model(String foo, String bar, String def) { 24 | this.foo = foo; 25 | this.bar = bar; 26 | this.def = def; 27 | } 28 | 29 | public String getFoo() { 30 | return foo; 31 | } 32 | 33 | public void setFoo(String foo) { 34 | this.foo = foo; 35 | } 36 | 37 | public String getBar() { 38 | return bar; 39 | } 40 | 41 | public void setBar(String bar) { 42 | this.bar = bar; 43 | } 44 | 45 | public String getDef() { 46 | return def; 47 | } 48 | 49 | public void setDef(String def) { 50 | this.def = def; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/validator/AbstractParameterizedTest.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator; 2 | 3 | import com.google.inject.Guice; 4 | import org.junit.runner.RunWith; 5 | import org.junit.runners.Parameterized; 6 | 7 | import java.util.Arrays; 8 | import java.util.Collection; 9 | 10 | /** 11 | * @author Vyacheslav Rusakov 12 | * @since 19.12.2014 13 | */ 14 | @RunWith(Parameterized.class) 15 | public abstract class AbstractParameterizedTest { 16 | 17 | public static final String EXPLICIT = "Explicit"; 18 | public static final String IMPLICIT = "Implicit"; 19 | T service; 20 | String type; 21 | 22 | public AbstractParameterizedTest(String type, T service) { 23 | this.service = service; 24 | this.type = type; 25 | } 26 | 27 | // @Parameterized.Parameters(name = "{index}: {0}") 28 | public static Collection generateData(Class type) { 29 | return Arrays.asList(new Object[][]{ 30 | {EXPLICIT, Guice.createInjector(new ValidationModule().validateAnnotatedOnly()).getInstance(type)}, 31 | {IMPLICIT, Guice.createInjector(new ValidationModule()).getInstance(type)} 32 | }); 33 | } 34 | 35 | boolean isExplicit() { 36 | return type.equals(EXPLICIT); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/validator/CustomExplicitAnnotationTest.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator; 2 | 3 | import com.google.inject.Guice; 4 | import com.google.inject.Injector; 5 | import org.junit.BeforeClass; 6 | import org.junit.Test; 7 | import ru.vyarus.guice.validator.customann.ExplicitMethod; 8 | import ru.vyarus.guice.validator.customann.ToValidate; 9 | 10 | import jakarta.validation.ConstraintViolationException; 11 | 12 | /** 13 | * @author Vyacheslav Rusakov 14 | * @since 24.12.2019 15 | */ 16 | public class CustomExplicitAnnotationTest { 17 | 18 | static ExplicitMethod service; 19 | 20 | @BeforeClass 21 | public static void setUp() throws Exception { 22 | Injector injector = Guice.createInjector(new ValidationModule() 23 | .validateAnnotatedOnly(ToValidate.class)); 24 | 25 | service = injector.getInstance(ExplicitMethod.class); 26 | } 27 | 28 | @Test 29 | public void testMatchedExclusion() throws Exception { 30 | service.doSmth(null); 31 | // error not happen, because method not annotated 32 | } 33 | 34 | @Test(expected = ConstraintViolationException.class) 35 | public void testExclusion() throws Exception { 36 | service.doSmth2(null); 37 | // method annotated 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | name: Java ${{ matrix.java }} 11 | strategy: 12 | matrix: 13 | java: [8, 11, 17] 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | 18 | - name: Set up JDK ${{ matrix.java }} 19 | uses: actions/setup-java@v4 20 | with: 21 | distribution: 'zulu' 22 | java-version: ${{ matrix.java }} 23 | 24 | - name: Setup Gradle 25 | uses: gradle/actions/setup-gradle@v3 26 | 27 | - name: Build 28 | run: | 29 | chmod +x gradlew 30 | ./gradlew assemble --no-daemon 31 | 32 | - name: Test 33 | env: 34 | GH_ACTIONS: true 35 | run: ./gradlew check --no-daemon 36 | 37 | - name: Build coverage report 38 | if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request' 39 | run: ./gradlew jacocoTestReport --no-daemon 40 | 41 | - uses: codecov/codecov-action@v4 42 | if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request' 43 | with: 44 | files: build/reports/jacoco/test/jacocoTestReport.xml 45 | flags: LINUX 46 | fail_ci_if_error: true 47 | token: ${{ secrets.CODECOV_TOKEN }} -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/validator/CustomTypeValidationTest.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator; 2 | 3 | import com.google.inject.Guice; 4 | import com.google.inject.Injector; 5 | import org.junit.BeforeClass; 6 | import org.junit.Test; 7 | import org.junit.runners.Parameterized; 8 | import ru.vyarus.guice.validator.customtype.ComplexBean; 9 | import ru.vyarus.guice.validator.customtype.CustomService; 10 | 11 | import jakarta.validation.ConstraintViolationException; 12 | import java.util.Collection; 13 | 14 | /** 15 | * Check custom validator with injection. 16 | * 17 | * @author Vyacheslav Rusakov 18 | * @since 24.06.2014 19 | */ 20 | public class CustomTypeValidationTest extends AbstractParameterizedTest { 21 | 22 | public CustomTypeValidationTest(String type, CustomService service) { 23 | super(type, service); 24 | } 25 | 26 | @Parameterized.Parameters(name = "{index}: {0}") 27 | public static Collection generateData() { 28 | return AbstractParameterizedTest.generateData(CustomService.class); 29 | } 30 | 31 | @Test(expected = ConstraintViolationException.class) 32 | public void testValidationFail() throws Exception { 33 | service.doAction(new ComplexBean("soso", 12)); 34 | } 35 | 36 | @Test 37 | public void testValidation() throws Exception { 38 | service.doAction(new ComplexBean("perfect", 12)); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/validator/CheckCustomMethodMatcher.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator; 2 | 3 | import com.google.inject.Guice; 4 | import com.google.inject.Injector; 5 | import com.google.inject.matcher.Matchers; 6 | import org.junit.BeforeClass; 7 | import org.junit.Test; 8 | import ru.vyarus.guice.validator.matcher.ExceptionalMethod; 9 | import ru.vyarus.guice.validator.matcher.SuppressValidation; 10 | 11 | import jakarta.validation.ConstraintViolationException; 12 | 13 | /** 14 | * @author Vyacheslav Rusakov 15 | * @since 24.12.2019 16 | */ 17 | public class CheckCustomMethodMatcher { 18 | 19 | static ExceptionalMethod exceptional; 20 | 21 | @BeforeClass 22 | public static void setUp() throws Exception { 23 | Injector injector = Guice.createInjector(new ValidationModule() 24 | .targetMethods(Matchers.not(Matchers.annotatedWith(SuppressValidation.class)))); 25 | 26 | exceptional = injector.getInstance(ExceptionalMethod.class); 27 | } 28 | 29 | @Test 30 | public void testMatchedExclusion() throws Exception { 31 | exceptional.doSmth(null); 32 | // error not happen, because matcher exclude type from aop processing 33 | } 34 | 35 | @Test(expected = ConstraintViolationException.class) 36 | public void testExclusion() throws Exception { 37 | exceptional.doSmth2(null); 38 | // method not suppressed 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/ru/vyarus/guice/validator/constraint/GuiceConstraintValidatorFactory.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator.constraint; 2 | 3 | import com.google.inject.Injector; 4 | 5 | import com.google.inject.Inject; 6 | import jakarta.validation.ConstraintValidator; 7 | import jakarta.validation.ConstraintValidatorFactory; 8 | 9 | /** 10 | * Creates validator instances with guice injections available. 11 | * Any type of injection is allowed (constructor, setter, field). 12 | * Pay attention that validator usually stateful, so not declare the as singletons 13 | * (better not declare at all and rely on guice automatic dependecy resolution). 14 | * 15 | * @author Vyacheslav Rusakov 16 | * @since 24.06.2014 17 | */ 18 | public class GuiceConstraintValidatorFactory implements ConstraintValidatorFactory { 19 | 20 | @Inject 21 | private Injector injector; 22 | 23 | @Override 24 | public > T getInstance(final Class key) { 25 | /* By default, all beans are in prototype scope, so new instance will be obtained each time. 26 | Validator implementer may declare it as singleton and manually maintain internal state 27 | (to re-use validators and simplify life for GC) */ 28 | return injector.getInstance(key); 29 | } 30 | 31 | @Override 32 | public void releaseInstance(final ConstraintValidator instance) { 33 | /* Garbage collector will do it */ 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/validator/ComposedCheckValidationTest.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.junit.runners.Parameterized; 6 | import ru.vyarus.guice.validator.compositeannotation.ComposedCheckService; 7 | 8 | import jakarta.validation.ConstraintViolationException; 9 | import java.util.Collection; 10 | 11 | /** 12 | * @author Vyacheslav Rusakov 13 | * @since 25.06.2014 14 | */ 15 | @RunWith(Parameterized.class) 16 | public class ComposedCheckValidationTest extends AbstractParameterizedTest { 17 | 18 | public ComposedCheckValidationTest(String type, ComposedCheckService service) { 19 | super(type, service); 20 | } 21 | 22 | @Parameterized.Parameters(name = "{index}: {0}") 23 | public static Collection generateData() { 24 | return AbstractParameterizedTest.generateData(ComposedCheckService.class); 25 | } 26 | 27 | @Test(expected = ConstraintViolationException.class) 28 | public void testValidationFail() throws Exception { 29 | service.checkParam("f"); 30 | } 31 | 32 | @Test 33 | public void testValidation() { 34 | service.checkParam("valid string"); 35 | } 36 | 37 | // todo can't make composed annotation work for return value 38 | // @Test(expected = ConstraintViolationException.class) 39 | // public void testReturn() { 40 | // service.checkReturn("valid string"); 41 | // } 42 | } 43 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/validator/CheckCustomMatcher.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator; 2 | 3 | import com.google.inject.Guice; 4 | import com.google.inject.Injector; 5 | import com.google.inject.matcher.Matchers; 6 | import org.junit.BeforeClass; 7 | import org.junit.Test; 8 | import ru.vyarus.guice.validator.matcher.ExceptionalService; 9 | import ru.vyarus.guice.validator.matcher.MatchedService; 10 | import ru.vyarus.guice.validator.matcher.SuppressValidation; 11 | 12 | import jakarta.validation.ConstraintViolationException; 13 | 14 | /** 15 | * @author Vyacheslav Rusakov 16 | * @since 20.12.2014 17 | */ 18 | public class CheckCustomMatcher { 19 | 20 | static MatchedService matchedService; 21 | static ExceptionalService exceptionalService; 22 | 23 | @BeforeClass 24 | public static void setUp() throws Exception { 25 | Injector injector = Guice.createInjector(new ValidationModule() 26 | .targetClasses(Matchers.not(Matchers.annotatedWith(SuppressValidation.class)))); 27 | 28 | matchedService = injector.getInstance(MatchedService.class); 29 | exceptionalService = injector.getInstance(ExceptionalService.class); 30 | } 31 | 32 | @Test(expected = ConstraintViolationException.class) 33 | public void testCustomMatcher() throws Exception { 34 | matchedService.doSmth(null); 35 | } 36 | 37 | @Test 38 | public void testMatchedExclusion() throws Exception { 39 | exceptionalService.doSmth(null); 40 | // error not happen, because matcher exclude type from aop processing 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/validator/InstanceBindingTest.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator; 2 | 3 | import com.google.inject.AbstractModule; 4 | import com.google.inject.Guice; 5 | import com.google.inject.Module; 6 | import org.junit.Test; 7 | import org.junit.runners.Parameterized; 8 | import ru.vyarus.guice.validator.simple.SimpleService; 9 | 10 | import java.util.Arrays; 11 | import java.util.Collection; 12 | 13 | /** 14 | * Shows that validation will not work on beans bind as instances. 15 | * 16 | * @author Vyacheslav Rusakov 17 | * @since 20.12.2014 18 | */ 19 | public class InstanceBindingTest extends AbstractParameterizedTest { 20 | 21 | public InstanceBindingTest(String type, SimpleService service) { 22 | super(type, service); 23 | } 24 | 25 | @Parameterized.Parameters(name = "{index}: {0}") 26 | public static Collection generateData() { 27 | final Class type = SimpleService.class; 28 | final Module module = new AbstractModule() { 29 | @Override 30 | protected void configure() { 31 | bind(type).toInstance(new SimpleService()); 32 | } 33 | }; 34 | return Arrays.asList(new Object[][]{ 35 | {EXPLICIT, Guice.createInjector(new ValidationModule().validateAnnotatedOnly(), module).getInstance(type)}, 36 | {IMPLICIT, Guice.createInjector(new ValidationModule(), module).getInstance(type)} 37 | }); 38 | } 39 | 40 | @Test 41 | public void SimpleBeanRequiredFail() throws Exception { 42 | service.beanRequired(null); 43 | // validation should fail, but aop can't be applied to instances 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/ru/vyarus/guice/validator/aop/ValidatedMethodMatcher.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator.aop; 2 | 3 | import com.google.inject.matcher.AbstractMatcher; 4 | 5 | import jakarta.validation.Constraint; 6 | import jakarta.validation.Valid; 7 | import java.lang.annotation.Annotation; 8 | import java.lang.reflect.Method; 9 | 10 | /** 11 | * Validation method matcher. Method require validation if it's annotated with {@code @Valid} or any 12 | * validation annotation (all validation annotations must be annotated with {@code @Constraint}) or 13 | * any param annotated. 14 | * 15 | * @author Vyacheslav Rusakov 16 | * @since 19.12.2014 17 | */ 18 | public class ValidatedMethodMatcher extends AbstractMatcher { 19 | 20 | @Override 21 | public boolean matches(final Method method) { 22 | boolean matches = isValidationAnnotations(method.getAnnotations()); 23 | if (!matches) { 24 | for (Annotation[] annotations : method.getParameterAnnotations()) { 25 | if (isValidationAnnotations(annotations)) { 26 | matches = true; 27 | break; 28 | } 29 | } 30 | } 31 | return matches; 32 | } 33 | 34 | private boolean isValidationAnnotations(final Annotation... annotations) { 35 | boolean matches = false; 36 | for (Annotation ann : annotations) { 37 | final Class annotationType = ann.annotationType(); 38 | if (Valid.class.equals(annotationType) || annotationType.isAnnotationPresent(Constraint.class)) { 39 | matches = true; 40 | break; 41 | } 42 | } 43 | return matches; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/validator/ScriptedValidationTest.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator; 2 | 3 | import org.junit.Test; 4 | import org.junit.runners.Parameterized; 5 | import ru.vyarus.guice.validator.script.ScriptedBean; 6 | import ru.vyarus.guice.validator.script.ScriptedService; 7 | 8 | import jakarta.validation.ConstraintViolationException; 9 | import java.util.Arrays; 10 | import java.util.Collection; 11 | import java.util.Collections; 12 | import java.util.Date; 13 | 14 | /** 15 | * @author Vyacheslav Rusakov 16 | * @since 25.06.2014 17 | */ 18 | public class ScriptedValidationTest extends AbstractParameterizedTest { 19 | 20 | public ScriptedValidationTest(String type, ScriptedService service) { 21 | super(type, service); 22 | } 23 | 24 | @Parameterized.Parameters(name = "{index}: {0}") 25 | public static Collection generateData() { 26 | return AbstractParameterizedTest.generateData(ScriptedService.class); 27 | } 28 | 29 | @Test(expected = ConstraintViolationException.class) 30 | public void testParamsFail() throws Exception { 31 | service.paramsValid(Collections.emptyList(), 1); 32 | } 33 | 34 | @Test 35 | public void testParams() { 36 | service.paramsValid(Arrays.asList(1, 2, 3), 3); 37 | } 38 | 39 | @Test(expected = ConstraintViolationException.class) 40 | public void testScriptedParamFail() throws Exception { 41 | Date now = new Date(); 42 | service.validBean(new ScriptedBean(now, now)); 43 | } 44 | 45 | @Test 46 | public void testScriptedParam() { 47 | Date now = new Date(); 48 | service.validBean(new ScriptedBean(now, new Date(now.getTime() + 10000))); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/validator/CustomValidatorFactoryTest.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator; 2 | 3 | import com.google.inject.Guice; 4 | import org.junit.Test; 5 | import ru.vyarus.guice.validator.customtype.ComplexBean; 6 | import ru.vyarus.guice.validator.customtype.CustomService; 7 | import ru.vyarus.guice.validator.simple.SimpleBean; 8 | import ru.vyarus.guice.validator.simple.SimpleService; 9 | 10 | import jakarta.validation.ConstraintViolationException; 11 | import jakarta.validation.Validation; 12 | import jakarta.validation.ValidatorFactory; 13 | 14 | /** 15 | * @author Vyacheslav Rusakov 16 | * @since 19.12.2014 17 | */ 18 | public class CustomValidatorFactoryTest { 19 | 20 | @Test(expected = ConstraintViolationException.class) 21 | public void testExplicitCustomFactory() throws Exception { 22 | Guice.createInjector(new ValidationModule(Validation.buildDefaultValidatorFactory()).validateAnnotatedOnly()) 23 | .getInstance(SimpleService.class) 24 | .validReturn(new SimpleBean()); 25 | } 26 | 27 | @Test(expected = ConstraintViolationException.class) 28 | public void testImplicitCustomFactory() throws Exception { 29 | Guice.createInjector(new ValidationModule(Validation.buildDefaultValidatorFactory())) 30 | .getInstance(SimpleService.class) 31 | .validReturn(new SimpleBean()); 32 | } 33 | 34 | @Test 35 | public void testConstraintCleanup() throws Exception { 36 | final ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); 37 | Guice.createInjector(new ValidationModule(factory).validateAnnotatedOnly()) 38 | .getInstance(CustomService.class) 39 | .doAction(new ComplexBean("perfect", 12)); 40 | factory.close(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/ru/vyarus/guice/validator/group/annotation/GroupUtils.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator.group.annotation; 2 | 3 | import com.google.common.collect.Lists; 4 | 5 | import java.lang.annotation.Annotation; 6 | import java.lang.reflect.Method; 7 | import java.util.List; 8 | 9 | /** 10 | * Utility methods for group annotation ({@link ValidationGroups}) resolution. 11 | * 12 | * @author Vyacheslav Rusakov 13 | * @since 09.03.2016 14 | */ 15 | public final class GroupUtils { 16 | 17 | private GroupUtils() { 18 | } 19 | 20 | /** 21 | * Search for {@link ValidationGroups} annotations on class and method. 22 | * Checks direct annotations and annotations annotating method/class annotations (inside annotations). 23 | * 24 | * @param method method to analyze 25 | * @return list of found annotations (class annotations first) or empty list 26 | */ 27 | public static List findAnnotations(final Method method) { 28 | final Class declaringClass = method.getDeclaringClass(); 29 | final List annotations = filterAnnotations(declaringClass.getAnnotations()); 30 | annotations.addAll(filterAnnotations(method.getAnnotations())); 31 | return annotations; 32 | } 33 | 34 | private static List filterAnnotations(final Annotation... annotations) { 35 | final List res = Lists.newArrayList(); 36 | for (Annotation ann : annotations) { 37 | if (ValidationGroups.class.equals(ann.annotationType())) { 38 | res.add((ValidationGroups) ann); 39 | } else if (ann.annotationType().isAnnotationPresent(ValidationGroups.class)) { 40 | res.add(ann.annotationType().getAnnotation(ValidationGroups.class)); 41 | } 42 | } 43 | return res; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/validator/CustomMatchersInExplicitModeTest.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator; 2 | 3 | import com.google.inject.Guice; 4 | import com.google.inject.Injector; 5 | import com.google.inject.matcher.Matchers; 6 | import org.junit.BeforeClass; 7 | import org.junit.Test; 8 | import ru.vyarus.guice.validator.customann.SuppressedExplicitClass; 9 | import ru.vyarus.guice.validator.customann.SuppressedExplicitMethod; 10 | import ru.vyarus.guice.validator.customann.ToValidate; 11 | import ru.vyarus.guice.validator.matcher.SuppressValidation; 12 | 13 | import jakarta.validation.ConstraintViolationException; 14 | 15 | /** 16 | * @author Vyacheslav Rusakov 17 | * @since 24.12.2019 18 | */ 19 | public class CustomMatchersInExplicitModeTest { 20 | 21 | static SuppressedExplicitMethod method; 22 | static SuppressedExplicitClass type; 23 | 24 | @BeforeClass 25 | public static void setUp() throws Exception { 26 | Injector injector = Guice.createInjector(new ValidationModule() 27 | .validateAnnotatedOnly(ToValidate.class) 28 | .targetClasses(Matchers.not(Matchers.annotatedWith(SuppressValidation.class))) 29 | .targetMethods(Matchers.not(Matchers.annotatedWith(SuppressValidation.class)))); 30 | 31 | method = injector.getInstance(SuppressedExplicitMethod.class); 32 | type = injector.getInstance(SuppressedExplicitClass.class); 33 | } 34 | 35 | @Test(expected = ConstraintViolationException.class) 36 | public void testNoSuppression() throws Exception { 37 | method.doSmth(null); 38 | // error 39 | } 40 | 41 | @Test 42 | public void testMethodSuppression() throws Exception { 43 | method.doSmth2(null); 44 | // method suppressed 45 | } 46 | 47 | @Test 48 | public void testClassSuppression() throws Exception { 49 | type.doSmth(null); 50 | // type suppressed 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/validator/SingleMethodValidationTest.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator; 2 | 3 | 4 | import org.junit.Assert; 5 | import org.junit.Test; 6 | import org.junit.runners.Parameterized; 7 | 8 | import com.google.inject.Singleton; 9 | import jakarta.validation.ConstraintViolationException; 10 | import jakarta.validation.constraints.NotNull; 11 | import jakarta.validation.executable.ValidateOnExecution; 12 | import java.util.Collection; 13 | 14 | /** 15 | * Checks validation of only one method in service. 16 | * 17 | * @author Vyacheslav Rusakov 18 | * @since 24.06.2014 19 | */ 20 | public class SingleMethodValidationTest extends AbstractParameterizedTest { 21 | 22 | public SingleMethodValidationTest(String type, SampleService service) { 23 | super(type, service); 24 | } 25 | 26 | 27 | @Parameterized.Parameters(name = "{index}: {0}") 28 | public static Collection generateData() { 29 | return AbstractParameterizedTest.generateData(SampleService.class); 30 | } 31 | 32 | @Test 33 | public void testMethodValidation() throws Exception { 34 | if (isExplicit()) { 35 | service.noValidation(null); 36 | } else { 37 | try { 38 | service.noValidation(null); 39 | Assert.fail(); 40 | } catch (ConstraintViolationException ex) { 41 | } 42 | } 43 | } 44 | 45 | @Test(expected = ConstraintViolationException.class) 46 | public void testMethodValidationEnabledFail() throws Exception { 47 | service.enabledValidation(null); 48 | } 49 | 50 | @Test 51 | public void testMethodValidationEnabled() throws Exception { 52 | service.enabledValidation(new Object()); 53 | } 54 | 55 | @Singleton 56 | public static class SampleService { 57 | public void noValidation(@NotNull Object object) { 58 | } 59 | 60 | @ValidateOnExecution 61 | public void enabledValidation(@NotNull Object object) { 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/ru/vyarus/guice/validator/group/annotation/ValidationGroups.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator.group.annotation; 2 | 3 | import java.lang.annotation.Inherited; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.Target; 6 | 7 | import static java.lang.annotation.ElementType.*; 8 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 9 | 10 | /** 11 | * Defines validation groups used in validation of annotated method or any method called within it (context defines). 12 | * Annotation may be set on class to affect all methods or on method directly. If defied on both class and method 13 | * then groups from both annotations will be used. 14 | *

15 | * Works much like transaction (e.g. @Transactional) - defines scope where groups are defined. Definition is thread 16 | * bound. 17 | *

18 | * Inline contexts are also supported: e.g. annotated method defines groups scope; under this method other annotated 19 | * method is called - it will create new validation context with groups from both annotations. This should 20 | * allow writing generic logic and control validation inside it by groups annotation in upper level services. 21 | *

22 | * Annotation may be also declared on other annotation. This may be useful to group multiple group annotations. 23 | * But even in case of single group it could make groups more readable. Instead of: 24 | * {@code @ValidationGroups(CustomerGroup.class)} you can use simply {@code @CustomerGroup}. 25 | * Where: 26 | *


27 |  * {@literal @}Target({TYPE, METHOD})
28 |  * {@literal @}Retention(RetentionPolicy.RUNTIME)
29 |  * {@literal @}ValidationGroups(CustomerGroup.class)
30 |  *  public @interface CustomerGroup {}
31 |  * 
32 | * NOTE: annotation class used as group name. 33 | * 34 | * @author Vyacheslav Rusakov 35 | * @see ru.vyarus.guice.validator.group.ValidationContext 36 | * @since 07.03.2016 37 | */ 38 | @Target({TYPE, METHOD, ANNOTATION_TYPE}) 39 | @Retention(RUNTIME) 40 | @Inherited 41 | public @interface ValidationGroups { 42 | 43 | /** 44 | * @return validation groups to be used. 45 | */ 46 | Class[] value(); 47 | } 48 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/validator/group/NoDefaultGroupTest.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator.group; 2 | 3 | import com.google.common.collect.Sets; 4 | import com.google.inject.Guice; 5 | import com.google.inject.Injector; 6 | import org.junit.Assert; 7 | import org.junit.Test; 8 | import ru.vyarus.guice.validator.ValidationModule; 9 | import ru.vyarus.guice.validator.group.support.PropFunction; 10 | import ru.vyarus.guice.validator.group.support.groups.ann.Group1; 11 | import ru.vyarus.guice.validator.group.support.model.Model; 12 | 13 | import jakarta.validation.ConstraintViolationException; 14 | import jakarta.validation.Valid; 15 | import jakarta.validation.groups.Default; 16 | import java.util.Set; 17 | 18 | /** 19 | * @author Vyacheslav Rusakov 20 | * @since 11.03.2016 21 | */ 22 | public class NoDefaultGroupTest { 23 | 24 | @Test 25 | public void testDefaultGroupDisable() throws Exception { 26 | final Injector injector = Guice.createInjector(new ValidationModule() 27 | .strictGroupsDeclaration()); 28 | final Service service = injector.getInstance(Service.class); 29 | 30 | // default group not applied - no validation errors 31 | service.call(new Model("sample", null, null)); 32 | 33 | // verify default group would fail 34 | injector.getInstance(ValidationContext.class).doWithGroups(new GroupAction() { 35 | @Override 36 | public Object call() throws Throwable { 37 | try { 38 | service.call(new Model("sample", null, null)); 39 | Assert.fail(); 40 | } catch (ConstraintViolationException ex) { 41 | Set props = PropFunction.convert(ex.getConstraintViolations()); 42 | Assert.assertEquals(1, props.size()); 43 | Assert.assertEquals(Sets.newHashSet("def"), props); 44 | } 45 | return null; 46 | } 47 | }, Default.class); 48 | } 49 | 50 | public static class Service { 51 | 52 | // group required, otherwise default group used implicitly (when no groups) 53 | @Group1 54 | void call(@Valid Model model) { 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/validator/SimpleValidationTest.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator; 2 | 3 | import org.junit.Test; 4 | import org.junit.runners.Parameterized; 5 | import ru.vyarus.guice.validator.simple.SimpleBean; 6 | import ru.vyarus.guice.validator.simple.SimpleService; 7 | 8 | import jakarta.validation.ConstraintViolationException; 9 | import java.util.Collection; 10 | 11 | /** 12 | * Checks the simplest validation cases (params/return value) 13 | * 14 | * @author Vyacheslav Rusakov 15 | * @since 24.06.2014 16 | */ 17 | public class SimpleValidationTest extends AbstractParameterizedTest { 18 | 19 | public SimpleValidationTest(String type, SimpleService service) { 20 | super(type, service); 21 | } 22 | 23 | @Parameterized.Parameters(name = "{index}: {0}") 24 | public static Collection generateData() { 25 | return AbstractParameterizedTest.generateData(SimpleService.class); 26 | } 27 | 28 | @Test(expected = ConstraintViolationException.class) 29 | public void SimpleBeanRequiredFail() throws Exception { 30 | service.beanRequired(null); 31 | } 32 | 33 | @Test 34 | public void SimpleBeanRequired() throws Exception { 35 | service.beanRequired(new SimpleBean()); 36 | } 37 | 38 | @Test(expected = ConstraintViolationException.class) 39 | public void SimpleValidBeanRequiredFail() throws Exception { 40 | service.validBeanRequired(new SimpleBean()); 41 | } 42 | 43 | @Test 44 | public void SimpleValidBeanRequired() throws Exception { 45 | service.validBeanRequired(new SimpleBean("user", 10)); 46 | } 47 | 48 | @Test(expected = ConstraintViolationException.class) 49 | public void SimpleNotNullReturnFail() throws Exception { 50 | service.notNullReturn(null); 51 | } 52 | 53 | @Test 54 | public void SimpleNotNullReturn() throws Exception { 55 | service.notNullReturn(new SimpleBean()); 56 | } 57 | 58 | @Test(expected = ConstraintViolationException.class) 59 | public void SimpleValidReturnFail() throws Exception { 60 | service.validReturn(new SimpleBean()); 61 | } 62 | 63 | @Test 64 | public void SimpleValidReturn() throws Exception { 65 | service.notNullReturn(new SimpleBean("user", 10)); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### 3.0.2 (2023-04-12) 2 | * Avoid direct javax.inject annotations usage for jakarta.inject compatibility 3 | (guice native annotations used instead) 4 | 5 | ### 3.0.1 (2021-09-09) 6 | * Fix Default group manual usage ignored in strict groups mode 7 | * Update to guice 5.0.1 (still compatible with guice 4) 8 | 9 | ### 3.0.0 (2021-01-20) 10 | * (breaking) Switch to `jakarta.validation` (EE9, Bean validation 3, hibernate-validator 7 support) from `javax.validation`) 11 | * Fix module name (to ru.vyarus.guice.validator) 12 | * Update to guice 4.2.3 13 | 14 | ### 2.0.0 (2019-12-24) 15 | * Update to guice 4.2.2 16 | * (breaking) Minimum requirement is java 8 17 | * Update to validation api 2.0 ([changes comparing to 1.1](https://beanvalidation.org/2.0/)) 18 | * (breaking) Implicit/explicit modules merged into one configurable module: `ValidationModule`. 19 | - To enable explicit mode: `new ValidationModule().validateAnnotatedOnly()` 20 | or with custom annotation: `new ValidationModule().validateAnnotatedOnly(ToValidate.class)` 21 | - To avoid adding default group (previously `alwaysAddDefaultGroup`): `new ValidationModule().strictGroupsDeclaration()` 22 | - `.withMatcher` previously available only in implicit mode now works in both modes: 23 | `new ValidationModule().targetClasses(...)` 24 | * Add ability to filter target methods (in both modes): `new ValidationModule().targetMethods(...)` 25 | By default, synthetic and bridge methods are filtered 26 | 27 | Migration: 28 | 29 | Old | New 30 | ----|---- 31 | `new ValidationModule()` |`new ValidationModule().validateAnnotatedOnly()` 32 | `new ImplicitValidationModule` | `new ValidationModule()` 33 | `.withMatcher(...)` | `.targetClasses(...)` 34 | `.alwaysAddDefaultGroup(false)` | `strictGroupsDeclaration()` 35 | 36 | 37 | ### 1.2.0 (2016-04-05) 38 | * Add validation groups support: groups declared with annotation and used like transactions (defining groups scope) 39 | 40 | ### 1.1.0 (2014-12-20) 41 | * Update guice 3.0 -> 4.0-beta5 42 | * Add binding for ValidatorFactory instance 43 | * Add ImplicitValidationModule to apply validation based on validation annotations only (without need for explicit @ValidateOnExecution marker) 44 | 45 | ### 1.0.2 (2014-08-16) 46 | * Fix pmd/chcekstyle warnings 47 | 48 | ### 1.0.1 (2014-06-29) 49 | * Fix maven central compatibility 50 | 51 | ### 1.0.0 (2014-06-26) 52 | * Initial release -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created with https://www.gitignore.io 2 | 3 | ### Gradle ### 4 | .gradle/ 5 | build/ 6 | 7 | # Ignore Gradle GUI config 8 | gradle-app.setting 9 | 10 | ### JetBrains ### 11 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm 12 | 13 | /*.iml 14 | 15 | ## Directory-based project format: 16 | .idea/ 17 | 18 | ## File-based project format: 19 | *.ipr 20 | *.iws 21 | 22 | ## Plugin-specific files: 23 | 24 | # IntelliJ 25 | out/ 26 | 27 | # mpeltonen/sbt-idea plugin 28 | .idea_modules/ 29 | 30 | # JIRA plugin 31 | atlassian-ide-plugin.xml 32 | 33 | # Crashlytics plugin (for Android Studio and IntelliJ) 34 | com_crashlytics_export_strings.xml 35 | 36 | 37 | ### Eclipse ### 38 | *.pydevproject 39 | .metadata 40 | bin/ 41 | tmp/ 42 | *.tmp 43 | *.bak 44 | *.swp 45 | *~.nib 46 | local.properties 47 | .settings/ 48 | .loadpath 49 | 50 | # External tool builders 51 | .externalToolBuilders/ 52 | 53 | # Locally stored "Eclipse launch configurations" 54 | *.launch 55 | 56 | # CDT-specific 57 | .cproject 58 | 59 | # PDT-specific 60 | .buildpath 61 | 62 | # sbteclipse plugin 63 | .target 64 | 65 | # TeXlipse plugin 66 | .texlipse 67 | 68 | ### Java ### 69 | *.class 70 | 71 | # Mobile Tools for Java (J2ME) 72 | .mtj.tmp/ 73 | 74 | # Package Files # 75 | *.war 76 | *.ear 77 | 78 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 79 | hs_err_pid* 80 | 81 | 82 | ### NetBeans ### 83 | nbproject/private/ 84 | nbbuild/ 85 | dist/ 86 | nbdist/ 87 | nbactions.xml 88 | nb-configuration.xml 89 | 90 | 91 | ### OSX ### 92 | .DS_Store 93 | .AppleDouble 94 | .LSOverride 95 | 96 | # Icon must end with two \r 97 | Icon 98 | 99 | 100 | # Thumbnails 101 | ._* 102 | 103 | # Files that might appear on external disk 104 | .Spotlight-V100 105 | .Trashes 106 | 107 | # Directories potentially created on remote AFP share 108 | .AppleDB 109 | .AppleDesktop 110 | Network Trash Folder 111 | Temporary Items 112 | .apdisk 113 | 114 | 115 | ### Windows ### 116 | # Windows image file caches 117 | Thumbs.db 118 | ehthumbs.db 119 | 120 | # Folder config file 121 | Desktop.ini 122 | 123 | # Recycle Bin used on file shares 124 | $RECYCLE.BIN/ 125 | 126 | # Windows Installer files 127 | *.cab 128 | *.msi 129 | *.msm 130 | *.msp 131 | 132 | # Windows shortcuts 133 | *.lnk 134 | 135 | 136 | ### Linux ### 137 | *~ 138 | 139 | # KDE directory preferences 140 | .directory 141 | 142 | ### JEnv ### 143 | # JEnv local Java version configuration file 144 | .java-version 145 | 146 | # Used by previous versions of JEnv 147 | .jenv-version 148 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH= 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/validator/group/GroupCacheTest.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator.group; 2 | 3 | import org.junit.After; 4 | import org.junit.Assert; 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | import ru.vyarus.guice.validator.group.annotation.MethodGroupsFactory; 8 | import ru.vyarus.guice.validator.group.support.simple.SimpleAnnService; 9 | 10 | import java.lang.reflect.Field; 11 | import java.lang.reflect.Method; 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | import java.util.Map; 15 | import java.util.concurrent.ExecutorService; 16 | import java.util.concurrent.Executors; 17 | import java.util.concurrent.Future; 18 | 19 | /** 20 | * @author Vyacheslav Rusakov 21 | * @since 13.03.2016 22 | */ 23 | public class GroupCacheTest { 24 | 25 | ExecutorService executor; 26 | MethodGroupsFactory factory = new MethodGroupsFactory(false); 27 | 28 | @Before 29 | public void setUp() throws Exception { 30 | factory.clearCache(); 31 | executor = Executors.newFixedThreadPool(20); 32 | } 33 | 34 | @After 35 | public void tearDown() throws Exception { 36 | executor.shutdown(); 37 | } 38 | 39 | @Test 40 | public void testCache() throws Throwable { 41 | final Method method = SimpleAnnService.class.getMethod("single"); 42 | Class[] groups = factory.create(method); 43 | Assert.assertTrue(groups == factory.create(method)); 44 | } 45 | 46 | @Test 47 | public void testConcurrency() throws Exception { 48 | List> executed = new ArrayList>(); 49 | final Method[] method = new Method[]{ 50 | SimpleAnnService.class.getMethod("single"), 51 | SimpleAnnService.class.getMethod("multiple"), 52 | SimpleAnnService.class.getMethod("nothing"), 53 | SimpleAnnService.class.getMethod("custom") 54 | }; 55 | int count = 30; 56 | for (int i = 0; i < count; i++) { 57 | executed.add( 58 | executor.submit(new Runnable() { 59 | @Override 60 | public void run() { 61 | factory.create(method[(int)(method.length*Math.random())]); 62 | } 63 | }) 64 | ); 65 | } 66 | for(Future future: executed) { 67 | future.get(); 68 | } 69 | } 70 | 71 | @Test 72 | public void testCacheMethods() throws Exception { 73 | Field field = MethodGroupsFactory.class.getDeclaredField("cache"); 74 | field.setAccessible(true); 75 | Map cache = (Map) field.get(factory); 76 | 77 | Assert.assertTrue(cache.isEmpty()); 78 | 79 | final Method method = SimpleAnnService.class.getMethod("single"); 80 | factory.create(method); 81 | Assert.assertEquals(1, cache.size()); 82 | 83 | Assert.assertTrue(MethodGroupsFactory.isCacheEnabled()); 84 | factory.clearCache(); 85 | Assert.assertTrue(MethodGroupsFactory.isCacheEnabled()); 86 | Assert.assertTrue(cache.isEmpty()); 87 | 88 | MethodGroupsFactory.disableCache(); 89 | Assert.assertTrue(!MethodGroupsFactory.isCacheEnabled()); 90 | Assert.assertTrue(cache.isEmpty()); 91 | 92 | factory.create(method); 93 | Assert.assertTrue(cache.isEmpty()); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/validator/group/AnnotationRecognitionTest.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator.group; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | import ru.vyarus.guice.validator.group.annotation.GroupUtils; 6 | import ru.vyarus.guice.validator.group.annotation.ValidationGroups; 7 | import ru.vyarus.guice.validator.group.support.simple.*; 8 | 9 | import java.util.List; 10 | 11 | /** 12 | * @author Vyacheslav Rusakov 13 | * @since 09.03.2016 14 | */ 15 | public class AnnotationRecognitionTest { 16 | 17 | @Test 18 | public void checkSimpleCases() throws Exception { 19 | List res = GroupUtils.findAnnotations(SimpleAnnService.class.getMethod("nothing")); 20 | Assert.assertEquals(0, res.size()); 21 | 22 | res = GroupUtils.findAnnotations(SimpleAnnService.class.getMethod("single")); 23 | Assert.assertEquals(1, res.size()); 24 | 25 | res = GroupUtils.findAnnotations(SimpleAnnService.class.getMethod("multiple")); 26 | Assert.assertEquals(1, res.size()); 27 | 28 | res = GroupUtils.findAnnotations(SimpleAnnService.class.getMethod("custom")); 29 | Assert.assertEquals(1, res.size()); 30 | 31 | res = GroupUtils.findAnnotations(SimpleAnnService.class.getMethod("custom2")); 32 | Assert.assertEquals(2, res.size()); 33 | } 34 | 35 | @Test 36 | public void checkCompositeCases() throws Exception { 37 | List res = GroupUtils.findAnnotations(CompositeAnnService.class.getMethod("nothing")); 38 | Assert.assertEquals(1, res.size()); 39 | 40 | res = GroupUtils.findAnnotations(CompositeAnnService.class.getMethod("single")); 41 | Assert.assertEquals(2, res.size()); 42 | 43 | res = GroupUtils.findAnnotations(CompositeAnnService.class.getMethod("multiple")); 44 | Assert.assertEquals(2, res.size()); 45 | 46 | res = GroupUtils.findAnnotations(CompositeAnnService.class.getMethod("custom")); 47 | Assert.assertEquals(2, res.size()); 48 | 49 | res = GroupUtils.findAnnotations(CompositeAnnService.class.getMethod("custom2")); 50 | Assert.assertEquals(3, res.size()); 51 | } 52 | 53 | @Test 54 | public void checkCompositeCases2() throws Exception { 55 | List res = GroupUtils.findAnnotations(CompositeAnnService2.class.getMethod("nothing")); 56 | Assert.assertEquals(1, res.size()); 57 | 58 | res = GroupUtils.findAnnotations(CompositeAnnService2.class.getMethod("single")); 59 | Assert.assertEquals(2, res.size()); 60 | 61 | res = GroupUtils.findAnnotations(CompositeAnnService2.class.getMethod("custom")); 62 | Assert.assertEquals(2, res.size()); 63 | } 64 | 65 | @Test 66 | public void checkCompositeCases3() throws Exception { 67 | List res = GroupUtils.findAnnotations(CompositeAnnService3.class.getMethod("nothing")); 68 | Assert.assertEquals(2, res.size()); 69 | 70 | res = GroupUtils.findAnnotations(CompositeAnnService3.class.getMethod("single")); 71 | Assert.assertEquals(3, res.size()); 72 | 73 | res = GroupUtils.findAnnotations(CompositeAnnService3.class.getMethod("custom")); 74 | Assert.assertEquals(3, res.size()); 75 | } 76 | 77 | @Test 78 | public void checkInheritance() throws Exception { 79 | List res = GroupUtils.findAnnotations(InheritedService.class.getMethod("groupInherit")); 80 | Assert.assertEquals(1, res.size()); 81 | 82 | res = GroupUtils.findAnnotations(InheritedService.class.getMethod("single")); 83 | Assert.assertEquals(1, res.size()); 84 | 85 | res = GroupUtils.findAnnotations(NotInheritedService.class.getMethod("noGroups")); 86 | Assert.assertEquals(0, res.size()); 87 | 88 | res = GroupUtils.findAnnotations(NotInheritedService.class.getMethod("custom")); 89 | Assert.assertEquals(0, res.size()); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/ru/vyarus/guice/validator/group/ValidationContext.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator.group; 2 | 3 | import com.google.common.base.Throwables; 4 | import com.google.inject.name.Named; 5 | import ru.vyarus.guice.validator.ValidationModule; 6 | 7 | import com.google.inject.Inject; 8 | import com.google.inject.Singleton; 9 | import jakarta.validation.groups.Default; 10 | import java.util.*; 11 | 12 | /** 13 | * Defines validation groups used by method validation. 14 | * Actual groups are defined with {@link ru.vyarus.guice.validator.group.annotation.ValidationGroups} 15 | * annotation. 16 | *

17 | * Context is thread bound. 18 | *

19 | * Inline contexts inherit all groups from upper levels. 20 | *

21 | * Default group is implicitly appended if allowed by module configuration (default true): see 22 | * {@link ValidationModule#strictGroupsDeclaration()} 23 | *

24 | * Groups may be defined directly (without annotations usage) by using 25 | * {@link ValidationContext#doWithGroups(ru.vyarus.guice.validator.group.GroupAction, java.lang.Class[])}. 26 | * 27 | * @author Vyacheslav Rusakov 28 | * @since 07.03.2016 29 | */ 30 | @Singleton 31 | public class ValidationContext { 32 | 33 | private static final Class[] EMPTY = new Class[0]; 34 | private final ThreadLocal[]>> threadContext = new ThreadLocal<>(); 35 | 36 | private final boolean addDefaultGroup; 37 | 38 | @Inject 39 | public ValidationContext(@Named("guice.validator.addDefaultGroup") final boolean addDefaultGroup) { 40 | this.addDefaultGroup = addDefaultGroup; 41 | } 42 | 43 | /** 44 | * @return current context validation groups or empty array when no groups defined 45 | */ 46 | public Class[] getContextGroups() { 47 | Class[] res = EMPTY; 48 | final List[]> context = threadContext.get(); 49 | if (context != null) { 50 | res = context.get(context.size() - 1); 51 | } 52 | return res; 53 | } 54 | 55 | /** 56 | * Defines context validation groups. Context is defined for all logic inside action callback 57 | * (in current thread). 58 | * Note: does not override current context groups. 59 | * 60 | * @param action action callback to be executed with validation groups 61 | * @param groups validation groups to use 62 | * @param action return type 63 | * @return object produced by action callback 64 | */ 65 | public T doWithGroups(final GroupAction action, final Class... groups) { 66 | pushContext(groups); 67 | try { 68 | return action.call(); 69 | } catch (Throwable ex) { 70 | Throwables.throwIfUnchecked(ex); 71 | throw new IllegalStateException(ex); 72 | } finally { 73 | popContext(); 74 | } 75 | } 76 | 77 | @SuppressWarnings("PMD.UseVarargs") 78 | private void pushContext(final Class[] groups) { 79 | List[]> context = threadContext.get(); 80 | if (context == null) { 81 | context = new ArrayList<>(); 82 | threadContext.set(context); 83 | } 84 | // remove duplicates 85 | final Set> allgroups = new LinkedHashSet<>(); 86 | if (!context.isEmpty()) { 87 | Collections.addAll(allgroups, context.get(context.size() - 1)); 88 | // should not be applied otherwise because default group could be added manually 89 | if (addDefaultGroup) { 90 | // default group will always be last (here it comes from upper context and must be removed) 91 | allgroups.remove(Default.class); 92 | } 93 | } 94 | Collections.addAll(allgroups, groups); 95 | context.add(allgroups.toArray(new Class[0])); 96 | } 97 | 98 | private void popContext() { 99 | final List[]> context = threadContext.get(); 100 | if (context.size() > 1) { 101 | context.remove(context.size() - 1); 102 | } else { 103 | threadContext.remove(); 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/ru/vyarus/guice/validator/group/annotation/MethodGroupsFactory.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator.group.annotation; 2 | 3 | import com.google.common.base.Preconditions; 4 | import com.google.inject.name.Named; 5 | 6 | import com.google.inject.Inject; 7 | import com.google.inject.Singleton; 8 | import jakarta.validation.groups.Default; 9 | import java.lang.reflect.Method; 10 | import java.util.*; 11 | import java.util.concurrent.locks.ReentrantLock; 12 | 13 | /** 14 | * Builds method validation groups context, by resolving {@link ValidationGroups} annotations. 15 | * Resolved contexts are cached for future re-use. 16 | *

17 | * Cache can be disabled using environment variable or system property 18 | * {@code System.setProperty(MethodGroupsFactory.CACHE_PROPERTY, 'false')} (or use 19 | * {@code MethodGroupsFactory#disableCache()}). 20 | * Property value checked on cache write. To clear current cache state use instance method: 21 | * {@link #clearCache()}. 22 | *

23 | * Default group is implicitly appended if allowed by module configuration (default true): see 24 | * {@link ru.vyarus.guice.validator.ValidationModule#strictGroupsDeclaration()}. 25 | * 26 | * @author Vyacheslav Rusakov 27 | * @since 09.03.2016 28 | */ 29 | @Singleton 30 | public class MethodGroupsFactory { 31 | 32 | /** 33 | * System property or environment variable name to disable cache. 34 | * If value is 'false' - cache disabled, otherwise cache enabled. 35 | */ 36 | public static final String CACHE_PROPERTY = MethodGroupsFactory.class.getName() + ".cache"; 37 | // lock will not affect performance for cached descriptors, just to make sure nothing was build two times 38 | private static final ReentrantLock LOCK = new ReentrantLock(); 39 | 40 | private final Map[]> cache = new HashMap<>(); 41 | 42 | private final boolean addDefaultGroup; 43 | 44 | @Inject 45 | public MethodGroupsFactory(@Named("guice.validator.addDefaultGroup") final boolean addDefaultGroup) { 46 | this.addDefaultGroup = addDefaultGroup; 47 | } 48 | 49 | public Class[] create(final Method method) { 50 | Class[] groups = cache.get(method); 51 | if (groups == null) { 52 | LOCK.lock(); 53 | try { 54 | // groups could be created while thread wait for LOCK 55 | groups = cache.get(method); 56 | if (groups == null) { 57 | groups = buildGroups(method); 58 | if (isCacheEnabled()) { 59 | // internal check 60 | Preconditions.checkState(cache.get(method) == null, 61 | "Bad concurrency: groups already present in cache"); 62 | cache.put(method, groups); 63 | } 64 | } 65 | } finally { 66 | LOCK.unlock(); 67 | } 68 | } 69 | return groups; 70 | } 71 | 72 | private Class[] buildGroups(final Method method) { 73 | final List annotations = GroupUtils.findAnnotations(method); 74 | // remove duplicates 75 | final Set> result = new LinkedHashSet<>(); 76 | for (ValidationGroups group : annotations) { 77 | Collections.addAll(result, group.value()); 78 | } 79 | if (addDefaultGroup) { 80 | result.add(Default.class); 81 | } 82 | return result.toArray(new Class[0]); 83 | } 84 | 85 | /** 86 | * Clears cached contexts (already parsed). 87 | * Cache could be completely disabled using system property or environment variable 88 | * 89 | * @see #CACHE_PROPERTY 90 | */ 91 | public void clearCache() { 92 | LOCK.lock(); 93 | try { 94 | cache.clear(); 95 | } finally { 96 | LOCK.unlock(); 97 | } 98 | } 99 | 100 | /** 101 | * Disables descriptors cache. 102 | */ 103 | public static void disableCache() { 104 | System.setProperty(CACHE_PROPERTY, Boolean.FALSE.toString()); 105 | } 106 | 107 | /** 108 | * @return true is cache enabled, false otherwise 109 | */ 110 | public static boolean isCacheEnabled() { 111 | final String no = Boolean.FALSE.toString(); 112 | return !no.equals(System.getenv(CACHE_PROPERTY)) 113 | && !no.equals(System.getProperty(CACHE_PROPERTY)); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/main/java/ru/vyarus/guice/validator/aop/ValidationMethodInterceptor.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator.aop; 2 | 3 | import org.aopalliance.intercept.MethodInterceptor; 4 | import org.aopalliance.intercept.MethodInvocation; 5 | import ru.vyarus.guice.validator.group.ValidationContext; 6 | 7 | import com.google.inject.Inject; 8 | import com.google.inject.Singleton; 9 | import jakarta.validation.ConstraintViolation; 10 | import jakarta.validation.ConstraintViolationException; 11 | import jakarta.validation.ElementKind; 12 | import jakarta.validation.Path; 13 | import jakarta.validation.executable.ExecutableValidator; 14 | import java.lang.reflect.Member; 15 | import java.util.Arrays; 16 | import java.util.Iterator; 17 | import java.util.Set; 18 | 19 | /** 20 | * Method interceptor (actual method validation implementation). 21 | *

22 | * Based on {@code org.hibernate.validator.internal.cdi.interceptor.ValidationInterceptor} 23 | * from hibernate-validator-cdi-5.1.1.Final module. 24 | *

25 | * Note: validation groups are declared with {@link ru.vyarus.guice.validator.group.annotation.ValidationGroups} 26 | * annotation and handled by different interceptor. 27 | * 28 | * @author hibernate-validator team 29 | * @since 24.06.2014 30 | */ 31 | @Singleton 32 | public class ValidationMethodInterceptor implements MethodInterceptor { 33 | 34 | @Inject 35 | private ExecutableValidator validator; 36 | @Inject 37 | private ValidationContext context; 38 | 39 | @Override 40 | public Object invoke(final MethodInvocation invocation) throws Throwable { 41 | final Class[] groups = context.getContextGroups(); 42 | 43 | Set> violations = validator.validateParameters( 44 | invocation.getThis(), invocation.getMethod(), invocation.getArguments(), groups 45 | ); 46 | 47 | if (!violations.isEmpty()) { 48 | throw new ConstraintViolationException( 49 | getMessage(invocation.getMethod(), invocation.getArguments(), violations), violations); 50 | } 51 | 52 | final Object result = invocation.proceed(); 53 | violations = validator.validateReturnValue(invocation.getThis(), invocation.getMethod(), result, groups); 54 | 55 | if (!violations.isEmpty()) { 56 | throw new ConstraintViolationException( 57 | getMessage(invocation.getMethod(), invocation.getArguments(), violations), violations); 58 | } 59 | 60 | return result; 61 | } 62 | 63 | @SuppressWarnings("PMD.ConsecutiveLiteralAppends") 64 | private String getMessage(final Member member, final Object[] args, 65 | final Set> violations) { 66 | final StringBuilder message = new StringBuilder(300) 67 | .append(violations.size()) 68 | .append(" constraint violation(s) occurred during method validation.") 69 | .append("\nConstructor or Method: ").append(member) 70 | .append("\nArgument values: ").append(Arrays.toString(args)) 71 | .append("\nConstraint violations: "); 72 | int i = 1; 73 | for (ConstraintViolation constraintViolation : violations) { 74 | final Path.Node leafNode = getLeafNode(constraintViolation); 75 | message.append("\n (") 76 | .append(i++) 77 | .append(") Kind: ") 78 | .append(leafNode.getKind()); 79 | if (leafNode.getKind() == ElementKind.PARAMETER) { 80 | message.append("\n parameter index: ") 81 | .append(leafNode.as(Path.ParameterNode.class).getParameterIndex()); 82 | } 83 | message.append("\n message: ").append(constraintViolation.getMessage()) 84 | .append("\n root bean: ").append(constraintViolation.getRootBean()) 85 | .append("\n property path: ").append(constraintViolation.getPropertyPath()) 86 | .append("\n constraint: ").append(constraintViolation.getConstraintDescriptor().getAnnotation()); 87 | } 88 | 89 | return message.toString(); 90 | } 91 | 92 | private Path.Node getLeafNode(final ConstraintViolation constraintViolation) { 93 | final Iterator nodes = constraintViolation.getPropertyPath().iterator(); 94 | Path.Node leafNode = null; 95 | while (nodes.hasNext()) { 96 | leafNode = nodes.next(); 97 | } 98 | return leafNode; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/validator/group/GroupRecognitionTest.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator.group; 2 | 3 | import com.google.common.collect.Sets; 4 | import org.junit.Assert; 5 | import org.junit.Test; 6 | import ru.vyarus.guice.validator.group.annotation.MethodGroupsFactory; 7 | import ru.vyarus.guice.validator.group.support.groups.FooGroup; 8 | import ru.vyarus.guice.validator.group.support.groups.FooGroup2; 9 | import ru.vyarus.guice.validator.group.support.groups.RootFooGroup; 10 | import ru.vyarus.guice.validator.group.support.groups.ann.Group1; 11 | import ru.vyarus.guice.validator.group.support.groups.ann.Group2; 12 | import ru.vyarus.guice.validator.group.support.groups.ann.RootGroup; 13 | import ru.vyarus.guice.validator.group.support.simple.*; 14 | 15 | /** 16 | * @author Vyacheslav Rusakov 17 | * @since 11.03.2016 18 | */ 19 | @SuppressWarnings("unchecked") 20 | public class GroupRecognitionTest { 21 | 22 | MethodGroupsFactory factory = new MethodGroupsFactory(false); 23 | 24 | @Test 25 | public void checkSimpleCases() throws Throwable { 26 | Assert.assertArrayEquals(new Class[0], 27 | factory.create(SimpleAnnService.class.getMethod("nothing"))); 28 | 29 | Assert.assertArrayEquals(new Class[]{FooGroup.class}, 30 | factory.create(SimpleAnnService.class.getMethod("single"))); 31 | 32 | Assert.assertEquals(Sets.newHashSet(FooGroup.class, FooGroup2.class), 33 | Sets.newHashSet(factory.create(SimpleAnnService.class.getMethod("multiple")))); 34 | 35 | Assert.assertArrayEquals(new Class[]{Group1.class}, 36 | factory.create(SimpleAnnService.class.getMethod("custom"))); 37 | 38 | Assert.assertEquals(Sets.newHashSet(Group1.class, Group2.class), 39 | Sets.newHashSet(factory.create(SimpleAnnService.class.getMethod("custom2")))); 40 | } 41 | 42 | @Test 43 | public void checkCompositeCases() throws Throwable { 44 | Assert.assertArrayEquals(new Class[]{RootGroup.class}, 45 | factory.create(CompositeAnnService.class.getMethod("nothing"))); 46 | 47 | Assert.assertEquals(Sets.newHashSet(RootGroup.class, FooGroup.class), 48 | Sets.newHashSet(factory.create(CompositeAnnService.class.getMethod("single")))); 49 | 50 | Assert.assertEquals(Sets.newHashSet(RootGroup.class, FooGroup.class, FooGroup2.class), 51 | Sets.newHashSet(factory.create(CompositeAnnService.class.getMethod("multiple")))); 52 | 53 | Assert.assertEquals(Sets.newHashSet(RootGroup.class, Group1.class), 54 | Sets.newHashSet(factory.create(CompositeAnnService.class.getMethod("custom")))); 55 | 56 | Assert.assertEquals(Sets.newHashSet(RootGroup.class, Group1.class, Group2.class), 57 | Sets.newHashSet(factory.create(CompositeAnnService.class.getMethod("custom2")))); 58 | } 59 | 60 | @Test 61 | public void checkCompositeCases2() throws Throwable { 62 | Assert.assertArrayEquals(new Class[]{RootFooGroup.class}, 63 | factory.create(CompositeAnnService2.class.getMethod("nothing"))); 64 | 65 | Assert.assertEquals(Sets.newHashSet(RootFooGroup.class, FooGroup.class), 66 | Sets.newHashSet(factory.create(CompositeAnnService2.class.getMethod("single")))); 67 | 68 | Assert.assertEquals(Sets.newHashSet(RootFooGroup.class, Group1.class), 69 | Sets.newHashSet(factory.create(CompositeAnnService2.class.getMethod("custom")))); 70 | } 71 | 72 | @Test 73 | public void checkCompositeCases3() throws Throwable { 74 | Assert.assertEquals(Sets.newHashSet(RootGroup.class, RootFooGroup.class), 75 | Sets.newHashSet(factory.create(CompositeAnnService3.class.getMethod("nothing")))); 76 | 77 | Assert.assertEquals(Sets.newHashSet(RootGroup.class, RootFooGroup.class, FooGroup.class), 78 | Sets.newHashSet(factory.create(CompositeAnnService3.class.getMethod("single")))); 79 | 80 | Assert.assertEquals(Sets.newHashSet(RootGroup.class, RootFooGroup.class, Group1.class), 81 | Sets.newHashSet(factory.create(CompositeAnnService3.class.getMethod("custom")))); 82 | } 83 | 84 | @Test 85 | public void checkInheritance() throws Throwable { 86 | 87 | // @validationGroups is inheritable and propagate 88 | Assert.assertEquals(Sets.newHashSet(RootFooGroup.class), 89 | Sets.newHashSet(factory.create(InheritedService.class.getMethod("groupInherit")))); 90 | 91 | // custom annotation was not inheritable 92 | Assert.assertEquals(Sets.newHashSet(), 93 | Sets.newHashSet(factory.create(NotInheritedService.class.getMethod("noGroups")))); 94 | 95 | // annotations on methods are not inherit 96 | Assert.assertEquals(Sets.newHashSet(RootFooGroup.class), 97 | Sets.newHashSet(factory.create(InheritedService.class.getMethod("single")))); 98 | 99 | Assert.assertEquals(Sets.newHashSet(), 100 | Sets.newHashSet(factory.create(NotInheritedService.class.getMethod("custom")))); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/validator/GroupsTest.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator; 2 | 3 | import com.google.common.collect.Sets; 4 | import org.junit.Assert; 5 | import org.junit.Test; 6 | import org.junit.runners.Parameterized; 7 | import ru.vyarus.guice.validator.group.ValidationContext; 8 | import ru.vyarus.guice.validator.group.support.PropFunction; 9 | import ru.vyarus.guice.validator.group.support.groups.ann.Group1; 10 | import ru.vyarus.guice.validator.group.support.groups.ann.Group2; 11 | import ru.vyarus.guice.validator.group.support.model.Model; 12 | 13 | import com.google.inject.Inject; 14 | import jakarta.validation.ConstraintViolationException; 15 | import jakarta.validation.Valid; 16 | import jakarta.validation.executable.ValidateOnExecution; 17 | import jakarta.validation.groups.Default; 18 | import java.util.Collection; 19 | import java.util.Set; 20 | 21 | /** 22 | * @author Vyacheslav Rusakov 23 | * @since 11.03.2016 24 | */ 25 | public class GroupsTest extends AbstractParameterizedTest { 26 | 27 | public GroupsTest(String type, Service service) { 28 | super(type, service); 29 | } 30 | 31 | @Parameterized.Parameters(name = "{index}: {0}") 32 | public static Collection generateData() { 33 | return AbstractParameterizedTest.generateData(Service.class); 34 | } 35 | 36 | @Test 37 | public void testNoContext() throws Exception { 38 | // ok 39 | service.noContext(new Model(null, null, "sample")); 40 | Assert.assertTrue(service.lastCallGroups.length == 0); 41 | 42 | try { 43 | // default group 44 | service.noContext(new Model(null, null, null)); 45 | Assert.fail(); 46 | } catch (ConstraintViolationException ex) { 47 | Set props = PropFunction.convert(ex.getConstraintViolations()); 48 | Assert.assertEquals(1, props.size()); 49 | Assert.assertEquals(Sets.newHashSet("def"), props); 50 | } 51 | } 52 | 53 | @Test 54 | public void testContext1() throws Exception { 55 | // ok 56 | service.context1(new Model("sample", null, "sample")); 57 | Assert.assertArrayEquals(new Class[]{Group1.class, Default.class}, service.lastCallGroups); 58 | 59 | try { 60 | service.context1(new Model(null, null, "sample")); 61 | Assert.fail(); 62 | } catch (ConstraintViolationException ex) { 63 | Set props = PropFunction.convert(ex.getConstraintViolations()); 64 | Assert.assertEquals(1, props.size()); 65 | Assert.assertEquals(Sets.newHashSet("foo"), props); 66 | } 67 | 68 | // default group is implicitly activated by default 69 | try { 70 | service.context1(new Model(null, null, null)); 71 | Assert.fail(); 72 | } catch (ConstraintViolationException ex) { 73 | Set props = PropFunction.convert(ex.getConstraintViolations()); 74 | Assert.assertEquals(2, props.size()); 75 | Assert.assertEquals(Sets.newHashSet("foo", "def"), props); 76 | } 77 | } 78 | 79 | 80 | @Test 81 | public void testMultipleContexts() throws Exception { 82 | // ok 83 | service.multipleContexts(new Model("sample", "sample", "sample")); 84 | Assert.assertArrayEquals(new Class[]{Group1.class, Group2.class, Default.class}, service.lastCallGroups); 85 | 86 | try { 87 | service.multipleContexts(new Model(null, null, null)); 88 | Assert.fail(); 89 | } catch (ConstraintViolationException ex) { 90 | Set props = PropFunction.convert(ex.getConstraintViolations()); 91 | Assert.assertEquals(3, props.size()); 92 | Assert.assertEquals(Sets.newHashSet("foo", "bar", "def"), props); 93 | } 94 | } 95 | 96 | @Test 97 | public void testContextTree() throws Exception { 98 | service.contextTree(new Model("sample", "sample", "sample")); 99 | Assert.assertArrayEquals(new Class[]{Group2.class, Group1.class, Default.class}, service.lastCallGroups); 100 | 101 | try { 102 | service.contextTree(new Model(null, "sample", "sample")); 103 | Assert.fail(); 104 | } catch (ConstraintViolationException ex) { 105 | Set props = PropFunction.convert(ex.getConstraintViolations()); 106 | Assert.assertEquals(1, props.size()); 107 | Assert.assertEquals(Sets.newHashSet("foo"), props); 108 | } 109 | } 110 | 111 | 112 | @Test 113 | public void testDuplicateGroups() throws Exception { 114 | service.duplicateGroups(new Model("sample", "sample", "sample")); 115 | Assert.assertArrayEquals(new Class[]{Group1.class, Group2.class, Default.class}, service.lastCallGroups); 116 | } 117 | 118 | @ValidateOnExecution 119 | public static class Service { 120 | 121 | @Inject 122 | private ValidationContext context; 123 | 124 | public Class[] lastCallGroups; 125 | 126 | public void noContext(@Valid Model model) { 127 | lastCallGroups = context.getContextGroups(); 128 | } 129 | 130 | @Group1 131 | public void context1(@Valid Model model) { 132 | lastCallGroups = context.getContextGroups(); 133 | } 134 | 135 | @Group1 136 | @Group2 137 | public void multipleContexts(@Valid Model model) { 138 | lastCallGroups = context.getContextGroups(); 139 | } 140 | 141 | @Group2 142 | public void contextTree(@Valid Model model) { 143 | context1(model); 144 | } 145 | 146 | @Group1 147 | public void duplicateGroups(@Valid Model model) { 148 | contextTree(model); 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # SPDX-License-Identifier: Apache-2.0 19 | # 20 | 21 | ############################################################################## 22 | # 23 | # Gradle start up script for POSIX generated by Gradle. 24 | # 25 | # Important for running: 26 | # 27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 28 | # noncompliant, but you have some other compliant shell such as ksh or 29 | # bash, then to run this script, type that shell name before the whole 30 | # command line, like: 31 | # 32 | # ksh Gradle 33 | # 34 | # Busybox and similar reduced shells will NOT work, because this script 35 | # requires all of these POSIX shell features: 36 | # * functions; 37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 39 | # * compound commands having a testable exit status, especially «case»; 40 | # * various built-in commands including «command», «set», and «ulimit». 41 | # 42 | # Important for patching: 43 | # 44 | # (2) This script targets any POSIX shell, so it avoids extensions provided 45 | # by Bash, Ksh, etc; in particular arrays are avoided. 46 | # 47 | # The "traditional" practice of packing multiple parameters into a 48 | # space-separated string is a well documented source of bugs and security 49 | # problems, so this is (mostly) avoided, by progressively accumulating 50 | # options in "$@", and eventually passing that to Java. 51 | # 52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 54 | # see the in-line comments for details. 55 | # 56 | # There are tweaks for specific operating systems such as AIX, CygWin, 57 | # Darwin, MinGW, and NonStop. 58 | # 59 | # (3) This script is generated from the Groovy template 60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 61 | # within the Gradle project. 62 | # 63 | # You can find Gradle at https://github.com/gradle/gradle/. 64 | # 65 | ############################################################################## 66 | 67 | # Attempt to set APP_HOME 68 | 69 | # Resolve links: $0 may be a link 70 | app_path=$0 71 | 72 | # Need this for daisy-chained symlinks. 73 | while 74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 75 | [ -h "$app_path" ] 76 | do 77 | ls=$( ls -ld "$app_path" ) 78 | link=${ls#*' -> '} 79 | case $link in #( 80 | /*) app_path=$link ;; #( 81 | *) app_path=$APP_HOME$link ;; 82 | esac 83 | done 84 | 85 | # This is normally unused 86 | # shellcheck disable=SC2034 87 | APP_BASE_NAME=${0##*/} 88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH="\\\"\\\"" 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | if ! command -v java >/dev/null 2>&1 137 | then 138 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 139 | 140 | Please set the JAVA_HOME variable in your environment to match the 141 | location of your Java installation." 142 | fi 143 | fi 144 | 145 | # Increase the maximum file descriptors if we can. 146 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 147 | case $MAX_FD in #( 148 | max*) 149 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 150 | # shellcheck disable=SC2039,SC3045 151 | MAX_FD=$( ulimit -H -n ) || 152 | warn "Could not query maximum file descriptor limit" 153 | esac 154 | case $MAX_FD in #( 155 | '' | soft) :;; #( 156 | *) 157 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 158 | # shellcheck disable=SC2039,SC3045 159 | ulimit -n "$MAX_FD" || 160 | warn "Could not set maximum file descriptor limit to $MAX_FD" 161 | esac 162 | fi 163 | 164 | # Collect all arguments for the java command, stacking in reverse order: 165 | # * args from the command line 166 | # * the main class name 167 | # * -classpath 168 | # * -D...appname settings 169 | # * --module-path (only if needed) 170 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 171 | 172 | # For Cygwin or MSYS, switch paths to Windows format before running java 173 | if "$cygwin" || "$msys" ; then 174 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 175 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 176 | 177 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 178 | 179 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 180 | for arg do 181 | if 182 | case $arg in #( 183 | -*) false ;; # don't mess with options #( 184 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 185 | [ -e "$t" ] ;; #( 186 | *) false ;; 187 | esac 188 | then 189 | arg=$( cygpath --path --ignore --mixed "$arg" ) 190 | fi 191 | # Roll the args list around exactly as many times as the number of 192 | # args, so each arg winds up back in the position where it started, but 193 | # possibly modified. 194 | # 195 | # NB: a `for` loop captures its iteration list before it begins, so 196 | # changing the positional parameters here affects neither the number of 197 | # iterations, nor the values presented in `arg`. 198 | shift # remove old arg 199 | set -- "$@" "$arg" # push replacement arg 200 | done 201 | fi 202 | 203 | 204 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 205 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 206 | 207 | # Collect all arguments for the java command: 208 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 209 | # and any embedded shellness will be escaped. 210 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 211 | # treated as '${Hostname}' itself on the command line. 212 | 213 | set -- \ 214 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 215 | -classpath "$CLASSPATH" \ 216 | -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ 217 | "$@" 218 | 219 | # Stop when "xargs" is not available. 220 | if ! command -v xargs >/dev/null 2>&1 221 | then 222 | die "xargs is not available" 223 | fi 224 | 225 | # Use "xargs" to parse quoted args. 226 | # 227 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 228 | # 229 | # In Bash we could simply go: 230 | # 231 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 232 | # set -- "${ARGS[@]}" "$@" 233 | # 234 | # but POSIX shell has neither arrays nor command substitution, so instead we 235 | # post-process each arg (as a line of input to sed) to backslash-escape any 236 | # character that might be a shell metacharacter, then use eval to reverse 237 | # that process (while maintaining the separation between arguments), and wrap 238 | # the whole thing up as a single "set" statement. 239 | # 240 | # This will of course break if any of these variables contains a newline or 241 | # an unmatched quote. 242 | # 243 | 244 | eval "set -- $( 245 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 246 | xargs -n1 | 247 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 248 | tr '\n' ' ' 249 | )" '"$@"' 250 | 251 | exec "$JAVACMD" "$@" 252 | -------------------------------------------------------------------------------- /src/main/java/ru/vyarus/guice/validator/ValidationModule.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.validator; 2 | 3 | import com.google.inject.AbstractModule; 4 | import com.google.inject.matcher.Matcher; 5 | import com.google.inject.matcher.Matchers; 6 | import com.google.inject.name.Names; 7 | import ru.vyarus.guice.validator.aop.DeclaredMethodMatcher; 8 | import ru.vyarus.guice.validator.aop.ValidatedMethodMatcher; 9 | import ru.vyarus.guice.validator.aop.ValidationMethodInterceptor; 10 | import ru.vyarus.guice.validator.constraint.GuiceConstraintValidatorFactory; 11 | import ru.vyarus.guice.validator.group.ValidationContext; 12 | import ru.vyarus.guice.validator.group.aop.ValidationGroupInterceptor; 13 | import ru.vyarus.guice.validator.group.aop.ValidationGroupMatcher; 14 | 15 | import jakarta.validation.Validation; 16 | import jakarta.validation.Validator; 17 | import jakarta.validation.ValidatorFactory; 18 | import jakarta.validation.executable.ExecutableValidator; 19 | import jakarta.validation.executable.ValidateOnExecution; 20 | import java.lang.annotation.Annotation; 21 | import java.lang.reflect.AnnotatedElement; 22 | import java.lang.reflect.Method; 23 | 24 | /** 25 | * Validation module. By default, validation executed when {@code @Valid} or any {@code @Constraint} annotation found 26 | * on method or method parameter (implicit mode). Alternatively, it could validate only annotated 27 | * modules (explicit mode): see {@link #validateAnnotatedOnly()}. 28 | *

29 | * In both modes, target scope could be reduced using {@link #targetClasses(Matcher)} and 30 | * {@link #targetMethods(Matcher)}. 31 | *

32 | * Binds {@link jakarta.validation.Validator}, {@link jakarta.validation.executable.ExecutableValidator} and 33 | * {@link jakarta.validation.ValidatorFactory} instances to context. 34 | *

35 | * Validators are obtained from guice context (using custom {@link jakarta.validation.ConstraintValidatorFactory}, so 36 | * it is possible to use guice injections there. 37 | *

38 | * Custom validation groups could be declared for target methods using 39 | * {@link ru.vyarus.guice.validator.group.annotation.ValidationGroups}. 40 | * By default, {@link jakarta.validation.groups.Default} group assumed to be always selected when custom validation 41 | * groups declared with {@link ru.vyarus.guice.validator.group.annotation.ValidationGroups}. To avoid implicit 42 | * default groups inclusion use {@link #strictGroupsDeclaration()}. 43 | * 44 | * @author Vyacheslav Rusakov 45 | * @since 24.06.2014 46 | */ 47 | public class ValidationModule extends AbstractModule { 48 | 49 | /** 50 | * Matches declared methods, ignoring synthetic and bridge methods (introduced by compiler and causing 51 | * execution warnings). 52 | */ 53 | public static final Matcher DECLARED_METHOD_MATCHER = new DeclaredMethodMatcher(); 54 | 55 | private final ValidatorFactory factory; 56 | 57 | private Matcher> classMatcher = Matchers.any(); 58 | private Matcher methodMatcher = DECLARED_METHOD_MATCHER; 59 | private Class validationAnnotation; 60 | private boolean addDefaultGroup = true; 61 | 62 | /** 63 | * Creates module with default validator factory. 64 | */ 65 | public ValidationModule() { 66 | this(Validation.buildDefaultValidatorFactory()); 67 | } 68 | 69 | /** 70 | * Create module with custom validator factory. 71 | * Useful if other (non guice) parts use validation too and don't want to know about guice. 72 | * 73 | * @param factory base factory 74 | */ 75 | public ValidationModule(final ValidatorFactory factory) { 76 | this.factory = factory; 77 | } 78 | 79 | /** 80 | * By default, ({@link jakarta.validation.groups.Default}) group is always added to groups 81 | * defined with {@link ru.vyarus.guice.validator.group.annotation.ValidationGroups} annotation. 82 | *

83 | * Calling this method disables default behavior: after calling it, {@link jakarta.validation.groups.Default} 84 | * must be explicitly declared. 85 | * 86 | * @return module instance for chained calls 87 | */ 88 | public ValidationModule strictGroupsDeclaration() { 89 | this.addDefaultGroup = false; 90 | return this; 91 | } 92 | 93 | /** 94 | * Shortcut for {@link #validateAnnotatedOnly(Class)} to use default {@link ValidateOnExecution} annotation. 95 | *

96 | * Note that such annotation usage contradict with its javadoc, but annotation name is ideal for this use case 97 | * (besides, introducing new general annotation is even worse option). 98 | * 99 | * @return module instance for chained calls 100 | */ 101 | public ValidationModule validateAnnotatedOnly() { 102 | return validateAnnotatedOnly(ValidateOnExecution.class); 103 | } 104 | 105 | /** 106 | * Enables "explicit mode" when only methods annotated with provided annotation (and all methods in 107 | * annotated classes) are validated. 108 | *

109 | * Explicit validation activation may be useful when validation annotations used just for compile time checks 110 | * (see hibernate validator apt lib). Moreover, it makes "automatic" validation more explicit. 111 | *

112 | * Note that {@link #targetClasses(Matcher)} and {@link #targetMethods(Matcher)} could prevent method validation 113 | * (reduce scope). 114 | *

115 | * There is a matching difference between modes: implicit mode matches only methods with detected validation 116 | * annotations, whereas in explicit, when target annotation declared on class, all methods are matched. 117 | * 118 | * @param annotation annotation to use 119 | * @return module instance for chained calls 120 | * @see #validateAnnotatedOnly() for default annotation ({@link ValidateOnExecution}) 121 | */ 122 | public ValidationModule validateAnnotatedOnly(final Class annotation) { 123 | this.validationAnnotation = annotation; 124 | return this; 125 | } 126 | 127 | /** 128 | * Specifies class matcher. Useful to exclude some types from aop matching. Could be also used 129 | * to reduce scope in explicit mode {@link #validateAnnotatedOnly()}. 130 | *

131 | * By default, all types matched. 132 | * 133 | * @param classMatcher class matcher 134 | * @return module instance for chained calls 135 | */ 136 | public ValidationModule targetClasses(final Matcher> classMatcher) { 137 | this.classMatcher = classMatcher; 138 | return this; 139 | } 140 | 141 | /** 142 | * Specifies method matcher. Useful to exclude some methods from aop matching. Could be also used 143 | * to reduce scope in explicit mode {@link #validateAnnotatedOnly()}. 144 | *

145 | * By default, all methods, except synthetic and bridge matched. 146 | * 147 | * @param methodMatcher method matcher 148 | * @return module instance for chained calls 149 | * @see DeclaredMethodMatcher for default matcher 150 | */ 151 | public ValidationModule targetMethods(final Matcher methodMatcher) { 152 | this.methodMatcher = methodMatcher; 153 | return this; 154 | } 155 | 156 | @Override 157 | protected void configure() { 158 | final GuiceConstraintValidatorFactory constraintValidatorFactory = new GuiceConstraintValidatorFactory(); 159 | requestInjection(constraintValidatorFactory); 160 | 161 | /* Overriding just constraints factory to allow them use guice injections */ 162 | final Validator validator = factory.usingContext() 163 | .constraintValidatorFactory(constraintValidatorFactory) 164 | .getValidator(); 165 | 166 | bind(Validator.class).toInstance(validator); 167 | bind(ExecutableValidator.class).toInstance(validator.forExecutables()); 168 | // NOTE bound factory is not aware of guice! because it use default ConstraintValidatorFactory 169 | bind(ValidatorFactory.class).toInstance(factory); 170 | bind(ValidationContext.class); 171 | 172 | bindConstant().annotatedWith(Names.named("guice.validator.addDefaultGroup")).to(addDefaultGroup); 173 | final ValidationGroupInterceptor groupInterceptor = new ValidationGroupInterceptor(); 174 | requestInjection(groupInterceptor); 175 | configureGroupsAop(groupInterceptor); 176 | 177 | final ValidationMethodInterceptor interceptor = new ValidationMethodInterceptor(); 178 | requestInjection(interceptor); 179 | configureAop(interceptor); 180 | } 181 | 182 | /** 183 | * Called to apply validation groups aop (recognized by 184 | * {@link ru.vyarus.guice.validator.group.annotation.ValidationGroups} annotation). 185 | * 186 | * @param interceptor validation groups method interceptor 187 | */ 188 | protected void configureGroupsAop(final ValidationGroupInterceptor interceptor) { 189 | bindInterceptor(Matchers.any(), new ValidationGroupMatcher(), interceptor); 190 | } 191 | 192 | /** 193 | * Called to apply aop bindings. 194 | * 195 | * @param interceptor validation method interceptor 196 | */ 197 | protected void configureAop(final ValidationMethodInterceptor interceptor) { 198 | if (validationAnnotation != null) { 199 | // EXPLICIT MODE 200 | 201 | // all annotated methods 202 | bindInterceptor(classMatcher, getMethodMatcher(validationAnnotation), interceptor); 203 | // all methods in annotated beans (do not search for validation annotations!) 204 | bindInterceptor(getClassMatcher(validationAnnotation), methodMatcher, interceptor); 205 | } else { 206 | // IMPLICIT MODE 207 | 208 | // methods searched by validation annotations appearance 209 | bindInterceptor(classMatcher, getValidatedMethodMatcher(), interceptor); 210 | } 211 | } 212 | 213 | @SuppressWarnings({"unchecked", "PMD.CompareObjectsWithEquals"}) 214 | protected Matcher> getClassMatcher(final Class annotation) { 215 | final Matcher res = Matchers.annotatedWith(annotation); 216 | return classMatcher == Matchers.any() 217 | // combine custom filter with annotation 218 | ? res : res.and((Matcher) classMatcher); 219 | } 220 | 221 | @SuppressWarnings("unchecked") 222 | protected Matcher getMethodMatcher(final Class annotation) { 223 | final Matcher res = Matchers.annotatedWith(annotation); 224 | return methodMatcher == DECLARED_METHOD_MATCHER 225 | // combine custom filter with annotation 226 | ? res : res.and((Matcher) methodMatcher); 227 | } 228 | 229 | @SuppressWarnings("PMD.CompareObjectsWithEquals") 230 | protected Matcher getValidatedMethodMatcher() { 231 | final Matcher res = new ValidatedMethodMatcher(); 232 | // combine custom filter with annotation 233 | return methodMatcher == Matchers.any() ? res : res.and(methodMatcher); 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Guice validator 2 | [![License](http://img.shields.io/badge/license-MIT-blue.svg)](http://www.opensource.org/licenses/MIT) 3 | [![CI](https://github.com/xvik/guice-validator/actions/workflows/CI.yml/badge.svg)](https://github.com/xvik/guice-validator/actions/workflows/CI.yml) 4 | [![Appveyor build status](https://ci.appveyor.com/api/projects/status/github/xvik/guice-validator?svg=true&branch=master)](https://ci.appveyor.com/project/xvik/guice-validator) 5 | [![codecov](https://codecov.io/gh/xvik/guice-validator/branch/master/graph/badge.svg)](https://codecov.io/gh/xvik/guice-validator) 6 | 7 | Support: [gitter chat](https://gitter.im/xvik/guice-validator) 8 | 9 | ### About 10 | 11 | Validates service method parameters and return value using jakarta.validation 3.0 (the difference with 2.0 is only in api package) annotations. 12 | Used with [hibernate-validator](http://hibernate.org/validator/) (currently, the only [certified implementation](https://beanvalidation.org/2.0/)). 13 | 14 | Features: 15 | 16 | * Service method call parameters and return value validation 17 | * Explicit and implicit validation modes (driven by additional annotation or directly by validation annotations) 18 | * Guice injections work in custom validators 19 | * Validation groups support (as context, like transactional calls) 20 | 21 | For guice 5 (and 4) and java 8 (binary compatible with java 11) 22 | 23 | [Old version 1.2.0 docs](https://github.com/xvik/guice-validator/tree/1.2.0) 24 | 25 | ### Important! 26 | 27 | Since Java EE 9 `javax.validation` was renamed to `jakarta.validation` and *Bean Validation* become 3.0. 28 | [Hibernate-validator 7.0 targets new package](https://in.relation.to/2021/01/06/hibernate-validator-700-62-final-released/). 29 | 30 | Current guice-validator (3.x) targets hibernate 7 (and `jakarta.validation`), but if you still 31 | use hibernate-validator 6 (and `javax.validation`) then use previous version: 32 | 33 | Version | Target 34 | ----|---- 35 | guice validator 3.x | Hibernate-validator 7, for `jakarta.validation` 36 | [guice-validator 2.x](https://github.com/xvik/guice-validator/tree/2.0.1) | Hibernate-validator 6.x, for `javax.validation` 37 | 38 | ### Migration 39 | 40 | If you migrating from hibernate-validator 6.x then change dependencies: 41 | 42 | Before | After 43 | ---- | ------ 44 | ru.vyarus:guice-validator:2.0.1 | ru.vyarus:guice-validator:3.0.1 45 | javax.validation:validation-api:2.0.1.Final | jakarta.validation:jakarta.validation-api:3.0.0 46 | org.hibernate:hibernate-validator:6.2.0.Final| org.hibernate:hibernate-validator:7.0.1.Final 47 | org.glassfish:javax.el:3.0.1-b12 | org.glassfish:jakarta.el:4.0.2 48 | 49 | And rename `javax.validation` package to `jakarta.validation` everywhere. Everything else is the same. 50 | 51 | ### Setup 52 | 53 | [![Maven Central](https://img.shields.io/maven-central/v/ru.vyarus/guice-validator.svg?style=flat)](https://maven-badges.herokuapp.com/maven-central/ru.vyarus/guice-validator) 54 | 55 | Maven: 56 | 57 | ```xml 58 | 59 | ru.vyarus 60 | guice-validator 61 | 3.0.2 62 | 63 | 64 | org.hibernate 65 | hibernate-validator 66 | 7.0.1.Final 67 | 68 | 69 | org.glassfish 70 | jakarta.el 71 | 4.0.2 72 | 73 | ``` 74 | 75 | Gradle: 76 | 77 | ```groovy 78 | implementation 'ru.vyarus:guice-validator:3.0.2' 79 | implementation 'org.hibernate:hibernate-validator:7.0.1.Final' 80 | implementation 'org.glassfish:jakarta.el:4.0.2' 81 | ``` 82 | 83 | #### Snapshots 84 | 85 | Snapshots could be used through JitPack: 86 | 87 | * Go to [JitPack project page](https://jitpack.io/#ru.vyarus/guice-validator) 88 | * Select `Commits` section and click `Get it` on commit you want to use (you may need to wait while version builds if no one requested it before) 89 | * Follow displayed instruction: 90 | - Add jitpack repository: `maven { url 'https://jitpack.io' }` 91 | - Use commit hash as version: `ru.vyarus:guice-validator:6933889d41` 92 | 93 | 94 | ### Usage 95 | 96 | Install module: 97 | 98 | ```java 99 | install(new ValidationModule()) 100 | ``` 101 | 102 | #### Implicit 103 | 104 | By default, will work in "implicit mode": matching all methods with `@Valid` or `Constraint` (all validation 105 | annotations are annotated with `@Constraint` and so easy to recognize) annotations. 106 | 107 | For example, 108 | 109 | ```java 110 | public class SomeService { 111 | public SimpleBean beanRequired(@NotNull SimpleBean bean) {} 112 | } 113 | ``` 114 | 115 | Will throw `ConstraintViolationException` exception if called as: 116 | 117 | ```java 118 | service.beanRequired(null) 119 | ``` 120 | 121 | If return value must be validated, method must contain `@Valid` or `Constraint` annotation: 122 | 123 | ```java 124 | @NotNull 125 | public SimpleBean beanRequired(SimpleBean bean) { 126 | return null; 127 | } 128 | ``` 129 | 130 | Will throw `ConstraintViolationException` exception when called (due to returned null). 131 | 132 | #### Explicit 133 | 134 | Explicit mode may be used if you need to manually control validated methods: 135 | 136 | ```java 137 | install(new ValidationModule().validateAnnotatedOnly()) 138 | ``` 139 | 140 | This way only methods directly annotated with `@ValidateOnExecution` or methods inside annotated class 141 | will trigger validation. 142 | 143 | For example: 144 | 145 | ```java 146 | @ValidateOnExecution 147 | public class SampleService { 148 | public void method1() {} 149 | public void method2() {} 150 | } 151 | ``` 152 | 153 | Both methods will trigger validation. 154 | 155 | Note that in contrast to implicit mode, existence of constraint annotations is not checked 156 | (simply nothing will happen on validation it not annotated, but validation will be called). 157 | 158 | And for method: 159 | 160 | ```java 161 | public class SampleService { 162 | 163 | @ValidateOnExecution 164 | public void method1() {} 165 | 166 | public void method2() {} 167 | } 168 | ``` 169 | 170 | Now only `method1` will trigger validation. 171 | 172 | NOTE: javadoc of `@ValidateOnExecution` contradicts with such usage, but its name is ideal for such usage 173 | (no need to introduce more annotations). 174 | 175 | In case if you don't like default annotation, you can use your own: 176 | 177 | ```java 178 | install(new ValidationModule().validateAnnotatedOnly(ToValidate.class)) 179 | ``` 180 | 181 | NOTE Hibernate-validator provides annotation processor to perform additional checks in compile time: [see docs](https://docs.jboss.org/hibernate/validator/6.0/reference/en-US/html_single/#validator-annotation-processor) 182 | In this case explicit mode could be used to differentiate compile time-only annotations from 183 | runtime checks (only methods annotated with `@ValidateOnExecution` will be validated at runtime). 184 | 185 | #### Validation factory 186 | 187 | If you use custom validation factory then specify it directly: 188 | 189 | ```java 190 | install(new ValidationModule(yourValidationFactory)); 191 | ``` 192 | 193 | NOTE: even with custom validation factory, custom `ConstraintValidatorFactory` will be used 194 | in order to be able to wire injections inside custom validators. 195 | 196 | This also means that validator obtained directly from your validator factory and 197 | validator actually used in guice will be different: directly obtained validator will not be able 198 | to inject guice dependencies. 199 | 200 | #### Reducing scope 201 | 202 | You can specify additional class and method matchers to exclude classes or methods from 203 | validation triggering. This works in both implicit and explicit modes. 204 | 205 | For example, introduce custom annotation to manually disable validations: 206 | 207 | ```java 208 | @Target({ElementType.TYPE, ElementType.METHOD}) 209 | @Retention(RetentionPolicy.RUNTIME) 210 | public @interface SuppressValidation {} 211 | ``` 212 | 213 | ```java 214 | install(new ValidationModule() 215 | .targetClasses(Matchers.not(Matchers.annotatedWith(SuppressValidation.class))) 216 | .targetMethods(Matchers.not(Matchers.annotatedWith(SuppressValidation.class)))); 217 | ``` 218 | 219 | Now any annotated nethod (or all methods in annotated class) will not trigger validation: 220 | 221 | ```java 222 | public class SampleService { 223 | @SuppressValidation 224 | public void method(@NotNull String arg) {} 225 | } 226 | ``` 227 | 228 | ### Bound objects 229 | 230 | Both modules bind extra objects to context (available for injection) : 231 | 232 | * `jakarta.validation.Validator` 233 | * `jakarta.validation.executable.ExecutableValidator` 234 | * `jakarta.validation.ValidatorFactory` 235 | * `ru.vyarus.guice.validator.group.ValidationContext` 236 | 237 | For example, `@Inject Validator validator` may be useful for manual object validations. 238 | 239 | NOTE: don't use `ValidatorFactory` directly, because it is not aware of guice and so 240 | will not be able to wire guice injections into custom validators. 241 | 242 | 243 | ### Examples 244 | 245 | ##### Object state 246 | 247 | If parameter or returned object contains validation annotations, and it must be checked before/after method execution, 248 | add `@Valid` annotation. 249 | 250 | ```java 251 | public SimpleBean beanRequired(@NotNull @Valid SimpleBean bean) 252 | ``` 253 | 254 | `@Valid` used on method means validation of returned object: 255 | 256 | ```java 257 | @Valid @NotNull 258 | public SimpleBean validReturn(SimpleBean bean) 259 | ``` 260 | 261 | [Full example](https://github.com/xvik/guice-validator/tree/master/src/test/java/ru/vyarus/guice/validator/simple) 262 | 263 | ##### Annotations composition 264 | 265 | If you often declare multiple annotations, then it could be simplier to introduce new 266 | composite validation. 267 | 268 | For example, here is composition of `@NotNull` and `@Size(min = 2, max = 14)`: 269 | 270 | ```java 271 | @NotNull 272 | @Size(min = 2, max = 14) 273 | @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.PARAMETER}) 274 | @Retention(RetentionPolicy.RUNTIME) 275 | @Constraint(validatedBy = {}) 276 | @Documented 277 | @ReportAsSingleViolation //optional 278 | public @interface ComposedCheck { 279 | String message() default "Composed check failed"; 280 | 281 | Class[] groups() default {}; 282 | 283 | Class[] payload() default {}; 284 | } 285 | ``` 286 | 287 | ```java 288 | public String checkParam(@ComposedCheck String string) {} 289 | ``` 290 | 291 | * [Full example](https://github.com/xvik/guice-validator/tree/master/src/test/java/ru/vyarus/guice/validator/compositeannotation) 292 | 293 | ##### Cross parameters check 294 | 295 | If it is important to validate method parameters "together", then custom validator have to be declared: 296 | 297 | ```java 298 | @SupportedValidationTarget(ValidationTarget.PARAMETERS) 299 | public class CrossParamsValidator implements ConstraintValidator { 300 | 301 | @Override 302 | public void initialize(CrossParamsCheck constraintAnnotation) {} 303 | 304 | @Override 305 | public boolean isValid(Object[] value, ConstraintValidatorContext context) { 306 | Integer param1 = (Integer) value[0]; 307 | Object param2 = value[1]; 308 | return param1 != null && param1 == 1 && param2 instanceof Integer; 309 | } 310 | } 311 | ``` 312 | 313 | Validation annotation: 314 | 315 | ```java 316 | @Constraint(validatedBy = CrossParamsValidator.class) 317 | @Target({ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.ANNOTATION_TYPE }) 318 | @Retention(RetentionPolicy.RUNTIME) 319 | @Documented 320 | public @interface CrossParamsCheck { 321 | String message() default "Parameters are not valid"; 322 | 323 | Class[] groups() default { }; 324 | 325 | Class[] payload() default { }; 326 | } 327 | ``` 328 | 329 | And now, it may be used to validated method parameters: 330 | 331 | ```java 332 | @CrossParamsCheck 333 | public void action(Integer param1, Object param2) {} 334 | ``` 335 | 336 | [Hibernate docs](https://docs.jboss.org/hibernate/validator/6.0/reference/en-US/html_single/#example-using-cross-parameter-constraint) 337 | 338 | [Full example](https://github.com/xvik/guice-validator/tree/master/src/test/java/ru/vyarus/guice/validator/crossparams) 339 | 340 | ##### Scripted check 341 | 342 | Bean level validation: 343 | 344 | ```java 345 | @ScriptAssert(lang = "javascript", script = "it.start.before(it.finish)", alias = "it") 346 | public class ScriptedBean { 347 | 348 | private Date start; 349 | private Date finish; 350 | ... 351 | } 352 | ``` 353 | 354 | Validation could be triggered by `@Valid`: 355 | 356 | ```java 357 | public void method(@Valid ScriptedBean bean) {} 358 | ``` 359 | 360 | Parameter level check: 361 | 362 | ```java 363 | @ParameterScriptAssert(lang = "javascript", script = "arg0.size() == arg1") 364 | public void paramsValid(List list, int count) {} 365 | ``` 366 | 367 | [Hibernate docs](https://docs.jboss.org/hibernate/validator/6.0/reference/en-US/html_single/#section-builtin-method-constraints) 368 | 369 | [Full example](https://github.com/xvik/guice-validator/tree/master/src/test/java/ru/vyarus/guice/validator/script) 370 | 371 | ### Custom validator 372 | 373 | Guice injections could be used when writing custom validators 374 | 375 | ```java 376 | public class ComplexBeanValidator implements ConstraintValidator { 377 | 378 | @Inject 379 | private CustomService customService; 380 | 381 | @Override 382 | public void initialize(ComplexBeanValid constraintAnnotation) { 383 | /* if annotation contains addition parameter it must be parsed here.. skipping for simplicity. 384 | NOTE: in such simple case we can make validator singleton, because of no internal state */ 385 | } 386 | 387 | @Override 388 | public boolean isValid(ComplexBean value, ConstraintValidatorContext context) { 389 | /* common convention is to treat null values as valid and explicitly check them with @NotNull */ 390 | return value == null || customService.getRequiredValue().equals(value.getUser()); 391 | } 392 | } 393 | 394 | @Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE}) 395 | @Retention(RetentionPolicy.RUNTIME) 396 | @Constraint(validatedBy = {ComplexBeanValidator.class}) 397 | @Documented 398 | public @interface ComplexBeanValid { 399 | /* ideally there should be just localization key, but for simplicity just message */ 400 | String message() default "Bean is not valid"; 401 | 402 | Class[] groups() default {}; 403 | 404 | Class[] payload() default {}; 405 | } 406 | ``` 407 | 408 | [Full example](https://github.com/xvik/guice-validator/tree/master/src/test/java/ru/vyarus/guice/validator/customtype) 409 | 410 | ### Limitations 411 | 412 | Guice aop is applied only for objects constructed by guice, so validation will not work for types 413 | bound by instance: 414 | 415 | ```java 416 | bind(MyType.class).toInstance(new MyType()); 417 | ``` 418 | 419 | ### Validation context 420 | 421 | [Validation groups](https://docs.jboss.org/hibernate/validator/6.0/reference/en-US/html_single/#chapter-groups) 422 | could be used to apply different validations for the same object (or same method). 423 | 424 | For example, we have model class with 2 validation groups 425 | ```java 426 | public class MyModel { 427 | @NotNull 428 | private String defField; 429 | @NotNull(group = Group1.class) 430 | private String group1Field; 431 | @NotNull(group = Group2.class) 432 | private String group2Field; 433 | } 434 | ``` 435 | 436 | Note that Group1 and Group2 could be any classes or interfaces (it doesn't matter, because they simply define group by type). 437 | 438 | If we use model in method like this: 439 | 440 | ```java 441 | public class MyService { 442 | public void do(@Valid MyModel model) {...} 443 | } 444 | ``` 445 | 446 | Only `defField` will be validated (because it implicitly belongs to default (`Default`) group). 447 | 448 | #### Groups annotation 449 | 450 | In order to enable other groups use `@ValidationGroups` annotation. 451 | 452 | For example, 453 | 454 | ```java 455 | @ValidationGroups(Group1.class) 456 | public void do(@Valid MyModel mode); {...} 457 | ``` 458 | 459 | This enables `Group1` so `defField` and `group1Field` will be validated (default group included by default, but can be disabled (read below)). 460 | 461 | Annotation could define more then one group: 462 | 463 | ```java 464 | @ValidationGroups({Group1.class, Group2.class}) 465 | ``` 466 | 467 | Annotation may be used on class to affect all methods. Also, see advanced annotations usage below. 468 | 469 | #### Understanding context 470 | 471 | `@ValidationGroups` annotation affects not just one method, but all methods executed by this method or any subsequent method (in the same thread!). 472 | We can say that annotation creates *validation context*. 473 | 474 | Suppose we have service without context: 475 | 476 | ```java 477 | public class MyService { 478 | public void do(@Valid MyModel mode); {...} 479 | } 480 | ``` 481 | 482 | Defining upper level service with context: 483 | 484 | ```java 485 | @ValidationGroups(Group1.class) 486 | public class MyGroup1Service { 487 | @Inject 488 | private MyService service; 489 | 490 | public void foo(MyModel model) { 491 | service.do(model); 492 | } 493 | } 494 | ``` 495 | 496 | Validation context is defined for all methods in service (and all subsequent calls). 497 | So when `foo` method call `service.do` method, validation context would be already defined and actual validation 498 | would be performed with default and Group1 groups. 499 | 500 | The same way, some other upper level service could define different groups. So upper levels define general validation context, 501 | while lower levels stay generic and re-usable. 502 | 503 | Overall, validation context works very much like `@Transactional` in guice-persist or spring. 504 | 505 | ##### Context composition 506 | 507 | In situation like this: 508 | 509 | ```java 510 | public class RootService { 511 | @Inject 512 | private MyGroup1Service service; 513 | 514 | @ValidationGroups(Group2.class) 515 | public void bar(MyModel model) { 516 | service.foo(model); 517 | } 518 | } 519 | ``` 520 | 521 | * Method `RootService.bar` is under {`Group2`} context 522 | * Subsequent method `MyGroup1Service.foo` is under {`Group1`} context 523 | * (Sub)Subsequent `method MyService.do` performs validation 524 | 525 | Both contexts will compose (merge) and last method will be called with default, Group1 and Group2 groups. 526 | 527 | ##### Composition rules 528 | 529 | If `@ValidationGroups` annotation defined both on type and method, then actual method context will use groups from both annotations. 530 | 531 | Subsequent validation contexts inherit all groups from upper context. 532 | 533 | ##### Manual context definition 534 | 535 | Validation context could be defined manually, by using `ru.vyarus.guice.validator.group.ValidationContext` singleton: 536 | 537 | ```java 538 | public class ManualContextDemo { 539 | @Inject 540 | private ValidationContext context; 541 | 542 | public void foo() { 543 | context.doWithGroups(new GroupAction(){ 544 | public Void call() throws Throwable { 545 | // all methods called here will be validated with specified groups 546 | // this is equivalent to method annotation @ValidationGroups({Group1.class, Group10.class}) 547 | } 548 | }, Group1.class, Group10.class) 549 | } 550 | } 551 | ``` 552 | 553 | #### Default group specifics 554 | 555 | Default behaviour is to always use default group. So when you define validation context with groups {Group1, Group2}, 556 | actual context would be {Group1, Group2, Default}. This was done in order to provide more intuitive behavior: 557 | validation context extends default validation scope. 558 | 559 | If you want to prevent this behavior use `strictGroupsDeclaration` module option: 560 | 561 | ```java 562 | new ValidationModule().strictGroupsDeclaration() 563 | ``` 564 | 565 | Explicit module has the same option. If you disable default group addition, then default validations (annotations 566 | where you didn't specify group) will not be used, unless you specify Default group manually in validation context. 567 | 568 | #### Advanced annotations usage 569 | 570 | In some cases it makes sense to use your own annotations for context definition, e.g.: 571 | * Because they are more descriptive in code 572 | * You want to group multiple groups (the same way as you can group multiple validations in jakarta validation). 573 | 574 | Due to the fact that any class could be used for group name, we can use our new annotation class itself as group name. 575 | 576 | For example (example taken from [hibernate-validator docs](https://docs.jboss.org/hibernate/validator/7.0/reference/en-US/html_single/#chapter-groups)): 577 | ```java 578 | public class Person { 579 | @NotNull 580 | private String name; 581 | @AssertTrue(group = DriverContext.class) 582 | private boolean driverLicense; 583 | } 584 | ``` 585 | 586 | We used annotation class as group name. 587 | 588 | ```java 589 | @Target({TYPE, METHOD}) 590 | @Retention(RetentionPolicy.RUNTIME) 591 | @ValidationGroups(DriverScope.class) 592 | public @interface DriverScope { 593 | } 594 | ``` 595 | 596 | Note that annotation is annotated by `@ValidationGroups(DriverScope.class)`. 597 | 598 | Groups interceptor implementation is able to find such annotated annotations and use `@ValidationGroups` defined on them. 599 | 600 | So in service you could simply use your annotation: 601 | 602 | ```java 603 | @DriverContext 604 | public class DriverService { 605 | ... 606 | } 607 | ``` 608 | 609 | All method called by driver service will be validated with `DriverContext` group. 610 | 611 | If you don't like the idea of using annotations as validation groups, then you can still use your own annotations just 612 | for grouping. For example: 613 | 614 | ```java 615 | @Target({TYPE, METHOD}) 616 | @Retention(RetentionPolicy.RUNTIME) 617 | @ValidationGroups({Group1.class, Group10.class}) 618 | public @interface MyCustomScope { 619 | } 620 | ``` 621 | 622 | #### Cache 623 | 624 | In order to avoid `ValidationGroups` annotations lookup for each method call, resolution result is cached on first execution inside 625 | `ru.vyarus.guice.validator.group.annotation.MethodGroupsFactory`. 626 | 627 | If you use JRebel or other class reloading tool (maybe some other reason) you will need to disable descriptors caching. 628 | 629 | To do it set system property or environment variable: 630 | 631 | ``` 632 | ru.vyarus.guice.validator.group.annotation.MethodGroupsFactory.cache=false 633 | ``` 634 | 635 | Or from code: 636 | 637 | ```java 638 | MethodGroupsFactory.disableCache(); 639 | ``` 640 | 641 | Also, you can clear cache manually (on instance): 642 | 643 | ```java 644 | injector.getInstance(MethodGroupsFactory.class).clearCache() 645 | ``` 646 | 647 | ### More 648 | 649 | More examples could be found in tests. 650 | 651 | Also, read hibernate-validator docs: 652 | 653 | * [Constraints](https://docs.jboss.org/hibernate/validator/6.0/reference/en-US/html_single/#section-declaring-bean-constraints) 654 | * [Declaration](https://docs.jboss.org/hibernate/validator/6.0/reference/en-US/html_single/#chapter-method-constraints) 655 | * [Validation factory configuration](https://docs.jboss.org/hibernate/validator/6.0/reference/en-US/html_single/#chapter-bootstrapping) 656 | 657 | ### Supplement 658 | 659 | [validator-collection](https://github.com/jirutka/validator-collection) annotations to validate collections of simple types 660 | 661 | --- 662 | [![java lib generator](http://img.shields.io/badge/Powered%20by-%20Java%20lib%20generator-green.svg?style=flat-square)](https://github.com/xvik/generator-lib-java) 663 | --------------------------------------------------------------------------------