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.MuleException;
10  import org.mule.api.MuleMessage;
11  import org.mule.api.construct.FlowConstruct;
12  import org.mule.api.endpoint.InboundEndpoint;
13  import org.mule.api.lifecycle.CreateException;
14  import org.mule.api.transport.Connector;
15  import org.mule.api.transport.ReceiveException;
16  import org.mule.transport.AbstractPollingMessageReceiver;
17  import org.mule.transport.email.i18n.EmailMessages;
18  import org.mule.util.FileUtils;
19  import org.mule.util.StringUtils;
20  import org.mule.util.UUID;
21  
22  import java.io.File;
23  import java.io.FileOutputStream;
24  import java.io.IOException;
25  import java.util.ArrayList;
26  import java.util.List;
27  
28  import javax.mail.Address;
29  import javax.mail.Flags;
30  import javax.mail.Folder;
31  import javax.mail.Message;
32  import javax.mail.MessagingException;
33  import javax.mail.Store;
34  import javax.mail.event.MessageCountEvent;
35  import javax.mail.event.MessageCountListener;
36  import javax.mail.internet.InternetAddress;
37  import javax.mail.internet.MimeMessage;
38  
39  /**
40   * Poll a mailbox for messages, remove the messages and route them as events into
41   * Mule.
42   * <p/>
43   * This contains a reference to a mail folder (and also the endpoint and connector,
44   * via superclasses)
45   */
46  public class RetrieveMessageReceiver extends AbstractPollingMessageReceiver implements MessageCountListener
47  {
48      private Folder folder = null;
49      private Folder moveToFolder = null;
50      private boolean backupEnabled;
51      private String backupFolder = null;
52      // A lock to protect concurrent access to the folder.
53      private final Object folderLock = new Object();
54  
55      public RetrieveMessageReceiver(Connector connector,
56                                     FlowConstruct flowConstruct,
57                                     InboundEndpoint endpoint,
58                                     long checkFrequency,
59                                     boolean backupEnabled,
60                                     String backupFolder) throws CreateException
61      {
62          super(connector, flowConstruct, endpoint);
63          this.backupFolder = backupFolder;
64          this.backupEnabled = backupEnabled;
65          this.setFrequency(checkFrequency);
66      }
67  
68      private AbstractRetrieveMailConnector castConnector()
69      {
70          return (AbstractRetrieveMailConnector) getConnector();
71      }
72  
73      @Override
74      protected void doConnect() throws Exception
75      {
76          SessionDetails session = castConnector().getSessionDetails(endpoint);
77  
78          Store store = session.newStore();
79          store.connect();
80          folder = store.getFolder(castConnector().getMailboxFolder());
81          if (castConnector().getMoveToFolder() != null)
82          {
83              moveToFolder = store.getFolder(castConnector().getMoveToFolder());
84              moveToFolder.open(Folder.READ_WRITE);
85          }
86  
87          // set default value if empty/null
88          if (StringUtils.isEmpty(backupFolder))
89          {
90              this.backupFolder = connector.getMuleContext().getConfiguration().getWorkingDirectory()
91                                  + "/mail/" + folder.getName();
92          }
93  
94          if (backupFolder != null && !this.backupFolder.endsWith(File.separator))
95          {
96              this.backupFolder += File.separator;
97          }
98      }
99  
100     @Override
101     protected void doDisconnect() throws Exception
102     {
103         // nothing to do here
104     }
105 
106     @Override
107     protected void doStop()
108     {
109         synchronized (folderLock)
110         {
111             if (folder != null)
112             {
113                 folder.removeMessageCountListener(this);
114             }
115         }
116     }
117 
118     @Override
119     protected void doStart() throws MuleException
120     {
121         super.doStart();
122         synchronized (folderLock)
123         {
124             folder.addMessageCountListener(this);
125         }
126     }
127 
128     public void messagesAdded(MessageCountEvent event)
129     {
130         Message messages[] = event.getMessages();
131         List<Message> processedMessages = new ArrayList<Message>();
132         if (messages != null)
133         {
134             MuleMessage message = null;
135             for (int i = 0; i < messages.length; i++)
136             {
137                 if (getLifecycleState().isStopping() || getLifecycleState().isStopped())
138                 {
139                     break;
140                 }
141                 processedMessages.add(messages[i]);
142                 try
143                 {
144                     if (!messages[i].getFlags().contains(Flags.Flag.DELETED)
145                         && !messages[i].getFlags().contains(Flags.Flag.SEEN))
146                     {
147                         MimeMessage mimeMessage = new MimeMessage((MimeMessage) messages[i]);
148                         storeMessage(mimeMessage);
149                         message = createMuleMessage(mimeMessage, endpoint.getEncoding());
150 
151                         if (castConnector().isDeleteReadMessages())
152                         {
153                             // Mark as deleted
154                             messages[i].setFlag(Flags.Flag.DELETED, true);
155                         }
156                         else
157                         {
158                             if (this.getEndpoint().getFilter() != null && this.getEndpoint().getFilter().accept(message))
159                             {
160                                 Flags.Flag flag = castConnector().getDefaultProcessMessageAction();
161                                 if (flag != null)
162                                 {
163                                     messages[i].setFlag(flag, true);
164                                 }
165                             }
166                             else
167                             {
168                                 messages[i].setFlag(Flags.Flag.SEEN, false);
169                             }
170                         }
171 
172                         routeMessage(message);
173                     }
174                 }
175                 catch (MuleException e)
176                 {
177                     getConnector().getMuleContext().getExceptionListener().handleException(e);
178                 }
179                 catch (Exception e)
180                 {
181                     Exception forwarded;
182 
183                     if (message != null)
184                     {
185                         forwarded = new org.mule.api.MessagingException(EmailMessages.routingError(), message, e);
186                     }
187                     else
188                     {
189                         forwarded = new ReceiveException(endpoint, -1, e);
190                     }
191 
192                     getConnector().getMuleContext().getExceptionListener().handleException(forwarded);
193                 }
194             }
195             // Lets move all messages in one go
196             if (moveToFolder != null)
197             {
198                 try
199                 {
200                     folder.copyMessages(processedMessages.toArray(new Message[processedMessages.size()]), moveToFolder);
201                 }
202                 catch (MessagingException e)
203                 {
204                     getConnector().getMuleContext().getExceptionListener().handleException(e);
205                 }
206             }
207         }
208     }
209 
210     public void messagesRemoved(MessageCountEvent event)
211     {
212         if (logger.isDebugEnabled())
213         {
214             Message messages[] = event.getMessages();
215             for (int i = 0; i < messages.length; i++)
216             {
217                 try
218                 {
219                     logger.debug("Message removed: " + messages[i].getSubject());
220                 }
221                 catch (MessagingException ignore)
222                 {
223                     logger.debug("ignoring exception: " + ignore.getMessage());
224                 }
225             }
226         }
227     }
228 
229     /** @return the current Mail folder */
230     public Folder getFolder()
231     {
232         return folder;
233     }
234 
235     /** @param folder */
236     public void setFolder(Folder folder)
237     {
238         synchronized (folderLock)
239         {
240             if (folder == null)
241             {
242                 throw new IllegalArgumentException("Mail folder cannot be null");
243             }
244             this.folder = folder;
245             if (!this.folder.isOpen())
246             {
247                 try
248                 {
249                     this.folder.open(Folder.READ_WRITE);
250                 }
251                 catch (MessagingException e)
252                 {
253                     logger.warn("Failed to open folder: " + folder.getFullName(), e);
254                 }
255             }
256         }
257     }
258 
259     /**
260      * Helper method for testing which stores a copy of the message locally as the
261      * POP3
262      * <p/>
263      * message will be deleted from the server
264      *
265      * @param msg the message to store
266      * @throws IOException If a failure happens writing the message
267      * @throws MessagingException If a failure happens reading the message
268      */
269     protected void storeMessage(Message msg) throws IOException, MessagingException
270     {
271         if (backupEnabled)
272         {
273             String filename = msg.getFileName();
274             if (filename == null)
275             {
276                 Address[] from = msg.getFrom();
277                 if (from != null && from.length > 0)
278                 {
279                     filename = from[0] instanceof InternetAddress
280                                                                  ? ((InternetAddress) from[0]).getAddress()
281                                                                  : from[0].toString();
282                 }
283                 else
284                 {
285                     filename = "(no from address)";
286                 }
287                 filename += "[" + UUID.getUUID() + "]";
288             }
289             filename = FileUtils.prepareWinFilename(filename);
290             filename = backupFolder + filename + ".msg";
291             if (logger.isDebugEnabled())
292             {
293                 logger.debug("Writing message to: " + filename);
294             }
295             File f = FileUtils.createFile(filename);
296             FileOutputStream fos = new FileOutputStream(f);
297             msg.writeTo(fos);
298         }
299     }
300 
301     @Override
302     public void poll()
303     {
304         synchronized (folderLock)
305         {
306             try
307             {
308                 try
309                 {
310                     if (!folder.isOpen())
311                     {
312                         folder.open(Folder.READ_WRITE);
313                     }
314                 }
315                 catch (Exception e)
316                 {
317                     if (logger.isDebugEnabled())
318                     {
319                         logger.debug("ignoring exception: " + e.getMessage());
320                     }
321                 }
322 
323                 int count = folder.getMessageCount();
324                 if (count > 0)
325                 {
326                     Message[] messages = folder.getMessages();
327                     MessageCountEvent event = new MessageCountEvent(folder, MessageCountEvent.ADDED, true,
328                         messages);
329                     messagesAdded(event);
330                 }
331                 else if (count == -1)
332                 {
333                     throw new MessagingException("Cannot monitor folder: " + folder.getFullName()
334                                                  + " as folder is closed");
335                 }
336             }
337             catch (MessagingException e)
338             {
339                 getConnector().getMuleContext().getExceptionListener().handleException(e);
340             }
341             finally
342             {
343                 try
344                 {
345                     folder.close(true); // close and expunge deleted messages
346                 }
347                 catch (Exception e)
348                 {
349                     logger.error("Failed to close pop3 inbox: " + e.getMessage());
350                 }
351             }
352         }
353     }
354 
355     @Override
356     protected void doDispose()
357     {
358         synchronized (folderLock)
359         {
360             if (null != folder)
361             {
362                 folder.removeMessageCountListener(this);
363                 if (folder.isOpen())
364                 {
365                     try
366                     {
367                         folder.close(true);
368                     }
369                     catch (Exception e)
370                     {
371                         logger.debug("ignoring exception: " + e.getMessage(), e);
372                     }
373                 }
374             }
375         }
376     }
377 
378     @Override
379     protected MuleMessage handleUnacceptedFilter(MuleMessage message)
380     {
381         super.handleUnacceptedFilter(message);
382         if (message.getPayload() instanceof Message)
383         {
384             Message msg = (Message) message.getPayload();
385             try
386             {
387                 msg.setFlag(Flags.Flag.DELETED, endpoint.isDeleteUnacceptedMessages());
388             }
389             catch (MessagingException e)
390             {
391                 logger.error("failed to set message deleted: " + e.getMessage(), e);
392             }
393         }
394         return message;
395     }
396 }