From b375bbc82c6225612f4a31e2bd2725aa3c4a67ad Mon Sep 17 00:00:00 2001 From: Jozef Hartinger Date: Fri, 5 Nov 2010 15:39:22 +0100 Subject: [PATCH] Further Bean Validation integration Added caching of method metadata so that the method and its parameters is not scanned for annotations everytime it is invoked. --- .../validation/ValidatedMethodMetadata.java | 66 ++++++++++++++ .../validation/ValidationInterceptor.java | 85 ++++++++++++++----- .../rest/validation/ValidationMetadata.java | 54 ++++++++++++ .../seam/rest/test/validation/FormObject.java | 4 + 4 files changed, 186 insertions(+), 23 deletions(-) create mode 100644 impl/src/main/java/org/jboss/seam/rest/validation/ValidatedMethodMetadata.java create mode 100644 impl/src/main/java/org/jboss/seam/rest/validation/ValidationMetadata.java diff --git a/impl/src/main/java/org/jboss/seam/rest/validation/ValidatedMethodMetadata.java b/impl/src/main/java/org/jboss/seam/rest/validation/ValidatedMethodMetadata.java new file mode 100644 index 0000000..b268992 --- /dev/null +++ b/impl/src/main/java/org/jboss/seam/rest/validation/ValidatedMethodMetadata.java @@ -0,0 +1,66 @@ +/* + * JBoss, Home of Professional Open Source + * Copyright 2010, Red Hat, Inc., and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.jboss.seam.rest.validation; + +import java.lang.reflect.Method; +import java.util.Set; + +/** + * Caches method metadata needed to perform validation of JAX-RS requests. + * @author Jozef Hartinger + * + */ +public class ValidatedMethodMetadata +{ + private final Method method; + private final Integer messageBody; // position of the messageBody, may be null + private final Set parameterObjects; // positions of parameter objects + private final ValidateRequest interceptorBinding; + + public ValidatedMethodMetadata(Method method, Integer messageBody, Set parameterObjects, ValidateRequest interceptorBinding) + { + this.method = method; + this.messageBody = messageBody; + this.parameterObjects = parameterObjects; + this.interceptorBinding = interceptorBinding; + } + + public Method getMethod() + { + return method; + } + + public Integer getMessageBody() + { + return messageBody; + } + + public Set getParameterObjects() + { + return parameterObjects; + } + + public ValidateRequest getInterceptorBinding() + { + return interceptorBinding; + } +} diff --git a/impl/src/main/java/org/jboss/seam/rest/validation/ValidationInterceptor.java b/impl/src/main/java/org/jboss/seam/rest/validation/ValidationInterceptor.java index 24148f0..e82c1d0 100644 --- a/impl/src/main/java/org/jboss/seam/rest/validation/ValidationInterceptor.java +++ b/impl/src/main/java/org/jboss/seam/rest/validation/ValidationInterceptor.java @@ -23,6 +23,7 @@ import java.io.Serializable; import java.lang.annotation.Annotation; +import java.lang.reflect.Method; import java.util.HashSet; import java.util.Set; @@ -51,59 +52,97 @@ public class ValidationInterceptor implements Serializable @Inject private Validator validator; + @Inject + private ValidationMetadata metadata; @AroundInvoke public Object intercept(InvocationContext ctx) throws Exception { - log.debugv("Intercepting {0}", ctx.getMethod().toGenericString()); + log.infov("Validating {0}", ctx.getMethod().toGenericString()); // TODO + + // do scanning only once + if (! metadata.containsMethodMetadata(ctx.getMethod())) + { + scanMethod(ctx.getMethod()); + } Set> violations = new HashSet>(); - ValidateRequest interceptorBinding = getInterceptorBinding(ctx); + ValidatedMethodMetadata method = metadata.getMethodMetadata(ctx.getMethod()); + ValidateRequest interceptorBinding = method.getInterceptorBinding(); Class[] groups = interceptorBinding.groups(); - + // validate JAX-RS resource fields if (interceptorBinding.validateResourceFields()) { + log.infov("Validating JAX-RS resource {0}", ctx.getTarget()); // TODO violations.addAll(validator.validate(ctx.getTarget(), groups)); } - Annotation[][] parameterAnnotations = ctx.getMethod().getParameterAnnotations(); - for (int i = 0; i < parameterAnnotations.length; i++) + // validate message body + if (interceptorBinding.validateMessageBody() && (method.getMessageBody() != null)) { - if (interceptorBinding.validateMessageBody() && parameterAnnotations[i].length == 0) - { - log.debugv("Validating HTTP message body {0}", ctx.getParameters()[i]); - // entity body - violations.addAll(validator.validate(ctx.getParameters()[i], groups)); - } - - if (interceptorBinding.validateParameterObjects() && isParameterObject(ctx.getMethod().getParameterTypes()[i], ctx.getMethod().getParameterAnnotations()[i])) + Object parameter = ctx.getParameters()[method.getMessageBody()]; + log.infov("Validating HTTP message body {0}", parameter); // TODO + violations.addAll(validator.validate(parameter, groups)); + } + + // validate parameter objects + if (interceptorBinding.validateParameterObjects()) + { + for (Integer parameterIndex : method.getParameterObjects()) { - // parameter objects - log.debugv("Validating parameter object {0}", ctx.getParameters()[i]); - violations.addAll(validator.validate(ctx.getParameters()[i], groups)); + Object parameter = ctx.getParameters()[parameterIndex]; + log.infov("Validating parameter object {0}", parameter); // TODO + violations.addAll(validator.validate(parameter, groups)); } } - + if (violations.isEmpty()) { - log.debug("Validation completed. No violations found."); + log.info("Validation completed. No violations found."); // TODO return ctx.proceed(); } else { - log.debugv("Validation completed. {0} violations found.", violations.size()); + log.infov("Validation completed. {0} violations found.", violations.size()); // TODO throw new ValidationException(violations); } } + + private void scanMethod(Method method) + { + Integer messageBodyIndex = null; + Set parameterObjects = new HashSet(); + ValidateRequest interceptorBinding = getInterceptorBinding(method); + + log.infov("This is the first time {0} is invoked. Scanning.", method); // TODO + + Annotation[][] parameterAnnotations = method.getParameterAnnotations(); + for (int i = 0; i < parameterAnnotations.length; i++) + { + if (parameterAnnotations[i].length == 0) + { + log.infov("{0} identified as the message body.", method.getParameterTypes()[i]); // TODO + messageBodyIndex = i; + continue; + } + + if (isParameterObject(method.getParameterTypes()[i], method.getParameterAnnotations()[i])) + { + log.infov("{0} identified as the parameter object.", method.getParameterTypes()[i]); + parameterObjects.add(i); + } + } + metadata.addMethodMetadata(new ValidatedMethodMetadata(method, messageBodyIndex, parameterObjects, interceptorBinding)); + } - private ValidateRequest getInterceptorBinding(InvocationContext ctx) + private ValidateRequest getInterceptorBinding(Method method) { - ValidateRequest interceptorBinding = Annotations.getAnnotation(ctx.getMethod(), ValidateRequest.class); + ValidateRequest interceptorBinding = Annotations.getAnnotation(method, ValidateRequest.class); if (interceptorBinding == null) { - log.debugv("Unable to find @ValidateRequest interceptor binding for {0}", ctx.getMethod().toGenericString()); + log.debugv("Unable to find @ValidateRequest interceptor binding for {0}", method.toGenericString()); // There is no @Validate on the method // The interceptor is probably bound to the bean by @Interceptors // annotation @@ -124,7 +163,7 @@ private boolean isParameterObject(Class parameterType, Annotation[] annotatio } // primitive type parameters are definitely not form objects - if (parameterType.isPrimitive() || isPrimitiveWrapper(parameterType)) + if (parameterType.isPrimitive() || isPrimitiveWrapper(parameterType) || String.class.isAssignableFrom(parameterType)) { return false; } diff --git a/impl/src/main/java/org/jboss/seam/rest/validation/ValidationMetadata.java b/impl/src/main/java/org/jboss/seam/rest/validation/ValidationMetadata.java new file mode 100644 index 0000000..d254b74 --- /dev/null +++ b/impl/src/main/java/org/jboss/seam/rest/validation/ValidationMetadata.java @@ -0,0 +1,54 @@ +/* + * JBoss, Home of Professional Open Source + * Copyright 2010, Red Hat, Inc., and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.jboss.seam.rest.validation; + +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + +import javax.enterprise.context.ApplicationScoped; + +/** + * Container for {@link ValidatedMethodMetadata}. + * @author Jozef Hartinger + * + */ +@ApplicationScoped +public class ValidationMetadata +{ + private Map methods = new HashMap(); + + public ValidatedMethodMetadata getMethodMetadata(Method method) + { + return methods.get(method); + } + + void addMethodMetadata(ValidatedMethodMetadata method) + { + methods.put(method.getMethod(), method); + } + + public boolean containsMethodMetadata(Method method) + { + return methods.containsKey(method); + } +} diff --git a/impl/src/test/java/org/jboss/seam/rest/test/validation/FormObject.java b/impl/src/test/java/org/jboss/seam/rest/test/validation/FormObject.java index ff1eb03..36423cc 100644 --- a/impl/src/test/java/org/jboss/seam/rest/test/validation/FormObject.java +++ b/impl/src/test/java/org/jboss/seam/rest/test/validation/FormObject.java @@ -21,6 +21,10 @@ */ package org.jboss.seam.rest.test.validation; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) @interface FormObject {