Hibernate Validator : Using if - else kind of logic in annotation - java

I am using Hibernate validator like #NotEmpty to see if a specific property in a class is empty or not. The class is as as shown:
#Entity
#Table(name="emergency_messages")
public class EmergencyMessages implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="id", nullable=false)
private Integer id;
#NotEmpty(message="Home message cannot be empty")
#Column(name="home_page_message")
private String homePageMessage;
#Range(min=0, max=1, message="Please select one of the display announcement value")
#Column(name="messages_enabled")
private Integer messagesEnabled;
}
So far so good. Whenever the property "homePageMessage" is empty I can see that the correct error message in the form in the browser.
Now the situation has changed. The new requirement is that the property "homePageMessage" can be empty only if the other property "messagesEnabled" is set to 1. If it is set to 0 then there should be no empty check done for "homePageMessage". In simple words the validation of "homePageMessage" should now be dependent on the "messagesEnabled" value.
My question: Is this possible to do with annotations? If not, then I will have to dismantle my hibernate validator mechanism and create my own validation class.

I think you need to write custom annotation to achieve this. Also you can use other hibernate validation constraint with custom annotation, no need to remove anything.
Check this link for details.

Following is the code that I came up with (after suggestions from Ajinkya and Alex):
Customized Annotation:
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Constraint(validatedBy=HomePageEmptyMessageValidator.class)
public #interface HomePageEmptyMessage {
String message() default "";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Customized Validator:
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class HomePageEmptyMessageValidator implements ConstraintValidator<HomePageEmptyMessage, EmergencyMessages> {
#Override
public void initialize(HomePageEmptyMessage homePageEmptyMessage) {
}
#Override
public boolean isValid(EmergencyMessages emergencyMessages, ConstraintValidatorContext context) {
if (emergencyMessages == null) {
return false;
}
Integer messageEnabled = emergencyMessages.getMessagesEnabled();
if (messageEnabled != null) {
if (messageEnabled == 1) {
String homePageMessage = emergencyMessages.getHomePageMessage();
if (Util.isNullOrEmpty(homePageMessage)) {
return false;
} else {
return true;
}
} else {
return true;
}
}
return false;
}
}
Usage of customized annotation in the code:
#Entity
#Table(name="emergency_messages")
#HomePageEmptyMessage(message="Home page annoucement cannot be empty if the Display Announcement is set to Yes")
public class EmergencyMessages implements Serializable {
private static final long serialVersionUID = -7870767517772161300L;
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="id", nullable=false)
private Integer id;
#Column(name="home_page_message")
private String homePageMessage;
#Range(min=0, max=1, message="Please select one of the display announcement value")
#Column(name="messages_enabled")
private Integer messagesEnabled;
}
I hope it helps someone.

What you need is a ConstraintValidator implementation for your entity, using the #Constraint annotation on it.
This is where you will put conditions on fields that depends on other ones. Constraints using annotations on field are supposed to be used for check that can be made on the field itself, and not depending on another ones (like max size, nullable, etc...).

Related

Spring Boot JPA Custom validation null point error for password confirmation

I made a registration form and I want to validate all filed of the form I validated expect one field to match the fields of PASSWORD matching so make custom validtion but is not working i attaced code in
#Entity
public class Userlist {
......
#Size(min = 8, message = "Please enter atleast 8 digit password")
private String userpassword;
#PasswordMatch(message="Your Password is not match with created password")
private String confirmpassword;
}
package com.picture.picturesalbum.anotation;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.TYPE_USE;
import java.lang.annotation.*;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
#Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Constraint(validatedBy = PasswordMatchValidator.class)
public #interface PasswordMatch {
public String message() default "Your Password is not match with created password ";
public Class<?>[] groups() default {};
public Class<? extends Payload>[] payload() default {};
}
package com.picture.picturesalbum.anotation;
import com.picture.picturesalbum.model.Userlist;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
public class PasswordMatchValidator implements ConstraintValidator<PasswordMatch, String> {
Userlist userlist = new Userlist();
public boolean isValid(String value, ConstraintValidatorContext context) {
// Userlist userlist = new Userlist();
if (value.contentEquals(userlist.getUserpassword())) {
return true;
} else {
return false;
}
}
}
Error is
at java.base/java.lang.Thread.run(Thread.java:1589)
Caused by: java.lang.NullPointerException: Cannot invoke "java.lang.CharSequence.length()" because "cs" is null
If you want to validate two fields of a class, you have to use a custom validator with a class level constraint and then compare both values in the validator class.
Check this answer for more information.
Another solution is to define a method that must validate to true and put the #AssertTrue annotation on the top of it:
#AssertTrue
private boolean isEqual() {
return userpassword.equals(confirmPassword);
}

How to register a custom constraint validator

