Hi all,
I am working on the section explaining how a interception technology
integrates with Bean Validation.
The proposal is also on the web site at
http://beanvalidation.org/proposals/BVAL-274/
## Problem description
I was looking at how to compute whether or not method interception needs
to occur via on the metadata API.
The validation of method-level constraints comprises the following steps:
- Intercept the method call to be validated
- Validate the parameter values provided by the method caller using
Validator#validateParameters() or Validator#validateConstructorParameters().
- If this validation yields a non-empty set of constraint violations, throw a
ConstraintViolationException wrapping the violations. Otherwise proceed with the actual
method invocation.
- Validate the result returned by the invoked method using Validator#validateReturnValue()
or Validator#validateConstructorReturnValue().
- If this validation yields a non-empty set of constraint violations, throw a
ConstraintViolationException wrapping the violations. Otherwise return the invocation
result to the method caller.
I realised that we have a cumbersome API to detect whether or not
calling Bean Validation.
```
public boolean interceptMethod(Class<?> type, Method method) {
BeanDescriptor bean = validator.getConstraintsForClass( type );
MethodDescriptor methodDescriptor = bean.getConstraintsForMethod( method.getName(),
method.getParameterTypes() );
return methodDescriptor != null;
}
public boolean validateMethodParametersWithFullNavigation(Class<?> type, Method
method) {
BeanDescriptor bean = validator.getConstraintsForClass( type );
MethodDescriptor methodDescriptor = bean.getConstraintsForMethod( method.getName(),
method.getParameterTypes() );
if ( methodDescriptor != null ) {
boolean validate = false;
for (ParameterDescriptor paramDescriptor : methodDescriptor.getParameterDescriptors())
{
validate = paramDescriptor.hasConstraints() || paramDescriptor.isCascaded();
if (validate) {
break;
}
}
return validate;
}
else {
return false;
}
}
public boolean validateReturnValueWithFullNavigation(Class<?> type, Method method)
{
BeanDescriptor bean = validator.getConstraintsForClass( type );
MethodDescriptor methodDescriptor = bean.getConstraintsForMethod( method.getName(),
method.getParameterTypes() );
if ( methodDescriptor != null ) {
boolean validate = false;
ReturnValueDescriptor returnValue = methodDescriptor.getReturnValueDescriptor();
if ( returnValue!=null ) {
return returnValue.isCascaded() || returnValue.hasConstraints();
}
else {
return false;
}
}
else {
return false;
}
}
```
`interceptMethod` is used to decide whether or not we intercept the method
at all. it can be used to disable the interceptor entirely.
`validateMethodParametersWithFullNavigation` is used to decide whether
or not we need to call `validator.validateParameters()`. If no violation
is found, we goa nd execute the method.
After the method returns, we call
`validateReturnValueWithFullNavigation` to decide whether or not to
validate return value.
There is of course the equivalent for constructor validation.
## Proposal
What I am proposing is to add two aggregate methods to
`MethodDescriptor` and `ConstructorDescriptor`.
```
/**
* Returns true if method parameters are constrained either:
* - because of a constraint on at least one of the parameters
* - because of a cascade on at least one of the parameters (via {@code @Valid})
* - because of at least a cross-parameter constraint
*/
boolean isConstrainedOnParameters();
/**
* Returns true if the method return value is constrained either:
* - because of a constraint on the return value
* - because validation is cascaded on the return value (via {@code @Valid})
*/
boolean isConstrainedOnReturnValue();
```
The implementation would then become
```
public boolean validateMethodParametersWithMethodDescriptorShortcuts(Class<?> type,
Method method) {
BeanDescriptor bean = validator.getConstraintsForClass( type );
MethodDescriptor methodDescriptor = bean.getConstraintsForMethod( method.getName(),
method.getParameterTypes() );
if ( methodDescriptor != null ) {
return methodDescriptor.isConstrainedOnParameters();
}
else {
return false;
}
}
public boolean validateMethodReturnValueWithMethodDescriptorShortcuts(Class<?>
type, Method method) {
BeanDescriptor bean = validator.getConstraintsForClass( type );
MethodDescriptor methodDescriptor = bean.getConstraintsForMethod( method.getName(),
method.getParameterTypes() );
if ( methodDescriptor != null ) {
return methodDescriptor.isConstrainedOnReturnValue();
}
else {
return false;
}
}
```
Of course the lookup of `BeanDescriptor` and `MethodDescriptor` only has
to be done once. The methods result is also likely being cached by the
interceptor being placed and these calls are probably only happening at
initialization time.
It seems to be it goes in the right
direction and expose things at a higher level for the metadata API
users.
Anyone against the proposed change?
An alternative solution is to use the fluent API to find constraints
public boolean validateMethodParametersWithFindConstraintAPI(Class<?> type,
Method method) {
BeanDescriptor bean = validator.getConstraintsForClass( type );
MethodDescriptor methodDescriptor = bean.getConstraintsForMethod(
method.getName(), method.getParameterTypes() );
if ( methodDescriptor != null ) {
return methodDescriptor.findConstraints().declaredOn( ElementType.PARAMETER
).hasConstraints();
}
else {
return false;
}
}
But it's not 100% in line with the `findConstraints()` API: it should only return
constraints
that are on the element. Plus what is returned by the API is
`ConstraintDescriptor` which cannot differentiate where it comes from (parameter or
method).
Emmanuel