Using a custom ResourceBundle with Hibernate Validator - spring

I'm trying to set up a custom message source for Hibernate Validator 4.1 through Spring 3.0. I've set up the necessary configuration:
<!-- JSR-303 -->
<bean id="validator"
class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
<property name="validationMessageSource" ref="messageSource"/>
</bean>
The translations are served from my message source, but it seems that the replacement tokens in the messages themselves are looked up in the message source, i.e. for a:
my.message=the property {prop} is invalid
there are calls to look up 'prop' in the messageSource. Going into ResourceBundleMessageInterpolator.interpolateMessage I note that the javadoc states:
Runs the message interpolation according to algorithm specified in JSR 303.
Note:
Look-ups in user bundles is recursive whereas look-ups in default bundle are not!
This looks to me like the recursion will always take place for a user-specified bundle, so in effect I can not translate standard messages like the one for Size.
How can I plug-in my own message source and be able to have parameters be replaced in the message?

This looks to me like the recursion
will always take place for a
user-specified bundle, so in effect I
can not translate standard messages
like the one for Size.
Hibernate Validator's ResourceBundleMessageInterpolator create two instances of ResourceBundleLocator (i.e. PlatformResourceBundleLocator) one for UserDefined validation messages - userResourceBundleLocator and the other for JSR-303 Standard validation messages - defaultResourceBundleLocator.
Any text that appears within two curly braces e.g. {someText} in the message is treated as replacementToken. ResourceBundleMessageInterpolator tries to find the matching value which can replace the replacementToken in ResourceBundleLocators.
first in UserDefinedValidationMessages (which is recursive),
then in DefaultValidationMessages (which is NOT recursive).
So, if you put a Standard JSR-303 message in custom ResourceBundle say, validation_erros.properties, it will be replaced by your custom message. See in this EXAMPLE Standard NotNull validation message 'may not be null' has been replaced by custom 'MyNotNullMessage' message.
How can I plug-in my own message
source and be able to have parameters
be replaced in the message?
my.message=the property {prop} is
invalid
After going through both ResourceBundleLocators, ResourceBundleMessageInterpolator finds for more replaceTokens in the resolvedMessage (resolved by both bundles). These replacementToken are nothing but the names of Annotation's attributes, if such replaceTokens are found in the resolvedMessage, they are replaced by the values of matching Annotation attributes.
ResourceBundleMessageInterpolator.java [Line 168, 4.1.0.Final]
resolvedMessage = replaceAnnotationAttributes( resolvedMessage, annotationParameters );
Providing an example to replace {prop} with custom value, I hope it will help you....
MyNotNull.java
#Constraint(validatedBy = {MyNotNullValidator.class})
public #interface MyNotNull {
String propertyName(); //Annotation Attribute Name
String message() default "{myNotNull}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default {};
}
MyNotNullValidator.java
public class MyNotNullValidator implements ConstraintValidator<MyNotNull, Object> {
public void initialize(MyNotNull parameters) {
}
public boolean isValid(Object object, ConstraintValidatorContext constraintValidatorContext) {
return object != null;
}
}
User.java
class User {
private String userName;
/* whatever name you provide as propertyName will replace {propertyName} in resource bundle */
// Annotation Attribute Value
#MyNotNull(propertyName="userName")
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
}
validation_errors.properties
notNull={propertyName} cannot be null
Test
public void test() {
LocalValidatorFactoryBean factory = applicationContext.getBean("validator", LocalValidatorFactoryBean.class);
Validator validator = factory.getValidator();
User user = new User("James", "Bond");
user.setUserName(null);
Set<ConstraintViolation<User>> violations = validator.validate(user);
for(ConstraintViolation<User> violation : violations) {
System.out.println("Custom Message:- " + violation.getMessage());
}
}
Output
Custom Message:- userName cannot be null

Related

How to add domain object's ID to validation message?

