/**
   * Construct and load a new SubServicesBundle. Use default Spring application configuration to
   * build a new set of services.
   *
   * @param routerID
   * @return A new PathService instance from a new ApplicationContext.
   */
  private SubServicesBundle loadSubServices(String routerID) {
    /*
     * Create a parent context containing the bundle.
     */
    AbstractApplicationContext parentContext = new StaticApplicationContext();
    BeanDefinitionRegistry registry = (BeanDefinitionRegistry) parentContext;
    registerDataSource(routerID, parentContext, registry);
    parentContext.refresh();

    int retries = 0;
    SubServicesBundle retval = new SubServicesBundle();
    while (true) {
      try {
        /*
         * Create a new context to create a new path service, with all dependents services,
         * using the default application context definition. The creation of a new context
         * allow us to create new instances of service beans.
         */
        retval.context =
            new ClassPathXmlApplicationContext(subApplicationContextList, parentContext);
        AutowireCapableBeanFactory factory = retval.context.getAutowireCapableBeanFactory();
        retval.pathService = (PathService) factory.getBean("pathService", PathService.class);
        retval.patchService = (PatchService) factory.getBean("patchService", PatchService.class);
        break;

      } catch (BeanCreationException e) {
        /*
         * Copying a new graph should use an atomic copy, but for convenience if it is not,
         * we retry for a few times in case of truncated data before bailing out.
         *
         * The original StreamCorruptedException is buried within dozen of layers of other
         * exceptions, so we have to dig a bit (is this considered a hack?).
         */
        boolean streamCorrupted = false;
        Throwable t = e.getCause();
        while (t != null) {
          if (t instanceof StreamCorruptedException) {
            streamCorrupted = true;
            break;
          }
          t = t.getCause();
        }
        if (!streamCorrupted || retries++ > MAX_RETRIES) throw e;
        LOG.warn("Can't load " + routerID + " (" + e + "): retrying...");
        try {
          Thread.sleep(RETRY_INTERVAL);
        } catch (InterruptedException e1) {
        }
      }
    }
    return retval;
  }
 private SubServicesBundle doGetSubServiceBundle(String routerID) {
   /* Ensure we have a PathServiceBundle, even an empty one, to synchronize on. */
   SubServicesBundle ssb = null;
   synchronized (subServices) {
     ssb = subServices.get(routerID);
     if (ssb == null) {
       ssb = new SubServicesBundle();
       ssb.pathService = null;
       ssb.timestampLoaded = System.currentTimeMillis();
       ssb.reloadInProgress = false;
       subServices.put(routerID, ssb);
     }
   }
   /*
    * Synchronize access to the bundle only, to prevent blocking requests to other graphs.
    * Check for first-time loading, no background reload is possible since no older version is
    * available.
    */
   synchronized (ssb) {
     if (ssb.pathService == null) {
       SubServicesBundle newSsb = loadSubServices(routerID);
       ssb.pathService = newSsb.pathService;
       ssb.patchService = newSsb.patchService;
       ssb.context = newSsb.context;
       ssb.timestampLoaded = System.currentTimeMillis();
       return ssb;
     }
   }
   /* Here background reload becomes possible. */
   boolean reload = false;
   synchronized (ssb) {
     if (checkReload(routerID, ssb.timestampLoaded) && !ssb.reloadInProgress) {
       if (!asyncReload) {
         LOG.info("Reloading modified graph '" + routerID + "'");
         // Sync reload: remove old version before loading new one, in synchronized
         // block.
         ssb.pathService = null;
         ssb.patchService = null;
         ssb.context.close();
         ssb.context = null;
         SubServicesBundle newSsb = loadSubServices(routerID);
         ssb.pathService = newSsb.pathService;
         ssb.patchService = newSsb.patchService;
         ssb.context = newSsb.context;
         ssb.timestampLoaded = System.currentTimeMillis();
       } else {
         reload = true;
         ssb.reloadInProgress = true;
       }
     }
   }
   if (reload) {
     // Async reload: load new version but keep old one while not ready for other
     // requests.
     SubServicesBundle newSsb = loadSubServices(routerID);
     synchronized (ssb) {
       LOG.info("Async reloading modified graph '" + routerID + "'");
       ssb.pathService = newSsb.pathService;
       ssb.patchService = newSsb.patchService;
       ssb.context.close();
       ssb.context = newSsb.context;
       ssb.reloadInProgress = false;
       ssb.timestampLoaded = System.currentTimeMillis();
     }
   }
   return ssb;
 }