BeanValidation is not evaluating Constraints on Type-level - bean-validation

BeanValidation works on normal Validations which are placed at FIELD-level:
#Email
#Column(unique = true)
private String email;
However, placing a custom constraint on TYPE-Level:
#Entity
#UniqueEmail
public class UserAccount {
#Email
#Column(unique = true)
private String email;
...
}
with the annotation being:
#Constraint(validatedBy = {UniqueEmailValidator.class})
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface UniqueEmail { ... }
will not work. The UniqueEmailValidator is never called unless I programatically trigger it by calling .validate() on an injected javax.validation.Validator
Is this expected behaviour or can that be changed in a way that the BeanValidation will also evaluate custom constraints on type level.

Related

spring mvc rest validation on all method parameters

I would like to find a a way to validate a rest method based on all parameters outside the Controller.
First Question: Is there already a way to do it?
Second Question: If not - how can I hook the validation into spring mvc binding prozess.
A way how it could look like. It would be nice to mark the method with a new #MethodValidation Annotation:
#Validate
#MethodValidation(MyValidator.class)
public Response doSomthing(String param1, Integer param2, Something param3){}
Annotation
#Target({ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface MethodValidation{
Class<? extends MethodValidator<?, ?>>[] value();
}
Implement a Validator
public class MyValidator implements MethodValidator{
public void validate(Object[] params, Errors errors){
String param1 = (String ) params[0];
Integer param2 = (Integer) params[1];
Something param3 = (Something)params[3];
// .... do some validations
if(error)
errors.reject("Some.error.done");
}
}
what kind of parameters exactly? a lot of spring stuff is actually available in ThreadLocals, if you dare to dig into it.
you CAN inject stuff into the binding process:
#ControllerAdvice
public class FooControllerAdvice {
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(
Date.class,
new CustomFooEditor()
);
}
}
and the actual editor:
public class CustomFooEditor extends PropertyEditorSupport {
}
but this doesn't give you that much of an edge over regular validation.
or you can use spring aop triggered by an annotation, then annotate your methods, with the config:
#EnableAspectJAutoProxy(proxyTargetClass=true)
an aspect:
#Aspect
#Component
public class ValidationAspect {
#Pointcut("execution(public * * (..))")
private void anyPublicMethod() {}
#Around("anyPublicMethod() && #annotation(foo)")
public Object all(
ProceedingJoinPoint proceedingJoinPoint,
Foo ann) throws Throwable {
}
[...]
}
an annotation:
#Inherited
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface Foo {
}
public String value();
and then annotating your method:
#RequestMapping...
#Foo(value="foo.bar.ValidatorClassname")
public Response x() {
}
... so you see, there's a lot of ways you can go. i'd really like to know what keeps you from using standard validation?
.rm
thanx for the answer.
I hope I am right: The standard validation outside the controller just allows me to to validate each method parameter separately.
I actually get into problems when the validation depends on 2 or more method parameter. This could be in following situation: Some thing is a part of an Object hierarchy:
public class Parent{
private Integer id;
private List<Something> childs;
...
}
public class Something{
private Integer id;
private String name;
...
}
The Constrain: it is not allowed that a Parent has 2 somethings in the list with the same name. For saving a new some thing I am calling the method.
#RequestMapping(
value = "/chargingstation/{parentId}",
method = RequestMethod.Post)
public Response doSomthing(
#PathVariable("parentId") Integer parentId,
Something param3)
Add the parentId to the Something-ModelOject was not an option.
So is there a way to handle this situation with the standard validation?

how can i write rule with hibernate validator : field1 not null or field2 not null

public class User {
#NotBlank
private String name;
#???
private String email;
#???
private String mobile;
}
how can i write rule with hibernate validator(#???), like this code:
StringUtils.hasText(email) || StringUtils.hasText(mobile)
You need to look at how to implement class level constraints. This feature is described in the Bean Validation specification as well as the Hibernate Validator online docs.

Custom class level bean validation constraint

I already know how to add annotation based validation on specific attributes in Entity class like :-
public class Person {
#NotNull
private String firstName;
private String lastName;
//...
}
But is it possible to add annotation on class Person, in order to validate all the attributes inside this class, by creating a Customised Validation Class and handling validation there somewhere like :-
#Retention(value = RetentionPolicy.RUNTIME)
#Target(value = ElementType.METHOD)
public #interface PersonneName {
public String firstName();
}
I am working on a project to get Constraints from Database and creating Customised Validation Class and applying on the Entity class attributes according to the constaints got from DB.
Please suggest.
Yes, of course, it's possible. First, create the definition of your annotation. Pretty much like you did in your example, however, with a different #Target type
#Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = PersonValidator.class)
public #interface ValidPerson {
String message () default "Your custom message";
Class<?>[] groups () default {};
Class<? extends Payload>[] payload () default {};
}
Then implement the validator whose isValid method takes the instance of your Person class:
public class PersonValidator implements ConstraintValidator<ValidPerson, Person> {
#Override
public boolean isValid (Person person, ConstraintValidatorContext context) {
// your validation logic
}
}
Sure it is possible, just check the documentation regarding how to write custom class level constraints - http://docs.jboss.org/hibernate/stable/validator/reference/en-US/html_single/#section-class-level-constraints
The important thing of course is that you make sure that one can actually place the constraint annotation on the type level. For that you need to add ElementType.TYPE to the #Target annotation.

