/** * 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; }