I have a Spring Boot application, which uses annotations from javax.validation to validate my domain object. Unfortunately, the error message doesn't contain the ID of the domain object. I need the ID for operation and development (debugging).
Code
#SpringBootApplication
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
#Data
static class Model {
private String id;
#Min(2)
private int number;
}
#RestController
static class Controller {
#PostMapping("/test")
public int test(#RequestBody #Valid Model model) {
return 1;
}
}
}
Logs
2022-05-12 10:39:15.252 WARN 1424 --- [nio-8080-exec-2] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public int test.TestApplication$Controller.test(test.TestApplication$Model): [Field error in object 'model' on field 'number': rejected value [1]; codes [Min.model.number,Min.number,Min.int,Min]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [model.number,number]; arguments []; default message [number],2]; default message [muss größer-gleich 2 sein]] ]
Research
I can add values of the annotation attributes and the value of the validated property, see Hibernate Validator:
4.1.2. Interpolation with message expressions
As of Hibernate Validator 5 (Bean Validation 1.1) it is possible to use the Unified Expression Language (as defined by JSR 341) in constraint violation messages. This allows to define error messages based on conditional logic and also enables advanced formatting options. The validation engine makes the following objects available in the EL context:
the attribute values of the constraint mapped to the attribute names
the currently validated value (property, bean, method parameter etc.) under the name validatedValue
a bean mapped to the name formatter exposing the var-arg method format(String format, Object... args) which behaves like java.util.Formatter.format(String format, Object... args).
I can write a custom message interpolation, but the MessageInterpolator.Context only provides the validated value.
Question
How can I add domain object's ID to the validation message?

Binding request parameters (GET query) starting with underscore "_" to bean attributes

I need to handle requests like:
http://host/path?_param1=abc&_param2=xxx...
and bind them to bean, like:
#RestController
public class Controller {
#GetMapping("/path")
public String endpoint(#Valid Data data) {
...;
}
static public class Data {
private int _param1;
private String _param2;
...
public int get_param1() {
return _param1;
}
public void set_param1(int _param1) {
this._param1 = _param1;
}
...
}
}
The problem is that Spring ignores properties starting with underscore "_" or is unable to bind them to bean properly. I am just getting empty properties in data bean. Other properties are bound as expected.
Is there a way to handle that? I cannot change the URL and param names...
It costed me some time but I figured out how to solve it. Spring binding has by default turned on mechanism to handle missing attribute values and to distinguish them from just not used attributes (i.e. http checkbox when is not checked does not send any param, but yet it was in form and this case should be treated as "false"/"null" as opposite to case when there was no such checkbox in form element). To do that every such attribute has redundant attribute prefixed with underscore ("checkboxField" has "_checkboxField" companion that is a hidden field and is always sent).
But processing such "companions" looks for field without underscore prefix and creates one with null value when it is not found.
To turn off the mechanism one must use #InitBinder method:
#RestController
public class MyController {
#InitBinder
public void customizeBinding(WebDataBinder binder) {
binder.setFieldMarkerPrefix(null); //required to handle underscore prefixed fields ("_field")
}
#GetMapping(path = "/items")
String endpoint( #RequestParam("_param") String param ) {
... // param is populated with query string "_param"
}
}

Spring Boot + Thymeleaf - form validation

i have problem with Thymeleaf when validating form. I'm trying to create simple user register form to learn Spring and i'm unfortunately stuck.
Here is my UserForm class
public class UserForm {
#NotEmpty
private String username;
#NotEmpty
private String password;
#NotEmpty
private String passwordConfirm;
\\ Getters and Setters
}
First problem is when I add my custom validator class in initBinder
#Autowired
private UserFormValidator formValidator;
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.setValidator(formValidator);
}
"Default" annotated by #NotEmpty validation stops working. This is exptected behavior?
Second problem is how can I show global reject messages in thymeleaf?
My validator class is like below
public class UserFormValidator implements Validator {
#Autowired
UserService userService;
#Override
public boolean supports(Class<?> clazz) {
return UserForm.class.isAssignableFrom(clazz);
}
#Override
public void validate(Object target, Errors errors) {
UserForm userForm = (UserForm) target;
if(!userForm.getPassword().equals(userForm.getPasswordConfirm())) {
errors.reject("passwords.no.match", "Passwords not match");
}
if(userService.findOneByUsername(userForm.getUsername()).isPresent()) {
errors.reject("user.exist", "User already exists (default)");
}
}
}
and post mapping from controller
#PostMapping("/create")
public String registerUser(#ModelAttribute("form") #Valid final UserForm form, BindingResult bindingResult) {
if(bindingResult.hasErrors()) {
return "newuser";
}
userService.saveUser(form);
return "redirect:/";
}
As "default" validation errors i can show by using exth:if="${#fields.hasErrors('passwordConfirm')}" i have no idea how can i show message for error passwords.no.match or check if this error occured?
By default spring boot uses bean validation to validated form object annotated with #Valid. If you want to use your custom validator and register it through #InitBinder, then bean validation will not take place, this is expected behavior. If you want to bean validation also works with your custom validation you need to do it manually inside your validator class or even in controller.
Here comes your second problem to show password not match error message. Inside your custom validator UserFormValidator.class while rejecting any value you need to use rejectValue() method like below:
#Override
public void validate(Object target, Errors errors) {
UserForm userForm = (UserForm) target;
if(!userForm.getPassword().equals(userForm.getPasswordConfirm())) {
errors.rejectValue("passwordConfirm", "passwords.no.match", "Passwords not match");
}
if(userService.findOneByUsername(userForm.getUsername()).isPresent()) {
errors.rejectValue("username", "user.exist", "User already exists (default)");
}
}
The rejectValue() method is used to add a validation error to the Errors object.
The first parameter identifies which field the error is associated with. The second parameter is an error code which acts a message key for the messages.properties file (or messages_en.properties or messages_fr.properties etc, if these are being used). The third parameter of rejectValue() represents the fallback default message, which is displayed if no matching error code is found in the resource bundle.
Now you can show error messages using th:if="${#fields.hasErrors('passwordConfirm')} inside your form.

