Parametrizable JSR-303 validation values - java

I use JSR-303 bean validation and Spring 3 and I need to provide different values for the annotation depending on the use-case.
For example, the value of min parameter in #Size(min=?) must be 1 for some validation and 5 for another case and I want to read this values from a properties file.
I know the message parameter can be read from ValidationMessages.properties file if provided as a key but what about the other parameter?

As outlined by dpb you can use validation groups to specify the same constraint with different attribute values.
If you're working with Hibernate Validator as BV implementation, based on that you could use the programmatic API instead of annotations to define your constraints. That way you could retrieve the concrete constraint values at runtime like this:
int minValue1 = ...; //read from properties file etc.
int minValue2 = ...;
//programmatically define the constraints for the Test type
ConstraintMapping mapping = new ConstraintMapping();
mapping.type( Test.class )
.property( "prop", FIELD )
.constraint( new NotNullDef() )
.constraint( new SizeDef().min( minValue1 ).groups( GroupOne.class ) )
.constraint( new SizeDef().min( minValue2 ).groups( GroupTwo.class ) );
//retrieve a validator using the programmatic constraint mapping
HibernateValidatorConfiguration config =
Validation.byProvider( HibernateValidator.class ).configure();
config.addMapping( mapping );
ValidatorFactory factory = config.buildValidatorFactory();
Validator validator = factory.getValidator();

The values for annotation parameters can only be compile time expressions. This means that for the #Size(min=X, max=Z) X and Z must be resolvable at compile time.
Since min and max are declared as int on #Size, you are stuck.
If you need different values for min, I personally see two ways of doing it.
First, you could use a grouping on the validators. Use one group for min=1 and one group for min=5. For example, lets consider a Test class:
public class Test {
#NotNull
#Size.List({
#Size(min = 1, groups = GroupOne.class),
#Size(min = 5, groups = GroupTwo.class)
})
private String prop;
public String getProp() {
return prop;
}
public void setProp(String prop) {
this.prop = prop;
}
}
You must declare the groups:
public interface GroupOne {}
public interface GroupTwo {}
Then create some testing object plus the validator to go with it:
Test test = new Test();
test.setProp("XY");
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Then validate using the groups:
Set<ConstraintViolation<Test>> resultOne = validator.validate(test, GroupOne.class);
Set<ConstraintViolation<Test>> resultTwo = validator.validate(test, GroupTwo.class);
First case is valid since min=1 and "XY".length() == 2 but second will fail because min=5.
This method involves doing the validation manually and I don't think you can just rely on #Valid on a #RequestMapping annotated method to do the validation (since #Valid is just a trigger for the validation with no way of mentioning the required group for validation). Luckly Spring is very flexible and it won't be much overhead to call the validator yourself.
The second option I see involves creating your own validation annotation with a custom validator to match. Here you can find a simple example to get you started. With this solution you can declare min and max as string keys that your validator will resolve in the bundles prior to validation. This solution though is more overhead than the first.

Related

Hibernate validation: give the ability to user to only enter a digits(number) not instead

