Spring MVC: use different JSR-303 validators on the same bean? - java

I'm using Constraint Validators (JSR-303) on my beans in a Spring MVC Controller. Given the following java bean:
public class EntityDTO {
protected Long id;
protected Integer version;
protected String someOtherField;
// getters and setters
}
I have 2 different types of constraint validation I would like to perform. Either both id and version are null, or both are non-null.
I can create 2 different constraint validators and assign to 2 different annotations: DTOEntityFieldsEmpty & DTOEntityFieldsNotEmpty. But then I have to specify the validator annotation at the bean level.
#DTOEntityFieldsEmpty
public class EntityDTO {
....
}
However, I'm looking to specify the validator that I want to use in the actual controller method level. Under normal circumstances, my method would be:
public void updateData( #RequestBody #Valid EntityDTO dto){
...
}
where the #Valid annotation will apply the Validator that is defined in the EntityDTO object. But I'm looking to see if there is a way I can either pass a parameter at the #Valid request, or specify the validator to use.
// #Valid annotation not supported this way
public void updateData( #RequestBody #Valid(validator=DTOEntityFieldsEmpty.class) EntityDTO dto){
...
}
Is there anything I can do to get around this? I realize that I can use Spring's #InitBinder, but that will bind a validator to the entire Controller, and not just one specific method.
I've checked both JSR-303 1.0 and 1.1, and don't see anything that jumps out at me to handle this circumstance. Similarly, I can't find anything in the Spring 3 or 4 docs either. I wonder if there might be a way using Group validation, but not entirely sure. I would need to be able to know which validator was successful or failed in the controller, and that seems a little hacky to me.

You can use Validation Groupes. Therefore you need to assign the constraints at the fields to groups and then you need to tell spring which group to validate (this can be one or more groupes), therefore Spring 3.1 introduced the #Validated annotation.
public interface GroupA { } //empty marker interface
public interface GroupB { }
#DTOEntityFieldsEmpty(groups=GroupA.class)
#DTOEntityFieldsNotEmpty(groups=GroupB.class)
public class EntityDTO {
....
}
public void updateData(
#RequestBody #Validated({ GroupA.class }) EntityDTO dto){
...
}

Related