My email string field is annotated with javax.validation.constraints.Email:
import javax.validation.constraints.Email;
public class MyModel {
#Email
private String email;
}
Currently hibernate-validator's org.hibernate.validator.internal.constraintvalidators.AbstractEmailValidator automatically validates it when the corresponding api endpoint is called:
#Controller("/v1")
#Validated
public class MyController {
...
#Put(uri = "/{id}")
#Produces("application/json")
public IdType updateEntity(#Valid MyModel model) {
return delegate.updateEntity(model);
}
}
How can I register my custom class to validate the email field with Micronaut's validator, while still using the original javax.validation.constraints.Email annotation?
This is what my custom constraint validator looks like:
import javax.inject.Singleton;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.constraints.Email;
#Singleton
public class EmailValidator implements ConstraintValidator<Email, String> {
#Override
public void initialize(Email constraintAnnotation) {}
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
return value.contains("#");
}
}
Micronaut version: 1.1.4
It seems that what you are looking for is this:
HibernateValidatorConfiguration configuration = Validation
.byProvider( HibernateValidator.class )
.configure();
ConstraintMapping constraintMapping = configuration.createConstraintMapping();
constraintMapping
.constraintDefinition( Email.class )
.includeExistingValidators( false )
.validatedBy( MyEmailValidator.class );
configuration.addMapping( constraintMapping );
That's a programmatic way of "replacing" the existing validator for existing constraint. For more details on this check this part of the doc
Change your validator from #Email to #MailAdress or any non-standard annotation.
Everyone expects #Email as an import from Javax / Hibernate.
If you want to add a behaviour you may also check #Pattern (as dot is not checked in #Email annotation)

Spring DTO validation using ConstraintValidator

The DTO that I use is annotated with javax.validation annotations
For example
#JsonIgnoreProperties(ignoreUnknown = true)
public class StudentDTO {
#NotEmpty
private String name;
#Positive
private Long studentId;
}
What if I have to validate using ConstraintValidator for StudentDTO
Spring MVC has the ability to automatically validate #Controller
inputs. In previous versions it was up to the developer to manually
invoke validation logic.
But in your case , you are trying to validate a DTO object in which case , springboot might not be automatically binding your validator to your model and call the validator.So, in that case, you will need to manually bind the object to the validator.
or you can manually invoke the validator on a bean like :
#AutoWired
Validator validator;
...
validator.validate(book);
You can define a custom validator in springboot for model classes if you want and use annotations :
#Documented
#Constraint(validatedBy = CustomDataValidator.class)
#Target( { ElementType.METHOD, ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
public #interface CustomDataConstraint {
String message() default "Invalid data";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
and then define a validator class like :
public class CustomDataValidator implements
ConstraintValidator<CustomDataConstraint, String> {
#Override
public void initialize(CustomDataConstraint data) {
}
#Override
public boolean isValid(String field,
ConstraintValidatorContext cxt) {
return field!= null;
}
}
Your validator class must implement the ConstraintValidator interface and must implement the isValid method to define the validation rules, define the validation rules can be anything as you wish.Then, you can simply add the annotation to your field like :
#CustomDataConstraint
private String name;

How to validate 2 field with OR condition?

I want to validate two fields of a Request Class in manner that Either one field is valid OR another field is valid.
Eg:
Request Bean
public class CarRequest {
#NotEmpty
private String customerName;
#NotEmpty
private String customerId;
Controller Method
public #ResponseBody CarResponse addCar(
#ModelAttribute #Valid CarRequest request, BindingResult results)
throws RuntimeException, ValidationException {
if (results.hasErrors()) {
LOG.error("error occured while adding the car");
throw new ValidationException(
"Error Occoured while validiating car request");
}
}
Here I want to check that either customerName should be NotEmpty OR customerId should be NotEmpty. then my validation should pass. How can I implement it . Please suggest!!
You need to create custom validator to validate multiple fields.
create a custom annotation:
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
#Documented
#Constraint(validatedBy = CarRequestValidator.class)
#Target({ ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface RequestAnnotation {
String message() default "{RequestAnnotation}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
create a custom validator:
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class CarRequestValidator implements
ConstraintValidator<RequestAnnotation, CarRequest> {
#Override
public void initialize(RequestAnnotation constraintAnnotation) {
}
#Override
public boolean isValid(CarRequest value, ConstraintValidatorContext context) {
// validation logic goes here
return false;
}
}
Now, annotate your model with custom annotation:
#RequestAnnotation
public class CarRequest {
private String customerName;
private String customerId;
}

Spring IP address validation

I'm looking for a possiblity to validate IP addresses in my Spring roo project.
My entity looks like this
package com.ip.test.domain;
import javax.persistence.ManyToOne;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import org.springframework.roo.addon.javabean.RooJavaBean;
import org.springframework.roo.addon.jpa.activerecord.RooJpaActiveRecord;
import org.springframework.roo.addon.tostring.RooToString;
#RooJavaBean
#RooToString
#RooJpaActiveRecord
public class IP {
#NotNull
#Size(min = 7, max = 15)
private String ip;
#ManyToOne
private Hoster Hoster;
}
With this setup it validates only if the string contains 7 to 15 characters, but not really if it's an IP address.
Something like
#validIpAddress
private String ip;
would be nice.
Any idea if that's possible?
You can use the JSR 303 Pattern validator, with an IP address regex:
#NotNull
#Pattern(regexp = "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$")
private String ip;
edit: escape backslash
Definitely possible. You will need to code a custom annotation and implementation class. Not too much effort. See here for background: http://docs.jboss.org/hibernate/validator/5.0/reference/en-US/html_single/#validator-customconstraints
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE })
#Documented
#Constraint(validatedBy = IpAddressValidator.class)
public #interface IpAddress
{
String message() default "{ipAddress.invalid}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
and
public class IpAddressValidator implements ConstraintValidator<IpAddress, Object>
{
#Override
public void initialize(IpAddress constraintAnnotation)
{
}
#Override
public boolean isValid(Object value, ConstraintValidatorContext cvContext)
{
// logic here
}
}
Essentially you want to use JSR-303 annotations with a custom validator. See a full working example here.

Categories