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.

(cherry picked from commit 043937e523)

Signed-off-by: John Blum <jblum@pivotal.io>
This commit is contained in:
John Blum
2014-10-27 14:06:28 -07:00
parent 8d65fe358a
commit 51ce4da9ee
4 changed files with 225 additions and 54 deletions

View File

@@ -59,13 +59,14 @@ public abstract class LazyWiringDeclarableSupport implements ApplicationListener
// and the init method was called. // and the init method was called.
private final AtomicReference<Properties> parametersReference = new AtomicReference<Properties>(); private final AtomicReference<Properties> parametersReference = new AtomicReference<Properties>();
protected volatile boolean initialized = false; volatile boolean initialized = false;
/** /**
* Constructs an instance of the LazyWiringDeclarableSupport class registered with the * Constructs an instance of the LazyWiringDeclarableSupport class registered with the
* SpringContextBootstrappingInitializer to receive notification when the Spring context is created and initialized * SpringContextBootstrappingInitializer. This Declarable will receive notifications from the
* (refreshed) by GemFire in order for this Declarable component to be configured and properly initialized with any * SpringContextBootstrappingInitializer when the Spring context is created and initialized (refreshed).
* required Spring bean dependencies. * 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 * @see org.springframework.data.gemfire.support.SpringContextBootstrappingInitializer
* #register(org.springframework.context.ApplicationListener) * #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 * 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 * after GemFire has constructed this Declarable object during startup. It is recommended to call this method
* be called in any GemFire CacheCallback/Declarable object operational method (e.g. CacheLoader.load(..)) * in any GemFire CacheCallback/Declarable object operational method (e.g. CacheLoader.load(..)) before use
* before use in order to ensure that this Declarable was properly constructed, configured and initialized. * 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 * @throws IllegalStateException if the Declarable object has not been properly configured or initialized
* by the Spring container. * by the Spring container.
@@ -87,7 +88,23 @@ public abstract class LazyWiringDeclarableSupport implements ApplicationListener
*/ */
protected void assertInitialized() { protected void assertInitialized() {
Assert.state(isInitialized(), String.format( 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())); 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 * @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. * but no bean with the specified name could be found in the Spring context.
* @see #init(java.util.Properties) * @see #init(java.util.Properties)
* @see #doPostInit(java.util.Properties)
* @see org.springframework.beans.factory.wiring.BeanConfigurerSupport * @see org.springframework.beans.factory.wiring.BeanConfigurerSupport
* @see org.springframework.beans.factory.wiring.BeanWiringInfo * @see org.springframework.beans.factory.wiring.BeanWiringInfo
* @see org.springframework.beans.factory.wiring.BeanWiringInfoResolver * @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 &lt;initalizer&gt; element in GemFire's configuration meta-data. * constructed during GemFire startup using an &lt;initalizer&gt; element in GemFire's configuration meta-data.
* *
* @param parameters the configured parameters passed from the GemFire configuration (e.g. cache.xml) to this * @param parameters the configured parameters passed from the GemFire configuration (e.g. cache.xml) to this
* Declarable as a Properties instance. * 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 #doInit(org.springframework.beans.factory.config.ConfigurableListableBeanFactory, java.util.Properties)
* @see java.util.Properties * @see java.util.Properties
*/ */
@Override @Override
public final void init(final Properties parameters) { public final void init(final Properties parameters) {
Assert.state(parametersReference.compareAndSet(null, parameters), String.format( assertUninitialized();
"This Declarable (%1$s) has already been initialized.", getClass().getName())); parametersReference.set(parameters);
} }
/** /**
@@ -177,32 +196,39 @@ public abstract class LazyWiringDeclarableSupport implements ApplicationListener
return initialized; 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 * Event handler method called when GemFire has created and initialized (refreshed) the Spring ApplicationContext
* using the SpringContextBootstrappingInitializer Declarable class. * using the SpringContextBootstrappingInitializer Declarable class.
* *
* @param event the ContextRefreshedEvent published by the Spring ApplicationContext after it is successfully * @param event the ContextRefreshedEvent published by the Spring ApplicationContext after it is successfully
* created and initialized by GemFire. * 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. * @throws IllegalArgumentException if the ApplicationContext is not an instance of ConfigurableApplicationContext.
* @see #doInit(org.springframework.beans.factory.config.ConfigurableListableBeanFactory, java.util.Properties) * @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.ApplicationListener#onApplicationEvent(org.springframework.context.ApplicationEvent)
* @see org.springframework.context.event.ContextRefreshedEvent * @see org.springframework.context.event.ContextRefreshedEvent
*/ */
@Override @Override
public final void onApplicationEvent(final ContextRefreshedEvent event) { 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, Assert.isTrue(event.getApplicationContext() instanceof ConfigurableApplicationContext,
String.format("The Spring ApplicationContext (%1$s) must be an instance of ConfigurableApplicationContext.", String.format("The Spring ApplicationContext (%1$s) must be an instance of ConfigurableApplicationContext.",
ObjectUtils.nullSafeClassName(event.getApplicationContext()))); 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. * SpringContextBootstrappingInitializer.
* *
* @throws Exception if bean destruction is unsuccessful. * @throws Exception if bean destruction is unsuccessful.
* @see org.springframework.data.gemfire.support.SpringContextBootstrappingInitializer#unregister( * @see org.springframework.data.gemfire.support.SpringContextBootstrappingInitializer
* org.springframework.context.ApplicationListener) * #unregister(org.springframework.context.ApplicationListener)
*/ */
@Override @Override
public void destroy() throws Exception { public void destroy() throws Exception {
SpringContextBootstrappingInitializer.unregister(this); SpringContextBootstrappingInitializer.unregister(this);
parametersReference.set(null);
initialized = false;
} }
} }

View File

@@ -47,8 +47,8 @@ import org.springframework.util.Assert;
* @see org.springframework.test.context.junit4.SpringJUnit4ClassRunner * @see org.springframework.test.context.junit4.SpringJUnit4ClassRunner
* @since 1.3.4 * @since 1.3.4
*/ */
@ContextConfiguration("lazy-wiring-declarable-support.xml")
@RunWith(SpringJUnit4ClassRunner.class) @RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class LazyWiringDeclarableSupportIntegrationTest { public class LazyWiringDeclarableSupportIntegrationTest {
@@ -65,7 +65,7 @@ public class LazyWiringDeclarableSupportIntegrationTest {
public void testWiring() { public void testWiring() {
TestDeclarable declarable = new TestDeclarable(); TestDeclarable declarable = new TestDeclarable();
declarable.init(createParameters("testParam1", "testValue1")); declarable.init(createParameters("testParam", "testValue"));
declarable.onApplicationEvent(new ContextRefreshedEvent(applicationContext)); declarable.onApplicationEvent(new ContextRefreshedEvent(applicationContext));
declarable.assertInitialized(); declarable.assertInitialized();

View File

@@ -17,9 +17,14 @@
package org.springframework.data.gemfire; package org.springframework.data.gemfire;
import static org.junit.Assert.assertEquals; 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.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock; 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 static org.mockito.Mockito.when;
import java.util.Properties; import java.util.Properties;
@@ -30,6 +35,7 @@ import org.junit.Test;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.data.gemfire.support.SpringContextBootstrappingInitializer; import org.springframework.data.gemfire.support.SpringContextBootstrappingInitializer;
@@ -77,7 +83,7 @@ public class LazyWiringDeclarableSupportTest {
declarable.assertInitialized(); declarable.assertInitialized();
} }
catch (IllegalStateException expected) { 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()); TestLazyWiringDeclarableSupport.class.getName()), expected.getMessage());
throw expected; 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 @Test
public void testInit() { 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) @Test(expected = IllegalStateException.class)
public void testInitWhenInitialized() { public void testInitWhenInitialized() {
LazyWiringDeclarableSupport declarable = new TestLazyWiringDeclarableSupport(); LazyWiringDeclarableSupport declarable = new TestLazyWiringDeclarableSupport() {
@Override protected boolean isInitialized() {
return true;
}
};
try { try {
declarable.init(createParameters("testParam1", "testValue1")); declarable.init(createParameters("param", "value"));
}
catch (IllegalStateException unexpected) {
fail("Calling init the first time should not throw an IllegalStateException!");
}
try {
declarable.init(createParameters("testParam2", "testValue1"));
} }
catch (IllegalStateException expected) { 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()); declarable.getClass().getName()), expected.getMessage());
throw expected; throw expected;
} }
finally {
SpringContextBootstrappingInitializer.unregister(declarable);
}
} }
@Test(expected = IllegalStateException.class) @Test
public void testOnApplicationEventWithNoParameters() { public void testNullSafeGetParameters() {
LazyWiringDeclarableSupport declarable = new TestLazyWiringDeclarableSupport(); LazyWiringDeclarableSupport declarable = new TestLazyWiringDeclarableSupport();
try { try {
declarable.onApplicationEvent(new ContextRefreshedEvent(mock(ConfigurableApplicationContext.class, declarable.init(createParameters("param", "value"));
"testOnApplicationEventWithNoParameters")));
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) { finally {
assertEquals(String.format("This Declarable object's (%1$s) init method has not been invoked!", SpringContextBootstrappingInitializer.unregister(declarable);
declarable.getClass().getName()), expected.getMessage()); }
throw expected; }
@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(); LazyWiringDeclarableSupport declarable = new TestLazyWiringDeclarableSupport();
try { try {
declarable.init(createParameters("testParam1", "testValue1"));
declarable.onApplicationEvent(new ContextRefreshedEvent(mockApplicationContext)); declarable.onApplicationEvent(new ContextRefreshedEvent(mockApplicationContext));
} }
catch (IllegalArgumentException expected) { catch (IllegalArgumentException expected) {
@@ -143,6 +220,9 @@ public class LazyWiringDeclarableSupportTest {
mockApplicationContext.getClass().getName()), expected.getMessage()); mockApplicationContext.getClass().getName()), expected.getMessage());
throw expected; throw expected;
} }
finally {
SpringContextBootstrappingInitializer.unregister(declarable);
}
} }
@Test @Test
@@ -155,23 +235,86 @@ public class LazyWiringDeclarableSupportTest {
when(mockApplicationContext.getBeanFactory()).thenReturn(mockBeanFactory); when(mockApplicationContext.getBeanFactory()).thenReturn(mockBeanFactory);
Properties testParameters = createParameters("testParam1", "testValue1");
final AtomicBoolean doPostInitCalled = new AtomicBoolean(false); final AtomicBoolean doPostInitCalled = new AtomicBoolean(false);
TestLazyWiringDeclarableSupport declarable = new TestLazyWiringDeclarableSupport() { TestLazyWiringDeclarableSupport declarable = new TestLazyWiringDeclarableSupport() {
@Override protected void doPostInit(final Properties parameters) { @Override protected void doPostInit(final Properties parameters) {
super.doPostInit(parameters);
assertInitialized(); assertInitialized();
doPostInitCalled.set(true); doPostInitCalled.set(true);
} }
}; };
declarable.init(testParameters); Properties parameters = createParameters("param", "value");
declarable.onApplicationEvent(new ContextRefreshedEvent(mockApplicationContext));
declarable.assertEquals(testParameters);
declarable.assertSame(mockBeanFactory);
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 { protected static class TestLazyWiringDeclarableSupport extends LazyWiringDeclarableSupport {