diff --git a/src/main/java/org/springframework/data/gemfire/support/SpringContextBootstrappingInitializer.java b/src/main/java/org/springframework/data/gemfire/support/SpringContextBootstrappingInitializer.java index 571abe32..c39ab6c1 100644 --- a/src/main/java/org/springframework/data/gemfire/support/SpringContextBootstrappingInitializer.java +++ b/src/main/java/org/springframework/data/gemfire/support/SpringContextBootstrappingInitializer.java @@ -23,7 +23,9 @@ import org.springframework.context.ApplicationContext; 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; @@ -41,22 +43,18 @@ import com.gemstone.gemfire.cache.Declarable; * and initializes the Cache for use. * * @author John Blum - * @see java.util.Properties * @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.ApplicationEventMulticaster - * @see org.springframework.context.event.ContextRefreshedEvent - * @see org.springframework.context.event.SimpleApplicationEventMulticaster * @see org.springframework.context.support.ClassPathXmlApplicationContext * @see com.gemstone.gemfire.cache.Declarable - * @since 1.4.0 - * @see Setting Cache Initializer + * @see Setting Cache Initializer * @see SGF-248 + * @since 1.4.0 */ @SuppressWarnings("unused") -public class SpringContextBootstrappingInitializer implements Declarable, ApplicationListener { +public class SpringContextBootstrappingInitializer implements Declarable, ApplicationListener { public static final String BASE_PACKAGES_PARAMETER = "basePackages"; public static final String CONTEXT_CONFIG_LOCATIONS_PARAMETER = "contextConfigLocations"; @@ -64,12 +62,11 @@ public class SpringContextBootstrappingInitializer implements Declarable, Applic protected static final String CHARS_TO_DELETE = " \n\t"; protected static final String COMMA_DELIMITER = ","; + private static final ApplicationEventMulticaster applicationEventNotifier = new SimpleApplicationEventMulticaster(); + /* package-private */ static volatile ConfigurableApplicationContext applicationContext; - // TODO consider whether I should register a TaskExecutor to perform the event notifications in a separate Thread??? - private static final ApplicationEventMulticaster eventNotifier = new SimpleApplicationEventMulticaster(); - - private static ContextRefreshedEvent contextRefreshedEvent; + /* package-private */ static volatile ContextRefreshedEvent contextRefreshedEvent; /** * Gets a reference to the Spring ApplicationContext constructed, configured and initialized inside the GemFire @@ -79,19 +76,40 @@ public class SpringContextBootstrappingInitializer implements Declarable, Applic * @see org.springframework.context.ConfigurableApplicationContext */ public static synchronized ConfigurableApplicationContext getApplicationContext() { - Assert.state(applicationContext != null, "The Spring ApplicationContext has not been created!"); + Assert.state(applicationContext != null, "The Spring ApplicationContext has not been properly configured and initialized!"); return applicationContext; } + /** + * 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 + * 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(final ApplicationListener 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 declared Initializers from the GemFire native configuration file - * (e.g. cache.xml). + * when instantiating and initializing Declarables declared inside the <initializer> block inside GemFire's + * cache.xml file. * * @param the Class type of the Spring ApplicationListener. - * @param listener the ApplicationListener to register for ContextRefreshedEvents by this + * @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 @@ -99,38 +117,16 @@ public class SpringContextBootstrappingInitializer implements Declarable, Applic * #addApplicationListener(org.springframework.context.ApplicationListener) */ public static > T register(final T listener) { - synchronized (eventNotifier) { - eventNotifier.addApplicationListener(listener); - notifyListenerOfExistingContextRefreshedEvent(listener); + synchronized (applicationEventNotifier) { + applicationEventNotifier.addApplicationListener(listener); + notifyOnExistingContextRefreshedEvent(listener); } return listener; } /** - * Notifies any Spring ApplicationListeners of a current and existing ContextRefreshedEvent if the - * ApplicationContext was previously created, initialized and refreshed before any ApplicationListeners interested - * in ContextRefreshedEvents get registered so that application components (such as LazyWiringDeclarableSupport - * objects) arriving late to the game that also require configuration (auto-wiring) get wired accordingly too. - * - * @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 notifyListenerOfExistingContextRefreshedEvent( - final ApplicationListener listener) { - synchronized (eventNotifier) { - // NOTE the null check on the ApplicationContext is not absolutely necessary, but is an extra safety - // precaution none-the-less. - if (applicationContext != null && contextRefreshedEvent != null) { - listener.onApplicationEvent(contextRefreshedEvent); - } - } - } - - /** - * Unregisters the Spring ApplicationListener from this SpringContextBootstrappingInitializer in order to stop + * Un-registers the Spring ApplicationListener from this SpringContextBootstrappingInitializer in order to stop * receiving ApplicationEvents on Spring context refreshes. * * @param the Class type of the Spring ApplicationListener. @@ -144,8 +140,8 @@ public class SpringContextBootstrappingInitializer implements Declarable, Applic * #removeApplicationListener(org.springframework.context.ApplicationListener) */ public static > T unregister(final T listener) { - synchronized (eventNotifier) { - eventNotifier.removeApplicationListener(listener); + synchronized (applicationEventNotifier) { + applicationEventNotifier.removeApplicationListener(listener); } return listener; @@ -162,29 +158,28 @@ public class SpringContextBootstrappingInitializer implements Declarable, Applic * "refresh" method must be called manually before using the context. * @throws IllegalArgumentException if the configLocations parameter argument is null or empty. * @see #createApplicationContext(String[], String[]) - * @see org.springframework.context.support.ClassPathXmlApplicationContext + * @see org.springframework.context.ConfigurableApplicationContext */ protected ConfigurableApplicationContext createApplicationContext(final String[] configLocations) { - Assert.notEmpty(configLocations, "'configLocations' must be specified to construct an instance" - + " of the ClassPathXmlApplicationContext."); + Assert.notEmpty(configLocations, "'configLocations' must be specified to construct and configure" + + " an instance of the ClassPathXmlApplicationContext."); return createApplicationContext(null, configLocations); } /** * 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 used to configure the context. The created - * ConfigurableApplicationContext is not automatically "refreshed" and therefore must be "refreshed" - * by the caller manually. + * 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 returned; otherwise - * an instance of the ClassPathXmlApplicationContext is initialized with the configLocations and returned. - * This method prefers the ClassPathXmlApplicationContext to the AnnotationConfigApplicationContext when both - * basePackages and configLocations are specified. + * 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 application packages to scan for application @Components and @Configuration classes. * + * @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 ConfigurableApplicationContext instance. + * 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. @@ -201,8 +196,8 @@ public class SpringContextBootstrappingInitializer implements Declarable, Applic return new ClassPathXmlApplicationContext(configLocations, false); } else { - Assert.notEmpty(basePackages, "Either 'basePackages' or 'configLocations' must be specified" - + " to construct an instance of the ConfigurableApplicationContext."); + Assert.notEmpty(basePackages, "'basePackages' or 'configLocations' must be specified" + + " to construct and configure an instance of the ConfigurableApplicationContext."); AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); applicationContext.scan(basePackages); return applicationContext; @@ -210,46 +205,38 @@ public class SpringContextBootstrappingInitializer implements Declarable, Applic } /** - * Initializes a Spring ApplicationContext with the given parameters from a GemFire Initializer in GemFire native - * configuration meta-data (e.g. cache.xml). + * Initializes the given ApplicationContext by registering this SpringContextBootstrappingInitializer as an + * ApplicationListener and registering a Runtime shutdown hook. * - * @param parameters a Properties object containing the configuration parameters and settings defined in the - * GemFire cache.xml >initializer/< element. - * @see #createApplicationContext - * @see java.util.Properties + * @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! */ - @Override - @SuppressWarnings("null") - public void init(final Properties parameters) { - String basePackages = parameters.getProperty(BASE_PACKAGES_PARAMETER); - String contextConfigLocations = parameters.getProperty(CONTEXT_CONFIG_LOCATIONS_PARAMETER); + protected ConfigurableApplicationContext initApplicationContext( + final ConfigurableApplicationContext applicationContext) { + Assert.notNull(applicationContext, "The ConfigurableApplicationContext reference must not be null!"); + applicationContext.addApplicationListener(this); + applicationContext.registerShutdownHook(); + return applicationContext; + } - Assert.isTrue(StringUtils.hasText(basePackages) || StringUtils.hasText(contextConfigLocations), - "Either 'basePackages' or the 'contextConfigLocations' parameter must be specified."); - - String[] basePackagesArray = StringUtils.delimitedListToStringArray(basePackages, - COMMA_DELIMITER, CHARS_TO_DELETE); - - String[] configLocations = StringUtils.delimitedListToStringArray(contextConfigLocations, - COMMA_DELIMITER, CHARS_TO_DELETE); - - synchronized (SpringContextBootstrappingInitializer.class) { - Assert.state(applicationContext == null, String.format( - "A Spring application context with ID (%1$s) has already been created.", - nullSafeGetApplicationContextId(applicationContext))); - - applicationContext = createApplicationContext(basePackagesArray, configLocations); - Assert.notNull(applicationContext, "The 'created' ConfigurableApplicationContext cannot be null!"); - assert applicationContext != null; - applicationContext.addApplicationListener(this); - applicationContext.registerShutdownHook(); - applicationContext.refresh(); - - Assert.state(applicationContext.isActive(), String.format( - "The Spring application context (%1$s) has failed to be properly initialized with the following config files (%2$s) or base packages (%3$s)!", - nullSafeGetApplicationContextId(applicationContext), Arrays.toString(configLocations), - Arrays.toString(basePackagesArray))); - } + /** + * 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( + final ConfigurableApplicationContext applicationContext) { + Assert.notNull(applicationContext, "The ConfigurableApplicationContext reference must not be null!"); + applicationContext.refresh(); + return applicationContext; } /** @@ -264,21 +251,74 @@ public class SpringContextBootstrappingInitializer implements Declarable, Applic } /** - * Gets notified when the Spring ApplicationContext gets created and refreshed by GemFire. The handler method - * proceeds in notifying any other GemFire components that need to be aware that the Spring ApplicationContext - * now exists and is ready for use, such as other Declarable GemFire objects requiring auto-wiring support, etc. + * Initializes a Spring ApplicationContext with the given parameters specified with a GemFire <initializer> + * block in cache.xml. * - * @param event the ContextRefreshedEvent signaling that the Spring ApplicationContext has been created - * and refreshed by GemFire. - * @see org.springframework.context.event.ContextRefreshedEvent - * @see org.springframework.context.event.ApplicationEventMulticaster - * #multicastEvent(org.springframework.context.ApplicationEvent) + * @param parameters a Properties object containing the configuration parameters and settings defined in the + * GemFire cache.xml <initializer> block for the declared SpringContextBootstrappingInitializer + * GemFire Declarable object. + * @see #createApplicationContext(String[], String[]) + * @see #initApplicationContext(org.springframework.context.ConfigurableApplicationContext) + * @see #refreshApplicationContext(org.springframework.context.ConfigurableApplicationContext) + * @see java.util.Properties */ @Override - public void onApplicationEvent(final ContextRefreshedEvent event) { - synchronized (eventNotifier) { - contextRefreshedEvent = event; - eventNotifier.multicastEvent(event); + public void init(final Properties parameters) { + synchronized (SpringContextBootstrappingInitializer.class) { + if (applicationContext == null || !applicationContext.isActive()) { + String basePackages = parameters.getProperty(BASE_PACKAGES_PARAMETER); + String contextConfigLocations = parameters.getProperty(CONTEXT_CONFIG_LOCATIONS_PARAMETER); + + Assert.isTrue(StringUtils.hasText(basePackages) || StringUtils.hasText(contextConfigLocations), + "Either 'basePackages' or the 'contextConfigLocations' parameter must be specified."); + + String[] basePackagesArray = StringUtils.delimitedListToStringArray(basePackages, + COMMA_DELIMITER, CHARS_TO_DELETE); + + String[] contextConfigLocationsArray = StringUtils.delimitedListToStringArray(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(applicationContext), Arrays.toString(contextConfigLocationsArray), + Arrays.toString(basePackagesArray))); + + applicationContext = localApplicationContext; + } + } + } + + /** + * 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 GemFire components that need to be aware + * that the Spring ApplicationContext now exists and is ready for use, such as other Declarable 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(final ApplicationContextEvent event) { + if (event instanceof ContextRefreshedEvent) { + synchronized (applicationEventNotifier) { + contextRefreshedEvent = (ContextRefreshedEvent) event; + applicationEventNotifier.multicastEvent(event); + } + } + else if (event instanceof ContextClosedEvent) { + synchronized (applicationEventNotifier) { + contextRefreshedEvent = null; + } } } diff --git a/src/test/java/org/springframework/data/gemfire/support/SpringContextBootstrappingInitializerIntegrationTest.java b/src/test/java/org/springframework/data/gemfire/support/SpringContextBootstrappingInitializerIntegrationTest.java index 905ace5f..1b94b000 100644 --- a/src/test/java/org/springframework/data/gemfire/support/SpringContextBootstrappingInitializerIntegrationTest.java +++ b/src/test/java/org/springframework/data/gemfire/support/SpringContextBootstrappingInitializerIntegrationTest.java @@ -24,9 +24,10 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.util.Calendar; -import java.util.HashMap; import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import javax.sql.DataSource; @@ -69,14 +70,17 @@ import com.gemstone.gemfire.cache.Region; @SuppressWarnings("unused") public class SpringContextBootstrappingInitializerIntegrationTest { - private static final long CACHE_CLOSE_TIMEOUT = 15000l; // 15 seconds + private static final long CACHE_CLOSE_TIMEOUT = TimeUnit.SECONDS.toMillis(15); private static final Object MUTEX_LOCK = new Object(); protected static final String GEMFIRE_LOCATORS = "localhost[11235]"; - protected static final String GEMFIRE_LOG_LEVEL = "config"; + protected static final String GEMFIRE_LOG_LEVEL = "warning"; + protected static final String GEMFIRE_JMX_MANAGER = "true"; + protected static final String GEMFIRE_JMX_MANAGER_PORT = "1199"; + protected static final String GEMFIRE_JMX_MANAGER_START = "true"; protected static final String GEMFIRE_MCAST_PORT = "0"; - protected static final String GEMFIRE_NAME = "SpringContextBootstrappingInitializationTest"; + protected static final String GEMFIRE_NAME = "SpringContextBootstrappingInitializationIntegrationTest"; protected static final String GEMFIRE_START_LOCATORS = "localhost[11235]"; @Before @@ -99,7 +103,7 @@ public class SpringContextBootstrappingInitializerIntegrationTest { } } - fail(String.format("The Cache instance was not properly closed and shutdown by the timeout of %1$d seconds!%n", + fail(String.format("The Cache instance was not properly closed in the allotted timeout of %1$d seconds!%n", (CACHE_CLOSE_TIMEOUT / 1000))); } catch (CacheClosedException ignore) { @@ -108,7 +112,7 @@ public class SpringContextBootstrappingInitializerIntegrationTest { @After public void tearDown() { - SpringContextBootstrappingInitializer.applicationContext = null; + SpringContextBootstrappingInitializer.getApplicationContext().close(); UserDataStoreCacheLoader.INSTANCE.set(null); tearDownCache(); } @@ -145,15 +149,18 @@ public class SpringContextBootstrappingInitializerIntegrationTest { protected void doSpringContextBootstrappingInitializationTest(final String cacheXmlFile) { Cache gemfireCache = new CacheFactory() + .set("name", GEMFIRE_NAME) + .set("mcast-port", GEMFIRE_MCAST_PORT) + .set("log-level", GEMFIRE_LOG_LEVEL) .set("cache-xml-file", cacheXmlFile) //.set("locators", GEMFIRE_LOCATORS) - .set("log-level", GEMFIRE_LOG_LEVEL) - .set("mcast-port", GEMFIRE_MCAST_PORT) - .set("name", GEMFIRE_NAME) //.set("start-locator", GEMFIRE_LOCATORS) + //.set("jmx-manager", GEMFIRE_JMX_MANAGER) + //.set("jmx-manager-port", GEMFIRE_JMX_MANAGER_PORT) + //.set("jmx-manager-start", GEMFIRE_JMX_MANAGER_START) .create(); - assertNotNull("The GemFire Cache was not properly created or initialized!", gemfireCache); + assertNotNull("The GemFire Cache was not properly created and initialized!", gemfireCache); assertFalse("The GemFire Cache is closed!", gemfireCache.isClosed()); Set> rootRegions = gemfireCache.rootRegions(); @@ -184,7 +191,6 @@ public class SpringContextBootstrappingInitializerIntegrationTest { // NOTE a GemFire declared component initialized by Spring! UserDataStoreCacheLoader usersCacheLoader = UserDataStoreCacheLoader.getInstance(); - assertNotNull(usersCacheLoader); assertSame(userDataSource, usersCacheLoader.getDataSource()); Region users = gemfireCache.getRegion("/Users"); @@ -200,17 +206,17 @@ public class SpringContextBootstrappingInitializerIntegrationTest { assertEquals(3, users.size()); } - @Test - public void testSpringContextBootstrappingInitialization() { - doSpringContextBootstrappingInitializationTest("cache-with-spring-context-bootstrap-initializer.xml"); - } - @Test public void testSpringContextBootstrappingInitializationUsingBasePackages() { doSpringContextBootstrappingInitializationTest( "cache-with-spring-context-bootstrap-initializer-using-base-packages.xml"); } + @Test + public void testSpringContextBootstrappingInitializationUsingContextConfigLocations() { + doSpringContextBootstrappingInitializationTest("cache-with-spring-context-bootstrap-initializer.xml"); + } + public static final class TestDataSource extends DataSourceAdapter { } @@ -218,7 +224,7 @@ public class SpringContextBootstrappingInitializerIntegrationTest { private static final AtomicReference INSTANCE = new AtomicReference(); - private static final Map USER_DATA = new HashMap(3); + private static final Map USER_DATA = new ConcurrentHashMap(3); static { USER_DATA.put("jblum", new User("jblum")); @@ -234,18 +240,19 @@ public class SpringContextBootstrappingInitializerIntegrationTest { } protected static User createUser(final String username) { - return createUser(username, String.format("%1$s@xcompay.com", username), true, Calendar.getInstance()); + return createUser(username, true, Calendar.getInstance(), String.format("%1$s@xcompay.com", username)); } protected static User createUser(final String username, final Boolean active) { - return createUser(username, String.format("%1$s@xcompay.com", username), active, Calendar.getInstance()); + return createUser(username, active, Calendar.getInstance(), String.format("%1$s@xcompay.com", username)); } protected static User createUser(final String username, final Boolean active, final Calendar since) { - return createUser(username, String.format("%1$s@xcompay.com", username), active, since); + return createUser(username, active, since, String.format("%1$s@xcompay.com", username)); } - protected static User createUser(final String username, final String email, final Boolean active, final Calendar since) { + protected static User createUser(final String username, final Boolean active, final Calendar since, + final String email) { User user = new User(username); user.setActive(active); user.setEmail(email); diff --git a/src/test/java/org/springframework/data/gemfire/support/SpringContextBootstrappingInitializerTest.java b/src/test/java/org/springframework/data/gemfire/support/SpringContextBootstrappingInitializerTest.java index 88983d53..f17c7765 100644 --- a/src/test/java/org/springframework/data/gemfire/support/SpringContextBootstrappingInitializerTest.java +++ b/src/test/java/org/springframework/data/gemfire/support/SpringContextBootstrappingInitializerTest.java @@ -20,9 +20,9 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.eq; +import static org.mockito.Matchers.same; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -35,8 +35,10 @@ import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationListener; import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.event.ContextClosedEvent; import org.springframework.context.event.ContextRefreshedEvent; -import org.springframework.util.ObjectUtils; +import org.springframework.context.event.ContextStartedEvent; +import org.springframework.context.event.ContextStoppedEvent; /** * The SpringContextBootstrappingInitializerTest class is a test suite of test cases testing the contract @@ -69,143 +71,15 @@ public class SpringContextBootstrappingInitializerTest { @After public void tearDown() { SpringContextBootstrappingInitializer.applicationContext = null; - } - - @Test(expected = IllegalArgumentException.class) - public void testCreateApplicationContextWhenBasePackagesAndConfigLocationsAreBothUnspecified() { - try { - new SpringContextBootstrappingInitializer().createApplicationContext(null, null); - } - catch (IllegalArgumentException expected) { - assertEquals("Either 'basePackages' or 'configLocations' must be specified to construct an instance of the ConfigurableApplicationContext.", - expected.getMessage()); - throw expected; - } - } - - @Test(expected = IllegalArgumentException.class) - public void testCreateClassPathXmlApplicationContextWhenConfigLocationsAreUnspecified() { - try { - new SpringContextBootstrappingInitializer().createApplicationContext(null); - } - catch (IllegalArgumentException expected) { - assertEquals("'configLocations' must be specified to construct an instance of the ClassPathXmlApplicationContext.", - expected.getMessage()); - throw expected; - } - } - - @Test(expected = IllegalArgumentException.class) - public void testInitWithUnspecifiedBasePackagesAndContextConfigLocationsParameter() { - try { - new SpringContextBootstrappingInitializer().init(createParameters(createParameters( - SpringContextBootstrappingInitializer.CONTEXT_CONFIG_LOCATIONS_PARAMETER, ""), - SpringContextBootstrappingInitializer.BASE_PACKAGES_PARAMETER, "")); - } - catch (IllegalArgumentException expected) { - assertEquals("Either 'basePackages' or the 'contextConfigLocations' parameter must be specified.", - expected.getMessage()); - throw expected; - } - } - - @Test(expected = IllegalStateException.class) - public void testInitWithExistingApplicationContext() { - try { - ConfigurableApplicationContext mockApplicationContext = mock(ConfigurableApplicationContext.class, - "testInitWithExistingApplicationContext"); - - when(mockApplicationContext.getId()).thenReturn("testInitWithExistingApplicationContext"); - - SpringContextBootstrappingInitializer.applicationContext = mockApplicationContext; - - new SpringContextBootstrappingInitializer().init(createParameters( - SpringContextBootstrappingInitializer.CONTEXT_CONFIG_LOCATIONS_PARAMETER, - "/path/to/spring/context/configuration/file.xml")); - } - catch (IllegalStateException expected) { - assertEquals("A Spring application context with ID (testInitWithExistingApplicationContext) has already been created.", - expected.getMessage()); - throw expected; - } - } - - @Test(expected = IllegalArgumentException.class) - public void testInitWhenCreateApplicationContextReturnsNull() { - try { - SpringContextBootstrappingInitializer initializer = new SpringContextBootstrappingInitializer() { - @Override - protected ConfigurableApplicationContext createApplicationContext(final String[] basePackages, - final String[] configLocations) { - return null; - } - }; - - initializer.init(createParameters(SpringContextBootstrappingInitializer.CONTEXT_CONFIG_LOCATIONS_PARAMETER, - "/path/to/spring/context/configuration/file.xml")); - } - catch (IllegalArgumentException expected) { - assertEquals("The 'created' ConfigurableApplicationContext cannot be null!", expected.getMessage()); - throw expected; - } - } - - @Test(expected = IllegalStateException.class) - public void testInitWithNonActiveApplicationContext() { - final ConfigurableApplicationContext mockApplicationContext = mock(ConfigurableApplicationContext.class, - "testInitWithNonActiveApplicationConstruct"); - - when(mockApplicationContext.getId()).thenReturn("testInitWithNonActiveApplicationContext"); - when(mockApplicationContext.isActive()).thenReturn(false); - - SpringContextBootstrappingInitializer initializer = null; - - try { - initializer = new SpringContextBootstrappingInitializer() { - @Override - protected ConfigurableApplicationContext createApplicationContext(final String[] basePackages, - final String[] configLocations) { - return mockApplicationContext; - } - }; - - initializer.init(createParameters(SpringContextBootstrappingInitializer.CONTEXT_CONFIG_LOCATIONS_PARAMETER, - "/path/to/spring/context/configuration/file.xml")); - } - catch (IllegalStateException expected) { - assertEquals("The Spring application context (testInitWithNonActiveApplicationContext) has failed to be properly initialized with the following config files ([/path/to/spring/context/configuration/file.xml]) or base packages ([])!", - expected.getMessage()); - throw expected; - } - finally { - verify(mockApplicationContext, times(1)).addApplicationListener(eq(initializer)); - verify(mockApplicationContext, times(1)).registerShutdownHook(); - verify(mockApplicationContext, times(1)).refresh(); - } + SpringContextBootstrappingInitializer.contextRefreshedEvent = null; } @Test - public void testInitFollowedByGetApplicationContext() { - final ConfigurableApplicationContext mockApplicationContext = mock(ConfigurableApplicationContext.class, - "testInitFollowedByGetApplicationContext"); + public void testGetApplicationContext() { + ConfigurableApplicationContext mockApplicationContext = mock(ConfigurableApplicationContext.class, + "testGetApplicationContext"); - when(mockApplicationContext.getId()).thenReturn("testInitFollowedByGetApplicationContext"); - when(mockApplicationContext.isActive()).thenReturn(true); - - SpringContextBootstrappingInitializer initializer = new SpringContextBootstrappingInitializer() { - @Override - protected ConfigurableApplicationContext createApplicationContext(final String[] basePackages, - final String[] configLocations) { - return mockApplicationContext; - } - }; - - initializer.init(createParameters(SpringContextBootstrappingInitializer.CONTEXT_CONFIG_LOCATIONS_PARAMETER, - "/path/to/spring/context/configuration/file.xml")); - - verify(mockApplicationContext, times(1)).addApplicationListener(eq(initializer)); - verify(mockApplicationContext, times(1)).registerShutdownHook(); - verify(mockApplicationContext, times(1)).refresh(); + SpringContextBootstrappingInitializer.applicationContext = mockApplicationContext; assertSame(mockApplicationContext, SpringContextBootstrappingInitializer.getApplicationContext()); } @@ -216,7 +90,77 @@ public class SpringContextBootstrappingInitializerTest { SpringContextBootstrappingInitializer.getApplicationContext(); } catch (IllegalStateException expected) { - assertEquals("The Spring ApplicationContext has not been created!", expected.getMessage()); + assertEquals("The Spring ApplicationContext has not been properly configured and initialized!", + expected.getMessage()); + throw expected; + } + } + + @Test(expected = IllegalArgumentException.class) + public void testCreateApplicationContextWhenBothBasePackagesAndConfigLocationsAreUnspecified() { + try { + new SpringContextBootstrappingInitializer().createApplicationContext(null, null); + } + catch (IllegalArgumentException expected) { + assertEquals("'basePackages' or 'configLocations' must be specified to construct and configure" + +" an instance of the ConfigurableApplicationContext.", expected.getMessage()); + throw expected; + } + } + + @Test(expected = IllegalArgumentException.class) + public void testCreateClassPathXmlApplicationContextWhenConfigLocationsAreUnspecified() { + try { + new SpringContextBootstrappingInitializer().createApplicationContext(null); + } + catch (IllegalArgumentException expected) { + assertEquals("'configLocations' must be specified to construct and configure an instance of the ClassPathXmlApplicationContext.", + expected.getMessage()); + throw expected; + } + } + + @Test + public void testInitApplicationContext() { + ConfigurableApplicationContext mockApplicationContext = mock(ConfigurableApplicationContext.class, + "testInitApplicationContext"); + + SpringContextBootstrappingInitializer initializer = new SpringContextBootstrappingInitializer(); + + initializer.initApplicationContext(mockApplicationContext); + + verify(mockApplicationContext).addApplicationListener(same(initializer)); + verify(mockApplicationContext).registerShutdownHook(); + } + + @Test(expected = IllegalArgumentException.class) + public void testInitApplicationContextWithNullContext() { + try { + new SpringContextBootstrappingInitializer().initApplicationContext(null); + } + catch (IllegalArgumentException expected) { + assertEquals("The ConfigurableApplicationContext reference must not be null!", expected.getMessage()); + throw expected; + } + } + + @Test + public void testRefreshApplicationContext() { + ConfigurableApplicationContext mockApplicationContext = mock(ConfigurableApplicationContext.class, + "testInitApplicationContext"); + + new SpringContextBootstrappingInitializer().refreshApplicationContext(mockApplicationContext); + + verify(mockApplicationContext).refresh(); + } + + @Test(expected = IllegalArgumentException.class) + public void testRefreshApplicationContextWithNullContext() { + try { + new SpringContextBootstrappingInitializer().refreshApplicationContext(null); + } + catch (IllegalArgumentException expected) { + assertEquals("The ConfigurableApplicationContext reference must not be null!", expected.getMessage()); throw expected; } } @@ -238,97 +182,390 @@ public class SpringContextBootstrappingInitializerTest { } @Test - public void testRegisterAndOnApplicationEvent() { - TestApplicationListener testListener = SpringContextBootstrappingInitializer.register( - new TestApplicationListener()); - - try { - ContextRefreshedEvent testEvent = new ContextRefreshedEvent(mock(ApplicationContext.class, - "testRegisterAndOnApplicationEvent")); - - new SpringContextBootstrappingInitializer().onApplicationEvent(testEvent); - - testListener.assertCalled(); - testListener.assertSame(testEvent); - } - finally { - SpringContextBootstrappingInitializer.unregister(testListener); - } - } - - @Test - public void testRegisterUnregisterAndOnApplicationEvent() { - TestApplicationListener testListener = SpringContextBootstrappingInitializer.unregister( - SpringContextBootstrappingInitializer.register(new TestApplicationListener())); - - try { - ContextRefreshedEvent testEvent = new ContextRefreshedEvent(mock(ApplicationContext.class, - "testRegisterUnregisterAndOnApplicationEvent")); - - new SpringContextBootstrappingInitializer().onApplicationEvent(testEvent); - - testListener.assertNotCalled(); - } - finally { - SpringContextBootstrappingInitializer.unregister(testListener); - } - } - - @Test - public void testNotifyListenersOnContextRefreshedEventBeforeApplicationContextExists() { - TestApplicationListener testApplicationListener = new TestApplicationListener(); - - SpringContextBootstrappingInitializer.applicationContext = null; - SpringContextBootstrappingInitializer.register(testApplicationListener); - - testApplicationListener.assertNotCalled(); - } - - @Test - public void testNotifyListenersOnContextRefreshedEventAfterApplicationContextRefreshed() { + public void testInitWithExistingApplicationContext() { ConfigurableApplicationContext mockApplicationContext = mock(ConfigurableApplicationContext.class, - "testNotifyListenersOnContextRefreshedEventAfterApplicationContextRefreshed"); + "testInitWithExistingApplicationContext"); - ContextRefreshedEvent testEvent = new ContextRefreshedEvent(mockApplicationContext); + when(mockApplicationContext.isActive()).thenReturn(true); + when(mockApplicationContext.getId()).thenReturn("testInitWithExistingApplicationContext"); SpringContextBootstrappingInitializer.applicationContext = mockApplicationContext; - new SpringContextBootstrappingInitializer().onApplicationEvent(testEvent); - TestApplicationListener testApplicationListener = new TestApplicationListener(); + assertSame(mockApplicationContext, SpringContextBootstrappingInitializer.getApplicationContext()); - SpringContextBootstrappingInitializer.register(testApplicationListener); + SpringContextBootstrappingInitializer initializer = new SpringContextBootstrappingInitializer(); - testApplicationListener.assertCalled(); - testApplicationListener.assertSame(testEvent); + initializer.init(createParameters(SpringContextBootstrappingInitializer.CONTEXT_CONFIG_LOCATIONS_PARAMETER, + "/path/to/spring/application/context.xml")); + + verify(mockApplicationContext, never()).addApplicationListener(same(initializer)); + verify(mockApplicationContext, never()).registerShutdownHook(); + verify(mockApplicationContext, never()).refresh(); + + assertSame(mockApplicationContext, SpringContextBootstrappingInitializer.getApplicationContext()); } - // TODO add additional multi-thread test cases once MultithreadedTC test framework is added to the SDP project - // to properly test concurrency of the notification and registration during Spring ApplicationContext creation. + @Test + public void testInitWhenApplicationContextIsNull() { + assertNull(SpringContextBootstrappingInitializer.applicationContext); + + final ConfigurableApplicationContext mockApplicationContext = mock(ConfigurableApplicationContext.class, + "testInitWhenApplicationContextIsNull"); + + when(mockApplicationContext.getId()).thenReturn("testInitWhenApplicationContextIsNull"); + when(mockApplicationContext.isRunning()).thenReturn(true); + + SpringContextBootstrappingInitializer initializer = new SpringContextBootstrappingInitializer() { + @Override + protected ConfigurableApplicationContext createApplicationContext(final String[] basePackages, + final String[] configLocations) { + return mockApplicationContext; + } + }; + + initializer.init(createParameters(SpringContextBootstrappingInitializer.CONTEXT_CONFIG_LOCATIONS_PARAMETER, + "/path/to/spring/application/context.xml")); + + verify(mockApplicationContext, times(1)).addApplicationListener(same(initializer)); + verify(mockApplicationContext, times(1)).registerShutdownHook(); + verify(mockApplicationContext, times(1)).refresh(); + + assertSame(mockApplicationContext, SpringContextBootstrappingInitializer.getApplicationContext()); + } + + @Test + public void testInitWhenApplicationContextIsInactive() { + ConfigurableApplicationContext mockInactiveApplicationContext = mock(ConfigurableApplicationContext.class, + "testInitWhenApplicationContextIsInactive.Inactive"); + + when(mockInactiveApplicationContext.isActive()).thenReturn(false); + + SpringContextBootstrappingInitializer.applicationContext = mockInactiveApplicationContext; + + assertSame(mockInactiveApplicationContext, SpringContextBootstrappingInitializer.getApplicationContext()); + + final ConfigurableApplicationContext mockNewApplicationContext = mock(ConfigurableApplicationContext.class, + "testInitWhenApplicationContextIsInactive.New"); + + when(mockNewApplicationContext.getId()).thenReturn("testInitWhenApplicationContextIsInactive.New"); + when(mockNewApplicationContext.isRunning()).thenReturn(true); + + SpringContextBootstrappingInitializer initializer = new SpringContextBootstrappingInitializer() { + @Override + protected ConfigurableApplicationContext createApplicationContext(final String[] basePackages, + final String[] configLocations) { + return mockNewApplicationContext; + } + }; + + initializer.init(createParameters(SpringContextBootstrappingInitializer.CONTEXT_CONFIG_LOCATIONS_PARAMETER, + "/path/to/spring/application/context.xml")); + + verify(mockNewApplicationContext, times(1)).addApplicationListener(same(initializer)); + verify(mockNewApplicationContext, times(1)).registerShutdownHook(); + verify(mockNewApplicationContext, times(1)).refresh(); + + assertSame(mockNewApplicationContext, SpringContextBootstrappingInitializer.getApplicationContext()); + } + + @Test(expected = IllegalArgumentException.class) + public void testInitWhenBothBasePackagesAndContextConfigLocationsParametersAreUnspecified() { + assertNull(SpringContextBootstrappingInitializer.applicationContext); + + try { + new SpringContextBootstrappingInitializer().init(createParameters(createParameters( + SpringContextBootstrappingInitializer.CONTEXT_CONFIG_LOCATIONS_PARAMETER, ""), + SpringContextBootstrappingInitializer.BASE_PACKAGES_PARAMETER, "")); + } + catch (IllegalArgumentException expected) { + assertEquals("Either 'basePackages' or the 'contextConfigLocations' parameter must be specified.", + expected.getMessage()); + throw expected; + } + } + + @Test(expected = IllegalStateException.class) + public void testInitWhenApplicationContextIsNotRunning() { + assertNull(SpringContextBootstrappingInitializer.applicationContext); + + final ConfigurableApplicationContext mockApplicationContext = mock(ConfigurableApplicationContext.class, + "testInitWhenApplicationContextIsNotRunning"); + + when(mockApplicationContext.getId()).thenReturn("testInitWhenApplicationContextIsNotRunning"); + when(mockApplicationContext.isRunning()).thenReturn(false); + + SpringContextBootstrappingInitializer initializer = new SpringContextBootstrappingInitializer() { + @Override + protected ConfigurableApplicationContext createApplicationContext(final String[] basePackages, + final String[] configLocations) { + return mockApplicationContext; + } + }; + + initializer.init(createParameters(SpringContextBootstrappingInitializer.CONTEXT_CONFIG_LOCATIONS_PARAMETER, + "/path/to/spring/application/context.xml")); + + verify(mockApplicationContext, times(1)).addApplicationListener(same(initializer)); + verify(mockApplicationContext, times(1)).registerShutdownHook(); + verify(mockApplicationContext, times(1)).refresh(); + + SpringContextBootstrappingInitializer.getApplicationContext(); + } + + protected static void assertNotifiedWithEvent(final TestApplicationListener listener, final ContextRefreshedEvent expectedEvent) { + Assert.assertTrue(listener.isNotified()); + Assert.assertSame(expectedEvent, listener.getActualEvent()); + } + + protected static void assertUnnotified(final TestApplicationListener listener) { + assertFalse(listener.isNotified()); + assertNull(listener.getActualEvent()); + } + + @Test + public void testOnApplicationEvent() { + TestApplicationListener testApplicationListener = new TestApplicationListener("testOnApplicationEvent"); + + try { + testApplicationListener = SpringContextBootstrappingInitializer.register(testApplicationListener); + + assertUnnotified(testApplicationListener); + + ContextRefreshedEvent testContextRefreshedEvent = new ContextRefreshedEvent(mock(ApplicationContext.class, + "testOnApplicationEvent")); + + new SpringContextBootstrappingInitializer().onApplicationEvent(testContextRefreshedEvent); + + assertNotifiedWithEvent(testApplicationListener, testContextRefreshedEvent); + } + finally { + SpringContextBootstrappingInitializer.unregister(testApplicationListener); + } + } + + @Test + public void testOnApplicationEventWithContextStartedEvent() { + TestApplicationListener testApplicationListener = new TestApplicationListener( + "testOnApplicationEventWithContextStartedEvent"); + + try { + testApplicationListener = SpringContextBootstrappingInitializer.register(testApplicationListener); + + assertUnnotified(testApplicationListener); + + ContextStartedEvent testContextStartedEvent = mock(ContextStartedEvent.class, + "testOnApplicationEventWithContextStartedEvent"); + + new SpringContextBootstrappingInitializer().onApplicationEvent(testContextStartedEvent); + + assertUnnotified(testApplicationListener); + } + finally { + SpringContextBootstrappingInitializer.unregister(testApplicationListener); + } + } + + @Test + public void testOnApplicationEventWithMultipleRegisteredApplicationListeners() { + TestApplicationListener testApplicationListenerOne = new TestApplicationListener( + "testOnApplicationEventWithMultipleRegisteredApplicationListeners.1"); + + TestApplicationListener testApplicationListenerTwo = new TestApplicationListener( + "testOnApplicationEventWithMultipleRegisteredApplicationListeners.2"); + + TestApplicationListener testApplicationListenerThree = new TestApplicationListener( + "testOnApplicationEventWithMultipleRegisteredApplicationListeners.3"); + + try { + testApplicationListenerOne = SpringContextBootstrappingInitializer.register(testApplicationListenerOne); + testApplicationListenerTwo = SpringContextBootstrappingInitializer.register(testApplicationListenerTwo); + testApplicationListenerThree = SpringContextBootstrappingInitializer.register(testApplicationListenerThree); + + assertUnnotified(testApplicationListenerOne); + assertUnnotified(testApplicationListenerTwo); + assertUnnotified(testApplicationListenerThree); + + ContextRefreshedEvent testContextRefreshedEvent = new ContextRefreshedEvent(mock(ApplicationContext.class, + "testRegisterWithOnApplicationEvent")); + + new SpringContextBootstrappingInitializer().onApplicationEvent(testContextRefreshedEvent); + + assertNotifiedWithEvent(testApplicationListenerOne, testContextRefreshedEvent); + assertNotifiedWithEvent(testApplicationListenerTwo, testContextRefreshedEvent); + assertNotifiedWithEvent(testApplicationListenerThree, testContextRefreshedEvent); + } + finally { + SpringContextBootstrappingInitializer.unregister(testApplicationListenerOne); + SpringContextBootstrappingInitializer.unregister(testApplicationListenerTwo); + SpringContextBootstrappingInitializer.unregister(testApplicationListenerThree); + } + } + + @Test + public void testOnApplicationEventWithUnregisteredApplicationListener() { + TestApplicationListener testApplicationListener = new TestApplicationListener( + "testOnApplicationEventWithUnregisteredApplicationListener"); + + try { + testApplicationListener = SpringContextBootstrappingInitializer.unregister( + SpringContextBootstrappingInitializer.register(testApplicationListener)); + + assertUnnotified(testApplicationListener); + + ContextRefreshedEvent testContextRefreshedEvent = new ContextRefreshedEvent(mock(ApplicationContext.class, + "testRegisterThenUnregisterWithOnApplicationEvent")); + + new SpringContextBootstrappingInitializer().onApplicationEvent(testContextRefreshedEvent); + + assertUnnotified(testApplicationListener); + } + finally { + SpringContextBootstrappingInitializer.unregister(testApplicationListener); + } + } + + @Test + public void testNotifyOnExistingContextRefreshedEventBeforeApplicationContextExists() { + assertNull(SpringContextBootstrappingInitializer.contextRefreshedEvent); + + TestApplicationListener testApplicationListener = new TestApplicationListener( + "testNotifyOnExistingContextRefreshedEventBeforeApplicationContextExists"); + + try { + testApplicationListener = SpringContextBootstrappingInitializer.register(testApplicationListener); + assertUnnotified(testApplicationListener); + } + finally { + SpringContextBootstrappingInitializer.unregister(testApplicationListener); + } + } + + @Test + public void testNotifyOnExistingContextRefreshedEventAfterContextRefreshed() { + ContextRefreshedEvent testContextRefreshedEvent = new ContextRefreshedEvent(mock(ApplicationContext.class)); + + new SpringContextBootstrappingInitializer().onApplicationEvent(testContextRefreshedEvent); + + TestApplicationListener testApplicationListener = new TestApplicationListener( + "testNotifyApplicationListenersOnContextRefreshedEventAfterApplicationContextRefreshed"); + + try { + testApplicationListener = SpringContextBootstrappingInitializer.register(testApplicationListener); + assertNotifiedWithEvent(testApplicationListener, testContextRefreshedEvent); + } + finally { + SpringContextBootstrappingInitializer.unregister(testApplicationListener); + } + } + + @Test + public void testOnApplicationEventAndNotifyOnExistingContextRefreshedEvent() { + ConfigurableApplicationContext mockApplicationContext = mock(ConfigurableApplicationContext.class, + "testOnApplicationEventAndNotifyOnExistingContextRefreshedEvent"); + + SpringContextBootstrappingInitializer initializer = new SpringContextBootstrappingInitializer(); + + TestApplicationListener testApplicationListenerOne = new TestApplicationListener( + "testOnApplicationEventAndNotifyOnExistingContextRefreshedEvent.1"); + + TestApplicationListener testApplicationListenerTwo = new TestApplicationListener( + "testOnApplicationEventAndNotifyOnExistingContextRefreshedEvent.2"); + + TestApplicationListener testApplicationListenerThree = new TestApplicationListener( + "testOnApplicationEventAndNotifyOnExistingContextRefreshedEvent.3"); + + try { + testApplicationListenerOne = SpringContextBootstrappingInitializer.register(testApplicationListenerOne); + + assertUnnotified(testApplicationListenerOne); + assertUnnotified(testApplicationListenerTwo); + assertUnnotified(testApplicationListenerThree); + + ContextRefreshedEvent testContextRefreshedEvent = new ContextRefreshedEvent(mockApplicationContext); + + initializer.onApplicationEvent(testContextRefreshedEvent); + + assertNotifiedWithEvent(testApplicationListenerOne, testContextRefreshedEvent); + assertUnnotified(testApplicationListenerTwo); + assertUnnotified(testApplicationListenerThree); + + testApplicationListenerTwo = SpringContextBootstrappingInitializer.register(testApplicationListenerTwo); + + assertNotifiedWithEvent(testApplicationListenerTwo, testContextRefreshedEvent); + assertUnnotified(testApplicationListenerOne); + assertUnnotified(testApplicationListenerThree); + + ContextStoppedEvent contextStoppedEvent = new ContextStoppedEvent(mockApplicationContext); + + initializer.onApplicationEvent(contextStoppedEvent); + + assertUnnotified(testApplicationListenerOne); + assertUnnotified(testApplicationListenerTwo); + assertUnnotified(testApplicationListenerThree); + + initializer.onApplicationEvent(testContextRefreshedEvent); + + assertNotifiedWithEvent(testApplicationListenerOne, testContextRefreshedEvent); + assertNotifiedWithEvent(testApplicationListenerTwo, testContextRefreshedEvent); + assertUnnotified(testApplicationListenerThree); + + ContextClosedEvent testContextClosedEvent = new ContextClosedEvent(mockApplicationContext); + + initializer.onApplicationEvent(testContextClosedEvent); + + assertUnnotified(testApplicationListenerOne); + assertUnnotified(testApplicationListenerTwo); + assertUnnotified(testApplicationListenerThree); + + SpringContextBootstrappingInitializer.register(testApplicationListenerThree); + + assertUnnotified(testApplicationListenerOne); + assertUnnotified(testApplicationListenerTwo); + assertUnnotified(testApplicationListenerThree); + } + finally { + SpringContextBootstrappingInitializer.unregister(testApplicationListenerOne); + SpringContextBootstrappingInitializer.unregister(testApplicationListenerTwo); + SpringContextBootstrappingInitializer.unregister(testApplicationListenerThree); + } + } + + // TODO add additional multi-threaded test cases once MultithreadedTC test framework is added to the SDP project + // in order to properly test concurrency of notification and registration during Spring ApplicationContext creation. protected static class TestApplicationListener implements ApplicationListener { - private volatile boolean called = false; + private volatile boolean notified = false; private volatile ContextRefreshedEvent actualEvent; - public void assertCalled() { - assertTrue(String.format("Expected the (%1$s).onApplicationEvent(:ContextRefreshedEvent) method to be called!", - getClass().getName()), called); + private final String name; + + public TestApplicationListener(final String name) { + this.name = name; } - public void assertNotCalled() { - assertFalse(String.format("Expected the (%1$s).onApplicationEvent(:ContextRefreshedEvent) method to not be called for ApplicationEvent (%2$s)!", - getClass().getName(), ObjectUtils.nullSafeClassName(actualEvent)), called); + public ContextRefreshedEvent getActualEvent() { + ContextRefreshedEvent localActualEvent = this.actualEvent; + this.actualEvent = null; + return localActualEvent; } - public void assertSame(final ContextRefreshedEvent expectedEvent) { - Assert.assertSame(expectedEvent, actualEvent); + public boolean isNotified() { + boolean localNotified = this.notified; + this.notified = false; + return localNotified; } @Override public void onApplicationEvent(final ContextRefreshedEvent event) { this.actualEvent = event; - called = true; + this.notified = true; + } + + @Override + public String toString() { + return this.name; } }