View Javadoc

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