How to apply JSF Validator after annotations constraints? - validation

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;
// ...
}

Related

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.

GWT JSR303 Validation, validate method OR use custom annotations

I'm trying to use GWT's 2.5 in-build validation feature. I have a few complex validations.
Cross field validation with Hibernate Validator (JSR 303) suggests that I could either include methods which do the validation OR write my own annotations. However, both don't work.
public class PageData extends Serializable
#NotNull(message="Cannot be null!")
Boolean value
#AssertTrue(message="isValid() is false!")
private boolean isValid() {
return false;
}
//Getters and Setters
}
Boolean value is validated. However, isValid() is never called/validated. But why?
Is this a GWt specific problem?
Then I tried to write my own annotation, The #FieldMatch example in Cross field validation with Hibernate Validator (JSR 303) uses Beans.getProperty() from Apache Commons BeanUtils, which I cannot use in GWT. Is there any way to make these kind of complex annotations work in GWT?
Here is how I created a custom validation that works across multiple fields of one bean. It checks that when the field ContactProfile for a Contact bean is set to COMPANY, then company name must be filled out, otherwise when set to PERSON, the first name or last name must be filled out :
Annotation definition :
#Target({ ElementType.TYPE })
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = ValidCompanyOrPersonValidator.class)
public #interface ValidCompanyOrPerson {
String message() default "{contact.validcompanyorperson}";
Class<?>[] groups() default {};
Class<? extends Contact>[] payload() default {};
}
Implementation :
public class ValidCompanyOrPersonValidator implements ConstraintValidator<ValidCompanyOrPerson, Contact> {
ValidCompanyOrPerson annotation;
public void initialize(ValidCompanyOrPerson annotation) {
this.annotation = annotation;
}
#SuppressWarnings("nls")
public boolean isValid(Contact contact, ConstraintValidatorContext context) {
boolean ret = false;
if (contact.getContactProfile() == null) {
} else if (contact.getContactProfile().equals(ContactProfile.COMPANY)) {
ret = (contact.getCompanyName() != null);
} else if (contact.getContactProfile().equals(ContactProfile.PERSON)) {
ret = (contact.getGivenName() != null || contact.getFamilyName() != null);
}
return ret;
}
}
Now I can set
#ValidCompanyOrPerson
public class Contact {
...
}
I can use this validation both client (GWT) and server side.
Hope that helps ....

And / Or condition in Spring annotation based validation

I am using Spring 3 annotation based validation. I want to add a following validation for String fields
Field can be Null OR it should contain a non empty string
I know annotation like #Null, #NotEmpty but how I can use both with a OR condition?
Solution:
Using #Size(min=1) helps but it don't handle spaces. So added a custom annotation NotBlankOrNull which will allow null and non empty strings also it takes care of blank spaces. Thanks a lot #Ralph.
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 it on my site.
First of all, it is not Spring annotation based validation, it is JSR 303 Bean Validation, implemented for example by Hibernate Validation. It is really not spring related/
You can not combine the annotations in an OR way*.
But there is a simple workaround for the not null constraint, because the most basic validations accept null as an valid input (therefore you often need to combine the basic vaidations and an extra #NotNull, if you want to have a "normal" behavior but not what you asked for).
For Example:
#javax.validation.constraints.Size accept null as an valid input.
So what you need in your case is use #Size(min=1) instead of #NotEmpty.
BTW: Not #NotEmpty is just an combination of #NotNull and #Size(min = 1)
*except you implement it by your self.

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.

Using a custom ResourceBundle with Hibernate Validator

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

Resources