View Javadoc

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