1. Project Clover database Tue Dec 20 2016 21:24:09 CET
  2. Package org.xwiki.mail.internal.thread

File SendMailRunnable.java

 

Coverage histogram

../../../../../img/srcFileCovDistChart9.png
38% of files have more coverage

Code metrics

16
49
7
1
208
129
22
0.45
7
7
3.14

Classes

Class Line # Actions
SendMailRunnable 52 49 0% 22 8
0.888888988.9%
 

Contributing tests

This file is covered by 8 tests. .

Source view

1    /*
2    * See the NOTICE file distributed with this work for additional
3    * information regarding copyright ownership.
4    *
5    * This is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU Lesser General Public License as
7    * published by the Free Software Foundation; either version 2.1 of
8    * the License, or (at your option) any later version.
9    *
10    * This software is distributed in the hope that it will be useful,
11    * but WITHOUT ANY WARRANTY; without even the implied warranty of
12    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13    * Lesser General Public License for more details.
14    *
15    * You should have received a copy of the GNU Lesser General Public
16    * License along with this software; if not, write to the Free
17    * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18    * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
19    */
20    package org.xwiki.mail.internal.thread;
21   
22    import java.util.Collections;
23   
24    import javax.inject.Inject;
25    import javax.inject.Named;
26    import javax.inject.Provider;
27    import javax.inject.Singleton;
28    import javax.mail.MessagingException;
29    import javax.mail.Session;
30    import javax.mail.Transport;
31   
32    import org.apache.commons.lang.exception.ExceptionUtils;
33    import org.xwiki.component.annotation.Component;
34    import org.xwiki.context.ExecutionContext;
35    import org.xwiki.context.ExecutionContextException;
36    import org.xwiki.context.ExecutionContextManager;
37    import org.xwiki.mail.ExtendedMimeMessage;
38    import org.xwiki.mail.MailContentStore;
39    import org.xwiki.mail.MailListener;
40   
41    import com.xpn.xwiki.XWikiContext;
42   
43    /**
44    * Runnable that regularly check for mails on a Queue, and for each mail tries to send it.
45    *
46    * @version $Id: 3ed8f7f2a288eaac412c98c44b144f0c2d450042 $
47    * @since 6.4
48    */
49    @Component
50    @Named("send")
51    @Singleton
 
