HV000030: No validator could be found for constraint (Hibernate Validator) - spring

I was following this tutorial for create a custom validation with multiple parameters, but I get the following exception when executing the #PostMapping method:
HV000030: No validator could be found for constraint 'com.crimsonlogic.anotaciones.TimeRangeConstraints' validating type 'com.crimsonlogic.model.NuevoEvento'. Check configuration for ''
it catches my attention that at the "check configuration for ''" part, does not tell me any kind of information.
NuevoEvento class:
#TimeRangeConstraints.List({
#TimeRangeConstraints(
fechaEvento="fechaEvento",
horaInicio="horaInicio",
horaCulminacion="horaCulminacion"
)
})
public class NuevoEvento {
#NotNull(message="Como se llamara el evento?")
#Size(max=40, message="Titulo invalido")
private String titulo;
#NotNull(message="Seleccione un tipo.")
private String tipoEvento;
private String url;
#NotNull(message="Seleccione la fecha del evento")
private String fechaEvento;
#NotNull(message="A que hora inicia el evento?")
private String horaInicio;
#NotBlank(message="A que hora termina el evento?")
private String horaCulminacion;
#NotNull(message="Seleccione un salon.")
private int salonId;
public NuevoEvento() {}
public String getTitulo() {
return titulo;
}
public void setTitulo(String titulo) {
this.titulo = titulo;
}
public String getTipoEvento() {
return tipoEvento;
}
public void setTipoEvento(String tipoEvento) {
this.tipoEvento = tipoEvento;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getFechaEvento() {
return fechaEvento;
}
public void setFechaEvento(String fechaEvento) {
this.fechaEvento = fechaEvento;
}
public String getHoraInicio() {
return horaInicio;
}
public void setHoraInicio(String horaInicio) {
this.horaInicio = horaInicio;
}
public String getHoraCulminacion() {
return horaCulminacion;
}
public void setHoraCulminacion(String horaCulminacion) {
this.horaCulminacion = horaCulminacion;
}
public int getSalonId() {
return salonId;
}
public void setSalon(int salon) {
this.salonId = salon;
}
}
TimeRangeConstraint annotation:
#Documented
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy= TimeRangeValidator.class)
public #interface TimeRangeConstraints {
String fechaEvento();
String horaInicio();
String horaCulminacion();
String message() default "El rango de tiempo establecido no es valido o esta ocupado.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#interface List {
TimeRangeConstraints[] value();
}
}
Anyone knows causes of this problem?

