View Javadoc

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