Endpoint Validation(#Valid, #Pattern, etc) with Optional

We have a need to have certain objects be wrapped in an optional because we need to know three states:
Missing (not present)
Null (present but null)
Data (present with data)
Currently we are wrapping these fields with Optional because spring rest endpoint will give us these states as null, Optional.empty() and Optional.of(value).
However, it does not appear that the validations are working. Do I need to do something different than just add #Valid, #Pattern, #NotNull, etc??
Note: there is a more detail breakdown of the need in this question (Java Spring (Jackson) Annotation for Not Present, Present but Null, and Present with Value)
Add javax.validation dependency
Below are the 3 steps to add validation.
Add #NonNull on the data member in the POJO class
class Employee
{
#NonNull
String name;
}
Create following method
private void validate(Employee emp){
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<Employee>> violations = validator.validate(emp)
for(ConstraintViolation<Employee> violation: violations) {
//Your action. May be throw exception
}
}
Add #Valid where the object is used and invoke the validate method
void method(#Valid Employee emp){
validate(emp);
}
So the best solution that I found was to use Jakarta validations instead of Javax because the Jakarta will look into the optional field and apply the validation annotations.

Separate validation annotation and it's validator in separate modules

Situation: you have module with DTO objects used in your API, so that other project(s) can reuse then when sending requests. These DTO classes does have bean-validation annotations in them. And you would like to use your custom validations to validate DTO "arriving" via requests. The sender typically does not validate outgoing data, IIUC, and might not be interested in importing validators along with annotations.
Problem(?): bean-validation is defined in a way, where annotation defines who implements it (which is incorrect and it should be otherwise around imo), with possibility to specify empty array as annotation validator (seems like hack) and then pairing is done via manual hashmap manipulations instead of stuff like service loader etc.
How do you do this?
Would you split annotation and it's validator in separate modules?
How would you bind them together? I think it should be possible to use {} as validator and then use org.hibernate.validator.internal.metadata.core.ConstraintHelper#putValidatorDescriptors to bind them together, but I did not test it yet + maybe there is better way...
I agree that the annotation defining the validator does feel backwards. While not ideal, I've been able to work around this by separating my custom ConstraintValidator into an interface and implementation.
Example:
In api module define constraint and interface validator
#Constraint(validatedBy = MyConstraintValidator.class)
public #interface MyConstraint
{
}
public interface MyConstraintValidator
extends ConstraintValidator<MyConstraint, String>
{
}
In your service module define the implementation
public class MyConstraintValidatorImpl implements MyConstraintValidator
{
private FooService foo;
#Override
public boolean isValid( String value, ConstraintValidatorContext ctx)
{
// Implement your constraint logic
}
}
I we need to separate interface class and validator implementation into separate modules, it's possible. And even in a way, which I said in original question, that should be used. In API module you declare validation for example as:
#Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
#Retention(RUNTIME)
#Documented
#Constraint(validatedBy = {})
#SupportedValidationTarget(ValidationTarget.ANNOTATED_ELEMENT)
#ReportAsSingleViolation
public #interface AnyUuid {
//...
notice validatedBy = {}. The validator implementation looks like:
public class AnyUuidValidator implements ConstraintValidator<AnyUuid, Object> {
//...
and pairing can be setup using service loader(see javadoc if you don't know how that works). Put into file META-INF/services/javax.validation.ConstraintValidator FQDN of AnyUuidValidator shown above. And all other custom validators. And that should be it.
There is a bug I found with this. If I'm not mistaken. If you have DTO you cannot change (~annotate with constraints) and still want to validate them via bean validation, you can register validation definitions via xml file. If you are doing so and use service loader for pairing definition and implementation of custom validators, there is probably some bug and your custom validators won't be found. So verify this scenario before relying on service loader. But maybe I'm wrong, for me it was feasible to drop this validation trivially so I did to save some time and could ignore this.

How to throw Validate in a method with javax.validation and without #Validate annotation

I am developing a service in Java and with spring Framework, that uses javax.validation annotations in the methods, like this example:
public void method(
#NotNull #Positive Integer val1,
#NotNull #PastOrPresent Date startDate,
#NotNull #PastOrPresent Date endDate);
When I write the annotation #Validated in the class, it work fine, however I have some problems with Circular dependencies in some classes StackOverFlow and the annotation #Validated produces UnsatisfiedDependencyException errors. (I will not be able to change to #Lazy, but in spite of I have to validate with javax.validation annotation).
Therefor, I want to validate the method programmatic, however I have not found the way to do it for methods yet, I Only was able to from class (when I use Dto class), with the method validate of Validated class.
Does someone can tell me how to do it.
Regards.
Updated 11/09/2019:
I have found a way to do it from the follows method:
private <T> void validate(T currentObject, Method currentMethod, Object... parameterValues) {
Set<ConstraintViolation<T>> errors=null;
ExecutableValidator executableValidator = validator.forExecutables();
errors = executableValidator.validateParameters(currentObject, currentMethod, parameterValues);
System.out.println((errors != null && !errors.isEmpty())?"There are exceptions":"There are not exceptions");
}
If someone can do it with reflections and easiest, I will thanks you.
when you use the method, you can insert in the first and second argument: this and (new Object(){}).getClass().getEnclosingMethod(), respectively.

Spring Boot method validation

Problem
I have the following constraints on userUuid and itemUuid:
Both strings must not be null.
Both strings must be UUIDs (eg. f1aecbba-d454-40fd-83d6-a547ff6ff09e).
The composition (userUuid, itemUuid) must be unique.
I tried to implement the validation in my controller like:
#RestController
#Validated // (1)
public class CartItemController {
#PostMapping("/me/carts/{itemUuid}")
#ResponseStatus(HttpStatus.CREATED)
public void addItem(#PathVariable("itemUuid") String itemUuid,
Authentication auth) {
CartItemId id = getCartItemId(getUserUuidFrom(auth), itemUuid);
...
}
#Unique // (4)
public CartItemId getCartItemId(#NotNull #Uuid String userUuid, // (2)
#NotNull #Uuid String itemUuid) { // (3)
return new CartItemId(userUuid, itemUuid);
}
...
}
#Uuid and #Unique are custom constraints. Method validation is enabled in (1). (2) are the contraints for the user UUID. (3) are the constraints for the item UUID. The unique constraint is applied to the returned CartItemId in (4). However, the parameters and the return value are never validated. Neither for the standard #NotNull constraint nor for my custom constraints. I receive HTTP status 201 Created instead of 400 Bad Request.
What am I doing wrong?
Stuff that works
The following code works for the item UUID:
#RestController
#Validated
public class CartItemController {
#PostMapping("/me/{itemUuid}")
#ResponseStatus(HttpStatus.CREATED)
public void addItem(#PathVariable("itemUuid") #Uuid String itemUuid, // (1)
Authentication auth) {
...
}
}
Adding the #Uuid to the path variable parameter works. Values like anInvalidUuid are rejected. I also tested the #Unique constraint in other use cases and it worked perfectly.
What is the difference between addItem() and toId()?
Versions
I am using Java 1.8 and Spring Boot 2.0.0.RELEASE. org.hibernate.validator:hibernate-validator:6.0.7.Final is on my classpath.
Validation of method arguments is based on AOP: a validating proxy intercepts the method call and validates the argument before delegating (if everything is valid), to the actual method.
You're calling the getCartItemId() method from another method of the same class. So the method call doesn't go through the proxy. Only inter-bean calls can be intercepted.
So, in short, getCartItemId should be in a separate bean, injected into your controller.

Bind #PostMapping to multiple parameters instead of model bean?

When using #GetMapping, I could bind each get-query parameter to one method parameter with #RequestParam annotation.
The following does not work, it would only be valid with #GetMapping:
//#PostMapping("/search")
#GetMapping("/search")
public void search(#RequestParam String origin, #RequestParam destination) {
}
Question: how can I achieve the same with #PostMapping?
Or do I always have to use a model bean like:
#PostMapping("/search")
public void search(#RequestBody model) {
}
The two ways are different, if the payload contains an object representing a serializable entity you should go for the second way and let jackson handle the deserialization for you, if not you can use the first one or you can build an entity for that , both works

Categories