How to implement validation on 2 dependent fields? - spring

Hi everyone iam writing post api in spring boot and i need to implement this validation
i have 2 fields - order type and additional details, order type is enum class which consists of 2 enum's - a and b , suppose if "a" is given as input it will proceed as it is - no validation required, suppose if it's "b" then additional details field must required, this is my requirement --- this is a post call

There are multiple ways to achieve that.
Firstly, javax.validation.constraints package has an #AssertTrue annotation, using which you can define a method in your class with some validation logic, e.g. like this:
#AssertTrue(message = "secondField must not be null if firstField is B")
public boolean isValid() {
return SomeEnum.A.equals(firstField) || secondField != null;
}
Secondly, you can define your own validation annotation - similar to how annotations are implemented in validation package - and a validator that implements ConstraintValidator. E.g. like this:
#ValidMyObject
public class MyObject {
private SomeEnum firstField;
private Object secondField;
// getters, setters, etc.
}
#Target({TYPE,ANNOTATION_TYPE})
#Retention(RUNTIME)
#Constraint(validatedBy = MyObjectValidator.class)
public #interface ValidMyObject {
String message() default "secondField must not be null if firstField is B";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
#Component
public class MyObjectValidator
implements ConstraintValidator<ValidMyObject, MyObject> {
#Override
public boolean isValid(MyObject obj, ConstraintValidatorContext context){
return SomeEnum.A.equals(obj.getFirstField()) || obj.getSecondField != null;
}
}
Note that the #Target annotation defines that this annotation can be used at a TYPE level - on a class, to perform the validation of the entire MyObject object.
Take a look here for details.
P.S. Don't forget about dependencies for validation implementation: for spring-boot the most popular one is 'org.springframework.boot:spring-boot-starter-validation'.

Related

How can I create custom validator on Java List type?

