I'm trying to define constraint definition with Hibernate Validation 6.0.1 where the validator is in a different location (.jar/project) relative to the constraint annotation. Aka, I have my objects that I want to validate that are in the project "api" with the annotation definition, but I'll have the validators in the project "modules/common"
I was following what was describes in the documentation.
Configuration file
#Bean
public Validator validator() {
HibernateValidatorConfiguration configuration = Validation
.byProvider( HibernateValidator.class )
.configure();
ConstraintMapping constraintMapping = configuration.createConstraintMapping();
constraintMapping
.constraintDefinition(ValidationComplexePerson.class)
.validatedBy(ValidationComplexePersonValidator.class);
return configuration.addMapping( constraintMapping )
.buildValidatorFactory()
.getValidator();
Constraint Annotation
#Documented
#Constraint(validatedBy = { })
#Target({TYPE, ANNOTATION_TYPE})
#Retention(RUNTIME)
public #interface ValidationComplexePerson {
...}
Validator
public class ValidationComplexePersonValidator
implements ConstraintValidator<ValidationComplexePerson, Personne> {
#Override
public void initialize(ValidationComplexePerson constraintAnnotation) {
}
#Override public boolean isValid(
Personne personne,
ConstraintValidatorContext constraintValidatorContext) {
if (personne.nom.matches(".*\\d+.*")) {
return false;
}
return true;
}
My problem
The problem I have is that if I don't put the "#Constraint(validatedby={})" in the Annotation, I get the error
HV000116: The annotation type must be annotated with #javax.validation.Constraint when creating a constraint definition.
when reaching the ".constraintDefinition" in the Bean config.
On the other hand, if I put the "#Constraint(validatedby={})", I get
Error:(17, 1) java: For non-composed constraints a validator
implementation must be specified using #Constraint#validatedBy().
Any suggestion on what could be worng or alternatives to this solution?
I also tried the procedure presented here.
I suspect you are using the annotation processor as your second error comes from it?
The issue is that the annotation processor check is not correct in this case. I think we should probably remove it as there's no way to make this check work with the programmatic API.
Just remove the annotation processor for now and it should work OK.
I opened https://hibernate.atlassian.net/browse/HV-1470 to track this issue.
Related
I am new to Java annotation. I have used the following annotation in my Spring boot application as follows:
Original annotation definition:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface MyAnnotation {
EntityType entityType();
ActionType actionType();
Resource resourceType();
}
Now I would like to move actionType() and resourceType() to a different annotation say MySubAnnotation and use it in the original above annotation MyAnnotation as follows:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface MyAnnotation {
EntityType entityType();
MySubAnnotation mySubAnnotation();
}
But I am facing an issue with using this as follows:
#MyAnnotation(entityType = EntityType.MY_ENTITY,
mySubAnnotation = <???>) <---HERE I CANNOT UNDERSTAND WHAT TO SPECIFY
#MySubAnnotation(actionType=ActionType.UPDATE,
resourceType=Resource.MY_RESOURCE)
public void myMethod() {
...
}
As mentioned above, I cannot understand what to specify for sub annotation. Could anyone please help here? Thanks.
You didn’t include the declaration of your MySubAnnotation. Besides that, the syntax for the actual annotation values is not different for nested annotations. You just have to place it after the =:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface MyAnnotation {
EntityType entityType();
MySubAnnotation mySubAnnotation();
}
#Retention(RetentionPolicy.RUNTIME)
#Target({})
public #interface MySubAnnotation {
ActionType actionType();
Resource resourceType();
}
#MyAnnotation(
entityType = EntityType.MY_ENTITY,
mySubAnnotation = #MySubAnnotation(
actionType = ActionType.UPDATE,
resourceType = Resource.MY_RESOURCE
)
)
public void myMethod() {
}
Note that in this example, MySubAnnotation has an empty list of targets, i.e. #Target({}) which permits it only as a value within other annotations. Of course, you could add other permitted targets. That would not affect its use as “sub annotation”, as that’s always allowed.
But there’s not much advantage in designing annotation parts as sub annotation here. All you’ve achieved, is requiring more typing. One imaginable possibility, is to provided a default here, e.g.
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface MyAnnotation {
EntityType entityType();
MySubAnnotation mySubAnnotation() default
#MySubAnnotation(actionType=ActionType.UPDATE, resourceType=Resource.MY_RESOURCE);
}
The difference to just specifying defaults for actionType and resourceType is that now, the developer may use the default for MySubAnnotation, i.e. both values, or has to specify explicit values for both, they can not override only one of them.
I'm trying to create a custom annotation for enabling functionality based on feature flags. I'm basing my approach on the ConditionalOnProperty annotation, but to enable code reuse I want to define a common prefix that can be used wherever I need this functionality.
I have defined my annotation as below.
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.TYPE, ElementType.METHOD })
#Documented
#ConditionalOnProperty(prefix = "foo.bar")
public #interface ConditionalOnFeature {
#AliasFor(annotation = ConditionalOnProperty.class, attribute = "value")
String[] value();
}
I'm using the AliasFor annotation to set the value attribute in the ConditionalOnProperty annotation, and hardcoding my prefix as foo.bar (all my features will sit under this property). As far as I understand Spring annotation overriding, this should work.
However when I use this on a class (for example a controller), the code fails to compile, stating that The name or value attribute of #ConditionalOnProperty must be specified.
#RestController
#ConditionalOnFeature("enable-fancy-controller")
public class FancyController {
#RequestMapping(value = "/fancy-method", method = RequestMethod.GET)
public String fancyMethod() {
return "Fancy";
}
}
However it works perfectly fine when I instead use my annotation on a method. Compiles successfully and behaves as intended.
#RestController
public class FancierController {
#ConditionalOnFeature("enable-fancier-method")
#RequestMapping(value = "/fancier-method", method = RequestMethod.GET)
public String fancierMethod() {
return "Fancier";
}
}
Have I configured my annotation wrong? Or is this some bug with Spring?
I know it's a possible duplicate and I found several threads like How can I find all beans with the custom annotation #Foo? or Custom annotation is not working on spring Beans but that's not really what I do or want to do.
I want an easy validator for the length of attributes of a class. Dont tell me about #Length or #Size. They're not helpful here. I tried several solutions and none of them did work.
CustomAnnotation:
#Target({ ElementType.METHOD, ElementType.FIELD, ElementType.TYPE })
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = CheckLengthValidator.class)
#Qualifier // got this one from a solution
public #interface CheckLength {
Class<?> className();
Class<?>[] groups() default {};
String message() default "List is not valid";
Class<? extends Payload>[] payload() default {};
}
CustomAnnotationValidator (methods not implemented yet):
public class CheckLengthValidator implements ConstraintValidator<CheckLength, List<Transaction>> {
#Override
public void initialize(CheckLength a) {
//get values which are defined in the annotation
System.out.println("init");
test = a.message();
}
#Override
public boolean isValid(List<Transaction> value, ConstraintValidatorContext context) {
for (Transaction x : value) {
if (x.getTimestamp().length() > 30) {
System.out.println("not valid");
return false;
}
}
return true;
}
}
So where do I use it? At my API where all autowired repos are.
#CrossOrigin(origins = "http://localhost:4200")
#RestController
public class FileManagementRestAPI {
#Autowired
#CheckLength(className = Transaction.class)
List<Transaction> transaction = new ArrayList<>();
...
}
Later this will be called to fill the list.
transaction.addAll((Collection<? extends Transaction>) csvToBean.parse());
What I tried:
I read about a solution which I later found out it is deprecated or not working anymore with CommandLineRunner and AnnotationConfigApplicationContext.
Then I've read that I have to declare it as Bean but a List here isn't a Bean or do I still need to do something with Beans? Saw something like this but didn't know what to do with it then:
public class InitBeans implements BeanPostProcessor { ... }
The error I get now:
Field transaction in com.stuff.project.apis.FileManagementRestAPI required a bean of type 'java.util.List' that could not be found.
The injection point has the following annotations:
- #org.springframework.beans.factory.annotation.Autowired(required=true)
- #com.stuff.project.utils.CheckLength(message=List is not valid, groups=[], payload=[], className=class com.stuff.project.entity.Transaction)
Action:
Consider defining a bean of type 'java.util.List' in your configuration.
There were several other errors when I was trying to get it running.
I have a custom annotation #UniqueModel, which is validated by a ConstraintValidator:
#Component
public class UniquePlaceValidator implements ConstraintValidator<UniqueModel, Model> {
#Autowired
private ModelRepository repository;
public UniqueModelValidator() {
}
public void initialize(UniqueModel constraint) {
}
#Override
public boolean isValid(Model model, ConstraintValidatorContext context) {
if (repository == null)
return true;
Model dbModel = repository.findByNameAndMail(model.getName(), model.getMail());
return dbModel == null;
}
The problem is, that I need to do the validation before the safe()-method of the repository is called, otherwise the field injection won't work.
I therefor created a delegate-method with a #Valid-annotation, in order to force the unique-validation before:
Model save(#Valid Model model {
return repository.save(model);
}
Unfortunately this doesn't work, it seems like the #Valid-annotation is ignored by Spring.
How can I assure the correct timing of validation?
Depending on your Bean validation configuration you may need to annotate your repository bean with #ValidateOnExecution.
But I'm not sure if Spring does support this annoation (see SPR-10641) hence I'm using Spring's own #Validated annotation in my repository and service interfaces and method level validation works fine!
See also this question and have a look into MethodValidationPostProcessor which clearly states "Target classes with such annotated methods need to be annotated with Spring's #Validated annotation at the type level". So it seems to be pretty clear that you have to use #Validated instead of #ValidateOnExecution until SPR-10641 is fixed.
Hej,
I want to use the #Validated(group=Foo.class) annotation to validate an argument before executing a method like following:
public void doFoo(Foo #Validated(groups=Foo.class) foo){}
When i put this method in the Controller of my Spring application, the #Validated is executed and throws an error when the Foo object is not valid. However if I put the same thing in a method in the Service layer of my application, the validation is not executed and the method just runs even when the Foo object isn't valid.
Can't you use the #Validated annotation in the service layer ? Or do I have to do configure something extra to make it work ?
Update:
I have added the following two beans to my service.xml:
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor"/>
and replaced the #Validate with #Null like so:
public void doFoo(Foo #Null(groups=Foo.class) foo){}
I know it is a pretty silly annotation to do but I wanted to check that if I call the method now and passing null it would throw an violation exception which it does. So why does it execute the #Null annotation and not the #Validate annotation ? I know one is from javax.validation and the other is from Spring but I do not think that has anything to do with it ?
In the eyes of a Spring MVC stack, there is no such thing as a service layer. The reason it works for #Controller class handler methods is that Spring uses a special HandlerMethodArgumentResolver called ModelAttributeMethodProcessor which performs validation before resolving the argument to use in your handler method.
The service layer, as we call it, is just a plain bean with no additional behavior added to it from the MVC (DispatcherServlet) stack. As such you cannot expect any validation from Spring. You need to roll your own, probably with AOP.
With MethodValidationPostProcessor, take a look at the javadoc
Applicable methods have JSR-303 constraint annotations on their
parameters and/or on their return value (in the latter case specified
at the method level, typically as inline annotation).
Validation groups can be specified through Spring's Validated
annotation at the type level of the containing target class, applying
to all public service methods of that class. By default, JSR-303 will
validate against its default group only.
The #Validated annotation is only used to specify a validation group, it doesn't itself force any validation. You need to use one of the javax.validation annotations like #Null or #Valid. Remember that you can use as many annotations as you would like on a method parameter.
As a side note on Spring Validation for methods:
Since Spring uses interceptors in its approach, the validation itself is only performed when you're talking to a Bean's method:
When talking to an instance of this bean through the Spring or JSR-303 Validator interfaces, you'll be talking to the default Validator of the underlying ValidatorFactory. This is very convenient in that you don't have to perform yet another call on the factory, assuming that you will almost always use the default Validator anyway.
This is important because if you're trying to implement a validation in such a way for method calls within the class, it won't work. E.g.:
#Autowired
WannaValidate service;
//...
service.callMeOutside(new Form);
#Service
public class WannaValidate {
/* Spring Validation will work fine when executed from outside, as above */
#Validated
public void callMeOutside(#Valid Form form) {
AnotherForm anotherForm = new AnotherForm(form);
callMeInside(anotherForm);
}
/* Spring Validation won't work for AnotherForm if executed from inner method */
#Validated
public void callMeInside(#Valid AnotherForm form) {
// stuff
}
}
Hope someone finds this helpful. Tested with Spring 4.3, so things might be different for other versions.
#pgiecek You don't need to create a new Annotation. You can use:
#Validated
public class MyClass {
#Validated({Group1.class})
public myMethod1(#Valid Foo foo) { ... }
#Validated({Group2.class})
public myMethod2(#Valid Foo foo) { ... }
...
}
Be careful with rubensa's approach.
This only works when you declare #Valid as the only annotation. When you combine it with other annotations like #NotNull everything except the #Valid will be ignored.
The following will not work and the #NotNull will be ignored:
#Validated
public class MyClass {
#Validated(Group1.class)
public void myMethod1(#NotNull #Valid Foo foo) { ... }
#Validated(Group2.class)
public void myMethod2(#NotNull #Valid Foo foo) { ... }
}
In combination with other annotations you need to declare the javax.validation.groups.Default Group as well, like this:
#Validated
public class MyClass {
#Validated({ Default.class, Group1.class })
public void myMethod1(#NotNull #Valid Foo foo) { ... }
#Validated({ Default.class, Group2.class })
public void myMethod2(#NotNull #Valid Foo foo) { ... }
}
As stated above to specify validation groups is possible only through #Validated annotation at class level. However, it is not very convenient since sometimes you have a class containing several methods with the same entity as a parameter but each of which requiring different subset of properties to validate. It was also my case and below you can find several steps to take to solve it.
1) Implement custom annotation that enables to specify validation groups at method level in addition to groups specified through #Validated at class level.
#Target({ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface ValidatedGroups {
Class<?>[] value() default {};
}
2) Extend MethodValidationInterceptor and override determineValidationGroups method as follows.
#Override
protected Class<?>[] determineValidationGroups(MethodInvocation invocation) {
final Class<?>[] classLevelGroups = super.determineValidationGroups(invocation);
final ValidatedGroups validatedGroups = AnnotationUtils.findAnnotation(
invocation.getMethod(), ValidatedGroups.class);
final Class<?>[] methodLevelGroups = validatedGroups != null ? validatedGroups.value() : new Class<?>[0];
if (methodLevelGroups.length == 0) {
return classLevelGroups;
}
final int newLength = classLevelGroups.length + methodLevelGroups.length;
final Class<?>[] mergedGroups = Arrays.copyOf(classLevelGroups, newLength);
System.arraycopy(methodLevelGroups, 0, mergedGroups, classLevelGroups.length, methodLevelGroups.length);
return mergedGroups;
}
3) Implement your own MethodValidationPostProcessor (just copy the Spring one) and in the method afterPropertiesSet use validation interceptor implemented in step 2.
#Override
public void afterPropertiesSet() throws Exception {
Pointcut pointcut = new AnnotationMatchingPointcut(Validated.class, true);
Advice advice = (this.validator != null ? new ValidatedGroupsAwareMethodValidationInterceptor(this.validator) :
new ValidatedGroupsAwareMethodValidationInterceptor());
this.advisor = new DefaultPointcutAdvisor(pointcut, advice);
}
4) Register your validation post processor instead of Spring one.
<bean class="my.package.ValidatedGroupsAwareMethodValidationPostProcessor"/>
That's it. Now you can use it as follows.
#Validated(groups = Group1.class)
public class MyClass {
#ValidatedGroups(Group2.class)
public myMethod1(Foo foo) { ... }
public myMethod2(Foo foo) { ... }
...
}