Sunday, September 16, 2007

Custom Matcher and MethodInterceptor for DWR-Guice

I wanted a way to intercept calls to public methods of a remoted interface so I could replace my use of bindFilter with bindInterceptor, as described in an earlier posting.

At first I thought I could just use the static methods in Matchers:

bindInterceptor(
only(MyService.class),
any(),
myInterceptor
);

This doesn't intercept anything, because MyService is an interface. What I needed was to intercept calls to a method of a subclass of MyService for which there exists a method of the same name and argument types in MyService.

bindInterceptor(
subclassesOf(MyService.class),
declaredBy(MyService.class),
myInterceptor
);

The subclassesOf matcher is already provided in com.google.inject.matcher.Matchers, but I had to roll my own declaredBy method matcher. Here's the heart of the implementation (cls is a final Class<?> field, initialized from the argument to declaredBy):

public boolean matches(Method method) {
try {
// Matches if the method is from a subclass
// of the given class (or the class itself)
// and the given class declares a method
// with the same name and parameter types.
if (cls.isAssignableFrom(
method.getDeclaringClass())) {
// Return value of getDeclaredMethod
// is ignored. It throws an exception
// if the method is not found.
cls.getDeclaredMethod(
method.getName(),
method.getParameterTypes());
return true;
}
// fall through
} catch (NoSuchMethodException e) {
// fall through
} catch (SecurityException e) {
// fall through
}
return false;
}

This still didn't work quite right; the implementation of one method called another public method of the interface, and that call was intercepted even though it wasn't a remote call. To mimic AjaxFilter, I want to able to intercept only the outermost call. I wrote a MethodInterceptor-decorator:

public class OutermostCallInterceptor
implements MethodInterceptor {
/**
* Decorates a MethodInterceptor so that only the
* outermost invocation using that interceptor will
* be intercepted and nested invocations willbe
* ignored.
*/
public static MethodInterceptor outermostCall(
MethodInterceptor interceptor) {
return new OutermostCallInterceptor(interceptor);
}

/** Ensure underlying interceptor is injected. */
@Inject void injectInterceptor(Injector injector) {
injector.injectMembers(interceptor);
}

public Object invoke(MethodInvocation invocation)
throws Throwable {
int savedCount = count.get();
count.set(savedCount + 1);
try {
if (count.get() > 1)
return invocation.proceed();
else
return interceptor.invoke(invocation);
} finally {
count.set(savedCount);
}
}

private OutermostCallInterceptor(
MethodInterceptor interceptor) {
this.interceptor = interceptor;
}

private final MethodInterceptor interceptor;

private final ThreadLocal count =
new ThreadLocal() {
@Override protected Integer initialValue() {
return 0;
}
};
}

So now my binding looks like this:

bindInterceptor(
subclassesOf(MyService.class),
declaredBy(MyService.class),
outermostCall(myInterceptor)
);

and myInterceptor is injected. It's not as compact as using bindFilter, but I can apply multiple interceptors without having to resort to FluentConfigurator. The use of subclassesOf is probably redundant, since declaredBy checks that the method is from a subclass of MyService, but I think it helps to clarify what's going on.

No comments: