1 /********************************************************************************
2 * $Id: HylaFAXClient.java 125 2008-07-10 20:32:42Z sjardine $
3 *
4 * Copyright 1999, 2000 Joe Phillips <jaiger@net-foundry.com>
5 * Copyright 2001 Innovation Software Group, LLC - http://www.innovationsw.com
6 * Copyright 2006 John Yeary <jyeary@javanetwork.net>
7 * Copyright 2007, 2008 Steven Jardine, MJN Services, Inc. <sjardine@users.sourceforge.net>
8 *
9 * All rights reserved. This program and the accompanying materials are made
10 * available under the terms of the GNU Lesser Public License v2.1 which
11 * accompanies this distribution, and is available at
12 * http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
13 *
14 * For more information on the HylaFAX Fax Server please see
15 * HylaFAX - http://www.hylafax.org or
16 * Hylafax+ - http://hylafax.sourceforge.net
17 *
18 * Contributors:
19 * Stefan Unterhofer - Initial API and implementation
20 * Steven Jardine - Misc fixes, Code formatting, rework of license header,
21 * javadoc
22 ******************************************************************************/
23 package gnu.hylafax;
24
25 import gnu.inet.ftp.ActiveGetter;
26 import gnu.inet.ftp.ActivePutter;
27 import gnu.inet.ftp.ConnectionListener;
28 import gnu.inet.ftp.Getter;
29 import gnu.inet.ftp.PassiveConnection;
30 import gnu.inet.ftp.PassiveGetter;
31 import gnu.inet.ftp.PassivePutter;
32 import gnu.inet.ftp.Putter;
33 import gnu.inet.ftp.ServerResponseException;
34 import gnu.inet.ftp.TransferListener;
35
36 import java.io.BufferedReader;
37 import java.io.ByteArrayInputStream;
38 import java.io.ByteArrayOutputStream;
39 import java.io.FileNotFoundException;
40 import java.io.IOException;
41 import java.io.InputStream;
42 import java.io.InputStreamReader;
43 import java.io.OutputStream;
44 import java.util.ArrayList;
45 import java.util.Enumeration;
46 import java.util.List;
47 import java.util.Vector;
48
49 /***
50 * This class implements convenience methods that wrapper the ClientProtocol
51 * methods for common functionality.
52 *
53 * Most developers will want to use this class rather than the lower-level
54 * ClientProtocol methods directly.
55 *
56 * @version $Revision: 125 $
57 * @author Joe Phillips <jaiger@net-foundry.com>
58 * @author John Yeary <jyeary@javanetwork.net>
59 * @author Steven Jardine <steve@mjnservices.com>
60 */
61 public class HylaFAXClient extends HylaFAXClientProtocol implements Client {
62
63 private static final int GET = 0;
64
65 private static final int LIST = 1;
66
67 private static final int NAMELIST = 2;
68
69 /***
70 * This is a cached PassiveConnection instance. After some hair-pulling I
71 * came to realize that in order to avoid an annoying bug, I needed to cache
72 * the last PassiveConnection instance in case an error was encountered in
73 * the last passive transfer. In my test cases the error condition that
74 * triggered the wierd bug could be duplicated by attempting to
75 * getList(some-non-exising-file). This caused an exception during the LIST
76 * transfer. The next PASV command would return the same IP/Port values.
77 * Opening a second socket to the same IP/Port would yield an exception or
78 * other errors. Caching the last PassiveConnection (and by extension the
79 * Socket) I could reuse the last PassiveConnection values avoiding the
80 * bug/error-condition. This is not very pretty but it works. Unfortunately,
81 * I'm worried that it may not be very portable since it relies on the
82 * behavior of the HylaFAX server. I'll have to see if this behavior is in
83 * the FTP RFC (RFC0959.)
84 * <P>
85 * Whenever a successful passive transfer occurrs, this variable should be
86 * set to null, thereby invalidating the cached value.
87 */
88 protected PassiveConnection connection = null;
89
90 private Vector connectionListeners;
91
92 private char mode;
93
94
95 private boolean passive;
96
97 private Vector transferListeners;
98
99 /***
100 * default constructor. initialize class state.
101 */
102 public HylaFAXClient() {
103 passive = false;
104 mode = MODE_STREAM;
105 connectionListeners = new Vector();
106 transferListeners = new Vector();
107 }
108
109 /***
110 * Register a connection listener with the event source.
111 *
112 * @param listener
113 * the listener to register with the event source
114 */
115 public void addConnectionListener(ConnectionListener listener) {
116 connectionListeners.addElement(listener);
117 }
118
119 /***
120 * Register a set of connection listeners with the event source.
121 *
122 * @param listeners
123 * the listeners to register with the event source
124 */
125 public void addConnectionListeners(Vector listeners) {
126 Enumeration enumeration = listeners.elements();
127
128 while (enumeration.hasMoreElements()) {
129 ConnectionListener listener = (ConnectionListener) enumeration
130 .nextElement();
131 connectionListeners.addElement(listener);
132 }
133 }
134
135 /***
136 * Register a transfer listener with the event source.
137 *
138 * @param listener
139 * the listener to register with the event source
140 */
141 public void addTransferListener(TransferListener listener) {
142 transferListeners.addElement(listener);
143 }
144
145 /***
146 * Register a set of transfer listeners with the event source.
147 *
148 * @param listeners
149 * the listeners to register with the event source
150 */
151 public void addTransferListeners(Vector listeners) {
152 Enumeration enumeration = listeners.elements();
153
154 while (enumeration.hasMoreElements()) {
155 TransferListener listener = (TransferListener) enumeration
156 .nextElement();
157 transferListeners.addElement(listener);
158 }
159 }
160
161 /***
162 * Create a new job in the server
163 *
164 * @return a new Job instance on the server
165 * @exception ServerResponseException
166 * @exception IOException
167 * an IO error occurred while communicating with the server
168 */
169 public Job createJob() throws ServerResponseException, IOException {
170 return new gnu.hylafax.job.Job(this);
171 }
172
173 /***
174 * Delete the given done or suspended job.
175 *
176 * @param job
177 * the (done or suspended) job to delete
178 * @exception ServerResponseException
179 * @exception IOException
180 * an IO error occurred while communicating with the server
181 */
182 public void delete(Job job) throws ServerResponseException, IOException {
183 jdele(job.getId());
184 }
185
186 /***
187 * GET the named file, FTP style.
188 *
189 * @param path
190 * the name of the file to GET. This can be a full or partial
191 * path.
192 * @param out
193 * the OutputStream to write the file data to
194 * @exception IOException
195 * an IO error occurred
196 * @exception ServerResponseException
197 * the server reported an error
198 * @exception FileNotFoundException
199 * the given path does not exist
200 */
201 public synchronized void get(String path, OutputStream out)
202 throws IOException, FileNotFoundException, ServerResponseException {
203 get(path, out, GET);
204 }
205
206 private synchronized void get(String path, OutputStream out, int type)
207 throws IOException, FileNotFoundException, ServerResponseException {
208
209 Getter getter;
210 if (passive == true) {
211
212 if (connection == null) {
213 connection = new PassiveConnection(pasv());
214 }
215 getter = new PassiveGetter(out, connection);
216 } else {
217 getter = new ActiveGetter(out);
218
219 port(getInetAddress(), ((ActiveGetter) getter).getPort());
220 }
221
222
223 getter.addConnectionListeners(connectionListeners);
224 getter.addTransferListeners(transferListeners);
225 getter.start();
226
227
228 try {
229 switch (type) {
230 case GET:
231 retr(path);
232 break;
233 case LIST:
234 list(path);
235 break;
236 case NAMELIST:
237 nlst(path);
238 break;
239 }
240 } catch (FileNotFoundException fnfe) {
241 getter.cancel();
242 throw fnfe;
243 } catch (IOException ioe) {
244 getter.cancel();
245 throw ioe;
246 } catch (ServerResponseException sree) {
247 getter.cancel();
248 throw sree;
249 } finally {
250
251 try {
252 getter.join();
253 } catch (InterruptedException ie) {
254
255 }
256 }
257 connection = null;
258 }
259
260 /***
261 * Get a Job instance for the given job id
262 *
263 * @param id
264 * the id of the job to get
265 * @exception ServerResponseException
266 * @exception IOException
267 * an IO error occurred while communicating with the server
268 */
269 public Job getJob(long id) throws ServerResponseException, IOException {
270 return new gnu.hylafax.job.Job(this, id);
271 }
272
273 /***
274 * Get a long-style listing of files in the current directory.
275 *
276 * <b>NOTE:</b> this calls the list() method internally with the "." path.
277 *
278 * @exception IOException
279 * an IO error occurred
280 * @exception FileNotFoundException
281 * the "." path doesn't exist
282 * @exception ServerResponseException
283 * the server reported an error
284 * @return a Vector of Strings containing the list information
285 */
286 public synchronized Vector getList() throws IOException,
287 FileNotFoundException, ServerResponseException {
288 return getList(null, false);
289 }
290
291 /***
292 * Get a long-style listing of files in the given directory.
293 *
294 * <b>NOTE:</b> this calls the list() method internally.
295 *
296 * @param path
297 * the path that we're interested in finding the contents of
298 * @exception IOException
299 * an IO error occurred
300 * @exception FileNotFoundException
301 * the given path doesn't exist
302 * @exception ServerResponseException
303 * the server reported an error
304 * @return a Vector of Strings containing the list information
305 */
306 public synchronized Vector getList(String path) throws IOException,
307 FileNotFoundException, ServerResponseException {
308 return getList(path, false);
309 }
310
311 private synchronized Vector getList(String path, boolean namelist)
312 throws IOException, FileNotFoundException, ServerResponseException {
313
314 ByteArrayOutputStream buffer = new ByteArrayOutputStream();
315 get(path, buffer, namelist ? NAMELIST : LIST);
316
317 Vector result = new Vector();
318 BufferedReader data = new BufferedReader(new InputStreamReader(
319 new ByteArrayInputStream(buffer.toByteArray())));
320 try {
321 String line = null;
322 String next = null;
323 while ((next = data.readLine()) != null) {
324 next = next.trim();
325 if (next.endsWith("//")) {
326 next = next.substring(0, next.lastIndexOf("//"));
327 line = (line == null ? next : line + " " + next).trim();
328 continue;
329 }
330 line = (line == null ? next : line + " " + next).trim();
331 result.add(line);
332 line = null;
333 }
334 } finally {
335 data.close();
336 }
337
338 return result;
339
340 }
341
342 /***
343 * get name list of files in the current directory. Similar to getList() but
344 * returns filenames only where getList() returns other, system dependant
345 * information.
346 *
347 * @exception IOException
348 * an IO error occurred
349 * @exception ServerResponseException
350 * the server reported an error
351 * @exception FileNotFoundException
352 * the requested path does not exist
353 * @return Vector of Strings containing filenames
354 */
355 public synchronized Vector getNameList() throws IOException,
356 ServerResponseException, FileNotFoundException {
357 return getNameList(null);
358 }
359
360 /***
361 * Get name list of files in the given directory. Similar to getList() but
362 * returns filenames only where getList() returns other, system dependant
363 * information.
364 *
365 * @param path
366 * the path of the directory that we want the name list of
367 * @exception IOException
368 * an IO error occurred
369 * @exception ServerResponseException
370 * the server reported an error
371 * @exception FileNotFoundException
372 * the requested path does not exist
373 * @return Vector of Strings containing filenames
374 */
375 public synchronized Vector getNameList(String path) throws IOException,
376 ServerResponseException, FileNotFoundException {
377 return getList(path, true);
378 }
379
380 /***
381 * Check whether passive transfers have been enabled
382 *
383 * @return true if passive transfers are enabled, false otherwise
384 */
385 public synchronized boolean getPassive() {
386 return passive;
387 }
388
389 /***
390 * Interrupt the given job.
391 *
392 * @param job
393 * the job to interrupt
394 * @exception ServerResponseException
395 * @exception IOException
396 * an IO error occurred while communicating with the server
397 */
398 public void interrupt(Job job) throws ServerResponseException, IOException {
399 jintr(job.getId());
400 }
401
402 /***
403 * Kill the given job
404 *
405 * @param job
406 * the job to kill
407 * @exception ServerResponseException
408 * @exception IOException
409 * an IO error occurred while communicating with the server
410 */
411 public void kill(Job job) throws ServerResponseException, IOException {
412 jkill(job.getId());
413 }
414
415 /***
416 * Set the transfer mode. Valid mode values are MODE_* listed in the
417 * ClientProtocol class.
418 *
419 * @param mode
420 * the new mode setting
421 * @exception IOException
422 * an io error occurred talking to the server
423 * @exception ServerResponseException
424 * the server replied with an error code
425 */
426 public synchronized void mode(char newMode) throws IOException,
427 ServerResponseException {
428 super.mode(newMode);
429 this.mode = newMode;
430 }
431
432 /***
433 * put a file with a unique name.
434 *
435 * <b>NOTE:</b> this calls stou() internally.
436 *
437 * @exception IOException
438 * a socket IO error occurred
439 * @exception ServerResponseException
440 * the server responded with an error code
441 * @return the name of the file created
442 */
443 public synchronized String put(InputStream data) throws IOException,
444 ServerResponseException {
445 return put(data, null, false);
446 }
447
448 /***
449 * Store a file.
450 *
451 * <b>NOTE:</b> this calls stor() internally.
452 *
453 * @param pathname
454 * name of file to store on server (where to put the file on the
455 * server)
456 * @exception IOException
457 * a socket IO error occurred
458 * @exception ServerResponseException
459 * the server responded with an error
460 */
461 public synchronized void put(InputStream in, String pathname)
462 throws IOException, ServerResponseException {
463 put(in, pathname, false);
464 }
465
466 /***
467 * Puts a file on the server. If temporary is set the file is a temporary
468 * file and will be deleted when the connection is closed.
469 *
470 * <b>NOTE:</b> this calls stot(), stou() or stor() internally.
471 *
472 * @param data
473 * @param pathname
474 * the pathname of the file. Should be set to null if file is a
475 * temporary file or no specific pathname is desired.
476 * @param temporary
477 * is the file a temporary file?
478 * @return the filename of the file. Will be NULL when pathname is not null.
479 * @throws IOException
480 * io error occurred talking to the server
481 * @throws ServerResponseException
482 * server replied with error code
483 */
484 private synchronized String put(InputStream data, String pathname,
485 boolean temporary) throws IOException, ServerResponseException {
486 String filename;
487 Putter put;
488
489 if (passive == true) {
490
491 if (connection == null) {
492 connection = new PassiveConnection(pasv());
493 }
494 put = new PassivePutter(data, connection);
495 } else {
496
497 put = new ActivePutter(data);
498 port(getInetAddress(), ((ActivePutter) put).getPort());
499 }
500
501 put.setMode(mode);
502 put.addConnectionListeners(connectionListeners);
503 put.addTransferListeners(transferListeners);
504 put.start();
505
506
507 try {
508 if (pathname != null) {
509 stor(data, pathname);
510 return null;
511 }
512 filename = temporary ? stot(data) : stou(data);
513 } catch (IOException ioe) {
514 put.cancel();
515 throw ioe;
516 } catch (ServerResponseException sree) {
517 put.cancel();
518 throw sree;
519 } finally {
520
521 try {
522 put.join();
523 } catch (InterruptedException ie) {
524
525 }
526 }
527 connection = null;
528 return filename;
529 }
530
531 /***
532 * Put a temp file, the data is stored in a uniquely named file on the
533 * server. The remote temp file is deleted when the connection is closed.
534 *
535 * <b>NOTE:</b> this calls stot() internally.
536 *
537 * @exception IOException
538 * io error occurred talking to the server
539 * @exception ServerResponseException
540 * server replied with error code
541 * @return the filename of the temp file
542 */
543 public synchronized String putTemporary(InputStream data)
544 throws IOException, ServerResponseException {
545 return put(data, null, true);
546 }
547
548 /***
549 * De-register a connection listener with the event source.
550 *
551 * @param listener
552 * the listener to de-register with the event source
553 */
554 public void removeConnectionListener(ConnectionListener listener) {
555 connectionListeners.removeElement(listener);
556 }
557
558 /***
559 * De-register a transfer listener with the event source.
560 *
561 * @param listener
562 * the listener to de-register with the event source
563 */
564 public void removeTransferListener(TransferListener listener) {
565 transferListeners.removeElement(listener);
566 }
567
568 /***
569 * Retry a given job with a default killtime of "now + 3 hours".
570 *
571 * @param id
572 * the job id to retry.
573 * @return the job id associated with the new job.
574 * @throws ServerResponseException
575 * @throws IOException
576 */
577 public long retry(long id) throws ServerResponseException, IOException {
578 return retry(id, "000259");
579 }
580
581 /***
582 * Retry a given job.
583 *
584 * @param id
585 * the job id to retry.
586 * @param killTime
587 * the new killTime for the job.
588 * @return the job id associated with the new job.
589 * @throws ServerResponseException
590 * @throws IOException
591 */
592 public long retry(long id, String killTime) throws ServerResponseException,
593 IOException {
594 job(id);
595
596 List documents = new ArrayList();
597 String[] docs = jparm("document").split("\n");
598 for (int count = 0; count < docs.length; count++) {
599 String document = docs[count];
600 if (document.equals("End of documents."))
601 break;
602 documents.add(document.split(" ")[1]);
603 }
604 jnew();
605 for (int index = 0; index < documents.size(); index++) {
606 jparm("document", documents.get(index));
607 }
608 jparm("lasttime", killTime);
609 return jsubm();
610 }
611
612 /***
613 * enable or disable passive transfers
614 *
615 * @param passive
616 * indicates whether passive transfers should be used
617 */
618 public synchronized void setPassive(boolean passive) {
619 this.passive = passive;
620 }
621
622 /***
623 * Submit the given job to the scheduler.
624 *
625 * @param job
626 * the Job to submit
627 * @exception ServerResponseException
628 * @exception IOException
629 * an IO error occurred while communicating with the server
630 */
631 public void submit(Job job) throws ServerResponseException, IOException {
632 jsubm(job.getId());
633 }
634
635 /***
636 * Suspend the given job from the scheduler.
637 *
638 * @param job
639 * the Job to suspend
640 * @exception ServerResponseException
641 * @exception IOException
642 * an IO error occurred while communicating with the server
643 */
644 public void suspend(Job job) throws ServerResponseException, IOException {
645 jsusp(job.getId());
646 }
647
648 /***
649 * wait for the given job to complete
650 *
651 * @param job
652 * the job to wait for
653 * @exception ServerResponseException
654 * @exception IOException
655 * an IO error occurred while communicating with the server
656 */
657 public void wait(Job job) throws ServerResponseException, IOException {
658 jwait(job.getId());
659 }
660
661 }