Is it possible to have both a validator for a form and annotation constraints?
For example to have in a form object this field:
@NotEmpty
private String date;
but then validate the date's pattern in a validator.
I know there is the pattern annotation but I just want to see if I can use both types of validating.
Here is the link to a very good site where it's explained how you can combine the JSR-303 validator with the spring validator.
I'll present next my solution that works. Hope it helps.
My abstract Validator:
import java.util.Map;
import java.util.Set;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorFactory;
import javax.validation.ConstraintViolation;
import javax.validation.Validator;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.validation.Errors;
public abstract class AbstractValidator implements org.springframework.validation.Validator, ApplicationContextAware,
ConstraintValidatorFactory {
@Autowired
private Validator validator;
private ApplicationContext applicationContext;
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public <T extends ConstraintValidator<?, ?>> T getInstance(Class<T> key) {
Map<String, T> beansByNames = applicationContext.getBeansOfType(key);
if (beansByNames.isEmpty()) {
try {
return key.newInstance();
} catch (InstantiationException e) {
throw new RuntimeException("Could not instantiate constraint validator class '" + key.getName() + "'", e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Could not instantiate constraint validator class '" + key.getName() + "'", e);
}
}
if (beansByNames.size() > 1) {
throw new RuntimeException("Only one bean of type '" + key.getName() + "' is allowed in the application context");
}
return (T) beansByNames.values().iterator().next();
}
public boolean supports(Class<?> c) {
return true;
}
public void validate(Object objectForm, Errors errors) {
Set<ConstraintViolation<Object>> constraintViolations = validator.validate(objectForm);
for (ConstraintViolation<Object> constraintViolation : constraintViolations) {
String propertyPath = constraintViolation.getPropertyPath().toString();
String message = constraintViolation.getMessage();
errors.rejectValue(propertyPath, "", message);
}
addExtraValidation(objectForm, errors);
}
protected abstract void addExtraValidation(Object objectForm, Errors errors);
}
An actual Validator:
import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import ro.scorpionsoftware.demo3.dao.AbstractValidator;
@Component(value="doctorValidator")
public class DoctorValidator extends AbstractValidator {
@Override
protected void addExtraValidation(Object objectForm, Errors errors) {
//perform typical validation
//can autowire to context
}
}
A controller: (At the end it's the binding of the @Valid with the validator)
@Controller
public class DoctorEditController {
@Autowired
private DoctorValidator doctorValidator;
@RequestMapping(value = "/doctorEdit", method = RequestMethod.POST)
public String processSubmit(
@ModelAttribute("doctorForm") @Valid DoctorForm df,
BindingResult result,
ModelMap model) {
...
}
@InitBinder
protected void initBinder(WebDataBinder binder) {
binder.setValidator(doctorValidator);
}
}
In context declare the JSR-303 validator:
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" />
With this approach you can get the context in both the actual validator and any other custom annotation you'd like to implement.
You can compose together annotations to use multiple validators, so it would be something like this.
@NotEmpty
@Pattern("") // not sure of syntax here
@Target(ElementType.METHOD)
@Retention( RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {})
@Documented
public @interface DatePattern {
String message() default "";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}