Implements JIRA feature request SGF-226 enabling Spring Data GemFire to retrieve the shared, persistent cluster configuration from a GemFire 8 Locator Shared Configuration service when the SDG configured peer cache member joins the cluster.
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* 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.assertTrue;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javax.annotation.Resource;
|
||||
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.data.gemfire.fork.LocatorProcess;
|
||||
import org.springframework.data.gemfire.process.ProcessExecutor;
|
||||
import org.springframework.data.gemfire.process.ProcessWrapper;
|
||||
import org.springframework.data.gemfire.test.support.ThreadUtils;
|
||||
import org.springframework.data.gemfire.test.support.ZipUtils;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.util.FileSystemUtils;
|
||||
|
||||
import com.gemstone.gemfire.cache.DataPolicy;
|
||||
import com.gemstone.gemfire.cache.Region;
|
||||
import com.gemstone.gemfire.cache.Scope;
|
||||
|
||||
/**
|
||||
* The CacheUsingSharedConfigurationIntegrationTest class is a test suite of test cases testing the integration of
|
||||
* Spring Data GemFire with GemFire 8's new shared, persistent, cluster configuration service.
|
||||
*
|
||||
* @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.5.0
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration("cacheUsingSharedConfigurationIntegrationTest.xml")
|
||||
@SuppressWarnings("unused")
|
||||
public class CacheUsingSharedConfigurationIntegrationTest {
|
||||
|
||||
@Resource(name = "SharedConfigRegion")
|
||||
private Region<Long, String> sharedConfigRegion;
|
||||
|
||||
@Resource(name = "NativeLocalRegion")
|
||||
private Region<Integer, String> nativeLocalRegion;
|
||||
|
||||
@Resource(name = "NativePartitionRegion")
|
||||
private Region<Integer, String> nativePartitionRegion;
|
||||
|
||||
@Resource(name = "NativeReplicateRegion")
|
||||
private Region<Integer, String> nativeReplicateRegion;
|
||||
|
||||
@Resource(name = "LocalRegion")
|
||||
private Region<Integer, String> localRegion;
|
||||
|
||||
private static File locatorWorkingDirectory;
|
||||
|
||||
private static ProcessWrapper locatorProcess;
|
||||
|
||||
@BeforeClass
|
||||
public static void testSuiteSetup() throws IOException {
|
||||
String locatorName = "SharedConfigLocator";
|
||||
|
||||
locatorWorkingDirectory = new File(System.getProperty("user.dir"), locatorName.toLowerCase());
|
||||
|
||||
assertTrue(locatorWorkingDirectory.isDirectory() || locatorWorkingDirectory.mkdirs());
|
||||
|
||||
ZipUtils.unzip(new ClassPathResource("/shared_config.zip"), locatorWorkingDirectory);
|
||||
|
||||
List<String> arguments = new ArrayList<String>();
|
||||
|
||||
arguments.add("-Dgemfire.name=" + locatorName);
|
||||
arguments.add("-Dspring.gemfire.enable-shared-configuration=true");
|
||||
arguments.add("-Dspring.gemfire.load-shared-configuration=true");
|
||||
|
||||
locatorProcess = ProcessExecutor.launch(locatorWorkingDirectory, LocatorProcess.class,
|
||||
arguments.toArray(new String[arguments.size()]));
|
||||
|
||||
locatorProcess.registerShutdownHook();
|
||||
|
||||
waitForLocatorStart(TimeUnit.SECONDS.toMillis(30));
|
||||
|
||||
//System.out.println("Shared Configuration Locator should be running!");
|
||||
}
|
||||
|
||||
private static void waitForLocatorStart(final long milliseconds) {
|
||||
ThreadUtils.timedWait(milliseconds, 500, new ThreadUtils.WaitCondition() {
|
||||
File pidControlFile = new File(locatorWorkingDirectory, LocatorProcess.getLocatorProcessControlFilename());
|
||||
@Override public boolean waiting() {
|
||||
return !pidControlFile.isFile();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void testSuiteTearDown() {
|
||||
locatorProcess.shutdown();
|
||||
FileSystemUtils.deleteRecursively(locatorWorkingDirectory);
|
||||
}
|
||||
|
||||
protected Region assertRegion(final Region actualRegion, final String expectedRegionName) {
|
||||
return assertRegion(actualRegion, expectedRegionName, Region.SEPARATOR+expectedRegionName);
|
||||
}
|
||||
|
||||
protected Region assertRegion(final Region actualRegion, final String expectedRegionName, final String expectedRegionFullPath) {
|
||||
assertNotNull(String.format("The '%1$s' was not properly configured and initialized!", expectedRegionName), actualRegion);
|
||||
assertEquals(expectedRegionName, actualRegion.getName());
|
||||
assertEquals(expectedRegionFullPath, actualRegion.getFullPath());
|
||||
return actualRegion;
|
||||
}
|
||||
|
||||
protected Region assertRegionAttributes(final Region actualRegion, final DataPolicy expectedDataPolicy, final Scope expectedScope) {
|
||||
assertNotNull(actualRegion);
|
||||
assertNotNull(actualRegion.getAttributes());
|
||||
assertEquals(expectedDataPolicy, actualRegion.getAttributes().getDataPolicy());
|
||||
assertEquals(expectedScope, actualRegion.getAttributes().getScope());
|
||||
return actualRegion;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConfiguration() {
|
||||
assertRegionAttributes(assertRegion(sharedConfigRegion, "SharedConfigRegion"),
|
||||
DataPolicy.PARTITION, Scope.DISTRIBUTED_NO_ACK);
|
||||
|
||||
assertRegionAttributes(assertRegion(nativeLocalRegion, "NativeLocalRegion"), DataPolicy.NORMAL, Scope.LOCAL);
|
||||
|
||||
assertRegionAttributes(assertRegion(nativePartitionRegion, "NativePartitionRegion"),
|
||||
DataPolicy.PARTITION, Scope.DISTRIBUTED_NO_ACK);
|
||||
|
||||
assertRegionAttributes(assertRegion(nativeReplicateRegion, "NativeReplicateRegion"),
|
||||
DataPolicy.REPLICATE, Scope.DISTRIBUTED_ACK);
|
||||
|
||||
assertRegionAttributes(assertRegion(localRegion, "LocalRegion"), DataPolicy.NORMAL, Scope.LOCAL);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -27,6 +27,9 @@ import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.springframework.data.gemfire.fork.CacheServerProcess;
|
||||
import org.springframework.data.gemfire.test.support.IOUtils;
|
||||
import org.springframework.data.gemfire.test.support.ThreadUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -34,7 +37,9 @@ import org.springframework.util.StringUtils;
|
||||
*
|
||||
* @author Costin Leau
|
||||
* @author John Blum
|
||||
* @deprecated ForkUtils has serious design flaws; please use ProcessExecutor instead
|
||||
*/
|
||||
@Deprecated
|
||||
public class ForkUtil {
|
||||
|
||||
private static OutputStream processStandardInStream;
|
||||
@@ -47,13 +52,11 @@ public class ForkUtil {
|
||||
private static OutputStream cloneJVM(final String arguments) {
|
||||
String[] args = arguments.split("\\s+");
|
||||
|
||||
List<String> command = new ArrayList<String>(args.length + 5);
|
||||
List<String> command = new ArrayList<String>(args.length + 3);
|
||||
|
||||
command.add(JAVA_EXE);
|
||||
command.add("-classpath");
|
||||
command.add(JAVA_CLASSPATH);
|
||||
//command.add("-Xms256m");
|
||||
//command.add("-Xmx512m");
|
||||
command.addAll(Arrays.asList(args));
|
||||
|
||||
Process javaProcess;
|
||||
@@ -72,8 +75,8 @@ public class ForkUtil {
|
||||
final AtomicBoolean runCondition = new AtomicBoolean(true);
|
||||
final Process p = javaProcess;
|
||||
|
||||
new Thread(newProcessStreamReaderRunnable(runCondition, p.getErrorStream(), "[FORK-ERROR]")).start();
|
||||
new Thread(newProcessStreamReaderRunnable(runCondition, p.getInputStream(), "[FORK]")).start();
|
||||
startNewThread(newProcessStreamReader(runCondition, p.getErrorStream(), "[FORK-ERROR]"));
|
||||
startNewThread(newProcessStreamReader(runCondition, p.getInputStream(), "[FORK]"));
|
||||
|
||||
Runtime.getRuntime().addShutdownHook(new Thread() {
|
||||
@Override
|
||||
@@ -98,61 +101,69 @@ public class ForkUtil {
|
||||
return processStandardInStream;
|
||||
}
|
||||
|
||||
protected static Runnable newProcessStreamReaderRunnable(final AtomicBoolean runCondition, final InputStream processStream, final String label) {
|
||||
final BufferedReader processStreamReader = new BufferedReader(new InputStreamReader(processStream));
|
||||
protected static Thread startNewThread(final Runnable runnable) {
|
||||
Thread runnableThread = new Thread(runnable);
|
||||
runnableThread.start();
|
||||
return runnableThread;
|
||||
}
|
||||
|
||||
protected static Runnable newProcessStreamReader(final AtomicBoolean runCondition, final InputStream processStream, final String label) {
|
||||
return new Runnable() {
|
||||
public void run() {
|
||||
BufferedReader processStreamReader = new BufferedReader(new InputStreamReader(processStream));
|
||||
try {
|
||||
do {
|
||||
while (runCondition.get()) {
|
||||
for (String line = "Reading..."; line != null; line = processStreamReader.readLine()) {
|
||||
System.out.printf("%1$s %2$s%n ", label, line);
|
||||
}
|
||||
|
||||
Thread.sleep(200);
|
||||
}
|
||||
while (runCondition.get());
|
||||
}
|
||||
catch (Exception ignore) {
|
||||
}
|
||||
finally {
|
||||
IOUtils.close(processStreamReader);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static OutputStream cacheServer(Class<?> clazz) {
|
||||
return startCacheServer(clazz.getName());
|
||||
}
|
||||
|
||||
public static OutputStream cacheServer() {
|
||||
return startCacheServer("org.springframework.data.gemfire.fork.CacheServerProcess");
|
||||
return cacheServer(CacheServerProcess.class);
|
||||
}
|
||||
|
||||
|
||||
public static OutputStream cacheServer(Class<?> type) {
|
||||
return startCacheServer(type.getName());
|
||||
}
|
||||
|
||||
public static OutputStream startCacheServer(String args) {
|
||||
return startGemFireProcess(args, "cache server");
|
||||
}
|
||||
|
||||
protected static OutputStream startGemFireProcess(final String args, final String processName) {
|
||||
String className = args.split(" ")[0];
|
||||
|
||||
System.out.println("main class:" + className);
|
||||
|
||||
|
||||
System.out.println("main class: " + className);
|
||||
|
||||
if (controlFileExists(className)) {
|
||||
deleteControlFile(className);
|
||||
}
|
||||
OutputStream os = cloneJVM(args);
|
||||
int maxTime = 30000;
|
||||
int time = 0;
|
||||
while (!controlFileExists(className) && time < maxTime) {
|
||||
try {
|
||||
Thread.sleep(500);
|
||||
time += 500;
|
||||
} catch (InterruptedException ex) {
|
||||
// ignore and move on
|
||||
}
|
||||
|
||||
OutputStream outputStream = cloneJVM(args);
|
||||
|
||||
final long timeout = System.currentTimeMillis() + 30000;
|
||||
|
||||
while (!controlFileExists(className) && System.currentTimeMillis() < timeout) {
|
||||
ThreadUtils.sleep(500);
|
||||
}
|
||||
|
||||
if (controlFileExists(className)) {
|
||||
System.out.println("[FORK] Started cache server");
|
||||
System.out.printf("[FORK] Started %1$s%n", processName);
|
||||
}
|
||||
else {
|
||||
throw new RuntimeException("could not fork cache server");
|
||||
throw new RuntimeException(String.format("Failed to fork %1$s", processName));
|
||||
}
|
||||
return os;
|
||||
|
||||
return outputStream;
|
||||
}
|
||||
|
||||
public static void sendSignal() {
|
||||
@@ -165,19 +176,16 @@ public class ForkUtil {
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean deleteControlFile(String name) {
|
||||
String path = TEMP_DIRECTORY + File.separator + name;
|
||||
return new File(path).delete();
|
||||
public static boolean createControlFile(final String name) throws IOException {
|
||||
return new File(TEMP_DIRECTORY + File.separator + name).createNewFile();
|
||||
}
|
||||
|
||||
public static boolean createControlFile(String name) throws IOException {
|
||||
String path = TEMP_DIRECTORY + File.separator + name;
|
||||
return new File(path).createNewFile();
|
||||
public static boolean controlFileExists(final String name) {
|
||||
return new File(TEMP_DIRECTORY + File.separator + name).isFile();
|
||||
}
|
||||
|
||||
public static boolean controlFileExists(String name) {
|
||||
String path = TEMP_DIRECTORY + File.separator + name;
|
||||
return new File(path).exists();
|
||||
public static boolean deleteControlFile(final String name) {
|
||||
return new File(TEMP_DIRECTORY + File.separator + name).delete();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ public class GemfireV7GatewayNamespaceTest extends RecreatingContextTest {
|
||||
@Before
|
||||
@Override
|
||||
public void createCtx() {
|
||||
if (GemfireUtils.GEMFIRE_VERSION.startsWith("7")) {
|
||||
if (GemfireUtils.isGemfireVersion7OrAbove()) {
|
||||
super.createCtx();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,12 +38,12 @@ import com.gemstone.gemfire.cache.server.CacheServer;
|
||||
public class CacheServerProcess {
|
||||
|
||||
public static void main(final String[] args) throws Exception {
|
||||
Properties props = new Properties();
|
||||
props.setProperty("name", "CqServer");
|
||||
props.setProperty("mcast-port", "0");
|
||||
props.setProperty("log-level", "warning");
|
||||
Properties gemfireProperties = new Properties();
|
||||
gemfireProperties.setProperty("name", "CqServer");
|
||||
gemfireProperties.setProperty("mcast-port", "0");
|
||||
gemfireProperties.setProperty("log-level", "warning");
|
||||
|
||||
Cache cache = new CacheFactory(props).create();
|
||||
Cache cache = new CacheFactory(gemfireProperties).create();
|
||||
|
||||
RegionFactory<String, Integer> regionFactory = cache.createRegionFactory();
|
||||
regionFactory.setDataPolicy(DataPolicy.REPLICATE);
|
||||
@@ -73,6 +73,8 @@ public class CacheServerProcess {
|
||||
System.out.println("Waiting for shutdown...");
|
||||
|
||||
bufferedReader.readLine();
|
||||
|
||||
System.out.println("Shutting down!");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* 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.fork;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.springframework.data.gemfire.process.support.ProcessUtils;
|
||||
import org.springframework.data.gemfire.test.support.ThreadUtils;
|
||||
|
||||
import com.gemstone.gemfire.distributed.LocatorLauncher;
|
||||
import com.gemstone.gemfire.distributed.internal.DistributionConfig;
|
||||
import com.gemstone.gemfire.distributed.internal.InternalLocator;
|
||||
import com.gemstone.gemfire.distributed.internal.SharedConfiguration;
|
||||
|
||||
/**
|
||||
* The LocatorProcess class is a main Java class that is used fork and launch a GemFire Locator process using the
|
||||
* LocatorLauncher class.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see com.gemstone.gemfire.distributed.LocatorLauncher
|
||||
* @since 1.5.0
|
||||
*/
|
||||
public class LocatorProcess {
|
||||
|
||||
public static final int DEFAULT_LOCATOR_PORT = 20668;
|
||||
|
||||
public static final String DEFAULT_GEMFIRE_MEMBER_NAME = "SpringDataGemFire-Locator";
|
||||
public static final String DEFAULT_HOSTNAME_FOR_CLIENTS = "localhost";
|
||||
public static final String DEFAULT_HTTP_SERVICE_PORT = "0";
|
||||
public static final String DEFAULT_LOG_LEVEL = "config";
|
||||
|
||||
public static void main(final String... args) throws IOException {
|
||||
LocatorLauncher locatorLauncher = buildLocatorLauncher();
|
||||
|
||||
registerShutdownHook();
|
||||
|
||||
// start the GemFire Locator process...
|
||||
locatorLauncher.start();
|
||||
|
||||
waitForLocatorStart(TimeUnit.SECONDS.toMillis(20));
|
||||
|
||||
ProcessUtils.writePid(new File(System.getProperty("user.dir"), getLocatorProcessControlFilename()),
|
||||
ProcessUtils.currentPid());
|
||||
|
||||
ProcessUtils.waitForStopSignal();
|
||||
}
|
||||
|
||||
public static String getLocatorProcessControlFilename() {
|
||||
return LocatorProcess.class.getSimpleName().toLowerCase().concat(".pid");
|
||||
}
|
||||
|
||||
private static LocatorLauncher buildLocatorLauncher() {
|
||||
return new LocatorLauncher.Builder()
|
||||
.setMemberName(DEFAULT_GEMFIRE_MEMBER_NAME)
|
||||
.setHostnameForClients(System.getProperty("spring.gemfire.hostname-for-clients",
|
||||
DEFAULT_HOSTNAME_FOR_CLIENTS))
|
||||
.setPort(Integer.getInteger("spring.gemfire.locator-port", DEFAULT_LOCATOR_PORT))
|
||||
.setRedirectOutput(false)
|
||||
.set(DistributionConfig.ENABLE_SHARED_CONFIGURATION_NAME, String.valueOf(Boolean.getBoolean(
|
||||
"spring.gemfire.enable-shared-configuration")))
|
||||
.set(DistributionConfig.HTTP_SERVICE_PORT_NAME, System.getProperty("spring.gemfire.http-service-port",
|
||||
DEFAULT_HTTP_SERVICE_PORT))
|
||||
.set(DistributionConfig.JMX_MANAGER_NAME, String.valueOf(Boolean.TRUE))
|
||||
.set(DistributionConfig.JMX_MANAGER_START_NAME, String.valueOf(Boolean.FALSE))
|
||||
.set(DistributionConfig.LOAD_SHARED_CONFIG_FROM_DIR_NAME, String.valueOf(Boolean.getBoolean(
|
||||
"spring.gemfire.load-shared-configuration")))
|
||||
.set(DistributionConfig.LOG_LEVEL_NAME, System.getProperty("spring.gemfire.log-level", DEFAULT_LOG_LEVEL))
|
||||
.build();
|
||||
}
|
||||
|
||||
private static boolean isSharedConfigurationEnabled(final InternalLocator locator) {
|
||||
return (locator != null && Boolean.valueOf(locator.getDistributedSystem().getProperties().getProperty(
|
||||
DistributionConfig.ENABLE_SHARED_CONFIGURATION_NAME)));
|
||||
}
|
||||
|
||||
private static void registerShutdownHook() {
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
|
||||
@Override public void run() {
|
||||
stopSharedConfigurationService();
|
||||
LocatorLauncher.getInstance().stop();
|
||||
}
|
||||
|
||||
private void stopSharedConfigurationService() {
|
||||
InternalLocator locator = InternalLocator.getLocator();
|
||||
|
||||
if (isSharedConfigurationEnabled(locator)) {
|
||||
SharedConfiguration sharedConfiguration = locator.getSharedConfiguration();
|
||||
|
||||
if (sharedConfiguration != null) {
|
||||
sharedConfiguration.destroySharedConfiguration();
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private static void waitForLocatorStart(final long milliseconds) {
|
||||
final InternalLocator locator = InternalLocator.getLocator();
|
||||
|
||||
if (isSharedConfigurationEnabled(locator)) {
|
||||
ThreadUtils.timedWait(milliseconds, 500, new ThreadUtils.WaitCondition() {
|
||||
@Override public boolean waiting() {
|
||||
return !locator.isSharedConfigurationRunning();
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
LocatorLauncher.getInstance().waitOnStatusResponse(milliseconds, Math.min(500, milliseconds),
|
||||
TimeUnit.MILLISECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* 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.process;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* The ProcessContext class is a container encapsulating configuration and context meta-data for a running process.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.lang.ProcessBuilder
|
||||
* @since 1.5.0
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class ProcessConfiguration {
|
||||
|
||||
private final boolean redirectingErrorStream;
|
||||
|
||||
private final File workingDirectory;
|
||||
|
||||
private final List<String> command;
|
||||
|
||||
private final Map<String, String> environment;
|
||||
|
||||
public static ProcessConfiguration create(final ProcessBuilder processBuilder) {
|
||||
Assert.notNull(processBuilder, "The ProcessBuilder used to construct, configure and start the Process must not be null!");
|
||||
|
||||
return new ProcessConfiguration(processBuilder.command(), processBuilder.directory(),
|
||||
processBuilder.environment(), processBuilder.redirectErrorStream());
|
||||
}
|
||||
|
||||
public ProcessConfiguration(final List<String> command, final File workingDirectory,
|
||||
final Map<String, String> environment, final boolean redirectingErrorStream) {
|
||||
|
||||
Assert.notEmpty(command, "The command used to run the process must be specified!");
|
||||
|
||||
Assert.isTrue((workingDirectory != null && workingDirectory.isDirectory()), String.format(
|
||||
"The process working directory (%1$s) is not valid!", workingDirectory));
|
||||
|
||||
this.command = new ArrayList<String>(command);
|
||||
this.workingDirectory = workingDirectory;
|
||||
this.redirectingErrorStream = redirectingErrorStream;
|
||||
|
||||
this.environment = (environment != null
|
||||
? Collections.unmodifiableMap(new HashMap<String, String>(environment))
|
||||
: Collections.<String, String>emptyMap());
|
||||
}
|
||||
|
||||
public List<String> getCommand() {
|
||||
return Collections.unmodifiableList(command);
|
||||
}
|
||||
|
||||
public String getCommandString() {
|
||||
return StringUtils.arrayToDelimitedString(getCommand().toArray(), " ");
|
||||
}
|
||||
|
||||
public Map<String, String> getEnvironment() {
|
||||
return environment;
|
||||
}
|
||||
|
||||
public boolean isRedirectingErrorStream() {
|
||||
return redirectingErrorStream;
|
||||
}
|
||||
|
||||
public File getWorkingDirectory() {
|
||||
return workingDirectory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{ command = ".concat(getCommandString())
|
||||
.concat(", workingDirectory = ".concat(getWorkingDirectory().getAbsolutePath()))
|
||||
.concat(", redirectingErrorStream = ".concat(String.valueOf(isRedirectingErrorStream())))
|
||||
.concat(", environment = ".concat(String.valueOf(getEnvironment())))
|
||||
.concat(" }");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* 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.process;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* The ProcessLauncher class is a utility class for launching Java processes.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.lang.Process
|
||||
* @see java.lang.ProcessBuilder
|
||||
* @see org.springframework.data.gemfire.process.ProcessConfiguration
|
||||
* @see org.springframework.data.gemfire.process.ProcessWrapper
|
||||
* @since 1.5.0
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public abstract class ProcessExecutor {
|
||||
|
||||
protected static final String JAVA_CLASSPATH = System.getProperty("java.class.path");
|
||||
|
||||
protected static final String JAVA_HOME = System.getProperty("java.home");
|
||||
|
||||
protected static final String JAVA_EXE = JAVA_HOME.concat(File.separator).concat("bin")
|
||||
.concat(File.separator).concat("java");
|
||||
|
||||
protected static final String USER_HOME = System.getProperty("user.home");
|
||||
|
||||
protected static final String USER_WORKING_DIRECTORY = System.getProperty("user.dir");
|
||||
|
||||
public static ProcessWrapper launch(final Class<?> type, final String... args) throws IOException {
|
||||
return launch(new File(USER_WORKING_DIRECTORY), type, args);
|
||||
}
|
||||
|
||||
public static ProcessWrapper launch(final File workingDirectory, final Class<?> type, final String... args)
|
||||
throws IOException
|
||||
{
|
||||
ProcessBuilder processBuilder = new ProcessBuilder()
|
||||
.command(buildCommand(type, args))
|
||||
.directory(validateDirectory(workingDirectory))
|
||||
.redirectErrorStream(true);
|
||||
|
||||
Process process = processBuilder.start();
|
||||
|
||||
ProcessWrapper processWrapper = new ProcessWrapper(process, ProcessConfiguration.create(processBuilder));
|
||||
|
||||
processWrapper.register(new ProcessInputStreamListener() {
|
||||
@Override public void onInput(final String input) {
|
||||
System.err.printf("[FORK-OUT] - %1$s%n", input);
|
||||
}
|
||||
});
|
||||
|
||||
return processWrapper;
|
||||
}
|
||||
|
||||
protected static String[] buildCommand(final Class<?> type, final String... args) {
|
||||
Assert.notNull(type != null, "The main Class to launch must not be null!");
|
||||
|
||||
List<String> command = new ArrayList<String>();
|
||||
List<String> programArgs = Collections.emptyList();
|
||||
|
||||
command.add(JAVA_EXE);
|
||||
command.add("-server");
|
||||
command.add("-classpath");
|
||||
command.add(JAVA_CLASSPATH);
|
||||
|
||||
if (args != null) {
|
||||
programArgs = new ArrayList<String>(args.length);
|
||||
|
||||
for (String arg : args) {
|
||||
if (isJvmOption(arg)) {
|
||||
command.add(arg);
|
||||
}
|
||||
else if (!StringUtils.isEmpty(arg)) {
|
||||
programArgs.add(arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
command.add(type.getName());
|
||||
command.addAll(programArgs);
|
||||
|
||||
return command.toArray(new String[command.size()]);
|
||||
}
|
||||
|
||||
protected static boolean isJvmOption(final String option) {
|
||||
return (!StringUtils.isEmpty(option) && (option.startsWith("-D") || option.startsWith("-X")));
|
||||
}
|
||||
|
||||
protected static File validateDirectory(final File workingDirectory) {
|
||||
Assert.isTrue(workingDirectory != null && (workingDirectory.isDirectory() || workingDirectory.mkdirs()));
|
||||
return workingDirectory;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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.process;
|
||||
|
||||
import java.util.EventListener;
|
||||
|
||||
/**
|
||||
* The ProcessStreamListener interface is a callback listener that gets called when input arrives from either a
|
||||
* process's standard output steam or standard error stream.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.util.EventListener
|
||||
* @since 1.5.0
|
||||
*/
|
||||
public interface ProcessInputStreamListener extends EventListener {
|
||||
|
||||
void onInput(String input);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,274 @@
|
||||
/*
|
||||
* 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.process;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.springframework.data.gemfire.process.support.PidUnavailableException;
|
||||
import org.springframework.data.gemfire.process.support.ProcessUtils;
|
||||
import org.springframework.data.gemfire.test.support.IOUtils;
|
||||
import org.springframework.data.gemfire.test.support.ThreadUtils;
|
||||
import org.springframework.data.gemfire.test.support.ThrowableUtils;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* The ProcessWrapper class is a wrapper for a Process object representing an OS process and the ProcessBuilder used
|
||||
* to construct and start the process.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.lang.Process
|
||||
* @see java.lang.ProcessBuilder
|
||||
* @since 1.5.0
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class ProcessWrapper {
|
||||
|
||||
protected static final boolean DEFAULT_DAEMON_THREAD = true;
|
||||
|
||||
protected static final long DEFAULT_WAIT_TIME_MILLISECONDS = TimeUnit.SECONDS.toMillis(15);
|
||||
|
||||
private final List<ProcessInputStreamListener> listeners = new CopyOnWriteArrayList<ProcessInputStreamListener>();
|
||||
|
||||
protected final Logger log = Logger.getLogger(getClass().getName());
|
||||
|
||||
private final Process process;
|
||||
private final ProcessConfiguration processConfiguration;
|
||||
|
||||
public ProcessWrapper(final Process process, final ProcessConfiguration processConfiguration) {
|
||||
Assert.notNull(process, "The Process object backing this wrapper must not be null!");
|
||||
|
||||
Assert.notNull(processConfiguration, "The context and configuration meta-data providing details about"
|
||||
+ " the environment in which the process is running and how the process was configured must not be null!");
|
||||
|
||||
this.process = process;
|
||||
this.processConfiguration = processConfiguration;
|
||||
|
||||
postInit();
|
||||
}
|
||||
|
||||
private void postInit() {
|
||||
newThread("Process OUT Stream Reader", newProcessInputStreamReader(process.getInputStream())).start();
|
||||
|
||||
if (!isRedirectingErrorStream()) {
|
||||
newThread("Process ERR Stream Reader", newProcessInputStreamReader(process.getErrorStream())).start();
|
||||
}
|
||||
}
|
||||
|
||||
protected Runnable newProcessInputStreamReader(final InputStream in) {
|
||||
return new Runnable() {
|
||||
@Override public void run() {
|
||||
if (isRunning()) {
|
||||
BufferedReader inputReader = new BufferedReader(new InputStreamReader(in));
|
||||
|
||||
try {
|
||||
for (String input = inputReader.readLine(); input != null; input = inputReader.readLine()) {
|
||||
for (ProcessInputStreamListener listener : listeners) {
|
||||
listener.onInput(input);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException ignore) {
|
||||
// ignore IO error and just stop reading from the process input stream
|
||||
// IO error occurred most likely because the process was terminated
|
||||
}
|
||||
finally {
|
||||
IOUtils.close(inputReader);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected Thread newThread(final String name, final Runnable task) {
|
||||
Assert.isTrue(!StringUtils.isEmpty(name), "The name of the Thread must be specified!");
|
||||
Assert.notNull(task, "The Thread task must not be null!");
|
||||
Thread thread = new Thread(task, name);
|
||||
thread.setDaemon(DEFAULT_DAEMON_THREAD);
|
||||
thread.setPriority(Thread.NORM_PRIORITY);
|
||||
return thread;
|
||||
}
|
||||
|
||||
public List<String> getCommand() {
|
||||
return processConfiguration.getCommand();
|
||||
}
|
||||
|
||||
public String getCommandString() {
|
||||
return processConfiguration.getCommandString();
|
||||
}
|
||||
|
||||
public Map<String, String> getEnvironment() {
|
||||
return processConfiguration.getEnvironment();
|
||||
}
|
||||
|
||||
public int getPid() {
|
||||
return ProcessUtils.findAndReadPid(getWorkingDirectory());
|
||||
}
|
||||
|
||||
public int safeGetPid() {
|
||||
try {
|
||||
return getPid();
|
||||
}
|
||||
catch (PidUnavailableException ignore) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isRedirectingErrorStream() {
|
||||
return processConfiguration.isRedirectingErrorStream();
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
return ProcessUtils.isRunning(this.process);
|
||||
}
|
||||
|
||||
public File getWorkingDirectory() {
|
||||
return processConfiguration.getWorkingDirectory();
|
||||
}
|
||||
|
||||
public int exitValue() {
|
||||
return process.exitValue();
|
||||
}
|
||||
|
||||
public int safeExitValue() {
|
||||
try {
|
||||
return exitValue();
|
||||
}
|
||||
catch (IllegalThreadStateException ignore) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean register(final ProcessInputStreamListener listener) {
|
||||
return (listener != null && listeners.add(listener));
|
||||
}
|
||||
|
||||
public void registerShutdownHook() {
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
|
||||
@Override public void run() {
|
||||
shutdown();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
public void signalStop() {
|
||||
try {
|
||||
ProcessUtils.signalStop(this.process);
|
||||
}
|
||||
catch (IOException e) {
|
||||
log.warning("Failed to signal the process to stop!");
|
||||
|
||||
if (log.isLoggable(Level.FINE)) {
|
||||
log.fine(ThrowableUtils.toString(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int stop() {
|
||||
return stop(DEFAULT_WAIT_TIME_MILLISECONDS);
|
||||
}
|
||||
|
||||
public int stop(final long milliseconds) {
|
||||
if (isRunning()) {
|
||||
int exitValue = -1;
|
||||
final int pid = safeGetPid();
|
||||
final long timeout = (System.currentTimeMillis() + milliseconds);
|
||||
final AtomicBoolean exited = new AtomicBoolean(false);
|
||||
|
||||
ExecutorService executorService = Executors.newSingleThreadExecutor();
|
||||
|
||||
try {
|
||||
Future<Integer> futureExitValue = executorService.submit(new Callable<Integer>() {
|
||||
@Override public Integer call() throws Exception {
|
||||
process.destroy();
|
||||
int exitValue = process.waitFor();
|
||||
exited.set(true);
|
||||
return exitValue;
|
||||
}
|
||||
});
|
||||
|
||||
while (!exited.get() && System.currentTimeMillis() < timeout) {
|
||||
try {
|
||||
exitValue = futureExitValue.get(milliseconds, TimeUnit.MILLISECONDS);
|
||||
log.info(String.format("Process [%1$s] has been stopped.%n", pid));
|
||||
}
|
||||
catch (InterruptedException ignore) {
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (TimeoutException e) {
|
||||
exitValue = -1;
|
||||
log.warning(String.format("Process [%1$d] did not stop within the allotted timeout of %2$d seconds.%n",
|
||||
pid, TimeUnit.MILLISECONDS.toSeconds(milliseconds)));
|
||||
}
|
||||
catch (Exception ignore) {
|
||||
// handles CancellationException, ExecutionException
|
||||
}
|
||||
finally {
|
||||
executorService.shutdownNow();
|
||||
}
|
||||
|
||||
return exitValue;
|
||||
}
|
||||
else {
|
||||
return exitValue();
|
||||
}
|
||||
}
|
||||
|
||||
public int shutdown() {
|
||||
if (isRunning()) {
|
||||
log.info(String.format("Stopping process [%1$d]...%n", safeGetPid()));
|
||||
signalStop();
|
||||
waitFor();
|
||||
}
|
||||
|
||||
return stop();
|
||||
}
|
||||
|
||||
public boolean unregister(final ProcessInputStreamListener listener) {
|
||||
return listeners.remove(listener);
|
||||
}
|
||||
|
||||
public void waitFor() {
|
||||
waitFor(DEFAULT_WAIT_TIME_MILLISECONDS);
|
||||
}
|
||||
|
||||
public void waitFor(final long milliseconds) {
|
||||
ThreadUtils.timedWait(milliseconds, 500, new ThreadUtils.WaitCondition() {
|
||||
@Override public boolean waiting() {
|
||||
return isRunning();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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.process.support;
|
||||
|
||||
/**
|
||||
* The PidUnavailableException class is a RuntimeException indicating that the process ID (PID) is unobtainable for
|
||||
* the current process.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.lang.RuntimeException
|
||||
* @since 1.5.0
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class PidUnavailableException extends RuntimeException {
|
||||
|
||||
public PidUnavailableException() {
|
||||
}
|
||||
|
||||
public PidUnavailableException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public PidUnavailableException(final Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public PidUnavailableException(final String message, final Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
* 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.process.support;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.lang.management.RuntimeMXBean;
|
||||
import java.util.Scanner;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.springframework.data.gemfire.test.support.IOUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.gemstone.gemfire.management.internal.cli.util.spring.Assert;
|
||||
import com.sun.tools.attach.VirtualMachine;
|
||||
import com.sun.tools.attach.VirtualMachineDescriptor;
|
||||
|
||||
/**
|
||||
* The ProcessUtils class is a utilty class for working with process, or specifically instances
|
||||
* of the Java Process class.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.lang.Process
|
||||
* @see java.lang.management.RuntimeMXBean
|
||||
* @see com.sun.tools.attach.VirtualMachine
|
||||
* @since 1.5.0
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public abstract class ProcessUtils {
|
||||
|
||||
protected static final Logger log = Logger.getLogger(ProcessUtils.class.getName());
|
||||
|
||||
protected static final String TERM_TOKEN = "<TERM/>";
|
||||
|
||||
public static int currentPid() {
|
||||
RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean();
|
||||
String runtimeMXBeanName = runtimeMXBean.getName();
|
||||
|
||||
Exception cause = null;
|
||||
|
||||
if (StringUtils.hasText(runtimeMXBeanName)) {
|
||||
int atSignIndex = runtimeMXBeanName.indexOf('@');
|
||||
|
||||
if (atSignIndex > 0) {
|
||||
try {
|
||||
return Integer.parseInt(runtimeMXBeanName.substring(0, atSignIndex));
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
cause = e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new PidUnavailableException(String.format("The process ID (PID) is not available (%1$s)!",
|
||||
runtimeMXBeanName), cause);
|
||||
}
|
||||
|
||||
public static boolean isRunning(final int processId) {
|
||||
for (VirtualMachineDescriptor vmDescriptor : VirtualMachine.list()) {
|
||||
if (String.valueOf(processId).equals(vmDescriptor.id())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean isRunning(final Process process) {
|
||||
try {
|
||||
process.exitValue();
|
||||
return false;
|
||||
}
|
||||
catch (IllegalThreadStateException ignore) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static void signalStop(final Process process) throws IOException {
|
||||
if (isRunning(process)) {
|
||||
OutputStream processOutputStream = process.getOutputStream();
|
||||
processOutputStream.write(TERM_TOKEN.concat("\n").getBytes());
|
||||
processOutputStream.flush();
|
||||
}
|
||||
}
|
||||
|
||||
public static void waitForStopSignal() {
|
||||
Scanner in = new Scanner(System.in);
|
||||
while (!TERM_TOKEN.equals(in.next()));
|
||||
}
|
||||
|
||||
public static int findAndReadPid(final File workingDirectory) {
|
||||
Assert.isTrue(workingDirectory != null && workingDirectory.isDirectory(), String.format(
|
||||
"The file system pathname (%1$s) expected to contain a PID file is not a valid directory!",
|
||||
workingDirectory));
|
||||
|
||||
File pidFile = findPidFile(workingDirectory);
|
||||
|
||||
if (pidFile == null) {
|
||||
throw new PidUnavailableException(String.format(
|
||||
"No PID file was found in working directory (%1$s) or any of it's sub-directories!",
|
||||
workingDirectory));
|
||||
}
|
||||
|
||||
return readPid(pidFile);
|
||||
}
|
||||
|
||||
protected static File findPidFile(final File workingDirectory) {
|
||||
Assert.isTrue(workingDirectory != null && workingDirectory.isDirectory(), String.format(
|
||||
"The file system pathname (%1$s) is not valid directory!", workingDirectory));
|
||||
|
||||
for (File file : workingDirectory.listFiles(DirectoryPidFileFilter.INSTANCE)) {
|
||||
if (file.isDirectory()) {
|
||||
file = findPidFile(file);
|
||||
}
|
||||
|
||||
if (PidFileFilter.INSTANCE.accept(file)) {
|
||||
return file;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static int readPid(final File pidFile) {
|
||||
Assert.isTrue(pidFile != null && pidFile.isFile(), String.format(
|
||||
"The file system pathname (%1$s) is not a valid file!", pidFile));
|
||||
|
||||
BufferedReader fileReader = null;
|
||||
String pidValue = null;
|
||||
|
||||
try {
|
||||
fileReader = new BufferedReader(new FileReader(pidFile));
|
||||
pidValue = String.valueOf(fileReader.readLine()).trim();
|
||||
return Integer.parseInt(pidValue);
|
||||
}
|
||||
catch (FileNotFoundException e) {
|
||||
throw new PidUnavailableException(String.format("PID file (%1$s) could not be found!", pidFile), e);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new PidUnavailableException(String.format("Unable to read PID from file (%1$s)!", pidFile), e);
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
throw new PidUnavailableException(String.format(
|
||||
"The value (%1$s) from PID file (%2$s) was not a valid numerical PID!", pidValue, pidFile), e);
|
||||
}
|
||||
finally {
|
||||
IOUtils.close(fileReader);
|
||||
}
|
||||
}
|
||||
|
||||
public static void writePid(final File pidFile, final int pid) throws IOException {
|
||||
Assert.isTrue(pidFile != null && (pidFile.isFile() || pidFile.createNewFile()), String.format(
|
||||
"The file system pathname (%1$s) in which the PID will be written is not a valid file!", pidFile));
|
||||
|
||||
Assert.isTrue(pid > 0, String.format("The PID value (%1$d) must greater than 0!", pid));
|
||||
|
||||
PrintWriter fileWriter = new PrintWriter(new BufferedWriter(new FileWriter(pidFile, false), 16), true);
|
||||
|
||||
try {
|
||||
fileWriter.println(pid);
|
||||
}
|
||||
finally {
|
||||
pidFile.deleteOnExit();
|
||||
IOUtils.close(fileWriter);
|
||||
}
|
||||
}
|
||||
|
||||
protected static class DirectoryPidFileFilter extends PidFileFilter {
|
||||
|
||||
protected static final DirectoryPidFileFilter INSTANCE = new DirectoryPidFileFilter();
|
||||
|
||||
@Override
|
||||
public boolean accept(final File pathname) {
|
||||
return (pathname != null && (pathname.isDirectory() || super.accept(pathname)));
|
||||
}
|
||||
}
|
||||
|
||||
protected static class PidFileFilter implements FileFilter {
|
||||
|
||||
protected static final PidFileFilter INSTANCE = new PidFileFilter();
|
||||
|
||||
@Override
|
||||
public boolean accept(final File pathname) {
|
||||
return (pathname != null && pathname.isFile() && pathname.getName().endsWith(".pid"));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.test.support;
|
||||
|
||||
import java.util.Enumeration;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* The CollectionUtils class is a utility class for working with the Java Collections Framework.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.util.Collection
|
||||
* @see java.util.Collections
|
||||
* @since 1.5.0
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public abstract class CollectionUtils {
|
||||
|
||||
public static <T> Iterable<T> iterable(final Enumeration<T> enumeration) {
|
||||
return new Iterable<T>() {
|
||||
@Override public Iterator<T> iterator() {
|
||||
return org.springframework.util.CollectionUtils.toIterator(enumeration);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static <T> Iterable<T> iterable(final Iterator<T> iterator) {
|
||||
return new Iterable<T>() {
|
||||
@Override public Iterator<T> iterator() {
|
||||
return iterator;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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.test.support;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* The IOUtils class is an utility class working with IO operations.
|
||||
*
|
||||
* @author John Blum
|
||||
* @since 1.5.0
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public abstract class IOUtils {
|
||||
|
||||
private static final Logger log = Logger.getLogger(IOUtils.class.getName());
|
||||
|
||||
public static boolean close(final Closeable closeable) {
|
||||
if (closeable != null) {
|
||||
try {
|
||||
closeable.close();
|
||||
return true;
|
||||
}
|
||||
catch (IOException ignore) {
|
||||
if (log.isLoggable(Level.FINE)) {
|
||||
log.fine(String.format("Failed to close Closeable object (%1$s) due to I/O error:%n%2$s",
|
||||
closeable, ThrowableUtils.toString(ignore)));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* 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.test.support;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* The ThreadUtils class is a utility class for working with Java Threads.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.lang.Thread
|
||||
* @since 1.5.0
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public abstract class ThreadUtils {
|
||||
|
||||
public static boolean sleep(final long milliseconds) {
|
||||
try {
|
||||
Thread.sleep(milliseconds);
|
||||
return true;
|
||||
}
|
||||
catch (InterruptedException ignore) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void timedWait(final long milliseconds) {
|
||||
timedWait(milliseconds, milliseconds);
|
||||
}
|
||||
|
||||
public static void timedWait(final long milliseconds, final long interval) {
|
||||
timedWait(milliseconds, interval, new WaitCondition() {
|
||||
@Override public boolean waiting() {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void timedWait(final long milliseconds, long interval, final WaitCondition waitCondition) {
|
||||
final long timeout = (System.currentTimeMillis() + milliseconds);
|
||||
|
||||
interval = Math.min(interval, milliseconds);
|
||||
|
||||
while (waitCondition.waiting() && (System.currentTimeMillis() < timeout)) {
|
||||
try {
|
||||
synchronized (waitCondition) {
|
||||
TimeUnit.MILLISECONDS.timedWait(waitCondition, interval);
|
||||
}
|
||||
}
|
||||
catch (InterruptedException ignore) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static interface WaitCondition {
|
||||
boolean waiting();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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.test.support;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
|
||||
/**
|
||||
* The ExceptionUtils class is a utility class for working with Throwable, Exception and Error objects.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.lang.Error
|
||||
* @see java.lang.Exception
|
||||
* @see java.lang.Throwable
|
||||
* @since 1.5.0
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public abstract class ThrowableUtils {
|
||||
|
||||
public static String toString(Throwable t) {
|
||||
StringWriter writer = new StringWriter();
|
||||
t.printStackTrace(new PrintWriter(writer));
|
||||
return writer.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* 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.test.support;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
/**
|
||||
* The ZipUtils class is an abstract utility class for working with JAR and ZIP archives.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.io.File
|
||||
* @see java.util.zip.ZipFile
|
||||
* @since 1.5.0
|
||||
*/
|
||||
public abstract class ZipUtils {
|
||||
|
||||
public static void unzip(final Resource zipResource, final File directory) throws IOException {
|
||||
Assert.notNull(zipResource, "The ZIP Resource must not be null!");
|
||||
|
||||
Assert.isTrue(directory != null && directory.isDirectory(), String.format(
|
||||
"The file system pathname (%1$s) is not a valid directory!", directory));
|
||||
|
||||
ZipFile zipFile = new ZipFile(zipResource.getFile(), ZipFile.OPEN_READ);
|
||||
|
||||
for (ZipEntry entry : CollectionUtils.iterable(zipFile.entries())) {
|
||||
if (entry.isDirectory()) {
|
||||
new File(directory, entry.getName()).mkdirs();
|
||||
}
|
||||
else {
|
||||
DataInputStream entryInputStream = new DataInputStream(zipFile.getInputStream(entry));
|
||||
|
||||
DataOutputStream entryOutputStream = new DataOutputStream(new FileOutputStream(
|
||||
new File(directory, entry.getName())));
|
||||
|
||||
try {
|
||||
FileCopyUtils.copy(entryInputStream, entryOutputStream);
|
||||
}
|
||||
finally {
|
||||
IOUtils.close(entryInputStream);
|
||||
IOUtils.close(entryOutputStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user