/*******************************************************************************
* Copyright (c) 2012 VMWare, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* VMWare, Inc. - initial API and implementation
*******************************************************************************/
package org.grails.ide.eclipse.longrunning.process;
import static org.grails.ide.eclipse.runtime.shared.longrunning.GrailsProcessConstants.ACK_BAD;
import static org.grails.ide.eclipse.runtime.shared.longrunning.GrailsProcessConstants.ACK_OK;
import static org.grails.ide.eclipse.runtime.shared.longrunning.GrailsProcessConstants.BEGIN_COMMAND;
import static org.grails.ide.eclipse.runtime.shared.longrunning.GrailsProcessConstants.CHANGE_DIR;
import static org.grails.ide.eclipse.runtime.shared.longrunning.GrailsProcessConstants.COMMAND_DEPENDENCY_FILE;
import static org.grails.ide.eclipse.runtime.shared.longrunning.GrailsProcessConstants.COMMAND_UNPARSED;
import static org.grails.ide.eclipse.runtime.shared.longrunning.GrailsProcessConstants.CONSOLE_ERR;
import static org.grails.ide.eclipse.runtime.shared.longrunning.GrailsProcessConstants.CONSOLE_OUT;
import static org.grails.ide.eclipse.runtime.shared.longrunning.GrailsProcessConstants.END_COMMAND;
import static org.grails.ide.eclipse.runtime.shared.longrunning.GrailsProcessConstants.EXIT;
import static org.grails.ide.eclipse.runtime.shared.longrunning.GrailsProcessConstants.PROTOCOL_HEADER_LEN;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.lang.reflect.InvocationTargetException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import org.grails.ide.api.GrailsConnector;
import org.grails.ide.api.GrailsToolingAPI;
import org.grails.ide.api.impl.GrailsToolingAPIImpl;
import org.grails.ide.eclipse.runtime.GrailsBuildSettingsDependencyExtractor;
import org.grails.ide.eclipse.runtime.GrailsEclipseConsole;
import org.grails.ide.eclipse.runtime.shared.longrunning.CommandInput;
import org.grails.ide.eclipse.runtime.shared.longrunning.FlushingPrintStream;
import org.grails.ide.eclipse.runtime.shared.longrunning.PrefixedOutputStream;
import org.grails.ide.eclipse.runtime.shared.longrunning.ProtocolException;
import org.grails.ide.eclipse.runtime.shared.longrunning.SafeProcess;
/**
* Implements a 'remote' GrailsProcess that can be kept running to execute sequences of
* Grails commands, without having to restart and reinitialise Grails each time.
* <p>
* Note that this process runs outside of Eclipse, in a separate JVM. It should not be
* instantiated directly by Eclipse/STS plugins. See the {@link GrailsClient} and
* {@link GrailsProcessManager} to create and interact with external GrailsProcess
* instances.
*
* @since 2.6
* @author Kris De Volder
*/
public class GrailsProcess extends SafeProcess {
// We will be redirecting the "System.XXX" streams... To avoid confusion, keep a reference to
// the originals so we can always get to them...
private static final PrintStream system_out = System.out;
//private static final PrintStream system_err = System.err;
private static final InputStream system_in = System.in;
private static boolean DEBUG = false;
private void debug(String msg) {
if (DEBUG) {
System.out.println("%debug "+msg);
}
}
public static void main(String[] args) throws UnknownHostException {
//https://issuetracker.springsource.com/browse/STS-3258
//On windows it has been observed that the Grails process gets stuck calling InetAddress.getLocalHost.
//No idea why this happens. But it seems that if we call it here first then all is well later on.
System.out.println("Starting process on "+InetAddress.getLocalHost());
try {
int i = 0;
while (i < args.length) {
if (args[i].equals("--debug")) {
DEBUG = true;
i++;
}
else {
i++;
}
}
GrailsProcess process = new GrailsProcess();
process.run();
System.exit(0); //Make sure we exit, even if Grails may have started some non-daemon threads (Yes, it seems like it did and without
//calling System.exit the process doesn't terminate.
} catch (Throwable e) {
e.printStackTrace(System.out);
System.exit(-1);
}
}
private BufferedReader commands;
private GrailsToolingAPI API = GrailsToolingAPIImpl.INSTANCE; //TODO: Do we need a way to 'switch' API implementations?
private GrailsConnector grails = null; // Set once we know we have determined the baseDir.
public GrailsProcess() throws IOException {
System.setOut(new FlushingPrintStream(new PrefixedOutputStream(CONSOLE_OUT, system_out)));
System.setErr(new FlushingPrintStream(new PrefixedOutputStream(CONSOLE_ERR, system_out)));
commands = new BufferedReader(new InputStreamReader(system_in));
if (!DEBUG) {
//Killing the process after a timeout interferes with debugging and stepping
new HeartBeatMonitor().start();
}
}
private void run() throws IOException {
try {
heartBeat();
boolean loop = true;
while (loop) {
String line = commands.readLine();
if (line.startsWith(BEGIN_COMMAND)) {
ExternalGrailsCommand cmd = readCommand();
CommandInput cmdInput = new CommandInput(commands);
System.setIn(cmdInput.getInputStream());
int code = -999;
try {
code = grails().executeCommand(cmd.getCommandLine(), new GrailsEclipseConsole());
heartBeat();
if (code==0) {
writeDependencyFile(cmd);
}
} catch (Throwable e) {
e.printStackTrace(System.err);
} finally {
System.setIn(system_in);
println("\n"+END_COMMAND+code);
cmdInput.terminate();
}
} else if (line.startsWith(CHANGE_DIR)) {
File newBaseDir = new File(line.substring(PROTOCOL_HEADER_LEN));
if (changeDir(newBaseDir)) {
println(ACK_OK);
} else {
println(ACK_BAD);
System.exit(-1); //changing dir is too flaky, will need to shutdown and restart this process.
}
} else if (line.equals(EXIT)) {
loop = false;
println(ACK_OK);
} else {
throw new ProtocolException("Expecting "+BEGIN_COMMAND+" or "+EXIT+" but got '"+line+"'");
}
}
} catch (IOException e) {
throw e;
} catch (Exception e) {
throw new Error(e);
}
}
private GrailsConnector grails() {
if (grails==null) {
//For now, it is assumed that the 'baseDir' is the directory the process was launched in.
grails = API.connect(new File(System.getProperty("user.dir")));
}
return grails;
}
private boolean changeDir(File newBaseDir) {
if (newBaseDir.equals(getCurrentBaseDir())) {
return true; //already in correct dir... nothing to do.
}
//Either we have no connector yet, or it is connected to the wrong base directory.
//so try to create a new connector for the newBaseDir.
//Note: implementation doesn't currently allow 'reconnecting' to a different directory.
try {
grails = API.connect(newBaseDir);
return true;
} catch (Exception e) {
//e.printStackTrace(System.err);
return false;
}
}
private File getCurrentBaseDir() {
if (grails!=null) {
return grails.getBaseDir();
}
return null;
}
private void writeDependencyFile(ExternalGrailsCommand cmd) {
String file = cmd.getDependencyFile();
if (file!=null) {
GrailsBuildSettingsDependencyExtractor extractor = new GrailsBuildSettingsDependencyExtractor(grails.getBuildSettings());
try {
extractor.writeDependencyFile(file);
} catch (Exception e) {
log(e);
}
}
}
// private void welcomeMessage(ExternalGrailsCommand script) {
// println(CONSOLE_OUT+"Welcome to Grails "+ getGrailsVersion());
// println(CONSOLE_OUT+"Using STS Longrunning Grails Process!");
// println(CONSOLE_OUT+"command: "+script);
// }
//
private void log(Exception e) {
e.printStackTrace(System.err);
}
private ExternalGrailsCommand readCommand() throws IOException, SecurityException, IllegalArgumentException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, NoSuchFieldException {
debug(">>> Reading command");
ExternalGrailsCommand command = new ExternalGrailsCommand();
String line = commands.readLine();
debug("input> "+line);
while (line!=null && !line.startsWith(END_COMMAND)) {
// if (line.startsWith(COMMAND_SCRIPT_NAME)) {
// command.setScriptName(line.substring(PROTOCOL_HEADER_LEN));
// } else if (line.startsWith(COMMAND_ARGS)) {
// command.setArgs(line.substring(PROTOCOL_HEADER_LEN));
// } else if (line.startsWith(COMMAND_ENV)) {
// command.setEnv(line.substring(PROTOCOL_HEADER_LEN));
if (line.startsWith(COMMAND_UNPARSED)) {
command.parse(line.substring(PROTOCOL_HEADER_LEN));
} else if (line.startsWith(COMMAND_DEPENDENCY_FILE)) {
command.setDependencyFile(line.substring(PROTOCOL_HEADER_LEN));
} else {
throw new ProtocolException("Unexpected input: "+line);
}
line = commands.readLine();
debug("input> "+line);
}
debug("<<< Reading command");
return command;
}
private void println(String msg) {
system_out.println(msg);
system_out.flush();
}
}