View Javadoc

1   /*
2    * $Id: FtpMessageReceiver.java 19191 2010-08-25 21:05:23Z 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.ftp;
12  
13  import org.mule.api.MuleMessage;
14  import org.mule.api.construct.FlowConstruct;
15  import org.mule.api.endpoint.InboundEndpoint;
16  import org.mule.api.lifecycle.CreateException;
17  import org.mule.api.lifecycle.InitialisationException;
18  import org.mule.api.retry.RetryContext;
19  import org.mule.api.transport.Connector;
20  import org.mule.transport.AbstractPollingMessageReceiver;
21  import org.mule.transport.ConnectException;
22  
23  import java.io.FilenameFilter;
24  import java.io.IOException;
25  import java.text.MessageFormat;
26  import java.util.ArrayList;
27  import java.util.Collections;
28  import java.util.HashSet;
29  import java.util.List;
30  import java.util.Set;
31  
32  import javax.resource.spi.work.Work;
33  
34  import org.apache.commons.net.ftp.FTPClient;
35  import org.apache.commons.net.ftp.FTPFile;
36  import org.apache.commons.net.ftp.FTPReply;
37  
38  public class FtpMessageReceiver extends AbstractPollingMessageReceiver
39  {
40      protected final FtpConnector connector;
41      protected final FilenameFilter filenameFilter;
42  
43      // there's nothing like homegrown pseudo-2PC.. :/
44      // shared state management like this should go into the connector and use
45      // something like commons-tx
46      protected final Set<String> scheduledFiles = Collections.synchronizedSet(new HashSet<String>());
47      protected final Set<String> currentFiles = Collections.synchronizedSet(new HashSet<String>());
48  
49      public FtpMessageReceiver(Connector connector,
50                                FlowConstruct flowConstruct,
51                                InboundEndpoint endpoint,
52                                long frequency) throws CreateException
53      {
54          super(connector, flowConstruct, endpoint);
55  
56          this.setFrequency(frequency);
57  
58          this.connector = (FtpConnector) connector;
59  
60          if (endpoint.getFilter() instanceof FilenameFilter)
61          {
62              this.filenameFilter = (FilenameFilter) endpoint.getFilter();
63          }
64          else
65          {
66              this.filenameFilter = null;
67          }
68      }
69  
70      @Override
71      public void poll() throws Exception
72      {
73          FTPFile[] files = listFiles();
74          if (logger.isDebugEnabled())
75          {
76              logger.debug("Poll encountered " + files.length + " new file(s)");
77          }
78  
79          synchronized (scheduledFiles)
80          {
81              for (final FTPFile file : files)
82              {
83                  final String fileName = file.getName();
84  
85                  if (!scheduledFiles.contains(fileName) && !currentFiles.contains(fileName))
86                  {
87                      scheduledFiles.add(fileName);
88                      getWorkManager().scheduleWork(new FtpWork(fileName, file));
89                  }
90              }
91          }
92      }
93  
94      protected FTPFile[] listFiles() throws Exception
95      {
96          FTPClient client = null;
97          try
98          {
99              try
100             {
101                 client = connector.createFtpClient(endpoint);
102             }
103             catch (Exception e)
104             {
105                 throw new ConnectException(e, this);
106             }
107             FTPFile[] files = client.listFiles();
108 
109             if (!FTPReply.isPositiveCompletion(client.getReplyCode()))
110             {
111                 throw new IOException("Failed to list files. Ftp error: " + client.getReplyCode());
112             }
113 
114             if (files == null || files.length == 0)
115             {
116                 return files;
117             }
118 
119             List<FTPFile> v = new ArrayList<FTPFile>();
120 
121             for (FTPFile file : files)
122             {
123                 if (file.isFile())
124                 {
125                     if (filenameFilter == null || filenameFilter.accept(null, file.getName()))
126                     {
127                         v.add(file);
128                     }
129                 }
130             }
131 
132             return v.toArray(new FTPFile[v.size()]);
133         }
134         finally
135         {
136             if (client != null)
137             {
138                 connector.releaseFtp(endpoint.getEndpointURI(), client);
139             }
140         }
141     }
142 
143     protected void processFile(FTPFile file) throws Exception
144     {
145         FTPClient client = null;
146         try
147         {
148             if (!connector.validateFile(file))
149             {
150                 return;
151             }
152 
153             try
154             {
155                 client = connector.createFtpClient(endpoint);
156             }
157             catch (Exception e)
158             {
159                 throw new ConnectException(e, this);
160             }
161 
162             FtpMuleMessageFactory muleMessageFactory = createMuleMessageFactory(client);
163             MuleMessage message = muleMessageFactory.create(file, endpoint.getEncoding());
164 
165             routeMessage(message);
166             postProcess(client, file, message);
167         }
168         finally
169         {
170             if (client != null)
171             {
172                 connector.releaseFtp(endpoint.getEndpointURI(), client);
173             }
174         }
175     }
176 
177     @Override
178     protected void initializeMessageFactory() throws InitialisationException
179     {
180         // Do not initialize the muleMessageFactory instance variable of our super class as 
181         // we're creating MuleMessageFactory instances per request. 
182         // See createMuleMessageFactory(FTPClient) below.
183     }
184     
185     protected FtpMuleMessageFactory createMuleMessageFactory(FTPClient client) throws CreateException
186     {
187         FtpMuleMessageFactory factory = (FtpMuleMessageFactory) createMuleMessageFactory();
188         factory.setStreaming(connector.isStreaming());
189         factory.setFtpClient(client);    
190         
191         return factory;
192     }
193 
194     protected void postProcess(FTPClient client, FTPFile file, MuleMessage message) throws Exception
195     {
196         if (!client.deleteFile(file.getName()))
197         {
198             throw new IOException(MessageFormat.format("Failed to delete file {0}. Ftp error: {1}",
199                                                        file.getName(), client.getReplyCode()));
200         }
201         if (logger.isDebugEnabled())
202         {
203             logger.debug("Deleted processed file " + file.getName());
204         }
205         
206         if (connector.isStreaming())
207         {
208             if (!client.completePendingCommand())
209             {
210                 throw new IOException(MessageFormat.format("Failed to complete a pending command. Retrieveing file {0}. Ftp error: {1}",
211                                                            file.getName(), client.getReplyCode()));
212             }
213         }
214     }
215     
216     @Override
217     protected void doConnect() throws Exception
218     {
219         // no op
220     }
221 
222     @Override
223     public RetryContext validateConnection(RetryContext retryContext)
224     {
225         FTPClient client = null;
226         try
227         {
228             client = connector.createFtpClient(endpoint);
229             client.sendNoOp();
230             client.logout();
231             client.disconnect();
232 
233             retryContext.setOk();
234         }
235         catch (Exception ex)
236         {
237             retryContext.setFailed(ex);
238         }
239         finally
240         {
241             try
242             {
243                 if (client != null)
244                 {
245                     connector.releaseFtp(endpoint.getEndpointURI(), client);
246                 }
247             }
248             catch (Exception e)
249             {
250                 if (logger.isDebugEnabled())
251                 {
252                     logger.debug("Failed to release ftp client " + client, e);
253                 }
254             }
255         }
256 
257         return retryContext;
258     }
259         
260     @Override
261     protected void doDisconnect() throws Exception
262     {
263         // no op
264     }
265 
266     @Override
267     protected void doDispose()
268     {
269         // template method
270     }
271 
272     private final class FtpWork implements Work
273     {
274         private final String name;
275         private final FTPFile file;
276 
277         private FtpWork(String name, FTPFile file)
278         {
279             this.name = name;
280             this.file = file;
281         }
282 
283         public void run()
284         {
285             try
286             {
287                 currentFiles.add(name);
288                 processFile(file);
289             }
290             catch (Exception e)
291             {
292                 getConnector().getMuleContext().getExceptionListener().handleException(e);
293             }
294             finally
295             {
296                 currentFiles.remove(name);
297                 scheduledFiles.remove(name);
298             }
299         }
300 
301         public void release()
302         {
303             // no op
304         }
305     }
306 
307 }