diff --git a/src/main/java/org/springframework/data/gemfire/tests/process/PidUnavailableException.java b/src/main/java/org/springframework/data/gemfire/tests/process/PidNotFoundException.java similarity index 62% rename from src/main/java/org/springframework/data/gemfire/tests/process/PidUnavailableException.java rename to src/main/java/org/springframework/data/gemfire/tests/process/PidNotFoundException.java index 2b5676f..a2cbdbe 100644 --- a/src/main/java/org/springframework/data/gemfire/tests/process/PidUnavailableException.java +++ b/src/main/java/org/springframework/data/gemfire/tests/process/PidNotFoundException.java @@ -17,29 +17,28 @@ package org.springframework.data.gemfire.tests.process; /** - * The PidUnavailableException class is a RuntimeException indicating that the process ID (PID) is unobtainable for - * the current process. + * The {@link PidNotFoundException} class is a {@link RuntimeException} indicating that the process ID (PID) + * is unobtainable for the current {@link Process}. * * @author John Blum - * @see RuntimeException - * @since 1.5.0 + * @see java.lang.RuntimeException + * @since 0.0.1 */ @SuppressWarnings("unused") -public class PidUnavailableException extends RuntimeException { +public class PidNotFoundException extends RuntimeException { - public PidUnavailableException() { + public PidNotFoundException() { } - public PidUnavailableException(final String message) { + public PidNotFoundException(final String message) { super(message); } - public PidUnavailableException(final Throwable cause) { + public PidNotFoundException(final Throwable cause) { super(cause); } - public PidUnavailableException(final String message, final Throwable cause) { + public PidNotFoundException(final String message, final Throwable cause) { super(message, cause); } - } diff --git a/src/main/java/org/springframework/data/gemfire/tests/process/ProcessConfiguration.java b/src/main/java/org/springframework/data/gemfire/tests/process/ProcessConfiguration.java index 2130cbd..cc006bb 100644 --- a/src/main/java/org/springframework/data/gemfire/tests/process/ProcessConfiguration.java +++ b/src/main/java/org/springframework/data/gemfire/tests/process/ProcessConfiguration.java @@ -32,9 +32,10 @@ import org.springframework.util.StringUtils; * for a running process. * * @author John Blum - * @see ProcessBuilder - * @see org.springframework.data.gemfire.process.ProcessExecutor - * @since 1.5.0 + * @see java.lang.Process + * @see java.lang.ProcessBuilder + * @see org.springframework.data.gemfire.tests.process.ProcessExecutor + * @since 0.0.1 */ @SuppressWarnings("unused") public class ProcessConfiguration { @@ -48,7 +49,9 @@ public class ProcessConfiguration { private final Map environment; public static ProcessConfiguration create(ProcessBuilder processBuilder) { - Assert.notNull(processBuilder, "The ProcessBuilder used to configure and start the Process must not be null"); + + Assert.notNull(processBuilder, + "The ProcessBuilder used to configure and start the Process must not be null"); return new ProcessConfiguration(processBuilder.command(), processBuilder.directory(), processBuilder.environment(), processBuilder.redirectErrorStream()); @@ -57,22 +60,22 @@ public class ProcessConfiguration { public ProcessConfiguration(List command, File workingDirectory, Map environment, boolean redirectErrorStream) { - Assert.notEmpty(command, "Process command must be specified"); + Assert.notEmpty(command, "Process command is required"); - Assert.isTrue(FileSystemUtils.isDirectory(workingDirectory), String.format( - "Process working directory [%s] is not valid", workingDirectory)); + Assert.isTrue(FileSystemUtils.isDirectory(workingDirectory), + String.format("Process working directory [%s] is not valid", workingDirectory)); - this.command = new ArrayList(command); + this.command = Collections.unmodifiableList(new ArrayList<>(command)); this.workingDirectory = workingDirectory; this.redirectingErrorStream = redirectErrorStream; - this.environment = (environment != null - ? Collections.unmodifiableMap(new HashMap(environment)) - : Collections.emptyMap()); + this.environment = environment != null + ? Collections.unmodifiableMap(new HashMap<>(environment)) + : Collections.emptyMap(); } public List getCommand() { - return Collections.unmodifiableList(command); + return this.command; } public String getCommandString() { @@ -80,19 +83,20 @@ public class ProcessConfiguration { } public Map getEnvironment() { - return environment; + return this.environment; } public boolean isRedirectingErrorStream() { - return redirectingErrorStream; + return this.redirectingErrorStream; } public File getWorkingDirectory() { - return workingDirectory; + return this.workingDirectory; } @Override public String toString() { + return "{ command = ".concat(getCommandString()) .concat(", workingDirectory = ".concat(getWorkingDirectory().getAbsolutePath())) .concat(", environment = ".concat(String.valueOf(getEnvironment()))) diff --git a/src/main/java/org/springframework/data/gemfire/tests/process/ProcessExecutor.java b/src/main/java/org/springframework/data/gemfire/tests/process/ProcessExecutor.java index dfb60ae..e58dbbf 100644 --- a/src/main/java/org/springframework/data/gemfire/tests/process/ProcessExecutor.java +++ b/src/main/java/org/springframework/data/gemfire/tests/process/ProcessExecutor.java @@ -39,7 +39,7 @@ import org.springframework.util.StringUtils; * @see java.lang.System * @see org.springframework.data.gemfire.tests.process.ProcessConfiguration * @see org.springframework.data.gemfire.tests.process.ProcessWrapper - * @since 1.5.0 + * @since 0.0.1 */ @SuppressWarnings("unused") public abstract class ProcessExecutor { @@ -48,8 +48,8 @@ public abstract class ProcessExecutor { public static final String JAVA_CLASSPATH = System.getProperty("java.class.path"); - protected static final String SPRING_GEMFIRE_SYSTEM_PROPERTY_PREFIX = "spring.gemfire."; protected static final String SPRING_DATA_GEMFIRE_SYSTEM_PROPERTY_PREFIX = "spring.data.gemfire."; + protected static final String SPRING_GEMFIRE_SYSTEM_PROPERTY_PREFIX = "spring.gemfire."; public static ProcessWrapper launch(Class type, String... args) throws IOException { return launch(FileSystemUtils.WORKING_DIRECTORY, type, args); @@ -77,6 +77,7 @@ public abstract class ProcessExecutor { } protected static String[] buildCommand(String classpath, Class type, String... args) { + Assert.notNull(type, "The main Java class to launch must not be null"); List command = new ArrayList<>(); @@ -93,7 +94,7 @@ public abstract class ProcessExecutor { if (isJvmOption(arg)) { command.add(arg); } - else if (!StringUtils.isEmpty(arg)) { + else if (isValidArgument(arg)) { programArguments.add(arg); } } @@ -105,6 +106,7 @@ public abstract class ProcessExecutor { } protected static Collection getSpringGemFireSystemProperties() { + return System.getProperties().stringPropertyNames().stream() .filter(property -> property.startsWith(SPRING_DATA_GEMFIRE_SYSTEM_PROPERTY_PREFIX) || property.startsWith(SPRING_GEMFIRE_SYSTEM_PROPERTY_PREFIX)) @@ -116,7 +118,12 @@ public abstract class ProcessExecutor { return (StringUtils.hasText(option) && (option.startsWith("-D") || option.startsWith("-X"))); } + protected static boolean isValidArgument(String argument) { + return StringUtils.hasText(argument); + } + protected static File validateDirectory(File workingDirectory) { + Assert.isTrue(workingDirectory != null && (workingDirectory.isDirectory() || workingDirectory.mkdirs()), String.format("Failed to create working directory [%s]", workingDirectory)); diff --git a/src/main/java/org/springframework/data/gemfire/tests/process/ProcessInputStreamListener.java b/src/main/java/org/springframework/data/gemfire/tests/process/ProcessInputStreamListener.java index 056bff5..b8c4d83 100644 --- a/src/main/java/org/springframework/data/gemfire/tests/process/ProcessInputStreamListener.java +++ b/src/main/java/org/springframework/data/gemfire/tests/process/ProcessInputStreamListener.java @@ -23,8 +23,8 @@ import java.util.EventListener; * {@link Process process's} standard output steam or standard error stream. * * @author John Blum - * @see EventListener - * @since 1.5.0 + * @see java.util.EventListener + * @since 0.0.1 */ public interface ProcessInputStreamListener extends EventListener { @@ -33,8 +33,8 @@ public interface ProcessInputStreamListener extends EventListener { * or standard error streams. * * @param input {@link String} containing output from the {@link Process} that this listener is listening to. - * @see Process#getErrorStream() - * @see Process#getInputStream() + * @see java.lang.Process#getErrorStream() + * @see java.lang.Process#getInputStream() */ void onInput(String input); diff --git a/src/main/java/org/springframework/data/gemfire/tests/process/ProcessUtils.java b/src/main/java/org/springframework/data/gemfire/tests/process/ProcessUtils.java index 61dba6c..f2a1e90 100644 --- a/src/main/java/org/springframework/data/gemfire/tests/process/ProcessUtils.java +++ b/src/main/java/org/springframework/data/gemfire/tests/process/ProcessUtils.java @@ -42,7 +42,7 @@ import org.springframework.util.StringUtils; * @author John Blum * @see File * @see Process - * @since 1.5.0 + * @since 0.0.1 */ @SuppressWarnings("unused") public abstract class ProcessUtils { @@ -53,12 +53,15 @@ public abstract class ProcessUtils { /* (non-Javadoc) */ 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) { @@ -71,32 +74,20 @@ public abstract class ProcessUtils { } } - throw new PidUnavailableException(String.format("Process ID (PID) not available [%s]", runtimeMXBeanName), - cause); + throw new PidNotFoundException(String.format("Process ID (PID) not available [%s]", + runtimeMXBeanName), cause); } - /* (non-Javadoc) */ public static boolean isAlive(Process process) { - return (process != null && process.isAlive()); + return process != null && process.isAlive(); } - /* (non-Javadoc) */ public static boolean isRunning(int processId) { - /* - for (VirtualMachineDescriptor vmDescriptor : VirtualMachine.list()) { - if (String.valueOf(processId).equals(vmDescriptor.id())) { - return true; - } - } - - return false; - */ - - throw new UnsupportedOperationException("operation not supported"); + throw new UnsupportedOperationException("Operation not supported"); } - /* (non-Javadoc) */ public static boolean isRunning(Process process) { + try { process.exitValue(); return false; @@ -106,40 +97,24 @@ public abstract class ProcessUtils { } } - /* (non-Javadoc) */ - public static void signalStop(Process process) throws IOException { - if (isRunning(process)) { - OutputStream processOutputStream = process.getOutputStream(); - processOutputStream.write(TERM_TOKEN.concat("\n").getBytes()); - processOutputStream.flush(); - } - } - - /* (non-Javadoc) */ - @SuppressWarnings("all") - public static void waitForStopSignal() { - Scanner in = new Scanner(System.in); - while (!TERM_TOKEN.equals(in.next())); - } - - /* (non-Javadoc) */ public static int findAndReadPid(File workingDirectory) { + File pidFile = findPidFile(workingDirectory); if (pidFile == null) { - throw new PidUnavailableException(String.format( - "No PID file was found in working directory [%s] or any of it's sub-directories", + throw new PidNotFoundException( + String.format("No PID file was found in working directory [%s] or any of it's sub-directories", workingDirectory)); } return readPid(pidFile); } - /* (non-Javadoc) */ @SuppressWarnings("all") protected static File findPidFile(File workingDirectory) { - Assert.isTrue(FileSystemUtils.isDirectory(workingDirectory), String.format( - "File [%s] is not a valid directory", workingDirectory)); + + Assert.isTrue(FileSystemUtils.isDirectory(workingDirectory), + String.format("File [%s] is not a valid directory", workingDirectory)); for (File file : workingDirectory.listFiles(DirectoryPidFileFilter.INSTANCE)) { if (file.isDirectory()) { @@ -154,45 +129,48 @@ public abstract class ProcessUtils { return null; } - /* (non-Javadoc) */ @SuppressWarnings("all") public static int readPid(File pidFile) { - Assert.isTrue(pidFile != null && pidFile.isFile(), String.format( - "File [%s] is not a valid file", pidFile)); + + Assert.isTrue(pidFile != null && pidFile.isFile(), + String.format("File [%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 [%s] not found", pidFile), e); + catch (FileNotFoundException cause) { + throw new PidNotFoundException(String.format("PID file [%s] not found", pidFile), cause); } - catch (IOException e) { - throw new PidUnavailableException(String.format("failed to read PID from file [%s]", pidFile), e); + catch (IOException cause) { + throw new PidNotFoundException(String.format("Failed to read PID from file [%s]", pidFile), cause); } - catch (NumberFormatException e) { - throw new PidUnavailableException(String.format( - "value [%1$s] from PID file [%2$s] was not a valid numerical PID", pidValue, pidFile), e); + catch (NumberFormatException cause) { + throw new PidNotFoundException(String.format("Value [%1$s] from PID file [%2$s] was not a valid numerical PID", + pidValue, pidFile), cause); } finally { IOUtils.close(fileReader); } } - /* (non-Javadoc) */ @SuppressWarnings("all") public static void writePid(File pidFile, int pid) throws IOException { - Assert.isTrue(pidFile != null && (pidFile.isFile() || pidFile.createNewFile()), String.format( - "File [%s] is not a valid file", pidFile)); + + Assert.isTrue(pidFile != null && (pidFile.isFile() || pidFile.createNewFile()), + String.format("File [%s] is not a valid file", pidFile)); Assert.isTrue(pid > 0, String.format("PID [%d] must greater than 0", pid)); - PrintWriter fileWriter = new PrintWriter(new BufferedWriter(new FileWriter(pidFile, false), 16), true); + PrintWriter fileWriter = new PrintWriter(new BufferedWriter( + new FileWriter(pidFile, false), 16), true); try { fileWriter.println(pid); @@ -203,28 +181,37 @@ public abstract class ProcessUtils { } } - /* (non-Javadoc) */ + public static void signalStop(Process process) throws IOException { + + if (isRunning(process)) { + OutputStream processOutputStream = process.getOutputStream(); + processOutputStream.write(TERM_TOKEN.concat("\n").getBytes()); + processOutputStream.flush(); + } + } + + @SuppressWarnings("all") + public static void waitForStopSignal() { + + Scanner in = new Scanner(System.in); + + while (!TERM_TOKEN.equals(in.next())); + } + protected static class DirectoryPidFileFilter extends PidFileFilter { protected static final DirectoryPidFileFilter INSTANCE = new DirectoryPidFileFilter(); - /** - * @inheritDoc - */ @Override public boolean accept(File path) { return (path != null && (path.isDirectory() || super.accept(path))); } } - /* (non-Javadoc) */ protected static class PidFileFilter implements FileFilter { protected static final PidFileFilter INSTANCE = new PidFileFilter(); - /** - * @inheritDoc - */ @Override public boolean accept(File path) { return (path != null && path.isFile() && path.getName().toLowerCase().endsWith(".pid")); diff --git a/src/main/java/org/springframework/data/gemfire/tests/process/ProcessWrapper.java b/src/main/java/org/springframework/data/gemfire/tests/process/ProcessWrapper.java index 4ef5396..d92d06a 100644 --- a/src/main/java/org/springframework/data/gemfire/tests/process/ProcessWrapper.java +++ b/src/main/java/org/springframework/data/gemfire/tests/process/ProcessWrapper.java @@ -49,7 +49,7 @@ import org.springframework.util.Assert; * @author John Blum * @see Process * @see ProcessBuilder - * @since 1.5.0 + * @since 0.0.1 */ @SuppressWarnings("unused") public class ProcessWrapper { @@ -63,15 +63,16 @@ public class ProcessWrapper { protected final Logger log = Logger.getLogger(getClass().getName()); private final Process process; + private final ProcessConfiguration processConfiguration; - /* (non-Javadoc) */ public ProcessWrapper(Process process, ProcessConfiguration processConfiguration) { - Assert.notNull(process, "Process must not be null"); + + Assert.notNull(process, "Process is required"); 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 and executed" - + " must not be null"); + + " is required"); this.process = process; this.processConfiguration = processConfiguration; @@ -79,8 +80,8 @@ public class ProcessWrapper { init(); } - /* (non-Javadoc) */ private void init() { + newThread("Process OUT Stream Reader Thread", newProcessInputStreamReaderRunnable(process.getInputStream())).start(); @@ -90,15 +91,17 @@ public class ProcessWrapper { } } - /* (non-Javadoc) */ - protected Runnable newProcessInputStreamReaderRunnable(InputStream in) { + private Runnable newProcessInputStreamReaderRunnable(InputStream in) { + return () -> { + if (isRunning()) { + BufferedReader inputReader = new BufferedReader(new InputStreamReader(in)); try { for (String input = inputReader.readLine(); input != null; input = inputReader.readLine()) { - for (ProcessInputStreamListener listener : listeners) { + for (ProcessInputStreamListener listener : this.listeners) { listener.onInput(input); } } @@ -114,10 +117,10 @@ public class ProcessWrapper { }; } - /* (non-Javadoc) */ - protected Thread newThread(String name, Runnable task) { - Assert.hasText(name, "Thread name must be specified"); - Assert.notNull(task, "Thread task must not be null"); + private Thread newThread(String name, Runnable task) { + + Assert.hasText(name, "Thread name is required"); + Assert.notNull(task, "Thread task is required"); Thread thread = new Thread(task, name); @@ -127,73 +130,62 @@ public class ProcessWrapper { return thread; } - /* (non-Javadoc) */ public boolean isAlive() { return ProcessUtils.isAlive(process); } - /* (non-Javadoc) */ public boolean isNotAlive() { return !isAlive(); } - /* (non-Javadoc) */ public List getCommand() { - return processConfiguration.getCommand(); + return this.processConfiguration.getCommand(); } - /* (non-Javadoc) */ public String getCommandString() { - return processConfiguration.getCommandString(); + return this.processConfiguration.getCommandString(); } - /* (non-Javadoc) */ public Map getEnvironment() { - return processConfiguration.getEnvironment(); + return this.processConfiguration.getEnvironment(); } - /* (non-Javadoc) */ public int getPid() { return ProcessUtils.findAndReadPid(getWorkingDirectory()); } - /* (non-Javadoc) */ public int safeGetPid() { + try { return getPid(); } - catch (PidUnavailableException ignore) { + catch (PidNotFoundException ignore) { return -1; } } - /* (non-Javadoc) */ public boolean isRedirectingErrorStream() { - return processConfiguration.isRedirectingErrorStream(); + return this.processConfiguration.isRedirectingErrorStream(); } - /* (non-Javadoc) */ public boolean isNotRunning() { return !isRunning(); } - /* (non-Javadoc) */ public boolean isRunning() { - return ProcessUtils.isRunning(process); + return ProcessUtils.isRunning(this.process); } - /* (non-Javadoc) */ public File getWorkingDirectory() { - return processConfiguration.getWorkingDirectory(); + return this.processConfiguration.getWorkingDirectory(); } - /* (non-Javadoc) */ public int exitValue() { - return process.exitValue(); + return this.process.exitValue(); } - /* (non-Javadoc) */ public int safeExitValue() { + try { return exitValue(); } @@ -202,74 +194,75 @@ public class ProcessWrapper { } } - /* (non-Javadoc) */ public String readLogFile() throws IOException { + File[] logFiles = FileSystemUtils.listFiles(getWorkingDirectory(), - (path) -> (path != null && (path.isDirectory() || path.getAbsolutePath().endsWith(".log")))); + path -> (path != null && (path.isDirectory() || path.getAbsolutePath().endsWith(".log")))); if (logFiles.length > 0) { return readLogFile(logFiles[0]); } else { - throw new FileNotFoundException(String.format( - "No log files found in process's [%d] working directory [%s]", - safeGetPid(), getWorkingDirectory())); + throw new FileNotFoundException(String.format("No log files found in process's [%d] working directory [%s]", + safeGetPid(), getWorkingDirectory())); } } - /* (non-Javadoc) */ public String readLogFile(File log) throws IOException { return FileUtils.read(log); } - /* (non-Javadoc) */ public boolean register(ProcessInputStreamListener listener) { - return (listener != null && listeners.add(listener)); + return listener != null && listeners.add(listener); } - /* (non-Javadoc) */ public void registerShutdownHook() { Runtime.getRuntime().addShutdownHook(new Thread(this::shutdown)); } - /* (non-Javadoc) */ public void signal() { + try { - OutputStream outputStream = process.getOutputStream(); + + OutputStream outputStream = this.process.getOutputStream(); + outputStream.write("\n".getBytes()); outputStream.flush(); } - catch (IOException e) { - log.warning("Failed to signal process"); + catch (IOException cause) { - if (log.isLoggable(Level.FINE)) { - log.fine(ThrowableUtils.toString(e)); + this.log.warning("Failed to signal process"); + + if (this.log.isLoggable(Level.FINE)) { + this.log.fine(ThrowableUtils.toString(cause)); } } } /* (non-Javadoc) */ public void signalStop() { - try { - ProcessUtils.signalStop(process); - } - catch (IOException e) { - log.warning("Failed to signal the process to stop"); - if (log.isLoggable(Level.FINE)) { - log.fine(ThrowableUtils.toString(e)); + try { + ProcessUtils.signalStop(this.process); + } + catch (IOException cause) { + + this.log.warning("Failed to signal the process to stop"); + + if (this.log.isLoggable(Level.FINE)) { + this.log.fine(ThrowableUtils.toString(cause)); } } } - /* (non-Javadoc) */ public int stop() { return stop(DEFAULT_WAIT_TIME_MILLISECONDS); } - /* (non-Javadoc) */ public int stop(long milliseconds) { + if (isRunning()) { + boolean interrupted = false; int exitValue = -1; int pid = safeGetPid(); @@ -279,9 +272,10 @@ public class ProcessWrapper { ExecutorService executorService = Executors.newSingleThreadExecutor(); try { + Future futureExitValue = executorService.submit(() -> { - process.destroy(); - int localExitValue = process.waitFor(); + this.process.destroy(); + int localExitValue = this.process.waitFor(); exited.set(true); return localExitValue; }); @@ -289,16 +283,18 @@ public class ProcessWrapper { while (!exited.get() && System.currentTimeMillis() < timeout) { try { exitValue = futureExitValue.get(milliseconds, TimeUnit.MILLISECONDS); - log.info(String.format("Process [%s] has stopped%n", pid)); + this.log.info(String.format("Process [%s] has stopped%n", pid)); } catch (InterruptedException ignore) { interrupted = true; } } } - catch (TimeoutException e) { + catch (TimeoutException cause) { + exitValue = -1; - log.warning(String.format("Process [%1$d] did not stop within the allotted timeout of %2$d seconds%n", + + this.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) { @@ -319,10 +315,10 @@ public class ProcessWrapper { } } - /* (non-Javadoc) */ public int shutdown() { + if (isRunning()) { - log.info(String.format("Stopping process [%d]...%n", safeGetPid())); + this.log.info(String.format("Stopping process [%d]...%n", safeGetPid())); signalStop(); waitFor(); } @@ -330,17 +326,14 @@ public class ProcessWrapper { return stop(); } - /* (non-Javadoc) */ public boolean unregister(ProcessInputStreamListener listener) { - return listeners.remove(listener); + return this.listeners.remove(listener); } - /* (non-Javadoc) */ public void waitFor() { waitFor(DEFAULT_WAIT_TIME_MILLISECONDS); } - /* (non-Javadoc) */ public void waitFor(long milliseconds) { ThreadUtils.timedWait(milliseconds, 500, this::isRunning); }