How to add JSR 303 validation to arguments of a method - java

I would like to use Hibernate Validator to validate arguments of my method.
I have tried to look-up examples and went through the following example
JSR 303. Validate method parameter and throw exception
But the answer does not exactly solve my problem and also the links are expired in the answer.
Here is a sample code of what I have tried.
//removing imports to reduce the length of code.
/**
* Created by Nick on 15/06/2016.
*/
#Component
public class ValidationService {
#Value("${namesAllowed}")
private String validNames;
#Autowired
private Validator validator;
public boolean validateName(
#NotEmpty
#Pattern(regexp = ".*'.*'.*")
#Email
#Length(max = 10)
String name
) {
Set<ConstraintViolation<String>> violations = validator.validate(name);
for (ConstraintViolation<String> violation : violations) {
String propertyPath = violation.getPropertyPath().toString();
String message = violation.getMessage();
System.out.println("invalid value for: '" + propertyPath + "': " + message);
}
List<String> namesAllowed= Arrays.asList(validNames.split(","));
if (namesAllowed.contains(name.substring(name.indexOf(".") + 1))) {
return true;
}
return false;
}
}
}

Method validation has been standardized as part of Bean Validation 1.1. You can learn more about it in the Hibernate Validator reference guide.
With Spring you need to configure a MethodValidationPostProcessor bean and annotate the constrained class with #Validated. Also this answer may be helpful to you.

Related

Spring Boot - how to validate fields that depend on each other?

Is there some way in Spring Boot that I can perform validation on properties that depend on each other's values, and have the error message be associated with the property?
I want to return the errors to the user in a nice JSON structure:
{
"errors": {
"name": "is required if flag is true"
}
}
Example:
#Entity
public class MyEntity {
private boolean nameRequiredFlag;
// Required if "nameRequiredFlag" is set to true:
private String name;
}
One solution that doesn't solve my problem of associating the error message with the name property is to create a validator annotation for the entity:
#ValidEntity
public class MyEntity {
private boolean nameRequiredFlag;
// Required if "nameRequiredFlag" is set to true:
private String name;
}
#Constraint( validatedBy = { MyEntityValidator.class } )
#Documented
#Target( { ElementType.TYPE } )
#Retention( RetentionPolicy.RUNTIME )
public #interface ValidEntity{
Class<?>[] groups () default {};
String message () default "name is required if 'nameRequiredFlag' is true";
Class<? extends Payload>[] payload () default {};
}
public class MyEntityValidator implements Validator<ValidEntity, MyEntity> {
#Override
public boolean isValid ( MyEntity entity, ConstraintValidatorContext context ) {
if ( !entity.nameRequiredFlag ) return true;
return !StringUtils.isBlank( entity.getName() );
}
}
This is laughably cumbersome and doesn't solve my problem. Isn't there any way I can do this with the framework validation?
Edit: This is for a JSON API, and the consumer really needs to be able to associate the error message to a best guess at which field has an issue. It is not helpful to send the consumer an error message for the whole object, or a computed property.
Solution given by #EvicKhaosKat is one way of doing it. However, when there are too many fields dependent on each other in a complicated way, your class becomes full of annotations and I personally struggle a lot relating them.
A simpler approach is to create a method(s) in your pojo which does the cross field validations and returns a boolean. On the top of this method annotate it with #AssertTrue(message = "your message"). It will solve your problem in a cleaner fashion.
public class SampleClass {
private String duration;
private String week;
private String month;
#AssertTrue(message = "Duration and time attributes are not properly populated")
public boolean isDurationCorrect() {
if (this.duration.equalsIgnoreCase("month")) {
if (Arrays.asList("jan", "feb", "mar").contains(month))
return true;
}
if (this.duration.equalsIgnoreCase("week")) {
if (Arrays.asList("1-7", "8-15", "16-24", "25-31").contains(week))
return true;
}
return false;
}
}
Note: I have not tested this code but have used this approach in multiple places and it works.
Possible reason is that name validation operates on not-yet-fully constructed object, so nameRequiredFlag is not filled yet.
As an option there is a #GroupSequence annotation, which allows to group and perform validations in an order you specify.
For example it is possible to add to MyEntity annotations:
#ValidEntity(groups = DependentValidations.class)
#GroupSequence({MyEntity.class, DependentValidations.class})
So all the other validation annotations on MyEntity class gonna be performed first, and after that DependentValidations group, which consists of ValidEntity.
Thus ValidEntity will be called on fully created object, and the last in order.
(DependentValidations.class - just an empty interface created somewhere nearby, like any other marker interface)
https://www.baeldung.com/javax-validation-groups will possibly describe that in much more details.
p.s. answer provided by #Innovationchef will possibly suit the case more :)

Dynamic POJO validation based on groups in spring

