Coverage Report - org.mule.providers.ftp.FtpConnector
 
Classes in this File Line Coverage Branch Coverage Complexity
FtpConnector
0%
0/152
0%
0/31
2.5
FtpConnector$1
0%
0/8
0%
0/1
2.5
 
 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  0
 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  0
     private FilenameParser filenameParser = new SimpleFilenameParser();
 67  
 
 68  0
     private boolean passive = true;
 69  
 
 70  0
     private boolean binary = true;
 71  
 
 72  
     /**
 73  
      * Whether to test FTP connection on each take from pool.
 74  
      */
 75  0
     private boolean validateConnections = true;
 76  
 
 77  0
     private Map pools = new HashMap();
 78  
 
 79  0
     private String connectionFactoryClass = DEFAULT_FTP_CONNECTION_FACTORY_CLASS;
 80  
 
 81  
     public String getProtocol()
 82  
     {
 83  0
         return "ftp";
 84  
     }
 85  
 
 86  
     public UMOMessageReceiver createReceiver(UMOComponent component, UMOEndpoint endpoint) throws Exception
 87  
     {
 88  0
         long polling = pollingFrequency;
 89  0
         Map props = endpoint.getProperties();
 90  0
         if (props != null)
 91  
         {
 92  
             // Override properties on the endpoint for the specific endpoint
 93  0
             String tempPolling = (String) props.get(PROPERTY_POLLING_FREQUENCY);
 94  0
             if (tempPolling != null)
 95  
             {
 96  0
                 polling = Long.parseLong(tempPolling);
 97  
             }
 98  
         }
 99  0
         if (polling <= 0)
 100  
         {
 101  0
             polling = DEFAULT_POLLING_FREQUENCY;
 102  
         }
 103  0
         logger.debug("set polling frequency to " + polling);
 104  0
         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  0
         return pollingFrequency;
 114  
     }
 115  
 
 116  
     /**
 117  
      * @param pollingFrequency The pollingFrequency to set.
 118  
      */
 119  
     public void setPollingFrequency(long pollingFrequency)
 120  
     {
 121  0
         this.pollingFrequency = pollingFrequency;
 122  0
     }
 123  
 
 124  
     /**
 125  
      * Getter for property 'connectionFactoryClass'.
 126  
      *
 127  
      * @return Value for property 'connectionFactoryClass'.
 128  
      */
 129  
     public String getConnectionFactoryClass()
 130  
     {
 131  0
         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  0
         this.connectionFactoryClass = connectionFactoryClass;
 143  0
     }
 144  
 
 145  
     public FTPClient getFtp(UMOEndpointURI uri) throws Exception
 146  
     {
 147  0
         if (logger.isDebugEnabled())
 148  
         {
 149  0
             logger.debug(">>> retrieving client for " + uri);
 150  
         }
 151  0
         return (FTPClient) getFtpPool(uri).borrowObject();
 152  
     }
 153  
 
 154  
     public void releaseFtp(UMOEndpointURI uri, FTPClient client) throws Exception
 155  
     {
 156  0
         if (logger.isDebugEnabled())
 157  
         {
 158  0
             logger.debug("<<< releasing client for " + uri);
 159  
         }
 160  0
         if (dispatcherFactory.isCreateDispatcherPerRequest())
 161  
         {
 162  0
             destroyFtp(uri, client);
 163  
         }
 164  
         else
 165  
         {
 166  0
             getFtpPool(uri).returnObject(client);
 167  
         }
 168  0
     }
 169  
 
 170  
     public void destroyFtp(UMOEndpointURI uri, FTPClient client) throws Exception
 171  
     {
 172  0
         if (logger.isDebugEnabled())
 173  
         {
 174  0
             logger.debug("<<< destroying client for " + uri);
 175  
         }
 176  
         try
 177  
         {
 178  0
             getFtpPool(uri).invalidateObject(client);
 179  
         }
 180  0
         catch (Exception e)
 181  
         {
 182  
             // no way to test if pool is closed except try to access it
 183  0
             logger.debug(e.getMessage());
 184  0
         }
 185  0
     }
 186  
 
 187  
     protected synchronized ObjectPool getFtpPool(UMOEndpointURI uri)
 188  
     {
 189  0
         if (logger.isDebugEnabled())
 190  
         {
 191  0
             logger.debug("=== get pool for " + uri);
 192  
         }
 193  0
         String key = uri.getUsername() + ":" + uri.getPassword() + "@" + uri.getHost() + ":" + uri.getPort();
 194  0
         ObjectPool pool = (ObjectPool) pools.get(key);
 195  0
         if (pool == null)
 196  
         {
 197  
             try
 198  
             {
 199  0
                 FtpConnectionFactory connectionFactory =
 200  
                         (FtpConnectionFactory) ClassUtils.instanciateClass(getConnectionFactoryClass(),
 201  
                                                                             new Object[] {uri}, getClass());
 202  0
                 pool = new GenericObjectPool(connectionFactory);
 203  0
                 ((GenericObjectPool) pool).setTestOnBorrow(this.validateConnections);
 204  0
                 pools.put(key, pool);
 205  
             }
 206  0
             catch (Exception ex)
 207  
             {
 208  0
                 throw new MuleRuntimeException(
 209  
                         MessageFactory.createStaticMessage("Hmm, couldn't instanciate FTP connection factory."), ex);
 210  0
             }
 211  
         }
 212  0
         return pool;
 213  
     }
 214  
 
 215  
 
 216  
     protected void doInitialise() throws InitialisationException
 217  
     {
 218  
         try
 219  
         {
 220  0
             Class objectFactoryClass = ClassUtils.loadClass(this.connectionFactoryClass, getClass());
 221  0
             if (!FtpConnectionFactory.class.isAssignableFrom(objectFactoryClass))
 222  
             {
 223  0
                 throw new InitialisationException(MessageFactory.createStaticMessage(
 224  
                         "FTP connectionFactoryClass is not an instance of org.mule.providers.ftp.FtpConnectionFactory"),
 225  
                         this);
 226  
             }
 227  
         }
 228  0
         catch (ClassNotFoundException e)
 229  
         {
 230  0
             throw new InitialisationException(e, this);
 231  0
         }
 232  0
     }
 233  
 
 234  
     protected void doDispose()
 235  
     {
 236  
         // template method
 237  0
     }
 238  
 
 239  
     protected void doConnect() throws Exception
 240  
     {
 241  
         // template method
 242  0
     }
 243  
 
 244  
     protected void doDisconnect() throws Exception
 245  
     {
 246  
         // template method
 247  0
     }
 248  
 
 249  
     protected void doStart() throws UMOException
 250  
     {
 251  
         // template method
 252  0
     }
 253  
 
 254  
     protected void doStop() throws UMOException
 255  
     {
 256  0
         if (logger.isDebugEnabled())
 257  
         {
 258  0
             logger.debug("!!! stopping all pools");
 259  
         }
 260  
         try
 261  
         {
 262  0
             for (Iterator it = pools.values().iterator(); it.hasNext();)
 263  
             {
 264  0
                 ObjectPool pool = (ObjectPool)it.next();
 265  0
                 pool.close();
 266  
             }
 267  
         }
 268  0
         catch (Exception e)
 269  
         {
 270  0
             throw new ConnectorException(CoreMessages.failedToStop("FTP Connector"), this, e);
 271  0
         }
 272  0
     }
 273  
 
 274  
     /**
 275  
      * @return Returns the outputPattern.
 276  
      */
 277  
     public String getOutputPattern()
 278  
     {
 279  0
         return outputPattern;
 280  
     }
 281  
 
 282  
     /**
 283  
      * @param outputPattern The outputPattern to set.
 284  
      */
 285  
     public void setOutputPattern(String outputPattern)
 286  
     {
 287  0
         this.outputPattern = outputPattern;
 288  0
     }
 289  
 
 290  
     /**
 291  
      * @return Returns the filenameParser.
 292  
      */
 293  
     public FilenameParser getFilenameParser()
 294  
     {
 295  0
         return filenameParser;
 296  
     }
 297  
 
 298  
     /**
 299  
      * @param filenameParser The filenameParser to set.
 300  
      */
 301  
     public void setFilenameParser(FilenameParser filenameParser)
 302  
     {
 303  0
         this.filenameParser = filenameParser;
 304  0
     }
 305  
 
 306  
     /**
 307  
      * Getter for FTP passive mode.
 308  
      * 
 309  
      * @return true if using FTP passive mode
 310  
      */
 311  
     public boolean isPassive()
 312  
     {
 313  0
         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  0
         this.passive = passive;
 324  0
     }
 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  0
         final String passiveString = (String)endpoint.getProperty(FtpConnector.PROPERTY_PASSIVE_MODE);
 338  0
         if (passiveString == null)
 339  
         {
 340  
             // try the connector properties then
 341  0
             if (isPassive())
 342  
             {
 343  0
                 if (logger.isTraceEnabled())
 344  
                 {
 345  0
                     logger.trace("Entering FTP passive mode");
 346  
                 }
 347  0
                 client.enterLocalPassiveMode();
 348  
             }
 349  
             else
 350  
             {
 351  0
                 if (logger.isTraceEnabled())
 352  
                 {
 353  0
                     logger.trace("Entering FTP active mode");
 354  
                 }
 355  0
                 client.enterLocalActiveMode();
 356  
             }
 357  
         }
 358  
         else
 359  
         {
 360  
             // override with endpoint's definition
 361  0
             final boolean passiveMode = Boolean.valueOf(passiveString).booleanValue();
 362  0
             if (passiveMode)
 363  
             {
 364  0
                 if (logger.isTraceEnabled())
 365  
                 {
 366  0
                     logger.trace("Entering FTP passive mode (endpoint override)");
 367  
                 }
 368  0
                 client.enterLocalPassiveMode();
 369  
             }
 370  
             else
 371  
             {
 372  0
                 if (logger.isTraceEnabled())
 373  
                 {
 374  0
                     logger.trace("Entering FTP active mode (endpoint override)");
 375  
                 }
 376  0
                 client.enterLocalActiveMode();
 377  
             }
 378  
         }
 379  0
     }
 380  
 
 381  
     /**
 382  
      * Whether to test FTP connection on each take from pool.
 383  
      */
 384  
     public boolean isValidateConnections()
 385  
     {
 386  0
         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  0
         this.validateConnections = validateConnections;
 399  0
     }
 400  
 
 401  
     /**
 402  
      * Getter for FTP transfer type.
 403  
      * 
 404  
      * @return true if using FTP binary type
 405  
      */
 406  
     public boolean isBinary()
 407  
     {
 408  0
         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  0
         this.binary = binary;
 419  0
     }
 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  0
         final String binaryTransferString = (String)endpoint.getProperty(FtpConnector.PROPERTY_BINARY_TRANSFER);
 435  0
         if (binaryTransferString == null)
 436  
         {
 437  
             // try the connector properties then
 438  0
             if (isBinary())
 439  
             {
 440  0
                 if (logger.isTraceEnabled())
 441  
                 {
 442  0
                     logger.trace("Using FTP BINARY type");
 443  
                 }
 444  0
                 type = FTP.BINARY_FILE_TYPE;
 445  
             }
 446  
             else
 447  
             {
 448  0
                 if (logger.isTraceEnabled())
 449  
                 {
 450  0
                     logger.trace("Using FTP ASCII type");
 451  
                 }
 452  0
                 type = FTP.ASCII_FILE_TYPE;
 453  
             }
 454  
         }
 455  
         else
 456  
         {
 457  
             // override with endpoint's definition
 458  0
             final boolean binaryTransfer = Boolean.valueOf(binaryTransferString).booleanValue();
 459  0
             if (binaryTransfer)
 460  
             {
 461  0
                 if (logger.isTraceEnabled())
 462  
                 {
 463  0
                     logger.trace("Using FTP BINARY type (endpoint override)");
 464  
                 }
 465  0
                 type = FTP.BINARY_FILE_TYPE;
 466  
             }
 467  
             else
 468  
             {
 469  0
                 if (logger.isTraceEnabled())
 470  
                 {
 471  0
                     logger.trace("Using FTP ASCII type (endpoint override)");
 472  
                 }
 473  0
                 type = FTP.ASCII_FILE_TYPE;
 474  
             }
 475  
         }
 476  
 
 477  0
         client.setFileType(type);
 478  0
     }
 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  0
             final UMOEndpointURI uri = endpoint.getEndpointURI();
 496  0
             String filename = getFilename(endpoint, message);
 497  0
             final FTPClient client = getFtp(uri);
 498  
 
 499  
             try
 500  
             {
 501  0
                 enterActiveOrPassiveMode(client, endpoint);
 502  0
                 setupFileType(client, endpoint);
 503  0
                 if (!client.changeWorkingDirectory(uri.getPath()))
 504  
                 {
 505  0
                     throw new IOException("Ftp error: " + client.getReplyCode());
 506  
                 }
 507  0
                 OutputStream out = client.storeFileStream(filename);
 508  0
                 return new CallbackOutputStream(out,
 509  
                         new CallbackOutputStream.Callback()
 510  
                         {
 511  0
                             public void onClose() throws Exception
 512  
                             {
 513  
                                 try
 514  
                                 {
 515  0
                                     if (!client.completePendingCommand())
 516  
                                     {
 517  0
                                         client.logout();
 518  0
                                         client.disconnect();
 519  0
                                         throw new IOException("FTP Stream failed to complete pending request");
 520  
                                     }
 521  
                                 }
 522  
                                 finally
 523  
                                 {
 524  0
                                     releaseFtp(uri, client);
 525  0
                                 }
 526  0
                             }
 527  
                         });
 528  
             }
 529  0
             catch (Exception e)
 530  
             {
 531  0
                 logger.debug("Error getting output stream: ", e);
 532  0
                 releaseFtp(uri, client);
 533  0
                 throw e;
 534  
             }
 535  
         }
 536  0
         catch (Exception e)
 537  
         {
 538  0
             throw new DispatchException(CoreMessages.streamingFailedNoStream(), message, endpoint, e);
 539  
         }
 540  
     }
 541  
 
 542  
     private String getFilename(UMOImmutableEndpoint endpoint, UMOMessage message) throws IOException
 543  
     {
 544  0
         String filename = (String)message.getProperty(FtpConnector.PROPERTY_FILENAME);
 545  0
         if (filename == null)
 546  
         {
 547  0
             String outPattern = (String) endpoint.getProperty(FtpConnector.PROPERTY_OUTPUT_PATTERN);
 548  0
             if (outPattern == null){
 549  0
                 outPattern = message.getStringProperty(FtpConnector.PROPERTY_OUTPUT_PATTERN,
 550  
                         getOutputPattern());
 551  
             }
 552  0
             filename = generateFilename(message, outPattern);
 553  
         }
 554  0
         if (filename == null)
 555  
         {
 556  0
             throw new IOException("Filename is null");
 557  
         }
 558  0
         return filename;
 559  
     }
 560  
 
 561  
     private String generateFilename(UMOMessage message, String pattern)
 562  
     {
 563  0
         if (pattern == null)
 564  
         {
 565  0
             pattern = getOutputPattern();
 566  
         }
 567  0
         return getFilenameParser().getFilename(message, pattern);
 568  
     }
 569  
 
 570  
 }