2

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;
    }
}
Community
  • 1
  • 1
Eric
  • 11,392
  • 13
  • 57
  • 100

1 Answers1

4

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;
Kevin Day
  • 16,067
  • 8
  • 44
  • 68
Eric
  • 11,392
  • 13
  • 57
  • 100
  • 1
    Another option, without the Provider, is to make MyThing a proxy. I think it needs to be an interface though. `.to(MyThing.class).proxy(true).proxyForSameScope(true)`. You might be able to leave off the last call. That might be the default value. – Paul Samsotha Jun 28 '16 at 00:14
  • 1
    It doesn't *need* to be an interface to proxy it but if it is a class it'll need to follow the standard rules for proxies, like not be final and have a zero-arg constructor – jwells131313 Jun 28 '16 at 12:06
  • Injecting SecurityContext as shown above does not appear to work - it injects a proxy (see https://stackoverflow.com/questions/39487860/jax-rs-custom-securitycontext-has-unexpected-type-when-injected-into-resource-m ). Did you actually get the above working? If how, what was your magic for getting access to your custom MySecurityContext in the MyThingFactory? – Kevin Day Jan 10 '18 at 22:57
  • @KevinDay - yeah, it was working when I posted. But unfortunately it's been a long time since then and I don't remember the details any more. – Eric Jan 16 '18 at 22:39
  • Just in case anyone is trying it, Jersey doesn't provide access to the custom SecurityContext anymore. The solution is to push MyThing into the context as a property (you can use anything for the property name): context.setProperty(MyThing.class.getName(), myThing); then retrieve it in the factory (instead of using the SecurityContext): MyThing myThing = (MyThing)requestContext.getProperty(MyThing.class.getName()); – Kevin Day Jan 19 '18 at 01:31