View Javadoc

1   /*
2    * $Id: FtpConnector.java 7976 2007-08-21 14:26:13Z dirk.olmes $
3    * --------------------------------------------------------------------------------------
4    * Copyright (c) MuleSource, Inc.  All rights reserved.  http://www.mulesource.com
5    *
6    * The software in this package is published under the terms of the CPAL v1.0
7    * license, a copy of which has been included with this distribution in the
8    * LICENSE.txt file.
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.util.HashMap;
35  import java.util.Iterator;
36  import java.util.Map;
37  
38  import org.apache.commons.net.ftp.FTP;
39  import org.apache.commons.net.ftp.FTPClient;
40  import org.apache.commons.pool.ObjectPool;
41  import org.apache.commons.pool.impl.GenericObjectPool;
42  
43  public class FtpConnector extends AbstractConnector
44  {
45      public static final String PROPERTY_POLLING_FREQUENCY = "pollingFrequency";
46      public static final String PROPERTY_FILENAME = "filename";
47      public static final String PROPERTY_OUTPUT_PATTERN = "outputPattern";
48      public static final String PROPERTY_PASSIVE_MODE = "passive";
49      public static final String PROPERTY_BINARY_TRANSFER = "binary";
50  
51      public static final int DEFAULT_POLLING_FREQUENCY = 1000;
52  
53      /**
54       *  TODO it makes sense to have a type-safe adapter for FTP specifically, but without
55       *  Java 5's covariant return types the benefits are diminished. Keeping it simple for now.
56       */
57      public static final String DEFAULT_FTP_CONNECTION_FACTORY_CLASS = "org.mule.providers.ftp.FtpConnectionFactory";
58  
59      /**
60       * Time in milliseconds to poll. On each poll the poll() method is called
61       */
62      private long pollingFrequency;
63  
64      private String outputPattern;
65  
66      private FilenameParser filenameParser = new SimpleFilenameParser();
67  
68      private boolean passive = true;
69  
70      private boolean binary = true;
71  
72      /**
73       * Whether to test FTP connection on each take from pool.
74       */
75      private boolean validateConnections = true;
76  
77      private Map pools = new HashMap();
78  
79      private String connectionFactoryClass = DEFAULT_FTP_CONNECTION_FACTORY_CLASS;
80  
81      public String getProtocol()
82      {
83          return "ftp";
84      }
85  
86      public UMOMessageReceiver createReceiver(UMOComponent component, UMOEndpoint endpoint) throws Exception
87      {
88          long polling = pollingFrequency;
89          Map props = endpoint.getProperties();
90          if (props != null)
91          {
92              // Override properties on the endpoint for the specific endpoint
93              String tempPolling = (String) props.get(PROPERTY_POLLING_FREQUENCY);
94              if (tempPolling != null)
95              {
96                  polling = Long.parseLong(tempPolling);
97              }
98          }
99          if (polling <= 0)
100         {
101             polling = DEFAULT_POLLING_FREQUENCY;
102         }
103         logger.debug("set polling frequency to " + polling);
104         return serviceDescriptor.createMessageReceiver(this, component, endpoint,
105                 new Object[]{new Long(polling)});
106     }
107 
108     /**
109      * @return Returns the pollingFrequency.
110      */
111     public long getPollingFrequency()
112     {
113         return pollingFrequency;
114     }
115 
116     /**
117      * @param pollingFrequency The pollingFrequency to set.
118      */
119     public void setPollingFrequency(long pollingFrequency)
120     {
121         this.pollingFrequency = pollingFrequency;
122     }
123 
124     /**
125      * Getter for property 'connectionFactoryClass'.
126      *
127      * @return Value for property 'connectionFactoryClass'.
128      */
129     public String getConnectionFactoryClass()
130     {
131         return connectionFactoryClass;
132     }
133 
134     /**
135      * Setter for property 'connectionFactoryClass'. Should be an instance of
136      * {@link FtpConnectionFactory}.
137      *
138      * @param connectionFactoryClass Value to set for property 'connectionFactoryClass'.
139      */
140     public void setConnectionFactoryClass(final String connectionFactoryClass)
141     {
142         this.connectionFactoryClass = connectionFactoryClass;
143     }
144 
145     public FTPClient getFtp(UMOEndpointURI uri) throws Exception
146     {
147         if (logger.isDebugEnabled())
148         {
149             logger.debug(">>> retrieving client for " + uri);
150         }
151         return (FTPClient) getFtpPool(uri).borrowObject();
152     }
153 
154     public void releaseFtp(UMOEndpointURI uri, FTPClient client) throws Exception
155     {
156         if (logger.isDebugEnabled())
157         {
158             logger.debug("<<< releasing client for " + uri);
159         }
160         if (dispatcherFactory.isCreateDispatcherPerRequest())
161         {
162             destroyFtp(uri, client);
163         }
164         else
165         {
166             getFtpPool(uri).returnObject(client);
167         }
168     }
169 
170     public void destroyFtp(UMOEndpointURI uri, FTPClient client) throws Exception
171     {
172         if (logger.isDebugEnabled())
173         {
174             logger.debug("<<< destroying client for " + uri);
175         }
176         try
177         {
178             getFtpPool(uri).invalidateObject(client);
179         }
180         catch (Exception e)
181         {
182             // no way to test if pool is closed except try to access it
183             logger.debug(e.getMessage());
184         }
185     }
186 
187     protected synchronized ObjectPool getFtpPool(UMOEndpointURI uri)
188     {
189         if (logger.isDebugEnabled())
190         {
191             logger.debug("=== get pool for " + uri);
192         }
193         String key = uri.getUsername() + ":" + uri.getPassword() + "@" + uri.getHost() + ":" + uri.getPort();
194         ObjectPool pool = (ObjectPool) pools.get(key);
195         if (pool == null)
196         {
197             try
198             {
199                 FtpConnectionFactory connectionFactory =
200                         (FtpConnectionFactory) ClassUtils.instanciateClass(getConnectionFactoryClass(),
201                                                                             new Object[] {uri}, getClass());
202                 pool = new GenericObjectPool(connectionFactory);
203                 ((GenericObjectPool) pool).setTestOnBorrow(this.validateConnections);
204                 pools.put(key, pool);
205             }
206             catch (Exception ex)
207             {
208                 throw new MuleRuntimeException(
209                         MessageFactory.createStaticMessage("Hmm, couldn't instanciate FTP connection factory."), ex);
210             }
211         }
212         return pool;
213     }
214 
215 
216     protected void doInitialise() throws InitialisationException
217     {
218         try
219         {
220             Class objectFactoryClass = ClassUtils.loadClass(this.connectionFactoryClass, getClass());
221             if (!FtpConnectionFactory.class.isAssignableFrom(objectFactoryClass))
222             {
223                 throw new InitialisationException(MessageFactory.createStaticMessage(
224                         "FTP connectionFactoryClass is not an instance of org.mule.providers.ftp.FtpConnectionFactory"),
225                         this);
226             }
227         }
228         catch (ClassNotFoundException e)
229         {
230             throw new InitialisationException(e, this);
231         }
232     }
233 
234     protected void doDispose()
235     {
236         // template method
237     }
238 
239     protected void doConnect() throws Exception
240     {
241         // template method
242     }
243 
244     protected void doDisconnect() throws Exception
245     {
246         // template method
247     }
248 
249     protected void doStart() throws UMOException
250     {
251         // template method
252     }
253 
254     protected void doStop() throws UMOException
255     {
256         if (logger.isDebugEnabled())
257         {
258             logger.debug("!!! stopping all pools");
259         }
260         try
261         {
262             for (Iterator it = pools.values().iterator(); it.hasNext();)
263             {
264                 ObjectPool pool = (ObjectPool)it.next();
265                 pool.close();
266             }
267         }
268         catch (Exception e)
269         {
270             throw new ConnectorException(CoreMessages.failedToStop("FTP Connector"), this, e);
271         }
272     }
273 
274     /**
275      * @return Returns the outputPattern.
276      */
277     public String getOutputPattern()
278     {
279         return outputPattern;
280     }
281 
282     /**
283      * @param outputPattern The outputPattern to set.
284      */
285     public void setOutputPattern(String outputPattern)
286     {
287         this.outputPattern = outputPattern;
288     }
289 
290     /**
291      * @return Returns the filenameParser.
292      */
293     public FilenameParser getFilenameParser()
294     {
295         return filenameParser;
296     }
297 
298     /**
299      * @param filenameParser The filenameParser to set.
300      */
301     public void setFilenameParser(FilenameParser filenameParser)
302     {
303         this.filenameParser = filenameParser;
304     }
305 
306     /**
307      * Getter for FTP passive mode.
308      * 
309      * @return true if using FTP passive mode
310      */
311     public boolean isPassive()
312     {
313         return passive;
314     }
315 
316     /**
317      * Setter for FTP passive mode.
318      * 
319      * @param passive passive mode flag
320      */
321     public void setPassive(final boolean passive)
322     {
323         this.passive = passive;
324     }
325 
326     /**
327      * Passive mode is OFF by default. The value is taken from the connector
328      * settings. In case there are any overriding properties set on the endpoint,
329      * those will be used.
330      * 
331      * @see #setPassive(boolean)
332      */
333     public void enterActiveOrPassiveMode(FTPClient client, UMOImmutableEndpoint endpoint)
334     {
335         // well, no endpoint URI here, as we have to use the most common denominator
336         // in API :(
337         final String passiveString = (String)endpoint.getProperty(FtpConnector.PROPERTY_PASSIVE_MODE);
338         if (passiveString == null)
339         {
340             // try the connector properties then
341             if (isPassive())
342             {
343                 if (logger.isTraceEnabled())
344                 {
345                     logger.trace("Entering FTP passive mode");
346                 }
347                 client.enterLocalPassiveMode();
348             }
349             else
350             {
351                 if (logger.isTraceEnabled())
352                 {
353                     logger.trace("Entering FTP active mode");
354                 }
355                 client.enterLocalActiveMode();
356             }
357         }
358         else
359         {
360             // override with endpoint's definition
361             final boolean passiveMode = Boolean.valueOf(passiveString).booleanValue();
362             if (passiveMode)
363             {
364                 if (logger.isTraceEnabled())
365                 {
366                     logger.trace("Entering FTP passive mode (endpoint override)");
367                 }
368                 client.enterLocalPassiveMode();
369             }
370             else
371             {
372                 if (logger.isTraceEnabled())
373                 {
374                     logger.trace("Entering FTP active mode (endpoint override)");
375                 }
376                 client.enterLocalActiveMode();
377             }
378         }
379     }
380 
381     /**
382      * Whether to test FTP connection on each take from pool.
383      */
384     public boolean isValidateConnections()
385     {
386         return validateConnections;
387     }
388 
389     /**
390      * Whether to test FTP connection on each take from pool. This takes care of a
391      * failed (or restarted) FTP server at the expense of an additional NOOP command
392      * packet being sent, but increases overall availability. <p/> Disable to gain
393      * slight performance gain or if you are absolutely sure of the FTP server
394      * availability. <p/> The default value is <code>true</code>
395      */
396     public void setValidateConnections(final boolean validateConnections)
397     {
398         this.validateConnections = validateConnections;
399     }
400 
401     /**
402      * Getter for FTP transfer type.
403      * 
404      * @return true if using FTP binary type
405      */
406     public boolean isBinary()
407     {
408         return binary;
409     }
410 
411     /**
412      * Setter for FTP transfer type.
413      * 
414      * @param binary binary type flag
415      */
416     public void setBinary(final boolean binary)
417     {
418         this.binary = binary;
419     }
420 
421     /**
422      * Transfer type is BINARY by default. The value is taken from the connector
423      * settings. In case there are any overriding properties set on the endpoint,
424      * those will be used. <p/> The alternative type is ASCII. <p/>
425      * 
426      * @see #setBinary(boolean)
427      */
428     public void setupFileType(FTPClient client, UMOImmutableEndpoint endpoint) throws Exception
429     {
430         int type;
431 
432         // well, no endpoint URI here, as we have to use the most common denominator
433         // in API :(
434         final String binaryTransferString = (String)endpoint.getProperty(FtpConnector.PROPERTY_BINARY_TRANSFER);
435         if (binaryTransferString == null)
436         {
437             // try the connector properties then
438             if (isBinary())
439             {
440                 if (logger.isTraceEnabled())
441                 {
442                     logger.trace("Using FTP BINARY type");
443                 }
444                 type = FTP.BINARY_FILE_TYPE;
445             }
446             else
447             {
448                 if (logger.isTraceEnabled())
449                 {
450                     logger.trace("Using FTP ASCII type");
451                 }
452                 type = FTP.ASCII_FILE_TYPE;
453             }
454         }
455         else
456         {
457             // override with endpoint's definition
458             final boolean binaryTransfer = Boolean.valueOf(binaryTransferString).booleanValue();
459             if (binaryTransfer)
460             {
461                 if (logger.isTraceEnabled())
462                 {
463                     logger.trace("Using FTP BINARY type (endpoint override)");
464                 }
465                 type = FTP.BINARY_FILE_TYPE;
466             }
467             else
468             {
469                 if (logger.isTraceEnabled())
470                 {
471                     logger.trace("Using FTP ASCII type (endpoint override)");
472                 }
473                 type = FTP.ASCII_FILE_TYPE;
474             }
475         }
476 
477         client.setFileType(type);
478     }
479 
480     /**
481      * Well get the output stream (if any) for this type of transport. Typically this
482      * will be called only when Streaming is being used on an outbound endpoint
483      *
484      * @param endpoint the endpoint that releates to this Dispatcher
485      * @param message the current message being processed
486      * @return the output stream to use for this request or null if the transport
487      *         does not support streaming
488      * @throws org.mule.umo.UMOException
489      */
490     public OutputStream getOutputStream(UMOImmutableEndpoint endpoint, UMOMessage message)
491         throws UMOException
492     {
493         try
494         {
495             final UMOEndpointURI uri = endpoint.getEndpointURI();
496             String filename = getFilename(endpoint, message);
497             final FTPClient client = getFtp(uri);
498 
499             try
500             {
501                 enterActiveOrPassiveMode(client, endpoint);
502                 setupFileType(client, endpoint);
503                 if (!client.changeWorkingDirectory(uri.getPath()))
504                 {
505                     throw new IOException("Ftp error: " + client.getReplyCode());
506                 }
507                 OutputStream out = client.storeFileStream(filename);
508                 return new CallbackOutputStream(out,
509                         new CallbackOutputStream.Callback()
510                         {
511                             public void onClose() throws Exception
512                             {
513                                 try
514                                 {
515                                     if (!client.completePendingCommand())
516                                     {
517                                         client.logout();
518                                         client.disconnect();
519                                         throw new IOException("FTP Stream failed to complete pending request");
520                                     }
521                                 }
522                                 finally
523                                 {
524                                     releaseFtp(uri, client);
525                                 }
526                             }
527                         });
528             }
529             catch (Exception e)
530             {
531                 logger.debug("Error getting output stream: ", e);
532                 releaseFtp(uri, client);
533                 throw e;
534             }
535         }
536         catch (Exception e)
537         {
538             throw new DispatchException(CoreMessages.streamingFailedNoStream(), message, endpoint, e);
539         }
540     }
541 
542     private String getFilename(UMOImmutableEndpoint endpoint, UMOMessage message) throws IOException
543     {
544         String filename = (String)message.getProperty(FtpConnector.PROPERTY_FILENAME);
545         if (filename == null)
546         {
547             String outPattern = (String) endpoint.getProperty(FtpConnector.PROPERTY_OUTPUT_PATTERN);
548             if (outPattern == null){
549                 outPattern = message.getStringProperty(FtpConnector.PROPERTY_OUTPUT_PATTERN,
550                         getOutputPattern());
551             }
552             filename = generateFilename(message, outPattern);
553         }
554         if (filename == null)
555         {
556             throw new IOException("Filename is null");
557         }
558         return filename;
559     }
560 
561     private String generateFilename(UMOMessage message, String pattern)
562     {
563         if (pattern == null)
564         {
565             pattern = getOutputPattern();
566         }
567         return getFilenameParser().getFilename(message, pattern);
568     }
569 
570 }