2

I'm trying to run a groovy script, from within a java program, as a separate process (in order to avoid jar collision issues).

This is what I have so far:

public static void runGroovyScript(Path scriptPath, String... args) {
    try {
        List<String> argsList = newArrayList();
        argsList.add("groovy");
        argsList.add(scriptPath.toAbsolutePath().toString());
        Collections.addAll(argsList, args);

        Process process = Runtime.getRuntime().exec(argsList.toArray(new String[argsList.size()]));
        // Note - out input is the process' output
        String input = Streams.asString(process.getInputStream());
        String error = Streams.asString(process.getErrorStream());

        logger.info("Groovy output for " + Arrays.toString(args) + "\r\n" + input);
        logger.info("Groovy error for " + Arrays.toString(args) + "\r\n" + error);

        int returnValue = process.waitFor();
        if (returnValue != 0) {
            throw new RuntimeException("Groovy process returned " + returnValue);
        }
    } catch (Throwable e) {
        throw new RuntimeException("Failure running build script: " + scriptPath + " " + Joiner.on(" ").join(args), e);
    }
}

The problem, of course, is that groovy is not a recognized command. It works from the command line because of the PATH environment variable, and the resolving that cmd.exe does. On linux, there is a different resolving mechanism. What is a platform-independent way to find the groovy executable, in order to pass it along to Runtime.exec()?

ripper234
  • 222,824
  • 274
  • 634
  • 905

3 Answers3

1

A clean way would be to pass the absolute path of the executable to your application as some kind of a configuration parameter.

You could also parse PATH environment variable and search yourself, but:

  • Different platforms can have different mechanisms how to search for executables. For example, they use different path separator characters, you need to deal with that.
  • It's a kind of a security issue. An attacker could pass to your program PATH environment variable pointing to a malicious program named groovy.

I'd suggest to take a different approach. You could use a separate ClassLoader for loading your groovy script. Advanages:

  1. You'll avoid problems JAR collision issues as well.
  2. But you won't need to spawn any external processes.
  3. You could also restrict actions that the script is allowed to do using a custom SecurityManager.
  4. You could better communicate with the script - using method calls instead of just stdin/out.

See also:


You could also combine it with Java Scripting API. This is perhaps the most robust and flexible solution. For this, see

Community
  • 1
  • 1
Petr
  • 62,528
  • 13
  • 153
  • 317
  • We actually fiddled with another class loader, but no cigar. What we did was define the location of the groovy exeuctable in a system variable, and add "cmd /c" for Windows. I'll post our code as an answer. – ripper234 Aug 29 '12 at 06:59
1

The java-sandbox allows to remotely execute sandboxed code. Have a look at http://blog.datenwerke.net/2013/06/sandboxing-groovy-with-java-sandbox.html.

Arno Mittelbach
  • 952
  • 5
  • 12
0

We actually fiddled with another class loader, but no cigar. What we did was define the location of the groovy exeuctable in a system variable, and add "cmd /c" for Windows:

import com.google.common.base.Joiner;
import org.apache.commons.lang.SystemUtils;
import org.apache.log4j.Logger;

import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import static com.google.common.collect.Lists.newArrayList;
import static org.apache.ivy.util.Checks.checkNotNull;

public class GroovyRunner {
    private final static Logger logger = LoggerHelper.getLogger();

    public static void runGroovyScript(Path scriptPath, String... args) {
        try {
            List<String> argsList = newArrayList();
            String groovyPath = System.getenv("PLAY_GROOVY_PATH");

            if (SystemUtils.IS_OS_WINDOWS) {
                // Window, no easy default for PLAY_GROOVY_PATH
                checkNotNull(groovyPath, "Missing Env Var 'PLAY_GROOVY_PATH'");

                argsList.add("cmd");
                argsList.add("/c");
                argsList.add(groovyPath);
            } else {
                if (groovyPath == null) {
                    // Provide a reasonable default for linux
                    groovyPath = "/usr/bin/groovy";
                }
                argsList.add(groovyPath);
            }

            argsList.add(scriptPath.toAbsolutePath().toString());
            Collections.addAll(argsList, args);

            String join = Collections3.join(argsList, " ");

            ExecCommand process = new ExecCommand(join);

            // Note - out input is the process' output
            String output = process.getOutput();
            String error = process.getError();

            logger.info("Groovy output for " + Arrays.toString(args) + "\r\n" + output);
            logger.info("Groovy error for " + Arrays.toString(args) + "\r\n" + error);


            if (process.getReturnValue() != 0) {
                throw new RuntimeException("Groovy process returned " + process.getReturnValue());
            }
        } catch (Throwable e) {
            throw new RuntimeException("Failure running groovy script: " + scriptPath + " " + Joiner.on(" ").join(args), e);
        }
    }
}
ripper234
  • 222,824
  • 274
  • 634
  • 905