I am currently investigating using the hibernate validators support for Method validation using JSR 303 annotations. An initial attempt tried to wrap the resources with a proxy (generated using cglib) that performed the validation, however, this appears to have come to a dead-end, as the proxy methods I have tried do not seem to copy parameter annotations, so resource methods that rely on this end up being called with no parameters. I have a开发者_StackOverflow社区nother question open at the moment on that item:
How can I create a dynamic proxy in java that retains parameter annotations on methods?
Is there an alternative mechanism to hook into wink's request chain to do something like this without using proxies?
You can do this is a partially supported way by creating a RequestHandler, and overriding the actual HandlersFactory to return a list containing your request handler. The configuration for this is discussed here. This request handler will be inserted into the request handling chain directly before the InvokeMethodHandler (which is the last request handler called, and this is the one which actually invokes the resource method).
Based on reading the source code for the InvokeMethodHandler (which actually calls your JAX/RS resource), you can get the parameters, instance and method parameters as follows:
// Get Method Validator from hibernate
MethodValidator validator = Validation.byProvider(HibernateValidator.class).configure()
.buildValidatorFactory().getValidator().unwrap(
MethodValidator.class);
// Extract the method parameters, object instance and method metadata from the JAX/RS internals.
Method javaMethod = null;
Object instance = null;
Object[] parameters = null;
SearchResult searchResult = context.getAttribute(SearchResult.class);
javaMethod = searchResult.getMethod().getMetadata()
.getReflectionMethod();
parameters = searchResult.getInvocationParameters();
instance = searchResult.getResource().getInstance(context);
// Use all this to perform validation...
Set<MethodConstraintViolation<Object>> violations = validator
.validateAllParameters(instance, javaMethod, parameters);
if (!violations.isEmpty()) {
// do something with the violations here
}
This is somewhat hacky, as it relies on (undocumented as far as I can tell) implementation details of wink to get the instance, parameters, and metadata (it would be nice if they provided a public way to get to this information). However, it is somewhat preferable to using a proxy, because you don't infer multiple overheads from reflection that occur with a proxy.
I managed to perform bean validation with wink by adding EJB Interceptor. Here I used Openjpa validator as a provider:
@Interceptor
public class ValidationInterceptor {
Validator validator = Validation.byProvider(ApacheValidationProvider.class).configure().buildValidatorFactory().getValidator();
@AroundInvoke
public Object validate(InvocationContext ctx) throws Exception {
Object[] parameters = ctx.getParameters();
Set<ConstraintViolation<Object>> validateResult = new HashSet<>();
for (Object parameter : parameters) {
Set<ConstraintViolation<Object>> validateParam = validator.validate(parameter);
validateResult.addAll(validateParam);
}
if (!validateResult.isEmpty()) {
throw new RestValidationException(validateResult);
}
return ctx.proceed();
}
}
Custom exception to be pretty output:
public class RestValidationException extends WebApplicationException {
List<String> validationErrors = new ArrayList<String>();
public RestValidationException(Set<? extends ConstraintViolation<?>> violations) {
for(ConstraintViolation<?> constraintViolation : violations) {
String error = constraintViolation.getPropertyPath().toString() + ": " + constraintViolation.getMessage();
validationErrors.add(error);
}
}
@Override
public Response getResponse() {
return Response.status(Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(validationErrors).build();
}
}
Now you have to annotate your REST resources with @Interceptors(ValidationInterceptor.class) and all parameters are validated before entering the method. If you are not using EJB, you can intercept it with cglib's MethodInterceptor (Spring can help here).
精彩评论