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.optional.net;
020:
021: import org.apache.commons.net.bsd.RExecClient;
022:
023: import java.io.IOException;
024: import java.io.InputStream;
025: import java.io.OutputStream;
026: import java.util.Calendar;
027: import java.util.Enumeration;
028: import java.util.Vector;
029: import org.apache.tools.ant.BuildException;
030: import org.apache.tools.ant.Project;
031: import org.apache.tools.ant.Task;
032:
033: /**
034: * Automates the rexec protocol.
035: *
036: * @since Ant 1.6
037: */
038:
039: public class RExecTask extends Task {
040: /**
041: * The userid to login with, if automated login is used
042: */
043: private String userid = null;
044:
045: /**
046: * The password to login with, if automated login is used
047: */
048: private String password = null;
049:
050: /**
051: * The command to execute
052: */
053: private String command = null;
054:
055: /**
056: * The server to connect to.
057: */
058: private String server = null;
059:
060: /**
061: * The tcp port to connect to.
062: */
063: private int port = RExecClient.DEFAULT_PORT;
064:
065: /**
066: * The list of read/write commands for this session
067: */
068: private Vector rexecTasks = new Vector();
069:
070: /**
071: * If true, adds a CR to beginning of login script
072: */
073: private boolean addCarriageReturn = false;
074:
075: /**
076: * Default time allowed for waiting for a valid response
077: * for all child reads. A value of 0 means no limit.
078: */
079: private Integer defaultTimeout = null;
080:
081: /**
082: * This class is the parent of the Read and Write tasks.
083: * It handles the common attributes for both.
084: */
085: public class RExecSubTask {
086: // CheckStyle:VisibilityModifier OFF - bc
087: protected String taskString = "";
088:
089: // CheckStyle:VisibilityModifier ON
090:
091: /**
092: * Execute the subtask.
093: * @param rexec the client
094: * @throws BuildException always as it is not allowed to instantiate this object
095: */
096: public void execute(AntRExecClient rexec) throws BuildException {
097: throw new BuildException(
098: "Shouldn't be able instantiate a SubTask directly");
099: }
100:
101: /**
102: * the message as nested text
103: * @param s the nested text
104: */
105: public void addText(String s) {
106: setString(getProject().replaceProperties(s));
107: }
108:
109: /**
110: * the message as an attribute
111: * @param s a <code>String</code> value
112: */
113: public void setString(String s) {
114: taskString += s;
115: }
116: }
117:
118: /**
119: * Sends text to the connected server
120: */
121: public class RExecWrite extends RExecSubTask {
122: private boolean echoString = true;
123:
124: /**
125: * Execute the write exec task.
126: * @param rexec the task to use
127: * @throws BuildException on error
128: */
129: public void execute(AntRExecClient rexec) throws BuildException {
130: rexec.sendString(taskString, echoString);
131: }
132:
133: /**
134: * Whether or not the message should be echoed to the log.
135: * Defaults to <code>true</code>.
136: * @param b a <code>boolean</code> value
137: */
138: public void setEcho(boolean b) {
139: echoString = b;
140: }
141: }
142:
143: /**
144: * Reads the output from the connected server
145: * until the required string is found or we time out.
146: */
147: public class RExecRead extends RExecSubTask {
148: private Integer timeout = null;
149:
150: /**
151: * Execute the read exec task.
152: * @param rexec the task to use
153: * @throws BuildException on error
154: */
155: public void execute(AntRExecClient rexec) throws BuildException {
156: rexec.waitForString(taskString, timeout);
157: }
158:
159: /**
160: * a timeout value that overrides any task wide timeout.
161: * @param i an <code>Integer</code> value
162: */
163: public void setTimeout(Integer i) {
164: this .timeout = i;
165: }
166:
167: /**
168: * Sets the default timeout if none has been set already
169: * @param defaultTimeout an <code>Integer</code> value
170: * @ant.attribute ignore="true"
171: */
172: public void setDefaultTimeout(Integer defaultTimeout) {
173: if (timeout == null) {
174: timeout = defaultTimeout;
175: }
176: }
177: }
178:
179: /**
180: * This class handles the abstraction of the rexec protocol.
181: * Currently it is a wrapper around <a
182: * href="http://jakarta.apache.org/commons/net/index.html">Jakarta
183: * Commons Net</a>.
184: */
185: public class AntRExecClient extends RExecClient {
186: /**
187: * Read from the rexec session until the string we are
188: * waiting for is found
189: * @param s The string to wait on
190: */
191: public void waitForString(String s) {
192: waitForString(s, null);
193: }
194:
195: /**
196: * Read from the rexec session until the string we are
197: * waiting for is found or the timeout has been reached
198: * @param s The string to wait on
199: * @param timeout The maximum number of seconds to wait
200: */
201: public void waitForString(String s, Integer timeout) {
202: InputStream is = this .getInputStream();
203: try {
204: StringBuffer sb = new StringBuffer();
205: if (timeout == null || timeout.intValue() == 0) {
206: while (sb.toString().indexOf(s) == -1) {
207: sb.append((char) is.read());
208: }
209: } else {
210: Calendar endTime = Calendar.getInstance();
211: endTime.add(Calendar.SECOND, timeout.intValue());
212: while (sb.toString().indexOf(s) == -1) {
213: while (Calendar.getInstance().before(endTime)
214: && is.available() == 0) {
215: Thread.sleep(250);
216: }
217: if (is.available() == 0) {
218: throw new BuildException(
219: "Response timed-out waiting for \""
220: + s + '\"', getLocation());
221: }
222: sb.append((char) is.read());
223: }
224: }
225: log(sb.toString(), Project.MSG_INFO);
226: } catch (BuildException be) {
227: throw be;
228: } catch (Exception e) {
229: throw new BuildException(e, getLocation());
230: }
231: }
232:
233: /**
234: * Write this string to the rexec session.
235: * @param s the string to write
236: * @param echoString if true log the string sent
237: */
238: public void sendString(String s, boolean echoString) {
239: OutputStream os = this .getOutputStream();
240: try {
241: os.write((s + "\n").getBytes());
242: if (echoString) {
243: log(s, Project.MSG_INFO);
244: }
245: os.flush();
246: } catch (Exception e) {
247: throw new BuildException(e, getLocation());
248: }
249: }
250:
251: /**
252: * Read from the rexec session until the EOF is found or
253: * the timeout has been reached
254: * @param timeout The maximum number of seconds to wait
255: */
256: public void waitForEOF(Integer timeout) {
257: InputStream is = this .getInputStream();
258: try {
259: StringBuffer sb = new StringBuffer();
260: if (timeout == null || timeout.intValue() == 0) {
261: int read;
262: while ((read = is.read()) != -1) {
263: char c = (char) read;
264: sb.append(c);
265: if (c == '\n') {
266: log(sb.toString(), Project.MSG_INFO);
267: sb.delete(0, sb.length());
268: }
269: }
270: } else {
271: Calendar endTime = Calendar.getInstance();
272: endTime.add(Calendar.SECOND, timeout.intValue());
273: int read = 0;
274: while (read != -1) {
275: while (Calendar.getInstance().before(endTime)
276: && is.available() == 0) {
277: Thread.sleep(250);
278: }
279: if (is.available() == 0) {
280: log(sb.toString(), Project.MSG_INFO);
281: throw new BuildException(
282: "Response timed-out waiting for EOF",
283: getLocation());
284: }
285: read = is.read();
286: if (read != -1) {
287: char c = (char) read;
288: sb.append(c);
289: if (c == '\n') {
290: log(sb.toString(), Project.MSG_INFO);
291: sb.delete(0, sb.length());
292: }
293: }
294: }
295: }
296: if (sb.length() > 0) {
297: log(sb.toString(), Project.MSG_INFO);
298: }
299: } catch (BuildException be) {
300: throw be;
301: } catch (Exception e) {
302: throw new BuildException(e, getLocation());
303: }
304: }
305:
306: }
307:
308: /**
309: * A string to wait for from the server.
310: * A subTask <read> tag was found. Create the object,
311: * Save it in our list, and return it.
312: * @return a read sub task
313: */
314:
315: public RExecSubTask createRead() {
316: RExecSubTask task = (RExecSubTask) new RExecRead();
317: rexecTasks.addElement(task);
318: return task;
319: }
320:
321: /**
322: * Add text to send to the server
323: * A subTask <write> tag was found. Create the object,
324: * Save it in our list, and return it.
325: * @return a write sub task
326: */
327: public RExecSubTask createWrite() {
328: RExecSubTask task = (RExecSubTask) new RExecWrite();
329: rexecTasks.addElement(task);
330: return task;
331: }
332:
333: /**
334: * Verify that all parameters are included.
335: * Connect and possibly login.
336: * Iterate through the list of Reads and writes.
337: * @throws BuildException on error
338: */
339: public void execute() throws BuildException {
340: /** A server name is required to continue */
341: if (server == null) {
342: throw new BuildException("No Server Specified");
343: }
344: /** A userid and password must appear together
345: * if they appear. They are not required.
346: */
347: if (userid == null && password != null) {
348: throw new BuildException("No Userid Specified");
349: }
350: if (password == null && userid != null) {
351: throw new BuildException("No Password Specified");
352: }
353:
354: /** Create the telnet client object */
355: AntRExecClient rexec = null;
356: try {
357: rexec = new AntRExecClient();
358: try {
359: rexec.connect(server, port);
360: } catch (IOException e) {
361: throw new BuildException("Can't connect to " + server);
362: }
363: if (userid != null && password != null && command != null
364: && rexecTasks.size() == 0) {
365: // simple one-shot execution
366: rexec.rexec(userid, password, command);
367: } else {
368: // need nested read/write elements
369: handleMultipleTasks(rexec);
370: }
371:
372: /** Keep reading input stream until end of it or time-out */
373: rexec.waitForEOF(defaultTimeout);
374: } catch (IOException e) {
375: throw new BuildException("Error r-executing command", e);
376: } finally {
377: if (rexec != null && rexec.isConnected()) {
378: try {
379: rexec.disconnect();
380: } catch (IOException e) {
381: throw new BuildException(
382: "Error disconnecting from " + server);
383: }
384: }
385: }
386: }
387:
388: /**
389: * Process a 'typical' login. If it differs, use the read
390: * and write tasks explicitely
391: */
392: private void login(AntRExecClient rexec) {
393: if (addCarriageReturn) {
394: rexec.sendString("\n", true);
395: }
396: rexec.waitForString("ogin:");
397: rexec.sendString(userid, true);
398: rexec.waitForString("assword:");
399: rexec.sendString(password, false);
400: }
401:
402: /**
403: * Set the the comand to execute on the server;
404: * @param c a <code>String</code> value
405: */
406: public void setCommand(String c) {
407: this .command = c;
408: }
409:
410: /**
411: * send a carriage return after connecting; optional, defaults to false.
412: * @param b a <code>boolean</code> value
413: */
414: public void setInitialCR(boolean b) {
415: this .addCarriageReturn = b;
416: }
417:
418: /**
419: * Set the the login password to use
420: * required if <tt>userid</tt> is set.
421: * @param p a <code>String</code> value
422: */
423: public void setPassword(String p) {
424: this .password = p;
425: }
426:
427: /**
428: * Set the tcp port to connect to; default is 23.
429: * @param p an <code>int</code> value
430: */
431: public void setPort(int p) {
432: this .port = p;
433: }
434:
435: /**
436: * Set the hostname or address of the remote server.
437: * @param m a <code>String</code> value
438: */
439: public void setServer(String m) {
440: this .server = m;
441: }
442:
443: /**
444: * set a default timeout in seconds to wait for a response,
445: * zero means forever (the default)
446: * @param i an <code>Integer</code> value
447: */
448: public void setTimeout(Integer i) {
449: this .defaultTimeout = i;
450: }
451:
452: /**
453: * Set the the login id to use on the server;
454: * required if <tt>password</tt> is set.
455: * @param u a <code>String</code> value
456: */
457: public void setUserid(String u) {
458: this .userid = u;
459: }
460:
461: /**
462: * Deals with multiple read/write calls.
463: *
464: * @since Ant 1.6.3
465: */
466: private void handleMultipleTasks(AntRExecClient rexec) {
467:
468: /** Login if userid and password were specified */
469: if (userid != null && password != null) {
470: login(rexec);
471: }
472: /** Process each sub command */
473: Enumeration tasksToRun = rexecTasks.elements();
474: while (tasksToRun != null && tasksToRun.hasMoreElements()) {
475: RExecSubTask task = (RExecSubTask) tasksToRun.nextElement();
476: if (task instanceof RExecRead && defaultTimeout != null) {
477: ((RExecRead) task).setDefaultTimeout(defaultTimeout);
478: }
479: task.execute(rexec);
480: }
481: }
482: }
|