1. /*
  2. * Copyright 2000-2004 The Apache Software Foundation
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. *
  16. */
  17. package org.apache.tools.ant.taskdefs;
  18. import java.io.File;
  19. import java.io.IOException;
  20. import java.io.PrintWriter;
  21. import java.io.StringWriter;
  22. import java.util.Vector;
  23. import org.apache.tools.ant.BuildException;
  24. import org.apache.tools.ant.ExitException;
  25. import org.apache.tools.ant.Project;
  26. import org.apache.tools.ant.Task;
  27. import org.apache.tools.ant.types.Commandline;
  28. import org.apache.tools.ant.types.CommandlineJava;
  29. import org.apache.tools.ant.types.Environment;
  30. import org.apache.tools.ant.types.Path;
  31. import org.apache.tools.ant.types.PropertySet;
  32. import org.apache.tools.ant.types.Reference;
  33. import org.apache.tools.ant.types.Assertions;
  34. import org.apache.tools.ant.types.Permissions;
  35. import org.apache.tools.ant.types.RedirectorElement;
  36. /**
  37. * Launcher for Java applications. Allows use of
  38. * the same JVM for the called application thus resulting in much
  39. * faster operation.
  40. *
  41. * @since Ant 1.1
  42. *
  43. * @ant.task category="java"
  44. */
  45. public class Java extends Task {
  46. private CommandlineJava cmdl = new CommandlineJava();
  47. private Environment env = new Environment();
  48. private boolean fork = false;
  49. private boolean newEnvironment = false;
  50. private File dir = null;
  51. private boolean failOnError = false;
  52. private Long timeout = null;
  53. //include locally for screening purposes
  54. private String inputString;
  55. private File input;
  56. private File output;
  57. private File error;
  58. protected Redirector redirector = new Redirector(this);
  59. protected RedirectorElement redirectorElement;
  60. private String resultProperty;
  61. private Permissions perm = null;
  62. private boolean spawn = false;
  63. private boolean incompatibleWithSpawn = false;
  64. /**
  65. * Do the execution.
  66. * @throws BuildException if failOnError is set to true and the application
  67. * returns a non 0 result code
  68. */
  69. public void execute() throws BuildException {
  70. File savedDir = dir;
  71. Permissions savedPermissions = perm;
  72. int err = -1;
  73. try {
  74. err = executeJava();
  75. if (err != 0) {
  76. if (failOnError) {
  77. throw new BuildException("Java returned: " + err, getLocation());
  78. } else {
  79. log("Java Result: " + err, Project.MSG_ERR);
  80. }
  81. }
  82. maybeSetResultPropertyValue(err);
  83. } finally {
  84. dir = savedDir;
  85. perm = savedPermissions;
  86. }
  87. }
  88. /**
  89. * Do the execution and return a return code.
  90. *
  91. * @return the return code from the execute java class if it was
  92. * executed in a separate VM (fork = "yes").
  93. *
  94. * @throws BuildException if required parameters are missing
  95. */
  96. public int executeJava() throws BuildException {
  97. String classname = cmdl.getClassname();
  98. if (classname == null && cmdl.getJar() == null) {
  99. throw new BuildException("Classname must not be null.");
  100. }
  101. if (!fork && cmdl.getJar() != null) {
  102. throw new BuildException("Cannot execute a jar in non-forked mode."
  103. + " Please set fork='true'. ");
  104. }
  105. if (spawn && !fork) {
  106. throw new BuildException("Cannot spawn a java process in non-forked mode."
  107. + " Please set fork='true'. ");
  108. }
  109. if (spawn && incompatibleWithSpawn) {
  110. getProject().log("spawn does not allow attributes related to input, "
  111. + "output, error, result", Project.MSG_ERR);
  112. getProject().log("spawn also does not allow timeout", Project.MSG_ERR);
  113. getProject().log( "finally, spawn is not compatible "
  114. + "with a nested I/O <redirector>", Project.MSG_ERR);
  115. throw new BuildException("You have used an attribute "
  116. + "or nested element which is not compatible with spawn");
  117. }
  118. if (cmdl.getAssertions() != null && !fork) {
  119. log("Assertion statements are currently ignored in non-forked mode");
  120. }
  121. if (fork) {
  122. if (perm != null) {
  123. log("Permissions can not be set this way in forked mode.", Project.MSG_WARN);
  124. }
  125. log(cmdl.describeCommand(), Project.MSG_VERBOSE);
  126. } else {
  127. if (cmdl.getVmCommand().size() > 1) {
  128. log("JVM args ignored when same JVM is used.",
  129. Project.MSG_WARN);
  130. }
  131. if (dir != null) {
  132. log("Working directory ignored when same JVM is used.",
  133. Project.MSG_WARN);
  134. }
  135. if (newEnvironment || null != env.getVariables()) {
  136. log("Changes to environment variables are ignored when same "
  137. + "JVM is used.", Project.MSG_WARN);
  138. }
  139. if (cmdl.getBootclasspath() != null) {
  140. log("bootclasspath ignored when same JVM is used.",
  141. Project.MSG_WARN);
  142. }
  143. if (perm == null && failOnError == true) {
  144. perm = new Permissions(true);
  145. log("running " + this.cmdl.getClassname()
  146. + " with default permissions (exit forbidden)", Project.MSG_VERBOSE);
  147. }
  148. log("Running in same VM " + cmdl.describeJavaCommand(),
  149. Project.MSG_VERBOSE);
  150. }
  151. setupRedirector();
  152. try {
  153. if (fork) {
  154. if (!spawn) {
  155. return fork(cmdl.getCommandline());
  156. } else {
  157. spawn(cmdl.getCommandline());
  158. return 0;
  159. }
  160. } else {
  161. try {
  162. run(cmdl);
  163. return 0;
  164. } catch (ExitException ex) {
  165. return ex.getStatus();
  166. }
  167. }
  168. } catch (BuildException e) {
  169. if (failOnError) {
  170. throw e;
  171. } else {
  172. log(e);
  173. return 0;
  174. }
  175. } catch (Throwable t) {
  176. if (failOnError) {
  177. throw new BuildException(t);
  178. } else {
  179. log(t);
  180. return 0;
  181. }
  182. }
  183. }
  184. /**
  185. * set whether or not you want the process to be spawned
  186. * default is not spawned
  187. * @param spawn if true you do not want ant to wait for the end of the process
  188. * @since ant 1.6
  189. */
  190. public void setSpawn(boolean spawn) {
  191. this.spawn = spawn;
  192. }
  193. /**
  194. * Set the classpath to be used when running the Java class
  195. *
  196. * @param s an Ant Path object containing the classpath.
  197. */
  198. public void setClasspath(Path s) {
  199. createClasspath().append(s);
  200. }
  201. /**
  202. * Adds a path to the classpath.
  203. *
  204. * @return created classpath
  205. */
  206. public Path createClasspath() {
  207. return cmdl.createClasspath(getProject()).createPath();
  208. }
  209. /**
  210. * Adds a path to the bootclasspath.
  211. * @since Ant 1.6
  212. *
  213. * @return created bootclasspath
  214. */
  215. public Path createBootclasspath() {
  216. return cmdl.createBootclasspath(getProject()).createPath();
  217. }
  218. /**
  219. * Sets the permissions for the application run inside the same JVM.
  220. * @since Ant 1.6
  221. * @return .
  222. */
  223. public Permissions createPermissions() {
  224. if (perm == null) {
  225. perm = new Permissions();
  226. }
  227. return perm;
  228. }
  229. /**
  230. * Classpath to use, by reference.
  231. *
  232. * @param r a reference to an existing classpath
  233. */
  234. public void setClasspathRef(Reference r) {
  235. createClasspath().setRefid(r);
  236. }
  237. /**
  238. * The location of the JAR file to execute.
  239. *
  240. * @param jarfile the jarfile that one wants to execute
  241. *
  242. * @throws BuildException if there is also a main class specified
  243. */
  244. public void setJar(File jarfile) throws BuildException {
  245. if (cmdl.getClassname() != null) {
  246. throw new BuildException("Cannot use 'jar' and 'classname' "
  247. + "attributes in same command.");
  248. }
  249. cmdl.setJar(jarfile.getAbsolutePath());
  250. }
  251. /**
  252. * Sets the Java class to execute.
  253. *
  254. * @param s the name of the main class
  255. *
  256. * @throws BuildException if the jar attribute has been set
  257. */
  258. public void setClassname(String s) throws BuildException {
  259. if (cmdl.getJar() != null) {
  260. throw new BuildException("Cannot use 'jar' and 'classname' "
  261. + "attributes in same command");
  262. }
  263. cmdl.setClassname(s);
  264. }
  265. /**
  266. * Deprecated: use nested arg instead.
  267. * Set the command line arguments for the class.
  268. *
  269. * @param s arguments
  270. *
  271. * @ant.attribute ignore="true"
  272. */
  273. public void setArgs(String s) {
  274. log("The args attribute is deprecated. "
  275. + "Please use nested arg elements.", Project.MSG_WARN);
  276. cmdl.createArgument().setLine(s);
  277. }
  278. /**
  279. * Adds a command-line argument.
  280. *
  281. * @return created argument
  282. */
  283. public Commandline.Argument createArg() {
  284. return cmdl.createArgument();
  285. }
  286. /**
  287. * The name of a property in which the return code of the
  288. * command should be stored. Only of interest if failonerror=false.
  289. *
  290. * @param resultProperty name of property
  291. *
  292. * @since Ant 1.6
  293. */
  294. public void setResultProperty(String resultProperty) {
  295. this.resultProperty = resultProperty;
  296. incompatibleWithSpawn = true;
  297. }
  298. /**
  299. * helper method to set result property to the
  300. * passed in value if appropriate
  301. *
  302. * @param result the exit code
  303. */
  304. protected void maybeSetResultPropertyValue(int result) {
  305. String res = Integer.toString(result);
  306. if (resultProperty != null) {
  307. getProject().setNewProperty(resultProperty, res);
  308. }
  309. }
  310. /**
  311. * If true, execute in a new VM.
  312. *
  313. * @param s do you want to run Java in a new VM.
  314. */
  315. public void setFork(boolean s) {
  316. this.fork = s;
  317. }
  318. /**
  319. * Set the command line arguments for the JVM.
  320. *
  321. * @param s jvmargs
  322. */
  323. public void setJvmargs(String s) {
  324. log("The jvmargs attribute is deprecated. "
  325. + "Please use nested jvmarg elements.", Project.MSG_WARN);
  326. cmdl.createVmArgument().setLine(s);
  327. }
  328. /**
  329. * Adds a JVM argument.
  330. *
  331. * @return JVM argument created
  332. */
  333. public Commandline.Argument createJvmarg() {
  334. return cmdl.createVmArgument();
  335. }
  336. /**
  337. * Set the command used to start the VM (only if forking).
  338. *
  339. * @param s command to start the VM
  340. */
  341. public void setJvm(String s) {
  342. cmdl.setVm(s);
  343. }
  344. /**
  345. * Adds a system property.
  346. *
  347. * @param sysp system property
  348. */
  349. public void addSysproperty(Environment.Variable sysp) {
  350. cmdl.addSysproperty(sysp);
  351. }
  352. /**
  353. * Adds a set of properties as system properties.
  354. *
  355. * @param sysp set of properties to add
  356. *
  357. * @since Ant 1.6
  358. */
  359. public void addSyspropertyset(PropertySet sysp) {
  360. cmdl.addSyspropertyset(sysp);
  361. }
  362. /**
  363. * If true, then fail if the command exits with a
  364. * returncode other than 0
  365. *
  366. * @param fail if true fail the build when the command exits with a non
  367. * zero returncode
  368. */
  369. public void setFailonerror(boolean fail) {
  370. failOnError = fail;
  371. incompatibleWithSpawn |= fail;
  372. }
  373. /**
  374. * The working directory of the process
  375. *
  376. * @param d working directory
  377. *
  378. */
  379. public void setDir(File d) {
  380. this.dir = d;
  381. }
  382. /**
  383. * File the output of the process is redirected to.
  384. *
  385. * @param out name of the output file
  386. */
  387. public void setOutput(File out) {
  388. this.output = out;
  389. incompatibleWithSpawn = true;
  390. }
  391. /**
  392. * Set the input to use for the task
  393. *
  394. * @param input name of the input file
  395. */
  396. public void setInput(File input) {
  397. if (inputString != null) {
  398. throw new BuildException("The \"input\" and \"inputstring\" "
  399. + "attributes cannot both be specified");
  400. }
  401. this.input = input;
  402. incompatibleWithSpawn = true;
  403. }
  404. /**
  405. * Set the string to use as input
  406. *
  407. * @param inputString the string which is used as the input source
  408. */
  409. public void setInputString(String inputString) {
  410. if (input != null) {
  411. throw new BuildException("The \"input\" and \"inputstring\" "
  412. + "attributes cannot both be specified");
  413. }
  414. this.inputString = inputString;
  415. incompatibleWithSpawn = true;
  416. }
  417. /**
  418. * Controls whether error output of exec is logged. This is only useful
  419. * when output is being redirected and error output is desired in the
  420. * Ant log
  421. *
  422. * @param logError get in the ant log the messages coming from stderr
  423. * in the case that fork = true
  424. */
  425. public void setLogError(boolean logError) {
  426. redirector.setLogError(logError);
  427. incompatibleWithSpawn |= logError;
  428. }
  429. /**
  430. * File the error stream of the process is redirected to.
  431. *
  432. * @param error file getting the error stream
  433. *
  434. * @since ant 1.6
  435. */
  436. public void setError(File error) {
  437. this.error = error;
  438. incompatibleWithSpawn = true;
  439. }
  440. /**
  441. * Property name whose value should be set to the output of
  442. * the process.
  443. *
  444. * @param outputProp property name
  445. *
  446. */
  447. public void setOutputproperty(String outputProp) {
  448. redirector.setOutputProperty(outputProp);
  449. incompatibleWithSpawn = true;
  450. }
  451. /**
  452. * Property name whose value should be set to the error of
  453. * the process.
  454. *
  455. * @param errorProperty property name
  456. *
  457. * @since ant 1.6
  458. */
  459. public void setErrorProperty(String errorProperty) {
  460. redirector.setErrorProperty(errorProperty);
  461. incompatibleWithSpawn = true;
  462. }
  463. /**
  464. * Corresponds to -mx or -Xmx depending on VM version.
  465. *
  466. * @param max max memory parameter
  467. */
  468. public void setMaxmemory(String max) {
  469. cmdl.setMaxmemory(max);
  470. }
  471. /**
  472. * Sets the JVM version.
  473. * @param value JVM version
  474. */
  475. public void setJVMVersion(String value) {
  476. cmdl.setVmversion(value);
  477. }
  478. /**
  479. * Adds an environment variable.
  480. *
  481. * <p>Will be ignored if we are not forking a new VM.
  482. *
  483. * @param var new environment variable
  484. *
  485. * @since Ant 1.5
  486. */
  487. public void addEnv(Environment.Variable var) {
  488. env.addVariable(var);
  489. }
  490. /**
  491. * If true, use a completely new environment.
  492. *
  493. * <p>Will be ignored if we are not forking a new VM.
  494. *
  495. * @param newenv if true, use a completely new environment.
  496. *
  497. * @since Ant 1.5
  498. */
  499. public void setNewenvironment(boolean newenv) {
  500. newEnvironment = newenv;
  501. }
  502. /**
  503. * If true, append output to existing file.
  504. *
  505. * @param append if true, append output to existing file
  506. *
  507. * @since Ant 1.5
  508. */
  509. public void setAppend(boolean append) {
  510. redirector.setAppend(append);
  511. incompatibleWithSpawn = true;
  512. }
  513. /**
  514. * Timeout in milliseconds after which the process will be killed.
  515. *
  516. * @param value time out in milliseconds
  517. *
  518. * @since Ant 1.5
  519. */
  520. public void setTimeout(Long value) {
  521. timeout = value;
  522. incompatibleWithSpawn |= timeout != null;
  523. }
  524. /**
  525. * assertions to enable in this program (if fork=true)
  526. * @since Ant 1.6
  527. * @param asserts assertion set
  528. */
  529. public void addAssertions(Assertions asserts) {
  530. if(cmdl.getAssertions() != null) {
  531. throw new BuildException("Only one assertion declaration is allowed");
  532. }
  533. cmdl.setAssertions(asserts);
  534. }
  535. /**
  536. * Add a <CODE>RedirectorElement</CODE> to this task.
  537. * @param redirectorElement <CODE>RedirectorElement</CODE>.
  538. */
  539. public void addConfiguredRedirector(RedirectorElement redirectorElement) {
  540. if (this.redirectorElement != null) {
  541. throw new BuildException("cannot have > 1 nested <redirector>s");
  542. } else {
  543. this.redirectorElement = redirectorElement;
  544. incompatibleWithSpawn = true;
  545. }
  546. }
  547. /**
  548. * Pass output sent to System.out to specified output file.
  549. *
  550. * @param output a string of output on its way to the handlers
  551. *
  552. * @since Ant 1.5
  553. */
  554. protected void handleOutput(String output) {
  555. if (redirector.getOutputStream() != null) {
  556. redirector.handleOutput(output);
  557. } else {
  558. super.handleOutput(output);
  559. }
  560. }
  561. /**
  562. * Handle an input request by this task
  563. *
  564. * @param buffer the buffer into which data is to be read.
  565. * @param offset the offset into the buffer at which data is stored.
  566. * @param length the amount of data to read
  567. *
  568. * @return the number of bytes read
  569. *
  570. * @exception IOException if the data cannot be read
  571. * @since Ant 1.6
  572. */
  573. public int handleInput(byte[] buffer, int offset, int length)
  574. throws IOException {
  575. if (redirector.getInputStream() != null) {
  576. return redirector.handleInput(buffer, offset, length);
  577. } else {
  578. return super.handleInput(buffer, offset, length);
  579. }
  580. }
  581. /**
  582. * Pass output sent to System.out to specified output file.
  583. *
  584. * @param output string of output on its way to its handlers
  585. *
  586. * @since Ant 1.5.2
  587. */
  588. protected void handleFlush(String output) {
  589. if (redirector.getOutputStream() != null) {
  590. redirector.handleFlush(output);
  591. } else {
  592. super.handleFlush(output);
  593. }
  594. }
  595. /**
  596. * Handle output sent to System.err
  597. *
  598. * @param output string of stderr
  599. *
  600. * @since Ant 1.5
  601. */
  602. protected void handleErrorOutput(String output) {
  603. if (redirector.getErrorStream() != null) {
  604. redirector.handleErrorOutput(output);
  605. } else {
  606. super.handleErrorOutput(output);
  607. }
  608. }
  609. /**
  610. * Handle output sent to System.err and flush the stream.
  611. *
  612. * @param output string of stderr
  613. *
  614. * @since Ant 1.5.2
  615. */
  616. protected void handleErrorFlush(String output) {
  617. if (redirector.getErrorStream() != null) {
  618. redirector.handleErrorFlush(output);
  619. } else {
  620. super.handleErrorOutput(output);
  621. }
  622. }
  623. /**
  624. * Set up properties on the redirector that we needed to store locally.
  625. */
  626. protected void setupRedirector() {
  627. redirector.setInput(input);
  628. redirector.setInputString(inputString);
  629. redirector.setOutput(output);
  630. redirector.setError(error);
  631. if (redirectorElement != null) {
  632. redirectorElement.configure(redirector);
  633. }
  634. }
  635. /**
  636. * Executes the given classname with the given arguments as it
  637. * was a command line application.
  638. */
  639. private void run(CommandlineJava command) throws BuildException {
  640. try {
  641. ExecuteJava exe = new ExecuteJava();
  642. exe.setJavaCommand(command.getJavaCommand());
  643. exe.setClasspath(command.getClasspath());
  644. exe.setSystemProperties(command.getSystemProperties());
  645. exe.setPermissions(perm);
  646. exe.setTimeout(timeout);
  647. redirector.createStreams();
  648. exe.execute(getProject());
  649. redirector.complete();
  650. if (exe.killedProcess()) {
  651. throw new BuildException("Timeout: killed the sub-process");
  652. }
  653. } catch (IOException e) {
  654. throw new BuildException(e);
  655. }
  656. }
  657. /**
  658. * Executes the given classname with the given arguments in a separate VM.
  659. */
  660. private int fork(String[] command) throws BuildException {
  661. Execute exe
  662. = new Execute(redirector.createHandler(), createWatchdog());
  663. exe.setAntRun(getProject());
  664. if (dir == null) {
  665. dir = getProject().getBaseDir();
  666. } else if (!dir.exists() || !dir.isDirectory()) {
  667. throw new BuildException(dir.getAbsolutePath()
  668. + " is not a valid directory",
  669. getLocation());
  670. }
  671. exe.setWorkingDirectory(dir);
  672. String[] environment = env.getVariables();
  673. if (environment != null) {
  674. for (int i = 0; i < environment.length; i++) {
  675. log("Setting environment variable: " + environment[i],
  676. Project.MSG_VERBOSE);
  677. }
  678. }
  679. exe.setNewenvironment(newEnvironment);
  680. exe.setEnvironment(environment);
  681. exe.setCommandline(command);
  682. try {
  683. int rc = exe.execute();
  684. redirector.complete();
  685. if (exe.killedProcess()) {
  686. throw new BuildException("Timeout: killed the sub-process");
  687. }
  688. return rc;
  689. } catch (IOException e) {
  690. throw new BuildException(e, getLocation());
  691. }
  692. }
  693. /**
  694. * Executes the given classname with the given arguments in a separate VM.
  695. */
  696. private void spawn(String[] command) throws BuildException {
  697. Execute exe
  698. = new Execute();
  699. exe.setAntRun(getProject());
  700. if (dir == null) {
  701. dir = getProject().getBaseDir();
  702. } else if (!dir.exists() || !dir.isDirectory()) {
  703. throw new BuildException(dir.getAbsolutePath()
  704. + " is not a valid directory",
  705. getLocation());
  706. }
  707. exe.setWorkingDirectory(dir);
  708. String[] environment = env.getVariables();
  709. if (environment != null) {
  710. for (int i = 0; i < environment.length; i++) {
  711. log("Setting environment variable: " + environment[i],
  712. Project.MSG_VERBOSE);
  713. }
  714. }
  715. exe.setNewenvironment(newEnvironment);
  716. exe.setEnvironment(environment);
  717. exe.setCommandline(command);
  718. try {
  719. exe.spawn();
  720. } catch (IOException e) {
  721. throw new BuildException(e, getLocation());
  722. }
  723. }
  724. /**
  725. * Executes the given classname with the given arguments as it
  726. * was a command line application.
  727. *
  728. * @param classname the name of the class to run
  729. * @param args arguments for the class
  730. * @throws BuildException in case of IO Exception in the execution
  731. */
  732. protected void run(String classname, Vector args) throws BuildException {
  733. CommandlineJava cmdj = new CommandlineJava();
  734. cmdj.setClassname(classname);
  735. for (int i = 0; i < args.size(); i++) {
  736. cmdj.createArgument().setValue((String) args.elementAt(i));
  737. }
  738. run(cmdj);
  739. }
  740. /**
  741. * Clear out the arguments to this java task.
  742. */
  743. public void clearArgs() {
  744. cmdl.clearJavaArgs();
  745. }
  746. /**
  747. * Create the Watchdog to kill a runaway process.
  748. *
  749. * @return new watchdog
  750. *
  751. * @throws BuildException under unknown circumstances
  752. *
  753. * @since Ant 1.5
  754. */
  755. protected ExecuteWatchdog createWatchdog() throws BuildException {
  756. if (timeout == null) {
  757. return null;
  758. }
  759. return new ExecuteWatchdog(timeout.longValue());
  760. }
  761. /**
  762. * @since 1.6.2
  763. */
  764. private void log(Throwable t) {
  765. StringWriter sw = new StringWriter();
  766. PrintWriter w = new PrintWriter(sw);
  767. t.printStackTrace(w);
  768. w.close();
  769. log(sw.toString(), Project.MSG_ERR);
  770. }
  771. }