I have a Jersey REST application with token-based user authentication. When a request comes in, a custom RestContext
object gets created and added to the ContainerRequestContext
as a property (via a filter that runs right after the request is received). This context manages user authorization (through roles) and access to other business logic. It's available in the resources to perform business logic. When the request is processed, the RestContext
gets cleaned up in a second filter that executes at the very end of the pipeline.
This works fine although it requires two filters. I have been reading about the use of HK2 and the InjectionResolver
and I was wondering if I could use injection to inject this RestContext
in my resources and other filters (for example I have a filter that creates a SecurityContext
from the RestContext
) but I couldn't find an answer. In general, how do I inject an object per request that depends on the request context? Is this even possible? Is there an easier way to do it, e.g., using @Context
?
EDIT: As pointed out, I'm basically trying to inject in my resources a custom class along the lines of the documentation. However, I don't seem to be able to correctly register my AbstractBinder
to bind the injection of my class. I get the following:
org.glassfish.hk2.api.UnsatisfiedDependencyException: There was no object available for injection at Injectee(requiredType=RestContext,parent=RestContextFilter,qualifiers={}),position=0,optional=false,self=false,unqualified=null,1435496015)
EDIT 2: I managed to make some slight progress. I create my config in the following way:
new ResourceConfig(allResources())
.packages(packagesToScan())
.registerInstances(new RestContextBinder());
since the documentation clearly states that injection of binders is not supported via class but via instance.
However I am now getting this:
A MultiException has 3 exceptions. They are:
1. java.lang.IllegalStateException: Not inside a request scope.
2. java.lang.IllegalArgumentException: While attempting to resolve the dependencies of my.package.RestContextFilter errors were found
3. java.lang.IllegalStateException: Unable to perform operation: resolve on my.package.RestContextFilter
The RestContext is @Inject
-ed in a request/response filter. It's then used to create a SecurityContext
and set it in the ContainerRequestContext
and it's cleaned up in the response filter. Aren't response filter request scoped? Why am I getting the error?
UPDATE now that Jersey 2.7 is out, the solution is simpler.
It sounds like you want a RequestScoped
binding. Here is how you might set things up with Jersey 2.7:
You will need a request filter, and because RestContext
will be RequestScoped
, you can inject a provider into your filter, set some properties on it, and know they will be available for the remainder of the request.
@Priority(Priorities.AUTHORIZATION) // filter deals with roles, comes after AUTHENTICATION
public class RestContextFilter implements ContainerRequestFilter
{
// you need to inject a provider, rather than the class directly
// because this filter is instantiated before the request scope is ready
private Provider<RestContext> rcProvider;
@Inject
public RestContextFilter(Provider<RestContext> rcProvider)
{
this.rcProvider = rcProvider;
}
@Override
public void filter(ContainerRequestContext requestContext) throws IOException
{
// now you're in a request scope and can get your context
RestContext rc = rcProvider.get();
// set some properties on rc here (current user or roles or whatever)
}
}
You need to register the filter and bind RestContext
in your ResourceConfig
using an HK2 binding:
public class MyResourceConfig extends ResourceConfig
{
public MyResourceConfig()
{
register(RestContextFilter.class);
register(new AbstractBinder()
{
@Override
protected void configure()
{
bindFactory(new Factory<RestContext>()
{
@Override
public RestContext provide()
{
return new RestContext();
}
// this will get called at the end of the request
// allowing you to close your request scoped object
@Override
public void dispose(RestContext instance)
{
instance.close();
}
}, RequestScoped.class).to(RestContext.class).in(RequestScoped.class);
}
});
}
}
Then you can inject RestContext
in your resources, and it will have all of the info set up by your filter. For example:
@Path("/my/path")
public class MyResource
{
private RestContext rc;
@Inject
public MyResource(RestContext rc)
{
this.rc = rc;
}
@POST
@Consumes(MediaType.APPLICATION_JSON)
public void upload(MyPostObj data)
{
// do stuff here, and rc is all set up
}
}
I tend to prefer constructor injection over field injection because I think it makes your dependencies more obvious for other developers reading your code.