From b9881dfad9351184de86d2e6d7cd8acbdf68dcda Mon Sep 17 00:00:00 2001 From: John Blum Date: Sat, 18 Apr 2015 00:19:29 -0700 Subject: [PATCH] SGF-400 - Enable the ability to set the ClassLoader used by the Spring ApplicationContext created in the SpringContextBootstrappingInitializer for loading bean definition classes and resolving resources. --- ...SpringContextBootstrappingInitializer.java | 42 +++++- ...ngContextBootstrappingInitializerTest.java | 135 ++++++++++++++++-- 2 files changed, 164 insertions(+), 13 deletions(-) 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 737cb96b..5650567e 100644 --- a/src/main/java/org/springframework/data/gemfire/support/SpringContextBootstrappingInitializer.java +++ b/src/main/java/org/springframework/data/gemfire/support/SpringContextBootstrappingInitializer.java @@ -21,6 +21,7 @@ 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.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -35,6 +36,7 @@ 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.CollectionUtils; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; @@ -71,6 +73,8 @@ public class SpringContextBootstrappingInitializer implements Declarable, Applic private static final ApplicationEventMulticaster applicationEventNotifier = new SimpleApplicationEventMulticaster(); + private static final AtomicReference beanClassLoaderRef = new AtomicReference(null); + /* package-private */ static volatile ConfigurableApplicationContext applicationContext; /* package-private */ static volatile ContextRefreshedEvent contextRefreshedEvent; @@ -91,6 +95,24 @@ public class SpringContextBootstrappingInitializer implements Declarable, Applic return applicationContext; } + /** + * Sets the ClassLoader used by Spring ApplicationContext, created by this GemFire ("Bootstrapping") 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 (applicationContext == null || !applicationContext.isActive()) { + beanClassLoaderRef.set(beanClassLoader); + } + else { + throw new IllegalStateException("The 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 @@ -253,7 +275,7 @@ public class SpringContextBootstrappingInitializer implements Declarable, Applic Assert.notNull(applicationContext, "The ConfigurableApplicationContext reference must not be null!"); applicationContext.addApplicationListener(this); applicationContext.registerShutdownHook(); - return applicationContext; + return setClassLoader(applicationContext); } /** @@ -328,6 +350,24 @@ public class SpringContextBootstrappingInitializer implements Declarable, Applic 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 = beanClassLoaderRef.get(); + + if (applicationContext instanceof DefaultResourceLoader && beanClassLoader != null) { + ((DefaultResourceLoader) applicationContext).setClassLoader(beanClassLoader); + } + + return applicationContext; + } + /** * Initializes a Spring ApplicationContext with the given parameters specified with a GemFire <initializer> * block in cache.xml. 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 1dcfdc2a..af86f627 100644 --- a/src/test/java/org/springframework/data/gemfire/support/SpringContextBootstrappingInitializerTest.java +++ b/src/test/java/org/springframework/data/gemfire/support/SpringContextBootstrappingInitializerTest.java @@ -51,6 +51,8 @@ import org.springframework.context.event.ContextClosedEvent; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.context.event.ContextStartedEvent; import org.springframework.context.event.ContextStoppedEvent; +import org.springframework.context.support.AbstractApplicationContext; +import org.springframework.util.ObjectUtils; /** * The SpringContextBootstrappingInitializerTest class is a test suite of test cases testing the contract @@ -70,6 +72,15 @@ import org.springframework.context.event.ContextStoppedEvent; @SuppressWarnings("unused") public class SpringContextBootstrappingInitializerTest { + @After + public void tearDown() { + SpringContextBootstrappingInitializer.applicationContext = null; + SpringContextBootstrappingInitializer.contextRefreshedEvent = null; + SpringContextBootstrappingInitializer.setBeanClassLoader(null); + SpringContextBootstrappingInitializer.unregister(TestAppConfigOne.class); + SpringContextBootstrappingInitializer.unregister(TestAppConfigTwo.class); + } + protected static Properties createParameters(final String parameter, final String value) { Properties parameters = new Properties(); parameters.setProperty(parameter, value); @@ -81,14 +92,6 @@ public class SpringContextBootstrappingInitializerTest { return parameters; } - @After - public void tearDown() { - SpringContextBootstrappingInitializer.applicationContext = null; - SpringContextBootstrappingInitializer.contextRefreshedEvent = null; - SpringContextBootstrappingInitializer.unregister(TestAppConfigOne.class); - SpringContextBootstrappingInitializer.unregister(TestAppConfigTwo.class); - } - @Test public void testGetApplicationContext() { ConfigurableApplicationContext mockApplicationContext = mock(ConfigurableApplicationContext.class, @@ -111,6 +114,42 @@ public class SpringContextBootstrappingInitializerTest { } } + @Test + public void testSetBeanClassLoader() { + assertNull(SpringContextBootstrappingInitializer.applicationContext); + SpringContextBootstrappingInitializer.setBeanClassLoader(Thread.currentThread().getContextClassLoader()); + } + + @Test + public void testSetBeanClassLoaderWhenApplicationContextIsInactive() { + ConfigurableApplicationContext mockApplicationContext = mock(ConfigurableApplicationContext.class, + "testSetBeanClassLoaderWhenApplicationContextIsInactive.MockApplicationContext"); + + when(mockApplicationContext.isActive()).thenReturn(false); + + SpringContextBootstrappingInitializer.applicationContext = mockApplicationContext; + SpringContextBootstrappingInitializer.setBeanClassLoader(Thread.currentThread().getContextClassLoader()); + + verify(mockApplicationContext, times(1)).isActive(); + } + + @Test(expected = IllegalStateException.class) + public void testSetBeanClassLoaderWhenApplicationContextIsActive() { + ConfigurableApplicationContext mockApplicationContext = mock(ConfigurableApplicationContext.class, + "testSetBeanClassLoaderWhenApplicationContextIsActive.MockApplicationContext"); + + when(mockApplicationContext.isActive()).thenReturn(true); + + try { + SpringContextBootstrappingInitializer.applicationContext = mockApplicationContext; + SpringContextBootstrappingInitializer.setBeanClassLoader(Thread.currentThread().getContextClassLoader()); + } + catch (IllegalStateException expected) { + assertEquals("The Spring ApplicationContext has already been initialized!", expected.getMessage()); + throw expected; + } + } + @Test(expected = IllegalArgumentException.class) public void testCreateApplicationContextWhenBasePackagesAndConfigLocationsAreUnspecified() { try { @@ -123,10 +162,57 @@ public class SpringContextBootstrappingInitializerTest { } } + @Test + public void testCreateAnnotationApplicationContext() { + final ConfigurableApplicationContext mockXmlApplicationContext = mock(ConfigurableApplicationContext.class, + "testCreateAnnotationApplicationContext.MockXmlApplicationContext"); + + final AnnotationConfigApplicationContext mockAnnotationApplicationContext = mock(AnnotationConfigApplicationContext.class, + "testCreateAnnotationApplicationContext.MockAnnotationApplicationContext"); + + String[] basePackages = { "org.example.app" }; + + SpringContextBootstrappingInitializer initializer = new SpringContextBootstrappingInitializer() { + @Override ConfigurableApplicationContext createApplicationContext(final String[] configLocations) { + return (ObjectUtils.isEmpty(configLocations) ? mockAnnotationApplicationContext + : mockXmlApplicationContext); + } + }; + + ConfigurableApplicationContext actualApplicationContext = initializer.createApplicationContext(basePackages, null); + + assertSame(mockAnnotationApplicationContext, actualApplicationContext); + + verify(mockAnnotationApplicationContext, times(1)).scan(eq("org.example.app")); + } + + @Test + public void testCreateXmlApplicationContext() { + final ConfigurableApplicationContext mockXmlApplicationContext = mock(ConfigurableApplicationContext.class, + "testCreateXmlApplicationContext.MockXmlApplicationContext"); + + final ConfigurableApplicationContext mockAnnotationApplicationContext = mock(ConfigurableApplicationContext.class, + "testCreateXmlApplicationContext.MockAnnotationApplicationContext"); + + SpringContextBootstrappingInitializer initializer = new SpringContextBootstrappingInitializer() { + @Override ConfigurableApplicationContext createApplicationContext(final String[] configLocations) { + return (ObjectUtils.isEmpty(configLocations) ? mockAnnotationApplicationContext + : mockXmlApplicationContext); + } + }; + + ConfigurableApplicationContext actualApplicationContext = initializer.createApplicationContext(null, + new String[] { "/path/to/application/context.xml" }); + + assertSame(mockXmlApplicationContext, actualApplicationContext); + } + @Test public void testInitApplicationContext() { - ConfigurableApplicationContext mockApplicationContext = mock(ConfigurableApplicationContext.class, - "testInitApplicationContext"); + AbstractApplicationContext mockApplicationContext = mock(AbstractApplicationContext.class, + "testInitApplicationContext.MockApplicationContext"); + + SpringContextBootstrappingInitializer.setBeanClassLoader(Thread.currentThread().getContextClassLoader()); SpringContextBootstrappingInitializer initializer = new SpringContextBootstrappingInitializer(); @@ -134,10 +220,11 @@ public class SpringContextBootstrappingInitializerTest { verify(mockApplicationContext, times(1)).addApplicationListener(same(initializer)); verify(mockApplicationContext, times(1)).registerShutdownHook(); + verify(mockApplicationContext, times(1)).setClassLoader(eq(Thread.currentThread().getContextClassLoader())); } @Test(expected = IllegalArgumentException.class) - public void testInitApplicationContextWithNullContext() { + public void testInitApplicationContextWithNull() { try { new SpringContextBootstrappingInitializer().initApplicationContext(null); } @@ -158,7 +245,7 @@ public class SpringContextBootstrappingInitializerTest { } @Test(expected = IllegalArgumentException.class) - public void testRefreshApplicationContextWithNullContext() { + public void testRefreshApplicationContextWithNull() { try { new SpringContextBootstrappingInitializer().refreshApplicationContext(null); } @@ -228,6 +315,30 @@ public class SpringContextBootstrappingInitializerTest { verify(mockApplicationContext, never()).scan(any(String[].class)); } + @Test + public void testSetClassLoader() { + AbstractApplicationContext mockApplicationContext = mock(AbstractApplicationContext.class, + "testSetClassLoader.MockApplicationContext"); + + SpringContextBootstrappingInitializer.setBeanClassLoader(Thread.currentThread().getContextClassLoader()); + + new SpringContextBootstrappingInitializer().setClassLoader(mockApplicationContext); + + verify(mockApplicationContext, times(1)).setClassLoader(eq(Thread.currentThread().getContextClassLoader())); + } + + @Test + public void testSetClassLoaderWhenClassLoaderIsNull() { + AbstractApplicationContext mockApplicationContext = mock(AbstractApplicationContext.class, + "testSetClassLoaderWhenClassLoaderIsNull.MockApplicationContext"); + + SpringContextBootstrappingInitializer.setBeanClassLoader(null); + + new SpringContextBootstrappingInitializer().setClassLoader(mockApplicationContext); + + verify(mockApplicationContext, never()).setClassLoader(any(ClassLoader.class)); + } + private Class[] annotatedClasses(final Class... annotatedClasses) { return argThat(new ArgumentMatcher[]>() { @Override public boolean matches(final Object argument) {