I found the problem.
In my TimeRangeValidator class I had the code as follows:
//HERE WAS THE PROBLEM
public class TimeRangeValidator implements ConstraintValidator<TimeRangeConstraints,String> {
//--------------------------------------------------------------------------------->
private String fechaEvento;
private String horaInicial;
private String horaFinal;
#Autowired
private UsuarioSalonRepository usuarioSalon;
#Override
public void initialize(TimeRangeConstraints constraintAnnotation) {
this.fechaEvento = constraintAnnotation.fechaEvento();
this.horaInicial = constraintAnnotation.horaInicio();
this.horaFinal = constraintAnnotation.horaCulminacion();
}
//// MORE AND MOREEE CODE....////
I had to replace the String with Object
public class TimeRangeValidator implements ConstraintValidator<TimeRangeConstraints,Object>
and the problem disappeared.
Deeper explanation for those who did not understand what happened
TimeRangeValidator takes 3 fields from the form to perform the validation logic. The value that was changed previously prevented me from taking the 3 fields of the form due to the following reason:
#Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
Object dt = new BeanWrapperImpl(value).getPropertyValue(fechaEvento);
Object hInit = new BeanWrapperImpl(value).getPropertyValue(horaInicial);
Object hFin = new BeanWrapperImpl(value).getPropertyValue(horaFinal);
SimpleDateFormat form = new SimpleDateFormat("yyyy-MM-dd");
try {
Date dia = form.parse(dt.toString().replaceAll("/","-"));
return TimeUtils.detectOverlappingEvents(usuarioSalon.buscarEvento(dia),
hInit.toString().replaceAll("\\s","")+":00",
hFin.toString().replaceAll("\\s","")+":00");
} catch (ParseException e) {
e.printStackTrace();
return false;
}
With the type object I can have the ability (with the help of BeanWrapperImpl) to obtain multiple values of the form to validate them.
Normally, type String (or Integer, whatever) is used to validate a single value of the form.

Related

Swagger OpenApi sends empty value "" instead of null and this ends up giving a 400 Bad Request status

I'm doing a rest api in spring boot and I need to receive 3 normal values and a file, as you can see in the image below.
endpoint image in swagger
My problem is that it is not mandatory to send the file, so when I make the request without the file, instead of going null it goes "" which is considered a string and this ends up giving status 400, because it is waiting for a type MultipartFile.
My method in spring boot:
#Operation(tags = {"Contato"}, summary = "Atualizar contato", description = "Atualiza os dados de um contato ativo a partir do ID.")
#PutMapping(path = "/atualizarContato",consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseDto atualizarContato(#ModelAttribute ContatoUpdateDto contatoDto) throws IOException {// fazer a logica}
My class, I even tried to put the default value in #Schema but it doesn't work.
package app.api.denuncia.Dto;
import org.springframework.web.multipart.MultipartFile;
import io.swagger.v3.oas.annotations.media.Schema;
public class ContatoUpdateDto {
#Schema(description = "O identificador (ID) do contato", required = true)
private int id;
#Schema(description = "O nome do contato", required = true)
private String nome;
#Schema(description = "O número de telefone do contato")
private String telefone;
#Schema(description = "Imagem que representa o contato")
private MultipartFile logotipo;
public ContatoUpdateDto() {
}
public String getTelefone() {
return telefone;
}
public void setTelefone(String telefone) {
this.telefone = telefone;
}
public String getNome() {
return nome;
}
public void setNome(String nome) {
this.nome = nome;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public MultipartFile getLogotipo() {
return logotipo;
}
public void setLogotipo(MultipartFile logotipo) {
this.logotipo = logotipo;
}
}
I just need that instead of sending "" it sends null.
The error:
image 1
image 2

#Embedded Audit was working with spring boot 1.4 but now not working after upgrade to spring boot 2.1.x

public interface EAuditable {
public static interface Update{
public EUpdateInfo getUpdateInfo();
public void setUpdateInfo(EUpdateInfo updateInfo);
}
public static interface Create{
public ECreateInfo getCreateInfo();
public void setCreateInfo(ECreateInfo createInfo);
}
public static interface UpdateDate{
public ZonedDateTime getLastUpdatedOn();
public void setLastUpdatedOn(ZonedDateTime date);
}
public static interface UpdateUser{
public String getLastUpdatedBy();
public void setLastUpdatedBy(String user);
}
public static interface CreateDate{
public ZonedDateTime getCreatedOn();
public void setCreatedOn(ZonedDateTime date);
}
public static interface CreateUser{
public String getCreatedBy();
public void setCreatedBy(String user);
}
}
#Entity
#Table(name = "TRACKABLE_ITEM")
#Inheritance(strategy = InheritanceType.JOINED)
public class ETrackableItem extends AbstractIdTenantPersistable implements EAuditable.Create, EAuditable.Update {
#Embedded
private ECreateInfo createInfo;
#Embedded
private EUpdateInfo updateInfo;
}
#MappedSuperclass
//JPA: For Eclipse Link, use #AdditionaliCritera instead of #Filter and #FilterDef
#FilterDef(name="multiTenant", parameters=#ParamDef( name="tenant_id", type="string" ) )
#Filter(name="multiTenant", condition=":tenant_id = TENANT_ID")
public abstract class AbstractIdTenantPersistable extends AbstractIdPersistable {
#Column(name = "TENANT_ID", length = 12)
protected String tenantId;
public String getTenantId() {
return tenantId;
}
public void setTenantId(String tenantId) {
this.tenantId = tenantId;
}
protected AbstractIdTenantPersistable(){}
protected AbstractIdTenantPersistable(UUID id) {
super(id);
}
protected AbstractIdTenantPersistable(UUID id, String tenantId) {
super(id);
this.tenantId = tenantId;
}
}
#MappedSuperclass
#EntityListeners(value = { PersistableEntityListener.class })
#TypeDef(name = "json", typeClass = JsonUserType.class)
public abstract class AbstractPersistable {
public enum ConstraintType{UniqueName, Unique, Others};
/**
* Returns the constraint name to its type mapping.
* #param name
* #return
*/
public ConstraintType getConstraintType(String name){
return ConstraintType.Others;
}
}
public class PersistableEntityListener {
#PrePersist
public void prePersist(AbstractPersistable e) {
if (e instanceof EAuditable.Create){
EAuditable.Create create = (EAuditable.Create) e;
create.setCreateInfo(new ECreateInfo(ZonedDateTime.now(ZoneId.of("UTC")), getUserName()));
}
if (e instanceof EAuditable.CreateDate){
EAuditable.CreateDate createDate = (EAuditable.CreateDate) e;
createDate.setCreatedOn(ZonedDateTime.now(ZoneId.of("UTC")));
}
if (e instanceof EAuditable.CreateUser){
EAuditable.CreateUser createUser = (EAuditable.CreateUser) e;
createUser.setCreatedBy(getUserName());
}
if (e instanceof AbstractIdTenantPersistable){
((AbstractIdTenantPersistable)e).setTenantId(getTenantId());
}
preUpdate(e);
}
#PreUpdate
public void preUpdate(AbstractPersistable e) {
if (e instanceof EAuditable.Update){
EAuditable.Update update = (EAuditable.Update) e;
update.setUpdateInfo(new EUpdateInfo(ZonedDateTime.now(ZoneId.of("UTC")), getUserName()));
}
if (e instanceof EAuditable.UpdateDate){
EAuditable.UpdateDate updateDate = (EAuditable.UpdateDate) e;
updateDate.setLastUpdatedOn(ZonedDateTime.now(ZoneId.of("UTC")));
}
if (e instanceof EAuditable.UpdateUser){
EAuditable.UpdateUser updateUser = (EAuditable.UpdateUser) e;
updateUser.setLastUpdatedBy(getUserName());
}
}
private String getUserName() {
//checkContext();
if (isUserSet()){
return ContextHolder.get().getAuthenticatedContext().getUserName();
}
return "-";
}
private String getTenantId() {
//checkContext();
if (isTenantSet()){
return ContextHolder.get().getAuthenticatedContext().getTenantId();
}
return "-";
}
private boolean isUserSet(){
return ContextHolder.get() != null &&
ContextHolder.get().getAuthenticatedContext() != null &&
ContextHolder.get().getAuthenticatedContext().getUserName() != null;
}
private boolean isTenantSet(){
return ContextHolder.get() != null &&
ContextHolder.get().getAuthenticatedContext() != null &&
ContextHolder.get().getAuthenticatedContext().getTenantId() != null;
}
private boolean isContextSet(){
return isUserSet() && isTenantSet();
}
private void checkContext(){
BeanHolder.asserts().isTrue(
isContextSet(),`enter code here`
DomainException.class, "DOMAIN.CONTEXT_NOT_SET");
}
}
Below the code which was working fine for auto populate the audit info with the spring-data-jpa used with spring boot 1.4.x but it is not working when we upgraded to spring boot 2.1.x
Can any one please help if we have different way of handing for the same.
I need to follow this kind of #Embedded way of doing as here we are doing it by composition except inheritance.
Thanks in advance.

Converter works for RequestParameter but not for RequestBody field

I have the following converter:
#Component
public class CountryEnumConverter implements Converter<String, CountryEnum> {
#Override
public CountryEnum convert(String country) {
CountryEnum countryEnum = CountryEnum.getBySign(country);
if (countryEnum == null) {
throw new IllegalArgumentException(country + " - Country is not supported!");
}
return countryEnum;
}
}
Registered it is invoked when used for RequestParam
#GetMapping(value = RestApiEndpoints.RESULTS, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<ResultDto> getResults(
Principal principal,
#RequestParam CountryEnum country) {
....
}
But this converter is never invoked when used for field in the RequstBody:
#GetMapping(value = RestApiEndpoints.RESULTS, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<ResultDto> getResults(
Principal principal,
#RequestBody MyBody myBody) {
....
}
public class MyBody {
#NotNull
private CountryEnum country;
public MyBody() {
}
public CountryEnum getCountry() {
return country;
}
public void setCountry(CountryEnum country) {
this.country = country;
}
}
Your existing org.springframework.core.convert.converter.Converter instance will only work with data submitted as form encoded data. With #RequestBody you are sending JSON data which will be deserialized using using the Jackson library.
You can then create an instance of com.fasterxml.jackson.databind.util.StdConverter<IN, OUT>
public class StringToCountryTypeConverter extends StdConverter<String, CountryType> {
#Override
public CountryType convert(String value) {
//convert and return
}
}
and then apply this on the target property:
public class MyBody {
#NotNull
#JsonDeserialize(converter = StringToCountryTypeConverter.class)
private CountryEnum country;
}
Given the similarity of the 2 interfaces I would expect that you could create one class to handle both scenarios:
public class StringToCountryTypeConverter extends StdConverter<String, CountryType>
implements org.springframework.core.convert.converter.Converter<String, CountryType> {
#Override
public CountryType convert(String value) {
//convert and return
}
}
I found out that if I add the following code to my CountryEnum will do the trick.
#JsonCreator
public static CountryEnum fromString(String value) {
CountryEnumConverter converter = new CountryEnumConverter();
return converter.convert(value);
}

