From e3c3d116873d4919fd2d3a40762a2d3bc490126a Mon Sep 17 00:00:00 2001 From: John Blum Date: Sun, 11 Dec 2016 23:03:04 -0800 Subject: [PATCH] SGF-549 - Configure DiskStores with annotations. (cherry picked from commit c99d6ffbf8251a9fc6e01b7f5df13aae402043dd) Signed-off-by: John Blum --- .../annotation/DiskStoreConfiguration.java | 139 +++++++++ .../annotation/DiskStoresConfiguration.java | 89 ++++++ .../config/annotation/EnableDiskStore.java | 168 ++++++++++ .../config/annotation/EnableDiskStores.java | 80 +++++ ...nableDiskStoresConfigurationUnitTests.java | 289 ++++++++++++++++++ 5 files changed, 765 insertions(+) create mode 100644 src/main/java/org/springframework/data/gemfire/config/annotation/DiskStoreConfiguration.java create mode 100644 src/main/java/org/springframework/data/gemfire/config/annotation/DiskStoresConfiguration.java create mode 100644 src/main/java/org/springframework/data/gemfire/config/annotation/EnableDiskStore.java create mode 100644 src/main/java/org/springframework/data/gemfire/config/annotation/EnableDiskStores.java create mode 100644 src/test/java/org/springframework/data/gemfire/config/annotation/EnableDiskStoresConfigurationUnitTests.java diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/DiskStoreConfiguration.java b/src/main/java/org/springframework/data/gemfire/config/annotation/DiskStoreConfiguration.java new file mode 100644 index 00000000..beb13bd4 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/DiskStoreConfiguration.java @@ -0,0 +1,139 @@ +/* + * Copyright 2016 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.annotation; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.ManagedList; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.data.gemfire.DiskStoreFactoryBean; +import org.springframework.data.gemfire.config.xml.GemfireConstants; +import org.springframework.data.gemfire.util.ArrayUtils; + +/** + * The {@link DiskStoreConfiguration} class is a Spring {@link ImportBeanDefinitionRegistrar} used to register + * a GemFire/Geode {@link org.apache.geode.cache.DiskStore} bean definition. + * + * @author John Blum + * @see org.springframework.beans.factory.config.BeanDefinition + * @see org.springframework.beans.factory.support.BeanDefinitionBuilder + * @see org.springframework.beans.factory.support.BeanDefinitionRegistry + * @see org.springframework.context.annotation.ImportBeanDefinitionRegistrar + * @see org.springframework.data.gemfire.DiskStoreFactoryBean + * @see org.springframework.data.gemfire.config.annotation.EnableDiskStore + * @see org.springframework.data.gemfire.config.annotation.EnableDiskStores + * @see org.apache.geode.cache.DiskStore + * @since 1.9.0 + */ +public class DiskStoreConfiguration implements ImportBeanDefinitionRegistrar { + + /** + * @inheritDoc + */ + @Override + public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { + if (importingClassMetadata.hasAnnotation(EnableDiskStore.class.getName())) { + AnnotationAttributes enableDiskStoreAttributes = AnnotationAttributes.fromMap( + importingClassMetadata.getAnnotationAttributes(EnableDiskStore.class.getName())); + + registerDiskStoreBeanDefinition(importingClassMetadata, enableDiskStoreAttributes, registry); + } + } + + /* (non-Javadoc) */ + protected void registerDiskStoreBeanDefinition(AnnotationMetadata importingClassMetadata, + AnnotationAttributes enableDiskStoreAttributes, BeanDefinitionRegistry registry) { + + BeanDefinitionBuilder diskStoreFactoryBeanBuilder = + BeanDefinitionBuilder.genericBeanDefinition(DiskStoreFactoryBean.class); + + String diskStoreName = enableDiskStoreAttributes.getString("name"); + + diskStoreFactoryBeanBuilder.addPropertyValue("beanName", diskStoreName); + + diskStoreFactoryBeanBuilder.addPropertyReference("cache", GemfireConstants.DEFAULT_GEMFIRE_CACHE_NAME); + + setPropertyValueIfNotDefault(diskStoreFactoryBeanBuilder, "allowForceCompaction", + enableDiskStoreAttributes.getBoolean("allowForceCompaction"), false); + + setPropertyValueIfNotDefault(diskStoreFactoryBeanBuilder, "autoCompact", + enableDiskStoreAttributes.getBoolean("autoCompact"), false); + + setPropertyValueIfNotDefault(diskStoreFactoryBeanBuilder, "compactionThreshold", + enableDiskStoreAttributes.getNumber("compactionThreshold"), 50); + + setPropertyValueIfNotDefault(diskStoreFactoryBeanBuilder, "diskUsageCriticalPercentage", + enableDiskStoreAttributes.getNumber("diskUsageCriticalPercentage"), 99.0f); + + setPropertyValueIfNotDefault(diskStoreFactoryBeanBuilder, "diskUsageWarningPercentage", + enableDiskStoreAttributes.getNumber("diskUsageWarningPercentage"), 90.0f); + + setPropertyValueIfNotDefault(diskStoreFactoryBeanBuilder, "maxOplogSize", + enableDiskStoreAttributes.getNumber("maxOplogSize"), 1024L); + + setPropertyValueIfNotDefault(diskStoreFactoryBeanBuilder, "queueSize", + enableDiskStoreAttributes.getNumber("queueSize"), 0); + + setPropertyValueIfNotDefault(diskStoreFactoryBeanBuilder, "timeInterval", + enableDiskStoreAttributes.getNumber("timeInterval"), 1000L); + + setPropertyValueIfNotDefault(diskStoreFactoryBeanBuilder, "writeBufferSize", + enableDiskStoreAttributes.getNumber("writeBufferSize"), 32768); + + parseDiskStoreDiskDirectories(importingClassMetadata, enableDiskStoreAttributes, diskStoreFactoryBeanBuilder); + + registry.registerBeanDefinition(diskStoreName, diskStoreFactoryBeanBuilder.getBeanDefinition()); + } + + /* (non-Javadoc) */ + protected BeanDefinitionBuilder parseDiskStoreDiskDirectories(AnnotationMetadata importingClassMetadata, + AnnotationAttributes enableDiskStoreAttributes, BeanDefinitionBuilder diskStoreBeanFactoryBuilder) { + + AnnotationAttributes[] diskDirectories = ArrayUtils.nullSafeArray( + enableDiskStoreAttributes.getAnnotationArray("diskDirectories"), AnnotationAttributes.class); + + ManagedList diskDirectoryBeans = new ManagedList(diskDirectories.length); + + for (AnnotationAttributes diskDirectoryAttributes : diskDirectories) { + BeanDefinitionBuilder diskDirectoryBuilder = + BeanDefinitionBuilder.genericBeanDefinition(DiskStoreFactoryBean.DiskDir.class); + + diskDirectoryBuilder.addConstructorArgValue(diskDirectoryAttributes.getString("location")); + diskDirectoryBuilder.addConstructorArgValue(diskDirectoryAttributes.getNumber("maxSize")); + + diskDirectoryBeans.add(diskDirectoryBuilder.getBeanDefinition()); + } + + if (!diskDirectoryBeans.isEmpty()) { + diskStoreBeanFactoryBuilder.addPropertyValue("diskDirs", diskDirectoryBeans); + } + + return diskStoreBeanFactoryBuilder; + } + + /* (non-Javadoc) */ + private BeanDefinitionBuilder setPropertyValueIfNotDefault(BeanDefinitionBuilder beanDefinitionBuilder, + String propertyName, T value, T defaultValue) { + + return (value != null && !value.equals(defaultValue) ? + beanDefinitionBuilder.addPropertyValue(propertyName, value) : beanDefinitionBuilder); + } +} diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/DiskStoresConfiguration.java b/src/main/java/org/springframework/data/gemfire/config/annotation/DiskStoresConfiguration.java new file mode 100644 index 00000000..4a4e2e04 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/DiskStoresConfiguration.java @@ -0,0 +1,89 @@ +/* + * Copyright 2016 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.annotation; + +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.data.gemfire.util.ArrayUtils; + +/** + * The {@link DiskStoresConfiguration} class is a Spring {@link org.springframework.context.annotation.ImportBeanDefinitionRegistrar} + * used to register multiple GemFire/Geode {@link org.apache.geode.cache.DiskStore} bean definitions. + * + * @author John Blum + * @see org.springframework.beans.factory.support.BeanDefinitionRegistry + * @see org.springframework.data.gemfire.config.annotation.DiskStoreConfiguration + * @see org.springframework.data.gemfire.config.annotation.EnableDiskStore + * @see org.springframework.data.gemfire.config.annotation.EnableDiskStores + * @see org.apache.geode.cache.DiskStore + * @since 1.9.0 + */ +public class DiskStoresConfiguration extends DiskStoreConfiguration { + + /** + * @inheritDoc + */ + @Override + public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { + if (importingClassMetadata.hasAnnotation(EnableDiskStores.class.getName())) { + AnnotationAttributes enableDiskStoresAttributes = AnnotationAttributes.fromMap( + importingClassMetadata.getAnnotationAttributes(EnableDiskStores.class.getName())); + + AnnotationAttributes[] diskStores = ArrayUtils.nullSafeArray( + enableDiskStoresAttributes.getAnnotationArray("diskStores"), AnnotationAttributes.class); + + for (AnnotationAttributes diskStoreAttributes : diskStores) { + registerDiskStoreBeanDefinition(importingClassMetadata, + mergeDiskStoreAttributes(enableDiskStoresAttributes, diskStoreAttributes), registry); + } + } + } + + /* (non-Javadoc) */ + protected AnnotationAttributes mergeDiskStoreAttributes(AnnotationAttributes enableDiskStoresAttributes, + AnnotationAttributes diskStoreAttributes) { + + setAttributeIfNotDefault(diskStoreAttributes, "autoCompact", + enableDiskStoresAttributes.getBoolean("autoCompact"), false); + + setAttributeIfNotDefault(diskStoreAttributes, "compactionThreshold", + enableDiskStoresAttributes.getNumber("compactionThreshold"), 50); + + setAttributeIfNotDefault(diskStoreAttributes, "maxOplogSize", + enableDiskStoresAttributes.getNumber("maxOplogSize"), 1024); + + return diskStoreAttributes; + } + + /* (non-Javadoc) */ + private void setAttributeIfNotDefault(AnnotationAttributes diskStoreAttributes, + String attributeName, T newValue, T defaultValue) { + + if (!diskStoreAttributes.containsKey(attributeName) + || toString(diskStoreAttributes.get(attributeName)).equals(toString(defaultValue))) { + + diskStoreAttributes.put(attributeName, newValue); + } + } + + /* (non-Javadoc) */ + private String toString(Object value) { + return String.valueOf(value); + } +} diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/EnableDiskStore.java b/src/main/java/org/springframework/data/gemfire/config/annotation/EnableDiskStore.java new file mode 100644 index 00000000..e060a09a --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/EnableDiskStore.java @@ -0,0 +1,168 @@ +/* + * Copyright 2016 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.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.context.annotation.Import; +import org.springframework.core.annotation.AliasFor; + +/** + * The {@link EnableDiskStore} annotation marks a Spring {@link org.springframework.context.annotation.Configuration @Configuration} + * annotated application class to configure a single GemFire/Geode {@link org.apache.geode.cache.DiskStore} bean + * in the Spring context in which to persist or overflow data from 1 or more GemFire/Geode + * {@link org.apache.geode.cache.Region Regions} + * + * @author John Blum + * @see org.springframework.context.annotation.Import + * @see org.springframework.core.annotation.AliasFor + * @see org.springframework.data.gemfire.config.annotation.DiskStoreConfiguration + * @see org.springframework.data.gemfire.config.annotation.EnableDiskStores + * @see org.apache.geode.cache.DiskStore + * @see org.apache.geode.cache.Region + * @since 1.9.0 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +@Import(DiskStoreConfiguration.class) +@SuppressWarnings({ "unused" }) +public @interface EnableDiskStore { + + /** + * Name of the {@link org.apache.geode.cache.DiskStore}. + */ + @AliasFor(attribute = "name") + String value() default ""; + + /** + * Name of the {@link org.apache.geode.cache.DiskStore}. + */ + @AliasFor(attribute = "value") + String name() default ""; + + /** + * Set to true to allow disk compaction to be forced on this disk store. + * + * Default is {@literal false}. + */ + boolean allowForceCompaction() default false; + + /** + * Set to true to automatically compact the disk files. + * + * Default is {@literal false}. + */ + boolean autoCompact() default false; + + /** + * The threshold at which an oplog will become compactable. Until it reaches this threshold the oplog + * will not be compacted. + * + * The threshold is a percentage in the range 0 to 100. + * + * Defaults to {@literal 50} percent. + */ + int compactionThreshold() default 50; + + /** + * File system directory location(s) in which the {@link org.apache.geode.cache.DiskStore} files are stored. + * + * Defaults to current working directory with 2 petabytes of storage capacity maximum size. + */ + DiskDirectory[] diskDirectories() default {}; + + /** + * Disk usage above this threshold generates an error message and shuts down the member's cache. + * + * For example, if the threshold is set to 99%, then falling under 10 GB of free disk space on a 1 TB drive + * generates the error and shuts down the cache. + * + * Set to "0" (zero) to disable. + * + * Defaults to {@literal 99} percent. + */ + float diskUsageCriticalPercentage() default 99.0f; + + /** + * Disk usage above this threshold generates a warning message. + * + * For example, if the threshold is set to 90%, then on a 1 TB drive falling under 100 GB of free disk space + * generates the warning. + * + * Set to "0" (zero) to disable. + * + * Defaults to {@literal 90} percent. + */ + float diskUsageWarningPercentage() default 90.0f; + + /** + * The maximum size, in megabytes, of an oplog (operation log) file. + * + * Defaults to {@literal 1024} MB. + */ + long maxOplogSize() default 1024L; + + /** + * Maximum number of operations that can be asynchronously queued to be written to disk. + * + * Defaults to {@literal 0} (unlimited). + */ + int queueSize() default 0; + + /** + * The number of milliseconds that can elapse before unwritten data is written to disk. + * + * Defaults to {@literal 1000} ms. + */ + long timeInterval() default 1000L; + + /** + * The size of the write buffer that this disk store uses when writing data to disk. + * + * Larger values may increase performance but use more memory. The disk store allocates + * one direct memory buffer of this size. + * + * Defaults to {@literal 32768} bytes. + */ + int writeBufferSize() default 32768; + + @interface DiskDirectory { + + /** + * File system directory location of the {@link org.apache.geode.cache.DiskStore} files. + * + * Defaults to current working directory. + */ + String location() default "."; + + /** + * Maximum amount of space to use for the disk store, in megabytes. + * + * Defaults to {@literal 2} petabytes. + */ + int maxSize() default Integer.MAX_VALUE; + + } +} diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/EnableDiskStores.java b/src/main/java/org/springframework/data/gemfire/config/annotation/EnableDiskStores.java new file mode 100644 index 00000000..9aa1612c --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/EnableDiskStores.java @@ -0,0 +1,80 @@ +/* + * Copyright 2016 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.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.context.annotation.Import; + +/** + * The {@link EnableDiskStores} annotation marks a Spring {@link org.springframework.context.annotation.Configuration @Configuration} + * annotated application class to configure 1 or more GemFire/Geode {@link org.apache.geode.cache.DiskStore} beans + * in the Spring context in which to persist or overflow data from 1 or more GemFire/Geode + * {@link org.apache.geode.cache.Region Regions} + * + * @author John Blum + * @see org.springframework.context.annotation.Import + * @see org.springframework.data.gemfire.config.annotation.DiskStoresConfiguration + * @see org.springframework.data.gemfire.config.annotation.EnableDiskStore + * @see org.apache.geode.cache.DiskStore + * @see org.apache.geode.cache.Region + * @since 1.9.0 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +@Import(DiskStoresConfiguration.class) +@SuppressWarnings({ "unused" }) +public @interface EnableDiskStores { + + /** + * Set to true to automatically compact the disk files. + * + * Default is {@literal false}. + */ + boolean autoCompact() default false; + + /** + * The threshold at which an oplog will become compactable. Until it reaches this threshold the oplog + * will not be compacted. + * + * The threshold is a percentage in the range 0 to 100. + * + * Defaults to {@literal 50} percent. + */ + int compactionThreshold() default 50; + + /** + * The maximum size, in megabytes, of an oplog (operation log) file. + * + * Defaults to {@literal 1024} MB. + */ + long maxOplogSize() default 1024L; + + /** + * Defines 1 or more GemFire/Geode {@link org.apache.geode.cache.DiskStore DiskStores}. + */ + EnableDiskStore[] diskStores(); + +} diff --git a/src/test/java/org/springframework/data/gemfire/config/annotation/EnableDiskStoresConfigurationUnitTests.java b/src/test/java/org/springframework/data/gemfire/config/annotation/EnableDiskStoresConfigurationUnitTests.java new file mode 100644 index 00000000..246af024 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/config/annotation/EnableDiskStoresConfigurationUnitTests.java @@ -0,0 +1,289 @@ +/* + * Copyright 2016 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.annotation; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Matchers.anyFloat; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyLong; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import org.apache.geode.cache.Cache; +import org.apache.geode.cache.DiskStore; +import org.apache.geode.cache.DiskStoreFactory; +import org.junit.After; +import org.junit.Test; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Unit tests for the {@link EnableDiskStore} and {@link EnableDiskStores} annotations as well as + * the {@link DiskStoreConfiguration} and {@link DiskStoresConfiguration} classes. + * + * @author John Blum + * @see org.junit.Test + * @see org.mockito.Mockito + * @since 1.9.0 + */ +public class EnableDiskStoresConfigurationUnitTests { + + private static final AtomicInteger MOCK_ID = new AtomicInteger(0); + + private ConfigurableApplicationContext applicationContext; + + @After + public void tearDown() { + if (applicationContext != null) { + applicationContext.close(); + } + } + + /* (non-Javadoc) */ + protected void assertDiskStore(DiskStore diskStore, String name, boolean allowForceCompaction, boolean autoCompact, + int compactionThreshold, float diskUsageCriticalPercentage, float diskUsageWarningPercentage, + long maxOplogSize, int queueSize, long timeInterval, int writeBufferSize) { + + assertThat(diskStore).isNotNull(); + assertThat(diskStore.getName()).isEqualTo(name); + assertThat(diskStore.getAllowForceCompaction()).isEqualTo(allowForceCompaction); + assertThat(diskStore.getAutoCompact()).isEqualTo(autoCompact); + assertThat(diskStore.getCompactionThreshold()).isEqualTo(compactionThreshold); + assertThat(diskStore.getDiskUsageCriticalPercentage()).isEqualTo(diskUsageCriticalPercentage); + assertThat(diskStore.getDiskUsageWarningPercentage()).isEqualTo(diskUsageWarningPercentage); + assertThat(diskStore.getMaxOplogSize()).isEqualTo(maxOplogSize); + assertThat(diskStore.getQueueSize()).isEqualTo(queueSize); + assertThat(diskStore.getTimeInterval()).isEqualTo(timeInterval); + assertThat(diskStore.getWriteBufferSize()).isEqualTo(writeBufferSize); + } + + /* (non-Javadoc) */ + protected void assertDiskStoreDirectoryLocations(DiskStore diskStore, File... diskDirectories) { + assertThat(diskStore).isNotNull(); + + File[] diskStoreDirectories = diskStore.getDiskDirs(); + + assertThat(diskStoreDirectories).isNotNull(); + assertThat(diskStoreDirectories.length).isEqualTo(diskDirectories.length); + + int index = 0; + + for (File diskDirectory : diskDirectories) { + assertThat(diskStoreDirectories[index++]).isEqualTo(diskDirectory); + } + } + + /* (non-Javadoc) */ + protected void assertDiskStoreDirectorySizes(DiskStore diskStore, int... diskDirectorySizes) { + assertThat(diskStore).isNotNull(); + + int[] diskStoreDirectorySizes = diskStore.getDiskDirSizes(); + + assertThat(diskStoreDirectorySizes).isNotNull(); + assertThat(diskStoreDirectorySizes.length).isEqualTo(diskDirectorySizes.length); + + int index = 0; + + for (int size : diskDirectorySizes) { + assertThat(diskStoreDirectorySizes[index++]).isEqualTo(size); + } + } + + /* (non-Javadoc) */ + protected File newFile(String location) { + return new File(location); + } + + /* (non-Javadoc) */ + protected ConfigurableApplicationContext newApplicationContext(Class... annotatedClasses) { + ConfigurableApplicationContext applicationContext = new AnnotationConfigApplicationContext(annotatedClasses); + applicationContext.registerShutdownHook(); + return applicationContext; + } + + @Test + public void enableSingleDiskStore() { + applicationContext = newApplicationContext(SingleDiskStoreConfiguration.class); + + DiskStore testDiskStore = applicationContext.getBean("TestDiskStore", DiskStore.class); + + assertDiskStore(testDiskStore, "TestDiskStore", true, true, 75, 95.0f, 75.0f, 8192L, 100, 2000L, 65536); + assertDiskStoreDirectoryLocations(testDiskStore, newFile("/absolute/path/to/gemfire/disk/directory"), + newFile("relative/path/to/gemfire/disk/directory")); + assertDiskStoreDirectorySizes(testDiskStore, 1024, 4096); + } + + @Test + public void enablesMultipleDiskStores() { + applicationContext = newApplicationContext(MultipleDiskStoresConfiguration.class); + + DiskStore testDiskStoreOne = applicationContext.getBean("TestDiskStoreOne", DiskStore.class); + + assertDiskStore(testDiskStoreOne, "TestDiskStoreOne", false, true, 75, 99.0f, 90.0f, 2048L, 100, 1000L, 32768); + + DiskStore testDiskStoreTwo = applicationContext.getBean("TestDiskStoreTwo", DiskStore.class); + + assertDiskStore(testDiskStoreTwo, "TestDiskStoreTwo", true, true, 85, 99.0f, 90.0f, 4096L, 0, 1000L, 32768); + } + + static String mockName(String baseName) { + return String.format("%s%d", baseName, MOCK_ID.incrementAndGet()); + } + + /* (non-Javadoc) */ + protected static Answer newGetter(AtomicReference returnValue) { + return invocation -> returnValue.get(); + } + + /* (non-Javadoc) */ + protected static Answer newSetter(Class parameterType, AtomicReference argument, R returnValue) { + return invocation -> { + argument.set(invocation.getArgumentAt(0, parameterType)); + return returnValue; + }; + } + + @Configuration + @SuppressWarnings("unused") + static class CacheConfiguration { + + @Bean + Cache gemfireCache() { + Cache mockCache = mock(Cache.class); + + when(mockCache.createDiskStoreFactory()).thenAnswer(new Answer() { + @Override + public DiskStoreFactory answer(InvocationOnMock invocation) throws Throwable { + final DiskStoreFactory mockDiskStoreFactory = + mock(DiskStoreFactory.class, mockName("MockDiskStoreFactory")); + + final AtomicReference allowForceCompaction = new AtomicReference<>(false); + final AtomicReference autoCompact = new AtomicReference<>(false); + final AtomicReference compactionThreshold = new AtomicReference<>(50); + final AtomicReference diskDirectories = new AtomicReference<>(new File[0]); + final AtomicReference diskDirectorySizes = new AtomicReference<>(new int[0]); + final AtomicReference diskUsageCriticalPercentage = new AtomicReference<>(99.0f); + final AtomicReference diskUsageWarningPercentage = new AtomicReference<>(90.0f); + final AtomicReference maxOplogSize = new AtomicReference<>(1024L); + final AtomicReference queueSize = new AtomicReference<>(0); + final AtomicReference timeInterval = new AtomicReference<>(1000L); + final AtomicReference writeBufferSize = new AtomicReference<>(32768); + + when(mockDiskStoreFactory.setAllowForceCompaction(anyBoolean())).thenAnswer( + newSetter(Boolean.TYPE, allowForceCompaction, mockDiskStoreFactory)); + + when(mockDiskStoreFactory.setAutoCompact(anyBoolean())).thenAnswer( + newSetter(Boolean.TYPE, autoCompact, mockDiskStoreFactory)); + + when(mockDiskStoreFactory.setCompactionThreshold(anyInt())).thenAnswer( + newSetter(Integer.TYPE, compactionThreshold, mockDiskStoreFactory)); + + when(mockDiskStoreFactory.setDiskDirsAndSizes(any(File[].class), any(int[].class))).thenAnswer( + new Answer() { + @Override public DiskStoreFactory answer(InvocationOnMock invocation) throws Throwable { + File[] localDiskDirectories = invocation.getArgumentAt(0, File[].class); + int[] localDiskDirectorySizes = invocation.getArgumentAt(1, int[].class); + + diskDirectories.set(localDiskDirectories); + diskDirectorySizes.set(localDiskDirectorySizes); + + return mockDiskStoreFactory; + } + } + ); + + when(mockDiskStoreFactory.setDiskUsageCriticalPercentage(anyFloat())).thenAnswer( + newSetter(Float.TYPE, diskUsageCriticalPercentage, mockDiskStoreFactory)); + + when(mockDiskStoreFactory.setDiskUsageWarningPercentage(anyFloat())).thenAnswer( + newSetter(Float.TYPE, diskUsageWarningPercentage, mockDiskStoreFactory)); + + when(mockDiskStoreFactory.setMaxOplogSize(anyLong())).thenAnswer( + newSetter(Long.TYPE, maxOplogSize, mockDiskStoreFactory)); + + when(mockDiskStoreFactory.setQueueSize(anyInt())).thenAnswer( + newSetter(Integer.TYPE, queueSize, mockDiskStoreFactory)); + + when(mockDiskStoreFactory.setTimeInterval(anyLong())).thenAnswer( + newSetter(Long.TYPE, timeInterval, mockDiskStoreFactory)); + + when(mockDiskStoreFactory.setWriteBufferSize(anyInt())).thenAnswer( + newSetter(Integer.TYPE, writeBufferSize, mockDiskStoreFactory)); + + when(mockDiskStoreFactory.create(anyString())).thenAnswer(new Answer() { + @Override + public DiskStore answer(InvocationOnMock invocation) throws Throwable { + String diskStoreName = invocation.getArgumentAt(0, String.class); + + DiskStore mockDiskStore = mock(DiskStore.class, diskStoreName); + + when(mockDiskStore.getAllowForceCompaction()).thenAnswer(newGetter(allowForceCompaction)); + when(mockDiskStore.getAutoCompact()).thenAnswer(newGetter(autoCompact)); + when(mockDiskStore.getCompactionThreshold()).thenAnswer(newGetter(compactionThreshold)); + when(mockDiskStore.getDiskDirs()).thenAnswer(newGetter(diskDirectories)); + when(mockDiskStore.getDiskDirSizes()).thenAnswer(newGetter(diskDirectorySizes)); + when(mockDiskStore.getDiskUsageCriticalPercentage()).thenAnswer(newGetter(diskUsageCriticalPercentage)); + when(mockDiskStore.getDiskUsageWarningPercentage()).thenAnswer(newGetter(diskUsageWarningPercentage)); + when(mockDiskStore.getMaxOplogSize()).thenAnswer(newGetter(maxOplogSize)); + when(mockDiskStore.getName()).thenReturn(diskStoreName); + when(mockDiskStore.getQueueSize()).thenAnswer(newGetter(queueSize)); + when(mockDiskStore.getTimeInterval()).thenAnswer(newGetter(timeInterval)); + when(mockDiskStore.getWriteBufferSize()).thenAnswer(newGetter(writeBufferSize)); + + return mockDiskStore; + } + }); + + return mockDiskStoreFactory; + } + }); + + return mockCache; + } + } + + @EnableDiskStore(name = "TestDiskStore", allowForceCompaction = true, autoCompact = true, compactionThreshold = 75, + diskUsageCriticalPercentage = 95.0f, diskUsageWarningPercentage = 75.0f, maxOplogSize = 8192L, queueSize = 100, + timeInterval = 2000L, writeBufferSize = 65536, diskDirectories = { + @EnableDiskStore.DiskDirectory(location = "/absolute/path/to/gemfire/disk/directory", maxSize = 1024), + @EnableDiskStore.DiskDirectory(location = "relative/path/to/gemfire/disk/directory", maxSize = 4096) + }) + static class SingleDiskStoreConfiguration extends CacheConfiguration { + + } + + @EnableDiskStores(autoCompact = true, compactionThreshold = 75, maxOplogSize = 2048L, diskStores = { + @EnableDiskStore(name = "TestDiskStoreOne", queueSize = 100), + @EnableDiskStore(name = "TestDiskStoreTwo", allowForceCompaction = true, + compactionThreshold = 85, maxOplogSize = 4096) + }) + static class MultipleDiskStoresConfiguration extends CacheConfiguration { + + } +}