How to apply JSF Validator after annotations constraints?

My problem is more about performance than functionality.
I am dealing with an email field for a sign up form.
For its validation, I use annotations in the User entity for the email format, and an EmailExistValidator to check in the database if this email is already used.
#Entity
#Table(name = "Users")
public class User {
#Column(name = "email")
#NotNull(message = "Email address required")
#Pattern(regexp = "([^.#]+)(\\.[^.#]+)*#([^.#]+\\.)+([^.#]+)", message = "Invalid email address")
private String email;
// ... (other fields and getters/setters here)
}
The validator:
#FacesValidator(value = "emailExistValidator")
public class EmailExistValidator implements Validator {
private static final String EMAIL_ALREADY_EXISTS = "This email address is already used.";
#EJB
private UserDao userDao;
#Override
public void validate(FacesContext context, UIComponent component, Object value)
throws ValidatorException {
String email = (String) value;
try {
if (email != null && userDao.findByEmail(email) != null) {
throw new ValidatorException(new FacesMessage(FacesMessage.SEVERITY_ERROR,
EMAIL_ALREADY_EXISTS, null));
}
} catch (DaoException e) {
FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, e.getMessage(),
null);
FacesContext facesContext = FacesContext.getCurrentInstance();
facesContext.addMessage(component.getClientId(facesContext), message);
}
}
}
According to my tests, if an email does not verify the regexp pattern, the validator is still applied. I don't like the idea of performing a database query to check for email existence while I already know this email is not even valid. It could really slow down the performance (even more when using ajax on blur events in the form).
I have some guesses but I couldn't find clear answers, that's why I ask a few questions here:
Is this a bad thing to mix a validator with annotation constraints?
During the validation, is every constraint checked even if one of them does not pass? (If yes, then are all the messages (for each constraint) added to the FacesContext for the component, or just the first message?)
If yes to question 2, is there any way to force the validation to stop as soon as one constraint is not verified?
If no to question 2 or yes to question 3, is the order of application of the constraints/validator specified somewhere? Is this order customizable?
If this matters, I'm using PrimeFaces for my Facelets components.
I know I could put everything in the validator and stop whenever, but this validator is to be used only for the Sign Up form. When used for signing in, I would only check the entity's annotation constraints, not the "already exists" part. In addition, annotations and single constraint validators are to my mind more readable than the content of a multi-constraint validator.
JSF validation runs by design before JSR303 bean validation. So there's technically no way to skip JSF validation when bean validation fails. If they would run the other way round, then you would theoretically simply have checked UIInput#isValid() in the JSF validator:
UIInput input = (UIInput) component;
if (!input.isValid()) {
return;
}
There's unfortunately no API-provided way to control the JSF-JSR303 validation order. Your best bet is to turn the JSF validator into a true JSR303 bean validator and assign it to a different group which you declare to run after the default group. In JSR303, constraints are validated on a per-group basis. If one validation group fails, then any subsequent validation groups are not executed.
First create a custom JSR303 bean validation constraint annotation:
#Target({ElementType.METHOD, ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = UniqueEmailValidator.class)
#Documented
public #interface UniqueEmail {
String message() default "{invalid.unique.email}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Then create a custom JSR303 bean validation constraint validator:
public class UniqueEmailValidator implements ConstraintValidator<UniqueEmail, String> {
#Override
public void initialize(UniqueEmail annotation) {
// Grab EJB here via JNDI?
}
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return userDao.findByEmail(value) == null;
}
}
Unfortunately, #EJB/#Inject in a ConstraintValidator isn't natively supported in Java EE 6 / JSR303 Bean Validation 1.0. It's only supported in Java EE 7 / JSR303 Bean Validation 1.1. They are however available via JNDI.
Then create a custom validation group:
public interface ExpensiveChecks {}
And finally use it on your entity whereby the group ordering is declared via #GroupSequence annotation (the default group is identified by current class):
#Entity
#Table(name = "Users")
#GroupSequence({ User.class, ExpensiveChecks.class })
public class User {
#Column(name = "email")
#NotNull(message = "Email address required")
#Pattern(regexp = "([^.#]+)(\\.[^.#]+)*#([^.#]+\\.)+([^.#]+)", message = "Invalid email address")
#UniqueEmail(groups = ExpensiveChecks.class, message = "This email address is already used")
private String email;
// ...
}

