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 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.
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!"
}
}
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!"
}
}
Is it just me or Jersey really lacks documentation?