1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 */
18
19 package org.apache.tools.ant.taskdefs;
20
21 import java.io.File;
22 import java.io.IOException;
23 import java.io.PrintStream;
24 import java.lang.reflect.InvocationTargetException;
25 import java.lang.reflect.Method;
26 import java.lang.reflect.Modifier;
27 import org.apache.tools.ant.AntClassLoader;
28 import org.apache.tools.ant.BuildException;
29 import org.apache.tools.ant.Project;
30 import org.apache.tools.ant.ProjectComponent;
31 import org.apache.tools.ant.Task;
32 import org.apache.tools.ant.taskdefs.condition.Os;
33 import org.apache.tools.ant.types.Commandline;
34 import org.apache.tools.ant.types.CommandlineJava;
35 import org.apache.tools.ant.types.Path;
36 import org.apache.tools.ant.types.Permissions;
37 import org.apache.tools.ant.util.JavaEnvUtils;
38 import org.apache.tools.ant.util.TimeoutObserver;
39 import org.apache.tools.ant.util.Watchdog;
40
41 /**
42 * Execute a Java class.
43 * @since Ant 1.2
44 */
45 public class ExecuteJava implements Runnable, TimeoutObserver {
46
47 private Commandline javaCommand = null;
48 private Path classpath = null;
49 private CommandlineJava.SysProperties sysProperties = null;
50 private Permissions perm = null;
51 private Method main = null;
52 private Long timeout = null;
53 private volatile Throwable caught = null;
54 private volatile boolean timedOut = false;
55 private Thread thread = null;
56
57 /**
58 * Set the Java "command" for this ExecuteJava.
59 * @param javaCommand the classname and arguments in a Commandline.
60 */
61 public void setJavaCommand(Commandline javaCommand) {
62 this.javaCommand = javaCommand;
63 }
64
65 /**
66 * Set the classpath to be used when running the Java class.
67 *
68 * @param p an Ant Path object containing the classpath.
69 */
70 public void setClasspath(Path p) {
71 classpath = p;
72 }
73
74 /**
75 * Set the system properties to use when running the Java class.
76 * @param s CommandlineJava system properties.
77 */
78 public void setSystemProperties(CommandlineJava.SysProperties s) {
79 sysProperties = s;
80 }
81
82 /**
83 * Set the permissions for the application run.
84 * @param permissions the Permissions to use.
85 * @since Ant 1.6
86 */
87 public void setPermissions(Permissions permissions) {
88 perm = permissions;
89 }
90
91 /**
92 * Set the stream to which all output (System.out as well as System.err)
93 * will be written.
94 * @param out the PrintStream where output should be sent.
95 * @deprecated since 1.4.x.
96 * manage output at the task level.
97 */
98 public void setOutput(PrintStream out) {
99 }
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", new Class[] {String[].class});
143 if (main == null) {
144 throw new BuildException("Could not find main() method in "
145 + classname);
146 }
147 if ((main.getModifiers() & Modifier.STATIC) == 0) {
148 throw new BuildException("main() method in " + classname
149 + " is not declared static");
150 }
151 if (timeout == null) {
152 run();
153 } else {
154 thread = new Thread(this, "ExecuteJava");
155 Task currentThreadTask
156 = project.getThreadTask(Thread.currentThread());
157 // XXX is the following really necessary? it is in the same thread group...
158 project.registerThreadTask(thread, currentThreadTask);
159 // if we run into a timeout, the run-away thread shall not
160 // make the VM run forever - if no timeout occurs, Ant's
161 // main thread will still be there to let the new thread
162 // finish
163 thread.setDaemon(true);
164 Watchdog w = new Watchdog(timeout.longValue());
165 w.addTimeoutObserver(this);
166 synchronized (this) {
167 thread.start();
168 w.start();
169 try {
170 wait();
171 } catch (InterruptedException e) {
172 // ignore
173 }
174 if (timedOut) {
175 project.log("Timeout: sub-process interrupted",
176 Project.MSG_WARN);
177 } else {
178 thread = null;
179 w.stop();
180 }
181 }
182 }
183 if (caught != null) {
184 throw caught;
185 }
186 } catch (BuildException e) {
187 throw e;
188 } catch (SecurityException e) {
189 throw e;
190 } catch (ThreadDeath e) {
191 // XXX could perhaps also call thread.stop(); not sure if anyone cares
192 throw e;
193 } catch (Throwable e) {
194 throw new BuildException(e);
195 } finally {
196 if (loader != null) {
197 loader.resetThreadContextLoader();
198 loader.cleanup();
199 loader = null;
200 }
201 if (sysProperties != null) {
202 sysProperties.restoreSystem();
203 }
204 }
205 }
206
207 /**
208 * Run this ExecuteJava in a Thread.
209 * @since Ant 1.5
210 */
211 public void run() {
212 final Object[] argument = {javaCommand.getArguments()};
213 try {
214 if (perm != null) {
215 perm.setSecurityManager();
216 }
217 main.invoke(null, argument);
218 } catch (InvocationTargetException e) {
219 Throwable t = e.getTargetException();
220 if (!(t instanceof InterruptedException)) {
221 caught = t;
222 } /* else { swallow, probably due to timeout } */
223 } catch (Throwable t) {
224 caught = t;
225 } finally {
226 if (perm != null) {
227 perm.restoreSecurityManager();
228 }
229 synchronized (this) {
230 notifyAll();
231 }
232 }
233 }
234
235 /**
236 * Mark timeout as having occurred.
237 * @param w the responsible Watchdog.
238 * @since Ant 1.5
239 */
240 public synchronized void timeoutOccured(Watchdog w) {
241 if (thread != null) {
242 timedOut = true;
243 thread.interrupt();
244 }
245 notifyAll();
246 }
247
248 /**
249 * Get whether the process was killed.
250 * @return <code>true</code> if the process was killed, false otherwise.
251 * @since 1.19, Ant 1.5
252 */
253 public synchronized boolean killedProcess() {
254 return timedOut;
255 }
256
257 /**
258 * Run the Java command in a separate VM, this does not give you
259 * the full flexibility of the Java task, but may be enough for
260 * simple needs.
261 * @param pc the ProjectComponent to use for logging, etc.
262 * @return the exit status of the subprocess.
263 * @throws BuildException on error.
264 * @since Ant 1.6.3
265 */
266 public int fork(ProjectComponent pc) throws BuildException {
267 CommandlineJava cmdl = new CommandlineJava();
268 cmdl.setClassname(javaCommand.getExecutable());
269 String[] args = javaCommand.getArguments();
270 for (int i = 0; i < args.length; i++) {
271 cmdl.createArgument().setValue(args[i]);
272 }
273 if (classpath != null) {
274 cmdl.createClasspath(pc.getProject()).append(classpath);
275 }
276 if (sysProperties != null) {
277 cmdl.addSysproperties(sysProperties);
278 }
279 Redirector redirector = new Redirector(pc);
280 Execute exe
281 = new Execute(redirector.createHandler(),
282 timeout == null
283 ? null
284 : new ExecuteWatchdog(timeout.longValue()));
285 exe.setAntRun(pc.getProject());
286 if (Os.isFamily("openvms")) {
287 setupCommandLineForVMS(exe, cmdl.getCommandline());
288 } else {
289 exe.setCommandline(cmdl.getCommandline());
290 }
291 try {
292 int rc = exe.execute();
293 redirector.complete();
294 return rc;
295 } catch (IOException e) {
296 throw new BuildException(e);
297 } finally {
298 timedOut = exe.killedProcess();
299 }
300 }
301
302 /**
303 * On VMS platform, we need to create a special java options file
304 * containing the arguments and classpath for the java command.
305 * The special file is supported by the "-V" switch on the VMS JVM.
306 *
307 * @param exe the Execute instance to alter.
308 * @param command the command-line.
309 */
310 public static void setupCommandLineForVMS(Execute exe, 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.createVmsJavaOptionFile(args);
318 //we mark the file to be deleted on exit.
319 //the alternative would be to cache the filename and delete
320 //after execution finished, which is much better for long-lived runtimes
321 //though spawning complicates things...
322 vmsJavaOptionFile.deleteOnExit();
323 String [] vmsCmd = {command[0], "-V", vmsJavaOptionFile.getPath()};
324 exe.setCommandline(vmsCmd);
325 } catch (IOException e) {
326 throw new BuildException("Failed to create a temporary file for \"-V\" switch");
327 }
328 }
329
330 }