From 043937e523e2ed0f2276440d985ce0f9ddbd0e8c Mon Sep 17 00:00:00 2001 From: John Blum Date: Mon, 27 Oct 2014 14:06:28 -0700 Subject: [PATCH] SGF-346 - Enable LazyWiringDeclarableSupport-based GemFire components to be used inside both cache.xml and Spring config, especially in the context of GemFire 8's Cluster Configuration. --- .../gemfire/LazyWiringDeclarableSupport.java | 74 +++++-- ...iringDeclarableSupportIntegrationTest.java | 4 +- .../LazyWiringDeclarableSupportTest.java | 201 +++++++++++++++--- ...larableSupportIntegrationTest-context.xml} | 0 4 files changed, 225 insertions(+), 54 deletions(-) rename src/test/resources/org/springframework/data/gemfire/{lazy-wiring-declarable-support.xml => LazyWiringDeclarableSupportIntegrationTest-context.xml} (100%) diff --git a/src/main/java/org/springframework/data/gemfire/LazyWiringDeclarableSupport.java b/src/main/java/org/springframework/data/gemfire/LazyWiringDeclarableSupport.java index a124de13..210a340a 100644 --- a/src/main/java/org/springframework/data/gemfire/LazyWiringDeclarableSupport.java +++ b/src/main/java/org/springframework/data/gemfire/LazyWiringDeclarableSupport.java @@ -59,13 +59,14 @@ public abstract class LazyWiringDeclarableSupport implements ApplicationListener // and the init method was called. private final AtomicReference parametersReference = new AtomicReference(); - protected volatile boolean initialized = false; + volatile boolean initialized = false; /** * Constructs an instance of the LazyWiringDeclarableSupport class registered with the - * SpringContextBootstrappingInitializer to receive notification when the Spring context is created and initialized - * (refreshed) by GemFire in order for this Declarable component to be configured and properly initialized with any - * required Spring bean dependencies. + * SpringContextBootstrappingInitializer. This Declarable will receive notifications from the + * SpringContextBootstrappingInitializer when the Spring context is created and initialized (refreshed). + * The notification is necessary in order for this Declarable component to be configured and properly initialized + * with any required Spring bean dependencies. * * @see org.springframework.data.gemfire.support.SpringContextBootstrappingInitializer * #register(org.springframework.context.ApplicationListener) @@ -76,9 +77,9 @@ public abstract class LazyWiringDeclarableSupport implements ApplicationListener /** * Asserts that this Declarable object has been properly configured and initialized by the Spring container - * after GemFire has constructed this Declarable object during startup. It is recommended that this method - * be called in any GemFire CacheCallback/Declarable object operational method (e.g. CacheLoader.load(..)) - * before use in order to ensure that this Declarable was properly constructed, configured and initialized. + * after GemFire has constructed this Declarable object during startup. It is recommended to call this method + * in any GemFire CacheCallback/Declarable object operational method (e.g. CacheLoader.load(..)) before use + * in order to ensure that this Declarable was properly constructed, configured and initialized. * * @throws IllegalStateException if the Declarable object has not been properly configured or initialized * by the Spring container. @@ -87,7 +88,23 @@ public abstract class LazyWiringDeclarableSupport implements ApplicationListener */ protected void assertInitialized() { Assert.state(isInitialized(), String.format( - "This Declarable object (%1$s) has not ben properly configured and initialized!", + "This Declarable object (%1$s) has not been properly configured and initialized!", + getClass().getName())); + } + + /** + * Asserts that this Declarable object has not yet been used, or activated prior to being fully configured + * and initialized. It is possible, though rare, that the init(:Properties) might be called multiple times + * by GemFire before the Spring container configure, initializes and puts this component to use. + * + * @throws java.lang.IllegalStateException if the Declarable object has already been configured and initialized + * by the Spring container. + * @see #init(java.util.Properties) + * @see #isInitialized() + */ + protected void assertUninitialized() { + Assert.state(!isInitialized(), String.format( + "This Declarable object (%1$s) has already been configured and initialized, and is currently active!", getClass().getName())); } @@ -103,6 +120,7 @@ public abstract class LazyWiringDeclarableSupport implements ApplicationListener * @throws IllegalArgumentException if the bean-name parameter was specified in GemFire configuration meta-data * but no bean with the specified name could be found in the Spring context. * @see #init(java.util.Properties) + * @see #doPostInit(java.util.Properties) * @see org.springframework.beans.factory.wiring.BeanConfigurerSupport * @see org.springframework.beans.factory.wiring.BeanWiringInfo * @see org.springframework.beans.factory.wiring.BeanWiringInfoResolver @@ -149,19 +167,20 @@ public abstract class LazyWiringDeclarableSupport implements ApplicationListener } /** - * Initialization method called by GemFire with the configured parameters once this Declarable object has been + * Initialization method called by GemFire with configured parameters once this Declarable object has been * constructed during GemFire startup using an <initalizer> element in GemFire's configuration meta-data. * * @param parameters the configured parameters passed from the GemFire configuration (e.g. cache.xml) to this * Declarable as a Properties instance. - * @throws IllegalStateException if the Declarable object's init method has already been invoked. + * @throws IllegalStateException if this Declarable object has already been configured/initialized + * by the Spring container and is currently active. * @see #doInit(org.springframework.beans.factory.config.ConfigurableListableBeanFactory, java.util.Properties) * @see java.util.Properties */ @Override public final void init(final Properties parameters) { - Assert.state(parametersReference.compareAndSet(null, parameters), String.format( - "This Declarable (%1$s) has already been initialized.", getClass().getName())); + assertUninitialized(); + parametersReference.set(parameters); } /** @@ -177,32 +196,39 @@ public abstract class LazyWiringDeclarableSupport implements ApplicationListener return initialized; } + /** + * Null-safe operation to return the parameters passed to this Declarable object when created by GemFire from it's + * configuration meta-data. + * + * @return a Properties object containing the a parameters specified for this Declarable, or an + * empty Properties object if no parameters were supplied. + * @see java.util.Properties + */ + protected Properties nullSafeGetParameters() { + Properties parameters = parametersReference.get(); + return (parameters != null ? parameters : new Properties()); + } + /** * Event handler method called when GemFire has created and initialized (refreshed) the Spring ApplicationContext * using the SpringContextBootstrappingInitializer Declarable class. * * @param event the ContextRefreshedEvent published by the Spring ApplicationContext after it is successfully * created and initialized by GemFire. - * @throws IllegalStateException if the parameters have not been passed to this Declarable (i.e. GemFire has not - * called this Declarable object's init method yet, which is probably a bug and violates the lifecycle contract - * of Declarable GemFire objects). * @throws IllegalArgumentException if the ApplicationContext is not an instance of ConfigurableApplicationContext. * @see #doInit(org.springframework.beans.factory.config.ConfigurableListableBeanFactory, java.util.Properties) + * @see #nullSafeGetParameters() * @see org.springframework.context.ApplicationListener#onApplicationEvent(org.springframework.context.ApplicationEvent) * @see org.springframework.context.event.ContextRefreshedEvent */ @Override public final void onApplicationEvent(final ContextRefreshedEvent event) { - Properties parameters = parametersReference.get(); - - Assert.state(parameters != null, String.format( - "This Declarable object's (%1$s) init method has not been invoked!", getClass().getName())); - Assert.isTrue(event.getApplicationContext() instanceof ConfigurableApplicationContext, String.format("The Spring ApplicationContext (%1$s) must be an instance of ConfigurableApplicationContext.", ObjectUtils.nullSafeClassName(event.getApplicationContext()))); - doInit(((ConfigurableApplicationContext) event.getApplicationContext()).getBeanFactory(), parameters); + doInit(((ConfigurableApplicationContext) event.getApplicationContext()).getBeanFactory(), + nullSafeGetParameters()); } /** @@ -210,12 +236,14 @@ public abstract class LazyWiringDeclarableSupport implements ApplicationListener * SpringContextBootstrappingInitializer. * * @throws Exception if bean destruction is unsuccessful. - * @see org.springframework.data.gemfire.support.SpringContextBootstrappingInitializer#unregister( - * org.springframework.context.ApplicationListener) + * @see org.springframework.data.gemfire.support.SpringContextBootstrappingInitializer + * #unregister(org.springframework.context.ApplicationListener) */ @Override public void destroy() throws Exception { SpringContextBootstrappingInitializer.unregister(this); + parametersReference.set(null); + initialized = false; } } diff --git a/src/test/java/org/springframework/data/gemfire/LazyWiringDeclarableSupportIntegrationTest.java b/src/test/java/org/springframework/data/gemfire/LazyWiringDeclarableSupportIntegrationTest.java index e3d3c1dd..8712edcd 100644 --- a/src/test/java/org/springframework/data/gemfire/LazyWiringDeclarableSupportIntegrationTest.java +++ b/src/test/java/org/springframework/data/gemfire/LazyWiringDeclarableSupportIntegrationTest.java @@ -47,8 +47,8 @@ import org.springframework.util.Assert; * @see org.springframework.test.context.junit4.SpringJUnit4ClassRunner * @since 1.3.4 */ -@ContextConfiguration("lazy-wiring-declarable-support.xml") @RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration @SuppressWarnings("unused") public class LazyWiringDeclarableSupportIntegrationTest { @@ -65,7 +65,7 @@ public class LazyWiringDeclarableSupportIntegrationTest { public void testWiring() { TestDeclarable declarable = new TestDeclarable(); - declarable.init(createParameters("testParam1", "testValue1")); + declarable.init(createParameters("testParam", "testValue")); declarable.onApplicationEvent(new ContextRefreshedEvent(applicationContext)); declarable.assertInitialized(); diff --git a/src/test/java/org/springframework/data/gemfire/LazyWiringDeclarableSupportTest.java b/src/test/java/org/springframework/data/gemfire/LazyWiringDeclarableSupportTest.java index 8671e47e..745ac22f 100644 --- a/src/test/java/org/springframework/data/gemfire/LazyWiringDeclarableSupportTest.java +++ b/src/test/java/org/springframework/data/gemfire/LazyWiringDeclarableSupportTest.java @@ -17,9 +17,14 @@ package org.springframework.data.gemfire; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.util.Properties; @@ -30,6 +35,7 @@ import org.junit.Test; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.event.ContextClosedEvent; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.data.gemfire.support.SpringContextBootstrappingInitializer; @@ -77,7 +83,7 @@ public class LazyWiringDeclarableSupportTest { declarable.assertInitialized(); } catch (IllegalStateException expected) { - assertEquals(String.format("This Declarable object (%1$s) has not ben properly configured and initialized!", + assertEquals(String.format("This Declarable object (%1$s) has not been properly configured and initialized!", TestLazyWiringDeclarableSupport.class.getName()), expected.getMessage()); throw expected; } @@ -86,44 +92,116 @@ public class LazyWiringDeclarableSupportTest { } } + @Test + public void testAssertUninitialized() { + LazyWiringDeclarableSupport declarable = new TestLazyWiringDeclarableSupport(); + + try { + declarable.assertUninitialized(); + } + finally { + SpringContextBootstrappingInitializer.unregister(declarable); + } + } + + @Test(expected = IllegalStateException.class) + public void testAssertUninitializedWhenInitialized() { + LazyWiringDeclarableSupport declarable = new TestLazyWiringDeclarableSupport() { + @Override protected boolean isInitialized() { + return true; + } + }; + + try { + declarable.assertUninitialized(); + } + catch (IllegalStateException expected) { + assertEquals(String.format( + "This Declarable object (%1$s) has already been configured and initialized, and is currently active!", + declarable.getClass().getName()), expected.getMessage()); + throw expected; + } + finally { + SpringContextBootstrappingInitializer.unregister(declarable); + } + } + @Test public void testInit() { - new TestLazyWiringDeclarableSupport().init(createParameters("testParam1", "testValue1")); + LazyWiringDeclarableSupport declarable = new TestLazyWiringDeclarableSupport(); + + try { + assertFalse(declarable.isInitialized()); + declarable.init(createParameters("param", "value")); + declarable.init(createParameters("newParam", "newValue")); + } + finally { + SpringContextBootstrappingInitializer.unregister(declarable); + } } @Test(expected = IllegalStateException.class) public void testInitWhenInitialized() { - LazyWiringDeclarableSupport declarable = new TestLazyWiringDeclarableSupport(); + LazyWiringDeclarableSupport declarable = new TestLazyWiringDeclarableSupport() { + @Override protected boolean isInitialized() { + return true; + } + }; try { - declarable.init(createParameters("testParam1", "testValue1")); - } - catch (IllegalStateException unexpected) { - fail("Calling init the first time should not throw an IllegalStateException!"); - } - - try { - declarable.init(createParameters("testParam2", "testValue1")); + declarable.init(createParameters("param", "value")); } catch (IllegalStateException expected) { - assertEquals(String.format("This Declarable (%1$s) has already been initialized.", + assertEquals(String.format( + "This Declarable object (%1$s) has already been configured and initialized, and is currently active!", declarable.getClass().getName()), expected.getMessage()); throw expected; } + finally { + SpringContextBootstrappingInitializer.unregister(declarable); + } } - @Test(expected = IllegalStateException.class) - public void testOnApplicationEventWithNoParameters() { + @Test + public void testNullSafeGetParameters() { LazyWiringDeclarableSupport declarable = new TestLazyWiringDeclarableSupport(); try { - declarable.onApplicationEvent(new ContextRefreshedEvent(mock(ConfigurableApplicationContext.class, - "testOnApplicationEventWithNoParameters"))); + declarable.init(createParameters("param", "value")); + + Properties parameters = declarable.nullSafeGetParameters(); + + assertNotNull(parameters); + assertFalse(parameters.isEmpty()); + assertEquals(1, parameters.size()); + assertEquals("value", parameters.getProperty("param")); + + declarable.init(createParameters("newParam", "newValue")); + parameters = declarable.nullSafeGetParameters(); + + assertNotNull(parameters); + assertFalse(parameters.isEmpty()); + assertEquals(1, parameters.size()); + assertFalse(parameters.containsKey("param")); + assertEquals("newValue", parameters.getProperty("newParam")); } - catch (IllegalStateException expected) { - assertEquals(String.format("This Declarable object's (%1$s) init method has not been invoked!", - declarable.getClass().getName()), expected.getMessage()); - throw expected; + finally { + SpringContextBootstrappingInitializer.unregister(declarable); + } + } + + @Test + public void testNullSafeGetParametersWithNullReference() { + LazyWiringDeclarableSupport declarable = new TestLazyWiringDeclarableSupport(); + + try { + Properties parameters = declarable.nullSafeGetParameters(); + + assertNotNull(parameters); + assertTrue(parameters.isEmpty()); + } + finally { + SpringContextBootstrappingInitializer.unregister(declarable); } } @@ -135,7 +213,6 @@ public class LazyWiringDeclarableSupportTest { LazyWiringDeclarableSupport declarable = new TestLazyWiringDeclarableSupport(); try { - declarable.init(createParameters("testParam1", "testValue1")); declarable.onApplicationEvent(new ContextRefreshedEvent(mockApplicationContext)); } catch (IllegalArgumentException expected) { @@ -143,6 +220,9 @@ public class LazyWiringDeclarableSupportTest { mockApplicationContext.getClass().getName()), expected.getMessage()); throw expected; } + finally { + SpringContextBootstrappingInitializer.unregister(declarable); + } } @Test @@ -155,23 +235,86 @@ public class LazyWiringDeclarableSupportTest { when(mockApplicationContext.getBeanFactory()).thenReturn(mockBeanFactory); - Properties testParameters = createParameters("testParam1", "testValue1"); - final AtomicBoolean doPostInitCalled = new AtomicBoolean(false); TestLazyWiringDeclarableSupport declarable = new TestLazyWiringDeclarableSupport() { @Override protected void doPostInit(final Properties parameters) { + super.doPostInit(parameters); assertInitialized(); doPostInitCalled.set(true); } }; - declarable.init(testParameters); - declarable.onApplicationEvent(new ContextRefreshedEvent(mockApplicationContext)); - declarable.assertEquals(testParameters); - declarable.assertSame(mockBeanFactory); + Properties parameters = createParameters("param", "value"); - assertTrue(doPostInitCalled.get()); + try { + declarable.init(parameters); + declarable.onApplicationEvent(new ContextRefreshedEvent(mockApplicationContext)); + declarable.assertEquals(parameters); + declarable.assertSame(mockBeanFactory); + + assertTrue(doPostInitCalled.get()); + + verify(mockApplicationContext, times(1)).getBeanFactory(); + } + finally { + SpringContextBootstrappingInitializer.unregister(declarable); + } + } + + @Test + public void testFullLifecycleIntegrationWithDestroy() throws Exception { + ConfigurableApplicationContext mockApplicationContext = mock(ConfigurableApplicationContext.class, + "testFullLifecycleIntegrationWithDestroy.ApplicationContext"); + + ConfigurableListableBeanFactory mockBeanFactory = mock(ConfigurableListableBeanFactory.class, + "testFullLifecycleIntegrationWithDestroy.BeanFactory"); + + when(mockApplicationContext.getBeanFactory()).thenReturn(mockBeanFactory); + + final AtomicBoolean doPostInitCalled = new AtomicBoolean(false); + + TestLazyWiringDeclarableSupport declarable = new TestLazyWiringDeclarableSupport() { + @Override protected void doPostInit(final Properties parameters) { + super.doPostInit(parameters); + assertInitialized(); + doPostInitCalled.set(true); + } + }; + + SpringContextBootstrappingInitializer initializer = new SpringContextBootstrappingInitializer(); + + Properties parameters = createParameters("param", "value"); + + try { + declarable.init(parameters); + + assertFalse(declarable.isInitialized()); + assertFalse(doPostInitCalled.get()); + assertSame(parameters, declarable.nullSafeGetParameters()); + + initializer.onApplicationEvent(new ContextRefreshedEvent(mockApplicationContext)); + + assertTrue(declarable.isInitialized()); + assertTrue(doPostInitCalled.get()); + declarable.assertEquals(parameters); + declarable.assertSame(mockBeanFactory); + + declarable.destroy(); + doPostInitCalled.set(false); + + assertFalse(declarable.isInitialized()); + assertFalse(doPostInitCalled.get()); + assertNotSame(parameters, declarable.nullSafeGetParameters()); + + initializer.onApplicationEvent(new ContextRefreshedEvent(mockApplicationContext)); + + assertFalse(declarable.isInitialized()); + assertFalse(doPostInitCalled.get()); + } + finally { + initializer.onApplicationEvent(new ContextClosedEvent(mockApplicationContext)); + } } protected static class TestLazyWiringDeclarableSupport extends LazyWiringDeclarableSupport { diff --git a/src/test/resources/org/springframework/data/gemfire/lazy-wiring-declarable-support.xml b/src/test/resources/org/springframework/data/gemfire/LazyWiringDeclarableSupportIntegrationTest-context.xml similarity index 100% rename from src/test/resources/org/springframework/data/gemfire/lazy-wiring-declarable-support.xml rename to src/test/resources/org/springframework/data/gemfire/LazyWiringDeclarableSupportIntegrationTest-context.xml