001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018:
019: package org.apache.tools.ant.taskdefs;
020:
021: import java.io.File;
022: import java.io.IOException;
023: import java.io.PrintStream;
024: import java.lang.reflect.InvocationTargetException;
025: import java.lang.reflect.Method;
026: import java.lang.reflect.Modifier;
027: import org.apache.tools.ant.AntClassLoader;
028: import org.apache.tools.ant.BuildException;
029: import org.apache.tools.ant.Project;
030: import org.apache.tools.ant.ProjectComponent;
031: import org.apache.tools.ant.Task;
032: import org.apache.tools.ant.taskdefs.condition.Os;
033: import org.apache.tools.ant.types.Commandline;
034: import org.apache.tools.ant.types.CommandlineJava;
035: import org.apache.tools.ant.types.Path;
036: import org.apache.tools.ant.types.Permissions;
037: import org.apache.tools.ant.util.JavaEnvUtils;
038: import org.apache.tools.ant.util.TimeoutObserver;
039: import org.apache.tools.ant.util.Watchdog;
040:
041: /**
042: * Execute a Java class.
043: * @since Ant 1.2
044: */
045: public class ExecuteJava implements Runnable, TimeoutObserver {
046:
047: private Commandline javaCommand = null;
048: private Path classpath = null;
049: private CommandlineJava.SysProperties sysProperties = null;
050: private Permissions perm = null;
051: private Method main = null;
052: private Long timeout = null;
053: private volatile Throwable caught = null;
054: private volatile boolean timedOut = false;
055: private Thread thread = null;
056:
057: /**
058: * Set the Java "command" for this ExecuteJava.
059: * @param javaCommand the classname and arguments in a Commandline.
060: */
061: public void setJavaCommand(Commandline javaCommand) {
062: this .javaCommand = javaCommand;
063: }
064:
065: /**
066: * Set the classpath to be used when running the Java class.
067: *
068: * @param p an Ant Path object containing the classpath.
069: */
070: public void setClasspath(Path p) {
071: classpath = p;
072: }
073:
074: /**
075: * Set the system properties to use when running the Java class.
076: * @param s CommandlineJava system properties.
077: */
078: public void setSystemProperties(CommandlineJava.SysProperties s) {
079: sysProperties = s;
080: }
081:
082: /**
083: * Set the permissions for the application run.
084: * @param permissions the Permissions to use.
085: * @since Ant 1.6
086: */
087: public void setPermissions(Permissions permissions) {
088: perm = permissions;
089: }
090:
091: /**
092: * Set the stream to which all output (System.out as well as System.err)
093: * will be written.
094: * @param out the PrintStream where output should be sent.
095: * @deprecated since 1.4.x.
096: * manage output at the task level.
097: */
098: public void setOutput(PrintStream out) {
099: }
100:
101: /**
102: * Set the timeout for this ExecuteJava.
103: * @param timeout timeout as Long.
104: * @since Ant 1.5
105: */
106: public void setTimeout(Long timeout) {
107: this .timeout = timeout;
108: }
109:
110: /**
111: * Execute the Java class against the specified Ant Project.
112: * @param project the Project to use.
113: * @throws BuildException on error.
114: */
115: public void execute(Project project) throws BuildException {
116: final String classname = javaCommand.getExecutable();
117:
118: AntClassLoader loader = null;
119: try {
120: if (sysProperties != null) {
121: sysProperties.setSystem();
122: }
123: Class target = null;
124: try {
125: if (classpath == null) {
126: target = Class.forName(classname);
127: } else {
128: loader = project.createClassLoader(classpath);
129: loader.setParent(project.getCoreLoader());
130: loader.setParentFirst(false);
131: loader.addJavaLibraries();
132: loader.setIsolated(true);
133: loader.setThreadContextLoader();
134: loader.forceLoadClass(classname);
135: target = Class.forName(classname, true, loader);
136: }
137: } catch (ClassNotFoundException e) {
138: throw new BuildException("Could not find " + classname
139: + "." + " Make sure you have it in your"
140: + " classpath");
141: }
142: main = target.getMethod("main",
143: new Class[] { String[].class });
144: if (main == null) {
145: throw new BuildException(
146: "Could not find main() method in " + classname);
147: }
148: if ((main.getModifiers() & Modifier.STATIC) == 0) {
149: throw new BuildException("main() method in "
150: + classname + " is not declared static");
151: }
152: if (timeout == null) {
153: run();
154: } else {
155: thread = new Thread(this , "ExecuteJava");
156: Task currentThreadTask = project.getThreadTask(Thread
157: .currentThread());
158: // XXX is the following really necessary? it is in the same thread group...
159: project.registerThreadTask(thread, currentThreadTask);
160: // if we run into a timeout, the run-away thread shall not
161: // make the VM run forever - if no timeout occurs, Ant's
162: // main thread will still be there to let the new thread
163: // finish
164: thread.setDaemon(true);
165: Watchdog w = new Watchdog(timeout.longValue());
166: w.addTimeoutObserver(this );
167: synchronized (this ) {
168: thread.start();
169: w.start();
170: try {
171: wait();
172: } catch (InterruptedException e) {
173: // ignore
174: }
175: if (timedOut) {
176: project.log("Timeout: sub-process interrupted",
177: Project.MSG_WARN);
178: } else {
179: thread = null;
180: w.stop();
181: }
182: }
183: }
184: if (caught != null) {
185: throw caught;
186: }
187: } catch (BuildException e) {
188: throw e;
189: } catch (SecurityException e) {
190: throw e;
191: } catch (ThreadDeath e) {
192: // XXX could perhaps also call thread.stop(); not sure if anyone cares
193: throw e;
194: } catch (Throwable e) {
195: throw new BuildException(e);
196: } finally {
197: if (loader != null) {
198: loader.resetThreadContextLoader();
199: loader.cleanup();
200: loader = null;
201: }
202: if (sysProperties != null) {
203: sysProperties.restoreSystem();
204: }
205: }
206: }
207:
208: /**
209: * Run this ExecuteJava in a Thread.
210: * @since Ant 1.5
211: */
212: public void run() {
213: final Object[] argument = { javaCommand.getArguments() };
214: try {
215: if (perm != null) {
216: perm.setSecurityManager();
217: }
218: main.invoke(null, argument);
219: } catch (InvocationTargetException e) {
220: Throwable t = e.getTargetException();
221: if (!(t instanceof InterruptedException)) {
222: caught = t;
223: } /* else { swallow, probably due to timeout } */
224: } catch (Throwable t) {
225: caught = t;
226: } finally {
227: if (perm != null) {
228: perm.restoreSecurityManager();
229: }
230: synchronized (this ) {
231: notifyAll();
232: }
233: }
234: }
235:
236: /**
237: * Mark timeout as having occurred.
238: * @param w the responsible Watchdog.
239: * @since Ant 1.5
240: */
241: public synchronized void timeoutOccured(Watchdog w) {
242: if (thread != null) {
243: timedOut = true;
244: thread.interrupt();
245: }
246: notifyAll();
247: }
248:
249: /**
250: * Get whether the process was killed.
251: * @return <code>true</code> if the process was killed, false otherwise.
252: * @since 1.19, Ant 1.5
253: */
254: public synchronized boolean killedProcess() {
255: return timedOut;
256: }
257:
258: /**
259: * Run the Java command in a separate VM, this does not give you
260: * the full flexibility of the Java task, but may be enough for
261: * simple needs.
262: * @param pc the ProjectComponent to use for logging, etc.
263: * @return the exit status of the subprocess.
264: * @throws BuildException on error.
265: * @since Ant 1.6.3
266: */
267: public int fork(ProjectComponent pc) throws BuildException {
268: CommandlineJava cmdl = new CommandlineJava();
269: cmdl.setClassname(javaCommand.getExecutable());
270: String[] args = javaCommand.getArguments();
271: for (int i = 0; i < args.length; i++) {
272: cmdl.createArgument().setValue(args[i]);
273: }
274: if (classpath != null) {
275: cmdl.createClasspath(pc.getProject()).append(classpath);
276: }
277: if (sysProperties != null) {
278: cmdl.addSysproperties(sysProperties);
279: }
280: Redirector redirector = new Redirector(pc);
281: Execute exe = new Execute(redirector.createHandler(),
282: timeout == null ? null : new ExecuteWatchdog(timeout
283: .longValue()));
284: exe.setAntRun(pc.getProject());
285: if (Os.isFamily("openvms")) {
286: setupCommandLineForVMS(exe, cmdl.getCommandline());
287: } else {
288: exe.setCommandline(cmdl.getCommandline());
289: }
290: try {
291: int rc = exe.execute();
292: redirector.complete();
293: return rc;
294: } catch (IOException e) {
295: throw new BuildException(e);
296: } finally {
297: timedOut = exe.killedProcess();
298: }
299: }
300:
301: /**
302: * On VMS platform, we need to create a special java options file
303: * containing the arguments and classpath for the java command.
304: * The special file is supported by the "-V" switch on the VMS JVM.
305: *
306: * @param exe the Execute instance to alter.
307: * @param command the command-line.
308: */
309: public static void setupCommandLineForVMS(Execute exe,
310: String[] command) {
311: //Use the VM launcher instead of shell launcher on VMS
312: exe.setVMLauncher(true);
313: File vmsJavaOptionFile = null;
314: try {
315: String[] args = new String[command.length - 1];
316: System.arraycopy(command, 1, args, 0, command.length - 1);
317: vmsJavaOptionFile = JavaEnvUtils
318: .createVmsJavaOptionFile(args);
319: //we mark the file to be deleted on exit.
320: //the alternative would be to cache the filename and delete
321: //after execution finished, which is much better for long-lived runtimes
322: //though spawning complicates things...
323: vmsJavaOptionFile.deleteOnExit();
324: String[] vmsCmd = { command[0], "-V",
325: vmsJavaOptionFile.getPath() };
326: exe.setCommandline(vmsCmd);
327: } catch (IOException e) {
328: throw new BuildException(
329: "Failed to create a temporary file for \"-V\" switch");
330: }
331: }
332:
333: }
|