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