I have a jsf form and I have an input which normally accept only number(integer) .
I want to custom the error message when the user enter a string or char in this field. I want the validation in the data layer thats mean with hibernate annotation.
I don't want use this default message if the user enter a string instead of integer, I want using my custom error message.
: '10S' must be a number between -2147483648 and 2147483647 Example:
9346
Please the attached image can explain well.
How could I achieve this please.
Thank you in advance.
You should implement your own javax.validation.MessageInterpolator
(from https://docs.jboss.org/hibernate/stable/validator/reference/en-US/html_single/#section-validator-factory-message-interpolator)
Message interpolators are used by the validation engine to create user
readable error messages from constraint message descriptors.
In case the default message interpolation algorithm described in
Chapter 4, Interpolating constraint error messages is not sufficient
for your needs, you can pass in your own implementation of the
MessageInterpolator interface via Configuration#messageInterpolator()
as shown in the example below:
package org.hibernate.validator.referenceguide.chapter09;
public class MyMessageInterpolator implements MessageInterpolator {
#Override
public String interpolate(String messageTemplate, Context context) {
//...
return null;
}
#Override
public String interpolate(String messageTemplate, Context context, Locale locale) {
//...
return null;
}
}
You can configure your validator to use your custom interpolator like that:
ValidatorFactory validatorFactory = Validation.byDefaultProvider()
.configure()
.messageInterpolator( new MyMessageInterpolator() )
.buildValidatorFactory();
Validator validator = validatorFactory.getValidator();
You can achieve this with the #Range annotation
#Range(min = -2147483648, max = 2147483647, message= ": '10S' must be a number between -2147483648 and 2147483647 Example: 9346")
long value;

Bean validation specification prioritizing

I am using bean validation specification to validate my form on spring-boot thymeleaf project.
My entity property is as follow.
#NotEmpty(message = "{Password should not be empty}")
#Pattern(regexp = //Pattern for range 1-20, message = "{Wrong Input}")
private String password;
When I run and inputed to password field of my form with empty value, both of Validation Error Messages were shown.
My expectation is, while empty value is inputed, only #NotEmpty annotation should work and on the other hand, only #Pattern should be shown upon user input is wrong.
How can I do with Bean Validation Specification for that?
Regards.
1. Validation groups
#NotEmpty(groups = First.class), message = ...,
#Pattern(groups = Second.class, regexp = ...)
private String password;
Create the validation groups:
//Validation Groups - Just empty interface used as Group identifier
public interface First {
}
public interface Second {
}
and validate the model this way:
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
Set<ConstraintViolation<Model>> violations = validator.validate(model, First.class);
if(violations.isEmpty()){
violations = validator.validate(model, Second.class);
}
2. Groups Sequences
I've never used them, but it seems it does just what you want
Check this other answer (https://stackoverflow.com/a/7779681/641627). I've added below a quote from this answer (from #Gunnar), which ironically also uses First and Second group names:
#GroupSequence({First.class, Second.class})
public interface Sequence {}
#Size(min = 2, max = 10, message = "Name length improper", groups = { First.class })
#Pattern(regexp = "T.*", message = "Name doesn't start with T" , groups = { Second.class })
private String name;
When now validating a Bean instance using the defined sequence
(validator.validate(bean, Sequence.class)), at first the #Size
constraint will be validated and only if that succeeds the #Pattern
constraint.
With this solution, you wouldn't need to manually call validator.validate(...), the validations would be performed in the order defined in the Sequence with short-circuit if one fails.

JPA: Parameterized instances of AttributeConverter

We are developing an application connected to a legacy database. This is very "untyped", using strings for almost all data. What is worse is that is far of being homogeneous: it uses different patterns for dates or times ('YYDDMM', 'HHMMSS', milliseconds) and booleans ('Y'/'N', 'X'/' '), for example.
We want to use JPA (EclipseLink) and custom converters. The problem is that #Convert expects a class implementing AttributeConverter, so we have to do new classes for each pattern. What I'd like is a BooleanConverter class, which can be instantiated with values 'Y'/'N' or 'X'/' '.
This is obviously out of JPA spec, but maybe it's possible using EclipseLink annotations/configuration. Looking at its #Convert annotation, a converter can be specified by name. This sounds good to me if I can register a ynBooleanConverter and xSpaceBooleanConverter:
// Unfortunately, this method does not exist :(
Session.addConverter('ynBooleanConverter', new BooleanConverter("Y", "N"));
#Entity
public class MyEntity {
#Convert("ynBooleanConverter")
private Boolean myBoolean;
...
}
Is it possible? What other options do we have?
Try #ObjectTypeConverter:
#Entity
#ObjectTypeConverters({
#ObjectTypeConverter(name = "ynBooleanConverter", objectType = Boolean.class, dataType = String.class,
conversionValues = {
#ConversionValue(objectValue = "true", dataValue = "Y"),
#ConversionValue(objectValue = "false", dataValue = "N") }),
#ObjectTypeConverter(name = "xSpaceBooleanConverter", objectType = Boolean.class, dataType = String.class,
conversionValues = {
#ConversionValue(objectValue = "true", dataValue = "X"),
#ConversionValue(objectValue = "false", dataValue = " ") }),
})
public class MyEntity {
#Convert("ynBooleanConverter")
private boolean ynBoolean;
#Convert("xSpaceBooleanConverter")
private boolean xSpaceBoolean;
}
So your Converter behaves different depending on some state in the context? I think I would try to bind the context info to a threadlocal variable which I can read back in the Converter implementation.
Do you have access to a CDI-implementation? Then its even more elegant to inject some bean with your context info into your Converter-implementation. You mentioned that you are missing some session-Methods? Maybe a #SessionScope'ed bean will help you.
Sadly #Inject is not specified in a converter class. You will need to lookup the bean "by hand" like mentioned in this post.
Too late to this thread, but here is a blog post which shows how JPA converters are to be written. Has working code for String and LocalDate conversions.

Is there a way to get the declared value of constraint in hibernate validator in another class?

Using the hibernate validator i declare something like this
public class TestSomething{
#Length(max=30, message="Error Message.")
private String name;
getter and setter here
}
is it possible to get the maximum number of character in this case 30
something like
TestSomething ts = new TestSomething();
int maxValue = ts.getName.getThatMaximumNumberOrSomethng
will java reflection on this kind of situation?
You should use the Bean Validation metadata API. Provided you have a Validator instance you can get hold of a so called ConstraintDescriptor:
BeanDescriptor beanDescriptor = getBeanDescriptor( TestSomething.class );
PropertyDescriptor propertyDescriptor = beanDescriptor.getConstraintsForProperty( "name" );
Set<ConstraintDescriptor<?>> constraintDescriptors = propertyDescriptor.getConstraintDescriptors();
Once you have the right ConstraintDescriptor you can either call
constraintDescriptor.getAnnotation(); // to get the actual constraint annotation
constraintDescriptor.getAttributes().get("max"); // to retrieve the attribute from the attributes map provided by the descriptor as convenience

JSR 303: Is it possible to validate whole graph automatically?

Is it possible to validate whole graph automatically ? By full graph I mean the object that is being validated and all its fields that are beans too.
Or I have to traverse through them manually as shown below ?
Currently I do something like this
Set<ConstraintViolation<OrderProxy>> violationsOrder =
validator.validate(order, Default.class, ClientGroup.class);
Set<ConstraintViolation<OrganizationProxy>> violationsOrg =
validator.validate(order.getSender(), Default.class, ClientGroup.class);
Set<ConstraintViolation<PersonProxy>> violationsPerson =
validator.validate(order.getSender().getPerson(),
Default.class, ClientGroup.class);
You can annotate any fields that you want validated with #Valid and when validating your main object, it will also validate the fields.
This works for example:
public class Order
#Valid
protected Header header;
#Valid
protected List<Detail> details;
}
You would need to call
Set<ConstraintViolation<Order>> violationsOrder = validator.validate(order, Default.class, ClientGroup.class);
to get all violations (also those for the fields). You would then have to parse the causes using getPropertyPath() to get the exact validation source, if you need it.
javax.validation.Path rp = violation.getPropertyPath();

Categories

Resources