I have one NumberConstraint as follows:
#Constraint(validatedBy = { StringConstraintValidator.class, })
#Documented
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.FIELD, ElementType.PARAMETER, })
public #interface StringConstraint {
String message() default "'${validatedValue}' ist not valid Number. " +
"A String is composed of 7 characters (digits and capital letters). Valid example: WBAVD13.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
and it is validated by StringConstraintValidator as follows:
#Component
public class StringConstraintValidator implements ConstraintValidator<StringConstraint, String> {
private static final String REGEX_String = "^[A-Z0-9]{7}$";
#Override
public void initialize(final StringConstraint annotation) {
// noop
}
#Override
public boolean isValid(final String value, final ConstraintValidatorContext context) {
return isValid(value);
}
public boolean isValid(final String value) {
// Number is not always null Checked via #NotNull
LoggingContext.get().setString(value);
if (value == null) {
return false;
}
return Pattern.compile(REGEX_STRING).matcher(value).matches();
}
}
I can apply this StringConstraint on single field of request object as follows:
#StringConstraint
private String number;
but if my request object contains a List of Strings then how can I use this constraint on entire List or do I have to define new on List type ?? Something like ConstraintValidator<StringConstraint, List ???>
My request object is:
#JsonProperty(value = "items", required = true)
#Schema(description = "List of items.", required = true)
private List<String> items= new ArrayList<>();
So i want to apply my validator on all the strings in the list. How can i apply #StringConstraint on my list ?
Yes, you can add more validators for one constraint by using a comma-separated list of validator classes in the validatedBy attribute of the #Constraint annotation. For example, you can write:
#Constraint(validatedBy = {StringConstraintValidator.class, BlablaValidot.class})
public #interface MyConstraint {
// other attributes
}
Explanation
The #Constraint annotation is used to define a custom constraint annotation that can be applied to fields, methods, classes, etc. The validatedBy attribute specifies one or more classes that implement the ConstraintValidator interface and provide the logic to validate the annotated element. You can use multiple validators for the same constraint if you want to check different aspects or conditions of the value. For example, you can have one validator that checks the length of a string and another that checks the format of a string.
Examples
Here are some examples of custom constraint annotations with multiple validators:
A #PhoneNumber annotation that validates a phone number using two validators: one for the country code and one for the number format.
#Constraint(validatedBy = {CountryCodeValidator.class, PhoneNumberValidator.class})
#Target({ElementType.FIELD, ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
public #interface PhoneNumber {
String message() default "Invalid phone number";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
A #Password annotation that validates a password using three validators: one for the minimum length, one for the maximum length, and one for the presence of special characters.
#Constraint(validatedBy = {MinLengthValidator.class, MaxLengthValidator.class, SpecialCharValidator.class})
#Target({ElementType.FIELD, ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
public #interface Password {
String message() default "Invalid password";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
int minLength() default 8;
int maxLength() default 20;
String specialChars() default "!##$%^&*";
}
A custom validator in Java is a way to define your own rules for validating the data of your objects or parameters. You can use the javax.validation API to create and use custom validators. The API consists of two main components: annotations and validators.
Annotations are used to declare the constraints that you want to apply on your data. They are usually placed on the fields or parameters that you want to validate. You can use the built-in annotations provided by the API, such as #NotNull, #Size, #Pattern, etc., or you can define your own annotations for your custom constraints.
Validators are classes that implement the ConstraintValidator interface and provide the logic for validating the data against the constraints. The interface has two generic parameters: A, which is the annotation type, and T, which is the data type. The interface has two methods: initialize and isValid. The initialize method is used to initialize the validator with the annotation attributes, and the isValid method is used to check if the data is valid or not according to the annotation.
To create a custom validator for a list type, you need to specify the list type as the second generic parameter of the ConstraintValidator interface, and implement the isValid method to iterate over the list elements and validate them individually. You can use any logic that suits your needs, such as checking the length, format, range, etc. of the elements. You can also use other annotations or validators inside your custom validator to reuse the existing validation rules.
Example
Here is an example of how to create a custom validator for a list of strings that checks if each element is a valid number.
Define the annotation
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
#Documented
#Constraint(validatedBy = StringConstraintValidator.class)
#Target({ElementType.FIELD, ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
public #interface NumberConstraint {
String message() default "Invalid number";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Define the validator
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.List;
public class StringConstraintValidator implements ConstraintValidator<NumberConstraint, List<String>> {
#Override
public void initialize(NumberConstraint constraintAnnotation) {
// You can use this method to initialize the validator with the annotation attributes
}
#Override
public boolean isValid(List<String> value, ConstraintValidatorContext context) {
// You can use this method to validate the list elements
if (value == null || value.isEmpty()) {
return true; // You can change this to false if you want to reject null or empty lists
}
for (String s : value) {
try {
Double.parseDouble(s); // Try to parse the string as a double
} catch (NumberFormatException e) {
return false; // If the string is not a valid number, return false
}
}
return true; // If all the strings are valid numbers, return true
}
}
Apply the annotation
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import java.util.List;
public class Request {
#NotNull
#NumberConstraint
private List<String> numbers;
// Constructor, getters, setters, etc.
}
public class Controller {
public void processRequest(#Valid Request request) {
// Do something with the request
}
}
If you want to unit test the method
public void processRequest(#Valid Request request) {
// Do something with the request
}
with an invalid request, then the output should be an exception or an error message, depending on how you handle the validation.
Explanation
The #Valid annotation is used to indicate that the parameter should be validated before entering the method. This means that the request object should have some constraints or rules that define what makes it valid or invalid. For example, the request might have a required field, a maximum length, a specific format, etc.
If the request object does not meet these constraints, then the validation will fail and the method will not be executed. Instead, an exception will be thrown or an error message will be returned, depending on the implementation of the validation mechanism. This is to prevent the method from processing an invalid request that might cause unexpected behavior or errors.
Example
Suppose the Request class has the following constraints:
public class Request {
#NotNull
#Size(min = 1, max = 10)
private String name;
#Email
private String email;
// getters and setters
}
This means that the request object should have a non-null name that is between 1 and 10 characters long, and a valid email address. If we use the javax.validation API to perform the validation, then we can write a unit test like this:
import static org.junit.Assert.*;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import org.junit.Before;
import org.junit.Test;
public class RequestProcessorTest {
private RequestProcessor requestProcessor;
private Validator validator;
#Before
public void setUp() {
requestProcessor = new RequestProcessor();
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
}
#Test
public void testProcessRequestWithInvalidRequest() {
// create an invalid request object
Request request = new Request();
request.setName(""); // empty name
request.setEmail("invalid.com"); // invalid email
// validate the request object
Set<ConstraintViolation<Request>> violations = validator.validate(request);
// assert that there are violations
assertFalse(violations.isEmpty());
// try to process the request
try {
requestProcessor.processRequest(request);
fail("Should throw ConstraintViolationException");
} catch (ConstraintViolationException e) {
// assert that the exception contains the violations
assertEquals(violations, e.getConstraintViolations());
}
}
}

Use a ConstraintValidator to validate an object

I want to run this past my fellow Java hacks. See if this seems to be sane enough to you. There may be a better way to do this but I was thinking: Can I write a dual-purpose ConstraintValidator that can validate a field (an object of some kind) as it is passed into a method and also be used to validate an instance of that object outside of that context while still using annotations? I came up with the following approach. Let me know what you think of it. Use case:
...
#POST
public Response retrieveSomething(#Encoded #IsAParamValid MyParamObject myParamObject)
{
...
}
I also want to be able to do this somewhere else in code:
IsAParamValidValidator.validate(myParamObject);
without duplicating the logic to do the validation. Critical here is I want the automatic exception-generating service that using #IsAParamValid in a method call gets me.
My idea of a way to get the best of both worlds:
My annotation class:
#Target({ElementType.FIELD, ElementType.PARAMETER})
#Retention(value = RetentionPolicy.RUNTIME)
#Constraint(validatedBy = {IsAParamValidValidator.class})
#Documented
public #interface IsAParamValid
{
String message() default "The param was invalid";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
My validator class:
public class IsAParamValidValidator implements ConstraintValidator<IsAParamValid, MyParamObject>
{
public static void validate(#IsAParamValid MyParamObject myParamObject) {
}
#Override
public void initialize(IsAParamValid isAParamValid) {
}
#Override
public boolean isValid(MyParamObject myParamObject, ConstraintValidatorContext constraintValidatorContext)
{
// do the check
boolean bResult = ... whatever ...
return bResult;
}
}
OK so what do you all think?

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.

Resources