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