Spring MVC Validation for list and reporting the invalid value - spring

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

Related

Spring boot entity level custom annotation not working

So I have a type level custom annotation that checks for matching passwords in a user registration form. Even when the validator is returning false, it doesn't throw an error and show the error message. Any help is appreciated!
Entity class. Annotation in question is #ValidPassword
#Entity
#ValidPassword(fields = {"password", "matchingPassword"})
public class User {
private String password;
private String matchingPassword;
Constraintvalidator class
public class PasswordValidator implements ConstraintValidator<ValidPassword, User> {
private String[] fields;
private String message;
#Override
public boolean isValid(User user, ConstraintValidatorContext context) {
if (fields[0]== null || fields[1] == null) {
return false;
}
for ( String temp : fields) {
System.out.println(temp);
}
boolean flag = Pattern.matches("^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!#$%^&*-]).{8,}$", fields[0]);
boolean flag1 = fields[0].equals(fields[1]);
if ( !flag1 ) {
message = "Passwords do not match!";
}
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(message)
.addPropertyNode(fields[0]).addConstraintViolation();
return flag && flag1;
}
//Show default message if no special message is set
#Override
public void initialize(ValidPassword validPassword) {
fields = validPassword.fields();
message = validPassword.message();
}
}
validpassword
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = PasswordValidator.class)
#Documented
public #interface ValidPassword {
String message() default "Please enter at least 8 characters, 1 uppercase letter, 1 lowercase letter, and 1 special character";
String[] fields();
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

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

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.

How do I validate a #QueryParam?

I've got a simple REST resource which accepts a couple of query parameters. I'd like to validate one of these parameters, and came across ConstraintValidator for this purpose. The REST resource expects the query param territoryId to be a UUID, so I'd like to validate that it indeed is a valid UUID.
I've created an #IsValidUUID annotation, and a corresponding IsValidUUIDValidator (which is a ConstraintValidator). With what I have now, nothing gets validated and getSuggestions accepts anything I throw at it. So clearly I'm doing something wrong.
What am I doing wrong?
The REST resource now looks like this :
#Component
#Path("/search")
public class SearchResource extends AbstractResource {
#GET
#Path("/suggestions")
#Produces(MediaType.APPLICATION_XML)
public Response getSuggestions(
#QueryParam("phrase") List<String> phrases,
#IsValidUUID #QueryParam("territoryId") String territoryId) {
[...]
}
}
IsValidUUID
#Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Constraint(validatedBy = {IsValidUUIDValidator.class})
public #interface IsValidUUID {
String message() default "Invalid UUID";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
IsValidUUIDValidator
public class IsValidUUIDValidator implements ConstraintValidator<IsValidUUID, String> {
#Override
public void initialize(IsValidUUID constraintAnnotation) {
}
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
try {
UUID.fromString(value);
return true;
} catch (Exception e) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate("The provided UUID is not valid")
.addConstraintViolation();
return false;
}
}
}
You need to set the supported targets on IsValidUUID, using the following annotation.
#SupportedValidationTarget(ValidationTarget.ANNOTATED_ELEMENT)
or
#SupportedValidationTarget(ValidationTarget.PARAMETERS)
Edit:
Sorry, I wasn't able to make it work either on a RequestParam directly. However, if you can, try creating a POJO that you can bind your request parameters to and annotate the binding field with your constraint instead. This worked for me.
public class MyModel {
#IsValidUUID
private String territoryId;
public String getTerritoryId() {
return territoryId;
}
public void setTerritoryId(String territoryId) {
this.territoryId = territoryId;
}
}
#GET
#Path("/suggestions")
#Produces(MediaType.APPLICATION_XML)
public Response getSuggestions(
#QueryParam("phrase") List<String> phrases,
#Valid #ModelAttribute MyModel myModel) {
[...]
}

Jackson deserialization errorhandling in spring-framework

