1
2
3
4
5
6
7
8
9
10
11 package org.mule.providers.ftp;
12
13 import org.mule.MuleRuntimeException;
14 import org.mule.config.i18n.CoreMessages;
15 import org.mule.config.i18n.MessageFactory;
16 import org.mule.impl.model.streaming.CallbackOutputStream;
17 import org.mule.providers.AbstractConnector;
18 import org.mule.providers.file.FilenameParser;
19 import org.mule.providers.file.SimpleFilenameParser;
20 import org.mule.umo.UMOComponent;
21 import org.mule.umo.UMOException;
22 import org.mule.umo.UMOMessage;
23 import org.mule.umo.endpoint.UMOEndpoint;
24 import org.mule.umo.endpoint.UMOEndpointURI;
25 import org.mule.umo.endpoint.UMOImmutableEndpoint;
26 import org.mule.umo.lifecycle.InitialisationException;
27 import org.mule.umo.provider.ConnectorException;
28 import org.mule.umo.provider.DispatchException;
29 import org.mule.umo.provider.UMOMessageReceiver;
30 import org.mule.util.ClassUtils;
31
32 import java.io.IOException;
33 import java.io.OutputStream;
34 import java.text.MessageFormat;
35 import java.util.HashMap;
36 import java.util.Iterator;
37 import java.util.Map;
38
39 import org.apache.commons.net.ftp.FTP;
40 import org.apache.commons.net.ftp.FTPClient;
41 import org.apache.commons.pool.ObjectPool;
42 import org.apache.commons.pool.impl.GenericObjectPool;
43
44 public class FtpConnector extends AbstractConnector
45 {
46 public static final String PROPERTY_POLLING_FREQUENCY = "pollingFrequency";
47 public static final String PROPERTY_FILENAME = "filename";
48 public static final String PROPERTY_OUTPUT_PATTERN = "outputPattern";
49 public static final String PROPERTY_PASSIVE_MODE = "passive";
50 public static final String PROPERTY_BINARY_TRANSFER = "binary";
51
52 public static final int DEFAULT_POLLING_FREQUENCY = 1000;
53
54
55
56
57
58 public static final String DEFAULT_FTP_CONNECTION_FACTORY_CLASS = "org.mule.providers.ftp.FtpConnectionFactory";
59
60
61
62
63 private long pollingFrequency;
64
65 private String outputPattern;
66
67 private FilenameParser filenameParser = new SimpleFilenameParser();
68
69 private boolean passive = true;
70
71 private boolean binary = true;
72
73
74
75
76 private boolean validateConnections = true;
77
78 private Map pools = new HashMap();
79
80 private String connectionFactoryClass = DEFAULT_FTP_CONNECTION_FACTORY_CLASS;
81
82 public String getProtocol()
83 {
84 return "ftp";
85 }
86
87 public UMOMessageReceiver createReceiver(UMOComponent component, UMOEndpoint endpoint) throws Exception
88 {
89 long polling = pollingFrequency;
90 Map props = endpoint.getProperties();
91 if (props != null)
92 {
93
94 String tempPolling = (String) props.get(PROPERTY_POLLING_FREQUENCY);
95 if (tempPolling != null)
96 {
97 polling = Long.parseLong(tempPolling);
98 }
99 }
100 if (polling <= 0)
101 {
102 polling = DEFAULT_POLLING_FREQUENCY;
103 }
104 logger.debug("set polling frequency to " + polling);
105 return serviceDescriptor.createMessageReceiver(this, component, endpoint,
106 new Object[]{new Long(polling)});
107 }
108
109
110
111
112 public long getPollingFrequency()
113 {
114 return pollingFrequency;
115 }
116
117
118
119
120 public void setPollingFrequency(long pollingFrequency)
121 {
122 this.pollingFrequency = pollingFrequency;
123 }
124
125
126
127
128
129
130 public String getConnectionFactoryClass()
131 {
132 return connectionFactoryClass;
133 }
134
135
136
137
138
139
140
141 public void setConnectionFactoryClass(final String connectionFactoryClass)
142 {
143 this.connectionFactoryClass = connectionFactoryClass;
144 }
145
146 public FTPClient getFtp(UMOEndpointURI uri) throws Exception
147 {
148 if (logger.isDebugEnabled())
149 {
150 logger.debug(">>> retrieving client for " + uri);
151 }
152 return (FTPClient) getFtpPool(uri).borrowObject();
153 }
154
155 public void releaseFtp(UMOEndpointURI uri, FTPClient client) throws Exception
156 {
157 if (logger.isDebugEnabled())
158 {
159 logger.debug("<<< releasing client for " + uri);
160 }
161 if (dispatcherFactory.isCreateDispatcherPerRequest())
162 {
163 destroyFtp(uri, client);
164 }
165 else
166 {
167 getFtpPool(uri).returnObject(client);
168 }
169 }
170
171 public void destroyFtp(UMOEndpointURI uri, FTPClient client) throws Exception
172 {
173 if (logger.isDebugEnabled())
174 {
175 logger.debug("<<< destroying client for " + uri);
176 }
177 try
178 {
179 getFtpPool(uri).invalidateObject(client);
180 }
181 catch (Exception e)
182 {
183
184 logger.debug(e.getMessage());
185 }
186 }
187
188 protected synchronized ObjectPool getFtpPool(UMOEndpointURI uri)
189 {
190 if (logger.isDebugEnabled())
191 {
192 logger.debug("=== get pool for " + uri);
193 }
194 String key = uri.getUsername() + ":" + uri.getPassword() + "@" + uri.getHost() + ":" + uri.getPort();
195 ObjectPool pool = (ObjectPool) pools.get(key);
196 if (pool == null)
197 {
198 try
199 {
200 FtpConnectionFactory connectionFactory =
201 (FtpConnectionFactory) ClassUtils.instanciateClass(getConnectionFactoryClass(),
202 new Object[] {uri}, getClass());
203 pool = new GenericObjectPool(connectionFactory);
204 ((GenericObjectPool) pool).setTestOnBorrow(this.validateConnections);
205 pools.put(key, pool);
206 }
207 catch (Exception ex)
208 {
209 throw new MuleRuntimeException(
210 MessageFactory.createStaticMessage("Hmm, couldn't instanciate FTP connection factory."), ex);
211 }
212 }
213 return pool;
214 }
215
216
217 protected void doInitialise() throws InitialisationException
218 {
219 try
220 {
221 Class objectFactoryClass = ClassUtils.loadClass(this.connectionFactoryClass, getClass());
222 if (!FtpConnectionFactory.class.isAssignableFrom(objectFactoryClass))
223 {
224 throw new InitialisationException(MessageFactory.createStaticMessage(
225 "FTP connectionFactoryClass is not an instance of org.mule.providers.ftp.FtpConnectionFactory"),
226 this);
227 }
228 }
229 catch (ClassNotFoundException e)
230 {
231 throw new InitialisationException(e, this);
232 }
233 }
234
235 protected void doDispose()
236 {
237
238 }
239
240 protected void doConnect() throws Exception
241 {
242
243 }
244
245 protected void doDisconnect() throws Exception
246 {
247
248 }
249
250 protected void doStart() throws UMOException
251 {
252
253 }
254
255 protected void doStop() throws UMOException
256 {
257 if (logger.isDebugEnabled())
258 {
259 logger.debug("!!! stopping all pools");
260 }
261 try
262 {
263 for (Iterator it = pools.values().iterator(); it.hasNext();)
264 {
265 ObjectPool pool = (ObjectPool)it.next();
266 pool.close();
267 }
268 }
269 catch (Exception e)
270 {
271 throw new ConnectorException(CoreMessages.failedToStop("FTP Connector"), this, e);
272 }
273 }
274
275
276
277
278 public String getOutputPattern()
279 {
280 return outputPattern;
281 }
282
283
284
285
286 public void setOutputPattern(String outputPattern)
287 {
288 this.outputPattern = outputPattern;
289 }
290
291
292
293
294 public FilenameParser getFilenameParser()
295 {
296 return filenameParser;
297 }
298
299
300
301
302 public void setFilenameParser(FilenameParser filenameParser)
303 {
304 this.filenameParser = filenameParser;
305 }
306
307
308
309
310
311
312 public boolean isPassive()
313 {
314 return passive;
315 }
316
317
318
319
320
321
322 public void setPassive(final boolean passive)
323 {
324 this.passive = passive;
325 }
326
327
328
329
330
331
332
333
334 public void enterActiveOrPassiveMode(FTPClient client, UMOImmutableEndpoint endpoint)
335 {
336
337
338 final String passiveString = (String)endpoint.getProperty(FtpConnector.PROPERTY_PASSIVE_MODE);
339 if (passiveString == null)
340 {
341
342 if (isPassive())
343 {
344 if (logger.isTraceEnabled())
345 {
346 logger.trace("Entering FTP passive mode");
347 }
348 client.enterLocalPassiveMode();
349 }
350 else
351 {
352 if (logger.isTraceEnabled())
353 {
354 logger.trace("Entering FTP active mode");
355 }
356 client.enterLocalActiveMode();
357 }
358 }
359 else
360 {
361
362 final boolean passiveMode = Boolean.valueOf(passiveString).booleanValue();
363 if (passiveMode)
364 {
365 if (logger.isTraceEnabled())
366 {
367 logger.trace("Entering FTP passive mode (endpoint override)");
368 }
369 client.enterLocalPassiveMode();
370 }
371 else
372 {
373 if (logger.isTraceEnabled())
374 {
375 logger.trace("Entering FTP active mode (endpoint override)");
376 }
377 client.enterLocalActiveMode();
378 }
379 }
380 }
381
382
383
384
385 public boolean isValidateConnections()
386 {
387 return validateConnections;
388 }
389
390
391
392
393
394
395
396
397 public void setValidateConnections(final boolean validateConnections)
398 {
399 this.validateConnections = validateConnections;
400 }
401
402
403
404
405
406
407 public boolean isBinary()
408 {
409 return binary;
410 }
411
412
413
414
415
416
417 public void setBinary(final boolean binary)
418 {
419 this.binary = binary;
420 }
421
422
423
424
425
426
427
428
429 public void setupFileType(FTPClient client, UMOImmutableEndpoint endpoint) throws Exception
430 {
431 int type;
432
433
434
435 final String binaryTransferString = (String)endpoint.getProperty(FtpConnector.PROPERTY_BINARY_TRANSFER);
436 if (binaryTransferString == null)
437 {
438
439 if (isBinary())
440 {
441 if (logger.isTraceEnabled())
442 {
443 logger.trace("Using FTP BINARY type");
444 }
445 type = FTP.BINARY_FILE_TYPE;
446 }
447 else
448 {
449 if (logger.isTraceEnabled())
450 {
451 logger.trace("Using FTP ASCII type");
452 }
453 type = FTP.ASCII_FILE_TYPE;
454 }
455 }
456 else
457 {
458
459 final boolean binaryTransfer = Boolean.valueOf(binaryTransferString).booleanValue();
460 if (binaryTransfer)
461 {
462 if (logger.isTraceEnabled())
463 {
464 logger.trace("Using FTP BINARY type (endpoint override)");
465 }
466 type = FTP.BINARY_FILE_TYPE;
467 }
468 else
469 {
470 if (logger.isTraceEnabled())
471 {
472 logger.trace("Using FTP ASCII type (endpoint override)");
473 }
474 type = FTP.ASCII_FILE_TYPE;
475 }
476 }
477
478 client.setFileType(type);
479 }
480
481
482
483
484
485
486
487
488
489
490
491 public OutputStream getOutputStream(UMOImmutableEndpoint endpoint, UMOMessage message)
492 throws UMOException
493 {
494 try
495 {
496 final UMOEndpointURI uri = endpoint.getEndpointURI();
497 String filename = getFilename(endpoint, message);
498
499 final FTPClient client = this.createFtpClient(endpoint);
500 try
501 {
502 OutputStream out = client.storeFileStream(filename);
503 return new CallbackOutputStream(out,
504 new CallbackOutputStream.Callback()
505 {
506 public void onClose() throws Exception
507 {
508 try
509 {
510 if (!client.completePendingCommand())
511 {
512 client.logout();
513 client.disconnect();
514 throw new IOException("FTP Stream failed to complete pending request");
515 }
516 }
517 finally
518 {
519 releaseFtp(uri, client);
520 }
521 }
522 });
523 }
524 catch (Exception e)
525 {
526 logger.debug("Error getting output stream: ", e);
527 releaseFtp(uri, client);
528 throw e;
529 }
530 }
531 catch (Exception e)
532 {
533 throw new DispatchException(CoreMessages.streamingFailedNoStream(), message, endpoint, e);
534 }
535 }
536
537 private String getFilename(UMOImmutableEndpoint endpoint, UMOMessage message) throws IOException
538 {
539 String filename = (String)message.getProperty(FtpConnector.PROPERTY_FILENAME);
540 String outPattern = (String) endpoint.getProperty(FtpConnector.PROPERTY_OUTPUT_PATTERN);
541 if (outPattern == null)
542 {
543 outPattern = message.getStringProperty(FtpConnector.PROPERTY_OUTPUT_PATTERN,
544 getOutputPattern());
545 }
546 if (outPattern != null || filename == null)
547 {
548 filename = generateFilename(message, outPattern);
549 }
550 if (filename == null)
551 {
552 throw new IOException("Filename is null");
553 }
554 return filename;
555 }
556
557 private String generateFilename(UMOMessage message, String pattern)
558 {
559 if (pattern == null)
560 {
561 pattern = getOutputPattern();
562 }
563 return getFilenameParser().getFilename(message, pattern);
564 }
565
566
567
568
569
570 protected FTPClient createFtpClient(UMOImmutableEndpoint endpoint) throws Exception
571 {
572 UMOEndpointURI uri = endpoint.getEndpointURI();
573 FTPClient client = this.getFtp(uri);
574
575 this.enterActiveOrPassiveMode(client, endpoint);
576 this.setupFileType(client, endpoint);
577
578 String path = uri.getPath();
579
580
581 if ((path.length() >= 2) && (path.charAt(1) == '~'))
582 {
583 path = path.substring(1);
584 }
585
586 if (!client.changeWorkingDirectory(path))
587 {
588 throw new IOException(MessageFormat.format("Failed to change working directory to {0}. Ftp error: {1}",
589 new Object[] {path, new Integer(client.getReplyCode())}));
590 }
591 return client;
592 }
593 }