Type check in JSR-303 custom validator initialize method - bean-validation

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." );
}

Related

How to use hibernate-validator to validate interface parameters

I have the following code to implement interface input parameter validation and now want to use hibernate-validator to do this
public class Order
{
private String orderNo;
private String orderId;
private String status;
private String startTime;
private String endTime;
//getter and setter...
}
public class OrderService
{
public Object search(Order order) throws Exception
{
String message = "";
if (order.getOrderId().isEmpty() && order.getOrderNo().isEmpty() && order.getStatus().isEmpty())
{
if (order.getStartTime().isEmpty() && order.getEndTime().isEmpty())
message = "xxx";
}
if (!message.isEmpty())
throw new Exception(message);
Object result = null;
// splice sql according to the attribute of order and get the result
// result = sql query result
return result;
}
}
I tried to use Hibernate-validator's group to achieve this, but if there are more parameters, I need to write a lot of groups, which seems stupid. I have more than 100 interfaces, and will be added later, using Class-level constraints would be a good idea choice?
Below is the code trying to use Hibernate-validator's group implementation:
public class Order
{
#Empty(groups = One.class)
#NotEmpty(groups = Two.class)
private String orderNo;
#Empty(groups = One.class)
#NotEmpty(groups = Three.class)
private String orderId;
#Empty(groups = One.class)
#NotEmpty(groups = Four.class)
private String status;
#NotEmpty(groups = One.class)
private String startTime;
#NotEmpty(groups = One.class)
private String endTime;
}
public class BeanValidatorUtils
{
static Validator validator;
static
{
HibernateValidatorConfiguration configuration = Validation.byProvider(HibernateValidator.class).configure();
ValidatorFactory factory = configuration.failFast(true).buildValidatorFactory();
validator = factory.getValidator();
}
public static <T> void validation(T beanParam) throws AppException
{
if (!containsGroup(beanParam, One.class))
return;
Set<ConstraintViolation<T>> validate = validator.validate(beanParam, One.class);
ConstraintViolation<T> constraintViolation = validate.iterator().next();
String firstViolationMessage = constraintViolation.getMessage();
if (!validate.isEmpty() && containsGroup(beanParam, Two.class))
{
validate = validator.validate(beanParam, Two.class);
}
if (!validate.isEmpty() && containsGroup(beanParam, Three.class))
{
validate = validator.validate(beanParam, Three.class);
}
if (!validate.isEmpty())
throw new AppException(firstViolationMessage);
}
private static boolean containsGroup(Object bean, Class<?> groupClazz)
{
// ...
}
}
Is there any other way to use Hibernate-validator to verify the Order in the search method?
As you are trying to make a validation decision based on the state of multiple properties of the Order you might want to explore these 3 options:
Class level constraint
This would mean that you have to create your own constraint annotation (let's say #ValidOrder) and a corresponding ValidOrderValidator
#Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
#Retention(RUNTIME)
#Documented
#Constraint(validatedBy = { ValidOrderValidator.class })
#interface ValidOrder {
String message() default "{message.key}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
public class ValidOrderValidator implements ConstraintValidator<ValidOrder, Order> {
#Override
public boolean isValid(Order order, ConstraintValidatorContext constraintValidatorContext) {
//null values are valid
if ( order == null ) {
return true;
}
if (order.getOrderId().isEmpty() && order.getOrderNo().isEmpty() && order.getStatus().isEmpty()) {
if ( order.getStartTime().isEmpty() && order.getEndTime().isEmpty() ) { return false; }
}
return true;
}
}
You can also check this post for more detailed info on how to add new constraints using ServiceLoader.
#ScriptAssert constraint
If your validation logic is relatively simple and you either already have a dependency or are willing to add one for a scripting engine, you can consider using the #ScriptAssert constraint. This is similar to the previous option but you don't need to create annotations and validator implementations you just have to put script logic into this constraint:
#ScriptAssert(lang = "groovy", script = "your validation script logic")
class Order {
//...
}
#AssertTrue constraint
Last but not least, one of the easiest ways to address such validation is to use #AssertTrue constraint on a getter with validation logic inside the Order class:
class Order {
//...
#AssertTrue
public boolean isValidOrder() {
// your validation logic
}
}
Using any of these 3 approaches, you'd be able to make a validation decision based on multiple properties of the Order class.
As for validation group usage - you can leverage using the groups if you need to pass the same Order object into multiple different methods/interfaces where a different set of validation rules need to be applied in each of them. Let's say, in one case, you have to create an order, and half of the fields can be null, but then in the other - you want to update it, and everything should be present.

Custom validator throws exception javax.validation.UnexpectedTypeException: HV000030

I want to force user to send numeric value for a field in request, as user may enter char as well.
Since I haven't found any built in solution in spring mvc validation, I chose to create my own custom validator to check the entered value is number or not.
Please find below code snippet.
Constraint interface :
#Documented
#Constraint(validatedBy = {IntegerValidator.class})
#Target({ METHOD, FIELD, ANNOTATION_TYPE })
#Retention(RUNTIME)
public #interface IntegerConstraint {
String message() default "Please enter numers only...!!!";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Validator class :
public class IntegerValidator implements ConstraintValidator<IntegerConstraint, String> {
#Override
public void initialize(IntegerConstraint contactNumber) {
}
#Override
public boolean isValid(String reqParam, ConstraintValidatorContext cxt) {
return !StringUtils.isEmpty(reqParam) && reqParam.matches("^(0|[1-9][0-9]*)$");
}
}
DTO class field :
#IntegerConstraint
#PositiveOrZero(message = "Sorting number either can be positive or zero...!!!")
private Integer sortOrd;
Controller :
public ModelAndView addDetail(#Valid #ModelAttribute("fooDetails") FooDTO footDTO,
BindingResult result, HttpServletRequest request)
Error log :
javax.validation.UnexpectedTypeException: HV000030: No validator could be found for constraint 'com.eps.customvalidator.IntegerConstraint' validating type 'java.lang.Integer'. Check configuration for 'sortOrd'
at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree.getExceptionForNullValidator(ConstraintTree.java:108) ~[hibernate-validator-6.0.10.Final.jar:6.0.10.Final]
at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree.getInitializedConstraintValidator(ConstraintTree.java:140) ~[hibernate-validator-6.0.10.Final.jar:6.0.10.Final]
at org.hibernate.validator.internal.engine.constraintvalidation.SimpleConstraintTree.validateConstraints(SimpleConstraintTree.java:55) ~[hibernate-validator-6.0.10.Final.jar:6.0.10.Final]
at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree.validateConstraints(ConstraintTree.java:73) ~[hibernate-validator-6.0.10.Final.jar:6.0.10.Final]
This might happened because your IntegerValidator is implementing ConstraintValidator<IntegerConstraint, String>, thus it is expected to validate String fields. But you are applying it on an Integer field.
You should either consider to change ConstraintValidator<IntegerConstraint, String> to ConstraintValidator<IntegerConstraint, Integer> or to change your DTO to private String sortOrd;

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.

JSR-303 Bean Validation Collection Values

I am trying to use JSR-303 Bean Validation with Hibernate Validator to ensure that a collection does not contain null values.
I know that I can annotate my collection as follows:
#Valid
#NotEmpty
private Set<EmailAddress> emails = new HashSet<>();
This does the job for ensuring that the collection itself is not null or empty and in the case that I add a non-null EmailAddress element it also validates this correctly. However, it doesn't prevent adding a null element.
Is there a way to prevent a null element being added to the collection? In an ideal world the solution would be declarative (like the rest of the validation) and wouldn't involve programmatically iterating through the collection manually doing null checks.
Thanks in advance!
Bean Validation is missing the #NotBlank annotation for Collections that would pretty much fit the scenario you are describing. Basically, as you mentioned, you would require to implement a custom validator that will programatically check that the contents of the collection ensuring that none of the elements inside it are null.
Here is an example of the custom validator you would need:
public class CollectionNotBlankValidator implements
ConstraintValidator<CollectionNotBlank, Collection> {
#Override
public void initialize(CollectionNotBlank constraintAnnotation) {
}
#Override
public boolean isValid(Collection value, ConstraintValidatorContext context) {
Iterator<Object> iter = value.iterator();
while(iter.hasNext()){
Object element = iter.next();
if (element == null)
return false;
}
return true;
}
}
As you can see I have named the custom annotaion CollectionNotBlank. Here is an example of the code for the custom annotation:
#Target(FIELD)
#Retention(RUNTIME)
#Constraint(validatedBy = CollectionNotBlankValidator.class)
#ReportAsSingleViolation
#Documented
public #interface CollectionNotBlank {
String message() default "The elements inside the collection cannot be null values.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

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

Resources