1. /*
  2. * Copyright 2003-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.optional.net;
  18. import org.apache.commons.net.bsd.RExecClient;
  19. import java.io.IOException;
  20. import java.io.InputStream;
  21. import java.io.OutputStream;
  22. import java.util.Calendar;
  23. import java.util.Enumeration;
  24. import java.util.Vector;
  25. import org.apache.tools.ant.BuildException;
  26. import org.apache.tools.ant.Project;
  27. import org.apache.tools.ant.Task;
  28. /**
  29. * Automates the rexec protocol.
  30. *
  31. * @since Ant 1.6
  32. */
  33. public class RExecTask extends Task {
  34. /**
  35. * The userid to login with, if automated login is used
  36. */
  37. private String userid = null;
  38. /**
  39. * The password to login with, if automated login is used
  40. */
  41. private String password = null;
  42. /**
  43. * The command to execute
  44. */
  45. private String command = null;
  46. /**
  47. * The server to connect to.
  48. */
  49. private String server = null;
  50. /**
  51. * The tcp port to connect to.
  52. */
  53. private int port = RExecClient.DEFAULT_PORT;
  54. /**
  55. * The list of read/write commands for this session
  56. */
  57. private Vector rexecTasks = new Vector();
  58. /**
  59. * If true, adds a CR to beginning of login script
  60. */
  61. private boolean addCarriageReturn = false;
  62. /**
  63. * Default time allowed for waiting for a valid response
  64. * for all child reads. A value of 0 means no limit.
  65. */
  66. private Integer defaultTimeout = null;
  67. /**
  68. * This class is the parent of the Read and Write tasks.
  69. * It handles the common attributes for both.
  70. */
  71. public class RExecSubTask {
  72. protected String taskString = "";
  73. public void execute(AntRExecClient rexec)
  74. throws BuildException {
  75. throw new BuildException("Shouldn't be able instantiate a SubTask directly");
  76. }
  77. /**
  78. * the message as nested text
  79. */
  80. public void addText(String s) {
  81. setString(getProject().replaceProperties(s));
  82. }
  83. /**
  84. * the message as an attribute
  85. */
  86. public void setString(String s) {
  87. taskString += s;
  88. }
  89. }
  90. /**
  91. * Sends text to the connected server
  92. */
  93. public class RExecWrite extends RExecSubTask {
  94. private boolean echoString = true;
  95. public void execute(AntRExecClient rexec)
  96. throws BuildException {
  97. rexec.sendString(taskString, echoString);
  98. }
  99. /**
  100. * Whether or not the message should be echoed to the log.
  101. * Defaults to <code>true</code>.
  102. */
  103. public void setEcho(boolean b) {
  104. echoString = b;
  105. }
  106. }
  107. /**
  108. * Reads the output from the connected server
  109. * until the required string is found or we time out.
  110. */
  111. public class RExecRead extends RExecSubTask {
  112. private Integer timeout = null;
  113. public void execute(AntRExecClient rexec)
  114. throws BuildException {
  115. rexec.waitForString(taskString, timeout);
  116. }
  117. /**
  118. * a timeout value that overrides any task wide timeout.
  119. */
  120. public void setTimeout(Integer i) {
  121. this.timeout = i;
  122. }
  123. /**
  124. * Sets the default timeout if none has been set already
  125. * @ant.attribute ignore="true"
  126. */
  127. public void setDefaultTimeout(Integer defaultTimeout) {
  128. if (timeout == null) {
  129. timeout = defaultTimeout;
  130. }
  131. }
  132. }
  133. /**
  134. * This class handles the abstraction of the rexec protocol.
  135. * Currently it is a wrapper around <a
  136. * href="http://jakarta.apache.org/commons/net/index.html">Jakarta
  137. * Commons Net</a>.
  138. */
  139. public class AntRExecClient extends RExecClient {
  140. /**
  141. * Read from the rexec session until the string we are
  142. * waiting for is found
  143. * @param s The string to wait on
  144. */
  145. public void waitForString(String s) {
  146. waitForString(s, null);
  147. }
  148. /**
  149. * Read from the rexec session until the string we are
  150. * waiting for is found or the timeout has been reached
  151. * @param s The string to wait on
  152. * @param timeout The maximum number of seconds to wait
  153. */
  154. public void waitForString(String s, Integer timeout) {
  155. InputStream is = this.getInputStream();
  156. try {
  157. StringBuffer sb = new StringBuffer();
  158. if (timeout == null || timeout.intValue() == 0) {
  159. while (sb.toString().indexOf(s) == -1) {
  160. sb.append((char) is.read());
  161. }
  162. } else {
  163. Calendar endTime = Calendar.getInstance();
  164. endTime.add(Calendar.SECOND, timeout.intValue());
  165. while (sb.toString().indexOf(s) == -1) {
  166. while (Calendar.getInstance().before(endTime)
  167. && is.available() == 0) {
  168. Thread.sleep(250);
  169. }
  170. if (is.available() == 0) {
  171. throw new BuildException(
  172. "Response timed-out waiting for \"" + s + '\"',
  173. getLocation());
  174. }
  175. sb.append((char) is.read());
  176. }
  177. }
  178. log(sb.toString(), Project.MSG_INFO);
  179. } catch (BuildException be) {
  180. throw be;
  181. } catch (Exception e) {
  182. throw new BuildException(e, getLocation());
  183. }
  184. }
  185. /**
  186. * Write this string to the rexec session.
  187. * @param echoString Logs string sent
  188. */
  189. public void sendString(String s, boolean echoString) {
  190. OutputStream os = this.getOutputStream();
  191. try {
  192. os.write((s + "\n").getBytes());
  193. if (echoString) {
  194. log(s, Project.MSG_INFO);
  195. }
  196. os.flush();
  197. } catch (Exception e) {
  198. throw new BuildException(e, getLocation());
  199. }
  200. }
  201. /**
  202. * Read from the rexec session until the EOF is found or
  203. * the timeout has been reached
  204. * @param timeout The maximum number of seconds to wait
  205. */
  206. public void waitForEOF(Integer timeout) {
  207. InputStream is = this.getInputStream();
  208. try {
  209. StringBuffer sb = new StringBuffer();
  210. if (timeout == null || timeout.intValue() == 0) {
  211. int read;
  212. while ((read = is.read()) != -1) {
  213. char c = (char) read;
  214. sb.append(c);
  215. if (c == '\n') {
  216. log(sb.toString(), Project.MSG_INFO);
  217. sb.delete(0, sb.length());
  218. }
  219. }
  220. } else {
  221. Calendar endTime = Calendar.getInstance();
  222. endTime.add(Calendar.SECOND, timeout.intValue());
  223. int read = 0;
  224. while (read != -1) {
  225. while (Calendar.getInstance().before(endTime) && is.available() == 0) {
  226. Thread.sleep(250);
  227. }
  228. if (is.available() == 0) {
  229. log(sb.toString(), Project.MSG_INFO);
  230. throw new BuildException(
  231. "Response timed-out waiting for EOF",
  232. getLocation());
  233. }
  234. read = is.read();
  235. if (read != -1) {
  236. char c = (char) read;
  237. sb.append(c);
  238. if (c == '\n') {
  239. log(sb.toString(), Project.MSG_INFO);
  240. sb.delete(0, sb.length());
  241. }
  242. }
  243. }
  244. }
  245. if (sb.length() > 0) {
  246. log(sb.toString(), Project.MSG_INFO);
  247. }
  248. } catch (BuildException be) {
  249. throw be;
  250. } catch (Exception e) {
  251. throw new BuildException(e, getLocation());
  252. }
  253. }
  254. }
  255. /**
  256. * A string to wait for from the server.
  257. * A subTask <read> tag was found. Create the object,
  258. * Save it in our list, and return it.
  259. */
  260. public RExecSubTask createRead() {
  261. RExecSubTask task = (RExecSubTask) new RExecRead();
  262. rexecTasks.addElement(task);
  263. return task;
  264. }
  265. /**
  266. * Add text to send to the server
  267. * A subTask <write> tag was found. Create the object,
  268. * Save it in our list, and return it.
  269. */
  270. public RExecSubTask createWrite() {
  271. RExecSubTask task = (RExecSubTask) new RExecWrite();
  272. rexecTasks.addElement(task);
  273. return task;
  274. }
  275. /**
  276. * Verify that all parameters are included.
  277. * Connect and possibly login
  278. * Iterate through the list of Reads and writes
  279. */
  280. public void execute() throws BuildException {
  281. /** A server name is required to continue */
  282. if (server == null) {
  283. throw new BuildException("No Server Specified");
  284. }
  285. /** A userid and password must appear together
  286. * if they appear. They are not required.
  287. */
  288. if (userid == null && password != null) {
  289. throw new BuildException("No Userid Specified");
  290. }
  291. if (password == null && userid != null) {
  292. throw new BuildException("No Password Specified");
  293. }
  294. /** Create the telnet client object */
  295. AntRExecClient rexec = null;
  296. try {
  297. rexec = new AntRExecClient();
  298. try {
  299. rexec.connect(server, port);
  300. } catch (IOException e) {
  301. throw new BuildException("Can't connect to " + server);
  302. }
  303. /** Login if userid and password were specified */
  304. if (userid != null && password != null) {
  305. login(rexec);
  306. }
  307. /** Process each sub command */
  308. Enumeration tasksToRun = rexecTasks.elements();
  309. while (tasksToRun != null && tasksToRun.hasMoreElements()) {
  310. RExecSubTask task = (RExecSubTask) tasksToRun.nextElement();
  311. if (task instanceof RExecRead && defaultTimeout != null) {
  312. ((RExecRead) task).setDefaultTimeout(defaultTimeout);
  313. }
  314. task.execute(rexec);
  315. }
  316. /** Keep reading input stream until end of it or time-out */
  317. rexec.waitForEOF(defaultTimeout);
  318. } finally {
  319. if (rexec != null) {
  320. try {
  321. rexec.disconnect();
  322. } catch (IOException e) {
  323. throw new BuildException("Error disconnecting from "
  324. + server);
  325. }
  326. }
  327. }
  328. }
  329. /**
  330. * Process a 'typical' login. If it differs, use the read
  331. * and write tasks explicitely
  332. */
  333. private void login(AntRExecClient rexec) {
  334. if (addCarriageReturn) {
  335. rexec.sendString("\n", true);
  336. }
  337. rexec.waitForString("ogin:");
  338. rexec.sendString(userid, true);
  339. rexec.waitForString("assword:");
  340. rexec.sendString(password, false);
  341. }
  342. /**
  343. * Set the the comand to execute on the server;
  344. */
  345. public void setCommand(String c) { this.command = c; }
  346. /**
  347. * send a carriage return after connecting; optional, defaults to false.
  348. */
  349. public void setInitialCR(boolean b) {
  350. this.addCarriageReturn = b;
  351. }
  352. /**
  353. * Set the the login password to use
  354. * required if <tt>userid</tt> is set.
  355. */
  356. public void setPassword(String p) { this.password = p; }
  357. /**
  358. * Set the tcp port to connect to; default is 23.
  359. */
  360. public void setPort(int p) { this.port = p; }
  361. /**
  362. * Set the hostname or address of the remote server.
  363. */
  364. public void setServer(String m) { this.server = m; }
  365. /**
  366. * set a default timeout in seconds to wait for a response,
  367. * zero means forever (the default)
  368. */
  369. public void setTimeout(Integer i) {
  370. this.defaultTimeout = i;
  371. }
  372. /**
  373. * Set the the login id to use on the server;
  374. * required if <tt>password</tt> is set.
  375. */
  376. public void setUserid(String u) { this.userid = u; }
  377. }