Java tutorial
/** * ========================================================================================== * = JAHIA'S DUAL LICENSING - IMPORTANT INFORMATION = * ========================================================================================== * * http://www.jahia.com * * Copyright (C) 2002-2017 Jahia Solutions Group SA. All rights reserved. * * THIS FILE IS AVAILABLE UNDER TWO DIFFERENT LICENSES: * 1/GPL OR 2/JSEL * * 1/ GPL * ================================================================================== * * IF YOU DECIDE TO CHOOSE THE GPL LICENSE, YOU MUST COMPLY WITH THE FOLLOWING TERMS: * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * * 2/ JSEL - Commercial and Supported Versions of the program * =================================================================================== * * IF YOU DECIDE TO CHOOSE THE JSEL LICENSE, YOU MUST COMPLY WITH THE FOLLOWING TERMS: * * Alternatively, commercial and supported versions of the program - also known as * Enterprise Distributions - must be used in accordance with the terms and conditions * contained in a separate written agreement between you and Jahia Solutions Group SA. * * If you are unsure which license is appropriate for your use, * please contact the sales department at sales@jahia.com. */ /* * Copyright (c) 2004 Your Corporation. All Rights Reserved. */ package org.jahia.services; import java.io.FileNotFoundException; import java.io.IOException; import java.util.*; import java.util.concurrent.*; import org.apache.commons.lang.ArrayUtils; import org.jahia.data.templates.JahiaTemplatesPackage; import org.jahia.exceptions.JahiaRuntimeException; import org.jahia.registries.ServicesRegistry; import org.jahia.services.templates.JahiaTemplateManagerService.TemplatePackageRedeployedEvent; import org.jahia.settings.SettingsBean; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.event.ApplicationEventMulticaster; import org.springframework.context.support.AbstractApplicationContext; import org.springframework.core.io.Resource; /** * Spring application context holder. * * @author Sergiy Shyrkov */ public class SpringContextSingleton implements ApplicationContextAware, ApplicationListener<TemplatePackageRedeployedEvent> { private static final Logger logger = LoggerFactory.getLogger(SpringContextSingleton.class); private static final String[] APPLICATION_CONTEXT_INITIALIZATION_IN_PROGRESS_INDICATORS = new String[] { "org.jahia.test.osgi.SpringContextSingletonTest$GetBeanThread", //used by unit tests "org.jahia.bundles.blueprint.extender.config.JahiaOsgiBundleXmlApplicationContext" // used when a spring context is starting up }; private static SpringContextSingleton ourInstance = new SpringContextSingleton(); private ApplicationContext context; private boolean initialized; private Map<String, Resource[]> resourcesCache; /** * Returns an instance of the requested bean. * * @param beanId the requested bean ID * @return an instance of the requested bean */ public static Object getBean(String beanId) { try { return getInstance().getContext().getBean(beanId); } catch (BeansException e) { return getBeanInModulesContext(beanId); } } public static Object getBeanInModulesContext(String beanId) { return getBeanInModulesContext(beanId, SettingsBean.getInstance().getModuleSpringBeansWaitingTimeout()); } private static Object getBeanInModulesContext(final String beanId, long waitTimeout) { for (JahiaTemplatesPackage aPackage : ServicesRegistry.getInstance().getJahiaTemplateManagerService() .getAvailableTemplatePackages()) { if (aPackage.getContext() != null && aPackage.getContext().containsBean(beanId)) { return aPackage.getContext().getBean(beanId); } } // Waiting for a missing bean only makes sense in case it is a part of application context initialization // during module startup, because there is a chance for the bean to appear later. Otherwise, multiple threads // waiting for missing beans could cause application collapse. if (waitTimeout > 0 && isApplicationContextInitializationInProgress()) { ExecutorService executor = Executors.newSingleThreadExecutor(); Future<Object> future = executor.submit(new Callable<Object>() { @Override public Object call() throws Exception { while (true) { Thread.sleep(100); try { return getBeanInModulesContext(beanId, 0); } catch (NoSuchBeanDefinitionException e) { logger.debug("Bean '{}' not found by the task loop, will retry in 100 ms", beanId); } } } }); if (SettingsBean.getInstance().isDevelopmentMode()) { logger.warn( "Detected call to SpringContextSingleton.getBeanInModulesContext(...) for bean '{}' during module startup." + "Since 7.2.0.0 modules spring contexts are started independently, and beans may not be available." + "We recommend to use OSGI services instead of spring beans to communicate between modules.", beanId); } logger.info("Bean '{}' not found yet, will wait for its availability max {} seconds...", beanId, waitTimeout); try { return future.get(waitTimeout, TimeUnit.SECONDS); } catch (TimeoutException e) { future.cancel(true); logger.debug("Waiting for bean '{}' timed out", beanId); } catch (InterruptedException | ExecutionException e) { throw new JahiaRuntimeException(e); } finally { executor.shutdownNow(); } logger.info("Bean '{}' not found in module contexts", beanId); } throw new NoSuchBeanDefinitionException(beanId); } private static boolean isApplicationContextInitializationInProgress() { StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); for (StackTraceElement element : stackTrace) { for (String className : APPLICATION_CONTEXT_INITIALIZATION_IN_PROGRESS_INDICATORS) { if (element.getClassName().equals(className)) { return true; } } } return false; } /** * Returns a map with beans of the specified type, including beans in modules. * * @param type * the bean type to search for * @return a map with beans of the specified type, including beans in modules * @throws BeansException * in case of a lookup error */ public static <T> Map<String, T> getBeansOfType(Class<T> type) throws BeansException { Map<String, T> found = new LinkedHashMap<String, T>(); found.putAll(SpringContextSingleton.getInstance().getContext().getBeansOfType(type)); for (JahiaTemplatesPackage aPackage : ServicesRegistry.getInstance().getJahiaTemplateManagerService() .getAvailableTemplatePackages()) { if (aPackage.getContext() != null) { found.putAll(aPackage.getContext().getBeansOfType(type)); } } return found; } public static SpringContextSingleton getInstance() { return ourInstance; } private SpringContextSingleton() { resourcesCache = new HashMap<String, Resource[]>(2); } /** * Returns the Spring application context instance. * * @return the Spring application context instance */ public ApplicationContext getContext() { if (!initialized) { logger.warn( "Trying to access Spring context before it is available ! Please refactor code to avoid this !"); } return context; } /** * Publishes the specified event in the core Spring context and publishes to each module's context. When publishing the event to a * module, the {@link ApplicationEventMulticaster#multicastEvent(ApplicationEvent)} method is used to skip publishing event to the * module's parent context (which is our Spring core context). * * @param event * the Spring event to be published */ public void publishEvent(ApplicationEvent event) { publishEvent(event, true); } /** * Publishes the specified event in the core Spring context and if <code>propagateToModules</code> is set to true, publishes that event * to each module's context. When publishing the event to a module, the * {@link ApplicationEventMulticaster#multicastEvent(ApplicationEvent)} method is used to skip publishing event to the module's parent * context (which is our Spring core context). * * @param event * the Spring event to be published * @param propagateToModules * whether to propagate the event to all modules */ public void publishEvent(ApplicationEvent event, boolean propagateToModules) { getContext().publishEvent(event); for (JahiaTemplatesPackage aPackage : ServicesRegistry.getInstance().getJahiaTemplateManagerService() .getAvailableTemplatePackages()) { if (aPackage.getContext() != null) { multicastEvent(event, aPackage.getContext()); } } } private void multicastEvent(ApplicationEvent event, AbstractApplicationContext ctx) { if (!ctx.isActive()) { return; } if (ctx.containsBean(AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) { ((ApplicationEventMulticaster) ctx .getBean(AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) .multicastEvent(event); } else { // fall back to publishEvent() ctx.publishEvent(event); } } public boolean isInitialized() { return initialized; } public void onApplicationEvent(TemplatePackageRedeployedEvent event) { resourcesCache.clear(); } public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.context = applicationContext; initialized = true; } /** * Searches for Spring resource locations given the specified (pattern-based) location. Multiple locations can be provided separated by * comma (or any delimiter, defined in {@link org.springframework.context.ConfigurableApplicationContext#CONFIG_LOCATION_DELIMITERS} ). * * @param locationPatterns * (pattern-based) location to search for resources. Multiple locations can be provided separated by comma (or any delimiter, * defined in {@link org.springframework.context.ConfigurableApplicationContext#CONFIG_LOCATION_DELIMITERS} ) * @return an array of {@link Resource} objects found * @throws IOException * in case of a lookup error */ public Resource[] getResources(String locationPatterns) throws IOException { return getResources(locationPatterns, true); } /** * Searches for Spring resource locations given the specified (pattern-based) location. Multiple locations can be provided separated by * comma (or any delimiter, defined in {@link org.springframework.context.ConfigurableApplicationContext#CONFIG_LOCATION_DELIMITERS} ). * * @param locationPatterns * (pattern-based) location to search for resources. Multiple locations can be provided separated by comma (or any delimiter, * defined in {@link org.springframework.context.ConfigurableApplicationContext#CONFIG_LOCATION_DELIMITERS} ) * @param useCache can we use lookup caches? * @return an array of {@link Resource} objects found * @throws IOException * in case of a lookup error */ public Resource[] getResources(String locationPatterns, boolean useCache) throws IOException { Resource[] allResources = useCache ? resourcesCache.get(locationPatterns) : null; if (allResources == null) { allResources = new Resource[0]; for (String location : org.springframework.util.StringUtils.tokenizeToStringArray(locationPatterns, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)) { try { allResources = (Resource[]) ArrayUtils.addAll(allResources, context.getResources(location.trim())); } catch (FileNotFoundException e) { // Ignore logger.debug("Cannot find resources", e); } } if (useCache) { resourcesCache.put(locationPatterns, allResources); } } return allResources; } }