View Javadoc

1   /*
2    * $Id: FileConnector.java 19640 2010-09-13 22:00:05Z tcarlson $
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.api.MuleContext;
14  import org.mule.api.MuleEvent;
15  import org.mule.api.MuleException;
16  import org.mule.api.MuleMessage;
17  import org.mule.api.config.MuleProperties;
18  import org.mule.api.construct.FlowConstruct;
19  import org.mule.api.endpoint.InboundEndpoint;
20  import org.mule.api.endpoint.OutboundEndpoint;
21  import org.mule.api.lifecycle.CreateException;
22  import org.mule.api.lifecycle.InitialisationException;
23  import org.mule.api.transport.DispatchException;
24  import org.mule.api.transport.MessageReceiver;
25  import org.mule.api.transport.MuleMessageFactory;
26  import org.mule.config.i18n.CoreMessages;
27  import org.mule.transformer.simple.ByteArrayToSerializable;
28  import org.mule.transformer.simple.SerializableToByteArray;
29  import org.mule.transport.AbstractConnector;
30  import org.mule.transport.file.filters.FilenameWildcardFilter;
31  import org.mule.util.FileUtils;
32  
33  import java.io.File;
34  import java.io.FileOutputStream;
35  import java.io.IOException;
36  import java.io.OutputStream;
37  import java.util.Map;
38  import java.util.Properties;
39  
40  import org.apache.commons.logging.Log;
41  import org.apache.commons.logging.LogFactory;
42  
43  /**
44   * <code>FileConnector</code> is used for setting up listeners on a directory and
45   * for writing files to a directory. The connecotry provides support for defining
46   * file output patterns and filters for receiving files.
47   */
48  
49  public class FileConnector extends AbstractConnector
50  {
51  
52      private static Log logger = LogFactory.getLog(FileConnector.class);
53  
54      public static final String FILE = "file";
55      private static final String DEFAULT_WORK_FILENAME_PATTERN = "#[function:uuid].#[function:systime].#[header:originalFilename]";
56  
57      // These are properties that can be overridden on the Receiver by the endpoint declaration
58      // inbound only
59      public static final String PROPERTY_POLLING_FREQUENCY = "pollingFrequency";
60      public static final String PROPERTY_FILE_AGE = "fileAge";
61      public static final String PROPERTY_MOVE_TO_PATTERN = "moveToPattern";
62      public static final String PROPERTY_MOVE_TO_DIRECTORY = "moveToDirectory";
63      public static final String PROPERTY_READ_FROM_DIRECTORY = "readFromDirectoryName";
64      // outbound only
65      public static final String PROPERTY_OUTPUT_PATTERN = "outputPattern";
66  
67      // message properties
68      public static final String PROPERTY_FILENAME = "filename";
69      public static final String PROPERTY_ORIGINAL_FILENAME = "originalFilename";
70      public static final String PROPERTY_DIRECTORY = "directory";
71      public static final String PROPERTY_WRITE_TO_DIRECTORY = "writeToDirectoryName";
72      public static final String PROPERTY_FILE_SIZE = "fileSize";
73      public static final String PROPERTY_FILE_TIMESTAMP = "timestamp";
74  
75      public static final long DEFAULT_POLLING_FREQUENCY = 1000;
76  
77      /**
78       * Time in milliseconds to poll. On each poll the poll() method is called
79       */
80      private long pollingFrequency = 0;
81  
82      private String moveToPattern = null;
83  
84      private String writeToDirectoryName = null;
85  
86      private String moveToDirectoryName = null;
87      
88      private String workDirectoryName = null;
89  
90      private String workFileNamePattern = DEFAULT_WORK_FILENAME_PATTERN;
91  
92      private String readFromDirectoryName = null;
93  
94      private String outputPattern = null;
95  
96      private boolean outputAppend = false;
97  
98      private boolean autoDelete = true;
99  
100     private boolean checkFileAge = false;
101 
102     private long fileAge = 0;
103 
104     private FileOutputStream outputStream = null;
105 
106     private boolean serialiseObjects = false;
107 
108     private boolean streaming = true;
109 
110     public FilenameParser filenameParser;
111 
112     private boolean recursive = false;
113 
114     public FileConnector(MuleContext context)
115     {
116         super(context);
117         filenameParser = new ExpressionFilenameParser();
118     }
119 
120     @Override
121     protected void configureDispatcherPool()
122     {
123         if (isOutputAppend())
124         {
125             setMaxDispatchersActive(getDispatcherThreadingProfile().getMaxThreadsActive());
126         }
127         else
128         {
129             super.configureDispatcherPool();
130         }
131     }
132 
133     @Override
134     public void setMaxDispatchersActive(int value)
135     {
136         if (isOutputAppend() && value != 1)
137         {
138             logger.warn("MULE-1773: cannot configure maxDispatchersActive when using outputAppend. New value not set");
139         }
140         else
141         {
142             super.setMaxDispatchersActive(value);
143         }
144     }
145 
146     @Override
147     protected Object getReceiverKey(FlowConstruct flowConstruct, InboundEndpoint endpoint)
148     {
149         if (endpoint.getFilter() != null && endpoint.getFilter() instanceof FilenameWildcardFilter)
150         {
151             return endpoint.getEndpointURI().getAddress() + "/"
152                     + ((FilenameWildcardFilter) endpoint.getFilter()).getPattern();
153         }
154         return endpoint.getEndpointURI().getAddress();
155     }
156 
157     /**
158      * Registers a listener for a particular directory The following properties can
159      * be overriden in the endpoint declaration
160      * <ul>
161      * <li>moveToDirectory</li>
162      * <li>filterPatterns</li>
163      * <li>filterClass</li>
164      * <li>pollingFrequency</li>
165      * </ul>
166      */
167     @Override
168     public MessageReceiver createReceiver(FlowConstruct flowConstruct, InboundEndpoint endpoint) throws Exception
169     {
170         String readDir = endpoint.getEndpointURI().getAddress();
171         if (null != getReadFromDirectory())
172         {
173             readDir = getReadFromDirectory();
174         }
175 
176         long polling = this.pollingFrequency;
177 
178         String moveTo = moveToDirectoryName;
179         String moveToPattern = getMoveToPattern();
180 
181         Map props = endpoint.getProperties();
182         if (props != null)
183         {
184             // Override properties on the endpoint for the specific endpoint
185             String read = (String) props.get(PROPERTY_READ_FROM_DIRECTORY);
186             if (read != null)
187             {
188                 readDir = read;
189             }
190             String move = (String) props.get(PROPERTY_MOVE_TO_DIRECTORY);
191             if (move != null)
192             {
193                 moveTo = move;
194             }
195             String tempMoveToPattern = (String) props.get(PROPERTY_MOVE_TO_PATTERN);
196             if (tempMoveToPattern != null)
197             {
198                 if (logger.isDebugEnabled())
199                 {
200                     logger.debug("set moveTo Pattern to: " + tempMoveToPattern);
201                 }
202                 moveToPattern = tempMoveToPattern;
203             }
204 
205             String tempPolling = (String) props.get(PROPERTY_POLLING_FREQUENCY);
206             if (tempPolling != null)
207             {
208                 polling = Long.parseLong(tempPolling);
209             }
210 
211             if (polling <= 0)
212             {
213                 polling = DEFAULT_POLLING_FREQUENCY;
214             }
215 
216             if (logger.isDebugEnabled())
217             {
218                 logger.debug("set polling frequency to: " + polling);
219             }
220             String tempFileAge = (String) props.get(PROPERTY_FILE_AGE);
221             if (tempFileAge != null)
222             {
223                 try
224                 {
225                     setFileAge(Long.parseLong(tempFileAge));
226                 }
227                 catch (Exception ex1)
228                 {
229                     logger.error("Failed to set fileAge", ex1);
230                 }
231             }
232         }
233 
234         try
235         {
236             return serviceDescriptor.createMessageReceiver(this, flowConstruct, endpoint, new Object[]{readDir,
237                     moveTo, moveToPattern, new Long(polling)});
238 
239         }
240         catch (Exception e)
241         {
242             throw new InitialisationException(
243                     CoreMessages.failedToCreateObjectWith("Message Receiver",
244                             serviceDescriptor), e, this);
245         }
246     }
247 
248     public String getProtocol()
249     {
250         return FILE;
251     }
252 
253     public FilenameParser getFilenameParser()
254     {
255         return filenameParser;
256     }
257 
258     public void setFilenameParser(FilenameParser filenameParser)
259     {
260         this.filenameParser = filenameParser;
261         if (filenameParser != null)
262         {
263             filenameParser.setMuleContext(muleContext);
264         }
265     }
266 
267     @Override
268     protected void doDispose()
269     {
270         try
271         {
272             doStop();
273         }
274         catch (MuleException e)
275         {
276             logger.error(e.getMessage(), e);
277         }
278     }
279 
280     @Override
281     protected void doInitialise() throws InitialisationException
282     {
283         if (filenameParser != null)
284         {
285             filenameParser.setMuleContext(muleContext);
286         }
287 
288         // MULE-1773: limit the number of dispatchers per endpoint to 1 until
289         // there is a proper (Distributed)LockManager in place (MULE-2402).
290         // We also override the setter to prevent "wrong" configuration for now.
291         if (isOutputAppend())
292         {
293             super.setMaxDispatchersActive(1);
294         }
295     }
296 
297     @Override
298     protected void doConnect() throws Exception
299     {
300         // template method, nothing to do
301     }
302 
303     @Override
304     protected void doDisconnect() throws Exception
305     {
306         // template method, nothing to do
307     }
308 
309     @Override
310     protected void doStart() throws MuleException
311     {
312         // template method, nothing to do
313     }
314 
315     @Override
316     protected void doStop() throws MuleException
317     {
318         if (outputStream != null)
319         {
320             try
321             {
322                 outputStream.close();
323             }
324             catch (IOException e)
325             {
326                 logger.warn("Failed to close file output stream on stop: " + e);
327             }
328         }
329     }
330 
331     public String getMoveToDirectory()
332     {
333         return moveToDirectoryName;
334     }
335 
336     public void setMoveToDirectory(String dir)
337     {
338         this.moveToDirectoryName = dir;
339     }
340 
341     public void setWorkDirectory(String workDirectoryName) throws IOException 
342     {
343         this.workDirectoryName = workDirectoryName;
344         if (workDirectoryName != null)
345         {
346             File workDirectory = FileUtils.openDirectory(workDirectoryName);
347             if (!workDirectory.canWrite())
348             {
349                 throw new IOException(
350                         "Error on initialization, Work Directory '" + workDirectory +"' is not writeable");
351             }
352         }
353     }
354     
355     public String getWorkDirectory()
356     {
357         return workDirectoryName;
358     }
359 
360     public void setWorkFileNamePattern(String workFileNamePattern)
361     {
362         this.workFileNamePattern = workFileNamePattern;
363     }
364 
365     public String getWorkFileNamePattern()
366     {
367         return workFileNamePattern;
368     }
369 
370     public boolean isOutputAppend()
371     {
372         return outputAppend;
373     }
374 
375     public void setOutputAppend(boolean outputAppend)
376     {
377         this.outputAppend = outputAppend;
378     }
379 
380     public String getOutputPattern()
381     {
382         return outputPattern;
383     }
384 
385     public void setOutputPattern(String outputPattern)
386     {
387         this.outputPattern = outputPattern;
388     }
389 
390     public FileOutputStream getOutputStream()
391     {
392         return outputStream;
393     }
394 
395     public void setOutputStream(FileOutputStream outputStream)
396     {
397         this.outputStream = outputStream;
398     }
399 
400     public long getPollingFrequency()
401     {
402         return pollingFrequency;
403     }
404 
405     public void setPollingFrequency(long pollingFrequency)
406     {
407         this.pollingFrequency = pollingFrequency;
408     }
409 
410     public long getFileAge()
411     {
412         return fileAge;
413     }
414 
415     public boolean getCheckFileAge()
416     {
417         return checkFileAge;
418     }
419 
420     public void setFileAge(long fileAge)
421     {
422         this.fileAge = fileAge;
423         this.checkFileAge = true;
424     }
425 
426     public String getWriteToDirectory()
427     {
428         return writeToDirectoryName;
429     }
430 
431     public void setWriteToDirectory(String dir) throws IOException
432     {
433         this.writeToDirectoryName = dir;
434         if (writeToDirectoryName != null)
435         {
436             File writeToDirectory = FileUtils.openDirectory(writeToDirectoryName);
437             if (!writeToDirectory.canWrite())
438             {
439                 throw new IOException(
440                         "Error on initialization, " + writeToDirectory 
441                         + " does not exist or is not writeable");
442             }
443         }
444     }
445 
446     public String getReadFromDirectory()
447     {
448         return readFromDirectoryName;
449     }
450 
451     public void setReadFromDirectory(String dir) throws IOException
452     {
453         this.readFromDirectoryName = dir;
454         if (readFromDirectoryName != null)
455         {
456             // check if the directory exists/can be read
457             FileUtils.openDirectory((readFromDirectoryName));
458         }
459     }
460 
461     public boolean isSerialiseObjects()
462     {
463         return serialiseObjects;
464     }
465 
466     public void setSerialiseObjects(boolean serialiseObjects)
467     {
468         // set serialisable transformers on the connector if this is set
469         if (serialiseObjects)
470         {
471             if (serviceOverrides == null)
472             {
473                 serviceOverrides = new Properties();
474             }
475             serviceOverrides.setProperty(MuleProperties.CONNECTOR_INBOUND_TRANSFORMER,
476                     ByteArrayToSerializable.class.getName());
477             serviceOverrides.setProperty(MuleProperties.CONNECTOR_OUTBOUND_TRANSFORMER,
478                     SerializableToByteArray.class.getName());
479         }
480 
481         this.serialiseObjects = serialiseObjects;
482     }
483 
484     public boolean isAutoDelete()
485     {
486         return autoDelete;
487     }
488 
489     public void setAutoDelete(boolean autoDelete)
490     {
491         this.autoDelete = autoDelete;
492     }
493 
494     public String getMoveToPattern()
495     {
496         return moveToPattern;
497     }
498 
499     public void setMoveToPattern(String moveToPattern)
500     {
501         this.moveToPattern = moveToPattern;
502     }
503 
504     /**
505      * Well get the output stream (if any) for this type of transport. Typically this
506      * will be called only when Streaming is being used on an outbound endpoint
507      *
508      * @param endpoint the endpoint that releates to this Dispatcher
509      * @param message  the current message being processed
510      * @return the output stream to use for this request or null if the transport
511      *         does not support streaming
512      * @throws org.mule.api.MuleException
513      */
514     public OutputStream getOutputStream(OutboundEndpoint endpoint, MuleEvent event)
515             throws MuleException
516     {
517         MuleMessage message = event.getMessage();
518         String address = endpoint.getEndpointURI().getAddress();
519         String writeToDirectory = message.getOutboundProperty(FileConnector.PROPERTY_WRITE_TO_DIRECTORY);
520         if (writeToDirectory == null)
521         {
522             writeToDirectory = getWriteToDirectory();
523         }
524         if (writeToDirectory != null)
525         {
526             address = getFilenameParser().getFilename(message, writeToDirectory);
527         }
528 
529         String filename;
530         String outPattern = (String) endpoint.getProperty(FileConnector.PROPERTY_OUTPUT_PATTERN);
531         if (outPattern == null)
532         {
533             outPattern = message.getOutboundProperty(FileConnector.PROPERTY_OUTPUT_PATTERN);
534         }
535         if (outPattern == null)
536         {
537             outPattern = getOutputPattern();
538         }
539         try
540         {
541             if (outPattern != null)
542             {
543                 filename = generateFilename(message, outPattern);
544             }
545             else
546             {
547                 filename = message.getOutboundProperty(FileConnector.PROPERTY_FILENAME);
548                 if (filename == null)
549                 {
550                     filename = generateFilename(message, null);
551                 }
552             }
553 
554             if (filename == null)
555             {
556                 throw new IOException("Filename is null");
557             }
558             File file = FileUtils.createFile(address + "/" + filename);
559             if (logger.isInfoEnabled())
560             {
561                 logger.info("Writing file to: " + file.getAbsolutePath());
562             }
563 
564             return new FileOutputStream(file, isOutputAppend());
565         }
566         catch (IOException e)
567         {
568             throw new DispatchException(CoreMessages.streamingFailedNoStream(), event, endpoint, e);
569         }
570     }
571 
572     private String generateFilename(MuleMessage message, String pattern)
573     {
574         if (pattern == null)
575         {
576             pattern = getOutputPattern();
577         }
578         return getFilenameParser().getFilename(message, pattern);
579     }
580 
581     public boolean isStreaming()
582     {
583         return streaming;
584     }
585 
586     public void setStreaming(boolean streaming)
587     {
588         this.streaming = streaming;
589     }
590         
591     @Override
592     public MuleMessageFactory createMuleMessageFactory() throws CreateException
593     {
594         // See MULE-3209, MULE-3199
595         if (isStreaming())
596         {
597             return new FileMuleMessageFactory(muleContext);
598         }
599         else
600         {
601             return super.createMuleMessageFactory();
602         }
603     }
604     public boolean isRecursive()
605     {
606         return recursive;
607     }
608 
609     public void setRecursive(boolean recursive)
610     {
611         this.recursive = recursive;
612     }
613 }