SyntaxHighlighter

Friday, May 28, 2010

Getting the classpath with Groovy

In a recent Groovy project, I needed to get the classpath that was used to start the program so I could pass it as a command-line parameter to another program being launched.

You can easily get the the list of classpath URLs used by the program's classloader. Most of the hints I found showed how to do this from a Groovy script, but it appears to work a little differently in a program.

Here is the code I came up with to get the classpath in a script:

def getClassPath() {
   def loaderUrls = this.class.classLoader.rootLoader.URLs
   def files = loaderUrls.collect { new URI(it.toString()).path - '/'}
   return files.join(File.pathSeparator)
}

println getClassPath()

And here is the same code in a program (i.e. a class with a main):

class ClassPathTestClass {
   static void main(args) {
      println new ClassPathTestClass().getClassPath()
   }

   def getClassPath() {
      def loaderUrls = this.class.classLoader.URLs
      def files = loaderUrls.collect { new URI(it.toString()).path - '/' }
      return files.join(File.pathSeparator)
   }
}

The difference is in how the list of URLs is initially retrieved. From a script you can get the list with "this.class.classLoader.rootLoader.URLs". In a program you use "this.class.classLoader.URLs".

The second line in each of the "getClassPath" methods took more tweaking than expected. The "new URI(it.toString()).path - '/'" syntax removes any encoding from the URL paths and strips everything but the path out of the URL. This has only been tested on Windows, I'm not sure if this will work exactly the same on other platforms.

Monday, May 10, 2010

Spring 3 Validation Aspect

Update: The problem that this article addresses has been fixed in Spring 3.1. If possible, upgrade to Spring 3.1 and skip the work-around below. See SPR-6709 for more details.

This post is a follow-up to a thread on the Spring Framework support forums. The thread discusses an issue with Spring 3 and the JSR-303 style of annotation-based validation.

To summarize the thread: the @Valid annotation can be used to trigger validation of a parameter to a method of an MVC Controller, but the validation is not properly invoked when the @Valid annotation is used along with the @RequestBody annotation. Without the @RequestBody annotation, the Spring DataBinder mechanism - with full @Valid support - is used to unmarshal the input message to the parameter object. With the @RequestBody annotation, the MessageConverter mechanism - without any @Valid support - is used instead of the DataBinder mechanism.

The issue is also documented in the Spring issue tracking system.

While waiting for Spring to address this gap, several people have developed work-arounds using Spring AOP. I also decided to use this approach. One of my goals in a work-around was to come up with a solution that was very easy to back out when Spring fixes the problem with @RequestBody and @Valid. The AOP-based approach meets this goal, since we can simply remove the aspect when the framework has proper support for this combination of annotations.

There are a few ways to implement an aspect for this, including one posted to the Spring forum thread by user @taku. The implementations are similar but differ in details like how controller methods are intercepted and how validation errors are dealt with.

The aspect my project is using will intercept a call to a method of a Spring-managed bean when the method has the @RequestMapping annotation on it. (Other approaches intercept methods that following certain naming convention or methods of controllers in certain packages.) Each parameter of an intercepted method is inspected, and an injected validator is called for every parameter that has both the @RequestBody and @Valid annotation on it.

If any method parameter fails validation, an HttpMessageConversionException will be thrown. This exception can then be caught and handled by the framework or by @ExceptionHandler methods in a controller. BindException would have been a more natural exception to throw, but it is a checked exception so it cannot be easily thrown from the aspect.

Here is the code for the aspect:

RequestBodyValidatorAspect.java:
@Aspect
public class RequestBodyValidatorAspect {
  private Validator validator;

  @Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
  private void controllerInvocation() {
  }

  @Around("controllerInvocation()")
  public Object aroundController(ProceedingJoinPoint joinPoint) throws Throwable {

    MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
    Method method = methodSignature.getMethod();
    Annotation[][] argAnnotations = method.getParameterAnnotations();
    String[] argNames = methodSignature.getParameterNames();
    Object[] args = joinPoint.getArgs();

    for (int i = 0; i < args.length; i++) {
      if (hasRequestBodyAndValidAnnotations(argAnnotations[i])) {
        validateArg(args[i], argNames[i]);
      }
    }

    return joinPoint.proceed(args);
  }

  private boolean hasRequestBodyAndValidAnnotations(Annotation[] annotations) {
    if (annotations.length < 2)
      return false;

    boolean hasValid = false;
    boolean hasRequestBody = false;

    for (Annotation annotation : annotations) {
      if (Valid.class.isInstance(annotation))
        hasValid = true;
      else if (RequestBody.class.isInstance(annotation))
        hasRequestBody = true;

      if (hasValid && hasRequestBody)
        return true;
    }
    return false;
  }

  @SuppressWarnings({"ThrowableInstanceNeverThrown"})
  private void validateArg(Object arg, String argName) {
    BindingResult result = getBindingResult(arg, argName);
    validator.validate(arg, result);
    if (result.hasErrors()) {
      throw new HttpMessageConversionException("Validation of controller input parameter failed",
              new BindException(result));
    }
  }

