/******************************************************************************* * 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_ARGS; import static org.grails.ide.eclipse.runtime.shared.longrunning.GrailsProcessConstants.COMMAND_DEPENDENCY_FILE; import static org.grails.ide.eclipse.runtime.shared.longrunning.GrailsProcessConstants.COMMAND_ENV; import static org.grails.ide.eclipse.runtime.shared.longrunning.GrailsProcessConstants.COMMAND_SCRIPT_NAME; 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 grails.util.BuildSettings; 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.URLClassLoader; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import org.codehaus.groovy.grails.cli.GrailsScriptRunner; import org.grails.ide.eclipse.runtime.GrailsBuildSettingsDependencyExtractor; 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 static boolean IS_GRAILS_14 = false; private void debug(String msg) { if (DEBUG) { System.out.println("%debug "+msg); } } public static void main(String[] args) { try { IS_GRAILS_14 = true; int i = 0; while (i < args.length) { if (args[i].equals("--debug")) { DEBUG = true; i++; // } else if (args[i].equals("--is14")) { i++; } else { i++; } } GrailsProcess process = new GrailsProcess(); process.run(); } catch (Throwable e) { e.printStackTrace(); System.exit(-1); } } private GrailsScriptRunner scriptRunner; private BuildSettings buildSettings; private BufferedReader commands; public GrailsProcess() throws IOException { saveSystemProperties(); commands = new BufferedReader(new InputStreamReader(system_in)); buildSettings = createBuildSettings(); scriptRunner = new GrailsScriptRunner(buildSettings); System.setOut(new FlushingPrintStream(new PrefixedOutputStream(CONSOLE_OUT, system_out))); System.setErr(new FlushingPrintStream(new PrefixedOutputStream(CONSOLE_ERR, system_out))); File grailsHome = buildSettings.getGrailsHome(); debug("Starting Remote Script runner for Grails " + buildSettings.getGrailsVersion()); debug("Grails home is " + (grailsHome == null ? "not set" : "set to: " + grailsHome) + '\n'); if (!DEBUG) { //Killing the process after a timeout interferes with debugging and stepping new HeartBeatMonitor().start(); } } private BuildSettings createBuildSettings() { BuildSettings buildSettings = null; // Get hold of the GRAILS_HOME environment variable if it is available. String grailsHome = System.getProperty("grails.home"); // Now we can pick up the Grails version from the Ant project properties. try { buildSettings = new BuildSettings(new File(grailsHome)); if (IS_GRAILS_14) { //buildSettings.setRootLoader((URLClassLoader) GrailsScriptRunner.class.getClassLoader()); buildSettings.setRootLoader((URLClassLoader) GrailsProcess.class.getClassLoader()); } } catch (Exception e) { throw new Error("An error occurred loading the grails-app/conf/BuildConfig.groovy file: " + e.getMessage()); } return buildSettings; } private void run() throws IOException { try { heartBeat(); System.setProperty("grails.disable.exit", "true"); boolean loop = true; while (loop) { resetSystemProperties(); 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 = executeCommand(cmd); heartBeat(); if (code==0) { writeDependencyFile(cmd); } } finally { System.setIn(system_in); println(END_COMMAND+code); cmdInput.terminate(); } } else if (line.startsWith(CHANGE_DIR)) { File current = buildSettings.getBaseDir(); File newBaseDir = new File(line.substring(PROTOCOL_HEADER_LEN)); //Note: we have to send back some "ack" code so the client can properly synchronize if (newBaseDir.equals(current)) { 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); } } Map<String,String> savedSystemProps = null; private void saveSystemProperties() { try { savedSystemProps = new HashMap<String, String>(); Properties currentProps = System.getProperties(); Enumeration<?> enumeration = currentProps.propertyNames(); while (enumeration.hasMoreElements()) { String prop = (String)enumeration.nextElement(); savedSystemProps.put(prop, System.getProperty(prop)); } } catch (Exception e) { savedSystemProps = null; } } private void resetSystemProperties() { if (savedSystemProps!=null) { Properties currentProps = System.getProperties(); //1: clear any properties that got added since we saved... Enumeration<?> enumeration = currentProps.propertyNames(); while (enumeration.hasMoreElements()) { String prop = (String)enumeration.nextElement(); if (!savedSystemProps.containsKey(prop)) { System.clearProperty(prop); } } //2: reset values of any properties that were set when we saved for (Entry<String, String> entry : savedSystemProps.entrySet()) { if (entry.getValue()!=null) { System.setProperty(entry.getKey(), entry.getValue()); } } } } private int executeCommand(ExternalGrailsCommand script) { welcomeMessage(script); int code; if (script.env==null) { code = scriptRunner.executeCommand(script.scriptName, script.args); } else { code = scriptRunner.executeCommand(script.scriptName, script.args, script.env); } return code; } private void welcomeMessage(ExternalGrailsCommand script) { println(CONSOLE_OUT+"Welcome to Grails "+ buildSettings.getGrailsVersion()); println(CONSOLE_OUT+"Using STS Longrunning Grails Process!"); println(CONSOLE_OUT+"script and args: "+script); } private void writeDependencyFile(ExternalGrailsCommand cmd) { String file = cmd.getDependencyFile(); if (file!=null) { GrailsBuildSettingsDependencyExtractor extractor = new GrailsBuildSettingsDependencyExtractor(buildSettings); try { extractor.writeDependencyFile(file); } catch (Exception e) { log(e); } } } 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)); } else 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(); } }