blob: 6803eb80976d167201a9e4d898e453506369ad2c [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License 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 org.apache.tools.ant.taskdefs;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import org.apache.tools.ant.AntClassLoader;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.ProjectComponent;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.taskdefs.condition.Os;
import org.apache.tools.ant.types.Commandline;
import org.apache.tools.ant.types.CommandlineJava;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.types.Permissions;
import org.apache.tools.ant.util.JavaEnvUtils;
import org.apache.tools.ant.util.TimeoutObserver;
import org.apache.tools.ant.util.Watchdog;
/**
* Execute a Java class.
* @since Ant 1.2
*/
public class ExecuteJava implements Runnable, TimeoutObserver {
private Commandline javaCommand = null;
private Path classpath = null;
private CommandlineJava.SysProperties sysProperties = null;
private Permissions perm = null;
private Method main = null;
private Long timeout = null;
private volatile Throwable caught = null;
private volatile boolean timedOut = false;
private Thread thread = null;
/**
* Set the Java "command" for this ExecuteJava.
* @param javaCommand the classname and arguments in a Commandline.
*/
public void setJavaCommand(Commandline javaCommand) {
this.javaCommand = javaCommand;
}
/**
* Set the classpath to be used when running the Java class.
*
* @param p an Ant Path object containing the classpath.
*/
public void setClasspath(Path p) {
classpath = p;
}
/**
* Set the system properties to use when running the Java class.
* @param s CommandlineJava system properties.
*/
public void setSystemProperties(CommandlineJava.SysProperties s) {
sysProperties = s;
}
/**
* Set the permissions for the application run.
* @param permissions the Permissions to use.
* @since Ant 1.6
*/
public void setPermissions(Permissions permissions) {
perm = permissions;
}
/**
* Set the stream to which all output (System.out as well as System.err)
* will be written.
* @param out the PrintStream where output should be sent.
* @deprecated since 1.4.x.
* manage output at the task level.
*/
public void setOutput(PrintStream out) {
}
/**
* Set the timeout for this ExecuteJava.
* @param timeout timeout as Long.
* @since Ant 1.5
*/
public void setTimeout(Long timeout) {
this.timeout = timeout;
}
/**
* Execute the Java class against the specified Ant Project.
* @param project the Project to use.
* @throws BuildException on error.
*/
public void execute(Project project) throws BuildException {
final String classname = javaCommand.getExecutable();
AntClassLoader loader = null;
try {
if (sysProperties != null) {
sysProperties.setSystem();
}
Class target = null;
try {
if (classpath == null) {
target = Class.forName(classname);
} else {
loader = project.createClassLoader(classpath);
loader.setParent(project.getCoreLoader());
loader.setParentFirst(false);
loader.addJavaLibraries();
loader.setIsolated(true);
loader.setThreadContextLoader();
loader.forceLoadClass(classname);
target = Class.forName(classname, true, loader);
}
} catch (ClassNotFoundException e) {
throw new BuildException("Could not find " + classname + "."
+ " Make sure you have it in your"
+ " classpath");
}
main = target.getMethod("main", new Class[] {String[].class});
if (main == null) {
throw new BuildException("Could not find main() method in "
+ classname);
}
if ((main.getModifiers() & Modifier.STATIC) == 0) {
throw new BuildException("main() method in " + classname
+ " is not declared static");
}
if (timeout == null) {
run();
} else {
thread = new Thread(this, "ExecuteJava");
Task currentThreadTask
= project.getThreadTask(Thread.currentThread());
// XXX is the following really necessary? it is in the same thread group...
project.registerThreadTask(thread, currentThreadTask);
// if we run into a timeout, the run-away thread shall not
// make the VM run forever - if no timeout occurs, Ant's
// main thread will still be there to let the new thread
// finish
thread.setDaemon(true);
Watchdog w = new Watchdog(timeout.longValue());
w.addTimeoutObserver(this);
synchronized (this) {
thread.start();
w.start();
try {
wait();
} catch (InterruptedException e) {
// ignore
}
if (timedOut) {
project.log("Timeout: sub-process interrupted",
Project.MSG_WARN);
} else {
thread = null;
w.stop();
}
}
}
if (caught != null) {
throw caught;
}
} catch (BuildException e) {
throw e;
} catch (SecurityException e) {
throw e;
} catch (ThreadDeath e) {
// XXX could perhaps also call thread.stop(); not sure if anyone cares
throw e;
} catch (Throwable e) {
throw new BuildException(e);
} finally {
if (loader != null) {
loader.resetThreadContextLoader();
loader.cleanup();
loader = null;
}
if (sysProperties != null) {
sysProperties.restoreSystem();
}
}
}
/**
* Run this ExecuteJava in a Thread.
* @since Ant 1.5
*/
public void run() {
final Object[] argument = {javaCommand.getArguments()};
try {
if (perm != null) {
perm.setSecurityManager();
}
main.invoke(null, argument);
} catch (InvocationTargetException e) {
Throwable t = e.getTargetException();
if (!(t instanceof InterruptedException)) {
caught = t;
} /* else { swallow, probably due to timeout } */
} catch (Throwable t) {
caught = t;
} finally {
if (perm != null) {
perm.restoreSecurityManager();
}
synchronized (this) {
notifyAll();
}
}
}
/**
* Mark timeout as having occurred.
* @param w the responsible Watchdog.
* @since Ant 1.5
*/
public synchronized void timeoutOccured(Watchdog w) {
if (thread != null) {
timedOut = true;
thread.interrupt();
}
notifyAll();
}
/**
* Get whether the process was killed.
* @return <code>true</code> if the process was killed, false otherwise.
* @since 1.19, Ant 1.5
*/
public synchronized boolean killedProcess() {
return timedOut;
}
/**
* Run the Java command in a separate VM, this does not give you
* the full flexibility of the Java task, but may be enough for
* simple needs.
* @param pc the ProjectComponent to use for logging, etc.
* @return the exit status of the subprocess.
* @throws BuildException on error.
* @since Ant 1.6.3
*/
public int fork(ProjectComponent pc) throws BuildException {
CommandlineJava cmdl = new CommandlineJava();
cmdl.setClassname(javaCommand.getExecutable());
String[] args = javaCommand.getArguments();
for (int i = 0; i < args.length; i++) {
cmdl.createArgument().setValue(args[i]);
}
if (classpath != null) {
cmdl.createClasspath(pc.getProject()).append(classpath);
}
if (sysProperties != null) {
cmdl.addSysproperties(sysProperties);
}
Redirector redirector = new Redirector(pc);
Execute exe
= new Execute(redirector.createHandler(),
timeout == null
? null
: new ExecuteWatchdog(timeout.longValue()));
exe.setAntRun(pc.getProject());
if (Os.isFamily("openvms")) {
setupCommandLineForVMS(exe, cmdl.getCommandline());
} else {
exe.setCommandline(cmdl.getCommandline());
}
try {
int rc = exe.execute();
redirector.complete();
return rc;
} catch (IOException e) {
throw new BuildException(e);
} finally {
timedOut = exe.killedProcess();
}
}
/**
* On VMS platform, we need to create a special java options file
* containing the arguments and classpath for the java command.
* The special file is supported by the "-V" switch on the VMS JVM.
*
* @param exe the Execute instance to alter.
* @param command the command-line.
*/
public static void setupCommandLineForVMS(Execute exe, String[] command) {
//Use the VM launcher instead of shell launcher on VMS
exe.setVMLauncher(true);
File vmsJavaOptionFile = null;
try {
String [] args = new String[command.length - 1];
System.arraycopy(command, 1, args, 0, command.length - 1);
vmsJavaOptionFile = JavaEnvUtils.createVmsJavaOptionFile(args);
//we mark the file to be deleted on exit.
//the alternative would be to cache the filename and delete
//after execution finished, which is much better for long-lived runtimes
//though spawning complicates things...
vmsJavaOptionFile.deleteOnExit();
String [] vmsCmd = {command[0], "-V", vmsJavaOptionFile.getPath()};
exe.setCommandline(vmsCmd);
} catch (IOException e) {
throw new BuildException("Failed to create a temporary file for \"-V\" switch");
}
}
}