4

I have a POJO that I'd like to inject into resources and filters:

public final class MyObject { }

I implemented a custom provider for it:

@Provider
public final class MyProvider
extends AbstractHttpContextInjectable<MyObject>
implements InjectableProvider<Context, Type> {

    @Context private HttpServletRequest request;

    @Override
    public Injectable<MyObject> getInjectable(
            ComponentContext componentContext,
            Context annotation,
            Type type
    ) {
        if (type.equals(MyObject.class)) {
            return this;
        }
        return null;
    }

    @Override
    public ComponentScope getScope() {
        return ComponentScope.PerRequest;
    }

    @Override
    public MyObject getValue(HttpContext httpContext) {
        //in reality, use session info from injected request to create MyObject
        return new MyObject();
    }
}

The object is successfully injected into my resource:

@Path("/test")
@ResourceFilters(MyFilter.class)
public final class MyResource {

    @Context private HttpServletRequest request;
    @Context private MyObject myObject;

    @GET
    public String execute() {

        System.out.println(request != null);  //true
        System.out.println(myObject != null); //true

        return "data";
    }
}

But Jersey fails to inject it into my filter:

public final class MyFilter implements ResourceFilter {

    @Context private HttpServletRequest request;
    @Context private MyObject myObject;

    @Override
    public ContainerRequestFilter getRequestFilter() {
        return new ContainerRequestFilter() {
            @Override
            public ContainerRequest filter(ContainerRequest containerRequest) {

                System.out.println(request != null);  //true
                System.out.println(myObject != null); //false

                return containerRequest;
            }
        };
    }

    @Override
    public ContainerResponseFilter getResponseFilter() {
        return null;
    }
}

I'm guessing the difference has to do with the fact that in MyFilter the injection is done using proxies that defer to thread-local instances - this is because the fields annotated with @Context are declared in the outer class, which is instantiated once, but they are used to inject objects on a per-request basis. When I step through filter during debugging, I can see that MyFilter.request points to a proxy wrapping an instance of com.sun.jersey.server.impl.container.servlet.ThreadLocalInvoker.

What is my custom provider (or implementation otherwise) missing that it needs to do custom injection into my filter?

Note that I'm currently stuck with Jersey 1.1.4.1 (sorry).

EDIT: Using Jersey 1.17, I get an exception at startup instead:

SEVERE: Missing dependency for field: private mypackage.MyObject mypackage.MyFilter.myObject

Paul Bellora
  • 54,340
  • 18
  • 130
  • 181

2 Answers2

1

I found a workaround using the Providers injectable interface from JSR-311. First, I had to make my provider implement ContextResolver:

@Provider
public final class MyProvider
extends AbstractHttpContextInjectable<MyObject>
implements InjectableProvider<Context, Type>, ContextResolver<MyObject> {

    ...

    @Override
    public MyObject getContext(Class<?> type) {
        //in reality, using the same logic as before
        return new MyObject();
    }
}

Then I injected a Providers instance into my filter instead. When filter is called, I use it to look up the ContextResolver for MyObject and retrieve it dynamically:

public final class MyFilter implements ResourceFilter {

    @Context private HttpServletRequest request;
    @Context private Providers providers;

    @Override
    public ContainerRequestFilter getRequestFilter() {
        return new ContainerRequestFilter() {
            @Override
            public ContainerRequest filter(ContainerRequest containerRequest) {

                final ContextResolver<MyObject> myObjectResolver =
                            providers.getContextResolver(MyObject.class, null);
                final MyObject myObject =
                            myObjectResolver.getContext(MyObject.class);

                System.out.println(request != null);  //true
                System.out.println(myObject != null); //true

                return containerRequest;
            }
        };
    }

    ...
}

Credit goes to this answer for tipping me off about Providers. The solution works, but it isn't a pretty one. I'd still like to inject MyObject anywhere and just have it work, like HttpServletRequest - and I'd like to know what it is my provider's missing that it needs to make that happen.

Community
  • 1
  • 1
Paul Bellora
  • 54,340
  • 18
  • 130
  • 181
  • 1
    This doesn't really use the Injectable approach. The Providers are available as part of the default injection, and you essentially need to register a custom context resolver with the Jersey server. You can do away with not extending the AbstractHttpContextInjectable or implementing the InjectableProvider and achieve the same results. – baradas Jul 15 '13 at 06:47
  • @baradas You're right, and that's why I don't like this solution. I didn't end up using it in my code. – Paul Bellora Jul 15 '13 at 15:31
  • @PaulBellora, if I may ask, what did you end up doing? I'm having the same issue with injecting into a filter as well as injecting into a provider. – elanh Oct 11 '15 at 12:54
  • @elanh For this project, I ended up not needing it. For a more recent project I used Jersey 2 integrated with Spring, which allowed injection into providers and filters. Getting request scoped beans set up was still a PITA but I got it working. If you're able to use Spring, try searching for examples based on that. Let me know if you can't find a decent request scope example and I'll put one together. – Paul Bellora Oct 12 '15 at 04:12
  • @PaulBellora Unfortunately I have a constraint which requires I use Jersey 1.1x, and adding Spring to my project would cause a big overhead. I can't seem to understand the comment above: "The Providers are available as part of the default injection, and you essentially need to register a custom context resolver with the Jersey server." What does that mean? How do I register a custom context resolver, and how does that help me inject my own classes? – elanh Oct 12 '15 at 08:33
  • @baradas would need to speak to that - I originally read "you essentially need to" as a critique of my existing solution. But upon rereading, it sounds like he or she might have been describing an alternative solution. – Paul Bellora Oct 16 '15 at 04:43
1

I came across this question trying to achieve the same goal (i.e. inject something provided by a custom Provider implementation) and found that custom injection works nicely if you go by way of a filter factory.

So instead of putting the @Context MyClass myObj annotation into the Filter class, place an annotated field of that type in the filter factory responsible for creating the filter and have the factory pass "myObj" as a regular parameter.

I'm not sure this will help in your case and I haven't investigated the implications of using this with a per-request provider (mine is singleton-scoped), so YMMV.

Eike Lang
  • 11
  • 2