Consistent JSR-303 validation with optional properties in Spring controllers and Spring webflow?

Let's say I have a Model Bean "Vendor" with the mandatory property "name" and an optional property "email".
class Vendor {
#Email
private String email;
#NotNull
private String name;
}
#Email allows Null!
I want to use hibernate-validation in my #Controller and in my flows as well. I have in both scenarios the problem that a posted form with an empty field for email binds with "" and not NULL.
StringTrimmerEditor seems to solve my problem by converting "" into NULL. For my #Controller I found SPR-7077 which suggests implementing StringTrimmerEditor globally with #ControllerAdvice. But this doesn't work for my flows.
How do I achieve that globally empty Strings ("") are converted into NULL for JSR-303 validation in spring webflow?
I seem to have found a configuration which supports Null values for empty Strings. For my #Controller I stick with the StringTrimmerEditor in my #ControllerAdvice. Nothing new here.
For SWF ConversionService seems to do the trick: First I create a Converter which converts "" into Null:
public class StringToNullConverter implements Converter<String, String> {
#Override
public String convert(String source) {
if (StringUtils.isEmpty(source)) {
return null;
}
return source;
}
}
Now I have to register this in Spring:
import org.springframework.core.convert.*;
#Configuration
public class SpringConfiguration {
#Bean public ConversionService conversionService() {
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
conversionService.addConverter(new StringToNullConverter());
return conversionService;
}
}
Until now nothing new happens and we're talking about the org.springframework.core.convert package. Now comes the SWF glue into the org.springframework.binding.convert package:
import org.springframework.binding.convert.*;
#Bean public ConversionService flowConversionService(org.springframework.core.convert.ConversionService conversionService) {
DefaultConversionService service = new DefaultConversionService(conversionService);
return service;
}
Wire this into SWF with
<webflow:flow-builder-services [..] conversion-service="flowConversionService" />.
This looks a bit too much, but it does the job. I'm sure there must be a better way, as I have two different implementations (StringTrimmerEditor and StringToNullConverter) to achieve the same thing. For me it looks like ConversionService seems to be the one and only way to go. But I didn't figure out how to get it done for a Spring #Controller.
you can create annotation contains #ConstraintComposition if you want to using optional field.
For example:
import org.hibernate.validator.constraints.ConstraintComposition;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.NotBlank;
#ConstraintComposition
#Target({METHOD, FIELD, ANNOTATION_TYPE})
#Retention(RUNTIME)
#Constraint(validatedBy = {})
#Documented
#NotBlank
#Length(min = 1, max = 5)
#Pattern(regexp = "[A-Z]*")
public #interface SpecialField {
String message() default "{}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
and
#ConstraintComposition(OR)
#Target({METHOD, FIELD, ANNOTATION_TYPE})
#Retention(RUNTIME)
#Constraint(validatedBy = {})
#Documented
#Null
#SpecialField
public #interface OptionalSpecialField {
String message() default "{}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
and after that mark optional filed that:
class NewUser {
#OptionalSpecialField
private String firstName;
#SpecialField
private String lastName;
#Min(18)
private Integer age;
}
First name is not required but if is not null then have to be not blank, match pattern, etc.
All example is on here: https://github.com/lukaszguz/optional-field-validation/tree/master/src/main/java/pl/guz/domain/validation

Type check in JSR-303 custom validator initialize method

I'm attempting to create a class level JSR-303 validation definition that checks that one property occurs before another in time. Because the this validation only makes sense for Calendar properties I was wondering if it is possible to test the property type in the initialize method.
My annotation definition is:
#Target({TYPE, ANNOTATION_TYPE})
#Retention(RUNTIME)
#Constraint(validatedBy = TemporalSequenceValidator.class)
#Documented
public #interface TemporalSequence {
String message() default "{uk.co.zodiac2000.vcms.constraints.TemporalSequence}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String first();
String second();
}
and the validator implementation:
public class TemporalSequenceValidator implements
ConstraintValidator<TemporalSequence, Object> {
private String firstFieldName;
private String secondFieldName;
#Override
public void initialize(final TemporalSequence constraintAnnotation) {
firstFieldName = constraintAnnotation.first();
secondFieldName = constraintAnnotation.second();
// Is it possible to test type of firstFieldName and
// secondFieldName properties here?
}
#Override
public boolean isValid(final Object value, final ConstraintValidatorContext context) {
// omitted
}
}
Is this a sensible thing to do? What approach would you suggest I use if it is? And what action should occur if the properties are not of the correct type?
You can't really do the check in initialize() since you can't access the validated object there. Instead you could check the type of the fields of the validated object in isValid() using reflection:
if ( !Calendar.class.isAssignableFrom(
value.getClass().getField( firstFieldName ).getType() ) ) {
throw new ValidationException( "Field " + firstFieldName + " is not of type Calendar." );
}

Resources