From 96cb3775fb40e5a6af7e2f1eda6fa19537a60053 Mon Sep 17 00:00:00 2001 From: John Blum Date: Fri, 15 Aug 2014 15:45:58 -0700 Subject: [PATCH] Implements JIRA improvement SGF-223 enabling Spring Data GemFire to create GemFire Disk Store directory locations automatically (based on nested elements inside the element specified in Spring config) without the user having to create the disk directories for the location manually, unlike GemFire's native configuration options (such as cache.xml). --- .../data/gemfire/DiskStoreFactoryBean.java | 38 +++---- .../config/DiskStoreBeanPostProcessor.java | 104 ++++++++++++++++++ .../data/gemfire/config/DiskStoreParser.java | 24 ++++ .../gemfire/config/spring-gemfire-1.5.xsd | 56 +++++----- .../DiskStoreBeanPostProcessorTest.java | 68 ++++++++++++ ...DiskStoreBeanPostProcessorTest-context.xml | 25 +++++ 6 files changed, 268 insertions(+), 47 deletions(-) create mode 100644 src/main/java/org/springframework/data/gemfire/config/DiskStoreBeanPostProcessor.java create mode 100644 src/test/java/org/springframework/data/gemfire/config/DiskStoreBeanPostProcessorTest.java create mode 100644 src/test/resources/org/springframework/data/gemfire/config/DiskStoreBeanPostProcessorTest-context.xml diff --git a/src/main/java/org/springframework/data/gemfire/DiskStoreFactoryBean.java b/src/main/java/org/springframework/data/gemfire/DiskStoreFactoryBean.java index 6a32a95b..6fd0b6b2 100644 --- a/src/main/java/org/springframework/data/gemfire/DiskStoreFactoryBean.java +++ b/src/main/java/org/springframework/data/gemfire/DiskStoreFactoryBean.java @@ -40,7 +40,7 @@ import com.gemstone.gemfire.cache.GemFireCache; * @see org.springframework.beans.factory.InitializingBean */ @SuppressWarnings("unused") -public class DiskStoreFactoryBean implements FactoryBean, InitializingBean, BeanNameAware { +public class DiskStoreFactoryBean implements BeanNameAware , FactoryBean, InitializingBean { private Boolean allowForceCompaction; private Boolean autoCompact; @@ -66,7 +66,7 @@ public class DiskStoreFactoryBean implements FactoryBean, Initializin @Override public Class getObjectType() { - return DiskStore.class; + return (diskStore != null ? diskStore.getClass() : DiskStore.class); } @Override @@ -84,33 +84,34 @@ public class DiskStoreFactoryBean implements FactoryBean, Initializin if (allowForceCompaction != null) { diskStoreFactory.setAllowForceCompaction(allowForceCompaction); } + if (autoCompact != null) { + diskStoreFactory.setAutoCompact(autoCompact); + } if (compactionThreshold != null) { diskStoreFactory.setCompactionThreshold(compactionThreshold); } - if (autoCompact != null) { - diskStoreFactory.setAutoCompact(autoCompact); + if (maxOplogSize != null) { + diskStoreFactory.setMaxOplogSize(maxOplogSize); } if (queueSize != null) { diskStoreFactory.setQueueSize(queueSize); } - if (writeBufferSize != null) { - diskStoreFactory.setWriteBufferSize(writeBufferSize); - } if (timeInterval != null) { diskStoreFactory.setTimeInterval(timeInterval); } - if (maxOplogSize != null) { - diskStoreFactory.setMaxOplogSize(maxOplogSize); + if (writeBufferSize != null) { + diskStoreFactory.setWriteBufferSize(writeBufferSize); } if (!CollectionUtils.isEmpty(diskDirs)) { File[] diskDirFiles = new File[diskDirs.size()]; int[] diskDirSizes = new int[diskDirs.size()]; - for (int i = 0; i < diskDirs.size(); i++) { - DiskDir diskDir = diskDirs.get(i); - diskDirFiles[i] = new File(diskDir.location); - diskDirSizes[i] = diskDir.maxSize == null ? DiskStoreFactory.DEFAULT_DISK_DIR_SIZE : diskDir.maxSize; + for (int index = 0; index < diskDirs.size(); index++) { + DiskDir diskDir = diskDirs.get(index); + diskDirFiles[index] = new File(diskDir.location); + diskDirSizes[index] = (diskDir.maxSize != null ? diskDir.maxSize + : DiskStoreFactory.DEFAULT_DISK_DIR_SIZE); } diskStoreFactory.setDiskDirsAndSizes(diskDirFiles, diskDirSizes); @@ -118,7 +119,7 @@ public class DiskStoreFactoryBean implements FactoryBean, Initializin diskStore = diskStoreFactory.create(getName()); - Assert.notNull(diskStore, String.format("The DiskStore with name '%1$s' failed to be created successfully.", + Assert.notNull(diskStore, String.format("DiskStore with name '%1$s' failed to be created successfully.", diskStore.getName())); } @@ -178,15 +179,14 @@ public class DiskStoreFactoryBean implements FactoryBean, Initializin final Integer maxSize; final String location; - public DiskDir(String location, int maxSize) { - this.location = location; - this.maxSize = maxSize; - } - public DiskDir(String location) { this.location = location; this.maxSize = null; } + public DiskDir(String location, int maxSize) { + this.location = location; + this.maxSize = maxSize; + } } } diff --git a/src/main/java/org/springframework/data/gemfire/config/DiskStoreBeanPostProcessor.java b/src/main/java/org/springframework/data/gemfire/config/DiskStoreBeanPostProcessor.java new file mode 100644 index 00000000..55d4acca --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/DiskStoreBeanPostProcessor.java @@ -0,0 +1,104 @@ +/* + * 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.config; + +import java.io.File; +import java.lang.reflect.Field; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.data.gemfire.DiskStoreFactoryBean.DiskDir; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; + +/** + * The DiskStoreBeanPostProcessor class post processes any GemFire Disk Store DiskDir Spring beans defined + * in the application context to ensure that the Disk Store directory location (disk-dir) actually exists + * before creating the Disk Store. + * + * @author John Blum + * @see org.springframework.beans.factory.config.BeanPostProcessor + * @see org.springframework.data.gemfire.DiskStoreFactoryBean.DiskDir + * @since 1.5.0 + */ +@SuppressWarnings("unused") +public class DiskStoreBeanPostProcessor implements BeanPostProcessor { + + protected final Log log = LogFactory.getLog(getClass()); + + @Override + public Object postProcessBeforeInitialization(final Object bean, final String beanName) throws BeansException { + if (log.isDebugEnabled()) { + log.debug(String.format("Post Processing Bean (%1$s) of Type (%2$s) with Name (%3$s)...%n", bean, + ObjectUtils.nullSafeClassName(bean), beanName)); + } + + if (bean instanceof DiskDir) { + createIfNotExists((DiskDir) bean); + } + + return bean; + } + + private void createIfNotExists(final DiskDir diskDirectory) { + String location = readField(diskDirectory, "location"); + + File diskDirectoryFile = new File(location); + + Assert.isTrue(diskDirectoryFile.isDirectory() || diskDirectoryFile.mkdirs(), + String.format("Failed to create Disk Directory (%1$s)%n!", location)); + + if (log.isInfoEnabled()) { + log.info(String.format("The Disk Directory either exists or was created @ Location (%1$s).%n", location)); + } + } + + @SuppressWarnings("unchecked") + private T readField(final Object obj, final String fieldName) { + try { + Class type = obj.getClass(); + Field field; + + do { + field = type.getDeclaredField(fieldName); + type = type.getSuperclass(); // traverse up the object class hierarchy + } + while (field == null && !Object.class.equals(type)); + + if (field == null) { + throw new NoSuchFieldException(String.format("Field (%1$s) does not exist on Object of type (%2$s)!", + fieldName, ObjectUtils.nullSafeClassName(obj))); + } + + field.setAccessible(true); + + return (T) field.get(obj); + } + catch (Exception e) { + throw new RuntimeException(String.format("Failed to read field (%1$s) on Object of type (%2$s)!", + fieldName, ObjectUtils.nullSafeClassName(obj)), e); + } + } + + @Override + public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException { + return bean; + } + +} diff --git a/src/main/java/org/springframework/data/gemfire/config/DiskStoreParser.java b/src/main/java/org/springframework/data/gemfire/config/DiskStoreParser.java index 383c7bd9..1b9a94a5 100644 --- a/src/main/java/org/springframework/data/gemfire/config/DiskStoreParser.java +++ b/src/main/java/org/springframework/data/gemfire/config/DiskStoreParser.java @@ -16,9 +16,12 @@ package org.springframework.data.gemfire.config; import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; import org.springframework.beans.factory.support.ManagedList; import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; @@ -33,9 +36,28 @@ import org.w3c.dom.Element; * * @author David Turanski * @author John Blum + * @see org.springframework.beans.factory.support.AbstractBeanDefinition + * @see org.springframework.beans.factory.support.BeanDefinitionBuilder + * @see org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser + * @see org.springframework.data.gemfire.DiskStoreFactoryBean + * @see org.w3c.dom.Element */ public class DiskStoreParser extends AbstractSingleBeanDefinitionParser { + private static final AtomicBoolean registered = new AtomicBoolean(false); + + private static void registerDiskStoreBeanPostProcessor(final ParserContext parserContext) { + if (registered.compareAndSet(false, true)) { + AbstractBeanDefinition diskStoreBeanPostProcessor = BeanDefinitionBuilder + .genericBeanDefinition(DiskStoreBeanPostProcessor.class) + .setRole(BeanDefinition.ROLE_INFRASTRUCTURE) + .getBeanDefinition(); + + BeanDefinitionReaderUtils.registerWithGeneratedName(diskStoreBeanPostProcessor, + parserContext.getRegistry()); + } + } + @Override protected Class getBeanClass(Element element) { return DiskStoreFactoryBean.class; @@ -45,6 +67,8 @@ public class DiskStoreParser extends AbstractSingleBeanDefinitionParser { protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { super.doParse(element, parserContext, builder); + registerDiskStoreBeanPostProcessor(parserContext); + builder.setLazyInit(false); ParsingUtils.setPropertyReference(element, builder, "cache-ref", "cache"); diff --git a/src/main/resources/org/springframework/data/gemfire/config/spring-gemfire-1.5.xsd b/src/main/resources/org/springframework/data/gemfire/config/spring-gemfire-1.5.xsd index 0984d689..dfa7dc1d 100644 --- a/src/main/resources/org/springframework/data/gemfire/config/spring-gemfire-1.5.xsd +++ b/src/main/resources/org/springframework/data/gemfire/config/spring-gemfire-1.5.xsd @@ -1687,6 +1687,13 @@ The maximum size (in megabytes) of data stored in each directory. Default value + + + + + - - - - - - - - - - - - - - - - + +Sets the maximum size in megabytes a single oplog (operation log) is allowed to be. When an oplog is created this +amount of file space will be immediately reserved. + ]]> + + + + + + + + + + diff --git a/src/test/java/org/springframework/data/gemfire/config/DiskStoreBeanPostProcessorTest.java b/src/test/java/org/springframework/data/gemfire/config/DiskStoreBeanPostProcessorTest.java new file mode 100644 index 00000000..79571134 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/config/DiskStoreBeanPostProcessorTest.java @@ -0,0 +1,68 @@ +/* + * 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.config; + +import static org.junit.Assert.assertTrue; + +import java.io.File; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.data.gemfire.test.GemfireTestApplicationContextInitializer; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.util.FileSystemUtils; + +/** + * The DiskStoreBeanPostProcessorTest class is a test suite of test cases testing the contract and functionality of + * the DiskStoreBeanPostProcessor class. + * + * @author John Blum + * @see org.junit.Test + * @see org.junit.runner.RunWith + * @see org.springframework.data.gemfire.config.DiskStoreBeanPostProcessor + * @see org.springframework.test.context.ContextConfiguration + * @see org.springframework.test.context.junit4.SpringJUnit4ClassRunner + * @since 1.5.0 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(initializers = GemfireTestApplicationContextInitializer.class) +public class DiskStoreBeanPostProcessorTest { + + @BeforeClass + public static void testSuiteSetup() { + assertTrue("Failed to created directory './gemfire/disk-stores/local/ds2'!", + new File("./gemfire/disk-stores/local/ds2").mkdirs()); + } + + @AfterClass + public static void testSuiteTearDown() { + assertTrue("Failed to delete directory './gemfire'!", FileSystemUtils.deleteRecursively(new File("./gemfire"))); + assertTrue("Failed to delete directory './gfe'!", FileSystemUtils.deleteRecursively(new File("./gfe"))); + } + + @Test + public void testDiskStoreDirectoryLocationsExist() { + assertTrue(new File("./gemfire/disk-stores/ds1").isDirectory()); + assertTrue(new File("./gemfire/disk-stores/local/ds2").isDirectory()); + assertTrue(new File("./gemfire/disk-stores/remote/ds2").isDirectory()); + assertTrue(new File("./gfe/ds/local/store3").isDirectory()); + } + +} diff --git a/src/test/resources/org/springframework/data/gemfire/config/DiskStoreBeanPostProcessorTest-context.xml b/src/test/resources/org/springframework/data/gemfire/config/DiskStoreBeanPostProcessorTest-context.xml new file mode 100644 index 00000000..a5495500 --- /dev/null +++ b/src/test/resources/org/springframework/data/gemfire/config/DiskStoreBeanPostProcessorTest-context.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + +