diff --git a/src/main/java/org/springframework/data/gemfire/LazyWiringDeclarableSupport.java b/src/main/java/org/springframework/data/gemfire/LazyWiringDeclarableSupport.java index edee9d90..51988441 100644 --- a/src/main/java/org/springframework/data/gemfire/LazyWiringDeclarableSupport.java +++ b/src/main/java/org/springframework/data/gemfire/LazyWiringDeclarableSupport.java @@ -40,6 +40,8 @@ import com.gemstone.gemfire.cache.Declarable; * @author John Blum * @see org.springframework.context.ApplicationListener * @see org.springframework.context.event.ContextRefreshedEvent + * @see org.springframework.data.gemfire.DeclarableSupport + * @see org.springframework.data.gemfire.WiringDeclarableSupport * @see com.gemstone.gemfire.cache.Declarable * @since 1.3.4 */ @@ -47,19 +49,19 @@ import com.gemstone.gemfire.cache.Declarable; public abstract class LazyWiringDeclarableSupport implements ApplicationListener, Declarable { // The name of the template bean defined in the Spring context for wiring this Declarable instance. - private static final String BEAN_NAME_PARAMETER = "bean-name"; + protected static final String BEAN_NAME_PARAMETER = "bean-name"; // atomic reference to the parameter passed by GemFire when this Declared was constructed // and the init method was called. private final AtomicReference parametersReference = new AtomicReference(); - private volatile boolean initialized = false; + protected volatile boolean initialized = false; /** - * Constructs an instance of the LazyWiringDeclarableSupport class register with the + * Constructs an instance of the LazyWiringDeclarableSupport class registered with the * SpringContextBootstrappingInitializer to receive notification when the Spring context is created and initialized - * by GemFire to that this Declarable component can be configured and properly initialized with any required Spring - * bean dependencies. + * (refreshed) by GemFire 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) @@ -70,12 +72,13 @@ 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 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 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. *

- * @throws IllegalStateException if the Declarable object has not been properly configured or initialized by the - * Spring container. + * @throws IllegalStateException if the Declarable object has not been properly configured or initialized + * by the Spring container. + * @see #init(java.util.Properties) * @see #isInitialized() */ protected void assertInitialized() { @@ -86,19 +89,21 @@ public abstract class LazyWiringDeclarableSupport implements ApplicationListener /** * Performs the actual configuration and initialization of this Declarable object before use. This method - * is triggered by an ApplicationEvent indicating that the Spring context has been created and refreshed. + * is triggered by an ApplicationEvent (specifically, the ContextRefreshedEvent) indicating that the Spring context + * has been created and refreshed. *

* @param beanFactory the ConfigurableListableBeanFactory used to configure and initialize this Declarable GemFire * component. * @param parameters Properties instance containing the parameters from GemFire's configuration file - * (e.g. cache.xml) to configure this Declarable object. + * (e.g. cache.xml) to configure and initialize this Declarable object. * @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 org.springframework.beans.factory.wiring.BeanConfigurerSupport * @see org.springframework.beans.factory.wiring.BeanWiringInfo * @see org.springframework.beans.factory.wiring.BeanWiringInfoResolver */ - protected void doInit(final ConfigurableListableBeanFactory beanFactory, final Properties parameters) { + /* package-private */ void doInit(final ConfigurableListableBeanFactory beanFactory, final Properties parameters) { BeanConfigurerSupport beanConfigurer = new BeanConfigurerSupport(); beanConfigurer.setBeanFactory(beanFactory); @@ -124,11 +129,24 @@ public abstract class LazyWiringDeclarableSupport implements ApplicationListener beanConfigurer.destroy(); initialized = true; + + doPostInit(parameters); + } + + /** + * Default no operation method performed post initialization of this Declarable GemFire component to be overridden + * by subclasses for application specific extension and behavior. + *

+ * @param parameters Properties instance containing the parameters from GemFire's configuration file + * (e.g. cache.xml) to configure and initialize this Declarable object. + * @see #doInit(org.springframework.beans.factory.config.ConfigurableListableBeanFactory, java.util.Properties) + */ + protected void doPostInit(final Properties parameters) { } /** * Initialization method called by GemFire with the configured parameters once this Declarable object has been - * constructed during GemFire startup using an <initalizer>. + * 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. @@ -148,6 +166,7 @@ public abstract class LazyWiringDeclarableSupport implements ApplicationListener *

* @return a boolean value indicating whether this Declarable object has been configured and initialized by * the Spring container. + * @see #assertInitialized() * @see #doInit(org.springframework.beans.factory.config.ConfigurableListableBeanFactory, java.util.Properties) */ protected boolean isInitialized() { @@ -164,6 +183,9 @@ public abstract class LazyWiringDeclarableSupport implements ApplicationListener * 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 org.springframework.context.ApplicationListener#onApplicationEvent(org.springframework.context.ApplicationEvent) + * @see org.springframework.context.event.ContextRefreshedEvent */ @Override public final void onApplicationEvent(final ContextRefreshedEvent event) { 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 06f84316..6e4cd9dd 100644 --- a/src/main/java/org/springframework/data/gemfire/support/SpringContextBootstrappingInitializer.java +++ b/src/main/java/org/springframework/data/gemfire/support/SpringContextBootstrappingInitializer.java @@ -39,6 +39,11 @@ import com.gemstone.gemfire.cache.Declarable; * and initializes the Cache for use. *

* @author John Blum + * @see org.springframework.context.ApplicationContext + * @see org.springframework.context.ApplicationListener + * @see org.springframework.context.event.ApplicationEventMulticaster + * @see org.springframework.context.event.ContextRefreshedEvent + * @see org.springframework.context.event.SimpleApplicationEventMulticaster * @see org.springframework.context.ConfigurableApplicationContext * @see org.springframework.context.support.ClassPathXmlApplicationContext * @see com.gemstone.gemfire.cache.Declarable @@ -50,7 +55,7 @@ public class SpringContextBootstrappingInitializer implements Declarable, Applic public static final String CONTEXT_CONFIG_LOCATIONS_PARAMETER = "contextConfigLocations"; - private static ConfigurableApplicationContext applicationContext; + /* package-private */ static ConfigurableApplicationContext applicationContext; // TODO consider whether I should register a TaskExecutor to perform the event notifications in a separate Thread??? private static ApplicationEventMulticaster eventNotifier = new SimpleApplicationEventMulticaster(); @@ -72,25 +77,51 @@ public class SpringContextBootstrappingInitializer implements Declarable, Applic * when instantiating and initializing declared Initializers from the GemFire native configuration file * (e.g. cache.xml). *

- * @param listener the ApplicationListener to register for ContextStartedEvents by this + * @param the Class type of the Spring ApplicationListener. + * @param listener the ApplicationListener to register for ContextRefreshedEvents by this * SpringContextBootstrappingInitializer. + * @return the reference to the ApplicationListener for method call chaining purposes. + * @see #unregister(org.springframework.context.ApplicationListener) * @see org.springframework.context.ApplicationListener * @see org.springframework.context.event.SimpleApplicationEventMulticaster * #addApplicationListener(org.springframework.context.ApplicationListener) */ - public static void register(ApplicationListener listener) { + public static T register(final T listener) { eventNotifier.addApplicationListener(listener); + return listener; } /** - * Gets the the ID of the Spring ApplicationContext in a null-safe manner. + * Unregisters the Spring ApplicationListener from this SpringContextBootstrappingInitializer in order to stop + * receiving ApplicationEvents on Spring context refreshes. *

- * @param applicationContext the Spring ApplicationContext to retrieve the ID of. - * @return the ID of the given Spring ApplicationContext or null if the ApplicationContext reference is null. - * @see org.springframework.context.ApplicationContext#getId() + * @param the Class type of the Spring ApplicationListener. + * @param listener the ApplicationListener to unregister from receiving ContextRefreshedEvents by this + * SpringContextBootstrappingInitializer. + * @return the reference to the ApplicationListener for method call chaining purposes. + * @see #register(org.springframework.context.ApplicationListener) + * @see org.springframework.context.ApplicationListener + * @see org.springframework.context.event.SimpleApplicationEventMulticaster + * #removeApplicationListener(org.springframework.context.ApplicationListener) */ - protected static String nullSafeGetApplicationContextId(final ApplicationContext applicationContext) { - return (applicationContext != null ? applicationContext.getId() : null); + public static T unregister(final T listener) { + eventNotifier.removeApplicationListener(listener); + return listener; + } + + /** + * Creates (constructs and initializes) a ConfigurableApplicationContext instance based on the specified locations + * of the context configuration meta-data files and indicates whether ApplicationContext.refresh should happen + * automatically during creation. This method allows subclasses to override the type of + * ConfigurableApplicationContext created; by default, a ClassPathXmlApplicationContext is created. + *

+ * @param configLocations a String array indicating the locations of the context configuration meta-data files. + * @param refresh a boolean value indicating whether the ApplicationContext is refreshed on creation. + * @return a newly constructed and initialized instance of a ConfigurableApplicationContext. + * @see org.springframework.context.support.ClassPathXmlApplicationContext + */ + protected ConfigurableApplicationContext createApplicationContext(final String[] configLocations, final boolean refresh) { + return new ClassPathXmlApplicationContext(configLocations, refresh); } /** @@ -99,6 +130,7 @@ public class SpringContextBootstrappingInitializer implements Declarable, Applic *

* @param parameters a Properties object containing the configuration parameters and settings defined in the * GemFire cache.xml >initializer/< element. + * @see #createApplicationContext(String[], boolean) * @see java.util.Properties */ @Override @@ -116,17 +148,28 @@ public class SpringContextBootstrappingInitializer implements Declarable, Applic "A Spring application context with ID (%1$s) has already been created.", nullSafeGetApplicationContextId(applicationContext))); - applicationContext = new ClassPathXmlApplicationContext(configLocations, false); + applicationContext = createApplicationContext(configLocations, false); applicationContext.addApplicationListener(this); applicationContext.registerShutdownHook(); applicationContext.refresh(); Assert.state(applicationContext.isActive(), String.format( - "The Spring application context has failed to be properly initialized with the following config files (%1$s)!", - Arrays.toString(configLocations))); + "The Spring application context (%1$s) has failed to be properly initialized with the following config files (%2$s)!", + nullSafeGetApplicationContextId(applicationContext), Arrays.toString(configLocations))); } } + /** + * Gets the the ID of the Spring ApplicationContext in a null-safe manner. + *

+ * @param applicationContext the Spring ApplicationContext to retrieve the ID for. + * @return the ID of the given Spring ApplicationContext or null if the ApplicationContext reference is null. + * @see org.springframework.context.ApplicationContext#getId() + */ + protected String nullSafeGetApplicationContextId(final ApplicationContext applicationContext) { + return (applicationContext != null ? applicationContext.getId() : null); + } + /** * 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 diff --git a/src/test/java/org/springframework/data/gemfire/LazyWiringDeclarableSupportIntegrationTest.java b/src/test/java/org/springframework/data/gemfire/LazyWiringDeclarableSupportIntegrationTest.java new file mode 100644 index 00000000..69d4629d --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/LazyWiringDeclarableSupportIntegrationTest.java @@ -0,0 +1,130 @@ +/* + * Copyright 2010-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.gemfire; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.util.Properties; +import javax.sql.DataSource; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.data.gemfire.repository.sample.User; +import org.springframework.data.gemfire.test.support.DataSourceAdapter; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.util.Assert; + +/** + * The LazyWiringDeclarableSupportIntegrationTest class is a test suite of integration test cases testing + * a LazyWiringDeclarableSupport object/component's wiring configuration and initialization in + * a Spring container context. + *

+ * @author John Blum + * @see org.junit.Test + * @see org.junit.runner.RunWith + * @see org.springframework.test.context.ContextConfiguration + * @see org.springframework.test.context.junit4.SpringJUnit4ClassRunner + * @since 1.3.4 + */ +@ContextConfiguration("lazy-wiring-declarable-support.xml") +@RunWith(SpringJUnit4ClassRunner.class) +@SuppressWarnings("unused") +public class LazyWiringDeclarableSupportIntegrationTest { + + @Autowired + private ApplicationContext applicationContext; + + protected static Properties createParameters(final String parameter, final String value) { + Properties parameters = new Properties(); + parameters.setProperty(parameter, value); + return parameters; + } + + @Test + public void testWiring() { + TestDeclarable declarable = new TestDeclarable(); + + declarable.init(createParameters("testParam1", "testValue1")); + declarable.onApplicationEvent(new ContextRefreshedEvent(applicationContext)); + declarable.assertInitialized(); + + assertNull(declarable.getDataSource()); + assertNotNull(declarable.getUser()); + assertEquals("supertool", declarable.getUser().getUsername()); + } + + @Test + public void testWiringWithBeanTemplate() { + TestDeclarable declarable = new TestDeclarable(); + + declarable.init(createParameters(LazyWiringDeclarableSupport.BEAN_NAME_PARAMETER, "declarableTemplateBean")); + declarable.onApplicationEvent(new ContextRefreshedEvent(applicationContext)); + declarable.assertInitialized(); + + assertNotNull(declarable.getDataSource()); + assertNotNull(declarable.getUser()); + assertEquals("supertool", declarable.getUser().getUsername()); + } + + @Test(expected = IllegalArgumentException.class) + public void testWiringWithNonExistingBeanTemplate() { + TestDeclarable declarable = new TestDeclarable(); + + try { + declarable.init(createParameters(LazyWiringDeclarableSupport.BEAN_NAME_PARAMETER, + "nonExistingBeanTemplate")); + declarable.onApplicationEvent(new ContextRefreshedEvent(applicationContext)); + } + catch (IllegalArgumentException expected) { + assertTrue(expected.getMessage().startsWith( + "No bean with name 'nonExistingBeanTemplate' was found in the Spring context")); + throw expected; + } + } + + protected static final class TestDataSource extends DataSourceAdapter { + } + + protected static final class TestDeclarable extends LazyWiringDeclarableSupport { + + private DataSource dataSource; + + @Autowired + private User user; + + public final void setDataSource(final DataSource dataSource) { + this.dataSource = dataSource; + } + + protected DataSource getDataSource() { + return dataSource; + } + + protected User getUser() { + Assert.state(user != null, "A reference to the User was not properly configured!"); + return user; + } + } + +} diff --git a/src/test/java/org/springframework/data/gemfire/LazyWiringDeclarableSupportTest.java b/src/test/java/org/springframework/data/gemfire/LazyWiringDeclarableSupportTest.java new file mode 100644 index 00000000..06d25566 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/LazyWiringDeclarableSupportTest.java @@ -0,0 +1,199 @@ +/* + * Copyright 2010-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.gemfire; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.Properties; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.junit.Assert; +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.ContextRefreshedEvent; +import org.springframework.data.gemfire.support.SpringContextBootstrappingInitializer; + +/** + * The LazyWiringDeclarableSupportTest class is a test suite of test cases testing the contract and functionality + * of the LazyWiringDeclarableSupport class. This test class focuses on testing isolated units of functionality + * in the Declarable class directly, mocking any dependencies as appropriate, in order for the class to uphold + * it's contract. + *

+ * @author John Blum + * @see org.junit.Test + * @see org.mockito.Mockito + * @see org.springframework.data.gemfire.LazyWiringDeclarableSupport + * @since 1.3.4 + */ +public class LazyWiringDeclarableSupportTest { + + protected static Properties createParameters(final String parameter, final String value) { + Properties parameters = new Properties(); + parameters.setProperty(parameter, value); + return parameters; + } + + @Test + public void testAssertInitialized() { + LazyWiringDeclarableSupport declarable = new TestLazyWiringDeclarableSupport() { + @Override protected boolean isInitialized() { + return true; + } + }; + + try { + declarable.assertInitialized(); + } + finally { + SpringContextBootstrappingInitializer.unregister(declarable); + } + } + + @Test(expected = IllegalStateException.class) + public void testAssertInitializedWhenUninitialized() { + LazyWiringDeclarableSupport declarable = new TestLazyWiringDeclarableSupport(); + + try { + declarable.assertInitialized(); + } + catch (IllegalStateException expected) { + assertEquals(String.format("This Declarable object (%1$s) has not ben properly configured and initialized!", + TestLazyWiringDeclarableSupport.class.getName()), expected.getMessage()); + throw expected; + } + finally { + SpringContextBootstrappingInitializer.unregister(declarable); + } + } + + @Test + public void testInit() { + new TestLazyWiringDeclarableSupport().init(createParameters("testParam1", "testValue1")); + } + + @Test(expected = IllegalStateException.class) + public void testInitWhenInitialized() { + LazyWiringDeclarableSupport declarable = new TestLazyWiringDeclarableSupport(); + + 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")); + } + catch (IllegalStateException expected) { + assertEquals(String.format("This Declarable (%1$s) has already been initialized.", + declarable.getClass().getName()), expected.getMessage()); + throw expected; + } + } + + @Test(expected = IllegalStateException.class) + public void testOnApplicationEventWithNoParameters() { + LazyWiringDeclarableSupport declarable = new TestLazyWiringDeclarableSupport(); + + try { + declarable.onApplicationEvent(new ContextRefreshedEvent(mock(ConfigurableApplicationContext.class, + "testOnApplicationEventWithNoParameters"))); + } + 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; + } + } + + @Test(expected = IllegalArgumentException.class) + public void testOnApplicationEventWithNonConfigurableApplicationContext() { + ApplicationContext mockApplicationContext = mock(ApplicationContext.class, + "testOnApplicationEventWithNonConfigurableApplicationContext"); + + LazyWiringDeclarableSupport declarable = new TestLazyWiringDeclarableSupport(); + + try { + declarable.init(createParameters("testParam1", "testValue1")); + declarable.onApplicationEvent(new ContextRefreshedEvent(mockApplicationContext)); + } + catch (IllegalArgumentException expected) { + assertEquals(String.format("The Spring ApplicationContext (%1$s) must be an instance of ConfigurableApplicationContext.", + mockApplicationContext.getClass().getName()), expected.getMessage()); + throw expected; + } + } + + @Test + public void testOnApplicationEventAndDoPostInit() { + ConfigurableApplicationContext mockApplicationContext = mock(ConfigurableApplicationContext.class, + "testOnApplicationEventAndDoPostInit.ApplicationContext"); + + ConfigurableListableBeanFactory mockBeanFactory = mock(ConfigurableListableBeanFactory.class, + "testOnApplicationEventAndDoPostInit.BeanFactory"); + + 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) { + assertInitialized(); + doPostInitCalled.set(true); + } + }; + + declarable.init(testParameters); + declarable.onApplicationEvent(new ContextRefreshedEvent(mockApplicationContext)); + declarable.assertEquals(testParameters); + declarable.assertSame(mockBeanFactory); + + assertTrue(doPostInitCalled.get()); + } + + protected static class TestLazyWiringDeclarableSupport extends LazyWiringDeclarableSupport { + + private ConfigurableListableBeanFactory actualBeanFactory; + private Properties actualParameters; + + protected void assertEquals(final Properties expectedParameters) { + Assert.assertEquals(expectedParameters, actualParameters); + } + + protected void assertSame(final ConfigurableListableBeanFactory expectedBeanFactory) { + Assert.assertSame(expectedBeanFactory, actualBeanFactory); + } + + @Override + void doInit(final ConfigurableListableBeanFactory beanFactory, final Properties parameters) { + this.actualBeanFactory = beanFactory; + this.actualParameters = parameters; + initialized = true; + doPostInit(parameters); + } + } + +} diff --git a/src/test/java/org/springframework/data/gemfire/repository/sample/User.java b/src/test/java/org/springframework/data/gemfire/repository/sample/User.java index 323936a9..3cf6c82a 100644 --- a/src/test/java/org/springframework/data/gemfire/repository/sample/User.java +++ b/src/test/java/org/springframework/data/gemfire/repository/sample/User.java @@ -87,6 +87,10 @@ public class User implements Comparable { return getUsername().compareTo(user.getUsername()); } + protected static boolean equalsIgnoreNull(final Object obj1, final Object obj2) { + return (obj1 == null ? obj2 == null : obj1.equals(obj2)); + } + @Override public boolean equals(final Object obj) { if (obj == this) { @@ -103,6 +107,10 @@ public class User implements Comparable { && ObjectUtils.nullSafeEquals(this.getEmail(), that.getEmail()); } + protected static int hashCodeIgnoreNull(final Object obj) { + return (obj != null ? obj.hashCode() : 0); + } + @Override public int hashCode() { int hashValue = 17; 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 d18b27f6..ab926a3d 100644 --- a/src/test/java/org/springframework/data/gemfire/support/SpringContextBootstrappingInitializerIntegrationTest.java +++ b/src/test/java/org/springframework/data/gemfire/support/SpringContextBootstrappingInitializerIntegrationTest.java @@ -48,8 +48,8 @@ import com.gemstone.gemfire.cache.LoaderHelper; import com.gemstone.gemfire.cache.Region; /** - * The SpringContextBootstrappingInitializerTest class is a test suite of test cases testing the functionality of the - * SpringContextBootstrappingInitializer class + * The SpringContextBootstrappingInitializerTest class is a test suite of test cases testing the integrated + * functionality of the SpringContextBootstrappingInitializer class. *

* @author John Blum * @see org.junit.Test @@ -137,6 +137,7 @@ public class SpringContextBootstrappingInitializerIntegrationTest { private DataSource userDataSource; protected DataSource getDataSource() { + Assert.state(userDataSource != null, "A reference to the Users DataSource as not properly configured!"); return userDataSource; } } @@ -151,6 +152,7 @@ public class SpringContextBootstrappingInitializerIntegrationTest { private UserDao userDao; protected UserDao getUserDao() { + Assert.state(userDao != null, "A reference to the UserDao was not properly configured!"); return userDao; } } @@ -182,6 +184,10 @@ public class SpringContextBootstrappingInitializerIntegrationTest { return createUser(username, String.format("%1$s@xcompay.com", username), active, Calendar.getInstance()); } + 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); + } + protected static User createUser(final String username, final String email, final Boolean active, final Calendar since) { User user = new User(username); user.setActive(active); diff --git a/src/test/java/org/springframework/data/gemfire/support/SpringContextBootstrappingInitializerTest.java b/src/test/java/org/springframework/data/gemfire/support/SpringContextBootstrappingInitializerTest.java new file mode 100644 index 00000000..3fec4ba1 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/support/SpringContextBootstrappingInitializerTest.java @@ -0,0 +1,249 @@ +/* + * Copyright 2010-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.gemfire.support; + +import 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.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Properties; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationListener; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.util.ObjectUtils; + +/** + * The SpringContextBootstrappingInitializerTest class is a test suite of test cases testing the contract + * and functionality of the SpringContextBootstrappingInitializer class. This test class focuses on testing isolated + * units of functionality in the Initializer class directly, mocking any dependencies as appropriate, in order for the + * class to uphold it's contract. + *

+ * @author John Blum + * @see org.junit.Test + * @see org.mockito.Mockito + * @see org.springframework.data.gemfire.support.SpringContextBootstrappingInitializer + * @see org.springframework.data.gemfire.support.SpringContextBootstrappingInitializerIntegrationTest + * @since 1.3.4 + */ +public class SpringContextBootstrappingInitializerTest { + + protected static Properties createParameters(final String parameter, final String value) { + Properties parameters = new Properties(); + parameters.setProperty(parameter, value); + return parameters; + } + + @After + public void tearDown() { + SpringContextBootstrappingInitializer.applicationContext = null; + } + + @Test(expected = IllegalArgumentException.class) + public void testInitWithUnspecifiedContextConfigLocationsParameter() { + try { + new SpringContextBootstrappingInitializer().init(createParameters( + SpringContextBootstrappingInitializer.CONTEXT_CONFIG_LOCATIONS_PARAMETER, "")); + } + catch (IllegalArgumentException expected) { + assertEquals("The contextConfigLocations parameter is required.", 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 = IllegalStateException.class) + public void testInitWithNonActiveApplicationConstruct() { + final ConfigurableApplicationContext mockApplicationContext = mock(ConfigurableApplicationContext.class, + "testInitWithNonActiveApplicationConstruct"); + + when(mockApplicationContext.getId()).thenReturn("testInitWithNonActiveApplicationConstruct"); + when(mockApplicationContext.isActive()).thenReturn(false); + + SpringContextBootstrappingInitializer initializer = null; + + try { + initializer = new SpringContextBootstrappingInitializer() { + @Override + protected ConfigurableApplicationContext createApplicationContext(final String[] configLocations, final boolean refresh) { + 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 (testInitWithNonActiveApplicationConstruct) has failed to be properly initialized with the following config files ([/path/to/spring/context/configuration/file.xml])!", + expected.getMessage()); + throw expected; + } + finally { + verify(mockApplicationContext, times(1)).addApplicationListener(eq(initializer)); + verify(mockApplicationContext, times(1)).registerShutdownHook(); + verify(mockApplicationContext, times(1)).refresh(); + } + } + + @Test + public void testInitFollowedByGetApplicationContext() { + final ConfigurableApplicationContext mockApplicationContext = mock(ConfigurableApplicationContext.class, + "testInitFollowedByGetApplicationContext"); + + when(mockApplicationContext.getId()).thenReturn("testInitFollowedByGetApplicationContext"); + when(mockApplicationContext.isActive()).thenReturn(true); + + SpringContextBootstrappingInitializer initializer = new SpringContextBootstrappingInitializer() { + @Override + protected ConfigurableApplicationContext createApplicationContext(final String[] configLocations, final boolean refresh) { + 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(); + + assertSame(mockApplicationContext, SpringContextBootstrappingInitializer.getApplicationContext()); + } + + @Test(expected = IllegalStateException.class) + public void testGetApplicationContextUninitialized() { + try { + SpringContextBootstrappingInitializer.getApplicationContext(); + } + catch (IllegalStateException expected) { + assertEquals("The Spring ApplicationContext has not been created!", expected.getMessage()); + throw expected; + } + } + + @Test + public void testNullSafeGetApplicationContextIdWithNullReference() { + assertNull(new SpringContextBootstrappingInitializer().nullSafeGetApplicationContextId(null)); + } + + @Test + public void testNullSafeGetApplicationContextWithNonNullReference() { + ApplicationContext mockApplicationContext = mock(ApplicationContext.class, + "testNullSafeGetApplicationContextWithNonNullReference"); + + when(mockApplicationContext.getId()).thenReturn("testNullSafeGetApplicationContextWithNonNullReference"); + + assertEquals("testNullSafeGetApplicationContextWithNonNullReference", + new SpringContextBootstrappingInitializer().nullSafeGetApplicationContextId(mockApplicationContext)); + } + + @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, + "testUnregisterAndOnApplicationEvent")); + + new SpringContextBootstrappingInitializer().onApplicationEvent(testEvent); + + testListener.assertNotCalled(); + } + finally { + SpringContextBootstrappingInitializer.unregister(testListener); + } + } + + protected static class TestApplicationListener implements ApplicationListener { + + private volatile boolean called = false; + + private volatile ContextRefreshedEvent actualEvent; + + public void assertCalled() { + assertTrue(String.format("Expected the (%1$s).onApplicationEvent(:ContextRefreshedEvent) method to be called!", + getClass().getName()), called); + } + + 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 void assertSame(final ContextRefreshedEvent expectedEvent) { + Assert.assertSame(expectedEvent, actualEvent); + } + + @Override + public void onApplicationEvent(final ContextRefreshedEvent event) { + this.actualEvent = event; + called = true; + } + } + +} diff --git a/src/test/resources/cache-with-spring-context-bootstrap-initializer.xml b/src/test/resources/cache-with-spring-context-bootstrap-initializer.xml index ff0ba052..d73a5305 100644 --- a/src/test/resources/cache-with-spring-context-bootstrap-initializer.xml +++ b/src/test/resources/cache-with-spring-context-bootstrap-initializer.xml @@ -4,6 +4,8 @@ + java.lang.String + org.springframework.data.gemfire.repository.sample.User org.springframework.data.gemfire.support.SpringContextBootstrappingInitializerIntegrationTest$UserDataStoreCacheLoader diff --git a/src/test/resources/org/springframework/data/gemfire/lazy-wiring-declarable-support.xml b/src/test/resources/org/springframework/data/gemfire/lazy-wiring-declarable-support.xml new file mode 100644 index 00000000..db89d809 --- /dev/null +++ b/src/test/resources/org/springframework/data/gemfire/lazy-wiring-declarable-support.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + +