Coverage Report - org.mule.providers.file.FileMessageReceiver
 
Classes in this File Line Coverage Branch Coverage Complexity
FileMessageReceiver
0%
0/142
0%
0/32
5.8
 
 1  
 /*
 2  
  * $Id: FileMessageReceiver.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.file;
 12  
 
 13  
 import org.mule.MuleException;
 14  
 import org.mule.impl.MuleMessage;
 15  
 import org.mule.providers.AbstractPollingMessageReceiver;
 16  
 import org.mule.providers.ConnectException;
 17  
 import org.mule.providers.file.i18n.FileMessages;
 18  
 import org.mule.umo.UMOComponent;
 19  
 import org.mule.umo.UMOException;
 20  
 import org.mule.umo.endpoint.UMOEndpoint;
 21  
 import org.mule.umo.lifecycle.InitialisationException;
 22  
 import org.mule.umo.provider.UMOConnector;
 23  
 import org.mule.umo.provider.UMOMessageAdapter;
 24  
 import org.mule.umo.routing.RoutingException;
 25  
 import org.mule.util.FileUtils;
 26  
 
 27  
 import java.io.File;
 28  
 import java.io.FileFilter;
 29  
 import java.io.FileInputStream;
 30  
 import java.io.FileNotFoundException;
 31  
 import java.io.FileOutputStream;
 32  
 import java.io.FilenameFilter;
 33  
 import java.io.IOException;
 34  
 import java.io.RandomAccessFile;
 35  
 import java.nio.channels.FileChannel;
 36  
 import java.nio.channels.FileLock;
 37  
 
 38  
 import org.apache.commons.io.IOUtils;
 39  
 
 40  
 /**
 41  
  * <code>FileMessageReceiver</code> is a polling listener that reads files from a
 42  
  * directory.
 43  
  */
 44  
 
 45  
 public class FileMessageReceiver extends AbstractPollingMessageReceiver
 46  
 {
 47  0
     private String readDir = null;
 48  0
     private String moveDir = null;
 49  0
     private File readDirectory = null;
 50  0
     private File moveDirectory = null;
 51  0
     private String moveToPattern = null;
 52  0
     private FilenameFilter filenameFilter = null;
 53  0
     private FileFilter fileFilter = null;
 54  
 
 55  
     public FileMessageReceiver(UMOConnector connector,
 56  
                                UMOComponent component,
 57  
                                UMOEndpoint endpoint,
 58  
                                String readDir,
 59  
                                String moveDir,
 60  
                                String moveToPattern,
 61  
                                long frequency) throws InitialisationException
 62  
     {
 63  0
         super(connector, component, endpoint);
 64  0
         this.setFrequency(frequency);
 65  
 
 66  0
         this.readDir = readDir;
 67  0
         this.moveDir = moveDir;
 68  0
         this.moveToPattern = moveToPattern;
 69  
 
 70  0
         if (endpoint.getFilter() instanceof FilenameFilter)
 71  
         {
 72  0
             filenameFilter = (FilenameFilter)endpoint.getFilter();
 73  
         }
 74  0
         else if (endpoint.getFilter() instanceof FileFilter)
 75  
         {
 76  0
             fileFilter = (FileFilter)endpoint.getFilter();
 77  
         }
 78  0
         else if(endpoint.getFilter()!=null)
 79  
         {
 80  0
             throw new InitialisationException(
 81  
                 FileMessages.invalidFileFilter(endpoint.getEndpointURI()), this);
 82  
         }
 83  0
     }
 84  
 
 85  
     protected void doConnect() throws Exception
 86  
     {
 87  0
         if (readDir != null)
 88  
         {
 89  0
             readDirectory = FileUtils.openDirectory(readDir);
 90  0
             if (!(readDirectory.canRead()))
 91  
             {
 92  0
                 throw new ConnectException(
 93  
                     FileMessages.fileDoesNotExist(readDirectory.getAbsolutePath()), this);
 94  
             }
 95  
             else
 96  
             {
 97  0
                 logger.debug("Listening on endpointUri: " + readDirectory.getAbsolutePath());
 98  
             }
 99  
         }
 100  
 
 101  0
         if (moveDir != null)
 102  
         {
 103  0
             moveDirectory = FileUtils.openDirectory((moveDir));
 104  0
             if (!(moveDirectory.canRead()) || !moveDirectory.canWrite())
 105  
             {
 106  0
                 throw new ConnectException(
 107  
                     FileMessages.moveToDirectoryNotWritable(), this);
 108  
             }
 109  
         }
 110  0
     }
 111  
 
 112  
     protected void doDisconnect() throws Exception
 113  
     {
 114  
         // template method
 115  0
     }
 116  
 
 117  
     protected void doDispose()
 118  
     {
 119  
         // nothing to do               
 120  0
     }
 121  
 
 122  
     public void poll()
 123  
     {
 124  
         try
 125  
         {
 126  0
             File[] files = this.listFiles();
 127  0
             for (int i = 0; i < files.length; i++)
 128  
             {
 129  0
                 this.processFile(files[i]);
 130  
             }
 131  
         }
 132  0
         catch (Exception e)
 133  
         {
 134  0
             this.handleException(e);
 135  0
         }
 136  0
     }
 137  
 
 138  
     public synchronized void processFile(final File sourceFile) throws UMOException
 139  
     {
 140  
         //TODO RM*: This can be put in a Filter. Also we can add an AndFileFilter/OrFileFilter to allow users to
 141  
         //combine file filters (since we can only pass a single filter to File.listFiles, we would need to wrap
 142  
         //the current And/Or filters to extend {@link FilenameFilter}
 143  0
         boolean checkFileAge = ((FileConnector) connector).getCheckFileAge();
 144  0
         if (checkFileAge)
 145  
         {
 146  0
             long fileAge = ((FileConnector) connector).getFileAge();
 147  0
             long lastMod = sourceFile.lastModified();
 148  0
             long now = System.currentTimeMillis();
 149  0
             long thisFileAge = now - lastMod;
 150  0
             if (thisFileAge < fileAge)
 151  
             {
 152  0
                 if (logger.isDebugEnabled()) {
 153  0
                     logger.debug("The file has not aged enough yet, will return nothing for: " + sourceFile);
 154  
                 }
 155  0
                 return;
 156  
             }
 157  
         }
 158  
 
 159  
         // don't process a file that is locked by another process (probably still being written)
 160  0
         if (!attemptFileLock(sourceFile))
 161  
         {
 162  0
             return;
 163  
         }
 164  
 
 165  0
         File destinationFile = null;
 166  0
         String sourceFileOriginalName = sourceFile.getName();
 167  
         //Create a message adapter here to pass to the fileName parser
 168  
         UMOMessageAdapter msgAdapter;
 169  0
         if(endpoint.isStreaming())
 170  
         {
 171  
             try
 172  
             {
 173  0
                 msgAdapter = connector.getStreamMessageAdapter(new FileInputStream(sourceFile), null);
 174  
             }
 175  0
             catch (FileNotFoundException e)
 176  
             {
 177  
                 //we can ignore since we did manage to acquire a lock, but just in case
 178  0
                 logger.error("File being read disappeared!", e);
 179  0
                 return;
 180  0
             }
 181  
         }
 182  
         else
 183  
         {
 184  0
             msgAdapter = connector.getMessageAdapter(sourceFile);
 185  
         }
 186  0
         msgAdapter.setProperty(FileConnector.PROPERTY_ORIGINAL_FILENAME, sourceFileOriginalName);
 187  
 
 188  
         // set up destination file
 189  0
         if (moveDir != null)
 190  
         {
 191  0
             String destinationFileName = sourceFileOriginalName;
 192  
 
 193  0
             if (moveToPattern != null)
 194  
             {
 195  0
                 destinationFileName = ((FileConnector) connector).getFilenameParser().getFilename(msgAdapter,
 196  
                     moveToPattern);
 197  
             }
 198  
 
 199  
             // don't use new File() directly, see MULE-1112
 200  0
             destinationFile = FileUtils.newFile(moveDir, destinationFileName);
 201  
         }
 202  
 
 203  0
         boolean fileWasMoved = false;
 204  
 
 205  
         try
 206  
         {
 207  
             // Perform some quick checks to make sure file can be processed
 208  0
             if (!(sourceFile.canRead() && sourceFile.exists() && sourceFile.isFile()))
 209  
             {
 210  0
                 throw new MuleException(FileMessages.fileDoesNotExist(sourceFileOriginalName));
 211  
             }
 212  
 
 213  
             //If we are moving the file to a read directory, move it there now and hand over a reference to the
 214  
             //File in its moved location
 215  0
             if (destinationFile != null)
 216  
             {
 217  
                 // move sourceFile to new destination
 218  0
                 fileWasMoved = this.moveFile(sourceFile, destinationFile);
 219  
 
 220  
                 // move didn't work - bail out (will attempt rollback)
 221  0
                 if (!fileWasMoved)
 222  
                 {
 223  0
                     throw new MuleException(
 224  
                         FileMessages.failedToMoveFile(
 225  
                             sourceFile.getAbsolutePath(), destinationFile.getAbsolutePath()));
 226  
                 }
 227  
 
 228  
                 // create new MessageAdapter for destinationFile
 229  0
                 if(endpoint.isStreaming())
 230  
                 {
 231  0
                     msgAdapter = connector.getStreamMessageAdapter(new FileInputStream(destinationFile), null);
 232  
                 }
 233  
                 else
 234  
                 {
 235  0
                     msgAdapter = connector.getMessageAdapter(destinationFile);
 236  
                 }
 237  0
                 msgAdapter.setProperty(FileConnector.PROPERTY_FILENAME, destinationFile.getName());
 238  0
                 msgAdapter.setProperty(FileConnector.PROPERTY_ORIGINAL_FILENAME, sourceFileOriginalName);
 239  
             }
 240  
 
 241  
             // finally deliver the file message
 242  0
             this.routeMessage(new MuleMessage(msgAdapter), endpoint.isSynchronous());
 243  
             
 244  
             // at this point msgAdapter either points to the old sourceFile
 245  
             // or the new destinationFile.
 246  0
             if (((FileConnector) connector).isAutoDelete())
 247  
             {
 248  
                 // no moveTo directory
 249  0
                 if (destinationFile == null)
 250  
                 {
 251  
                     // delete source
 252  0
                     if (!sourceFile.delete())
 253  
                     {
 254  0
                         throw new MuleException(
 255  
                             FileMessages.failedToDeleteFile(sourceFile.getAbsolutePath()));
 256  
                     }
 257  
                 }
 258  
                 else
 259  
                 {
 260  
                     // nothing to do here since moveFile() should have deleted
 261  
                     // the source file for us
 262  
                 }
 263  
             }
 264  
         }
 265  0
         catch (Exception e)
 266  
         {
 267  0
             boolean fileWasRolledBack = false;
 268  
 
 269  
             // only attempt rollback if file move was successful
 270  0
             if (fileWasMoved)
 271  
             {
 272  0
                 fileWasRolledBack = this.rollbackFileMove(destinationFile, sourceFile.getAbsolutePath());
 273  
             }
 274  
 
 275  
             // wrap exception & handle it
 276  0
             Exception ex = new RoutingException(
 277  
                 FileMessages.exceptionWhileProcessing(sourceFile.getName(),
 278  
                 (fileWasRolledBack ? "successful" : "unsuccessful")), new MuleMessage(msgAdapter), endpoint,
 279  
                 e);
 280  0
             this.handleException(ex);
 281  0
         }
 282  0
     }
 283  
 
 284  
     /**
 285  
      * Try to acquire a lock on a file and release it immediately. Usually used as a quick check to
 286  
      * see if another process is still holding onto the file, e.g. a large file (more than 100MB) is
 287  
      * still being written to.
 288  
      * @param sourceFile file to check
 289  
      * @return <code>true</code> if the file can be locked
 290  
      */
 291  
     protected boolean attemptFileLock(final File sourceFile)
 292  
     {
 293  
         // check if the file can be processed, be sure that it's not still being written
 294  
         // if the file can't be locked don't process it yet, since creating
 295  
         // a new FileInputStream() will throw an exception
 296  0
         FileLock lock = null;
 297  0
         FileChannel channel = null;
 298  0
         boolean fileCanBeLocked = false;
 299  
         try
 300  
         {
 301  0
             channel = new RandomAccessFile(sourceFile, "rw").getChannel();
 302  
 
 303  
             // Try acquiring the lock without blocking. This method returns
 304  
             // null or throws an exception if the file is already locked.
 305  0
             lock = channel.tryLock();
 306  0
         }
 307  0
         catch (FileNotFoundException fnfe)
 308  
         {
 309  0
            logger.warn("Unable to open " + sourceFile.getAbsolutePath(), fnfe);
 310  0
         }
 311  0
         catch (IOException e)
 312  
         {
 313  
             // Unable to create a lock. This exception should only be thrown when
 314  
             // the file is already locked. No sense in repeating the message over
 315  
             // and over.
 316  0
         }
 317  
         finally {
 318  0
             if (lock != null)
 319  
             {
 320  
                 // if lock is null the file is locked by another process
 321  0
                 fileCanBeLocked = true;
 322  
                 try
 323  
                 {
 324  
                     // Release the lock
 325  0
                     lock.release();
 326  
                 }
 327  0
                 catch (IOException e)
 328  
                 {
 329  
                     // ignore
 330  0
                 }
 331  
             }
 332  
 
 333  0
             if (channel != null)
 334  
             {
 335  
                 try
 336  
                 {
 337  
                     // Close the file
 338  0
                     channel.close();
 339  
                 }
 340  0
                 catch (IOException e)
 341  
                 {
 342  
                     // ignore
 343  0
                 }
 344  
             }
 345  0
         }
 346  
 
 347  0
         return fileCanBeLocked;
 348  
     }
 349  
 
 350  
     /**
 351  
      * Try to move a file by renaming with backup attempt by copying/deleting via NIO
 352  
      */
 353  
     protected boolean moveFile(File sourceFile, File destinationFile)
 354  
     {
 355  
         // try fast file-system-level move/rename first
 356  0
         boolean success = sourceFile.renameTo(destinationFile);
 357  
 
 358  0
         if (!success)
 359  
         {
 360  
             // try again using NIO copy
 361  0
             FileInputStream fis = null;
 362  0
             FileOutputStream fos = null;
 363  
             try
 364  
             {
 365  0
                 fis = new FileInputStream(sourceFile);
 366  0
                 fos = new FileOutputStream(destinationFile);
 367  0
                 FileChannel srcChannel = fis.getChannel();
 368  0
                 FileChannel dstChannel = fos.getChannel();
 369  0
                 dstChannel.transferFrom(srcChannel, 0, srcChannel.size());
 370  0
                 srcChannel.close();
 371  0
                 dstChannel.close();
 372  0
                 success = sourceFile.delete();
 373  
             }
 374  0
             catch (IOException ioex)
 375  
             {
 376  
                 // grr!
 377  0
                 success = false;
 378  
             }
 379  
             finally
 380  
             {
 381  0
                 IOUtils.closeQuietly(fis);
 382  0
                 IOUtils.closeQuietly(fos);
 383  0
             }
 384  
         }
 385  
 
 386  0
         return success;
 387  
     }
 388  
 
 389  
     /**
 390  
      * Exception tolerant roll back method
 391  
      */
 392  
     protected boolean rollbackFileMove(File sourceFile, String destinationFilePath)
 393  
     {
 394  0
         boolean result = false;
 395  
         try
 396  
         {
 397  0
             result = this.moveFile(sourceFile, FileUtils.newFile(destinationFilePath));
 398  
         }
 399  0
         catch (Throwable t)
 400  
         {
 401  0
             logger.debug("rollback of file move failed: " + t.getMessage());
 402  0
         }
 403  0
         return result;
 404  
     }
 405  
 
 406  
     /**
 407  
      * Get a list of files to be processed.
 408  
      * 
 409  
      * @return an array of files to be processed.
 410  
      * @throws org.mule.MuleException which will wrap any other exceptions or errors.
 411  
      */
 412  
     File[] listFiles() throws MuleException
 413  
     {
 414  
         try
 415  
         {
 416  
             File[] todoFiles;
 417  0
             if(fileFilter!=null)
 418  
             {
 419  0
                 todoFiles = readDirectory.listFiles(fileFilter);
 420  
             }
 421  
             else
 422  
             {
 423  0
                 todoFiles = readDirectory.listFiles(filenameFilter);
 424  
             }
 425  
             // logger.trace("Reading directory " + readDirectory.getAbsolutePath() +
 426  
             // " -> " + todoFiles.length + " file(s)");
 427  0
             return (todoFiles == null ? new File[0] : todoFiles);
 428  
         }
 429  0
         catch (Exception e)
 430  
         {
 431  0
             throw new MuleException(FileMessages.errorWhileListingFiles(), e);
 432  
         }
 433  
     }
 434  
 
 435  
 }