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