Resteasy Form annotations for Jersey

January 16th, 2011

Lately I was developing a jax-rs application. What I really like with jaxrs is the way you can handle any kind of parameter values of a request using annotations. So when I need a query parameter I just annotate a field or an argument of a Resource class with @QueryParam("paramName") and the parameter value will get injected for me.

The problem

The problem arise when you want to handle a lot of parameters. This is the scenario of an html form posted by a user. In that case you can end up injecting a lot of @FormParam witch isn't the most maintainable code you can write (As Uncle Bob says, avoid methods with arguments). There is of course a solutions which is called the Builder pattern or the Facroty pattern.

Resteasy jaxrs offers you such an implementation out of the box. Just create a class that will work as a simple data transfer object, annotate their getters or fields with @*Param annotations and use it at a Resource class annotated with the @Form annotation.

Unfortunately Jersey did not provide something like that out of the box. At least some google research did not give me anything valuable. That lead me to try to find a solution. That means that I wanted a way to inject a custom class in a jaxrs resource. The class should get generated by a factory provided by me

The real solution to the problem is: InjectParam but I didn't find it before trying to do what I describe below.

A Jersey solution

After a small research found that a custom jersey @Provider will do the job just fine. So, we start with the our data transfer object class. We consider that we want to create an object holding the information:

public class ContactFormParams {
  private String name;
  private String email;
  private String comments;

  // Getters and setters...
}

Jersey provides a way of injecting custom classes to a resource class. In order to do that we should provide Jersey with that information. The way to do it is the Injectable interface.

import com.sun.jersey.api.core.HttpContext;
import javax.ws.rs.core.Context;

public class ContactFormParamsProvider implements Injectable<ContactFormParams> {
  @Context HttpContext ctx
  public ContactFormParams getValue() {
      // Create and return a ContactFormParams based on parameters
      // we can get from the ctx which Jersey will inject for us
  }
}

In order to make our life a little bit easier we are going to use com.sun.jersey.server.impl.inject.AbstractHttpContextInjectable witch is not part of the Jersey API but the Jersey implementation itself:

import com.sun.jersey.api.core.HttpContext;

public class ContactFormParamsProvider 
  extends AbstractHttpContextInjectable<ContactFormParams> {
  @Ovveride
  public ContactFormParams getValue(HttpContext ctx) {
    // Create and return a ContactFormParams based on parameters
    // we can get from the ctx which Jersey will inject for us
  }
}

Ok, we now have a factory that creates ContactFormParams for as and can get injected by Jersey. But in order be able to actually inject that class we must also provide an InjectableProvider. That tells jersey what kind of a factory should use in order to eject a value. We are going to use the same class for this:

import com.sun.jersey.api.core.HttpContext;

@Provider // Tell jersey that this class is a provider
public class ContactFormParamsProvider 
  extends AbstractHttpContextInjectable<ContactFormParams> 
  implements InjectableProvider<Context, Type> {

  @Ovveride
  public ContactFormParams getValue() {
    // Create and return a ContactFormParams based on parameters
    // we can get from the ctx which Jersey will inject for us
  }

  // InjectableProvider implementation:
  public ComponentScope getScope() {
    return ComponentScope.Singleton;
  }

  public Injectable getInjectable(ComponentContext ic, Context ctx, Type c) {
    if (ContactFormParams.class.equals(c)) return this;

    // In any other case:
    return null;
  }
}

Finally add this class to the Singleton set of your Application subclass and you can use the @Context annotation wherever you want with a ContactFormParams class and it will get created an injected for you:

@Path("/contact") // Tell jersey that this class is a provider
public class SimpleContactFormController { 
  @Post
  public String getValue(@Context ContactFormParam formParameters) {
    // Use formParameters
    return "Some answer!"
  }
}

DRY

Done! After that boilerplate code I have what resteasy gives me out of the box! I have to make it more abstract! A custom annotation (maybe @Form) that gives the same functionality with resteasy's @Form. After a research and mailing list reading came up with InjectParam annotation which seems to be for Jerset exactly what @Form annotation is for resteasy:

public class ContactFormParams {
  @FormParam("name")
  private String name;
  @FormParam("email")
  private String email;
  @FormParam("comments")
  private String comments;

  // Getters and setters...
}

//The resource class:
@Path("/contact") // Tell jersey that this class is a provider
public class SimpleContactFormController { 
  @Post
  public String getValue(@InjectParam ContactFormParam formParameters) {
    // Use formParameters
    return "Some answer!"
  }
}

Conclusion

Is it just me or Jersey really lacks documentation?

References

valotas.com v3.6.2 © Georgios Valotasios - CSS inspired by Adam Wathan's blog