52    public class SendMailRunnable extends AbstractMailRunnable
53    {
54    @Inject
55    private MailQueueManager<SendMailQueueItem> sendMailQueueManager;
56   
57    @Inject
58    @Named("filesystem")
59    private MailContentStore mailContentStore;
60   
61    @Inject
62    private ExecutionContextManager executionContextManager;
63   
64    @Inject
65    private Provider<XWikiContext> contextProvider;
66   
67    private Transport currentTransport;
68   
69    private Session currentSession;
70   
71    private int count;
72   
 
73  12 toggle @Override
74    public void run()
75    {
76  12 try {
77    // Make sure we initialize an execution context.
78  12 prepareContext();
79   
80  12 runInternal();
81    } catch (ExecutionContextException e) {
82    // Not much to do but log.
83  0 logger.error("Failed to initialize the send mail thread's execution context", e);
84    } finally {
85  12 closeTransport();
86    }
87    }
88   
 
89  12 toggle private void prepareContext() throws ExecutionContextException
90    {
91    // Create a single execution context and use it for the send mail thread.
92  12 ExecutionContext ec = new ExecutionContext();
93  12 this.executionContextManager.initialize(ec);
94    }
95   
 
96  21 toggle private void prepareContextForQueueItem(SendMailQueueItem mailItem)
97    {
98    // Set the current wiki in the context. This is needed for example to be able to locate the configuration
99    // properties when processing the mail queue items (in waitSendWaitTime()).
100  21 XWikiContext xcontext = this.contextProvider.get();
101  21 xcontext.setWikiId(mailItem.getWikiId());
102    }
103   
 
104  12 toggle private void runInternal()
105    {
106  12 do {
107  8282 try {
108    // Handle next message in the queue
109  8282 if (this.sendMailQueueManager.hasMessage()) {
110    // Important: only remove the mail item from the queue after the mail has been sent as
111    // otherwise, MailSender.waitTillSent() may return before the mail is actually sent!
112  21 SendMailQueueItem mailItem = this.sendMailQueueManager.peekMessage();
113  21 try {
114  21 sendMail(mailItem);
115    } finally {
116  21 this.sendMailQueueManager.removeMessageFromQueue(mailItem);
117    }
118    // Email throttling: Wait before processing the next mail queue item
119    // Note: it's important that we wait after the previous item has been removed from the queue in
120    // order to let users know as soon as possible that their mail has been sent (otherwise when sending
121    // a synchronous mail, the user would have to wait the send wait time!).
122  21 waitSendWaitTime();
123    }
124    // Note: a short pause to catch thread interruptions and to be kind on CPU.
125  8282 Thread.sleep(50L);
126    } catch (InterruptedException e) {
127    // Thread has been stopped, exit
128  12 this.logger.debug("Mail Sender Thread was forcefully stopped", e);
129  12 break;
130    } catch (Exception e) {
131    // There was an unexpected problem, we just log the problem but keep the thread alive!
132  0 this.logger.error("Unexpected error in the Mail Sender Thread", e);
133    }
134  8270 } while (!this.shouldStop);
135    }
136   
137    /**
138    * Send the mail.
139    *
140    * @param item the queue item containing all the data for sending the mail
141    */
 
142  21 toggle protected void sendMail(SendMailQueueItem item)
143    {
144  21 prepareContextForQueueItem(item);
145   
146  21 MailListener listener = item.getListener();
147   
148  21 ExtendedMimeMessage message;
149  21 try {
150    // Step 1: Load the message from the filesystem store
151  21 message = this.mailContentStore.load(item.getSession(), item.getBatchId(), item.getUniqueMessageId());
152    } catch (Exception e) {
153  2 if (listener != null) {
154  2 listener.onSendMessageFatalError(item.getUniqueMessageId(), e, Collections.<String, Object>emptyMap());
155    }
156  2 return;
157    }
158   
159  19 try {
160    // Step 2: If the current Session in use is different from the one passed then close
161    // the current Transport, get a new one and reconnect.
162    // Also do that every 100 mails sent.
163    // TODO: explain why!
164  19 if (item.getSession() != this.currentSession || (this.count % 100) == 0) {
165  14 closeTransport();
166  14 this.currentSession = item.getSession();
167  14 this.currentTransport = this.currentSession.getTransport("smtp");
168  14 this.currentTransport.connect();
169  5 } else if (!this.currentTransport.isConnected()) {
170  0 this.currentTransport.connect();
171    }
172   
173    // Step 3: Send the mail
174    // Unlike the static send method, the sendMessage method does not call the saveChanges method on the
175    // message; this prevent the MessageID header to be changed.
176  18 this.currentTransport.sendMessage(message, message.getAllRecipients());
177  16 this.count++;
178   
179    // Step 4: Notify the user of the success if a listener has been provided
180  16 if (listener != null) {
181  14 listener.onSendMessageSuccess(message, Collections.<String, Object>emptyMap());
182    }
183    } catch (Exception e) {
184    // An error occurred, notify the user if a listener has been provided.
185  3 if (listener != null) {
186  3 listener.onSendMessageError(message, e, Collections.<String, Object>emptyMap());
187    }
188    }
189    }
190   
 
191  21 toggle private void waitSendWaitTime() throws InterruptedException
192    {
193  21 long sendWaitTime = this.configuration.getSendWaitTime();
194  21 Thread.sleep(sendWaitTime);
195    }
196   
 
197  26 toggle private void closeTransport()
198    {
199  26 if (this.currentTransport != null) {
200  14 try {
201  14 this.currentTransport.close();
202    } catch (MessagingException e) {
203  0 this.logger.warn("Failed to close JavaMail Transport connection. Reason [{}]",
204    ExceptionUtils.getRootCauseMessage(e));
205    }
206    }
207    }
208    }