Thursday, August 19, 2010

A first stab at an IntelliJ plugin

While I'm not the biggest fan of IntelliJ's IDEA, I have been using it an awful lot lately. The IDE definitely has some neat features such as their Live Templates. One can get away with all kinds of tricks using the Live Template stuff; but not what I wanted.

What I really wanted was an added "Generator" for generating Chained setter methods (i.e. public T setF(F f){this.f=f;return this;})

I've spent some time working on eclipse plugins (about five years ago) so I figured I'd check out IDEA's plugin API.  Not having high expectations, I was pleasantly surprised and managed to actually knock out a plugin that does exactly what I wanted in just a few hours.  The API documentation appears scarce but luckily there seem to be quite a few open-source plugins out there.  Browsing through a few of these made comprehending the API a breeze (granted - I am doing something extremely simple here).  The "Basics of Plugin Development" document was also pretty nifty.

IntelliJ provides a neat web interface you can use to upload plugins, so I've uploaded my plugin (generate-chained-accessors) and put it up on google code.  It was pretty neat to see the plugin appear as an available plugin in the IDE almost immediately after it was uploaded to IntelliJ's website.

Monday, March 29, 2010

My attempt at Spring MVC custom annotations

While working on a simple project using SpringMVC, I stumbled upon the need to access a request or session attribute directly from within a controller method. I needed to bypass the binder and just access a request attribute set by a filter. This is obviously easy enough to do:

@RequestMapping
public String view(WebRequest request){
...
request.getAttribute("someAttribute", WebRequest.SCOPE_REQUEST);
...
}

Given modern mocking frameworks like Mockito and spring's own test libraries, it's easy enough to mock a WebRequest or even an HttpServletRequest. But I am pedantic, so I decided to see if I could create my own @RequestAttribute annotation instead! Ideally, I figured I'd be able to create a new annotation representing new behaviour without touching any of the spring source code.

An initial cursory look over AnnotationMethodHandlerAdapter did not reveal any clean hooks, so I decided to go with an aspect.

First Approach - Aspects



At first, I tried creating a pointcut around HandlerMethodInvoker#resolveHandlerArguments. At this point I discussed my plan with coworker and friend Martin Snyman, who quickly pointed out that it might be simpler to write the pointcut to intercept @RequestMapping annotated controller classes instead.

Starting out, my original pointcut looked like this:

@Around("execution(@org.springframework.web.bind.annotation.RequestMapping * *(..))")

This worked well enough, but it created unncessary proxies. The pointcut picked up the execution of all methods annotated with @RequestMapping - or worse.

What I really needed was to pick up execution of all methods annotated with @RequestMapping that had at least one parameter annotated with my special annotation - "@RequestAttribute". This turned out to be difficult. I could not for the life of me figure out how to write a pointcut that picked up annotated parameters rather than matching annotations on the actual classes of passed parameters. After a couple of stabs during the weekend, I found a blog post that discussed the issue I was facing in some detail. With that, I was able to write the following pointcut:

@Around("execution(@org.springframework.web.bind.annotation.RequestMapping * *.*(..,@o.c.RequestAttribute (*),..))")

I wrote a supporting interface to allow my aspect to support any number of annotations:

public interface ArgumentModifyingAnnotationBehavior<T extends Annotation> {
public Object getArgumentValue(Object argumentValue, T annotation);
}

The actual aspect then looked something like this:

protected final Map<Class<Annotation>, ArgumentModifyingAnnotationBehavior<Annotation>> annotationRegistry = new ConcurrentHashMap<Class<Annotation>, ArgumentModifyingAnnotationBehavior<Annotation>>();
...
@Around("execution(@org.springframework.web.bind.annotation.RequestMapping * *.*(..,@o.c.RequestAttribute (*),..))")
private Object resolveHandlerArguments(ProceedingJoinPoint pjp)
throws Throwable {
MethodSignature sig = (MethodSignature) pjp.getSignature();
Annotation[][] annotations = sig.getMethod().getParameterAnnotations();
Object[] arguments = pjp.getArgs();
if (annotations != null) {
/* iterate arguments */
for (int argumentIndex = 0; argumentIndex < annotations.length; argumentIndex++) {
Annotation[] argumentAnnotations = annotations[argumentIndex];
if (argumentAnnotations != null) {
for (int annoationIndex = 0; annoationIndex < argumentAnnotations.length; annoationIndex++) {
Annotation argumentAnnotation = argumentAnnotations[annoationIndex];
ArgumentModifyingAnnotationBehavior behavior = annotationRegistry
.get(argumentAnnotation.annotationType());
if (behavior != null) {
arguments[argumentIndex] = behavior
.getArgumentValue(arguments[argumentIndex],
argumentAnnotation);
}
}
}
}
}
return pjp.proceed(arguments);
}

I was very proud of myself. Then disaster struck...

@RequestMapping
public String view(@RequestAttribute("stringAttribute") String stringAttribute){...

worked like a champ.

@RequestMapping
public String view(@RequestAttribute("objectAttribute") java.util.Map objectAttribute){...

however, grenaded. My annotation worked great with concrete types but blew up when it was used to annotate interfaces or abstract classes.

Second Approach - WebArgumentResolvers



Debugging revealed that HandlerMethodInvoker was attempting to resolve "objectAttribute" to a model attribute and actually attempted to instantiate java.util.Map.

Debugging also revealed the curious new WebArgumentResolver class. By this point, I've spent hours of my time writing and debugging my awesome aspect only to find that HandlerMethodInvoker actually *did* have a hook for allowing custom behaviour using a WebArgumentResolvers!

A little bit more digging revealed that one can actually register any number of WebArgumentResolvers with the AnnotationMethodHandlerAdapter, which would pass these on through to the HandlerMethodInvoker

After 8+ hours of screwing around with my Aspect, I found I could actually achieve what I needed to do without the creation of any proxies with this:

public class RequestAttributeCustomArgumentResolver implements WebArgumentResolver {
public static @interface RequestAttribute {
String value() default "";
}
public Object resolveArgument(MethodParameter methodParameter,
NativeWebRequest webRequest) throws Exception {
RequestAttribute annotation=methodParameter.getParameterAnnotation(RequestAttribute.class);
if(annotation!=null){
return webRequest.getAttribute(annotation.value(),WebRequest.SCOPE_REQUEST);
}else{
return UNRESOLVED;
}
}
}

and the following in the applciation context XML:

<bean id="handlerAdapter"
class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="customArgumentResolvers">
<list>
<bean class="org.campuseai.spring.webmvc.annotation.RequestAttributeCustomArgumentResolver"/>
</list>
</property>
</bean>

Ouch. Googling "WebArgumentResolver" also resulted in this blog post which does something similar to what I attempted to do.