Conditional validations in Spring

I have a form which contain some radio button and check-boxes. Whenever user selects/deselects a radio button or check-box few other input fields are enabled/disabled.
I want to add validation only for fields which are enabled when user submits the form. Fields which are disabled at the time of submission should not be considered for validation.
I don't want to add this in client side validation.Is there any better /easy way to implement conditional validation in Spring 3.0 instead of adding multiple if in validator ?
Thanks!
If you use JSR 303 Bean validation then you can use validation groups (groups) for this.
Assume you have this user input, containing two sections.
The two booleans indicating if the sections are enabled or disabled. (Of course you can use more useful annotations than #NotNull)
public class UserInput {
boolean sectionAEnabled;
boolean sectionBEnabled;
#NotNull(groups=SectionA.class)
String someSectionAInput;
#NotNull(groups=SectionA.class)
String someOtherSectionAInput;
#NotNull(groups=SectionB.class)
String someSectionBInput;
Getter and Setter
}
You need two Interfaces for the groups. They work only as marker.
public interface SectionA{}
public interface SectionB{}
Since Spring 3.1 you can use the Spring #Validated annotation (instead of #Validate) in your controller method to trigger the validation:
#RequestMapping...
public void controllerMethod(
#Validated({SectionGroupA.class}) UserInput userInput,
BindingResult binding, ...){...}
Before Spring 3.1 there was no way to specify the validation group that should been used for validation (because #Validated does not exist and #Validate does not have a group attribute), so you need to start the validation by hand written code: This an an example how to trigger the validation in dependence to witch section is enabled in Spring 3.0.
#RequestMapping...
public void controllerMethod(UserInput userInput,...){
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
List<Class<?>> groups = new ArrayList<Class<?>>();
groups.add(javax.validation.groups.Default.class); //Always validate default
if (userInput.isSectionAEnabled) {
groups.add(SectionA.class);
}
if (userInput.isSectionBEnabled) {
groups.add(SectionB.class);
}
Set<ConstraintViolation<UserInput>> validationResult =
validator.validate(userInput, groups.toArray(new Class[0]));
if(validationResult.isEmpty()) {
...
} else {
...
}
}
(BTW: For the Spring 3.0 solution, it is also possible to let Spring inject the validator:
#Inject javax.validation.Validator validator
<mvc:annotation-driven validator="validator"/>
<bean id="validator"
class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
<property name="validationMessageSource" ref="messageSource" />
</bean>
)
When fields are disabled they are transferred to controller as null. I wanted to add a validation which will allow either null i.e disabled field or not blank field i.e. enabled but empty field.
So I created a custom annotation NotBlankOrNull which will allow null and non empty strings also it takes care of blank spaces.
Here is my Annotation
#Documented
#Constraint(validatedBy = { NotBlankOrNullValidator.class })
#Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
#Retention(RUNTIME)
public #interface NotBlankOrNull {
String message() default "{org.hibernate.validator.constraints.NotBlankOrNull.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
Validator class
public class NotBlankOrNullValidator implements ConstraintValidator<NotBlankOrNull, String> {
public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
if ( s == null ) {
return true;
}
return s.trim().length() > 0;
}
#Override
public void initialize(NotBlankOrNull constraint) {
}
}
I have also updated more details on my site.

Resources