Eric
Eric

Reputation: 11662

Jersey 2 per-request @Context injection

Overview

In Jersey 2, can I inject a custom, request-specific value into my resource? Specifically, I would like to inject a MyThing which can be derived from my custom security context MySecurityContext. I would like to inject MyThing directly to make the code clean.

Is there any way to do this? According to this question it can't be done using a ContextResolver although this article and this example suggest it might be possible.

What Works

Using an auth filter, I am able to set my custom security context using code like this:

@Provider
public class HttpTokenAuthFilter implements IComposableJaxRsAuthFilter {

   @Override
   public boolean doAuth(ContainerRequestContext requestContext) throws WebApplicationException {
       // error handling omitted
       requestContext.setSecurityContext(MySecurityContext.fromHeaders(requestContext));
   }
}

... and then in my resource I can pull a value from it:

@Path("/foo")
public class MyResource {
    @Context 
    private SecurityContext securityContext;

    @Get
    public String doGetFoo() {
       MyThing myThing = ((MySecurityContext)securityContext).getThing();
       // use myThing to produce a result
    }

Where I'm Stuck

... however, since this is going to be repeated a lot, I would much rather just write:

    @Context
    private MyThing myThing;

I tried defining a ContextResolver. I see it getting constructed, but I never see it getting invoked, so I have not yet tried any of the techniques linked above. Is this even the correct class to be using?

@Provider
public class MyThingResolver implements ContextResolver<MyThing> {

    public MyThingResolver() {
        System.out.println("ctor");
    }

    @Override
    public MyThing getContext(Class type) {
        System.out.println("getContext");

        if (type.equals(MyThing.class)) {
           return new MyThing(); // TODO: SHOULD ACTUALLY USE CURRENT MySession
        }
        return null;
    }
}

Upvotes: 3

Views: 1858

Answers (1)

Eric
Eric

Reputation: 11662

Almost the solution

Per this answer and the refinements specified at this followup, it's almost possible to accomplish the injection using a Factory. The only caveat is, you must inject MyThing via a Provider, otherwise it's going to get created (with the default SecurityContext) before the filter runs and swaps in the MySecurityContext.

The factory code looks like this:

public class MyThingFactory implements Factory<MyThing> {

    @Context
    private SecurityContext securityContext;

    @Override
    public MyThing provide() {
        return ((MySecurityContext)securityContext).getThing();
    }

    @Override
        public void dispose(MyThing session) {
    }
}

The resource can then inject it like this:

@Context
private Provider<MyThing> myThingProvider;

... and consume it like this:

MyThing myThing = myThingProvider.get();
// use myThing

The factory registration in the AbstractBinder looks like this:

this.bindFactory(MyThingFactory.class) //
    .to(MyThing.class) //
    .in(RequestScoped.class);

(Edit) Proxies to the Rescue!

Per the comment from @peeskillet, it is possible to get rid of the Provider by proxying MyThing. (Per @ jwells131313, MyThing must therefore be an interface or a proxy-able class.)

The binding then looks like this:

this.bindFactory(MyThingFactory.class) //
    .to(MyThing.class) //
    .proxy(true) //
    .in(RequestScoped.class);

and injection finally works as desired:

@Context
private MyThing myThing;

Upvotes: 5

Related Questions