Coverage Report - org.mule.transport.file.FileMessageReceiver
 
Classes in this File Line Coverage Branch Coverage Complexity
FileMessageReceiver
0%
0/187
0%
0/100
0
 
 1  
 /*
 2  
  * $Id: FileMessageReceiver.java 20109 2010-11-07 05:20:30Z mike.schilling $
 3  
  * --------------------------------------------------------------------------------------
 4  
  * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.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.transport.file;
 12  
 
 13  
 import org.mule.DefaultMuleMessage;
 14  
 import org.mule.api.DefaultMuleException;
 15  
 import org.mule.api.MessagingException;
 16  
 import org.mule.api.MuleException;
 17  
 import org.mule.api.MuleMessage;
 18  
 import org.mule.api.construct.FlowConstruct;
 19  
 import org.mule.api.endpoint.InboundEndpoint;
 20  
 import org.mule.api.lifecycle.CreateException;
 21  
 import org.mule.api.transport.Connector;
 22  
 import org.mule.config.i18n.Message;
 23  
 import org.mule.transport.AbstractPollingMessageReceiver;
 24  
 import org.mule.transport.ConnectException;
 25  
 import org.mule.transport.file.i18n.FileMessages;
 26  
 import org.mule.util.FileUtils;
 27  
 
 28  
 import java.io.File;
 29  
 import java.io.FileFilter;
 30  
 import java.io.FileNotFoundException;
 31  
 import java.io.FilenameFilter;
 32  
 import java.io.IOException;
 33  
 import java.io.RandomAccessFile;
 34  
 import java.nio.channels.FileChannel;
 35  
 import java.nio.channels.FileLock;
 36  
 import java.util.ArrayList;
 37  
 import java.util.Collections;
 38  
 import java.util.Comparator;
 39  
 import java.util.List;
 40  
 
 41  
 import org.apache.commons.collections.comparators.ReverseComparator;
 42  
 
 43  
 /**
 44  
  * <code>FileMessageReceiver</code> is a polling listener that reads files from a
 45  
  * directory.
 46  
  */
 47  
 
 48  
 public class FileMessageReceiver extends AbstractPollingMessageReceiver
 49  
 {
 50  
     public static final String COMPARATOR_CLASS_NAME_PROPERTY = "comparator";
 51  
     public static final String COMPARATOR_REVERSE_ORDER_PROPERTY = "reverseOrder";
 52  
 
 53  0
     private static final List<File> NO_FILES = new ArrayList<File>();
 54  
 
 55  0
     private String readDir = null;
 56  0
     private String moveDir = null;
 57  0
     private String workDir = null;
 58  0
     private File readDirectory = null;
 59  0
     private File moveDirectory = null;
 60  0
     private String moveToPattern = null;
 61  0
     private String workFileNamePattern = null;
 62  0
     private FilenameFilter filenameFilter = null;
 63  0
     private FileFilter fileFilter = null;
 64  
 
 65  
     public FileMessageReceiver(Connector connector,
 66  
                                FlowConstruct flowConstruct,
 67  
                                InboundEndpoint endpoint,
 68  
                                String readDir,
 69  
                                String moveDir,
 70  
                                String moveToPattern,
 71  
                                long frequency) throws CreateException
 72  
     {
 73  0
         super(connector, flowConstruct, endpoint);
 74  0
         this.setFrequency(frequency);
 75  
 
 76  0
         this.readDir = readDir;
 77  0
         this.moveDir = moveDir;
 78  0
         this.moveToPattern = moveToPattern;
 79  0
         this.workDir = ((FileConnector) connector).getWorkDirectory();
 80  0
         this.workFileNamePattern = ((FileConnector) connector).getWorkFileNamePattern();
 81  
 
 82  0
         if (endpoint.getFilter() instanceof FilenameFilter)
 83  
         {
 84  0
             filenameFilter = (FilenameFilter) endpoint.getFilter();
 85  
         }
 86  0
         else if (endpoint.getFilter() instanceof FileFilter)
 87  
         {
 88  0
             fileFilter = (FileFilter) endpoint.getFilter();
 89  
         }
 90  0
         else if (endpoint.getFilter() != null)
 91  
         {
 92  0
             throw new CreateException(FileMessages.invalidFileFilter(endpoint.getEndpointURI()), this);
 93  
         }
 94  
         
 95  0
         logWarningWhenConnectorAutoDeletesAndEndpointIsOneWay();
 96  0
     }
 97  
 
 98  
     private void logWarningWhenConnectorAutoDeletesAndEndpointIsOneWay() throws CreateException
 99  
     {
 100  0
         boolean connectorIsAutoDelete = false;
 101  0
         if (connector instanceof FileConnector)
 102  
         {
 103  0
             connectorIsAutoDelete = ((FileConnector) connector).isAutoDelete();
 104  
         }
 105  
 
 106  0
         boolean messageFactoryConsumes = (createMuleMessageFactory() instanceof FileContentsMuleMessageFactory);
 107  
         
 108  0
         if (connectorIsAutoDelete && !messageFactoryConsumes)
 109  
         {
 110  0
             logger.warn(FileMessages.connectorAutodeletesWithoutConsuming(connector).getMessage());
 111  
         }
 112  0
     }
 113  
 
 114  
     @Override
 115  
     protected void doConnect() throws Exception
 116  
     {
 117  0
         if (readDir != null)
 118  
         {
 119  0
             readDirectory = FileUtils.openDirectory(readDir);
 120  0
             if (!(readDirectory.canRead()))
 121  
             {
 122  0
                 throw new ConnectException(FileMessages.fileDoesNotExist(readDirectory.getAbsolutePath()), this);
 123  
             }
 124  
             else
 125  
             {
 126  0
                 logger.debug("Listening on endpointUri: " + readDirectory.getAbsolutePath());
 127  
             }
 128  
         }
 129  
 
 130  0
         if (moveDir != null)
 131  
         {
 132  0
             moveDirectory = FileUtils.openDirectory((moveDir));
 133  0
             if (!(moveDirectory.canRead()) || !moveDirectory.canWrite())
 134  
             {
 135  0
                 throw new ConnectException(FileMessages.moveToDirectoryNotWritable(), this);
 136  
             }
 137  
         }
 138  0
     }
 139  
 
 140  
     @Override
 141  
     protected void doDisconnect() throws Exception
 142  
     {
 143  
         // template method
 144  0
     }
 145  
 
 146  
     @Override
 147  
     protected void doDispose()
 148  
     {
 149  
         // nothing to do
 150  0
     }
 151  
 
 152  
     @Override
 153  
     public void poll()
 154  
     {
 155  
         try
 156  
         {
 157  0
             List<File> files = this.listFiles();
 158  0
             if (logger.isDebugEnabled())
 159  
             {
 160  0
                 logger.debug("Files: " + files.toString());
 161  
             }
 162  0
             Comparator<File> comparator = getComparator();
 163  0
             if (comparator != null)
 164  
             {
 165  0
                 Collections.sort(files, comparator);
 166  
             }
 167  0
             for (File file : files)
 168  
             {
 169  
                 // don't process directories
 170  0
                 if (file.isFile())
 171  
                 {
 172  0
                     processFile(file);
 173  
                 }
 174  
             }
 175  
         }
 176  0
         catch (Exception e)
 177  
         {
 178  0
             getConnector().getMuleContext().getExceptionListener().handleException(e);
 179  0
         }
 180  0
     }
 181  
 
 182  
     public synchronized void processFile(File sourceFile) throws MuleException
 183  
     {
 184  0
         FileConnector fileConnector = (FileConnector) connector;
 185  
         
 186  
         //TODO RM*: This can be put in a Filter. Also we can add an AndFileFilter/OrFileFilter to allow users to
 187  
         //combine file filters (since we can only pass a single filter to File.listFiles, we would need to wrap
 188  
         //the current And/Or filters to extend {@link FilenameFilter}
 189  0
         boolean checkFileAge = fileConnector.getCheckFileAge();
 190  0
         if (checkFileAge)
 191  
         {
 192  0
             long fileAge = ((FileConnector) connector).getFileAge();
 193  0
             long lastMod = sourceFile.lastModified();
 194  0
             long now = System.currentTimeMillis();
 195  0
             long thisFileAge = now - lastMod;
 196  0
             if (thisFileAge < fileAge)
 197  
             {
 198  0
                 if (logger.isDebugEnabled())
 199  
                 {
 200  0
                     logger.debug("The file has not aged enough yet, will return nothing for: " + sourceFile);
 201  
                 }
 202  0
                 return;
 203  
             }
 204  
         }
 205  
 
 206  
         // don't process a file that is locked by another process (probably still being written)
 207  0
         if (!attemptFileLock(sourceFile))
 208  
         {
 209  0
             return;
 210  
         }
 211  0
         else if(logger.isInfoEnabled())
 212  
         {
 213  0
             logger.info("Lock obtained on file: " + sourceFile.getAbsolutePath());
 214  
         }
 215  
 
 216  0
         String sourceFileOriginalName = sourceFile.getName();
 217  
 
 218  
         // Perform some quick checks to make sure file can be processed
 219  0
         if (!(sourceFile.canRead() && sourceFile.exists() && sourceFile.isFile()))
 220  
         {
 221  0
             throw new DefaultMuleException(FileMessages.fileDoesNotExist(sourceFileOriginalName));
 222  
         }
 223  
 
 224  
         // This isn't nice but is needed as MuleMessage is required to resolve
 225  
         // destination file name
 226  0
         DefaultMuleMessage fileParserMessasge = new DefaultMuleMessage(null, connector.getMuleContext());
 227  0
         fileParserMessasge.setOutboundProperty(FileConnector.PROPERTY_ORIGINAL_FILENAME, sourceFileOriginalName);
 228  
         
 229  0
         File workFile = null;
 230  0
         if (workDir != null && (moveDir == null || (moveDir != null && fileConnector.isStreaming())))
 231  
         {
 232  0
             String workFileName = sourceFileOriginalName;
 233  
             
 234  0
             workFileName = fileConnector.getFilenameParser().getFilename(fileParserMessasge,
 235  
                     workFileNamePattern);
 236  
             // don't use new File() directly, see MULE-1112
 237  0
             workFile = FileUtils.newFile(workDir, workFileName);
 238  
             
 239  0
             move(sourceFile, workFile);
 240  
             // Now the Work File is the Source file
 241  0
             sourceFile = workFile;
 242  
         }
 243  
         
 244  
         // set up destination file
 245  0
         File destinationFile = null;
 246  0
         if (moveDir != null)
 247  
         {
 248  0
             String destinationFileName = sourceFileOriginalName;
 249  0
             if (moveToPattern != null)
 250  
             {
 251  0
                 destinationFileName = ((FileConnector) connector).getFilenameParser().getFilename(fileParserMessasge,
 252  
                     moveToPattern);
 253  
             }
 254  
             // don't use new File() directly, see MULE-1112
 255  0
             destinationFile = FileUtils.newFile(moveDir, destinationFileName);
 256  
         }
 257  
 
 258  0
         MuleMessage message = null;
 259  0
         String encoding = endpoint.getEncoding();
 260  
         try
 261  
         {
 262  0
             if (fileConnector.isStreaming())
 263  
             {
 264  0
                 ReceiverFileInputStream payload = new ReceiverFileInputStream(sourceFile,
 265  
                     fileConnector.isAutoDelete(), destinationFile);
 266  0
                 message = createMuleMessage(payload, encoding);
 267  0
             }
 268  
             else
 269  
             {
 270  0
                 message = createMuleMessage(sourceFile, encoding);
 271  
             }
 272  
         }
 273  0
         catch (FileNotFoundException e)
 274  
         {
 275  
             // we can ignore since we did manage to acquire a lock, but just in case
 276  0
             logger.error("File being read disappeared!", e);
 277  0
             return;
 278  0
         }
 279  
 
 280  0
         message.setOutboundProperty(FileConnector.PROPERTY_ORIGINAL_FILENAME, sourceFileOriginalName);
 281  
 
 282  0
         if (!fileConnector.isStreaming())
 283  
         {
 284  0
             moveAndDelete(sourceFile, destinationFile, sourceFileOriginalName, message);
 285  
         }
 286  
         else
 287  
         {
 288  
             // If we are streaming no need to move/delete now, that will be done when
 289  
             // stream is closed
 290  0
             message.setOutboundProperty(FileConnector.PROPERTY_FILENAME, sourceFile.getName());
 291  0
             this.routeMessage(message);
 292  
         }
 293  0
     }
 294  
 
 295  
     private void moveAndDelete(final File sourceFile, File destinationFile,
 296  
         String sourceFileOriginalName, MuleMessage message)
 297  
     {
 298  0
         boolean fileWasMoved = false;
 299  
 
 300  
         try
 301  
         {
 302  
             // If we are moving the file to a read directory, move it there now and
 303  
             // hand over a reference to the
 304  
             // File in its moved location
 305  0
             if (destinationFile != null)
 306  
             {
 307  
                 // move sourceFile to new destination
 308  
                 try
 309  
                 {
 310  0
                     FileUtils.moveFile(sourceFile, destinationFile);
 311  
                 }
 312  0
                 catch (IOException e)
 313  
                 {
 314  
                     // move didn't work - bail out (will attempt rollback)
 315  0
                     throw new DefaultMuleException(FileMessages.failedToMoveFile(
 316  
                         sourceFile.getAbsolutePath(), destinationFile.getAbsolutePath()));
 317  0
                 }
 318  
 
 319  
                 // create new Message for destinationFile
 320  0
                 message = createMuleMessage(destinationFile, endpoint.getEncoding());
 321  0
                 message.setOutboundProperty(FileConnector.PROPERTY_FILENAME, destinationFile.getName());
 322  0
                 message.setOutboundProperty(FileConnector.PROPERTY_ORIGINAL_FILENAME, sourceFileOriginalName);
 323  
             }
 324  
 
 325  
             // finally deliver the file message
 326  0
             this.routeMessage(message);
 327  
 
 328  
             // at this point msgAdapter either points to the old sourceFile
 329  
             // or the new destinationFile.
 330  0
             if (((FileConnector) connector).isAutoDelete())
 331  
             {
 332  
                 // no moveTo directory
 333  0
                 if (destinationFile == null)
 334  
                 {
 335  
                     // delete source
 336  0
                     if (!sourceFile.delete())
 337  
                     {
 338  0
                         throw new DefaultMuleException(FileMessages.failedToDeleteFile(sourceFile));
 339  
                     }
 340  
                 }
 341  
                 else
 342  
                 {
 343  
                     // nothing to do here since moveFile() should have deleted
 344  
                     // the source file for us
 345  
                 }
 346  
             }
 347  
         }
 348  0
         catch (Exception e)
 349  
         {
 350  0
             boolean fileWasRolledBack = false;
 351  
 
 352  
             // only attempt rollback if file move was successful
 353  0
             if (fileWasMoved)
 354  
             {
 355  
                 try
 356  
                 {
 357  0
                     rollbackFileMove(destinationFile, sourceFile.getAbsolutePath());
 358  0
                     fileWasRolledBack = true;
 359  
                 }
 360  0
                 catch (IOException ioException)
 361  
                 {
 362  
                     // eat it
 363  0
                 }
 364  
             }
 365  
 
 366  
             // wrap exception & handle it
 367  0
             Message msg = FileMessages.exceptionWhileProcessing(sourceFile.getName(),
 368  
                 (fileWasRolledBack ? "successful" : "unsuccessful"));
 369  0
             getConnector().getMuleContext().getExceptionListener().handleException(new MessagingException(msg, message, e));
 370  0
         }
 371  0
     }
 372  
 
 373  
     /**
 374  
      * Try to acquire a lock on a file and release it immediately. Usually used as a
 375  
      * quick check to see if another process is still holding onto the file, e.g. a
 376  
      * large file (more than 100MB) is still being written to.
 377  
      * 
 378  
      * @param sourceFile file to check
 379  
      * @return <code>true</code> if the file can be locked
 380  
      */
 381  
     protected boolean attemptFileLock(final File sourceFile)
 382  
     {
 383  
         // check if the file can be processed, be sure that it's not still being
 384  
         // written
 385  
         // if the file can't be locked don't process it yet, since creating
 386  
         // a new FileInputStream() will throw an exception
 387  0
         FileLock lock = null;
 388  0
         FileChannel channel = null;
 389  0
         boolean fileCanBeLocked = false;
 390  
         try
 391  
         {
 392  0
             channel = new RandomAccessFile(sourceFile, "rw").getChannel();
 393  
 
 394  
             // Try acquiring the lock without blocking. This method returns
 395  
             // null or throws an exception if the file is already locked.
 396  0
             lock = channel.tryLock();
 397  
         }
 398  0
         catch (FileNotFoundException fnfe)
 399  
         {
 400  0
             logger.warn("Unable to open " + sourceFile.getAbsolutePath(), fnfe);
 401  
         }
 402  0
         catch (IOException e)
 403  
         {
 404  
             // Unable to create a lock. This exception should only be thrown when
 405  
             // the file is already locked. No sense in repeating the message over
 406  
             // and over.
 407  
         }
 408  
         finally
 409  
         {
 410  0
             if (lock != null)
 411  
             {
 412  
                 // if lock is null the file is locked by another process
 413  0
                 fileCanBeLocked = true;
 414  
                 try
 415  
                 {
 416  
                     // Release the lock
 417  0
                     lock.release();
 418  
                 }
 419  0
                 catch (IOException e)
 420  
                 {
 421  
                     // ignore
 422  0
                 }
 423  
             }
 424  
 
 425  0
             if (channel != null)
 426  
             {
 427  
                 try
 428  
                 {
 429  
                     // Close the file
 430  0
                     channel.close();
 431  
                 }
 432  0
                 catch (IOException e)
 433  
                 {
 434  
                     // ignore
 435  0
                 }
 436  
             }
 437  
         }
 438  
 
 439  0
         return fileCanBeLocked;
 440  
     }
 441  
 
 442  
     /**
 443  
      * Get a list of files to be processed.
 444  
      * 
 445  
      * @return an array of files to be processed.
 446  
      * @throws org.mule.api.MuleException which will wrap any other exceptions or
 447  
      *             errors.
 448  
      */
 449  
     List<File> listFiles() throws MuleException
 450  
     {
 451  
         try
 452  
         {
 453  0
             List<File> files = new ArrayList<File>();
 454  0
             this.basicListFiles(readDirectory, files);
 455  0
             return (files.isEmpty() ? NO_FILES : files);
 456  
         }
 457  0
         catch (Exception e)
 458  
         {
 459  0
             throw new DefaultMuleException(FileMessages.errorWhileListingFiles(), e);
 460  
         }
 461  
     }
 462  
 
 463  
     protected void basicListFiles(File currentDirectory, List<File> discoveredFiles)
 464  
     {
 465  
         File[] files;
 466  0
         if (fileFilter != null)
 467  
         {
 468  0
             files = currentDirectory.listFiles(fileFilter);
 469  
         }
 470  
         else
 471  
         {
 472  0
             files = currentDirectory.listFiles(filenameFilter);
 473  
         }
 474  
         
 475  
         // the listFiles calls above may actually return null (check the JDK code).
 476  0
         if (files == null)
 477  
         {
 478  0
             return;
 479  
         }
 480  
 
 481  0
         for (File file : files)
 482  
         {
 483  0
             if (!file.isDirectory())
 484  
             {
 485  0
                 discoveredFiles.add(file);
 486  
             }
 487  
             else
 488  
             {
 489  0
                 if (((FileConnector) this.getConnector()).isRecursive())
 490  
                 {
 491  0
                     this.basicListFiles(file, discoveredFiles);
 492  
                 }
 493  
             }
 494  
         }
 495  0
     }
 496  
 
 497  
     /**
 498  
      * Exception tolerant roll back method
 499  
      * 
 500  
      * @throws Throwable
 501  
      */
 502  
     protected void rollbackFileMove(File sourceFile, String destinationFilePath) throws IOException
 503  
     {
 504  
         try
 505  
         {
 506  0
             FileUtils.moveFile(sourceFile, FileUtils.newFile(destinationFilePath));
 507  
         }
 508  0
         catch (IOException t)
 509  
         {
 510  0
             logger.debug("rollback of file move failed: " + t.getMessage());
 511  0
             throw t;
 512  0
         }
 513  0
     }
 514  
 
 515  
     protected Comparator<File> getComparator() throws Exception
 516  
     {
 517  0
         Object comparatorClassName = getEndpoint().getProperty(COMPARATOR_CLASS_NAME_PROPERTY);
 518  0
         if (comparatorClassName != null)
 519  
         {
 520  0
             Object reverseProperty = this.getEndpoint().getProperty(COMPARATOR_REVERSE_ORDER_PROPERTY);
 521  0
             boolean reverse = false;
 522  0
             if (reverseProperty != null)
 523  
             {
 524  0
                 reverse = Boolean.valueOf((String) reverseProperty);
 525  
             }
 526  
             
 527  0
             Class<?> clazz = Class.forName(comparatorClassName.toString());
 528  0
             Comparator<?> comparator = (Comparator<?>)clazz.newInstance();
 529  0
             return reverse ? new ReverseComparator(comparator) : comparator;
 530  
         }
 531  0
         return null;
 532  
     }
 533  
     
 534  
     private void move(final File sourceFile,File destinationFile) throws DefaultMuleException
 535  
     {
 536  0
         if (destinationFile != null)
 537  
         {
 538  
             // move sourceFile to new destination. Do not use FileUtils here as it ultimately
 539  
             // falls back to copying the file which will cause problems on large files again -
 540  
             // which is what we're trying to avoid in the first place
 541  0
             boolean fileWasMoved = sourceFile.renameTo(destinationFile);
 542  
 
 543  
             // move didn't work - bail out
 544  0
             if (!fileWasMoved)
 545  
             {
 546  0
                 throw new DefaultMuleException(FileMessages.failedToMoveFile(sourceFile.getAbsolutePath(),
 547  
                     destinationFile.getAbsolutePath()));
 548  
             }
 549  
         }
 550  0
     }
 551  
     
 552  
 }