  private BindingResult getBindingResult(Object target, String targetName) {
    return new BeanPropertyBindingResult(target, targetName);
  }

  @Required
  public void setValidator(Validator validator) {
    this.validator = validator;
  }
}

One limitation with this work-around is that it can only apply a single validator to all controllers. If you are using JSR-303 style annotation-based validation exclusively then this is not a problem - you just inject a LocalValidatorFactoryBean into the aspect. If you need to use a mix of annotation-based validation and class-based validation, this becomes a problem. 

To get around this limitation and make this AOP-based approach more flexible, I also implemented a meta-validator that finds all Validator classes in the application context and calls the appropriate validator for the type of object being validated. This meta-validator can then be injected into the aspect (and into the DataBinder). All other validators are just declared as beans in the app context.

Here is the code for the meta-validator:

public class TypeMatchingValidator implements Validator, InitializingBean, ApplicationContextAware {
  private ApplicationContext context;
  private Collection validators;

  public void afterPropertiesSet() throws Exception {
    findAllValidatorBeans();
  }

  public boolean supports(Class clazz) {
    for (Validator validator : validators) {
      if (validator.supports(clazz)) {
        return true;
      }
    }

    return false;
  }

  public void validate(Object target, Errors errors) {
    for (Validator validator : validators) {
      if (validator.supports(target.getClass())) {
        validator.validate(target, errors);
      }
    }
  }

  private void findAllValidatorBeans() {
    Map<String, Validator> validatorBeans =
            BeanFactoryUtils.beansOfTypeIncludingAncestors(context, Validator.class, true, false);
    validators = validatorBeans.values();
    validators.remove(this);
  }

  public void setApplicationContext(ApplicationContext context) throws BeansException {
    this.context = context;
  }
}


Here is an example of a Spring XML configuration file using the validator aspect and the meta-validator together:

<!-- enable Spring AOP support -->  
  <aop:aspectj-autoproxy proxy-target-class="true"/>

  <!-- declare the validator aspect and inject the validator into it -->
  <bean id="validatorAspect" class="com.something.RequestBodyValidatorAspect">
    <property name="validator" ref="validator"/>
  </bean>

  <!-- inject the validator into the DataBinder framework -->
  <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
    <property name="webBindingInitializer">
      <bean class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer" p:validator-ref="validator"/>
    </property>
  </bean>

  <!-- declare the meta-validator bean -->
  <bean id="validator" class="com.something.TypeMatchingValidator"/>

  <!-- declare all Validator beans, these will be discovered by TypeMatchingValidator -->
  <bean class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
  <bean class="com.something.PersonValidator"/>
  <bean class="com.something.AccountValidator"/>

Friday, April 30, 2010

Introducing SpringDoclet

As the Spring Framework evolves, one of the areas that gets better with each release is annotation-based application configuration and wiring. Using Spring annotations in Java classes along with component scanning helps fight the XML bloat that can make some applications difficult to work with.

However, one downside to annotation-based configuration is that there is no central place to find all Spring-managed beans in an application. With XML Spring configuration, all the bean definitions and their injected dependencies can be seen in one or more XML files (if you have the patience to wade through all the XML required for a larger system).

Reducing the XML bloat and moving the wiring closer to the code is worth the loss of visibility in my opinion.

IDEs can help with the visibility issue. SpringSource Tool Suite (or STS, available as a stand-alone Eclipse distribution or as a set of Eclipse plugins) provides a Spring Explorer view which shows components, MVC request mappings, AOP advices, auto-wired dependencies, and more. IntelliJ IDEA has some nice Spring-related features, including navigation between beans and their auto-wired dependencies via gutter icons, but (as far as I know) it doesn't have one view to show all Spring-managed components and mappings in an application.

On the Spring Framework community forums, the user @petrp suggested a tool to generate documentation for controllers and request mappings in a Spring MVC application. The more I thought about this idea the more I liked it. Generating an HTML report from the source code would be another good way to mitigate the visibility issue.

And so, SpringDoclet was born. SpringDoclet is a Javadoc doclet designed to generate an HTML report as part of a project's build process. The report lists all classes decorated with any of the annotations from the org.springframework.stereotype package (@Component, @Service, @Repository, @Controller). It also lists all @RequestMappings with the HTTP method, URL template, and implementing class. Classes listed on the report can be cross-linked to standard Javadoc HTML reports or other source code reports like JXR.

I decided to implement this as doclet so I could get the source code scanning for free, along with integration with Maven, Ant, and other tools that already support Javadoc.

I also chose to implement the doclet in Groovy. I knew the code would need to generate HTML, and Groovy's MarkupBuilder makes that chore a breeze compared to Java. I'm still fairly new to Groovy, and using a new language on a real project (even if it is a small one) is the best way for me to really learn it. I tried to do everything the Groovy way, learned a few tricks along the way, and became much more comfortable with the language.

The code for SpringDoclet is at http://github.com/scottfrederick/springdoclet. For now, you will have to build the doclet before using it. Instructions for building and using SpringDoclet are in the README file on GitHub.

Give it a try, and let me know what you think. Please comment on this post with any feedback, suggestions for enhancements, or (gasp!) bugs.