Use a ConstraintValidator to validate an object - validation

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?

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

How to implement validation on 2 dependent fields?

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'.

Jersey custom validators unittest

I have a REST service written with Jersey & Spring-Boot. I have written custom validator classes for POST params. I want to unittest the same. I could not figure out how to do it. My Validator looks like below:
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = ValidTaskForCreate.Validator.class)
public #interface ValidTaskForCreate {
String message() default "Invalid Request to create a Task";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
public class Validator implements ConstraintValidator<ValidTaskForCreate, Task> {
#Override
public void initialize(ValidTaskForCreate constraintAnnotation) {
}
#Override
public boolean isValid(final Task task, ConstraintValidatorContext context) {
context.disableDefaultConstraintViolation();
if(task.getName() == null || task.getName().isEmpty()) {
context.buildConstraintViolationWithTemplate("Task name should be specified").addConstraintViolation();
return false;
}
if(task.getTaskType() == null) {
context.buildConstraintViolationWithTemplate("Specify a valid TaskType in the range of [1..3]").addConstraintViolation();
return false;
}
return true;
}
}
}
Now i want to test the isValid() function by passing various Task objects. I am not sure how to call this method now.
I can create instance of Validator class like this,
ValidTaskForCreate.Validator taskValidator = null;
taskValidator = new ValidTaskForCreate.Validator();
To call isValid(), i can use taskValidator.isValid(). But i do not know how to create the ConstraintValidatorContext object to pass as 2nd parameter.
Or is there any way to UnitTest custom validations classes like this?
But i do not know how to create the ConstraintValidatorContext object to pass as 2nd parameter.
Just use Mockito and mock it. Then just verify that the correct methods were called. This is how to test the behavior of the unit when dependencies are involved.
private ConstraintValidatorContext context;
private ConstraintValidatorContext.ConstraintViolationBuilder builder;
#Before
public void setup() {
// mock the context
context = Mockito.mock(ConstraintValidatorContext.class);
// context.buildConstraintViolationWithTemplate returns
// ConstraintValidatorContext.ConstraintViolationBuilder
// so we mock that too as you will be calling one of it's methods
builder = Mockito.mock(ConstraintValidatorContext.ConstraintViolationBuilder.class);
// when the context.buildConstraintViolationWithTemplate is called,
// the mock should return the builder.
Mockito.when(context.buildConstraintViolationWithTemplate(Mockito.anyString()))
.thenReturn(builder);
}
#Test
public void test() {
// call the unit to be tested
boolean result = ..isValid(badTask, context);
// assert the result
assertThat(result).isFalse();
// verify that the context is called with the correct argument
Mockito.verify(context)
.buildConstraintViolationWithTemplate("Task name should be specified");
}
Note the use of Mockito directly. In most cases you will probably just use static imports to make it less verbose. I just wanted to make it more readable
This is the best way I found using standard Spring unit testing with no need to mock anything.
#RunWith(SpringRunner.class)
#SpringBootTest(classes= {ValidationAutoConfiguration.class})
public class AllowedValuesValidatorTest {
#Autowired
private Validator validator;
#Test
public void testIsValid() {
ObjectToBeValidated obj = // create object
Set<ConstraintViolation<ObjectToBeValidated>> violations = validator.validate(obj);
boolean violationsFound =
violations.stream().anyMatch(v -> v.getConstraintDescriptor().getAnnotation().annotationType().equals(
NonNullLowercaseLettersOrNumbersOnly.class));
assertThat(externalIdViolationFound).isTrue();
}
}
It's the ValidationAutoConfiguration.class as the configuration of the test that does the heavy lifting. This will exercise all validation on the ObjectToBeValidated and you can search the violations for just that one you're testing for.

can #Email and spring custom validation coexists?

I need to validate Email and password while creating an account using web services. I am using Spring REST controller and plan to use #Email(hibernate validator) annotation for email id validation.For Password validation I have to write custom validator by implementing Validator interface.
#XmlRootElement
public class Account implements Serializable {
#Email(message =AccountErrorCodes.ACCOUNT_EMAIL_VALIDATION)
private String emailId;
private String password;
For password writing the own validator
#Autowired
private PasswordValidator passwordValidator;
#InitBinder
private void initBinder(WebDataBinder binder) {
binder.setValidator(passwordValidator);
}
It looks like both cant coexist. When I try to validate EmailId spring calls passwordValidator but fails to validate Email. I expect the failure due to incorrect Email id. When I disable the custom validator I get the required error message.
Looks like I am missing something. Do I need to do something to make it work? or it is not supported? If not supported Can I call Hibernate validator directly from spring custom validator to avoid writing my own validation for email?
Instead of injecting your custom validator in your controller, make a special #Password annotation for it that uses this as a validator. Remove any constraint logic from controller and do it all on your DTO class. Something like this:
#Target({ METHOD, FIELD, ANNOTATION_TYPE })
#Retention(RUNTIME)
#Constraint(validatedBy = PasswordValidator.class)
public #interface Password {
String message() default "{Invalid password}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
You will find loads of tutorials for custom annotations that use your own made validators. Hope that helps.
You asked :
By Writing our own custom annotations can I still use Binding Result object that Spring provides which returns multiple error messages?
Yes .
You can make your own custom annotations for any type .
For example :
Phone Number
#Phone
#NotEmpty
private String phone;
Now make a java file :
Phone.java
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.RetentionPolicy;
import javax.validation.Constraint;
import javax.validation.Payload;
#Documented
#Constraint(validatedBy = PhoneValidator.class)
#Target( { ElementType.METHOD, ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
public #interface Phone {
String message() default "{Phone}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
And a class to provide validations measures :
PhoneValidator.java
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class PhoneValidator implements ConstraintValidator<Phone, String> {
#Override
public void initialize(Phone paramA) {
// TODO Auto-generated method stub
}
#Override
public boolean isValid(String phoneNo, ConstraintValidatorContext ctx) {
// TODO Auto-generated method stub
if(phoneNo == null) {
return false;
}
//Validate phone numbers of format "1234567890"
if (phoneNo.matches("\\d{10}")) return true;
//validating phone number with -, . or spaces
else if(phoneNo.matches("\\d{3}[-\\.\\s]\\d{3}[-\\.\\s]\\d{4}")) return true;
//validating phone number with extension length from 3 to 5
else if(phoneNo.matches("\\d{3}-\\d{3}-\\d{4}\\s(x|(ext))\\d{3,5}")) return true;
//validating phone number where area code is in braces ()
else if(phoneNo.matches("\\(\\d{3}\\)-\\d{3}-\\d{4}")) return true;
//return false if nothing matches the input
else return false;
}
}
Now in :
message_en.properties
Include :
#Custom validation annotation
Phone=Invalid format, valid formats are 1234567890, 123-456-7890 x1234
By this way , you will succesfully get a custom validator .
BindingResult
will catch the error and get the message from the properties file .
Hope this helps .

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 ....

Resources