Why does Hibernate validator in Spring Boot not address field validation constraints after processing class constraints? - spring-boot

I am trying to use Spring Boot Validation/ Hibernate Validator in one of our services and for the purpose of that service I created a DTO where I set customized constraints to the DTO root and on its fields.
The DTO looks like this:
#JobIsInUserQueue
#CanPerformTask
class PerformableJob(
#HasCurrentTaskList
val job: Job,
val user: Person
)
Given I have defined those annotations, and the implementation classes following the Jakarta validation framework API,
When I create an instance and let the spring boot provided validator bean process that bean:
val testable = PerformableJob(job, person)
val violations = validator.validate(testable)
Then the expected behaviour is that the three validators #JobIsInUserQueue, #CanPerformQCTask and #HasCurrentTaskList are checked,
But the actual behaviour is that only the class level annotations #JobIsInUserQueue and #CanPerformQCTask are checked.
I tried already to add #Valid to the field, but there are no nested validations within Job, so that would not be the right solution.
All custom constrains are marked with #Retention(AnnotationRetention.RUNTIME).
What do I miss here?

Related

Spring #AliasFor not working for #Profile annotation

Environment: Kotlin 1.5.30, SpringBoot 2.5.4(Spring 5.3.9)
Background & Issue
I'm trying to create a composed annotation to simplify similar template annotation codes. The annotation class is like this:
#Profile("default") //NOTE 1: use a placeholder, see the investigations below
annotation class ProfileAware(
#get: AliasFor(annotation = Profile::class, attribute = "value")
val profiles: Array<String>,
//properties delegated for other annotations
)
expected usage:
#Component
#ProfileAware(profiles = ["dev"])
class TheBean {
init {
LoggerFactory.getLogger("TheBean").info("TheBean: I'm registered")
}
}
in application.yaml:
spring:
profiles:
active: dev
But after the application starts, TheBean is not registered as expected.
Investigation & Try
First I've search in github spring repository, and found this: Regression: custom composed #Profile annotation without runtime retention no longer supported with component scanning. So I tried to add #Retention(AnnotationRetention.RUNTIME) on #ProfileAware, but no effect.
Tried to remove ("default") value from meta annotation (and, whether add the default value to profiles attribute or not), but got java.lang.IllegalArgumentException: Must specify at least one profile.
Tried to remove #Profile("default") from meta annotation, but got AnnotationConfigurationException: #AliasFor declaration on attribute 'profile' in annotation ... which is not meta-present.
(Important) Try to use #Profile("dev") directly on bean instead of ProfileAware , works as expected.
(Important) Try to change the value on meta annotaion as "dev", it works, but obviously it is hardcoded and not match my need.
Is there something I did wrong? Or is it possible to create composed annotation with dynamic #Profile value?
Thanks for your reading and help.
#Profile is looked up by org.springframework.context.annotation.ProfileCondition whose matches(...) method uses org.springframework.core.type.AnnotatedTypeMetadata.getAllAnnotationAttributes(String) to look up the #Profile annotations, and the Javadoc for that method states the following (bold emphasis added by me).
Retrieve all attributes of all annotations of the given type, if any (i.e. if defined on the underlying element, as direct annotation or meta-annotation). Note that this variant does not take attribute overrides into account.
Thus, you currently cannot use #Profile with #AliasFor for annotation attribute overrides.

Best way to provide validation to spring mvc & hibernate projects

