skip to Main Content

I use Jersey in a Java SE application. HK2 provides dependency injection to the overall application. HK2 RunLevel services are registered in the application service locator, which is the parent to Jerseys service locator.

+ application locator
|- RunLevel capabilities
| - MyCustomService, @RunLevel(value=1)
 
  + jersey locator
   - jersey resource class
      @Inject MyCustomService

My problem is that I cannot access runlevel-scoped services from within Jersey. When – in the above example – the jersey resource is opened, injection of MyCustomService fails:

java.lang.IllegalStateException: Could not find an active context for org.glassfish.hk2.runlevel.RunLevel

The reason for this seems to be that the services behind the HK2 RunLevel feature have visibility LOCAL: The jersey locator cannot access them via its parent locator. See here.

Questions:

  • Why are services of the runlevel feature restricted in visibility?
  • What can I do to overcome this?

Update

To give context to the question, I’m using runlevels in a “System-V” style.

  • The Java SE application starts. Default initial runlevel is -1, target runlevel is 3. On its way there, different stages must be passed successfully to continue.
  • At runlevel 1, connections to dependent external applications are established (database, memcache, message broker, etc).
  • At runlevel 2, ExecutorServices for background processing and HTTP services (running jersey) are started. Jersey rejects all incoming requests at this level.
  • At runlevel 3, MessageListeners are attached to the broker, feeding requests to the background executors. Jersey accepts and processes HTTP requests.

This concept allows granular control over availability and long running requests. When shutting down, the application will be at runlevel 2 until previously accepted HTTP requests are fulfilled and enqueued background tasks completed. However, no new tasks/requests are accepted. Then, runlevel 1, 0, -1, exit.

3

Answers


  1. Chosen as BEST ANSWER

    UPDATE: This does not work!

    I'll leave this here for educational purposes, but do not do this! Once the RunLevelController is accessed from within the service locator of jersey, it looses track of all the services it managed on the other service locator.

    UPDATE END

    I've solved it with this hack: I deliberately override the LOCAL visibility and insert the required descriptors to the service locator that is missing them.

    public class RunLevelBridge implements Feature {
        private final Logger log = LoggerFactory.getLogger(getClass());
        private final ServiceLocator sourceLocator;
    
        public RunLevelBridge(ServiceLocator sourceLocator) {
            this.sourceLocator = sourceLocator;
        }
    
        @Override
        public boolean configure(FeatureContext context) {
            if (sourceLocator == null) {
                log.error("Unable to bridge descriptors, the source service locator cannot be null");
                return false;
            }
    
            InjectionManager im = InjectionManagerProvider.getInjectionManager(context);
            ServiceLocator jerseyLocator = im.getInstance(ServiceLocator.class);
            if (jerseyLocator == null) {
                log.error("Unable to bridge descriptors, the target service locator cannot be null");
                return false;
            }
    
            if (!sourceLocator.equals(jerseyLocator.getParent())) {
                ExtrasUtilities.bridgeServiceLocator(jerseyLocator, sourceLocator);
                log.info("Bridge from {} into {} established", sourceLocator.getName(), jerseyLocator.getName());
            }
    
            Filter filter;
            filter = BuilderHelper.createContractFilter(RunLevelContext.class.getName());
            sourceLocator.getDescriptors(filter).forEach(
                    (descriptor) -> ServiceLocatorUtilities.addOneDescriptor(
                            jerseyLocator, 
                            descriptor, 
                            false
                    )
            );
            filter = BuilderHelper.createContractFilter(RunLevelController.class.getName());
            sourceLocator.getDescriptors(filter).forEach(
                    (descriptor) -> ServiceLocatorUtilities.addOneDescriptor(
                            jerseyLocator, 
                            descriptor, 
                            false
                    )
            );
    
            log.info("Added the RunLevel feature to jersey's service locator");
            return true;
        }
    
    }
    

    The bridge is registered in the ResourceConfig:

    public class ApplicationConfig extends ResourceConfig {
    
        public ApplicationConfig(ServiceLocator parentLocator) {
    
            // bridge runlevel into jersey
            register(new RunLevelBridge(parentLocator));
    
            // ... other stuff ...
    
        }
    }
    

    I encourage feedback regarding this unorthodox approach.


  2. The idea with children and the RunLevelService was that actual RunLevelServices would be thin service in the child locators that orchestrated the real services in the parent. And that multiple “subsystems” in a process might have different RunLevelService “compartments,” each in it’s own child of a parent.

    In that model you start/add the RunLevelService in the children, not in the parent. And in that way you can have multiple RunLevelServices at different levels in different children of the same parent.

    Sounds like you have a different use case though, which may not completely work. It’s worth considering your use case.

    Login or Signup to reply.
  3. The solution is to respect the DescriptorVisibility#LOCAL and inject services dependent on RunLevelContext only from the service locator that manages them.

    It is a bit cumbersome:

    • from within a Jersey resource, fetch the service locator with runlevel capabilities by name
    • get the runlevel-scoped service injected explicitely

      ServiceLocator applicationLocator = ServiceLocatorFactory.getInstance().find("applicationLocator");
      MyCustomService mcs = applicationLocator.getService(MyCustomService.class);
      mcs.doSomething();
      

    To reduce the danger of forgetting to do it this way, and just injecting MyCustomService into a jersey resource, I’ve now marked my runlevel-scoped services to be also of DescriptorVisibility#LOCAL. That way they can’t be injected by the jersey locator.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search