Spring MVC Validation for list and reporting the invalid value

I have a list of strings which should be of a specific format. I need to return the error message with the strings which are not of the format specified. How to do this with spring validation(I am using the hibernate validator).
The annotation:
#Documented
#Retention(RUNTIME)
#Target({FIELD, METHOD})
#Constraint(validatedBy = HostsValidator.class)
public #interface HostsConstraint {
String message();
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
The implementation:
public class HostsValidator implements ConstraintValidator<HostsConstraint, List<String>>{
#Override
public void initialize(OriginHostsConstraint constraintAnnotation) {
}
#Override
public boolean isValid(List<String> strings, ConstraintValidatorContext context) {
for (String s : strings) {
if (!s.matches("[0-9]+") {
//How do I say: Invalid string <s> ?
return false;
}
}
}
}
The usage:
public class Test {
#HostsConstraint(message="Invalid string ")
private List<String> hosts;
}
Using validatedValue will give the entire list.
Use JSR 380 validation, it allows container element constraints.
Here is a link to the container element section in the Hibernate Validator 6.0.6.FINAL Document
I think I found a solution but it is coupled to hibernate validator. May be it is even a hacky implementation.
The usage:
public class Test {
#HostsConstraint(message="Invalid string : ${invalidStr}")
private List<String> hosts;
}
The implementation
public class HostsValidator implements ConstraintValidator<HostsConstraint, List<String>>{
#Override
public void initialize(OriginHostsConstraint constraintAnnotation) {}
#Override
public boolean isValid(List<String> strings, ConstraintValidatorContext context) {
for (String s : strings) {
if (!s.matches("[0-9]+") {
ConstraintValidatorContextImpl contextImpl =
(ConstraintValidatorContextImpl) context
.unwrap(HibernateConstraintValidatorContext.class);
contextImpl.addExpressionVariable("invalidStr", s);
return false;
}
}
}
}

Cross field validation with Hibernate Validator (JSR 303)

Is there an implementation of (or third-party implementation for) cross field validation in Hibernate Validator 4.x? If not, what is the cleanest way to implement a cross field validator?
As an example, how can you use the API to validate two bean properties are equal (such as validating a password field matches the password verify field).
In annotations, I'd expect something like:
public class MyBean {
#Size(min=6, max=50)
private String pass;
#Equals(property="pass")
private String passVerify;
}
Each field constraint should be handled by a distinct validator annotation, or in other words it's not suggested practice to have one field's validation annotation checking against other fields; cross-field validation should be done at the class level. Additionally, the JSR-303 Section 2.2 preferred way to express multiple validations of the same type is via a list of annotations. This allows the error message to be specified per match.
For example, validating a common form:
#FieldMatch.List({
#FieldMatch(first = "password", second = "confirmPassword", message = "The password fields must match"),
#FieldMatch(first = "email", second = "confirmEmail", message = "The email fields must match")
})
public class UserRegistrationForm {
#NotNull
#Size(min=8, max=25)
private String password;
#NotNull
#Size(min=8, max=25)
private String confirmPassword;
#NotNull
#Email
private String email;
#NotNull
#Email
private String confirmEmail;
}
The Annotation:
package constraints;
import constraints.impl.FieldMatchValidator;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.TYPE;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Target;
/**
* Validation annotation to validate that 2 fields have the same value.
* An array of fields and their matching confirmation fields can be supplied.
*
* Example, compare 1 pair of fields:
* #FieldMatch(first = "password", second = "confirmPassword", message = "The password fields must match")
*
* Example, compare more than 1 pair of fields:
* #FieldMatch.List({
* #FieldMatch(first = "password", second = "confirmPassword", message = "The password fields must match"),
* #FieldMatch(first = "email", second = "confirmEmail", message = "The email fields must match")})
*/
#Target({TYPE, ANNOTATION_TYPE})
#Retention(RUNTIME)
#Constraint(validatedBy = FieldMatchValidator.class)
#Documented
public #interface FieldMatch
{
String message() default "{constraints.fieldmatch}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
/**
* #return The first field
*/
String first();
/**
* #return The second field
*/
String second();
/**
* Defines several <code>#FieldMatch</code> annotations on the same element
*
* #see FieldMatch
*/
#Target({TYPE, ANNOTATION_TYPE})
#Retention(RUNTIME)
#Documented
#interface List
{
FieldMatch[] value();
}
}
The Validator:
package constraints.impl;
import constraints.FieldMatch;
import org.apache.commons.beanutils.BeanUtils;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class FieldMatchValidator implements ConstraintValidator<FieldMatch, Object>
{
private String firstFieldName;
private String secondFieldName;
#Override
public void initialize(final FieldMatch constraintAnnotation)
{
firstFieldName = constraintAnnotation.first();
secondFieldName = constraintAnnotation.second();
}
#Override
public boolean isValid(final Object value, final ConstraintValidatorContext context)
{
try
{
final Object firstObj = BeanUtils.getProperty(value, firstFieldName);
final Object secondObj = BeanUtils.getProperty(value, secondFieldName);
return firstObj == null && secondObj == null || firstObj != null && firstObj.equals(secondObj);
}
catch (final Exception ignore)
{
// ignore
}
return true;
}
}
I suggest you another possible solution. Perhaps less elegant, but easier!
public class MyBean {
#Size(min=6, max=50)
private String pass;
private String passVerify;
#NotNull
private LocalDate passExpiry;
#NotNull
private LocalDate dateOfJoining;
#AssertTrue(message = "Fields `pass` and `passVerify` should be equal")
// Any method name is ok als long it begins with `is`
private boolean isValidPass() {
//return pass == null && passVerify == null || pass.equals(passVerify);
// Since Java 7:
return Objects.equals(pass, passVerify);
}
#AssertTrue(message = "Field `passExpiry` should be later than `dateOfJoining`")
// Other rules can also be validated in other methods
private boolean isPassExpiryAfterDateOfJoining() {
return dateOfJoining.isBefore(passExpiry);
}
}
The isValid() and isPassExpiryAfterDateOfJoining() methods are invoked automatically by the validator. The property paths reported in the ConstraintViolations will be extracted from the method names: valid and passExpiryAfterDateOfJoining.
I'm surprised this isn't available out of the box. Anyway, here is a possible solution.
I've created a class level validator, not the field level as described in the original question.
Here is the annotation code:
package com.moa.podium.util.constraints;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
#Target({TYPE, ANNOTATION_TYPE})
#Retention(RUNTIME)
#Constraint(validatedBy = MatchesValidator.class)
#Documented
public #interface Matches {
String message() default "{com.moa.podium.util.constraints.matches}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String field();
String verifyField();
}
And the validator itself:
package com.moa.podium.util.constraints;
import org.mvel2.MVEL;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class MatchesValidator implements ConstraintValidator<Matches, Object> {
private String field;
private String verifyField;
public void initialize(Matches constraintAnnotation) {
this.field = constraintAnnotation.field();
this.verifyField = constraintAnnotation.verifyField();
}
public boolean isValid(Object value, ConstraintValidatorContext context) {
Object fieldObj = MVEL.getProperty(field, value);
Object verifyFieldObj = MVEL.getProperty(verifyField, value);
boolean neitherSet = (fieldObj == null) && (verifyFieldObj == null);
if (neitherSet) {
return true;
}
boolean matches = (fieldObj != null) && fieldObj.equals(verifyFieldObj);
if (!matches) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate("message")
.addNode(verifyField)
.addConstraintViolation();
}
return matches;
}
}
Note that I've used MVEL to inspect the properties of the object being validated. This could be replaced with the standard reflection APIs or if it is a specific class you are validating, the accessor methods themselves.
The #Matches annotation can then be used used on a bean as follows:
#Matches(field="pass", verifyField="passRepeat")
public class AccountCreateForm {
#Size(min=6, max=50)
private String pass;
private String passRepeat;
...
}
As a disclaimer, I wrote this in the last 5 minutes, so I probably haven't ironed out all the bugs yet. I'll update the answer if anything goes wrong.
With Hibernate Validator 4.1.0.Final I recommend using #ScriptAssert. Exceprt from its JavaDoc:
Script expressions can be written in any scripting or expression
language, for which a JSR 223 ("Scripting for the JavaTM Platform")
compatible engine can be found on the classpath.
Note: the evaluation is being performed by a scripting "engine" running in the Java VM, therefore on Java "server side", not on "client side" as stated in some comments.
Example:
#ScriptAssert(lang = "javascript", script = "_this.passVerify.equals(_this.pass)")
public class MyBean {
#Size(min=6, max=50)
private String pass;
private String passVerify;
}
or with shorter alias and null-safe:
#ScriptAssert(lang = "javascript", alias = "_",
script = "_.passVerify != null && _.passVerify.equals(_.pass)")
public class MyBean {
#Size(min=6, max=50)
private String pass;
private String passVerify;
}
or with Java 7+ null-safe Objects.equals():
#ScriptAssert(lang = "javascript", script = "Objects.equals(_this.passVerify, _this.pass)")
public class MyBean {
#Size(min=6, max=50)
private String pass;
private String passVerify;
}
Nevertheless, there is nothing wrong with a custom class level validator #Matches solution.
Cross fields validations can be done by creating custom constraints.
Example:- Compare password and confirmPassword fields of User instance.
CompareStrings
#Target({TYPE})
#Retention(RUNTIME)
#Constraint(validatedBy=CompareStringsValidator.class)
#Documented
public #interface CompareStrings {
String[] propertyNames();
StringComparisonMode matchMode() default EQUAL;
boolean allowNull() default false;
String message() default "";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
StringComparisonMode
public enum StringComparisonMode {
EQUAL, EQUAL_IGNORE_CASE, NOT_EQUAL, NOT_EQUAL_IGNORE_CASE
}
CompareStringsValidator
public class CompareStringsValidator implements ConstraintValidator<CompareStrings, Object> {
private String[] propertyNames;
private StringComparisonMode comparisonMode;
private boolean allowNull;
#Override
public void initialize(CompareStrings constraintAnnotation) {
this.propertyNames = constraintAnnotation.propertyNames();
this.comparisonMode = constraintAnnotation.matchMode();
this.allowNull = constraintAnnotation.allowNull();
}
#Override
public boolean isValid(Object target, ConstraintValidatorContext context) {
boolean isValid = true;
List<String> propertyValues = new ArrayList<String> (propertyNames.length);
for(int i=0; i<propertyNames.length; i++) {
String propertyValue = ConstraintValidatorHelper.getPropertyValue(String.class, propertyNames[i], target);
if(propertyValue == null) {
if(!allowNull) {
isValid = false;
break;
}
} else {
propertyValues.add(propertyValue);
}
}
if(isValid) {
isValid = ConstraintValidatorHelper.isValid(propertyValues, comparisonMode);
}
if (!isValid) {
/*
* if custom message was provided, don't touch it, otherwise build the
* default message
*/
String message = context.getDefaultConstraintMessageTemplate();
message = (message.isEmpty()) ? ConstraintValidatorHelper.resolveMessage(propertyNames, comparisonMode) : message;
context.disableDefaultConstraintViolation();
ConstraintViolationBuilder violationBuilder = context.buildConstraintViolationWithTemplate(message);
for (String propertyName : propertyNames) {
NodeBuilderDefinedContext nbdc = violationBuilder.addNode(propertyName);
nbdc.addConstraintViolation();
}
}
return isValid;
}
}
ConstraintValidatorHelper
public abstract class ConstraintValidatorHelper {
public static <T> T getPropertyValue(Class<T> requiredType, String propertyName, Object instance) {
if(requiredType == null) {
throw new IllegalArgumentException("Invalid argument. requiredType must NOT be null!");
}
if(propertyName == null) {
throw new IllegalArgumentException("Invalid argument. PropertyName must NOT be null!");
}
if(instance == null) {
throw new IllegalArgumentException("Invalid argument. Object instance must NOT be null!");
}
T returnValue = null;
try {
PropertyDescriptor descriptor = new PropertyDescriptor(propertyName, instance.getClass());
Method readMethod = descriptor.getReadMethod();
if(readMethod == null) {
throw new IllegalStateException("Property '" + propertyName + "' of " + instance.getClass().getName() + " is NOT readable!");
}
if(requiredType.isAssignableFrom(readMethod.getReturnType())) {
try {
Object propertyValue = readMethod.invoke(instance);
returnValue = requiredType.cast(propertyValue);
} catch (Exception e) {
e.printStackTrace(); // unable to invoke readMethod
}
}
} catch (IntrospectionException e) {
throw new IllegalArgumentException("Property '" + propertyName + "' is NOT defined in " + instance.getClass().getName() + "!", e);
}
return returnValue;
}
public static boolean isValid(Collection<String> propertyValues, StringComparisonMode comparisonMode) {
boolean ignoreCase = false;
switch (comparisonMode) {
case EQUAL_IGNORE_CASE:
case NOT_EQUAL_IGNORE_CASE:
ignoreCase = true;
}
List<String> values = new ArrayList<String> (propertyValues.size());
for(String propertyValue : propertyValues) {
if(ignoreCase) {
values.add(propertyValue.toLowerCase());
} else {
values.add(propertyValue);
}
}
switch (comparisonMode) {
case EQUAL:
case EQUAL_IGNORE_CASE:
Set<String> uniqueValues = new HashSet<String> (values);
return uniqueValues.size() == 1 ? true : false;
case NOT_EQUAL:
case NOT_EQUAL_IGNORE_CASE:
Set<String> allValues = new HashSet<String> (values);
return allValues.size() == values.size() ? true : false;
}
return true;
}
public static String resolveMessage(String[] propertyNames, StringComparisonMode comparisonMode) {
StringBuffer buffer = concatPropertyNames(propertyNames);
buffer.append(" must");
switch(comparisonMode) {
case EQUAL:
case EQUAL_IGNORE_CASE:
buffer.append(" be equal");
break;
case NOT_EQUAL:
case NOT_EQUAL_IGNORE_CASE:
buffer.append(" not be equal");
break;
}
buffer.append('.');
return buffer.toString();
}
private static StringBuffer concatPropertyNames(String[] propertyNames) {
//TODO improve concating algorithm
StringBuffer buffer = new StringBuffer();
buffer.append('[');
for(String propertyName : propertyNames) {
char firstChar = Character.toUpperCase(propertyName.charAt(0));
buffer.append(firstChar);
buffer.append(propertyName.substring(1));
buffer.append(", ");
}
buffer.delete(buffer.length()-2, buffer.length());
buffer.append("]");
return buffer;
}
}
User
#CompareStrings(propertyNames={"password", "confirmPassword"})
public class User {
private String password;
private String confirmPassword;
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public String getConfirmPassword() { return confirmPassword; }
public void setConfirmPassword(String confirmPassword) { this.confirmPassword = confirmPassword; }
}
Test
public void test() {
User user = new User();
user.setPassword("password");
user.setConfirmPassword("paSSword");
Set<ConstraintViolation<User>> violations = beanValidator.validate(user);
for(ConstraintViolation<User> violation : violations) {
logger.debug("Message:- " + violation.getMessage());
}
Assert.assertEquals(violations.size(), 1);
}
Output Message:- [Password, ConfirmPassword] must be equal.
By using the CompareStrings validation constraint, we can also compare more than two properties and we can mix any of four string comparison methods.
ColorChoice
#CompareStrings(propertyNames={"color1", "color2", "color3"}, matchMode=StringComparisonMode.NOT_EQUAL, message="Please choose three different colors.")
public class ColorChoice {
private String color1;
private String color2;
private String color3;
......
}
Test
ColorChoice colorChoice = new ColorChoice();
colorChoice.setColor1("black");
colorChoice.setColor2("white");
colorChoice.setColor3("white");
Set<ConstraintViolation<ColorChoice>> colorChoiceviolations = beanValidator.validate(colorChoice);
for(ConstraintViolation<ColorChoice> violation : colorChoiceviolations) {
logger.debug("Message:- " + violation.getMessage());
}
Output Message:- Please choose three different colors.
Similarly, we can have CompareNumbers, CompareDates, etc cross-fields validation constraints.
P.S. I have not tested this code under production environment (though I tested it under dev environment), so consider this code as Milestone Release. If you find a bug, please write a nice comment. :)
If you’re using the Spring Framework then you can use the Spring Expression Language (SpEL) for that. I’ve wrote a small library that provides JSR-303 validator based on SpEL – it makes cross-field validations a breeze! Take a look at https://github.com/jirutka/validator-spring.
This will validate length and equality of the password fields.
#SpELAssert(value = "pass.equals(passVerify)",
message = "{validator.passwords_not_same}")
public class MyBean {
#Size(min = 6, max = 50)
private String pass;
private String passVerify;
}
You can also easily modify this to validate the password fields only when not both empty.
#SpELAssert(value = "pass.equals(passVerify)",
applyIf = "pass || passVerify",
message = "{validator.passwords_not_same}")
public class MyBean {
#Size(min = 6, max = 50)
private String pass;
private String passVerify;
}
I have tried Alberthoven's example (hibernate-validator 4.0.2.GA) and i get an ValidationException: „Annotated methods must follow the JavaBeans naming convention. match() does not.“ too. After I renamed the method from „match“ to "isValid" it works.
public class Password {
private String password;
private String retypedPassword;
public Password(String password, String retypedPassword) {
super();
this.password = password;
this.retypedPassword = retypedPassword;
}
#AssertTrue(message="password should match retyped password")
private boolean isValid(){
if (password == null) {
return retypedPassword == null;
} else {
return password.equals(retypedPassword);
}
}
public String getPassword() {
return password;
}
public String getRetypedPassword() {
return retypedPassword;
}
}
I like the idea from Jakub Jirutka to use Spring Expression Language. If you don't want to add another library/dependency (assuming that you already use Spring), here is a simplified implementation of his idea.
The constraint:
#Constraint(validatedBy=ExpressionAssertValidator.class)
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface ExpressionAssert {
String message() default "expression must evaluate to true";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String value();
}
The validator:
public class ExpressionAssertValidator implements ConstraintValidator<ExpressionAssert, Object> {
private Expression exp;
public void initialize(ExpressionAssert annotation) {
ExpressionParser parser = new SpelExpressionParser();
exp = parser.parseExpression(annotation.value());
}
public boolean isValid(Object value, ConstraintValidatorContext context) {
return exp.getValue(value, Boolean.class);
}
}
Apply like this:
#ExpressionAssert(value="pass == passVerify", message="passwords must be same")
public class MyBean {
#Size(min=6, max=50)
private String pass;
private String passVerify;
}
I made a small adaptation in Nicko's solution so that it is not necessary to use the Apache Commons BeanUtils library and replace it with the solution already available in spring, for those using it as I can be simpler:
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.PropertyAccessorFactory;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class FieldMatchValidator implements ConstraintValidator<FieldMatch, Object> {
private String firstFieldName;
private String secondFieldName;
#Override
public void initialize(final FieldMatch constraintAnnotation) {
firstFieldName = constraintAnnotation.first();
secondFieldName = constraintAnnotation.second();
}
#Override
public boolean isValid(final Object object, final ConstraintValidatorContext context) {
BeanWrapper beanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(object);
final Object firstObj = beanWrapper.getPropertyValue(firstFieldName);
final Object secondObj = beanWrapper.getPropertyValue(secondFieldName);
boolean isValid = firstObj == null && secondObj == null || firstObj != null && firstObj.equals(secondObj);
if (!isValid) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate())
.addPropertyNode(firstFieldName)
.addConstraintViolation();
}
return isValid;
}
}
I don't have the reputation for commenting on the first answer but wanted to add that I have added unit tests for the winning answer and have the following observations:
If you get the first or field names wrong then you get a validation error as though the values don't match. Don't get tripped up by spelling mistakes e.g.
#FieldMatch(first="invalidFieldName1", second="validFieldName2")
The validator will accept equivalent data types i.e. these will all pass with FieldMatch:
private String stringField = "1";
private Integer integerField = new Integer(1)
private int intField = 1;
If the fields are of an object type which does not implement equals, the validation will fail.
Very nice solution bradhouse. Is there any way to apply the #Matches annotation to more than one field?
EDIT:
Here's the solution I came up with to answer this question, I modified the Constraint to accept an array instead of a single value:
#Matches(fields={"password", "email"}, verifyFields={"confirmPassword", "confirmEmail"})
public class UserRegistrationForm {
#NotNull
#Size(min=8, max=25)
private String password;
#NotNull
#Size(min=8, max=25)
private String confirmPassword;
#NotNull
#Email
private String email;
#NotNull
#Email
private String confirmEmail;
}
The code for the annotation:
package springapp.util.constraints;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
#Target({TYPE, ANNOTATION_TYPE})
#Retention(RUNTIME)
#Constraint(validatedBy = MatchesValidator.class)
#Documented
public #interface Matches {
String message() default "{springapp.util.constraints.matches}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String[] fields();
String[] verifyFields();
}
And the implementation:
package springapp.util.constraints;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import org.apache.commons.beanutils.BeanUtils;
public class MatchesValidator implements ConstraintValidator<Matches, Object> {
private String[] fields;
private String[] verifyFields;
public void initialize(Matches constraintAnnotation) {
fields = constraintAnnotation.fields();
verifyFields = constraintAnnotation.verifyFields();
}
public boolean isValid(Object value, ConstraintValidatorContext context) {
boolean matches = true;
for (int i=0; i<fields.length; i++) {
Object fieldObj, verifyFieldObj;
try {
fieldObj = BeanUtils.getProperty(value, fields[i]);
verifyFieldObj = BeanUtils.getProperty(value, verifyFields[i]);
} catch (Exception e) {
//ignore
continue;
}
boolean neitherSet = (fieldObj == null) && (verifyFieldObj == null);
if (neitherSet) {
continue;
}
boolean tempMatches = (fieldObj != null) && fieldObj.equals(verifyFieldObj);
if (!tempMatches) {
addConstraintViolation(context, fields[i]+ " fields do not match", verifyFields[i]);
}
matches = matches?tempMatches:matches;
}
return matches;
}
private void addConstraintViolation(ConstraintValidatorContext context, String message, String field) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(message).addNode(field).addConstraintViolation();
}
}
You need to call it explicitly. In the example above, bradhouse has given you all the steps to write a custom constraint.
Add this code in your caller class.
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
Set<ConstraintViolation<yourObjectClass>> constraintViolations = validator.validate(yourObject);
in the above case it would be
Set<ConstraintViolation<AccountCreateForm>> constraintViolations = validator.validate(objAccountCreateForm);
Why not try Oval: http://oval.sourceforge.net/
I looks like it supports OGNL so maybe you could do it by a more natural
#Assert(expr = "_value ==_this.pass").
You guys are awesome. Really amazing ideas. I like Alberthoven's and McGin's most, so I decided to combine both ideas. And develop some generic solution to cater all cases. Here is my proposed solution.
#Documented
#Constraint(validatedBy = NotFalseValidator.class)
#Target({ElementType.METHOD, ElementType.FIELD,ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface NotFalse {
String message() default "NotFalse";
String[] messages();
String[] properties();
String[] verifiers();
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class NotFalseValidator implements ConstraintValidator<NotFalse, Object> {
private String[] properties;
private String[] messages;
private String[] verifiers;
#Override
public void initialize(NotFalse flag) {
properties = flag.properties();
messages = flag.messages();
verifiers = flag.verifiers();
}
#Override
public boolean isValid(Object bean, ConstraintValidatorContext cxt) {
if(bean == null) {
return true;
}
boolean valid = true;
BeanWrapper beanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(bean);
for(int i = 0; i< properties.length; i++) {
Boolean verified = (Boolean) beanWrapper.getPropertyValue(verifiers[i]);
valid &= isValidProperty(verified,messages[i],properties[i],cxt);
}
return valid;
}
boolean isValidProperty(Boolean flag,String message, String property, ConstraintValidatorContext cxt) {
if(flag == null || flag) {
return true;
} else {
cxt.disableDefaultConstraintViolation();
cxt.buildConstraintViolationWithTemplate(message)
.addPropertyNode(property)
.addConstraintViolation();
return false;
}
}
}
#NotFalse(
messages = {"End Date Before Start Date" , "Start Date Before End Date" } ,
properties={"endDateTime" , "startDateTime"},
verifiers = {"validDateRange" , "validDateRange"})
public class SyncSessionDTO implements ControllableNode {
#NotEmpty #NotPastDate
private Date startDateTime;
#NotEmpty
private Date endDateTime;
public Date getStartDateTime() {
return startDateTime;
}
public void setStartDateTime(Date startDateTime) {
this.startDateTime = startDateTime;
}
public Date getEndDateTime() {
return endDateTime;
}
public void setEndDateTime(Date endDateTime) {
this.endDateTime = endDateTime;
}
public Boolean getValidDateRange(){
if(startDateTime != null && endDateTime != null) {
return startDateTime.getTime() <= endDateTime.getTime();
}
return null;
}
}
Solution realated with question:
How to access a field which is described in annotation property
#Target(ElementType.FIELD)
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface Match {
String field();
String message() default "";
}
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = MatchValidator.class)
#Documented
public #interface EnableMatchConstraint {
String message() default "Fields must match!";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class MatchValidator implements ConstraintValidator<EnableMatchConstraint, Object> {
#Override
public void initialize(final EnableMatchConstraint constraint) {}
#Override
public boolean isValid(final Object o, final ConstraintValidatorContext context) {
boolean result = true;
try {
String mainField, secondField, message;
Object firstObj, secondObj;
final Class<?> clazz = o.getClass();
final Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(Match.class)) {
mainField = field.getName();
secondField = field.getAnnotation(Match.class).field();
message = field.getAnnotation(Match.class).message();
if (message == null || "".equals(message))
message = "Fields " + mainField + " and " + secondField + " must match!";
firstObj = BeanUtils.getProperty(o, mainField);
secondObj = BeanUtils.getProperty(o, secondField);
result = firstObj == null && secondObj == null || firstObj != null && firstObj.equals(secondObj);
if (!result) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(message).addPropertyNode(mainField).addConstraintViolation();
break;
}
}
}
} catch (final Exception e) {
// ignore
//e.printStackTrace();
}
return result;
}
}
And how to use it...? Like this:
#Entity
#EnableMatchConstraint
public class User {
#NotBlank
private String password;
#Match(field = "password")
private String passwordConfirmation;
}

Resources