Search code examples
javadependency-injectionjerseyhk2

Optionally inject ContainerRequestContext


In my Jersey application, I'd like to have a ContainerRequestContext instance injected into various objects. In the case that the object in being created outside of the context of a request, I would like null to be injected.

I noticed HK2 has an @Optional annotation that you can annotate dependencies with, and I was hoping that would do the job for, unfortunately it doesn't change the behaviour at all.

public class MyObject {

    private final ContainerRequestContext containerRequestContext;

    @Inject
    public MyObject(@Optional ContainerRequestContext containerRequestContext) {
        this.containerRequestContext = containerRequestContext;
    }

}

If this object is instantiated outside of a request scope (in my case, a Job run by a Quartz scheduler), then an exception like this gets thrown:

java.lang.IllegalStateException: Not inside a request scope.

It would massively simplify my code if Jersey would just inject null when outside of a request scope, any ideas how to do this?


Solution

  • I've figured out a way of doing it, but it's basically a hack. Instead of having ContainerRequestContext injected, you can instead try to explicitly get a ContainerRequestContext instance from the ServiceLocator, and handle the exception when the context is outside of a request scope.

    public class MyObject {
    
        private final Optional<ContainerRequestContext> containerRequestContext;
    
        @Inject
        public MyObject(ServiceLocator serviceLocator) {
            this.containerRequestContext = getContainerRequestContext(serviceLocator);
        }
    
        private Optional<ContainerRequestContext> getContainerRequestContext(ServiceLocator serviceLocator) {
            try {
                return Optional.of(serviceLocator.getService(ContainerRequestContext.class));
            } catch (MultiException e) {
                if (e.getCause() instanceof IllegalStateException) {
                    return Optional.empty();
                } else {
                    throw new ExceptionInInitializerError(e);
                }
            }
        }
    }
    

    It's then possible to go one step further and create your own OptionalContainerRequestContext type.

    public class OptionalContainerRequestContext {
    
        private final Optional<ContainerRequestContext> containerRequestContext;
    
        @Inject
        public OptionalContainerRequestContext(ServiceLocator serviceLocator) {
            this.containerRequestContext = getContainerRequestContext(serviceLocator);
        }
    
        public ContainerRequestContext get() {
            return containerRequestContext.get();
        }
    
        public boolean isPresent() {
            return containerRequestContext.isPresent();
        }
    
        private Optional<ContainerRequestContext> getContainerRequestContext(ServiceLocator serviceLocator) {
            try {
                return Optional.of(serviceLocator.getService(ContainerRequestContext.class));
            } catch (MultiException e) {
                if (e.getCause() instanceof IllegalStateException) {
                    return Optional.empty();
                } else {
                    throw new ExceptionInInitializerError(e);
                }
            }
        }
    }
    

    You can then bind it:

    bind(OptionalContainerRequestContext.class).to(OptionalContainerRequestContext.class);
    

    And then inject it wherever you need:

    public class MyObject {
    
        private final OptionalContainerRequestContext optionalContainerRequestContext;
    
        @Inject
        public MyObject(OptionalContainerRequestContext optionalContainerRequestContext) {
            this.optionalContainerRequestContext = optionalContainerRequestContext;
        }
    
    }