package org.eclipse.buckminster.executor.actor; import java.io.File; import java.io.IOException; import java.io.PrintStream; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.buckminster.core.CorePlugin; import org.eclipse.buckminster.core.actor.AbstractActor; import org.eclipse.buckminster.core.actor.IActionContext; import org.eclipse.buckminster.core.helpers.PropertyExpander; import org.eclipse.buckminster.core.helpers.TextUtils; import org.eclipse.buckminster.executor.Messages; import org.eclipse.buckminster.runtime.BuckminsterException; import org.eclipse.buckminster.runtime.Logger; import org.eclipse.buckminster.runtime.MonitorUtils; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.osgi.util.NLS; /** * This class declares a new Buckminster Actor that allow execution of commands * directly from the CSpec * * @author Guillaume CHATELET * */ public class ExecutorActor extends AbstractActor { private static final String EXECUTOR_ENV = "env"; //$NON-NLS-1$ private static final String EXECUTOR_EXEC_ACTION = "exec"; //$NON-NLS-1$ private static final String EXECUTOR_EXEC_DIR_ACTION = "execDir"; //$NON-NLS-1$ private static final String EXECUTOR_SHELL_ACTION = "shell"; //$NON-NLS-1$ private static final String EXECUTOR_NEW_ENVIRONMENT_ACTION = "newenvironment"; //$NON-NLS-1$ private static final String EXECUTOR_FAIL_ON_ERROR = "failonerror"; //$NON-NLS-1$ private static final String[] validProperties = { EXECUTOR_ENV, EXECUTOR_EXEC_ACTION, EXECUTOR_EXEC_DIR_ACTION, EXECUTOR_SHELL_ACTION, EXECUTOR_NEW_ENVIRONMENT_ACTION, EXECUTOR_FAIL_ON_ERROR }; private static final String PLUGIN_ID = "org.eclipse.buckminster.executor"; //$NON-NLS-1$ /** * Splits the environment variables but protects quoted parts * * @param env * @return */ public static String[] splitEnvironnementVariables(final String env) { final List<Integer> semicolonIndexes = indexesOf(env, ';'); final List<Integer> quoteIndexes = indexesOf(env, '\"'); if (quoteIndexes.size() % 2 != 0) throw new IllegalStateException(NLS.bind(Messages.odd_number_of_quoting_chars_in_0, env)); // removing semicolon indexes between quote indexes final Iterator<Integer> quoteItr = quoteIndexes.iterator(); while (quoteItr.hasNext()) { int min = quoteItr.next().intValue(); int max = quoteItr.next().intValue(); Iterator<Integer> semicolonItr = semicolonIndexes.iterator(); while (semicolonItr.hasNext()) { int i = semicolonItr.next().intValue(); if (i > min && i < max) semicolonItr.remove(); } } // splitting the variables final List<String> result = new ArrayList<String>(); int lastIndex = 0; for (int i : semicolonIndexes) { result.add(env.substring(lastIndex, i)); lastIndex = i + 1; } result.add(env.substring(lastIndex)); return result.toArray(new String[result.size()]); } /** * returns the list of indexes where you can find the character c in string * * @param string * @param c * @return */ private static List<Integer> indexesOf(String string, char c) { final List<Integer> list = new ArrayList<Integer>(); int fromIndex = 0; int index; while ((index = string.indexOf(c, fromIndex)) != -1) { list.add(new Integer(index)); fromIndex = index + 1; } return list; } @Override protected IStatus internalPerform(IActionContext ctx, IProgressMonitor monitor) throws CoreException { monitor = MonitorUtils.ensureNotNull(monitor); monitor.beginTask(null, 1); monitor.subTask(ctx.getAction().getQualifiedName()); try { checkProperties(); final String EXE = "[EXE] "; //$NON-NLS-1$ final PropertyExpander expander = new PropertyExpander(ctx); final PrintStream errorStream = ctx.getErrorStream(); final PrintStream outputStream = ctx.getOutputStream(); final File executionDir = getExecutionDir(ctx); final String command = expander.expand(prepareCommandLine()); final String[] env = prepareEnvironmentVariables(expander); CorePlugin.getLogger().info(EXE + NLS.bind(Messages.now_executing_0, command)); CorePlugin.getLogger().info(EXE + NLS.bind(Messages.in_directory_0, executionDir)); final Process proc = Runtime.getRuntime().exec(command, env, executionDir); // any error message ? final StreamGobblerRedirector errorGobbler = new StreamGobblerRedirector(proc.getErrorStream(), errorStream); // any output ? final StreamGobblerRedirector outputGobbler = new StreamGobblerRedirector(proc.getInputStream(), outputStream); // kick them off errorGobbler.start(); outputGobbler.start(); final int returnCode = proc.waitFor(); outputStream.flush(); if (returnCode != 0) { CorePlugin.getLogger().error(NLS.bind(Messages.program_0_exit_code_1, command, String.valueOf(returnCode))); if (getFailStatus()) return Status.CANCEL_STATUS; } } catch (IOException e) { Throwable t = BuckminsterException.unwind(e); CorePlugin.getLogger().error(t, t.toString()); return new Status(IStatus.ERROR, PLUGIN_ID, e.getMessage()); } catch (InterruptedException e) { Throwable t = BuckminsterException.unwind(e); CorePlugin.getLogger().error(t, t.toString()); return new Status(IStatus.ERROR, PLUGIN_ID, e.getMessage()); } catch (CoreException e) { Throwable t = BuckminsterException.unwind(e); CorePlugin.getLogger().error(t, t.toString()); throw e; } finally { monitor.done(); } return Status.OK_STATUS; } private void checkProperties() throws CoreException { final HashSet<String> validSet = new HashSet<String>(Arrays.asList(validProperties)); final Map<String, String> actorProperties = getActiveContext().getAction().getActorProperties(); final Set<String> keySet = actorProperties.keySet(); for (String property : keySet) { if (validSet.contains(property) == false) { final StringBuffer buffer = new StringBuffer(); for (String validProperty : validSet) buffer.append(validProperty).append(' '); throw new IllegalStateException(NLS.bind(Messages.actorProperty_0_invalid_valid_are_1, property, buffer.toString())); } } } /** * @return "exec" actorProperty or null if not set */ private String getExecCommand() { return TextUtils.notEmptyTrimmedString(this.getActorProperty(EXECUTOR_EXEC_ACTION)); } private File getExecutionDir(IActionContext ctx) throws CoreException { final String executionDir = TextUtils.notEmptyTrimmedString(this.getActorProperty(EXECUTOR_EXEC_DIR_ACTION)); final String componentLocation = ctx.getComponentLocation().toOSString(); if (executionDir == null) return new File(componentLocation); final File executionDirFile = new File(executionDir); if (executionDirFile.isAbsolute()) return executionDirFile; return new File(componentLocation + executionDir); } private boolean getFailStatus() { final String failOnErrorValue = this.getActorProperty(EXECUTOR_FAIL_ON_ERROR); if (failOnErrorValue == null) return true; return Boolean.parseBoolean(TextUtils.notEmptyTrimmedString(failOnErrorValue)); } /** * @return "shell" actorProperty or null if not set */ private String getShellCommand() { return TextUtils.notEmptyTrimmedString(this.getActorProperty(EXECUTOR_SHELL_ACTION)); } private String prepareCommandLine() throws CoreException { // verifying command relevance final String execCommand = getExecCommand(); final String shellCommand = getShellCommand(); if (execCommand == null && shellCommand == null) throwError(NLS.bind(Messages.actorProperty_at_least_one_0_1, EXECUTOR_EXEC_ACTION, EXECUTOR_SHELL_ACTION)); if (execCommand != null && shellCommand != null) throwError(NLS.bind(Messages.actorProperty_at_most_one_0_1, EXECUTOR_EXEC_ACTION, EXECUTOR_SHELL_ACTION)); if (execCommand != null) { return execCommand; } final String shell = ShellCommand.getShellCommand(); if (shell == null) throw new Error(NLS.bind(Messages.shell_interpreter_for_0_not_supported, ShellCommand.getOsName())); return shell + ' ' + shellCommand; } /** * Prepare the environment variables. Retrieve them from the "env" * actorProperties then tries to replace every variables by their value. If * an environment variable refers to a Property or a NamedPath then the * variable is replaced by the Property or NamedPath values * * @return a list of initialized environment variables * @throws CoreException */ private String[] prepareEnvironmentVariables(PropertyExpander expander) throws CoreException { final String ENV = "[ENV] "; //$NON-NLS-1$ final Set<String> envSet = new HashSet<String>(); final String envProperty = TextUtils.notEmptyTrimmedString(this.getActorProperty(EXECUTOR_ENV)); final boolean useEnvironment = !Boolean.parseBoolean(this.getActorProperty(EXECUTOR_NEW_ENVIRONMENT_ACTION)); CorePlugin.getLogger().info(ENV + NLS.bind(Messages.using_system_environment_0, String.valueOf(useEnvironment))); if (useEnvironment) { final Map<String, String> getenv = System.getenv(); for (String key : getenv.keySet()) envSet.add(key + '=' + getenv.get(key)); } if (envProperty != null) { final String[] split = splitEnvironnementVariables(envProperty); for (String env : split) envSet.add(expander.expand(env)); } Logger logger = CorePlugin.getLogger(); if (logger.isDebugEnabled() && envSet.isEmpty() == false) { for (String string : envSet) logger.debug("%sSetting environment variable %s%n", ENV, string); //$NON-NLS-1$ } return envSet.toArray(new String[envSet.size()]); } /** * Helper to generate an error * * @param message * @throws CoreException */ private void throwError(final String message) throws CoreException { throw new CoreException(new Status(IStatus.ERROR, PLUGIN_ID, message)); } }