Consider the following pojo for reference:
public class User{
private String username;
private String firstName;
private String middleName;
private String lastName;
private String phone;
//getters and setters
}
My application is a basically spring-boot based REST API which exposes two endpoints, one to create the user and the other to retrieve a user.
The "users" fall into certain categories, group-a, group-b etc. which I get from the headers of the post request.
I need to validated the user data in runtime and the validations may differ based on the group of a user.
for example, the users that fall into group-a may have phone numbers as an optional field whereas it might be a mandatory field for some other group.
The regex may also vary based on their groups.
I need to be able to configure spring, to somehow dynamically validate my pojo as soon as they are created and their respective set of validations get triggered based on their groups.
Maybe I can create a yml/xml configuration which would allow me to enable this?
I would prefer to not annotate my private String phone with #NotNull and #Pattern.
My configuration is as follows:
public class NotNullValidator implements Validator {
private String group;
private Object target;
public String getGroup() {
return group;
}
public void setGroup(String group) {
this.group = group;
}
public Object getTarget() {
return target;
}
public void setTarget(Object target) {
this.target = target;
}
#Override
public void validate(Object o) {
if (Objects.nonNull(o)) {
throw new RuntimeException("Target is null");
}
}
}
public interface Validator {
void validate(Object o);
}
#ConfigurationProperties(prefix = "not-null")
#Component
public class NotNullValidators {
List<NotNullValidator> validators;
public List<NotNullValidator> getValidators() {
return validators;
}
public void setValidators(List<NotNullValidator> validators) {
this.validators = validators;
}
}
application.yml
not-null:
validators:
-
group: group-a
target: user.username
-
group: group-b
target: user.phone
I want to configure my application to somehow allow the validators to pick their targets (the actual objects, not the strings mentioned in the yml), and invoke their respective public void validate(Object o) on their targets.
P.S.
Please feel free to edit the question to make it better.
I am using jackson for serializing and deserializing JSON.
The easiest solution to your problem, as i see it, is not with Spring or the POJOs themselves but with a design pattern.
The problem you're describing is easily solved by a strategy pattern solution.
You match the strategy to use by the header you're expecting in the request, that describes the type of user, and then you perform said validations inside the strategy itself.
This will allow you to use the same POJO for the whole approach, and deal with the specifics of handling/parsing and validating data according to the each type of user's strategy.
Here's a link from wiki books with a detailed explanation of the pattern
Strategy Pattern
Suppose you have a basic interface for your strategies:
interface Strategy {
boolean validate(User user);
}
And you have 2 different implementations for the 2 different types of user:
public class StrategyA implements Strategy {
public boolean validate(User user){
return user.getUsername().isEmpty();
}
}
public class StrategyB implements Strategy {
public boolean validate(User user){
return user.getPhone().isEmpty();
}
}
You add a Strategy attribute to your User POJO and assign the right implementation of the Strategy to that attribute when you receive the post request.
Everytime you need to validate data for that user you just have to invoke the validate method of the assigned strategy.
If each User can fit multiple strategies, you can add a List<Strategy> as an attribute instead of a single one.
If you don't want to change the POJO you have to check which is the correct strategy every time you receive a post request.
Besides the validate method you can add methods to handle data, specific to each strategy.
Hope this helps.
You can use validation groups to control which type of user which field gets validated for. For example:
#NotBlank(groups = {GroupB.class})
private String phone;
#NotBlank(groups = {GroupA.class, GroupB.class})
private String username;
Then you use the headers from the request that you mentioned to decide which group to validate against.
See http://blog.codeleak.pl/2014/08/validation-groups-in-spring-mvc.html?m=1 for a complete example.
Updated to include a more comprehensive example:
public class Val {
private Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
public boolean isValid(User user, String userType) {
usergroups userGroup = usergroups.valueOf(userType);
Set<ConstraintViolation<User>> constraintViolations = validator.validate(user, userGroup.getValidationClass());
return constraintViolations.isEmpty();
}
public interface GroupA {}
public interface GroupB {}
public enum usergroups {
a(GroupA.class),
b(GroupB.class);
private final Class clazz;
usergroups(Class clazz) {
this.clazz = clazz;
}
public Class getValidationClass() {
return clazz;
}
}
}
This doesn't use application.yaml, instead the mapping of which fields are validated for each group is set in annotations, similar results using Spring's built in validation support.
I was able to solve my problem with the use of Jayway JsonPath.
My solution goes as follows:
Add a filter to your API which has the capability to cache the InputStream of the ServletRequest since it can be read only once. To achieve this, follow this link.
Create a bunch of validators and configure them in your application.yml file with the help of #ConfigurationProperties. To achieve this, follow this link
Create a wrapper which would contain all your validators as a list and initialize it with #ConfigurationProperties and the following configuration:
validators:
regexValidators:
-
target: $.userProfile.lastName
pattern: '[A-Za-z]{0,12}'
group: group-b
minMaxValidators:
-
target: $.userProfile.age
min: 18
max: 50
group: group-b
Call the validate method in this wrapper with the group which comes in the header, and then call the validate of the individual validators. To achieve this, I wrote the following piece of code in my wrapper:
public void validate(String input, String group) {
regexValidators.stream()
.filter(validator -> group.equals(validator.getGroup()))
.forEach(validator -> validator.validate(input));
minMaxValidators.stream()
.filter(validator -> group.equals(validator.getGroup()))
.forEach(validator -> validator.validate(input));
}
and the following method in my validator:
public void validate(String input) {
String data = JsonPath.parse(input).read(target);
if (data == null) {
throw new ValidationException("Target: " + target + " is NULL");
}
Matcher matcher = rule.matcher(data);
if (!matcher.matches()) {
throw new ValidationException("Target: " + target + " does not match the pattern: " + pattern);
}
}
I have created a functioning project to demonstrate the validations and it can be found here.
I understand that the answer alone might not be very clear, please follow the above mentioned url for the complete source code.

