3

I want to inject a Datastore for a single request by field, like

@Context
protected HttpServletRequest request;

Currently I have implemented a similar approach to this: Jersey 2.x Custom Injection Annotation With Attributes as follows:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER, ElementType.FIELD})
public @interface TenantDatastore {}
public class TenantDatastoreFactory extends AbstractContainerRequestValueFactory<Datastore> {

    public TenantDatastoreFactory() {}

    @Override
    public Datastore provide() {
        ContainerRequest request = getContainerRequest();
        return DatastoreManager.getDs(request.getHeaders().get("Host")));
    }

    @Override
    public void dispose(Datastore d) {}
}
public class TenantDatastoreFactoryProvider extends AbstractValueFactoryProvider {

    private final TenantDatastoreFactory tenantDatastoreFactory;

    @Inject
    public TenantDatastoreFactoryProvider(
            final MultivaluedParameterExtractorProvider extractorProvider,
            ServiceLocator locator,
            TenantDatastoreFactory tenantDatastoreFactory) {

        super(extractorProvider, locator, Parameter.Source.UNKNOWN);
        this.tenantDatastoreFactory = tenantDatastoreFactory;
    }

    @Override
    protected Factory<?> createValueFactory(Parameter parameter) {
         Class<?> paramType = parameter.getRawType();
         TenantDatastore annotation = parameter.getAnnotation(TenantDatastore.class);
         if (annotation != null && paramType.isAssignableFrom(Datastore.class)) {
             return tenantDatastoreFactory;
         }
         return null;
    }
}
public class TenantDatastoreInjectionResolver extends ParamInjectionResolver {
    public TenantDatastoreInjectionResolver() {
        super(TenantDatastoreFactoryProvider.class);
    }
}
@Path("/users")
public class User {
    @TenantDatastore
    private Datastore    ds;
    private ObjectMapper objectMapper;

    public User(ObjectMapper objectMapper) {
      this.objectMapper = objectMapper;
    }

    @GET
    public Response getUsers(){
      return Response.ok(ds.find(User.class).asList()).build();
    }
}

And in the run method of the dropwizard application:

environment.jersey().register(new UserResource(objectMapper));

environment.jersey().getResourceConfig().register(new AbstractBinder(){
    @Override
    public void configure() {
        bind(TenantDatastoreFactory.class)
          .to(TenantDatastoreFactory.class)
          .in(Singleton.class);
        bind(TenantDatastoreFactoryProvider.class)
          .to(ValueFactoryProvider.class)
          .in(Singleton.class);
        bind(TenantDatastoreInjectionResolver.class)
          .to(new TypeLiteral<InjectionResolver<TenantDatastore>>(){})
          .in(Singleton.class);
    }
});

I read, that you have to register the resource as a singleton, like this:

environment.jersey().register(UserResource.class);

but I have to pass objects to the constructor, which isn't possible with a singleton. javax.servlet.http.HttpServletRequest along with javax.ws.rs.core.Context works very well in a resource, registered as an instance, so how can I make this behavior possible for my usecase?

Community
  • 1
  • 1
sclausen
  • 1,720
  • 3
  • 15
  • 22

1 Answers1

9

So when you instantiate the resource to make it a singleton, Jersey tries to do all the injection on startup. This means that attempt to access any object that is inherently request scoped, will fail... UNLESS... the object is proxiable.

Some objects are made proxiable by Jersey, and this is by design, and by specification. For example HttpHeaders, UriInfo, SecurityContext, and a few others listed here. Though HttpServletRequest is not listed, it is also one of the objects that are proxiable.

