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