I'm looking for a clean way to handle Jackson Deserialization errors for REST web requests.
More precisely: I have an Enum in a incoming DTO object, mapped from JSON. But if the user sends a wrong value, a 400 Bad Request is returned. I would like to return a 422 Unprocessable Entity with a correct message.
One option would be to accept a String, and use bean validation. However, it's not possible to pass all enum values as a list to the annotation (not a constant), so I would need to pass all enum values separately and keep them up to date. This will be very error prone over the whole application. I'm looking for a more structural way to handle this.
I solved this by using a String in the DTO and using a public #interface EnumValueas annotation.
The EnumValue:
#ReportAsSingleViolation
#Documented
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = EnumValueValidator.class)
#Target(ElementType.FIELD)
public #interface EnumValue {
Class<? extends Enum> value();
String message() default "The input contains validation errors.";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
The validator:
public class EnumValueValidator implements ConstraintValidator<EnumValue, String> {
private Class<? extends Enum> enumClass;
private String message;
#Override
public void initialize(final EnumValue constraintAnnotation) {
this.enumClass = constraintAnnotation.value();
this.message = constraintAnnotation.message();
}
#Override
public boolean isValid(final String value, final ConstraintValidatorContext context) {
boolean valid = false;
for (final Enum enumValue : enumClass.getEnumConstants()) {
if (enumValue.name().equals(value)) {
valid = true;
}
}
if (!valid) {
context.buildConstraintViolationWithTemplate(message) //
.addConstraintViolation();
}
return valid;
}
}

Spring validator fails looking up value of class level constraint

I'm getting an error with JSR 303 class level validation and spring and I'd like to know if I am setting things up in the correct way.
I'm using validation in spring (4.2.6.RELEASE) using hibernate-validator (5.2.4.Final) with a typical setup inside a spring controller like:
#RequestMapping(value = "/{id}", method = RequestMethod.PUT)
public SomeDto update(#PathVariable Integer id, #Valid #RequestBody SomeDto someDto) {
...
return someDto;
}
This works fine with most setups, but when the target of the validation includes a set of objects that are annotated with a Class level validation the SpringValidatorAdaptor fails when trying to lookup the original value
The following code illustrates the problem:
Target class to validate:
public class Base {
#Valid
Set<MyClass> myClassSet = new HashSet<>();
public Set<MyClass> getMyClassSet() {
return myClassSet;
}
Class with class level validation annotation:
#CheckMyClass
public class MyClass {
String a;
String b;
public MyClass(final String a, final String b) {
this.a = a;
this.b = b;
}
}
Constraint:
#Target({ TYPE, ANNOTATION_TYPE })
#Retention(RUNTIME)
#Constraint(validatedBy = CheckMyClassValidator.class)
#Documented
public #interface CheckMyClass {
String message() default "Invalid class";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
Validator:
public class CheckMyClassValidator implements ConstraintValidator<CheckMyClass, MyClass> {
#Override
public void initialize(final CheckMyClass constraintAnnotation) {
}
#Override
public boolean isValid(final MyClass value, final ConstraintValidatorContext context) {
return false; // want it to fail for testing purposes
}
}
Test class:
#SpringBootApplication
public class SpringValidationTest {
#Bean
public org.springframework.validation.Validator validator() {
return new org.springframework.validation.beanvalidation.LocalValidatorFactoryBean();
}
private void doStandardValidation() {
Base base = createBase();
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<Base>> violations = validator.validate(base);
for (ConstraintViolation<?> violation : violations) {
System.out.println(violation.getMessage());
}
}
private Base createBase() {
Base base = new Base();
base.getMyClassSet().add(new MyClass("a1", "b1"));
// base.getMyClassSet().add(new MyClass("a2", "b2"));
return base;
}
#PostConstruct
private void doSpringValidation() {
Base base = createBase();
org.springframework.validation.Validator validator = validator();
DataBinder binder = new DataBinder(base);
binder.setValidator(validator);
binder.validate();
BindingResult results = binder.getBindingResult();
for (ObjectError error : results.getAllErrors()) {
System.out.println(error.getDefaultMessage());
}
}
public static void main(String[] args) {
(new SpringValidationTest()).doStandardValidation();
System.out.println();
ApplicationContext applicationContext = SpringApplication.run(SpringValidationTest.class);
}
}
The standard validation works fine, but when it is wrapped by the spring validator (as in the typical controller setup) it ends up with an exception (as below) trying to lookup the value of the property:
Caused by: java.lang.NumberFormatException: For input string: ""
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) ~[na:1.8.0_45]
at java.lang.Integer.parseInt(Integer.java:592) ~[na:1.8.0_45]
at java.lang.Integer.parseInt(Integer.java:615) ~[na:1.8.0_45]
at org.springframework.beans.AbstractNestablePropertyAccessor.getPropertyValue(AbstractNestablePropertyAccessor.java:657) ~[spring-beans-4.2.6.RELEASE.jar:4.2.6.RELEASE]
... 37 common frames omitted

Resources