I'm new to spring & hibernate, please suggest a best way to provide validation to spring mvc & hibernate projects. I tried hibernate validator but i don't know how to use it when there is relationship(like #OneToOne) between entity objects. If there is any examples, please provide the link.
Your #OneToOne relationship is not clear enough. Could you please give a concrete scenario?
In Spring MVC you have three types of validations:
JSR-303 bean validation (hibernate validator)
Custom validation
Spring validation
JSR-303 works great for simple validations on single fields. You have standard annotations like (#Max, #Min, #NULL, #Pattern, etc.). It does not seem enough for you in this case.
Custom validation is used in a more complex scenario. For instance, what if we need to validate that the newly added product ID is not the same as any of the existing product IDs?
Spring validation represents cross-field validation.
For instance, it's used when we want to compare two or more fields to see if
their values can be considered as valid when combined.
you can use spring validation method.
using valid annotion in controller classes infront of modelattribute annotaion.
then you can use validaions in the model class.
below is an example for controller and bean classes.
this is a many to one configuration inside a bean class:
#ManyToOne(targetEntity = UserType.class)
#JoinColumn(name = "user_type", referencedColumnName = "id")
#NotNull
private UserType userTypeTb;
this is a method inside controller class with valid and modelattribute annotaion:
public ModelAndView home(#Valid #ModelAttribute("user") User user, BindingResult result,
HttpServletRequest request) {
//
//
}

Will the #Validated annotation in the DAO Interface help at all?

I have seen a lot of #Validated annotations in the DAOs interface that I didn't write. For example:
#Validated
public interface CompanyDAO extends BaseDAO<Company> {
public List<Company> list();
What behaviour should I expect from this:
Validating all the objects passed to the DAO before saving them into the database?
Validating all the objects that the DAO retrieves from the database?
Both of them?
Nothing at all. This annotation should not be placed in DAO interfaces.
Just a few pointers which you might not be aware of:
8.8 Spring Validation of 8. Validation, Data Binding, and Type Conversion in the manual specifies that
In order to be eligible for Spring-driven method validation, all target classes need to be annotated with Spring’s #Validated annotation, optionally declaring the validation groups to use. Check out the MethodValidationPostProcessor javadocs for setup details with Hibernate Validator and Bean Validation 1.1 providers.
MethodValidationPostProcessor says
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), e.g.:
public #NotNull Object myValidMethod(#NotNull String arg1, #Max(10) int arg2)
Target classes with such annotated methods need to be annotated with Spring's Validated annotation at the type level, for their methods to be searched for inline constraint annotations. Validation groups can be specified through #Validated as well. By default, JSR-303 will validate against its default group only.
So, maybe the answer to your question
What behaviour should I expect
is: Whatever constraints are given in the code.

Missing Converter when using Spring LdapTemplate with Grails Validateable annotation

I'm using the Spring LDAP (docs) library in a Grails application. I have a class annotated with the #Entry annotation, so it is mapped to an LDAP server. This all works quite beautifully.
However, when I add the Grails #Validateable annotation (to enable validating the LDAP class similarly to Grails domain classes) and attempt to retrieve data from LDAP (i.e. a findAll operation on the LdapUserRepo, or similar), I get the following exception:
Message: Missing converter from class java.lang.String to interface org.springframework.validation.Errors, this is needed for field errors on Entry class com.ldap.portal.LdapUser
Basically, it seems like the AST transformation performed by the #Validateable annotation is producing extra fields (namely the errors field) on the LdapUser object. It appears that Spring LDAP, in processing the #Entry logic, assumes a default mapping for the fields property (probably interpreting it as a string field on the LDAP object). When it gets nothing from the LDAP server, it attempts to set the field of type ValidationErrors to a value of type String -- an empty string.
I did some looking in github and found this code that seems relevant and may support my theory.
My question is: is this behavior expected for annotations, and how can one prevent fields added by one annotation from being inappropriately processed by another annotation?
At present the best workaround I've come up with for my specific issue is to add an errors field to my LdapUser object and mark it as transient (so that LDAP ignores it):
#Transient
ValidationErrors errors

Implementing cross-validation in java

I use Spring Roo + jpa + hibernate and I would like to implement cross-validation (validation of several fields at the same time) in my application.
I am not sure how to go about implementing it. Can anyone please advise me and/or direct me to relevant documentation?
Have a look at Hibernate Validator, which allows entity validation (using annotations).
http://www.hibernate.org/subprojects/validator.html
In short, you annotate your field constraints by placing hibernate validator/ JPA annotations above them. (E.g. #Min(10)) and use the following piece of code to find any invalid fields;
ValidatorFactory factory = Validation.byDefaultProvider().configure().traversableResolver(new CustomTraversableResolver() ).buildValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<BaseValidationObject>> constraintViolations = Validator.validate(myEntityToValidate);
If you need to validate specific relationships between entities, you can write custom validators to fit that need.

Resources