What it means to be proxiable is that instead of injecting the actual object (which doesn't exist until there is request), a proxy is injected. When calls are made on the proxy, they get forwarded to the actual object that is available in the current request. You can try to print/log the class of the HttpServletRequest and you will see that the class is actually com.sun.proxy.ProxyX instead of HttpServletRequestSomeImpl. This is the magic of Java's dynamic proxies at work.

The current problem you are facing is the injection of Datastore. It is inherently request scoped because it's creation in dependent on request context information, i.e. the headers. So during injection, it fails on this call to obtain the ContainerRequest inside your factory

ContainerRequest request = getContainerRequest();

The error message being "Not inside a request scope", which makes perfect sense as there is no request when we try to obtain it.

So how can we fix this? Well we need to make the Datastore proxiable. Generally, the way you can do this is by configuring it during binding declaration, for example

bindFactory(...).proxy(true).proxyForSameScope(false).to(...);

The proxy(true) method makes it proxiable, and the proxyForSameScope(false) says that if we are trying to inject into the same scope, it should not be a proxy, but the actual instance.

One problem with your current configuration is that you are binding the factory to the factory

bind(TenantDatastoreFactory.class)
  .to(TenantDatastoreFactory.class)
  .in(Singleton.class);

This makes sense for your current implementation, as you are trying to inject the factory into the TenantDatastoreFactoryProvider. But what we actually need to make the proxying work, is for the factory to be binded to the actual Datastore:

bindFactory(TenantDatastoreFactory.class)
        .proxy(true)
        .proxyForSameScope(false)
        .to(Datastore.class)
        .in(RequestScoped.class);

So now we have taken out the binding of the factory, we can't inject it. So we just have to problem of returning a Factory from the createValueFactory method. We don't want to just return TenantDatastoreFactory instance because we'll still face the same problem where the provide method is called to get the Datastore. To get around this, we can do the following

@Override
protected Factory<?> createValueFactory(Parameter parameter) {
     Class<?> paramType = parameter.getRawType();
     TenantDatastore annotation = parameter.getAnnotation(TenantDatastore.class);
     if (annotation != null && paramType.isAssignableFrom(Datastore.class)) {
         return getFactory();
     }
     return null;
}

private Factory<Object> getFactory() {
    return new Factory<Object>() {

        @Context
        Datastore datastore;

        @Override
        public Object provide() {
            return datastore;
        }

        @Override
        public void dispose(Object t) {}
    };
}

So we are creating a Factory dynamically, where we inject the proxied Datastore. Now when Jersey tries to inject the resource class, it will inject the proxy, and the provide method is never called on start up. It is only called when we try o actually use the Datastore during the request.

It may seem redundant, that we have both the TenantDatastoreFactory and the anonymous Factory created as runtime. But this is necessary to make the Datastore proxiable and make sure the provide() method is never called on startup.

Another note, is that if you don't require parameter injection, you could've simplified the implementation by taking out the TenantDatastoreFactoryProvider. This is only required for parameter injection. All we need is the InjectionResolver to handle the custom annotation, and the factory to create the Datastore. The InjectionResolver implementation would need to be changed as follows

public class TenantDatastoreInjectionResolver 
        implements InjectionResolver<TenantDatastore> {

    @Inject
    @Named(InjectionResolver.SYSTEM_RESOLVER_NAME)
    InjectionResolver<Inject> systemInjectionResolver;

    @Override
    public Object resolve(Injectee injectee, ServiceHandle<?> handle) {
        if (Datastore.class == injectee.getRequiredType()) {
            return systemInjectionResolver.resolve(injectee, handle);
        }
        return null;
    }

    @Override
    public boolean isConstructorParameterIndicator() { return false; }
    @Override
    public boolean isMethodParameterIndicator() { return false; }
}

Then in the binder, just take out the TenantDatastoreFactoryProvider

@Override
public void configure() {
    bindFactory(TenantDatastoreFactory.class)
            .proxy(true)
            .proxyForSameScope(false)
            .to(Datastore.class)
            .in(RequestScoped.class);
    bind(TenantDatastoreInjectionResolver.class)
            .to(new TypeLiteral<InjectionResolver<TenantDatastore>>() {
            })
            .in(Singleton.class);
}

Again this is only if you don't require parameter injection.

See Also

Community
  • 1
  • 1
Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720
  • Really really nice. I think i've understood, what should happen. Unfortunately, now provide is just called once and not on future requests :-/ – sclausen Jan 13 '16 at 08:42
  • 1
    Try binding the `TenantDatastoreFactory` `in(RequestScoped.class)` – Paul Samsotha Jan 13 '16 at 08:48
  • But RequestScope isn't an annotation and ` bindFactory(TenantDatastoreFactory.class) .proxy(true) .proxyForSameScope(false) .to(Datastore.class) .in(RequestScope.class);` wouldn't work – sclausen Jan 13 '16 at 09:21
  • @PaulSamsotha great explanation but i am a little confused because you have mentioned in answer that a resource marked as singleton will be injected on startup but on your blog https://psamsotha.github.io/jersey/2015/12/16/request-scope-into-singleton-scope.html you have mentioned "The reason is that Singleton resources are not created until the first request. So there actually is a request scope during injection." so can you please explain further – JayD Jan 01 '21 at 11:59
  • @JayD I didn't get a chance to read this whole post. but if you are referring to the first sentence _"So when you instantiate the resource to make it a singleton, Jersey tries to do all the injection on startup"_, then I was referring to resources that you register as an _instance_ (as opposed to a _class_) – Paul Samsotha Jan 01 '21 at 12:56
  • well in that case whats the difference between resources registered as instance rather than a class and by resource we are talking about the jersey resource class right? – JayD Jan 01 '21 at 13:07
  • 1
    @JayD Well if you register the resource as a class, it is not instantiated until the first request. Unlike an instance, it is already instantiated, and therefore all the injections will happen on startup, unlike with a class where the injections will not occur until it is instantiated (on first request). – Paul Samsotha Jan 01 '21 at 13:38
  • 1
    @PaulSamsotha cheers mate you really cleared it out for me you have been a lot of help man keep spreading the knowledge. – JayD Jan 01 '21 at 13:41