I created a custom class-level ConstraintValidator that validates a certain field if another field has a specific value. Here's the object:
#ValidRequest
public class Request {
interface ValidationName {}
#NotBlank(groups = {Default.class})
private final String fieldName;
#NotBlank(groups = {ValidationName.class})
#Length(max = 40, groups = {ValidationName.class})
private final String fieldValue;
// Constructor, getters
}
And the class-level ConstraintValidator:
public class ValidRequestImpl implements
ConstraintValidator<ValidRequest, Request> {
private Validator validator;
#Autowired
public ValidRequestImpl (Validator validator) {
this.validator = validator;
}
#Override
public void initialize(ValidRequest constraintAnnotation) {}
#Override
public boolean isValid(ValidRequest value, ConstraintValidatorContext context) {
if (!validator.validateProperty(value, "fieldName").isEmpty()) {
return true;
}
if (value.getFieldName().equals("name")) {
Set<ConstraintViolation<Request>> violations =
validator.validateProperty(value, "fieldValue", ValidationName.class);
violations.forEach(violation -> context
.buildConstraintViolationWithTemplate(violation.getMessage())
.addPropertyNode("fieldValue")
.addConstraintViolation());
context.disableDefaultConstraintViolation();
return violations.isEmpty();
}
return true;
}
}
This works fine, but the returned errors object isn't very helpful:
"errors": [
{
"message": "Please enter a name.",
"field_name": "request.fieldValue",
"rejected_value": "com.example.Request#5f017a8c",
"code": "ValidRequest"
}
],
Ideally, I would want the rejected_value field to be the actual fieldValue that was passed (in this case empty string), instead of the class-level object. I would also want the code field to be the actual annotation that failed (NotBlank), rather than ValidRequest. context.buildConstraintViolationWithTemplate() doesn't appear to have any methods that allow you to change these fields.
How can I accomplish this?
Related
I am pretty new with Java, and I have just created a spring boot application to practice a bit.
I created a controller that receives a POST request with json:
{
"team1": {
"score": 10,
"colour": "RED"
},
"team2": {
"score": 3,
"colour": "BLUE"
}
}
Controller:
public #ResponseBody
ResponseEntity<Response<Game>> saveGame(
#Valid #RequestBody PostedGameRequest postedGameRequest
) {
// ...
}
The PostedGameRequest gets validated, and that works fine, but there are two properties: team1 and team2 of type PostedGameRequestTeam, and they don't get validated. I added some #NotEmpty attributes in there, but that didn't work.
So now I created a custom TeamValidator, that validates the fields in the Team, but when it fails, it will only show the message that I specified in the #Team attribute class. It would be nice if I could validate each field by itself.
#Builder
public class PostedGameRequest {
#Team()
private final PostedGameRequestTeam team1;
#Team()
private final PostedGameRequestTeam team2;
public PostedGameRequestTeam getTeam1() {
return team1;
}
public PostedGameRequestTeam getTeam2() {
return team2;
}
}
public class TeamValidator implements ConstraintValidator<Team, PostedGameRequestTeam> {
List<String> colours = Arrays.asList("RED","BLUE");
#Override
public boolean isValid(#Valid PostedGameRequestTeam team, ConstraintValidatorContext context) {
if (team == null || team.getColour() == null || team.getScore() == null) {
return false;
}
if (team.getScore() < 0 || team.getScore() > 11) {
// This will only lead to an invalid team message, should get a message about the score in this case
return false;
}
if (!colours.contains(team.getColour())) {
// This will only lead to an invalid team message, should get a message about the colour in this case
return false;
}
return (team.getId() != null || team.getName() != null);
}
}
I have the feeling I am not doing this as I am supposed to do it. Why did my first approach not work? Is there something I have to add that makes the validator also run for the team1 and team2 properties without me adding a custom Validator #Team?
Instead of #NotEmpty try with
#NotNull
This will make sure that team1 and team2 always contain an object which is not null
If you want more validation for each property of team1 and team2 get inside that object in java and add more annotations to those fields. For the string property you can add
#NotNull
#Size (min = 1)
which will make sure that it always contains a value
Also to get one step inside with validations just add #Valid So your object will become
public class PostedGameRequest {
#NotNull
#Valid
private final PostedGameRequestTeam team1;
#NotNull
#Valid
private final PostedGameRequestTeam team2;
And then inside
public class PostedGameRequestTeam {
#Min(0)
integer score;
#NotNull
#Size (min=1)
String color;
}
Edit:#Size applies only in String fields not custom objects
Instead of using your #Team custom validator that handles all the validation logic (which I don't think that is pretty useful in this case), you should declare your validation annotations individually inside of each PostedGameRequestTeam object.
public class PostedGameRequestTeam {
#NotBlank(message = "The team colour value must not be null, empty or blank!")
#TeamColour(message = "The team colour must be RED or BLUE")
private String colour;
#Min(value = 0, message = "The score value must be higher than 0!")
#Max(value = 11, message = "The score value must be lesser than 11!")
private Integer score;
// Getters and Setters
}
Note that I used #Min and #Max annotation, that is because javax.validation.constraints.Size validates only string's length.
And now you can add the custom constraint validation of #TeamColour.
#Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
#Retention(RUNTIME)
#Constraint(validatedBy = { StatusNotaFiscalValidator.class })
public #interface TeamColour {
String message() default "The team colour must be RED or BLUE!";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
public class TeamColourValidator implements ConstraintValidator<TeamColour, String> {
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
boolean valid = true;
if (value != null) {
if (!value.equals("BLUE") && !value.equals("RED")) {
valid = false;
}
}
return valid;
}
}
And then, you should add #NotNull property on your parent object so you can validate the JSON body which have the team object.
#Builder
public class PostedGameRequest {
#NotNull
#Valid
private final PostedGameRequestTeam team1;
#NotNull
#Valid
private final PostedGameRequestTeam team2;
public PostedGameRequestTeam getTeam1() {
return team1;
}
public PostedGameRequestTeam getTeam2() {
return team2;
}
}
public #ResponseBody
ResponseEntity<Response<Game>> saveGame(
#Valid #RequestBody PostedGameRequest postedGameRequest
) {
// ...
}
This logic handles all of cascade validation which you want to use.
I am working on javax validation API in Spring Boot Application. I have a User bean and i have to validate that username given in request is unique and doesn't exist into Database.
I have created custom annotation (UniqueUser.java) and custom Validator(UniqueUserValidator.java) for this requirement.
public class User {
#NotNull
#UniqueUser
private String username;
#NotNull
private String password;
#NotNull
#Email
private String email;
#NotNull
private String phone;
}
UniqueUser.java
#Target({TYPE, ANNOTATION_TYPE})
#Retention(RUNTIME)
#Constraint(validatedBy = UniqueUserValidator.class)
#Documented
public #interface NameMatch
{
String message() default "User id already exists";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
UniqueUserValidator.java
public class UniqueUserValidator implements ConstraintValidator<NameMatch, Object>
{
#Autowired
UserRepository userRepository;
#Override
public boolean isValid(String userName, final ConstraintValidatorContext context)
{
boolean isValidUser = false;
if(userName!=null && !userName.isEmpty()) {
Optional<User> user= userRepository.findByUserId(userName);
isValidUser = !user.isPresent();
}
return isValidUser;
}
}
In above code, the unique user validation get called for username field even if the field is null and shows Username already exists error message. I want the custom validator to get called only when username has some value. Is it possible to avoid this call.
I can fix the error by modifying the below method and returning true if username is null, but i want't to avoid this unnecessary call.
public class UniqueUserValidator implements ConstraintValidator<NameMatch, Object>
{
#Autowired
UserRepository userRepository;
#Override
public boolean isValid(String userName, final ConstraintValidatorContext context)
{
boolean isValidUser = false;
if(userName!=null && !userName.isEmpty()) {
Optional<User> user= userRepository.findByUserId(userName);
isValidUser = !user.isPresent();
} else {
isValidUser = true;
}
return isValidUser;
}
}
I think what you are looking for is Grouping constraints;
Groups allow you to restrict the set of constraints applied during
validation. One use case for validation groups are UI wizards where in
each step only a specified subset of constraints should get validated.
The groups targeted are passed as var-arg parameters to the
appropriate validate method.
In your case it should look like this:
#GroupSequence({ FirstConstaint.class, SecondConstaint.class })
public class User {
#NotNull(groups = FirstConstaint.class)
#UniqueUser(groups = SecondConstaint.class)
private String username;
// the rest of your fields
}
interface FirstConstaint {
}
interface SecondConstaint {
}
This way, it will check #UniqueUser only if the field is not null, otherwise, it will return the message of #NotNull validation.
Otherwise like #User said you can use this checks in service layer ^^
I am working on cross field validation using javax validation API in Spring Boot Application. I have a User bean and i have to validate that both firstname and lastname are not null/empty. At least one of this field should have a value.
I have created custom annotation (NameMatch.java) and custom Validator (NameValidator.java) for this requirement.
#NameMatch(first = "firstname", second = "lastname", message = "The first and lastname can't be null")
public class User {
private String firstname;
private String lastname;
#NotNull
#Email
private String email;
#NotNull
private String phone;
}
NameMatch.java
#Target({TYPE, ANNOTATION_TYPE})
#Retention(RUNTIME)
#Constraint(validatedBy = NameValidator.class)
#Documented
public #interface NameMatch
{
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();
}
NameValidator.java
public class NameValidator implements ConstraintValidator<NameMatch, Object>
{
private String firstFieldName;
private String secondFieldName;
#Override
public void initialize(final NameMatch constraintAnnotation)
{
firstFieldName = constraintAnnotation.first();
secondFieldName = constraintAnnotation.second();
}
#Override
public boolean isValid(final Object value, final ConstraintValidatorContext context)
{
boolean isValidName = false;
try
{
final Object firstName = BeanUtils.getProperty(value, firstFieldName);
final Object lastName = BeanUtils.getProperty(value, secondFieldName);
// Validation logic
}
catch (final Exception ignore)
{
}
return isValidName;
}
}
UserValidator.java
public class UserValidator
{
public void isValidUser()
{
//Create ValidatorFactory which returns validator
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
//It validates bean instances
Validator validator = factory.getValidator();
User user = new User();
user.setEmail("test#gmail.com");
user.setPhone("12345678")
//Validate bean
Set<ConstraintViolation<User>> constraintViolations = validator.validate(user);
//Show errors
if (constraintViolations.size() > 0) {
for (ConstraintViolation<User> violation : constraintViolations) {
System.out.println(violation.getMessage());
}
} else {
System.out.println("Valid Object");
}
}
}
I have to write JUnit test cases for the Custom Validator class. I explored hibernate validator docs but couldn't find a way to invoke custom validator method through JUnit. Can someone please help to write JUnit test cases for above scenario.
Your NameValidator has public methods, so you can instantiate an object and write unit tests like for any other public method.
A possible JUnit 5 test with Mockito can look like the following:
#ExtendWith(MockitoExtension.class)
class NameValidatorTest {
#Mock
private NameMatch nameMatch;
#Mock
private ConstraintValidatorContext constraintValidatorContext;
#Test
public void testIsValid() {
when(nameMatch.first()).thenReturn("firstname");
when(nameMatch.second()).thenReturn("lastname");
NameValidator nameValidator = new NameValidator();
nameValidator.initialize(nameMatch);
User user = new User();
user.setFirstname("Duke");
user.setLastname("Duke");
boolean result = nameValidator.isValid(user, constraintValidatorContext);
assertTrue(result);
}
}
Depending of what you need the ConstraintValidatorContext you might also want to mock methods or later verify that specific methods were invoked.
If you are not using JUnit 5, you can adjust the code to not the JUnit 5 MockitoExtension and create the mocks using Mockito.mock().
One way is definitely Mockito (as #rieckpil mentioned).
If you dont want that, and actually want to invoke the validator, you can have something like this:
#SpringBootTest
public class NameValidatorUnitTest {
#Test
public void whenExistingRootRole_thenFail()
{
AnnotationDescriptor<NameMatch> descriptor = new AnnotationDescriptor<NameMatch>( NameMatch.class );
AnnotationFactory.create( descriptor );
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
User user = new User();
user.setEmail("test#gmail.com");
user.setPhone("12345678");
Set<ConstraintViolation<User>> constraintViolations = validator.validate(user);
if (constraintViolations.size() > 0) {
for (ConstraintViolation<User> violation : constraintViolations) {
System.out.println(violation.getMessage());
}
} else {
System.out.println("Valid Object");
}
Assert.assertEquals(true, constraintViolations.size()>0);
}
}
Before asking questions, I apologize for not being good at English.
I'm implementing a custom ConstraintValidator for cross-field validation as shown below.
Validation Target Class
public class ValidationTarget {
#Valid
private Inner inner;
#ValidDates(fromField = "from", toField = "to",
message = "{from} must not be later than {to}")
public class Inner {
private Date from;
private Date to;
}
// ...
}
CustomConstraintValidator
// imports ...
public class CustomConstraintValidator implements ConstraintValidator<ValidDates, Object> {
private String fromFieldName;
private String toFieldName;
private String message;
#Override
public void initialize(ValidDatesvalidationSpec) {
this.fromFieldName = validationSpec.fromField();
this.toFieldName = validationSpec.toField();
this.message = validationSpec.message();
}
#Override
public boolean isValid(Object target, ConstraintValidatorContext ctx) {
Date startDateObject = getFieldValue(target, fromFieldName);
Date endDateObject = getFieldValue(target, toFieldName);
if (start.after(end)) {
addConstraintViolation(toFieldName, message, ctx);
return false;
}
return true;
}
private void addConstraintViolation(String propertyName, String message, ConstraintValidatorContext ctx) {
ctx.buildConstraintViolationWithTemplate(message)
.addPropertyNode(propertyName)
.addConstraintViolation()
.disableDefaultConstraintViolation();
}
private Date getFieldValue(Object instance, String fieldName) {
Field field = ReflectionUtils.findField(instance.getClass(), fieldName);
ReflectionUtils.makeAccessible(field);
return (Date) ReflectionUtils.getField(field, instance);
}
}
In the above code, Inner class object is returned when calling getInvalidValue() of ConstraintViolation.
I want to change this value only to a specific field value rather than to the entire Inner class.
I'm looking to do a little custom validation with JSR-303 javax.validation.
I have a field. And If a certain value is entered into this field I want to require that a few other fields are not null.
I'm trying to figure this out. Not sure exactly what I would call this to help find an explanation.
Any help would be appreciated. I am pretty new to this.
At the moment I'm thinking a Custom Constraint. But I'm not sure how to test the value of the dependent field from within the annotation. Basically I'm not sure how to access the panel object from the annotation.
public class StatusValidator implements ConstraintValidator<NotNull, String> {
#Override
public void initialize(NotNull constraintAnnotation) {}
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if ("Canceled".equals(panel.status.getValue())) {
if (value != null) {
return true;
}
} else {
return false;
}
}
}
It's the panel.status.getValue(); giving me trouble.. not sure how to accomplish this.
Define method that must validate to true and put the #AssertTrue annotation on the top of it:
#AssertTrue
private boolean isOk() {
return someField != something || otherField != null;
}
The method must start with 'is'.
In this case I suggest to write a custom validator, which will validate at class level (to allow us get access to object's fields) that one field is required only if another field has particular value. Note that you should write generic validator which gets 2 field names and work with only these 2 fields. To require more than one field you should add this validator for each field.
Use the following code as an idea (I've not test it).
Validator interface
/**
* Validates that field {#code dependFieldName} is not null if
* field {#code fieldName} has value {#code fieldValue}.
**/
#Target({TYPE, ANNOTATION_TYPE})
#Retention(RUNTIME)
#Repeatable(NotNullIfAnotherFieldHasValue.List.class) // only with hibernate-validator >= 6.x
#Constraint(validatedBy = NotNullIfAnotherFieldHasValueValidator.class)
#Documented
public #interface NotNullIfAnotherFieldHasValue {
String fieldName();
String fieldValue();
String dependFieldName();
String message() default "{NotNullIfAnotherFieldHasValue.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
#Target({TYPE, ANNOTATION_TYPE})
#Retention(RUNTIME)
#Documented
#interface List {
NotNullIfAnotherFieldHasValue[] value();
}
}
Validator implementation
/**
* Implementation of {#link NotNullIfAnotherFieldHasValue} validator.
**/
public class NotNullIfAnotherFieldHasValueValidator
implements ConstraintValidator<NotNullIfAnotherFieldHasValue, Object> {
private String fieldName;
private String expectedFieldValue;
private String dependFieldName;
#Override
public void initialize(NotNullIfAnotherFieldHasValue annotation) {
fieldName = annotation.fieldName();
expectedFieldValue = annotation.fieldValue();
dependFieldName = annotation.dependFieldName();
}
#Override
public boolean isValid(Object value, ConstraintValidatorContext ctx) {
if (value == null) {
return true;
}
try {
String fieldValue = BeanUtils.getProperty(value, fieldName);
String dependFieldValue = BeanUtils.getProperty(value, dependFieldName);
if (expectedFieldValue.equals(fieldValue) && dependFieldValue == null) {
ctx.disableDefaultConstraintViolation();
ctx.buildConstraintViolationWithTemplate(ctx.getDefaultConstraintMessageTemplate())
.addNode(dependFieldName)
.addConstraintViolation();
return false;
}
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException ex) {
throw new RuntimeException(ex);
}
return true;
}
}
Validator usage example (hibernate-validator >= 6 with Java 8+)
#NotNullIfAnotherFieldHasValue(
fieldName = "status",
fieldValue = "Canceled",
dependFieldName = "fieldOne")
#NotNullIfAnotherFieldHasValue(
fieldName = "status",
fieldValue = "Canceled",
dependFieldName = "fieldTwo")
public class SampleBean {
private String status;
private String fieldOne;
private String fieldTwo;
// getters and setters omitted
}
Validator usage example (hibernate-validator < 6; the old example)
#NotNullIfAnotherFieldHasValue.List({
#NotNullIfAnotherFieldHasValue(
fieldName = "status",
fieldValue = "Canceled",
dependFieldName = "fieldOne"),
#NotNullIfAnotherFieldHasValue(
fieldName = "status",
fieldValue = "Canceled",
dependFieldName = "fieldTwo")
})
public class SampleBean {
private String status;
private String fieldOne;
private String fieldTwo;
// getters and setters omitted
}
Note that validator implementation uses BeanUtils class from commons-beanutils library but you could also use BeanWrapperImpl from Spring Framework.
See also this great answer: Cross field validation with Hibernate Validator (JSR 303)
You should make use of custom DefaultGroupSequenceProvider<T>:
ConditionalValidation.java
// Marker interface
public interface ConditionalValidation {}
MyCustomFormSequenceProvider.java
public class MyCustomFormSequenceProvider
implements DefaultGroupSequenceProvider<MyCustomForm> {
#Override
public List<Class<?>> getValidationGroups(MyCustomForm myCustomForm) {
List<Class<?>> sequence = new ArrayList<>();
// Apply all validation rules from ConditionalValidation group
// only if someField has given value
if ("some value".equals(myCustomForm.getSomeField())) {
sequence.add(ConditionalValidation.class);
}
// Apply all validation rules from default group
sequence.add(MyCustomForm.class);
return sequence;
}
}
MyCustomForm.java
#GroupSequenceProvider(MyCustomFormSequenceProvider.class)
public class MyCustomForm {
private String someField;
#NotEmpty(groups = ConditionalValidation.class)
private String fieldTwo;
#NotEmpty(groups = ConditionalValidation.class)
private String fieldThree;
#NotEmpty
private String fieldAlwaysValidated;
// getters, setters omitted
}
See also related question on this topic.
Here's my take on it, tried to keep it as simple as possible.
The interface:
#Target({TYPE, ANNOTATION_TYPE})
#Retention(RUNTIME)
#Constraint(validatedBy = OneOfValidator.class)
#Documented
public #interface OneOf {
String message() default "{one.of.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String[] value();
}
Validation implementation:
public class OneOfValidator implements ConstraintValidator<OneOf, Object> {
private String[] fields;
#Override
public void initialize(OneOf annotation) {
this.fields = annotation.value();
}
#Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
BeanWrapper wrapper = PropertyAccessorFactory.forBeanPropertyAccess(value);
int matches = countNumberOfMatches(wrapper);
if (matches > 1) {
setValidationErrorMessage(context, "one.of.too.many.matches.message");
return false;
} else if (matches == 0) {
setValidationErrorMessage(context, "one.of.no.matches.message");
return false;
}
return true;
}
private int countNumberOfMatches(BeanWrapper wrapper) {
int matches = 0;
for (String field : fields) {
Object value = wrapper.getPropertyValue(field);
boolean isPresent = detectOptionalValue(value);
if (value != null && isPresent) {
matches++;
}
}
return matches;
}
private boolean detectOptionalValue(Object value) {
if (value instanceof Optional) {
return ((Optional) value).isPresent();
}
return true;
}
private void setValidationErrorMessage(ConstraintValidatorContext context, String template) {
context.disableDefaultConstraintViolation();
context
.buildConstraintViolationWithTemplate("{" + template + "}")
.addConstraintViolation();
}
}
Usage:
#OneOf({"stateType", "modeType"})
public class OneOfValidatorTestClass {
private StateType stateType;
private ModeType modeType;
}
Messages:
one.of.too.many.matches.message=Only one of the following fields can be specified: {value}
one.of.no.matches.message=Exactly one of the following fields must be specified: {value}
A different approach would be to create a (protected) getter that returns an object containing all dependent fields. Example:
public class MyBean {
protected String status;
protected String name;
#StatusAndSomethingValidator
protected StatusAndSomething getStatusAndName() {
return new StatusAndSomething(status,name);
}
}
StatusAndSomethingValidator can now access StatusAndSomething.status and StatusAndSomething.something and make a dependent check.
Sample below:
package io.quee.sample.javax;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import javax.validation.ConstraintViolation;
import javax.validation.Valid;
import javax.validation.Validator;
import javax.validation.constraints.Pattern;
import java.util.Set;
/**
* Created By [**Ibrahim Al-Tamimi **](https://www.linkedin.com/in/iloom/)
* Created At **Wednesday **23**, September 2020**
*/
#SpringBootApplication
public class SampleJavaXValidation implements CommandLineRunner {
private final Validator validator;
public SampleJavaXValidation(Validator validator) {
this.validator = validator;
}
public static void main(String[] args) {
SpringApplication.run(SampleJavaXValidation.class, args);
}
#Override
public void run(String... args) throws Exception {
Set<ConstraintViolation<SampleDataCls>> validate = validator.validate(new SampleDataCls(SampleTypes.TYPE_A, null, null));
System.out.println(validate);
}
public enum SampleTypes {
TYPE_A,
TYPE_B;
}
#Valid
public static class SampleDataCls {
private final SampleTypes type;
private final String valueA;
private final String valueB;
public SampleDataCls(SampleTypes type, String valueA, String valueB) {
this.type = type;
this.valueA = valueA;
this.valueB = valueB;
}
public SampleTypes getType() {
return type;
}
public String getValueA() {
return valueA;
}
public String getValueB() {
return valueB;
}
#Pattern(regexp = "TRUE")
public String getConditionalValueA() {
if (type.equals(SampleTypes.TYPE_A)) {
return valueA != null ? "TRUE" : "";
}
return "TRUE";
}
#Pattern(regexp = "TRUE")
public String getConditionalValueB() {
if (type.equals(SampleTypes.TYPE_B)) {
return valueB != null ? "TRUE" : "";
}
return "TRUE";
}
}
}