JSR-303 validation in Spring controller and getting #JsonProperty name

I do validation with JSR-303 in my Spring app, it works as needed.
This is an example:
#Column(nullable = false, name = "name")
#JsonProperty("customer_name")
#NotEmpty
#Size(min = 3, max = 32)
private String name;
And REST API clients use customer_name as name of input field that send to API bud validation field error org.springframework.validation.FieldError returns name as name of the field.
Is there some way hot to get JSON-ish name that is specified in #JsonProperty? Or do I have to implement own mapper to map class fields name into its JSON alternative?
Edit1: Renaming class fields into names that correspond to JSON names is not alternative (for many reasons).
This can now be done by using PropertyNodeNameProvider.
There is no way to achieve this currently. We have an issue for this in the reference implementation: HV-823.
This would address the issue on the side of Hibernate Validator (i.e. return the name you expect from Path.Node#getName()), it'd require some more checking whether Spring actually picks up the name from there.
Maybe you'd be interested in helping out with implemeting this one?
For MethodArgumentNotValidException and BindException I have written a method that tries to access the private ConstraintViolation from Spring ViolationFieldError via reflection.
/**
* Try to get the #JsonProperty annotation value from the field. If not present then the
* fieldError.getField() is returned.
* #param fieldError {#link FieldError}
* #return fieldName
*/
private String getJsonFieldName(final FieldError fieldError) {
try {
final Field violation = fieldError.getClass().getDeclaredField("violation");
violation.setAccessible(true);
var constraintViolation = (ConstraintViolation) violation.get(fieldError);
final Field declaredField = constraintViolation.getRootBeanClass()
.getDeclaredField(fieldError.getField());
final JsonProperty annotation = declaredField.getAnnotation(JsonProperty.class);
//Check if JsonProperty annotation is present and if value is set
if (annotation != null && annotation.value() != null && !annotation.value().isEmpty()) {
return annotation.value();
} else {
return fieldError.getField();
}
} catch (Exception e) {
return fieldError.getField();
}
}
This code can be used in methods handling BindExceptions #ExceptionHandler(BindException.class) within a Class with #ControllerAdvice:
#ControllerAdvice
public class ControllerExceptionHandler {
#ExceptionHandler(BindException.class)
public ResponseEntity<YourErrorResultModel> handleBindException(final BindException exception) {
for (FieldError fieldError : exception.getBindingResult().getFieldErrors()) {
final String fieldName = getJsonFieldName(fieldError);
...
}

TYPE_USE annotation in hibernate validator

I know hibernate validator supports TYPE_USE annotations: though it does not define its own, it lets you define and use custom ones.
I could define and validate correctly such an annotation (code soon), but then I want to map the error into a path that is used to display the error to the user.
Given then following sample
public class SampleTest {
private final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
public static class LimitedSizeStringValidator implements ConstraintValidator<LimitedSize, String> {
private LimitedSize constraint;
#Override
public void initialize(LimitedSize constraintAnnotation) {
this.constraint = constraintAnnotation;
}
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
String s = Ensure.notNull(value);
return s.length() >= constraint.min() &&
s.length() <= constraint.max();
}
}
#Retention(RUNTIME)
#Documented
#Target({TYPE_USE})
#Constraint(validatedBy = {LimitedSizeStringValidator.class})
public #interface LimitedSize {
String message() default "{javax.validation.constraints.Size.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
int min() default 0;
int max() default Integer.MAX_VALUE;
}
private static class TestBean {
#Valid
private Collection<#LimitedSize(max = 3) String> strings = new ArrayList<>();
#Valid
private Collection<InnerBean> beans = new ArrayList<>();
}
private static class InnerBean {
#Min(3)
private final int value;
private InnerBean(int value) {
this.value = value;
}
}
#Test
public void testBeanInvalid() {
TestBean testBean = new TestBean();
assertThat(validator.validate(testBean)).isEmpty();
testBean.strings.add("ok");
testBean.strings.add("ok2");
testBean.beans.add(new InnerBean(4));
assertThat(validator.validate(testBean)).isEmpty();
testBean.strings.add("not_ok");
testBean.beans.add(new InnerBean(2));
Set<ConstraintViolation<TestBean>> violations = validator.validate(testBean);
assertThat(violations).hasSize(2);
StreamSupport.stream(violations.spliterator(), false)
.forEach(v -> {
System.out.println(v.getPropertyPath());
System.out.println(v.getMessage());
v.getPropertyPath().forEach(p -> System.out.print("'" + p.getName() + (p.getIndex() != null ? "[" + p.getIndex() + "]" : "") + "' -> "));
System.out.println();
});
}
}
I would like map the errors in an object like
errors: [
["beans", "1", "value"],
["strings", "2"]
]
As in my sample, my approach at the moment is by navigating the violation path (http://docs.oracle.com/javaee/7/api/javax/validation/ConstraintViolation.html#getPropertyPath--) which works perfectly for the first case, but fails for the second (I cannot find a way to retrieve the index of the failing object). I think the reason is in the implementation of javax.validation.Path.PropertyNode in hibernate-validator (I am currently on version 5.2.4.Final, and the code looks the same as in the linked 5.2.1.Final. For reference:
#Override
public final Integer getIndex() {
if ( parent == null ) {
return null;
}
else {
return parent.index;
}
}
With TYPE_USE this approach cannot work in my opinion, because the failing object is a leaf, thus no child node can retrieve the index from it.
Nice enough, hibernate implementation of javax.validation.Path overrides the toString method is way such that violation.getPropertyPath().toString() is beans[1].value and strings[2] (in the sample code above).
So, to the question(s): is my navigation approach wrong and there is another way to extract such a mapping from the ConstraintViolation? Or is this a feature request for hibernate developers (I can see that before TYPE_USE annotations the getIndex approach they implemented was totally fine?
It just feels strange I am the first one with this problem (I tried to google and could not find anything related, the closest being: https://github.com/hibernate/hibernate-validator/pull/441) so I am wondering whether the mistake is mine rather than a hibernate limitation
I agree that the index should be set for that value and think you uncovered an issue in Hibernate Validator. Could you open an issue in our JIRA tracker?
Btw. the notion of TYPE_USE level constraints will be standardized as of Bean Validation 2.0. So there may be some more changes coming up in this area, specifically I'm wondering what Kind that node should have (currently it's PROPERTY which seems questionable).

Where to find a NotNull java annotation

I searched for a #NotNull java annotation and found the one from javax. I tried to use it but ran into the same issues described here. In short: I need to set up some stuff to get it working - but I actually don't need all that (I am in plain Java/Groovy context, no JavaEE). Are there alternatives to the mentioned annotation which work standalone, where to find those?
oval can help you with this.
download the jar from http://mvnrepository.com/artifact/net.sf.oval/oval/1.31 and look at the documentation here http://oval.sourceforge.net/
for example:
import net.sf.oval.constraint.MaxLength;
import net.sf.oval.constraint.NotEmpty;
import net.sf.oval.constraint.NotNull;
public class Request {
#NotNull
#NotEmpty
#MaxLength(value = 30)
private String id;
//.....getters setters......
}
above will be your pojo
/**
* Method returns if valid request or not
*/
private boolean isValid(Request request) {
List<ConstraintViolation> violations = validator.validate(request);
if (violations.size() > 0) {
return false;
} else {
return true;
}
}
and will do validation like above.
you can also find many more examples online.
You can use the validation quite fine just with groovy. There is the hibernate-validator implementation. e.g.
#Grapes([
#Grab('javax.validation:validation-api:1.1.0.Final'),
#Grab('org.glassfish:javax.el:3.0.0'),
#Grab('org.hibernate:hibernate-validator:5.1.3.Final'),
])
import javax.validation.*
import javax.validation.constraints.*
class Pogo {
#NotNull
String name
#NotNull
#Min(1L)
Long size
}
def validator = Validation.buildDefaultValidatorFactory().getValidator()
println validator.validate(new Pogo()).collect{ "Error on $it.propertyPath: $it.message" }
//; [Error on name: may not be null, Error on size: may not be null]
println validator.validate(new Pogo(name:"x", size:0)).collect{ "Error on $it.propertyPath: $it.message" }
//; [Error on size: must be greater than or equal to 1]

Categories

Resources