I have a simple bean, i.e.:
public class MyBean {
private boolean selected;
private String someString;
...
}
So if selected is true, I want someString to be #NotNull etc. .
Any hints, links how to achieve this behaviour?
Thanks
Jonny
If you’re using Spring Framework then you can use Spring Expression Language (SpEL) for that. I’ve wrote small library that provides JSR-303 validator based on SpEL that makes cross-field validations very easy. Take a look at https://github.com/jirutka/validator-spring.
And there’s example for your case:
#SpELAssert(value = "someString != null", applyIf = "selected",
message = "{validator.missing_some_string}")
public class MyBean {
private boolean selected;
private String someString;
...
}
Actually this was too easy. Try something more interesting, maybe an equality of password fields when one of them is not null.
#SpELAssert(value = "password.equals(passwordVerify)",
applyIf = "password || passwordVerify",
message = "{validator.passwords_not_same}")
public class User {
private String password;
private String passwordVerify;
}
And you can even use your own “helper methods” in these expressions!
Compared to Hibernate Validator’s #ScriptAssert annotation, this is pure Java solution, it’s not using any JSR-223 compliant scripting language which may be a little problematic. On the other side, this solution is interesting only for Spring-based applications.
You could do this by annotating MyBean with a custom validator, for example:
#ValidMyBean
public class MyBean {
private boolean selected;
private String someString;
...
}
ValidMyBean:
#Target({ ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = MyBeanValidator.class)
public #interface ValidMyBean {
boolean allViolationMessages() default true;
Class<?>[] constraints() default {};
Class<?>[] groups() default {};
String message() default "{ValidMyBean.message}";
Class<? extends Payload>[] payload() default {};
}
MyBeanValidator:
public final class MyBeanValidator implements
ConstraintValidator<ValidMyBean, MyBean> {
#Override
public void initialize(
#SuppressWarnings("unused") final ValidMyBean constraintAnnotation) {
}
#Override
public boolean isValid(final MyBean value,
final ConstraintValidatorContext context) {
boolean isValid = true;
//your validation here
return isValid;
}
}
Related
Use Case:
Let's assume this is my POJO:
class Sample{
String field1;
String field2;
double field3;
LocalDateTime field4;
LocalDateTime field5;
//...getters, setters and parameterised constructor
}
I am reading certain values from an external file and creating a POJO using a parameterised constructor. All the fields have certain validation constraints for them.
What I am looking for is a way for those constraints to be evaluated automatically when I am creating an object using the parameterised constructor. If one or more validation constraints fail, it should throw an error.
What I have tried so far:
I have tried the Bean Validation approach in Spring by creating my own annotation and validator. The code is below:
POJO
#ValidChecker(groups = Default.class)
class Sample{
String field1;
String field2;
double field3;
LocalDateTime field4;
LocalDateTime field5;
//...getters, setters and parameterised constructor
}
ValidChecker Annotation
#Constraint(validatedBy = DataValidator.class)
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface ValidChecker {
String message() default "Data is not valid";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
DataValidator.java
#SupportedValidationTarget(ValidationTarget.ANNOTATED_ELEMENT)
public class DataValidator implements ConstraintValidator<ValidChecker, ValidationData> {
#Override
public void initialize(ValidChecker constraintAnnotation) {
ConstraintValidator.super.initialize(constraintAnnotation);
}
#Override
public boolean isValid(ValidationData validationData, ConstraintValidatorContext constraintValidatorContext) {
if (validationData == null) {
return false;
}
if (BigDecimal.valueOf(validationData.getField3()).scale() != 2) {
return false;
}
if (validationData.getField5().isBefore(validationData.getField4())) {
return false;
}
return true;
}
}
The above code didn't work.
Suggestions Needed
Problem with the above approach
Alternate approach using Spring
Alternate approach by using some third party library
I looked quite a bit but couldn't find an approach without Spring bean validation. Can someone please help?
You could use the Bean Validation API directly in your constructor:
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<Sample>> violations = validator.validate(this);
if (!violations.isEmpty()) {
throw new ConstraintViolationException(violations);
}
I am validating REST service request/bean in a spring-boot 2.3.1.RELEASE web application. Currently, I am using Hibernate Validator, though I am open to using any other way for validation.
Say, I have a model Foo, which I receive as a request in a Rest Controller. And I want to validate if completionDate is not null then status should be either "complete" or "closed".
#StatusValidate
public class Foo {
private String status;
private LocalDate completionDate;
// getters and setters
}
I created a custom class level annotation #StatusValidate.
#Constraint(validatedBy = StatusValidator.class)
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface StatusValidate {
String message() default "default status error";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
I created StatusValidator class.
public class StatusValidator implements ConstraintValidator<StatusValidate, Foo> {
#Override
public void initialize(StatusValidateconstraintAnnotation) {
}
#Override
public boolean isValid(Foovalue, ConstraintValidatorContext context) {
if (null != value.getCompletionDate() && (!value.getStatus().equalsIgnoreCase("complete") && !value.getStatus().equalsIgnoreCase("closed"))) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate()).
.addPropertyNode("status").addConstraintViolation();
return false;
}
return true;
}
}
When I validate Foo object (by using #Valid or #Validated or manually calling the validator.validate() method), I get following data in the ConstraintViolation.
Code:
// Update.class is a group
Set<ConstraintViolation<Foo>> constraintViolations = validator.validate(foo, Update.class);
constraintViolations.forEach(constraintViolation -> {
ErrorMessage errorMessage = new ErrorMessage();
errorMessage.setKey(constraintViolation.getPropertyPath().toString());
errorMessage.setValue(constraintViolation.getInvalidValue());
// Do something with errorMessage here
});
constraintViolation.getPropertyPath().toString() => status
constraintViolation.getInvalidValue() => (Foo object)
How can I set an invalid value (actual value of status attribute) in custom ConstraintValidator or anywhere else so that constraintViolation.getInvalidValue() returns value of status attribute?
OR
Is there a better way of validating request payload/bean where validation of an attribute depends on another attribute's value?
Edit :
I can do something like
if(constraintViolation.getPropertyPath().toString().equals("status")) {
errorMessage.setValue(foo.getStatus());
}
but this would involve maintaining the String constant of attribute names somewhere for eg. "status". Though, in the StatusValidator also, I am setting the attribute name .addPropertyNode("status") which also I would like to avoid.
Summary :
I am looking for a solution (not necessarily using custom validations or hibernate validator) where
I can validate json requestor or a bean, for an attribute whose validations depends on values of other attributes.
I don't have to maintain bean attribute names as String constants anywhere (maintenance nightmare).
I am able to get the invalid property name and value both.
You can use dynamic payload to provide additional data in the constraint violation. It can be set using HibernateConstraintValidatorContext:
context.unwrap(HibernateConstraintValidatorContext.class)
.withDynamicPayload(foo.getStatus().toString());
And javax.validation.ConstraintViolation can, in turn, be unwrapped to HibernateConstraintViolation in order to retrieve the dynamic payload:
constraintViolation.unwrap(HibernateConstraintViolation.class)
.getDynamicPayload(String.class);
In the example above, we pass a simple string, but you can pass an object containing all the properties you need.
Note that this will only work with Hibernate Validator, which is the most widely used implementation of the Bean Validation specification (JSR-303/JSR-349), and used by Spring as its default validation provider.
You can use the expression language to evaluate the property path. E.g.
Set<ConstraintViolation<Foo>> constraintViolations = validator.validate(foo);
constraintViolations.forEach(constraintViolation -> {
Path propertyPath = constraintViolation.getPropertyPath();
Foo rootBean = constraintViolation.getRootBean();
Object invalidPropertyValue = getPropertyValue(rootBean, propertyPath);
System.out.println(MessageFormat.format("{0} = {1}", propertyPath, invalidPropertyValue));
});
private static Object getPropertyValue(Object bean, Path propertyPath) {
ELProcessor el = new ELProcessor();
el.defineBean("bean", bean);
String propertyExpression = MessageFormat.format("bean.{0}", propertyPath);
Object propertyValue = el.eval(propertyExpression);
return propertyValue;
}
The expression language does also work with nested beans. Here is a full example
You will need Java >1.8 and the follwing dependencies:
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.0.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.2.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator-annotation-processor</artifactId>
<version>6.0.2.Final</version>
</dependency>
and my java code
public class Main {
public static void main(String[] args) {
ValidatorFactory buildDefaultValidatorFactory = Validation.buildDefaultValidatorFactory();
Validator validator = buildDefaultValidatorFactory.getValidator();
// I added Bar to show how nested bean property validation works
Bar bar = new Bar();
// Must be 2 - 4 characters
bar.setName("A");
Foo foo = new Foo();
foo.setBar(bar);
foo.setCompletionDate(LocalDate.now());
// must be complete or closed
foo.setStatus("test");
Set<ConstraintViolation<Foo>> constraintViolations = validator.validate(foo);
System.out.println("Invalid Properties:");
constraintViolations.forEach(constraintViolation -> {
Path propertyPath = constraintViolation.getPropertyPath();
Foo rootBean = constraintViolation.getRootBean();
Object invalidPropertyValue = getPropertyValue(rootBean, propertyPath);
System.out.println(MessageFormat.format("{0} = {1}", propertyPath, invalidPropertyValue));
});
}
private static Object getPropertyValue(Object bean, Path propertyPath) {
ELProcessor el = new ELProcessor();
el.defineBean("bean", bean);
String propertyExpression = MessageFormat.format("bean.{0}", propertyPath);
Object propertyValue = el.eval(propertyExpression);
return propertyValue;
}
#StatusValidate
public static class Foo {
private String status;
private LocalDate completionDate;
#Valid
private Bar bar;
public void setBar(Bar bar) {
this.bar = bar;
}
public Bar getBar() {
return bar;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public LocalDate getCompletionDate() {
return completionDate;
}
public void setCompletionDate(LocalDate completionDate) {
this.completionDate = completionDate;
}
}
public static class Bar {
#Size(min = 2, max = 4)
private String status;
public String getStatus() {
return status;
}
public void setName(String status) {
this.status = status;
}
}
#Constraint(validatedBy = StatusValidator.class)
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public static #interface StatusValidate {
String message()
default "default status error";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public static class StatusValidator implements ConstraintValidator<StatusValidate, Foo> {
#Override
public boolean isValid(Foo value, ConstraintValidatorContext context) {
if (null != value.getCompletionDate() && (!value.getStatus().equalsIgnoreCase("complete")
&& !value.getStatus().equalsIgnoreCase("closed"))) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate())
.addPropertyNode("status").addConstraintViolation();
return false;
}
return true;
}
}
}
Output is:
Invalid Properties:
status = test
bar.status = A
Use #NotNull for Completion date and use Custom enum validator for status like this :
/*enum class*/
public enum Status{
COMPLETE,
CLOSED
}
/*custom validator*/
#ValueValidator(EnumValidatorClass = Status.class)
#NotNull
private String status;
#NotNull
private LocalDate completionDate;
/*anotation interface*/
#Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = EnumValueValidator.class)
#Documented
public #interface ValueValidator {
public abstract String message() default "Invalid Status!";
public abstract Class<?>[] groups() default {};
public abstract Class<? extends Payload>[] payload() default {};
public abstract Class<? extends java.lang.Enum<?>> EnumValidatorClass();
}
/*anotation implementation*/
public class EnumValueValidator implements ConstraintValidator<ValueValidator, String>{
private List<String> values;
#Override
public void initialize(ValueValidator annotation)
{
values = Stream.of(annotation.EnumValidatorClass().getEnumConstants()).map(Enum::name).collect(Collectors.toList());
}
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
return values.contains(value);
}}
I am trying to validate my rest request according to some fields existance. For example, if transactionDate field is null or didnt exist in my request object, I want to throw an error to client.
I couldn't do it despite the source of this guide and still my requests can pass in controller.
How can I validate two or more fields in combination?
DTO
#FraudRestRequestValidator
public class FraudActionsRestRequest {
private BigDecimal amount;
private String receiverTransactionDate;
private String receiverNameSurname;
private BigDecimal exchangeRate;
private String transactionReferenceNumber;
#NotNull
private String transactionDate;
#NotNull
private String transactionTime;
private String transactionTimeMilliseconds;
private BigDecimal tlAmount;
private String channel;
}
ANNOTATION
#Constraint(validatedBy = FraudActionsRestValidator.class)
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface FraudRestRequestValidator {
String message() default "Invalid Limit of Code";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
VALIDATOR
public class FraudActionsRestValidator implements ConstraintValidator<FraudRestRequestValidator, FraudActionsRestRequest> {
#Override
public void initialize(FraudRestRequestValidator constraintAnnotation) {
}
#Override
public boolean isValid(FraudActionsRestRequest fraudActionsRestRequest, ConstraintValidatorContext constraintValidatorContext) {
//I will implement my logic in future
return false;
}
}
REST CONTROLLER
#PostMapping("/getFraudActions")
public ResponseEntity<?> getFraudActions(#Valid #RequestBody FraudActionsRestRequest fraudActionsRestRequest, Errors errors) throws Exception
Thanks.
In your custom validator just implement logic you want to have. You did everything correct except some minor thing:
#Constraint(validatedBy = FraudActionsRestValidator.class)
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface ValidFraudRestRequest {
String message() default "Invalid Limit of Code";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class FraudActionsRestValidator implements ConstraintValidator<ValidFraudRestRequest, FraudActionsRestRequest> {
#Override
public void initialize(ValidFraudRestRequest constraintAnnotation) {
}
#Override
public boolean isValid(FraudActionsRestRequest fraudActionsRestRequest, ConstraintValidatorContext constraintValidatorContext) {
return fraudActionsRestRequest.getTransactionDate() != null && fraudActionsRestRequest.getTransactionTime() != null && additional check you need;
}
}
Looks all okaish.
You might be missing the #Validated annotation on the rest controller class,
See https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-validation.html for more info
I want to Implement a validation in a jersey such that if I send a duplicate value of UserName or Email which already exists in DataBase then it should throw an Error saying UserName/Email already exists.
How can I acheive this?
I gone through this jersey documentation
https://jersey.java.net/documentation/latest/bean-validation.html
https://github.com/jersey/jersey/tree/2.6/examples/bean-validation-webapp/src
But I couldn't understood what exactly I have to follow to make my custom Jersey validations.
Suppose I send a Json in Body while Creating a User like:
{
"name":"Krdd",
"userName":"khnfknf",
"password":"sfastet",
"email":"xyz#gmail.com",
"createdBy":"xyz",
"modifiedBy":"xyz",
"createdAt":"",
"modifiedAt":"",
}
Thanks in Advance for your helping hands.
Assuming you have a request instance of class:
public class UserRequest {
// --> NOTICE THE ANNOTATION HERE <--
#UniqueEmail(message = "email already registered")
private final String email;
public UserRequest(String email) {
this.email = email;
}
public String getEmail() {
return email;
}
}
You have to add a new annotation (and link it to your validator class using #Constraint):
#Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE })
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = { UniqueEmailValidator.class })
#Documented
public #interface UniqueEmail {
String message();
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
then you also have to implement the validation itself:
public class UniqueEmailValidator implements ConstraintValidator<UniqueEmail, UserRequest> {
#Override
public void initialize(UniqueEmail constraintAnnotation) {
}
#Override
public boolean isValid(UserRequest value, ConstraintValidatorContext context) {
// call to the DB and verify that value.getEmail() is unique
return false;
}
}
and you're done. Remember that Jersey is using HK2 internally so binding some sort of a DAO to your Validator instance can be tricky if you use Spring or other DI.
I am trying to use spring to check user online input to ensure that the two characters they enter is an actual US state, is there any way of doing this, hopefully using a preset pattern? like, #State or something (if that was a legit annotation). Also, is there a good annotation commonly used for a String street, and String city field? That is other than #NotNull and #NotEmpty
Any help would be greatly appreciated!!
Unfortunately there is no out of the box however you can create your own #State annotation , all you need is to define your annotation and class implementing ConstraintValidator(which handles the validation logic) E.g.
#Constraint(validatedBy = StateConstraintValidator.class)
#Target( { ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
public #interface State {
String message() default "{State}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class StateConstraintValidator implements ConstraintValidator<String, String> {
private static final Set<String> CODE_MAP = new HashSet<>(){
{add("AR");}
{add("AK");} //add more codes ...
};
#Override
public void initialize(String state) { }
#Override
public boolean isValid(String value, ConstraintValidatorContext cxt) {
if(value == null) {
return false;
}
return CODE_MAP.contains(value);
}
}
In the similar manner you can create other annotations.