diff --git a/src/main/java/org/springframework/data/gemfire/config/PdxDiskStoreAwareBeanFactoryPostProcessor.java b/src/main/java/org/springframework/data/gemfire/config/PdxDiskStoreAwareBeanFactoryPostProcessor.java index 24986433..26f3bddc 100644 --- a/src/main/java/org/springframework/data/gemfire/config/PdxDiskStoreAwareBeanFactoryPostProcessor.java +++ b/src/main/java/org/springframework/data/gemfire/config/PdxDiskStoreAwareBeanFactoryPostProcessor.java @@ -25,9 +25,14 @@ import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.data.gemfire.DiskStoreFactoryBean; import org.springframework.data.gemfire.RegionLookupFactoryBean; import org.springframework.data.gemfire.util.ArrayUtils; +import org.springframework.data.gemfire.wan.AsyncEventQueueFactoryBean; import org.springframework.util.Assert; import org.springframework.util.StringUtils; +import com.gemstone.gemfire.cache.DiskStore; +import com.gemstone.gemfire.cache.Region; +import com.gemstone.gemfire.cache.asyncqueue.AsyncEventQueue; + /** * The PdxDiskStoreAwareBeanFactoryPostProcessor class is a BeanFactoryPostProcessor that modifies all Region bean * definitions in the Spring BeanFactory to form a dependency on the Cache's PDX Disk Store bean. A persistent Region @@ -35,6 +40,7 @@ import org.springframework.util.StringUtils; * having PDX data is loaded from disk. *

* @author John Blum + * @see org.springframework.beans.factory.config.BeanDefinition * @see org.springframework.beans.factory.config.BeanFactoryPostProcessor * @see org.springframework.beans.factory.config.ConfigurableListableBeanFactory * @since 1.3.3 @@ -42,28 +48,52 @@ import org.springframework.util.StringUtils; @SuppressWarnings("unused") public class PdxDiskStoreAwareBeanFactoryPostProcessor implements BeanFactoryPostProcessor { - private static final String[] EMPTY_STRING_ARRAY = new String[0]; + protected static final String DATA_POLICY_PROPERTY = "dataPolicy"; + protected static final String DATA_POLICY_NAME_PROPERTY = "dataPolicyName"; + protected static final String PERSISTENT_KEYWORD = "PERSISTENT"; + protected static final String PERSISTENT_PROPERTY = "persistent"; + protected static final String SHORTCUT_PROPERTY = "shortcut"; - private String pdxDiskStoreName; + protected static final String[] EMPTY_STRING_ARRAY = new String[0]; - public PdxDiskStoreAwareBeanFactoryPostProcessor() { - } + private final String pdxDiskStoreName; public PdxDiskStoreAwareBeanFactoryPostProcessor(final String pdxDiskStoreName) { - setPdxDiskStoreName(pdxDiskStoreName); + Assert.isTrue(StringUtils.hasText(pdxDiskStoreName), "The name of the PDX Disk Store must be specified!"); + this.pdxDiskStoreName = pdxDiskStoreName; } public String getPdxDiskStoreName() { - Assert.state(!StringUtils.isEmpty(pdxDiskStoreName), "The PDX Disk Store name was not properly initialized!"); return pdxDiskStoreName; } - public final void setPdxDiskStoreName(final String pdxDiskStoreName) { - this.pdxDiskStoreName = pdxDiskStoreName; - } - @Override public void postProcessBeanFactory(final ConfigurableListableBeanFactory beanFactory) throws BeansException { + Assert.state(!beanFactory.isConfigurationFrozen(), + "The BeanFactory configuration meta-data is frozen and cannot be modified further!"); + //postProcessPdxDiskStoreDependencies(beanFactory); + postProcessPdxDiskStoreDependencies(beanFactory, AsyncEventQueue.class, DiskStore.class, Region.class); + //postProcessPdxDiskStoreDependencies(beanFactory, AsyncEventQueueFactoryBean.class, DiskStoreFactoryBean.class, + // RegionLookupFactoryBean.class); + } + + private void postProcessPdxDiskStoreDependencies(final ConfigurableListableBeanFactory beanFactory, final Class... beanTypes) { + for (Class beanType : beanTypes) { + for (String beanName : beanFactory.getBeanNamesForType(beanType)) { + if (!beanName.equalsIgnoreCase(getPdxDiskStoreName())) { + BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName); + + if (isDiskStore(beanDefinition) || (isPersistent(beanDefinition) + && !hasDiskStoreReference(beanFactory, beanDefinition))) { + addPdxDiskStoreDependency(beanDefinition); + } + } + } + } + } + + // TODO remove + private void postProcessPdxDiskStoreDependencies(final ConfigurableListableBeanFactory beanFactory) { for (String beanName : beanFactory.getBeanDefinitionNames()) { // NOTE do not add the PDX Disk Store bean dependency to itself! if (!beanName.equalsIgnoreCase(getPdxDiskStoreName())) { @@ -75,7 +105,8 @@ public class PdxDiskStoreAwareBeanFactoryPostProcessor implements BeanFactoryPos // NOTE add the dependency to the Disk Store bean definition in case the Disk Store bean is defined // before the Region that uses it! // TODO what else depends on a PDX Disk Store besides Regions? - if (isPersistentRegion(beanDefinition) || isDiskStore(beanDefinition)) { + if (isDiskStore(beanDefinition) || isPersistentAsyncEventQueue(beanDefinition) + || isPersistentRegion(beanDefinition)) { addPdxDiskStoreDependency(beanDefinition); } } @@ -89,37 +120,131 @@ public class PdxDiskStoreAwareBeanFactoryPostProcessor implements BeanFactoryPos private boolean isDiskStore(final AbstractBeanDefinition beanDefinition) { return (beanDefinition.hasBeanClass() - && DiskStoreFactoryBean.class.isAssignableFrom(beanDefinition.getBeanClass())); + && (DiskStoreFactoryBean.class.isAssignableFrom(beanDefinition.getBeanClass())) + || DiskStore.class.isAssignableFrom(beanDefinition.getBeanClass())); } + // TODO remove + private boolean isPersistentAsyncEventQueue(final BeanDefinition beanDefinition) { + return (beanDefinition instanceof AbstractBeanDefinition + && isPersistentAsyncEventQueue((AbstractBeanDefinition) beanDefinition)); + } + + // TODO remove + private boolean isPersistentAsyncEventQueue(final AbstractBeanDefinition beanDefinition) { + return (isAsyncEventQueue(beanDefinition) && isPersistent(beanDefinition)); + } + + // TODO remove + private boolean isAsyncEventQueue(final AbstractBeanDefinition beanDefinition) { + return (beanDefinition.hasBeanClass() + && AsyncEventQueueFactoryBean.class.isAssignableFrom(beanDefinition.getBeanClass())); + } + + // TODO remove private boolean isPersistentRegion(final BeanDefinition beanDefinition) { return (beanDefinition instanceof AbstractBeanDefinition && isPersistentRegion((AbstractBeanDefinition) beanDefinition)); } + // TODO remove private boolean isPersistentRegion(final AbstractBeanDefinition beanDefinition) { return (isRegion(beanDefinition) && isPersistent(beanDefinition)); } + // TODO remove // NOTE Class.isAssignableFrom is not null-safe, hence the AbstractBeanDefinition.hasBeanClass call! private boolean isRegion(final AbstractBeanDefinition beanDefinition) { return (beanDefinition.hasBeanClass() && RegionLookupFactoryBean.class.isAssignableFrom(beanDefinition.getBeanClass())); } - private boolean isPersistent(final AbstractBeanDefinition beanDefinition) { - PropertyValue persistentPropertyValue = beanDefinition.getPropertyValues().getPropertyValue("persistent"); - - return (persistentPropertyValue != null - && Boolean.parseBoolean(String.valueOf(persistentPropertyValue.getValue()))); + private boolean hasDiskStoreReference(final ConfigurableListableBeanFactory beanFactory, + final BeanDefinition beanDefinition) { + String diskStoreName = getPropertyValue(beanDefinition, "diskStoreName"); + return (StringUtils.hasText(diskStoreName) && beanFactory.containsBeanDefinition(diskStoreName)); } + // TODO will property placeholders be a problem or will the PropertyPlaceholderConfigurer BeanFactoryPostProcessor + // execute before this BeanFactoryPostProcessor??? + private boolean isPersistent(final BeanDefinition beanDefinition) { + boolean persistent = getPropertyValue(beanDefinition, DATA_POLICY_PROPERTY, DATA_POLICY_NAME_PROPERTY) + .contains(PERSISTENT_KEYWORD); + + persistent |= Boolean.parseBoolean(getPropertyValue(beanDefinition, PERSISTENT_PROPERTY)); + + persistent |= getPropertyValue(beanDefinition, SHORTCUT_PROPERTY).contains(PERSISTENT_KEYWORD); + + return persistent; + } + + /** + * Gets the value of a potentially multi-named property on a BeanDefinition returning the first non-null value. + *

+ * @param beanDefinition the BeanDefinition of the Bean with the property identified by name(s). + * @param propertyNames a String array containing all the possible names of the property. + * @return the first non-null value of the property, which might have multi-names (aliases). + * @see #getPropertyValue(org.springframework.beans.factory.config.BeanDefinition, String) + */ + private String getPropertyValue(final BeanDefinition beanDefinition, final String... propertyNames) { + String propertyValue = null; + + for (String propertyName : propertyNames) { + propertyValue = getPropertyValue(beanDefinition, propertyName); + if (!isNull(propertyValue)) { + break; + } + } + + return String.valueOf(propertyValue); + } + + /** + * Gets the value of the property identified by name from the Bean's BeanDefinition. + *

+ * @param beanDefinition the BeanDefinition containing the configuration meta-data and values of the properties + * for the Bean. + * @param propertyName a String identifying the property by name. + * @return a String value of the named property for the Bean. + * @see #getPropertyValue(org.springframework.beans.factory.config.BeanDefinition, String...) + */ + private String getPropertyValue(final BeanDefinition beanDefinition, final String propertyName) { + PropertyValue propertyValue = beanDefinition.getPropertyValues().getPropertyValue(propertyName); + return String.valueOf(propertyValue != null ? propertyValue.getValue() : null); + } + + /** + * Determines whether the specified String value is null. The String value is null if it is a null references + * or is equal to the "null" String irrespective of case or whitespace. + *

+ * @param value the String to evaluate for null value. + * @return a boolean indicating whether the String value is null. + */ + private boolean isNull(final String value) { + return (value == null || "null".equalsIgnoreCase(value.trim())); + } + + /** + * Adds the PDX Disk Store dependency to the beginning of the list of dependencies declared by the Bean. + *

+ * @param beanDefinition the BeanDefinition to add the dependency to the PDX Disk Store on. + * @see org.springframework.beans.factory.config.BeanDefinition#setDependsOn(String[]) + */ private void addPdxDiskStoreDependency(final BeanDefinition beanDefinition) { String[] newDependsOn = (String[]) ArrayUtils.insert(getDependsOn(beanDefinition), 0, getPdxDiskStoreName()); beanDefinition.setDependsOn(newDependsOn); } + /** + * Gets the current list of dependencies declared in the BeanDefinition for the Bean, returning an + * empty String array if the dependsOn property is null. + *

+ * @param beanDefinition the BeanDefinition of the Bean containing the dependencies. + * @return an array of Bean names that this Bean depends on, or an empty String array if the dependencies + * are undefined. + * @see org.springframework.beans.factory.config.BeanDefinition#getDependsOn() + */ private String[] getDependsOn(final BeanDefinition beanDefinition) { return (beanDefinition.getDependsOn() != null ? beanDefinition.getDependsOn() : EMPTY_STRING_ARRAY); } diff --git a/src/test/java/org/springframework/data/gemfire/config/PdxDiskStoreAwareBeanFactoryPostProcessorTest.java b/src/test/java/org/springframework/data/gemfire/config/PdxDiskStoreAwareBeanFactoryPostProcessorTest.java index f8113cba..5d71f944 100644 --- a/src/test/java/org/springframework/data/gemfire/config/PdxDiskStoreAwareBeanFactoryPostProcessorTest.java +++ b/src/test/java/org/springframework/data/gemfire/config/PdxDiskStoreAwareBeanFactoryPostProcessorTest.java @@ -21,12 +21,15 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.isA; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.junit.Test; @@ -34,19 +37,24 @@ import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.data.gemfire.CacheFactoryBean; -import org.springframework.data.gemfire.DiskStoreFactoryBean; -import org.springframework.data.gemfire.PartitionedRegionFactoryBean; -import org.springframework.data.gemfire.ReplicatedRegionFactoryBean; + +import com.gemstone.gemfire.cache.DataPolicy; +import com.gemstone.gemfire.cache.DiskStore; +import com.gemstone.gemfire.cache.Region; +import com.gemstone.gemfire.cache.RegionShortcut; +import com.gemstone.gemfire.cache.asyncqueue.AsyncEventQueue; +import com.gemstone.gemfire.internal.cache.PartitionedRegion; /** * The PdxDiskStoreAwareBeanFactoryPostProcessorTest class is a test suite of test cases testing the functionality * of the PdxDiskStoreAwareBeanFactoryPostProcessor class. *

* @author John Blum - * @see org.mockito.Mockito * @see org.junit.Test + * @see org.mockito.Mockito * @see org.springframework.data.gemfire.config.PdxDiskStoreAwareBeanFactoryPostProcessor * @since 1.3.3 */ @@ -63,17 +71,48 @@ public class PdxDiskStoreAwareBeanFactoryPostProcessorTest { return (array == null || array.length == 0); } + protected static boolean isBeanType(final BeanDefinition beanDefinition, final Class beanType) { + return (beanDefinition instanceof AbstractBeanDefinition + && ((AbstractBeanDefinition) beanDefinition).hasBeanClass() + && beanType.isAssignableFrom(((AbstractBeanDefinition) beanDefinition).getBeanClass())); + } + protected ConfigurableListableBeanFactory createMockBeanFactory(final Map beanDefinitions) { final ConfigurableListableBeanFactory mockBeanFactory = mock(ConfigurableListableBeanFactory.class); when(mockBeanFactory.getBeanDefinitionNames()).thenReturn(toStringArray(beanDefinitions.keySet())); + when(mockBeanFactory.getBeanNamesForType(isA(Class.class))).then(new Answer() { + @Override + public String[] answer(final InvocationOnMock invocation) throws Throwable { + Object[] arguments = invocation.getArguments(); + + assertNotNull(arguments); + assertTrue(arguments.length == 1); + assertTrue(arguments[0] instanceof Class); + + Class beanType = (Class) arguments[0]; + + List beanNames = new ArrayList(beanDefinitions.size()); + + for (Map.Entry entry : beanDefinitions.entrySet()) { + BeanDefinition beanDefinition = entry.getValue(); + + if (isBeanType(beanDefinition, beanType)) { + beanNames.add(entry.getKey()); + } + } + + return toStringArray(beanNames); + } + }); + when(mockBeanFactory.getBeanDefinition(anyString())).then(new Answer() { @Override public BeanDefinition answer(final InvocationOnMock invocation) throws Throwable { - final Object[] arguments = invocation.getArguments(); + Object[] arguments = invocation.getArguments(); assertNotNull(arguments); - assertTrue(arguments.length > 0); + assertTrue(arguments.length == 1); return beanDefinitions.get(String.valueOf(arguments[0])); } }); @@ -115,40 +154,71 @@ public class PdxDiskStoreAwareBeanFactoryPostProcessorTest { return createBeanDefinitionBuilder(CacheFactoryBean.class).getBeanDefinition(); } - protected BeanDefinition defineDiskStore(String... dependencies) { - return createBeanDefinitionBuilder(DiskStoreFactoryBean.class, dependencies).getBeanDefinition(); - } + protected BeanDefinition defineAsyncEventQueue(Boolean persistent, String... dependencies) { + BeanDefinitionBuilder builder = createBeanDefinitionBuilder(AsyncEventQueue.class, dependencies); + + if (persistent != null) { + builder.addPropertyValue("persistent", persistent.toString()); + } - protected BeanDefinition defineRegion(Class beanClass, boolean persistent, String... dependencies) { - BeanDefinitionBuilder builder = createBeanDefinitionBuilder(beanClass, dependencies); - builder.addPropertyValue("persistent", persistent); return builder.getBeanDefinition(); } - protected BeanDefinition definePartitionedRegion(boolean persistent, String... dependencies) { - return defineRegion(PartitionedRegionFactoryBean.class, persistent, dependencies); + protected BeanDefinition defineDiskStore(String... dependencies) { + return createBeanDefinitionBuilder(DiskStore.class, dependencies).getBeanDefinition(); } - protected BeanDefinition defineReplicatedRegion(boolean persistent, String... dependencies) { - return defineRegion(ReplicatedRegionFactoryBean.class, persistent, dependencies); + protected BeanDefinition defineRegion(Class beanClass, Boolean persistent, DataPolicy dataPolicy, + RegionShortcut shortcut, String... dependencies) { + BeanDefinitionBuilder builder = createBeanDefinitionBuilder(beanClass, dependencies); + + if (persistent != null) { + builder.addPropertyValue("persistent", persistent.toString()); + } + + if (dataPolicy != null) { + builder.addPropertyValue("dataPolicy", dataPolicy.toString()); + } + + if (shortcut != null) { + builder.addPropertyValue("shortcut", shortcut.toString()); + } + + return builder.getBeanDefinition(); + } + + protected BeanDefinition definePartitionedRegion(Boolean persistent, DataPolicy dataPolicy, RegionShortcut shortcut, + String... dependencies) { + return defineRegion(PartitionedRegion.class, persistent, dataPolicy, shortcut, dependencies); + } + + protected BeanDefinition defineReplicatedRegion(Boolean persistent, DataPolicy dataPolicy, RegionShortcut shortcut, + String... dependencies) { + return defineRegion(Region.class, persistent, dataPolicy, shortcut, dependencies); + } + + @Test(expected = IllegalArgumentException.class) + public void testCreatePdxDiskStoreAwareBeanFactoryPostProcessorWithBlankDiskStoreName() { + new PdxDiskStoreAwareBeanFactoryPostProcessor(" "); + } + + @Test(expected = IllegalArgumentException.class) + public void testCreatePdxDiskStoreAwareBeanFactoryPostProcessorWithEmptyDiskStoreName() { + new PdxDiskStoreAwareBeanFactoryPostProcessor(""); + } + + @Test(expected = IllegalArgumentException.class) + public void testCreatePdxDiskStoreAwareBeanFactoryPostProcessorWithNullDiskStoreName() { + new PdxDiskStoreAwareBeanFactoryPostProcessor(null); } @Test public void testInitializedPdxDiskStoreAwareBeanFactoryPostProcessor() { - final PdxDiskStoreAwareBeanFactoryPostProcessor postProcessor = + PdxDiskStoreAwareBeanFactoryPostProcessor postProcessor = new PdxDiskStoreAwareBeanFactoryPostProcessor("testPdxDiskStoreName"); assertNotNull(postProcessor); assertEquals("testPdxDiskStoreName", postProcessor.getPdxDiskStoreName()); - - postProcessor.setPdxDiskStoreName("mockPdxDiskStoreName"); - - assertEquals("mockPdxDiskStoreName", postProcessor.getPdxDiskStoreName()); - } - - @Test(expected = IllegalStateException.class) - public void testUninitializedPdxDiskStoreAwareBeanFactoryPostProcessor() { - new PdxDiskStoreAwareBeanFactoryPostProcessor().getPdxDiskStoreName(); } @Test @@ -159,15 +229,22 @@ public class PdxDiskStoreAwareBeanFactoryPostProcessorTest { beanDefinitions.put("gemfireCache", defineCache()); beanDefinitions.put("pdxDiskStore", defineDiskStore()); beanDefinitions.put("someOtherBean", defineBean("org.company.app.domain.SomeOtherBean")); + beanDefinitions.put("queue1", defineAsyncEventQueue(null, "someOtherBean")); beanDefinitions.put("overflowDiskStore", defineDiskStore()); - beanDefinitions.put("region1", defineReplicatedRegion(NOT_PERSISTENT, "overflowDiskStore")); - beanDefinitions.put("region2DiskStore", defineDiskStore("someBean")); - beanDefinitions.put("region2", defineReplicatedRegion(PERSISTENT, "region2DiskStore")); - beanDefinitions.put("colocatedRegion", definePartitionedRegion(NOT_PERSISTENT, "residentRegion", + beanDefinitions.put("region1", defineReplicatedRegion(NOT_PERSISTENT, null, RegionShortcut.REPLICATE_OVERFLOW, "overflowDiskStore")); + beanDefinitions.put("region2DiskStore", defineDiskStore("someBean")); + beanDefinitions.put("region2", defineReplicatedRegion(PERSISTENT, null, null, "region2DiskStore")); + beanDefinitions.put("colocatedRegion", definePartitionedRegion(null, DataPolicy.PARTITION, + RegionShortcut.PARTITION_REDUNDANT_OVERFLOW, "residentRegion", "overflowDiskStore")); beanDefinitions.put("residentRegionDiskStore", defineDiskStore("someBean", "yetAnotherBean")); - beanDefinitions.put("residentRegion", definePartitionedRegion(PERSISTENT, "residentRegionDiskStore")); + beanDefinitions.put("residentRegion", definePartitionedRegion(PERSISTENT, null, null, + "residentRegionDiskStore")); beanDefinitions.put("yetAnotherBean", defineBean("org.company.app.domain.YetAnotherBean", "someBean")); + beanDefinitions.put("queue2", defineAsyncEventQueue(PERSISTENT)); + beanDefinitions.put("region3", definePartitionedRegion(PERSISTENT, null, RegionShortcut.PARTITION_PERSISTENT)); + beanDefinitions.put("region4", definePartitionedRegion(null, DataPolicy.PERSISTENT_PARTITION, null, "queue2")); + beanDefinitions.put("region5", defineReplicatedRegion(null, null, RegionShortcut.REPLICATE_PERSISTENT_OVERFLOW)); final ConfigurableListableBeanFactory mockBeanFactory = createMockBeanFactory(beanDefinitions); @@ -180,6 +257,7 @@ public class PdxDiskStoreAwareBeanFactoryPostProcessorTest { assertTrue(isEmpty(beanDefinitions.get("gemfireCache").getDependsOn())); assertTrue(isEmpty(beanDefinitions.get("pdxDiskStore").getDependsOn())); assertTrue(isEmpty(beanDefinitions.get("someOtherBean").getDependsOn())); + assertDependencies(beanDefinitions.get("queue1"), "someOtherBean"); assertDependencies(beanDefinitions.get("overflowDiskStore"), "pdxDiskStore"); assertDependencies(beanDefinitions.get("region1"), "overflowDiskStore"); assertDependencies(beanDefinitions.get("region2DiskStore"), "pdxDiskStore", "someBean"); @@ -188,6 +266,10 @@ public class PdxDiskStoreAwareBeanFactoryPostProcessorTest { assertDependencies(beanDefinitions.get("residentRegionDiskStore"), "pdxDiskStore", "someBean", "yetAnotherBean"); assertDependencies(beanDefinitions.get("residentRegion"), "pdxDiskStore", "residentRegionDiskStore"); assertDependencies(beanDefinitions.get("yetAnotherBean"), "someBean"); + assertDependencies(beanDefinitions.get("queue2"), "pdxDiskStore"); + assertDependencies(beanDefinitions.get("region3"), "pdxDiskStore"); + assertDependencies(beanDefinitions.get("region4"), "pdxDiskStore", "queue2"); + assertDependencies(beanDefinitions.get("region5"), "pdxDiskStore"); } }