View Javadoc

1   /*
2    * $Id: RetrieveMessageRequester.java 20321 2010-11-24 15:21:24Z dfeist $
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.email;
12  
13  import org.mule.api.MuleEvent;
14  import org.mule.api.MuleMessage;
15  import org.mule.api.endpoint.EndpointURI;
16  import org.mule.api.endpoint.InboundEndpoint;
17  import org.mule.transport.AbstractMessageRequester;
18  
19  import com.sun.mail.imap.IMAPMessage;
20  
21  import java.net.URLDecoder;
22  
23  import javax.mail.Flags;
24  import javax.mail.Folder;
25  import javax.mail.Message;
26  import javax.mail.MessagingException;
27  import javax.mail.Store;
28  import javax.mail.internet.MimeMessage;
29  
30  /**
31   * This dispatcher can only be used to receive message (as opposed to listening for them).
32   * Trying to send or dispatch will throw an UnsupportedOperationException.
33   * <p/>
34   * This contains a reference to a mail folder (and also the endpoint and connector, via superclasses)
35   */
36  public class RetrieveMessageRequester extends AbstractMessageRequester
37  {
38      private Folder folder;
39      private Folder moveToFolder;
40  
41      public RetrieveMessageRequester(InboundEndpoint endpoint)
42      {
43          super(endpoint);
44      }
45  
46      private AbstractRetrieveMailConnector castConnector()
47      {
48          return (AbstractRetrieveMailConnector) getConnector();
49      }
50  
51      @Override
52      protected void doConnect() throws Exception
53      {
54          if (folder == null || !folder.isOpen())
55          {
56              Store store = castConnector().getSessionDetails(endpoint).newStore();
57  
58              EndpointURI uri = endpoint.getEndpointURI();
59              String encoding = endpoint.getEncoding();
60              String user = (uri.getUser() != null ? URLDecoder.decode(uri.getUser(), encoding) : null);
61              String pass = (uri.getPassword() != null ? URLDecoder.decode(uri.getPassword(), encoding) : null);
62              store.connect(uri.getHost(), uri.getPort(), user, pass);
63  
64              folder = store.getFolder(castConnector().getMailboxFolder());
65              ensureFolderIsOpen(folder);
66  
67              if (castConnector().getMoveToFolder() != null)
68              {
69                  moveToFolder = store.getFolder(castConnector().getMoveToFolder());
70                  ensureFolderIsOpen(moveToFolder);
71              }
72          }
73      }
74      
75      protected void ensureFolderIsOpen(Folder fldr)
76      {
77          if (!fldr.isOpen())
78          {
79              try
80              {
81                  // Depending on Server implementation it's not always
82                  // necessary to open the folder to check it
83                  // Opening folders can be exprensive!
84                  fldr.open(Folder.READ_WRITE);
85              }
86              catch (MessagingException e)
87              {
88                  logger.warn("Failed to open folder: " + fldr.getFullName() + " This is not an exception since some server implementations do not require the folder to be open", e);
89              }
90          }
91      }
92  
93      @Override
94      protected void doDisconnect() throws Exception
95      {
96          // close and expunge deleted messages
97          try
98          {
99              if (folder != null)
100             {
101                 try
102                 {
103                     folder.expunge();
104                 }
105                 catch (MessagingException e)
106                 {
107                     if (logger.isDebugEnabled())
108                     {
109                         logger.debug("ignoring exception on expunge: " + e.getMessage());
110                     }
111                 }
112                 if (folder.isOpen())
113                 {
114                     folder.close(true);
115                 }
116             }
117         }
118         catch (Exception e)
119         {
120             logger.error("Failed to close inbox: " + e.getMessage(), e);
121         }
122 
123         try
124         {
125             if (moveToFolder != null)
126             {
127                 if (moveToFolder.isOpen())
128                 {
129                     moveToFolder.close(false);
130                 }
131             }
132         }
133         catch (Exception e)
134         {
135             logger.error("Failed to close moveToFolder: " + e.getMessage(), e);
136         }
137     }
138 
139     /**
140      * @param event
141      * @throws UnsupportedOperationException
142      */
143     protected void doDispatch(MuleEvent event) throws Exception
144     {
145         throw new UnsupportedOperationException("Cannot dispatch from a POP3/IMAP connection");
146     }
147 
148     /**
149      * @param event
150      * @throws UnsupportedOperationException
151      */
152     protected MuleMessage doSend(MuleEvent event) throws Exception
153     {
154         throw new UnsupportedOperationException("Cannot send from a POP3/IMAP connection");
155     }
156 
157     /**
158      * Make a specific request to the underlying transport. Endpoint can be in the
159      * form of pop3://username:password@pop3.lotsofmail.org
160      *
161      * @param timeout the maximum time the operation should block before returning.
162      *                The call should return immediately if there is data available. If
163      *                no data becomes available before the timeout elapses, null will be
164      *                returned
165      * @return the result of the request wrapped in a MuleMessage object. Null will be
166      *         returned if no data was avaialable
167      * @throws Exception if the call to the underlying protocal causes an exception
168      */
169     @Override
170     protected MuleMessage doRequest(long timeout) throws Exception
171     {
172         long t0 = System.currentTimeMillis();
173         if (timeout < 0)
174         {
175             timeout = Long.MAX_VALUE;
176         }
177 
178         do
179         {
180             if (hasMessages())
181             {
182                 int count = getMessageCount();
183                 if (count > 0)
184                 {
185                     Message message = getNextMessage();
186                     if (message != null)
187                     {
188                         // so we don't get the same message again
189                         flagMessage(message);
190 
191                         if (moveToFolder != null)
192                         {
193                             Message newMessage = message;
194                             //If we're using IMAP we need to cache the message contents so the message is accessible after the
195                             //folder is closed
196                             if (message instanceof IMAPMessage)
197                             {
198                                 //We need to copy and cache so that the message cna be moved
199                                 newMessage = new MimeMessage((IMAPMessage) message);
200                             }
201                             folder.copyMessages(new Message[]{message}, moveToFolder);
202                             message = newMessage;
203                         }
204                         return createMuleMessage(message, endpoint.getEncoding());
205                     }
206                 }
207                 else if (count == -1)
208                 {
209                     throw new MessagingException("Cannot monitor folder: " + folder.getFullName()
210                             + " as folder is closed");
211                 }
212             }
213 
214             long sleep =
215                     Math.min(castConnector().getCheckFrequency(),
216                             timeout - (System.currentTimeMillis() - t0));
217 
218             if (sleep > 0)
219             {
220                 if (logger.isDebugEnabled())
221                 {
222                     logger.debug("No results, sleeping for " + sleep);
223                 }
224                 try
225                 {
226                     Thread.sleep(sleep);
227                 }
228                 catch (InterruptedException e)
229                 {
230                     logger.warn("Thread interrupted while requesting email on: " + endpoint.getEndpointURI().toString());
231                     return null;
232                 }
233             }
234             else
235             {
236 
237                 logger.debug("Timeout");
238                 return null;
239             }
240 
241         }
242         while (true);
243     }
244 
245     /**
246      * There seems to be some variation on pop3 implementation so it may be
247      * preferrable to mark messages as seen here and also overload the getMessages
248      * method to grab only new messages
249      */
250     protected void flagMessage(Message message) throws MessagingException
251     {
252         if (castConnector().isDeleteReadMessages())
253         {
254             message.setFlag(Flags.Flag.DELETED, true);
255         }
256         else
257         {
258             message.setFlag(Flags.Flag.SEEN, true);
259         }
260     }
261 
262     protected Message getNextMessage() throws MessagingException
263     {
264         if (getMessageCount() > 0)
265         {
266             Message message = folder.getMessage(1);
267             if (!message.isExpunged())
268             {
269                 return message;
270             }
271         }
272         return null;
273     }
274 
275     protected int getMessageCount() throws MessagingException
276     {
277         return folder.getMessageCount();
278     }
279 
280     /**
281      * Optimised check to se whether to return the message count and retrieve the
282      * messages. Some pop3 implementations differ so an optimised check such as
283      * folder.hasNewMessages() cannot be used
284      */
285     protected boolean hasMessages() throws MessagingException
286     {
287         return getMessageCount() > 0;
288     }
289 
290     @Override
291     protected void doDispose()
292     {
293         if (null != folder && folder.isOpen())
294         {
295             try
296             {
297 
298                 folder.close(true);
299             }
300             catch (Exception e)
301             {
302                 logger.debug("ignoring exception: " + e.getMessage(), e);
303             }
304         }
305     }
306 }