/*
* Copyright 2013 Amazon Technologies, Inc.
*
* 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://aws.amazon.com/apache2.0
*
* This file 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 com.amazonaws.eclipse.dynamodb.testtool;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Collection;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.AbstractFileFilter;
import org.apache.commons.io.filefilter.IOFileFilter;
import org.apache.commons.io.filefilter.TrueFileFilter;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.launching.IVMInstall;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.console.ConsolePlugin;
import org.eclipse.ui.console.IConsole;
import org.eclipse.ui.console.IConsoleConstants;
import org.eclipse.ui.console.IConsoleManager;
import org.eclipse.ui.console.IConsoleView;
import org.eclipse.ui.console.MessageConsole;
import org.eclipse.ui.console.MessageConsoleStream;
import com.amazonaws.eclipse.core.AwsToolkitCore;
import com.amazonaws.eclipse.dynamodb.DynamoDBPlugin;
/**
* A DynamoDB Local Test Tool process.
*/
public class TestToolProcess {
private final IVMInstall jre;
private final File installDirectory;
private final int port;
private final Thread shutdownHook = new Thread() {
@Override
public void run() {
TestToolProcess.this.stopProcess();
}
};
private Process process;
/**
* Create a new {@code TestToolProcess}.
*
* @param jre the JRE to use to run DynamoDBLocal
* @param installDirectory the root install directory for DynamoDBLocal
* @param port the TCP port to bind to
*/
public TestToolProcess(final IVMInstall jre,
final File installDirectory,
final int port) {
this.jre = jre;
this.installDirectory = installDirectory;
this.port = port;
}
/**
* @return the port that this DynamoDBLocal process is listening on
*/
public int getPort() {
return port;
}
/**
* Start the DynamoDBLocal process.
*
* @param onExitAction optional action to be executed when the process
* exits
* @throws IOException if starting the process fails
*/
public synchronized void start(final Runnable onExitAction)
throws IOException {
if (process != null) {
throw new IllegalStateException("Already started!");
}
ProcessBuilder builder = new ProcessBuilder();
builder.directory(installDirectory);
builder.command(
jre.getInstallLocation().getAbsolutePath().concat("/bin/java"),
"-Djava.library.path=".concat(findLibraryDirectory().getAbsolutePath()),
"-jar",
"DynamoDBLocal.jar",
"--port",
Integer.toString(port)
);
// Drop STDERR into STDOUT so we can handle them together.
builder.redirectErrorStream(true);
// Register a shutdown hook to kill DynamoDBLocal if Eclipse exits.
Runtime.getRuntime().addShutdownHook(shutdownHook);
// Start the DynamoDBLocal process.
process = builder.start();
// Start a background thread to read any output from DynamoDBLocal
// and dump it to an IConsole.
new ConsoleOutputLogger(process.getInputStream(), onExitAction)
.start();
}
/**
* Searches within the install directory for the native libraries required
* by DyanmoDB Local (i.e. SQLite) and returns the directory containing the
* native libraries.
*
* @return The directory within the install directory where native libraries
* were found; otherwise, if no native libraries are found, the
* install directory is returned.
*/
private File findLibraryDirectory() {
// Mac and Linux libraries start with "libsqlite4java-" so
// use that pattern to identify the library directory
IOFileFilter fileFilter = new AbstractFileFilter() {
public boolean accept(File dir, String name) {
return name.startsWith("libsqlite4java-");
}
};
Collection<File> files = FileUtils.listFiles(installDirectory, fileFilter, TrueFileFilter.INSTANCE);
// Log a warning if we can't identify the library directory,
// and then just try to use the install directory
if (files == null || files.isEmpty()) {
Status status = new Status(IStatus.WARNING, DynamoDBPlugin.PLUGIN_ID,
"Unable to find DynamoDB Local native libraries in " + installDirectory);
AwsToolkitCore.getDefault().getLog().log(status);
return installDirectory;
}
return files.iterator().next().getParentFile();
}
/**
* Stop the DynamoDBLocal process and deregister the shutdown hook.
*/
public synchronized void stop() {
stopProcess();
Runtime.getRuntime().removeShutdownHook(shutdownHook);
}
/**
* Internal helper method to actually stop the DynamoDBLocal process.
*/
private void stopProcess() {
if (process != null) {
process.destroy();
process = null;
}
}
/**
* A background thread that reads output from the DynamoDBLocal process
* and pumps it to a console window.
*/
private static class ConsoleOutputLogger extends Thread {
private final BufferedReader input;
private final Runnable onExitAction;
/**
* Create a new logger.
*
* @param stream the input stream to read from
* @param onExitaction an optional action to be run when the process
* exits
*/
public ConsoleOutputLogger(final InputStream stream,
final Runnable onExitAction) {
this.input = new BufferedReader(new InputStreamReader(stream));
this.onExitAction = onExitAction;
super.setDaemon(true);
super.setName("DynamoDBLocal Console Output Logger");
}
@Override
public void run() {
MessageConsole console = findConsole();
displayConsole(console);
MessageConsoleStream stream = console.newMessageStream();
try {
while (true) {
String line = input.readLine();
if (line == null) {
stream.println("*** DynamoDB Local Exited ***");
break;
}
stream.println(line);
}
} catch (IOException exception) {
stream.println("Exception reading the output of "
+ "DynamoDB Local: " + exception.toString());
} finally {
try {
input.close();
} catch (IOException exception) {
exception.printStackTrace();
}
try {
stream.close();
} catch (IOException exception) {
exception.printStackTrace();
}
if (onExitAction != null) {
onExitAction.run();
}
}
}
/**
* Find or create a new console window for DynamoDBLocal's output.
*
* @return the console to log output to
*/
private MessageConsole findConsole() {
ConsolePlugin plugin = ConsolePlugin.getDefault();
IConsoleManager manager = plugin.getConsoleManager();
IConsole[] existing = manager.getConsoles();
for (int i = 0; i < existing.length; ++i) {
if (existing[i].getName().equals("DynamoDB Local")) {
return (MessageConsole) existing[i];
}
}
MessageConsole console = new MessageConsole("DynamoDB Local", null);
manager.addConsoles(new IConsole[] { console });
return console;
}
/**
* Display the given console if it's not already visible.
*
* @param console the console to display
*/
private void displayConsole(final IConsole console) {
Display.getDefault().asyncExec(new Runnable() {
public void run() {
IWorkbenchPage page = PlatformUI.getWorkbench()
.getActiveWorkbenchWindow()
.getActivePage();
try {
IConsoleView view = (IConsoleView)
page.showView(IConsoleConstants.ID_CONSOLE_VIEW);
view.display(console);
} catch (PartInitException exception) {
// Is there something more intelligent we should be
// doing with this?
exception.printStackTrace();
}
}
});
}
}
}