Java tutorial
/* * Copyright 2010-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.data.gemfire.support; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Properties; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicReference; import org.apache.geode.cache.Cache; import org.apache.geode.cache.Declarable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextException; import org.springframework.context.ApplicationListener; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.event.ApplicationContextEvent; import org.springframework.context.event.ApplicationEventMulticaster; import org.springframework.context.event.ContextClosedEvent; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.context.event.SimpleApplicationEventMulticaster; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.data.gemfire.util.CacheUtils; import org.springframework.data.gemfire.util.CollectionUtils; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; /** * The {@link SpringContextBootstrappingInitializer} class is a Pivotal GemFire configuration initializer used to bootstrap * a Spring {@link ApplicationContext} inside a Pivotal GemFire Server JVM-based process. This enables a Pivotal GemFire Server * resource to be mostly configured with SDG's configuration meta-data. The Pivotal GemFire {@link Cache} * itself is the only resource that cannot be configured and initialized in a Spring context since the initializer * is not invoked until after Pivotal GemFire creates and initializes the Pivotal GemFire {@link Cache} for use. * * @author John Blum * @see org.springframework.context.ApplicationContext * @see org.springframework.context.ApplicationListener * @see org.springframework.context.ConfigurableApplicationContext * @see org.springframework.context.annotation.AnnotationConfigApplicationContext * @see org.springframework.context.event.ApplicationContextEvent * @see org.springframework.context.event.ApplicationEventMulticaster * @see org.springframework.context.support.ClassPathXmlApplicationContext * @see org.springframework.core.io.DefaultResourceLoader * @see org.apache.geode.cache.Cache * @see org.apache.geode.cache.Declarable * @link https://gemfire.docs.pivotal.io/latest/userguide/index.html#basic_config/the_cache/setting_cache_initializer.html * @link https://jira.springsource.org/browse/SGF-248 * @since 1.4.0 */ @SuppressWarnings("unused") public class SpringContextBootstrappingInitializer implements Declarable, ApplicationListener<ApplicationContextEvent> { public static final String BASE_PACKAGES_PARAMETER = "basePackages"; public static final String CONTEXT_CONFIG_LOCATIONS_PARAMETER = "contextConfigLocations"; protected static final String CHARS_TO_DELETE = " \n\t"; protected static final String COMMA_DELIMITER = ","; private static final ApplicationEventMulticaster applicationEventNotifier = new SimpleApplicationEventMulticaster(); private static final AtomicReference<ClassLoader> beanClassLoaderReference = new AtomicReference<>(null); static volatile ConfigurableApplicationContext applicationContext; static volatile ContextRefreshedEvent contextRefreshedEvent; private static final List<Class<?>> registeredAnnotatedClasses = new CopyOnWriteArrayList<>(); protected final Logger logger = initLogger(); /** * Gets a reference to the Spring ApplicationContext constructed, configured and initialized inside the GemFire * Server-based JVM process. * * @return a reference to the Spring ApplicationContext bootstrapped by GemFire. * @see org.springframework.context.ConfigurableApplicationContext */ public static synchronized ConfigurableApplicationContext getApplicationContext() { Assert.state(applicationContext != null, "A Spring ApplicationContext was not configured and initialized properly"); return applicationContext; } /** * Sets the ClassLoader used by the Spring ApplicationContext, created by this Pivotal GemFire Initializer, when creating * bean definition classes. * * @param beanClassLoader the ClassLoader used by the Spring ApplicationContext to load bean definition classes. * @throws java.lang.IllegalStateException if the Spring ApplicationContext has already been created * and initialized. * @see java.lang.ClassLoader */ public static void setBeanClassLoader(ClassLoader beanClassLoader) { if (isApplicationContextInitializable()) { beanClassLoaderReference.set(beanClassLoader); } else { throw new IllegalStateException("A Spring ApplicationContext has already been initialized"); } } /** * Notifies any Spring ApplicationListeners of a current and existing ContextRefreshedEvent if the * ApplicationContext had been previously created, initialized and refreshed before any ApplicationListeners * interested in ContextRefreshedEvents were registered so that application components (such as the * Pivotal GemFire CacheLoaders extending LazyWiringDeclarableSupport objects) registered late, requiring configuration * (auto-wiring), also get notified and wired accordingly. * * @param listener a Spring ApplicationListener requiring notification of any ContextRefreshedEvents after the * ApplicationContext has already been created, initialized and/or refreshed. * @see org.springframework.context.ApplicationListener#onApplicationEvent(org.springframework.context.ApplicationEvent) * @see org.springframework.context.event.ContextRefreshedEvent */ protected static void notifyOnExistingContextRefreshedEvent( ApplicationListener<ContextRefreshedEvent> listener) { synchronized (applicationEventNotifier) { if (contextRefreshedEvent != null) { listener.onApplicationEvent(contextRefreshedEvent); } } } /** * Registers a Spring ApplicationListener to be notified when the Spring ApplicationContext is created by GemFire * when instantiating and initializing Declarables declared inside the <initializer> block inside Pivotal GemFire's * cache.xml file. * * @param <T> the Class type of the Spring ApplicationListener. * @param listener the ApplicationListener to register for ContextRefreshedEvents multi-casted by this * SpringContextBootstrappingInitializer. * @return the reference to the ApplicationListener for method call chaining purposes. * @see #notifyOnExistingContextRefreshedEvent(org.springframework.context.ApplicationListener) * @see #unregister(org.springframework.context.ApplicationListener) * @see org.springframework.context.ApplicationListener * @see org.springframework.context.event.ContextRefreshedEvent * @see org.springframework.context.event.SimpleApplicationEventMulticaster * #addApplicationListener(org.springframework.context.ApplicationListener) */ public static <T extends ApplicationListener<ContextRefreshedEvent>> T register(T listener) { synchronized (applicationEventNotifier) { applicationEventNotifier.addApplicationListener(listener); notifyOnExistingContextRefreshedEvent(listener); } return listener; } /** * Registers the specified Spring annotated POJO class, which will be used to configure and initialize * the Spring ApplicationContext. * * @param annotatedClass the Spring annotated (@Configuration) POJO class to register. * @return a boolean value indicating whether the Spring annotated POJO class was successfully registered. * @see #unregister(Class) */ public static boolean register(Class<?> annotatedClass) { Assert.notNull(annotatedClass, "The Spring annotated class to register must not be null"); return registeredAnnotatedClasses.add(annotatedClass); } /** * Un-registers the Spring ApplicationListener from this SpringContextBootstrappingInitializer in order to stop * receiving ApplicationEvents on Spring context refreshes. * * @param <T> the Class type of the Spring ApplicationListener. * @param listener the ApplicationListener to unregister from receiving ContextRefreshedEvents by this * SpringContextBootstrappingInitializer. * @return the reference to the ApplicationListener for method call chaining purposes. * @see #register(org.springframework.context.ApplicationListener) * @see org.springframework.context.ApplicationListener * @see org.springframework.context.event.ContextRefreshedEvent * @see org.springframework.context.event.SimpleApplicationEventMulticaster * #removeApplicationListener(org.springframework.context.ApplicationListener) */ public static <T extends ApplicationListener<ContextRefreshedEvent>> T unregister(T listener) { synchronized (applicationEventNotifier) { applicationEventNotifier.removeApplicationListener(listener); } return listener; } /** * Un-registers the specified Spring annotated POJO class used to configure and initialize * the Spring ApplicationContext. * * @param annotatedClass the Spring annotated (@Configuration) POJO class to unregister. * @return a boolean value indicating whether the Spring annotated POJO class was successfully un-registered. * @see #register(Class) */ public static boolean unregister(Class<?> annotatedClass) { return registeredAnnotatedClasses.remove(annotatedClass); } /** * Initialization method for the logger used to log important messages from this initializer. * * @return a Apache Commons Log used to log messages from this initializer * @see org.apache.commons.logging.LogFactory#getLog(Class) * @see org.apache.commons.logging.Log */ protected Logger initLogger() { return LoggerFactory.getLogger(getClass()); } private boolean isConfigurable(Collection<Class<?>> annotatedClasses, String[] basePackages, String[] contextConfigLocations) { return !(CollectionUtils.isEmpty(annotatedClasses) && ObjectUtils.isEmpty(basePackages) && ObjectUtils.isEmpty(contextConfigLocations)); } /** * Creates (constructs and configures) an instance of the ConfigurableApplicationContext based on either the * specified base packages containing @Configuration, @Component or JSR 330 annotated classes to scan, or the * specified locations of context configuration meta-data files. The created ConfigurableApplicationContext * is not automatically "refreshed" and therefore must be "refreshed" by the caller manually. * * When basePackages are specified, an instance of AnnotationConfigApplicationContext is constructed and a scan * is performed; otherwise an instance of the ClassPathXmlApplicationContext is initialized with the * configLocations. This method prefers the ClassPathXmlApplicationContext to the * AnnotationConfigApplicationContext when both basePackages and configLocations are specified. * * @param basePackages the base packages to scan for application @Components and @Configuration classes. * @param configLocations a String array indicating the locations of the context configuration meta-data files * used to configure the ClassPathXmlApplicationContext instance. * @return an instance of ConfigurableApplicationContext configured and initialized with either configLocations * or the basePackages when configLocations is unspecified. Note, the "refresh" method must be called manually * before using the context. * @throws IllegalArgumentException if both the basePackages and configLocation parameter arguments * are null or empty. * @see #createApplicationContext(String[]) * @see org.springframework.context.annotation.AnnotationConfigApplicationContext * @see org.springframework.context.annotation.AnnotationConfigApplicationContext#scan(String...) * @see org.springframework.context.support.ClassPathXmlApplicationContext */ protected ConfigurableApplicationContext createApplicationContext(String[] basePackages, String[] configLocations) { String message = "'AnnotatedClasses', 'basePackages' or 'configLocations' must be specified" + " in order to construct and configure an instance of the ConfigurableApplicationContext"; Assert.isTrue(isConfigurable(registeredAnnotatedClasses, basePackages, configLocations), message); Class<?>[] annotatedClasses = registeredAnnotatedClasses.toArray(new Class<?>[0]); ConfigurableApplicationContext applicationContext = createApplicationContext(configLocations); return scanBasePackages(registerAnnotatedClasses(applicationContext, annotatedClasses), basePackages); } ConfigurableApplicationContext createApplicationContext(String[] configLocations) { return ObjectUtils.isEmpty(configLocations) ? new AnnotationConfigApplicationContext() : new ClassPathXmlApplicationContext(configLocations, false); } /** * Initializes the given ApplicationContext by registering this SpringContextBootstrappingInitializer as an * ApplicationListener and registering a runtime shutdown hook. * * @param applicationContext the ConfigurableApplicationContext to initialize. * @return the initialized ApplicationContext. * @see org.springframework.context.ConfigurableApplicationContext * @see org.springframework.context.ConfigurableApplicationContext#addApplicationListener(org.springframework.context.ApplicationListener) * @see org.springframework.context.ConfigurableApplicationContext#registerShutdownHook() * @throws java.lang.IllegalArgumentException if the ApplicationContext reference is null! */ protected ConfigurableApplicationContext initApplicationContext( ConfigurableApplicationContext applicationContext) { Assert.notNull(applicationContext, "ConfigurableApplicationContext must not be null"); applicationContext.addApplicationListener(this); applicationContext.registerShutdownHook(); return setClassLoader(applicationContext); } /** * Refreshes the given ApplicationContext making the context active. * * @param applicationContext the ConfigurableApplicationContext to refresh. * @return the refreshed ApplicationContext. * @see org.springframework.context.ConfigurableApplicationContext * @see org.springframework.context.ConfigurableApplicationContext#refresh() * @throws java.lang.IllegalArgumentException if the ApplicationContext reference is null! */ protected ConfigurableApplicationContext refreshApplicationContext( ConfigurableApplicationContext applicationContext) { Assert.notNull(applicationContext, "ConfigurableApplicationContext must not be null"); applicationContext.refresh(); return applicationContext; } /** * Registers the given Spring annotated (@Configuration) POJO classes with the specified * AnnotationConfigApplicationContext. * * @param applicationContext the AnnotationConfigApplicationContext used to register the Spring annotated, * POJO classes. * @param annotatedClasses a Class array of Spring annotated (@Configuration) classes used to configure * and initialize the Spring AnnotationConfigApplicationContext. * @return the given AnnotationConfigApplicationContext. * @see org.springframework.context.annotation.AnnotationConfigApplicationContext#register(Class[]) */ ConfigurableApplicationContext registerAnnotatedClasses(ConfigurableApplicationContext applicationContext, Class<?>[] annotatedClasses) { return applicationContext instanceof AnnotationConfigApplicationContext && !ObjectUtils.isEmpty(annotatedClasses) ? doRegister(applicationContext, annotatedClasses) : applicationContext; } ConfigurableApplicationContext doRegister(ConfigurableApplicationContext applicationContext, Class<?>[] annotatedClasses) { ((AnnotationConfigApplicationContext) applicationContext).register(annotatedClasses); return applicationContext; } /** * Configures classpath component scanning using the specified base packages on the specified * AnnotationConfigApplicationContext. * * @param applicationContext the AnnotationConfigApplicationContext to setup with classpath component scanning * using the specified base packages. * @param basePackages an array of Strings indicating the base packages to use in the classpath component scan. * @return the given AnnotationConfigApplicationContext. * @see org.springframework.context.annotation.AnnotationConfigApplicationContext#scan(String...) */ ConfigurableApplicationContext scanBasePackages(ConfigurableApplicationContext applicationContext, String[] basePackages) { return applicationContext instanceof AnnotationConfigApplicationContext && !ObjectUtils.isEmpty(basePackages) ? doScan(applicationContext, basePackages) : applicationContext; } ConfigurableApplicationContext doScan(ConfigurableApplicationContext applicationContext, String[] basePackages) { ((AnnotationConfigApplicationContext) applicationContext).scan(basePackages); return applicationContext; } /** * Sets the ClassLoader used to load bean definition classes on the Spring ApplicationContext. * * @param applicationContext the Spring ApplicationContext in which to configure the ClassLoader. * @return the given Spring ApplicationContext. * @see org.springframework.core.io.DefaultResourceLoader#setClassLoader(ClassLoader) * @see java.lang.ClassLoader */ ConfigurableApplicationContext setClassLoader(ConfigurableApplicationContext applicationContext) { ClassLoader beanClassLoader = beanClassLoaderReference.get(); if (applicationContext instanceof DefaultResourceLoader && beanClassLoader != null) { ((DefaultResourceLoader) applicationContext).setClassLoader(beanClassLoader); } return applicationContext; } @Override @SuppressWarnings("deprecation") public void init(Properties parameters) { init(CacheUtils.getCache(), parameters); } /** * Initializes a Spring {@link ApplicationContext} with the given parameters specified with an Apache Geode * or Pivotal GemFire <initializer> block in {@literal cache.xml}. * * @param parameters {@link Properties} object containing the configuration parameters and settings defined in the * Apache Geode/Pivotal GemFire {@literal cache.xml} <initializer> block for the declared * {@link SpringContextBootstrappingInitializer} Apache Geode/Pivotal GemFire {@link Declarable} object. * @param cache reference to the peer {@link Cache}. * @throws org.springframework.context.ApplicationContextException if the Spring {@link ApplicationContext} * could not be successfully constructed, configured and initialized. * @see #createApplicationContext(String[], String[]) * @see #initApplicationContext(org.springframework.context.ConfigurableApplicationContext) * @see #refreshApplicationContext(org.springframework.context.ConfigurableApplicationContext) * @see java.util.Properties */ public void init(Cache cache, Properties parameters) { try { synchronized (SpringContextBootstrappingInitializer.class) { if (isApplicationContextInitializable()) { String basePackages = parameters.getProperty(BASE_PACKAGES_PARAMETER); String contextConfigLocations = parameters.getProperty(CONTEXT_CONFIG_LOCATIONS_PARAMETER); String[] basePackagesArray = StringUtils.delimitedListToStringArray( StringUtils.trimWhitespace(basePackages), COMMA_DELIMITER, CHARS_TO_DELETE); String[] contextConfigLocationsArray = StringUtils.delimitedListToStringArray( StringUtils.trimWhitespace(contextConfigLocations), COMMA_DELIMITER, CHARS_TO_DELETE); ConfigurableApplicationContext localApplicationContext = refreshApplicationContext( initApplicationContext( createApplicationContext(basePackagesArray, contextConfigLocationsArray))); Assert.state(localApplicationContext.isRunning(), String.format( "The Spring ApplicationContext (%1$s) failed to be properly initialized with the context config files (%2$s) or base packages (%3$s)!", nullSafeGetApplicationContextId(localApplicationContext), Arrays.toString(contextConfigLocationsArray), Arrays.toString(basePackagesArray))); applicationContext = localApplicationContext; } } } catch (Throwable cause) { String message = "Failed to bootstrap the Spring ApplicationContext"; logger.error(message, cause); throw new ApplicationContextException(message, cause); } } private static boolean isApplicationContextInitializable() { return applicationContext == null || !applicationContext.isActive(); } /** * Null-safe operation used to get the ID of the Spring ApplicationContext. * * @param applicationContext the Spring ApplicationContext from which to get the ID. * @return the ID of the given Spring ApplicationContext or null if the ApplicationContext reference is null. * @see org.springframework.context.ApplicationContext#getId() */ String nullSafeGetApplicationContextId(ApplicationContext applicationContext) { return applicationContext != null ? applicationContext.getId() : null; } /** * Gets notified when the Spring ApplicationContext gets created and refreshed by GemFire, once the * <initializer> block is processed and the SpringContextBootstrappingInitializer Declarable component * is initialized. This handler method proceeds in notifying any other Pivotal GemFire components that need to be aware * that the Spring ApplicationContext now exists and is ready for use, such as other Declarable Pivotal GemFire objects * requiring auto-wiring support, etc. * * In addition, this method handles the ContextClosedEvent by removing the ApplicationContext reference. * * @param event the ApplicationContextEvent signaling that the Spring ApplicationContext has been created * and refreshed by GemFire, or closed when the JVM process exits. * @see org.springframework.context.event.ContextClosedEvent * @see org.springframework.context.event.ContextRefreshedEvent * @see org.springframework.context.event.ApplicationEventMulticaster * #multicastEvent(org.springframework.context.ApplicationEvent) */ @Override public void onApplicationEvent(ApplicationContextEvent event) { if (event instanceof ContextRefreshedEvent) { synchronized (applicationEventNotifier) { contextRefreshedEvent = (ContextRefreshedEvent) event; applicationEventNotifier.multicastEvent(event); } } else if (event instanceof ContextClosedEvent) { synchronized (applicationEventNotifier) { contextRefreshedEvent = null; } } } }