From a1eabf8650b8c6414e40d81f0ea5ccc68828755f Mon Sep 17 00:00:00 2001 From: John Blum Date: Wed, 5 Oct 2022 14:59:16 -0700 Subject: [PATCH] Add support for configuring the working directory of the Locator created/started by the LocatorFactoryBean. Resolves #623. --- .../data/gemfire/LocatorFactoryBean.java | 52 ++++++++++++++++++ .../gemfire/LocatorFactoryBeanUnitTests.java | 11 +++- .../LocatorApplicationIntegrationTests.java | 53 ++++++++++++++++++- 3 files changed, 114 insertions(+), 2 deletions(-) diff --git a/spring-data-geode/src/main/java/org/springframework/data/gemfire/LocatorFactoryBean.java b/spring-data-geode/src/main/java/org/springframework/data/gemfire/LocatorFactoryBean.java index c89ec9fa..e27ab5e6 100644 --- a/spring-data-geode/src/main/java/org/springframework/data/gemfire/LocatorFactoryBean.java +++ b/spring-data-geode/src/main/java/org/springframework/data/gemfire/LocatorFactoryBean.java @@ -15,6 +15,7 @@ */ package org.springframework.data.gemfire; +import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -64,6 +65,8 @@ public class LocatorFactoryBean extends AbstractFactoryBeanSupport impl private boolean useBeanFactoryLocator = false; private boolean useClusterConfigurationService = false; + private File workingDirectory; + private Integer port = DEFAULT_PORT; private final List locatorConfigurers = new ArrayList<>(); @@ -120,6 +123,10 @@ public class LocatorFactoryBean extends AbstractFactoryBeanSupport impl getLocators().ifPresent(locators -> locatorBuilder.set(LOCATORS_PROPERTY, locators)); getName().ifPresent(locatorBuilder::setMemberName); + getWorkingDirectory() + .map(File::getAbsolutePath) + .ifPresent(locatorBuilder::setWorkingDirectory); + locatorBuilder.set(LOG_LEVEL_PROPERTY, getLogLevel()); locatorBuilder.setPort(getPort()); @@ -305,4 +312,49 @@ public class LocatorFactoryBean extends AbstractFactoryBeanSupport impl public boolean isUseClusterConfigurationService() { return this.useClusterConfigurationService; } + + /** + * Sets the file system {@link File directory} in which the {@link Locator} will write all files to disk. + * + * This configuration setting should not be taken to mean this is the {@link File directory} in which + * the {@link Locator} process will run, or in other words, the {@link Locator} process's actual + * {@link File working directory}. If this {@link LocatorFactoryBean} is used to create and start + * a {@link Locator}, then the {@link Locator} will run in an embedded mode, or the same JVM process + * as the Spring application that called this {@link LocatorFactoryBean} and started the {@link Locator}) + * and therefore will inherit the same {@link File working directory} as the parent Spring application process. + * + * This configuration property is named after the same + * {@link LocatorLauncher.Builder#getWorkingDirectory() workingDirectory} configuration property + * on the {@link LocatorLauncher.Builder}. + * + * @param workingDirectory {@link File directory} in which the {@link Locator} will write its files to disk. + * @see org.apache.geode.distributed.LocatorLauncher.Builder#setWorkingDirectory(String) + * @see java.io.File + */ + public void setWorkingDirectory(@Nullable File workingDirectory) { + this.workingDirectory = workingDirectory; + } + + /** + * Get the file system {@link File directory} in which the {@link Locator} will write all files to disk. + * + * This configuration setting should not be taken to mean this is the {@link File directory} in which + * the {@link Locator} process will run, or in other words, the {@link Locator} process's actual + * {@link File working directory}. If this {@link LocatorFactoryBean} is used to create and start + * a {@link Locator}, then the {@link Locator} will run in an embedded mode, or the same JVM process + * as the Spring application that called this {@link LocatorFactoryBean} and started the {@link Locator}) + * and therefore will inherit the same {@link File working directory} as the parent Spring application process. + * + * This configuration property is named after the same + * {@link LocatorLauncher.Builder#getWorkingDirectory() workingDirectory} configuration property + * on the {@link LocatorLauncher.Builder}. + * + * @return the {@link File directory} in which the {@link Locator} will write its files to disk. + * @see org.apache.geode.distributed.LocatorLauncher.Builder#getWorkingDirectory() + * @see java.util.Optional + * @see java.io.File + */ + public Optional getWorkingDirectory() { + return Optional.ofNullable(this.workingDirectory); + } } diff --git a/spring-data-geode/src/test/java/org/springframework/data/gemfire/LocatorFactoryBeanUnitTests.java b/spring-data-geode/src/test/java/org/springframework/data/gemfire/LocatorFactoryBeanUnitTests.java index 33e763a1..5116b52b 100644 --- a/spring-data-geode/src/test/java/org/springframework/data/gemfire/LocatorFactoryBeanUnitTests.java +++ b/spring-data-geode/src/test/java/org/springframework/data/gemfire/LocatorFactoryBeanUnitTests.java @@ -31,6 +31,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; +import java.io.File; import java.net.InetAddress; import java.util.Arrays; import java.util.List; @@ -132,6 +133,12 @@ public class LocatorFactoryBeanUnitTests { @Test public void initBuildsLocator() throws Exception { + File workingDirectory = new File(System.getProperty("java.io.tmpdir")); + + assertThat(workingDirectory) + .describedAs("Working directory [%s] does not exist", workingDirectory.getAbsolutePath()) + .isDirectory(); + Locator mockLocator = mock(Locator.class); LocatorLauncher mockLocatorLauncher = mock(LocatorLauncher.class); @@ -150,6 +157,7 @@ public class LocatorFactoryBeanUnitTests { this.locatorFactoryBean.setLocators("host1[1234],host2[6789]"); this.locatorFactoryBean.setName("TestMember"); this.locatorFactoryBean.setPort(54321); + this.locatorFactoryBean.setWorkingDirectory(workingDirectory); this.locatorFactoryBean.init(); assertThat(this.locatorFactoryBean.getLocator()).isEqualTo(mockLocator); @@ -161,6 +169,7 @@ public class LocatorFactoryBeanUnitTests { verify(locatorBuilderSpy, times(1)).setHostnameForClients(eq("skullbox")); verify(locatorBuilderSpy, times(1)).setMemberName(eq("TestMember")); verify(locatorBuilderSpy, times(1)).setPort(eq(54321)); + verify(locatorBuilderSpy, times(1)).setWorkingDirectory(eq(workingDirectory.getAbsolutePath())); verify(this.locatorFactoryBean, times(1)).postProcess(eq(locatorBuilderSpy)); verify(this.locatorFactoryBean, times(1)).postProcess(eq(mockLocatorLauncher)); verify(mockLocatorLauncher, times(1)).start(); @@ -203,7 +212,7 @@ public class LocatorFactoryBeanUnitTests { } @Test - public void getObjectThrowsIllegalStateException() throws Exception { + public void getObjectThrowsIllegalStateException() { assertThatIllegalStateException() .isThrownBy(() -> this.locatorFactoryBean.getObject()) diff --git a/spring-data-geode/src/test/java/org/springframework/data/gemfire/config/annotation/LocatorApplicationIntegrationTests.java b/spring-data-geode/src/test/java/org/springframework/data/gemfire/config/annotation/LocatorApplicationIntegrationTests.java index a76ad667..4ab7e2d4 100644 --- a/spring-data-geode/src/test/java/org/springframework/data/gemfire/config/annotation/LocatorApplicationIntegrationTests.java +++ b/spring-data-geode/src/test/java/org/springframework/data/gemfire/config/annotation/LocatorApplicationIntegrationTests.java @@ -16,8 +16,13 @@ package org.springframework.data.gemfire.config.annotation; import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newRuntimeException; +import java.io.File; +import java.util.Arrays; import java.util.Properties; +import java.util.UUID; +import java.util.function.Function; import org.junit.Before; import org.junit.Test; @@ -29,11 +34,16 @@ import org.apache.geode.distributed.DistributedSystem; import org.apache.geode.distributed.Locator; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; import org.springframework.data.gemfire.GemFireProperties; import org.springframework.data.gemfire.GemfireUtils; +import org.springframework.data.gemfire.LocatorFactoryBean; import org.springframework.data.gemfire.tests.integration.IntegrationTestsSupport; +import org.springframework.data.gemfire.util.SpringExtensions; +import org.springframework.lang.NonNull; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.util.StringUtils; /** * Integration Tests for {@link LocatorApplication} and {@link LocatorApplicationConfiguration} asserting that @@ -112,6 +122,47 @@ public class LocatorApplicationIntegrationTests extends IntegrationTestsSupport bindAddress = "localhost", port = 0 ) - static class TestConfiguration { } + static class TestConfiguration { + @Bean + LocatorConfigurer workingDirectoryLocatorConfigurer() { + + return (beanName, locatorFactoryBean) -> { + + File workingDirectory = newWorkingDirectory(locatorFactoryBean); + + Function workingDirectoryCreationErrorHandler = cause -> { + throw newRuntimeException(cause, "Failed to create temporary working directory [%s]", workingDirectory); + }; + + SpringExtensions.ValueReturningThrowableOperation createWorkingDirectory = () -> { + boolean result = workingDirectory.mkdirs(); + workingDirectory.deleteOnExit(); + return result; + }; + + SpringExtensions.safeGetValue(createWorkingDirectory, workingDirectoryCreationErrorHandler); + + assertThat(workingDirectory).isDirectory(); + + locatorFactoryBean.setWorkingDirectory(workingDirectory); + }; + } + + private String fromTemporaryDirectory(String... pathElements) { + + StringBuilder relativePath = new StringBuilder(System.getProperty("java.io.tmpdir")); + + Arrays.stream(pathElements) + .filter(StringUtils::hasText) + .forEach(pathElement -> relativePath.append(File.separator).append(pathElement)); + + return relativePath.toString(); + } + + private @NonNull File newWorkingDirectory(@NonNull LocatorFactoryBean locatorBean) { + return new File(fromTemporaryDirectory("locator", + locatorBean.getName().orElse("NO-NAME"), UUID.randomUUID().toString())); + } + } }