Lazy Ways - Using Aspectj with Restlet Resources
I'll say it - I'm Lazy. I'll say it again, just in case you missed it the first time - I'M LAZY!!! Not in the sense you might mean, but in the DRY fashion. DRY, for the uninitiated is Don't Repeat Yourself - a coders mantra that you never write imperfectly twice (even if it's quicker to do) what you can spend a smidge more time on and write perfectly once! In that vein, when working with Restlet, there are instances where you write code like:
getRequest().getAttributes().get("valuename")
While not an issue in and of themselves, this gets tedious, and I might have to do it multiple times in a RESTlet (supporting 4 methods). So I decided to use a little runtime annotation on instance fields with a parameter name mapping. My annotation looks like so:
package org.example.webresources;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target ({ElementType.FIELD, ElementType.METHOD })
@Inherited
public @interface RestletResourceVariable {
String name();
}
Then the parameters my resource supports are codified at the top of the class definition, and are ensured to be the same across all the REST methods. Wait, wait. Just using annotations doesn't confer this capability to my class, WTF?
Well, to perform a little magic, I call in the powerful AspectJ and wield it's mighty capabilities to do compile-time weaving to intercept calls and modify my instance variables in my ServerResources from the Requests passed in. My aspect creates targets called pointcuts that are the descriptions of where my "before" interceptor functions are going to be attached - in this case the methods in my ServerResources that are annotation with @Delete, @Get, @Post and @Put method annotations.
package org.example.webresources;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import org.aspectj.lang.Signature;
import org.restlet.*;
import org.restlet.resource.ServerResource;
import org.slf4j.*;
public aspect RestletResourceVariableAspect {
static Logger logger = LoggerFactory.getLogger(RestletResourceVariableAspect.class);
pointcut RESTMethods() :
execution( @org.restlet.resource.Delete * org.example.webresources..*(..) ) ||
execution( @org.restlet.resource.Get * org.example.webresources..*(..) ) ||
execution( @org.restlet.resource.Post * org.example.webresources..*(..) ) ||
execution( @org.restlet.resource.Put * org.example.webresources..*(..) ) ;
before(): RESTMethods() {
Signature sig = thisJoinPoint.getSignature();
Object target = thisJoinPoint.getTarget();
Class<?> c = target.getClass();
Field[] fields = c.getDeclaredFields() ;
for ( Field field : fields ) {
Annotation[] annotations = field.getDeclaredAnnotations();
for(Annotation annotation : annotations){
if(annotation instanceof RestletResourceVariable){
RestletResourceVariable myAnnotation = (RestletResourceVariable) annotation;
// get matching name from request variables.
String reqVarName = myAnnotation.name();
// set request instance var to request var.
ServerResource sres = (ServerResource)target;
Request req = sres.getRequest();
String reqVarVal = (String)req.getAttributes().get(reqVarName);
try {
logger.info("On Class " + c.getName() + " Field " + reqVarName + " value : " + reqVarVal );
field.set(target, reqVarVal);
} catch ( IllegalAccessException ex ) {
logger.info( ex.toString() );
}
}
}
}
}
}
This aspect iterates through the fields on ServerResource, finds any that are annotated with my custom @RequestResourceVariable annotation. It then finds the request input attribute that matches the annotation name() property and stored that value in the ServerResource's instance variable. Then my @Get, @Post methods get the same variable - I don't have to take a chance that multiple methods are going to get difference variable/parameter names due to typos or failure to use a class Constant to document. Mistakes are eliminated, simple coding errors. And my code becomes self-documenting. The parameter names are typed to the annotation. The annotation is tied to the instance variable and the instance variable is pinned to the actual value passed in the Request object.
public class QuestionResource extends ServerResource {
static Logger logger = LoggerFactory.getLogger(QuestionResource.class);
@RestletResourceVariable(name="questionId")
public String questionId;
@Get("json")
public JsonRepresentation doGet() {
logger.info("QuestionResource::doGet() {}.", questionId);
QuestionRepository qr = new QuestionRepository();
Question question = qr.getById("en", questionId);
}
}
Transparently.
Granted, there was effort involved in setting this up and making work properly (though as I think about it, I know there's a bug to fix - what if the @RequestResourceVariable annotation doesn't have a name property, it should default to the instance variable name). But if there hadn't have been the niggling runtime @Retention I forgot to add (4 hours) and the familiarity with AspectJ that I needed to re-acquaint myself with (4 hours) - what would have been a day-long job would have been a half-hours work, and saved me countless bugs. I only have to debug this Aspect once. I have to debug all my request variable access code in every method I write. Now I let the computer write correct code for me.
That's being Lazy the Hard Way.