javajersey

How to bind custom context to Jersey request


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?


Solution

  • 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.