1. Project Clover database Tue Dec 20 2016 21:24:09 CET
  2. Package com.xpn.xwiki.plugin.mailsender

File MailSenderPlugin.java

 

Coverage histogram

../../../../../img/srcFileCovDistChart7.png
64% of files have more coverage

Code metrics

102
336
24
1
892
586
99
0.29
14
24
4.12

Classes

Class Line # Actions
MailSenderPlugin 89 336 0% 99 146
0.683982768.4%
 

Contributing tests

This file is covered by 13 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 com.xpn.xwiki.plugin.mailsender;
21   
22    import java.io.BufferedReader;
23    import java.io.File;
24    import java.io.FileOutputStream;
25    import java.io.IOException;
26    import java.io.PrintWriter;
27    import java.io.StringReader;
28    import java.io.StringWriter;
29    import java.io.UnsupportedEncodingException;
30    import java.util.ArrayList;
31    import java.util.Collection;
32    import java.util.Date;
33    import java.util.Iterator;
34    import java.util.List;
35    import java.util.Locale;
36    import java.util.Map;
37    import java.util.Properties;
38    import java.util.regex.Matcher;
39    import java.util.regex.Pattern;
40   
41    import javax.activation.DataHandler;
42    import javax.activation.DataSource;
43    import javax.activation.FileDataSource;
44    import javax.mail.BodyPart;
45    import javax.mail.MessagingException;
46    import javax.mail.Multipart;
47    import javax.mail.SendFailedException;
48    import javax.mail.Session;
49    import javax.mail.Transport;
50    import javax.mail.internet.AddressException;
51    import javax.mail.internet.InternetAddress;
52    import javax.mail.internet.MimeBodyPart;
53    import javax.mail.internet.MimeMessage;
54    import javax.mail.internet.MimeMultipart;
55   
56    import org.apache.commons.lang3.StringUtils;
57    import org.apache.velocity.VelocityContext;
58    import org.apache.velocity.app.Velocity;
59    import org.apache.velocity.context.Context;
60    import org.slf4j.Logger;
61    import org.slf4j.LoggerFactory;
62    import org.xwiki.localization.LocaleUtils;
63    import org.xwiki.rendering.syntax.Syntax;
64    import org.xwiki.velocity.VelocityManager;
65   
66    import com.xpn.xwiki.XWiki;
67    import com.xpn.xwiki.XWikiContext;
68    import com.xpn.xwiki.XWikiException;
69    import com.xpn.xwiki.api.Api;
70    import com.xpn.xwiki.api.Attachment;
71    import com.xpn.xwiki.api.Document;
72    import com.xpn.xwiki.doc.XWikiDocument;
73    import com.xpn.xwiki.objects.BaseObject;
74    import com.xpn.xwiki.objects.classes.BaseClass;
75    import com.xpn.xwiki.objects.classes.TextAreaClass;
76    import com.xpn.xwiki.plugin.XWikiDefaultPlugin;
77    import com.xpn.xwiki.plugin.XWikiPluginInterface;
78    import com.xpn.xwiki.util.Util;
79    import com.xpn.xwiki.web.ExternalServletURLFactory;
80    import com.xpn.xwiki.web.Utils;
81    import com.xpn.xwiki.web.XWikiURLFactory;
82   
83    /**
84    * Plugin that brings powerful mailing capabilities.
85    *
86    * @see MailSender
87    * @version $Id: 3eb59691f95d35befe46f9d00c472f5379167fe6 $
88    */
 
89    public class MailSenderPlugin extends XWikiDefaultPlugin
90    {
91    /** Logging helper object. */
92    private static final Logger LOGGER = LoggerFactory.getLogger(MailSenderPlugin.class);
93   
94    /**
95    * Since Java uses full Unicode Strings and email clients manage it we force email encoding to UTF-8. XWiki encoding
96    * must be used when working with storage or the container since they can be configured to use another encoding,
97    * this constraint does not apply here.
98    */
99    private static final String EMAIL_ENCODING = "UTF-8";
100   
101    /**
102    * Error code signaling that the mail template requested for
103    * {@link #sendMailFromTemplate(String, String, String, String, String, String, VelocityContext, XWikiContext)} was
104    * not found.
105    */
106    public static int ERROR_TEMPLATE_EMAIL_OBJECT_NOT_FOUND = -2;
107   
108    /** Generic error code for plugin failures. */
109    public static int ERROR = -1;
110   
111    /** The name of the Object Type holding mail templates. */
112    public static final String EMAIL_XWIKI_CLASS_NAME = "XWiki.Mail";
113   
114    /** The name of the plugin, used for accessing it from scripting environments. */
115    public static final String ID = "mailsender";
116   
117    protected static final String URL_SEPARATOR = "/";
118   
119    /** A pattern for determining if a line represents a SMTP header, conforming to RFC 2822. */
120    private static final Pattern SMTP_HEADER = Pattern.compile("^([\\x21-\\x7E&&[^\\x3A]]++):(.*+)$");
121   
122    /** The name of the header that specifies the subject of the mail. */
123    private static final String SUBJECT = "Subject";
124   
125    /** The name of the header that specifies the sender of the mail. */
126    private static final String FROM = "From";
127   
128    /**
129    * Default plugin constructor.
130    *
131    * @see XWikiDefaultPlugin#XWikiDefaultPlugin(String,String,com.xpn.xwiki.XWikiContext)
132    */
 
133  5 toggle public MailSenderPlugin(String name, String className, XWikiContext context)
134    {
135  5 super(name, className, context);
136    }
137   
 
138  1 toggle @Override
139    public void init(XWikiContext context)
140    {
141  1 try {
142  1 initMailClass(context);
143    } catch (Exception e) {
144  0 e.printStackTrace();
145    }
146    }
147   
 
148  0 toggle @Override
149    public void virtualInit(XWikiContext context)
150    {
151  0 try {
152  0 initMailClass(context);
153    } catch (Exception e) {
154  0 e.printStackTrace();
155    }
156    }
157   
 
158  2 toggle @Override
159    public String getName()
160    {
161  2 return ID;
162    }
163   
 
164  1 toggle @Override
165    public Api getPluginApi(XWikiPluginInterface plugin, XWikiContext context)
166    {
167  1 return new MailSenderPluginApi((MailSenderPlugin) plugin, context);
168    }
169   
170    /**
171    * Split comma separated list of emails
172    *
173    * @param email comma separated list of emails
174    * @return An array containing the emails
175    */
 
176  12 toggle public static String[] parseAddresses(String email)
177    {
178  12 if (email == null) {
179  7 return null;
180    }
181  5 email = email.trim();
182  5 String[] emails = email.split(",");
183  11 for (int i = 0; i < emails.length; i++) {
184  6 emails[i] = emails[i].trim();
185    }
186  5 return emails;
187    }
188   
189    /**
190    * Filters a list of emails : removes illegal addresses
191    *
192    * @param email List of emails
193    * @return An Array containing the correct adresses
194    */
 
195  12 toggle private static InternetAddress[] toInternetAddresses(String email) throws AddressException
196    {
197  12 String[] mails = parseAddresses(email);
198  12 if (mails == null) {
199  7 return null;
200    }
201   
202  5 InternetAddress[] address = new InternetAddress[mails.length];
203  11 for (int i = 0; i < mails.length; i++) {
204  6 address[i] = new InternetAddress(mails[i]);
205    }
206  5 return address;
207    }
208   
209    /**
210    * Creates the Mail XWiki Class
211    *
212    * @param context Context of the request
213    * @return the Mail XWiki Class
214    */
 
215  1 toggle protected BaseClass initMailClass(XWikiContext context) throws XWikiException
216    {
217  1 XWikiDocument doc;
218  1 XWiki xwiki = context.getWiki();
219  1 boolean needsUpdate = false;
220   
221  1 try {
222  1 doc = xwiki.getDocument(EMAIL_XWIKI_CLASS_NAME, context);
223    } catch (Exception e) {
224  0 doc = new XWikiDocument();
225  0 String[] spaceAndName = EMAIL_XWIKI_CLASS_NAME.split(".");
226  0 doc.setSpace(spaceAndName[0]);
227  0 doc.setName(spaceAndName[1]);
228  0 needsUpdate = true;
229    }
230   
231  1 BaseClass bclass = doc.getXClass();
232  1 bclass.setName(EMAIL_XWIKI_CLASS_NAME);
233  1 needsUpdate |= bclass.addTextField("subject", "Subject", 40);
234  1 needsUpdate |= bclass.addTextField("language", "Language", 5);
235  1 needsUpdate |= bclass.addTextAreaField("text", "Text", 80, 15, TextAreaClass.ContentType.PURE_TEXT);
236  1 needsUpdate |= bclass.addTextAreaField("html", "HTML", 80, 15, TextAreaClass.ContentType.PURE_TEXT);
237   
238  1 if (StringUtils.isBlank(doc.getCreator())) {
239  0 needsUpdate = true;
240  0 doc.setCreator("superadmin");
241    }
242  1 if (StringUtils.isBlank(doc.getAuthor())) {
243  0 needsUpdate = true;
244  0 doc.setAuthor(doc.getCreator());
245    }
246  1 if (StringUtils.isBlank(doc.getParent())) {
247  0 needsUpdate = true;
248  0 doc.setParent("XWiki.XWikiClasses");
249    }
250  1 if (StringUtils.isBlank(doc.getTitle())) {
251  0 needsUpdate = true;
252  0 doc.setTitle("XWiki Mail Class");
253    }
254  1 if (StringUtils.isBlank(doc.getContent()) || !Syntax.XWIKI_2_0.equals(doc.getSyntax())) {
255  1 needsUpdate = true;
256  1 doc.setContent("{{include reference=\"XWiki.ClassSheet\" /}}");
257  1 doc.setSyntax(Syntax.XWIKI_2_0);
258    }
259  1 if (!doc.isHidden()) {
260  0 needsUpdate = true;
261  0 doc.setHidden(true);
262    }
263   
264  1 if (needsUpdate) {
265  1 xwiki.saveDocument(doc, context);
266    }
267  1 return bclass;
268    }
269   
270    /**
271    * Creates a MIME message (message with binary content carrying capabilities) from an existing Mail
272    *
273    * @param mail The original Mail object
274    * @param session Mail session
275    * @return The MIME message
276    */
 
277  4 toggle private MimeMessage createMimeMessage(Mail mail, Session session, XWikiContext context) throws MessagingException,
278    XWikiException, IOException
279    {
280    // this will also check for email error
281  4 InternetAddress from = new InternetAddress(mail.getFrom());
282  4 String recipients = mail.getHeader("To");
283  4 if (StringUtils.isBlank(recipients)) {
284  4 recipients = mail.getTo();
285    } else {
286  0 recipients = mail.getTo() + "," + recipients;
287    }
288  4 InternetAddress[] to = toInternetAddresses(recipients);
289  4 recipients = mail.getHeader("Cc");
290  4 if (StringUtils.isBlank(recipients)) {
291  3 recipients = mail.getCc();
292    } else {
293  1 recipients = mail.getCc() + "," + recipients;
294    }
295  4 InternetAddress[] cc = toInternetAddresses(recipients);
296  4 recipients = mail.getHeader("Bcc");
297  4 if (StringUtils.isBlank(recipients)) {
298  4 recipients = mail.getBcc();
299    } else {
300  0 recipients = mail.getBcc() + "," + recipients;
301    }
302  4 InternetAddress[] bcc = toInternetAddresses(recipients);
303   
304  4 if ((to == null) && (cc == null) && (bcc == null)) {
305  0 LOGGER.info("No recipient -> skipping this email");
306  0 return null;
307    }
308   
309  4 MimeMessage message = new MimeMessage(session);
310  4 message.setSentDate(new Date());
311  4 message.setFrom(from);
312   
313  4 if (to != null) {
314  4 message.setRecipients(javax.mail.Message.RecipientType.TO, to);
315    }
316   
317  4 if (cc != null) {
318  1 message.setRecipients(javax.mail.Message.RecipientType.CC, cc);
319    }
320   
321  4 if (bcc != null) {
322  0 message.setRecipients(javax.mail.Message.RecipientType.BCC, bcc);
323    }
324   
325  4 message.setSubject(mail.getSubject(), EMAIL_ENCODING);
326   
327  4 for (Map.Entry<String, String> header : mail.getHeaders().entrySet()) {
328  3 message.setHeader(header.getKey(), header.getValue());
329    }
330   
331  4 if (mail.getHtmlPart() != null || mail.getAttachments() != null) {
332  1 Multipart multipart = createMimeMultipart(mail, context);
333  1 message.setContent(multipart);
334    } else {
335  3 message.setText(mail.getTextPart());
336    }
337   
338  4 message.setSentDate(new Date());
339  4 message.saveChanges();
340  4 return message;
341    }
342   
343    /**
344    * Add attachments to a multipart message
345    *
346    * @param attachment the attachment to create the body part for.
347    * @param context the XWiki context.
348    * @return the body part for the given attachment.
349    */
 
350  0 toggle public MimeBodyPart createAttachmentBodyPart(Attachment attachment, XWikiContext context) throws XWikiException,
351    IOException, MessagingException
352    {
353  0 String name = attachment.getFilename();
354  0 byte[] stream = attachment.getContent();
355  0 File temp = File.createTempFile("tmpfile", ".tmp");
356  0 FileOutputStream fos = new FileOutputStream(temp);
357  0 fos.write(stream);
358  0 fos.close();
359  0 DataSource source = new FileDataSource(temp);
360  0 MimeBodyPart part = new MimeBodyPart();
361  0 String mimeType = MimeTypesUtil.getMimeTypeFromFilename(name);
362   
363  0 part.setDataHandler(new DataHandler(source));
364  0 part.setHeader("Content-Type", mimeType);
365  0 part.setFileName(name);
366  0 part.setContentID("<" + name + ">");
367  0 part.setDisposition("inline");
368   
369  0 temp.deleteOnExit();
370   
371  0 return part;
372    }
373   
374    /**
375    * Creates a Multipart MIME Message (multiple content-types within the same message) from an existing mail
376    *
377    * @param mail The original Mail
378    * @return The Multipart MIME message
379    */
 
380  1 toggle public Multipart createMimeMultipart(Mail mail, XWikiContext context) throws MessagingException, XWikiException,
381    IOException
382    {
383  1 Multipart multipart;
384  1 List<Attachment> rawAttachments =
385  1 mail.getAttachments() != null ? mail.getAttachments() : new ArrayList<Attachment>();
386   
387  1 if (mail.getHtmlPart() == null && mail.getAttachments() != null) {
388  0 multipart = new MimeMultipart("mixed");
389   
390    // Create the text part of the email
391  0 BodyPart textPart = new MimeBodyPart();
392  0 textPart.setContent(mail.getTextPart(), "text/plain; charset=" + EMAIL_ENCODING);
393  0 multipart.addBodyPart(textPart);
394   
395    // Add attachments to the main multipart
396  0 for (Attachment attachment : rawAttachments) {
397  0 multipart.addBodyPart(createAttachmentBodyPart(attachment, context));
398    }
399    } else {
400  1 multipart = new MimeMultipart("mixed");
401  1 List<Attachment> attachments = new ArrayList<Attachment>();
402  1 List<Attachment> embeddedImages = new ArrayList<Attachment>();
403   
404    // Create the text part of the email
405  1 BodyPart textPart;
406  1 textPart = new MimeBodyPart();
407  1 textPart.setText(mail.getTextPart());
408   
409    // Create the HTML part of the email, define the html as a multipart/related in case there are images
410  1 Multipart htmlMultipart = new MimeMultipart("related");
411  1 BodyPart htmlPart = new MimeBodyPart();
412  1 htmlPart.setContent(mail.getHtmlPart(), "text/html; charset=" + EMAIL_ENCODING);
413  1 htmlPart.setHeader("Content-Disposition", "inline");
414  1 htmlPart.setHeader("Content-Transfer-Encoding", "quoted-printable");
415  1 htmlMultipart.addBodyPart(htmlPart);
416   
417    // Find images used with src="cid:" in the email HTML part
418  1 Pattern cidPattern =
419    Pattern.compile("src=('|\")cid:([^'\"]*)('|\")", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
420  1 Matcher matcher = cidPattern.matcher(mail.getHtmlPart());
421  1 List<String> foundEmbeddedImages = new ArrayList<String>();
422  1 while (matcher.find()) {
423  0 foundEmbeddedImages.add(matcher.group(2));
424    }
425   
426    // Loop over the attachments of the email, add images used from the HTML to the list of attachments to be
427    // embedded with the HTML part, add the other attachements to the list of attachments to be attached to the
428    // email.
429  1 for (Attachment attachment : rawAttachments) {
430  0 if (foundEmbeddedImages.contains(attachment.getFilename())) {
431  0 embeddedImages.add(attachment);
432    } else {
433  0 attachments.add(attachment);
434    }
435    }
436   
437    // Add the images to the HTML multipart (they should be hidden from the mail reader attachment list)
438  1 for (Attachment image : embeddedImages) {
439  0 htmlMultipart.addBodyPart(createAttachmentBodyPart(image, context));
440    }
441   
442    // Wrap the HTML and text parts in an alternative body part and add it to the main multipart
443  1 Multipart alternativePart = new MimeMultipart("alternative");
444  1 BodyPart alternativeMultipartWrapper = new MimeBodyPart();
445  1 BodyPart htmlMultipartWrapper = new MimeBodyPart();
446  1 alternativePart.addBodyPart(textPart);
447  1 htmlMultipartWrapper.setContent(htmlMultipart);
448  1 alternativePart.addBodyPart(htmlMultipartWrapper);
449  1 alternativeMultipartWrapper.setContent(alternativePart);
450  1 multipart.addBodyPart(alternativeMultipartWrapper);
451   
452    // Add attachments to the main multipart
453  1 for (Attachment attachment : attachments) {
454  0 multipart.addBodyPart(createAttachmentBodyPart(attachment, context));
455    }
456    }
457   
458  1 return multipart;
459    }
460   
461    /**
462    * Splits a raw mail into headers and the actual content, filling in a {@link Mail} object. This method should be
463    * compliant with RFC 2822 as much as possible. If the message accidentally starts with what looks like a mail
464    * header, then that line <strong>WILL</strong> be considered a header; no check on the semantics of the header is
465    * performed.
466    *
467    * @param rawMessage the raw content of the message that should be parsed
468    * @param toMail the {@code Mail} to create
469    * @throws IllegalArgumentException if the target Mail or the content to parse are null or the empty string
470    */
 
471  11 toggle protected void parseRawMessage(String rawMessage, Mail toMail)
472    {
473    // Sanity check
474  11 if (toMail == null) {
475  1 throw new IllegalArgumentException("The target Mail can't be null");
476  10 } else if (rawMessage == null) {
477  1 throw new IllegalArgumentException("rawMessage can't be null");
478  9 } else if (StringUtils.isBlank(rawMessage)) {
479  0 throw new IllegalArgumentException("rawMessage can't be empty");
480    }
481   
482  9 try {
483    // The message is read line by line
484  9 BufferedReader input = new BufferedReader(new StringReader(rawMessage));
485  9 String line;
486  9 StringWriter result = new StringWriter();
487  9 PrintWriter output = new PrintWriter(result);
488  9 boolean headersFound = false;
489   
490  9 line = input.readLine();
491    // Additional headers are at the start. Parse them and put them in the Mail object.
492    // Warning: no empty lines are allowed before the headers.
493  9 Matcher m = SMTP_HEADER.matcher(line);
494  21 while (line != null && m.matches()) {
495  12 String header = m.group(1);
496  12 String value = m.group(2);
497  12 line = input.readLine();
498  14 while (line != null && (line.startsWith(" ") || line.startsWith("\t"))) {
499  2 value += line;
500  2 line = input.readLine();
501    }
502  12 if (header.equals(SUBJECT)) {
503  6 toMail.setSubject(value);
504  6 } else if (header.equals(FROM)) {
505  3 toMail.setFrom(value);
506    } else {
507  3 toMail.setHeader(header, value);
508    }
509  12 if (line != null) {
510  12 m.reset(line);
511    }
512  12 headersFound = true;
513    }
514   
515    // There should be one empty line here, separating the body from the headers.
516  9 if (headersFound && line != null && StringUtils.isBlank(line)) {
517  7 line = input.readLine();
518    } else {
519  2 if (headersFound) {
520  0 LOGGER.warn("Mail body does not contain an empty line between the headers and the body.");
521    }
522    }
523   
524    // If no text exists after the headers, return
525  9 if (line == null) {
526  0 toMail.setTextPart("");
527  0 return;
528    }
529   
530  9 do {
531    // Mails always use \r\n as EOL
532  19 output.print(line + "\r\n");
533  ? } while ((line = input.readLine()) != null);
534   
535  9 toMail.setTextPart(result.toString());
536    } catch (IOException ioe) {
537    // Can't really happen here
538  0 LOGGER.error("Unexpected IO exception while preparing a mail", ioe);
539    }
540    }
541   
542    /**
543    * Evaluates a String property containing Velocity
544    *
545    * @param property The String property
546    * @param context Context of the request
547    * @return The evaluated String
548    */
 
549  0 toggle protected String evaluate(String property, Context context) throws Exception
550    {
551  0 String value = (String) context.get(property);
552  0 StringWriter stringWriter = new StringWriter();
553  0 Velocity.evaluate(context, stringWriter, property, value);
554  0 stringWriter.close();
555  0 return stringWriter.toString();
556    }
557   
558    /**
559    * Get a file name from its path
560    *
561    * @param path The file path
562    * @return The file name
563    */
 
564  0 toggle protected String getFileName(String path)
565    {
566  0 return path.substring(path.lastIndexOf(URL_SEPARATOR) + 1);
567    }
568   
569    /**
570    * Init a Mail Properties map (exs: smtp, host)
571    *
572    * @return The properties
573    */
 
574  4 toggle private Properties initProperties(MailConfiguration mailConfiguration)
575    {
576  4 Properties properties = new Properties();
577   
578    // Note: The full list of available properties that we can set is defined here:
579    // http://java.sun.com/products/javamail/javadocs/com/sun/mail/smtp/package-summary.html
580   
581  4 properties.put("mail.smtp.port", Integer.toString(mailConfiguration.getPort()));
582  4 properties.put("mail.smtp.host", mailConfiguration.getHost());
583  4 properties.put("mail.smtp.localhost", "localhost");
584  4 properties.put("mail.host", "localhost");
585  4 properties.put("mail.debug", "false");
586   
587  4 if (mailConfiguration.getFrom() != null) {
588  1 properties.put("mail.smtp.from", mailConfiguration.getFrom());
589    }
590   
591  4 if (mailConfiguration.usesAuthentication()) {
592  0 properties.put("mail.smtp.auth", "true");
593    }
594   
595  4 mailConfiguration.appendExtraPropertiesTo(properties, true);
596   
597  4 return properties;
598    }
599   
600    /**
601    * Prepares a Mail Velocity context
602    *
603    * @param fromAddr Mail from
604    * @param toAddr Mail to
605    * @param ccAddr Mail cc
606    * @param bccAddr Mail bcc
607    * @param vcontext The Velocity context to prepare
608    * @return The prepared context
609    */
 
610  1 toggle public VelocityContext prepareVelocityContext(String fromAddr, String toAddr, String ccAddr, String bccAddr,
611    VelocityContext vcontext, XWikiContext context)
612    {
613  1 if (vcontext == null) {
614    // Use the original velocity context as a starting point
615  0 VelocityManager velocityManager = Utils.getComponent(VelocityManager.class);
616  0 vcontext = new VelocityContext(velocityManager.getVelocityContext());
617    }
618   
619  1 vcontext.put("from.name", fromAddr);
620  1 vcontext.put("from.address", fromAddr);
621  1 vcontext.put("to.name", toAddr);
622  1 vcontext.put("to.address", toAddr);
623  1 vcontext.put("to.cc", ccAddr);
624  1 vcontext.put("to.bcc", bccAddr);
625  1 vcontext.put("bounce", fromAddr);
626   
627  1 return vcontext;
628    }
629   
630    /**
631    * Prepares a Mail Velocity context based on a map of parameters
632    *
633    * @param fromAddr Mail from
634    * @param toAddr Mail to
635    * @param ccAddr Mail cc
636    * @param bccAddr Mail bcc
637    * @param parameters variables to be passed to the velocity context
638    * @return The prepared context
639    */
 
640  1 toggle public VelocityContext prepareVelocityContext(String fromAddr, String toAddr, String ccAddr, String bccAddr,
641    Map<String, Object> parameters, XWikiContext context)
642    {
643  1 VelocityManager velocityManager = Utils.getComponent(VelocityManager.class);
644  1 VelocityContext vcontext = new VelocityContext(velocityManager.getVelocityContext());
645   
646  1 if (parameters != null) {
647  1 for (Map.Entry<String, Object> entry : parameters.entrySet()) {
648  0 vcontext.put(entry.getKey(), entry.getValue());
649    }
650    }
651   
652  1 return vcontext;
653    }
654   
655    /**
656    * Send a single Mail
657    *
658    * @param mailItem The Mail to send
659    * @return True if the the email has been sent
660    */
 
661  3 toggle public boolean sendMail(Mail mailItem, XWikiContext context) throws MessagingException,
662    UnsupportedEncodingException
663    {
664    // TODO: Fix the need to instantiate a new XWiki API object
665  3 com.xpn.xwiki.api.XWiki xwikiApi = new com.xpn.xwiki.api.XWiki(context.getWiki(), context);
666  3 return sendMail(mailItem, new MailConfiguration(xwikiApi), context);
667    }
668   
669    /**
670    * Send a single Mail
671    *
672    * @param mailItem The Mail to send
673    * @return True if the the email has been sent
674    */
 
675  4 toggle public boolean sendMail(Mail mailItem, MailConfiguration mailConfiguration, XWikiContext context)
676    throws MessagingException, UnsupportedEncodingException
677    {
678  4 ArrayList<Mail> mailList = new ArrayList<Mail>();
679  4 mailList.add(mailItem);
680  4 return sendMails(mailList, mailConfiguration, context);
681    }
682   
683    /**
684    * Send a Collection of Mails (multiple emails)
685    *
686    * @param emails Mail Collection
687    * @return True in any case (TODO ?)
688    */
 
689  0 toggle public boolean sendMails(Collection<Mail> emails, XWikiContext context) throws MessagingException,
690    UnsupportedEncodingException
691    {
692    // TODO: Fix the need to instantiate a new XWiki API object
693  0 com.xpn.xwiki.api.XWiki xwikiApi = new com.xpn.xwiki.api.XWiki(context.getWiki(), context);
694  0 return sendMails(emails, new MailConfiguration(xwikiApi), context);
695    }
696   
697    /**
698    * Send a Collection of Mails (multiple emails)
699    *
700    * @param emails Mail Collection
701    * @return True in any case (TODO ?)
702    */
 
703  4 toggle public boolean sendMails(Collection<Mail> emails, MailConfiguration mailConfiguration, XWikiContext context)
704    throws MessagingException, UnsupportedEncodingException
705    {
706  4 Session session = null;
707  4 Transport transport = null;
708  4 int emailCount = emails.size();
709  4 int count = 0;
710  4 int sendFailedCount = 0;
711  4 try {
712  8 for (Iterator<Mail> emailIt = emails.iterator(); emailIt.hasNext();) {
713  4 count++;
714   
715  4 Mail mail = emailIt.next();
716  4 LOGGER.info("Sending email: " + mail.toString());
717   
718  4 if ((transport == null) || (session == null)) {
719    // initialize JavaMail Session and Transport
720  4 Properties props = initProperties(mailConfiguration);
721  4 session = Session.getInstance(props, null);
722  4 transport = session.getTransport("smtp");
723  4 if (!mailConfiguration.usesAuthentication()) {
724    // no auth info - typical 127.0.0.1 open relay scenario
725  4 transport.connect();
726    } else {
727    // auth info present - typical with external smtp server
728  0 transport.connect(mailConfiguration.getSmtpUsername(), mailConfiguration.getSmtpPassword());
729    }
730    }
731   
732  4 try {
733  4 MimeMessage message = createMimeMessage(mail, session, context);
734  4 if (message == null) {
735  0 continue;
736    }
737   
738  4 transport.sendMessage(message, message.getAllRecipients());
739   
740    // close the connection every other 100 emails
741  4 if ((count % 100) == 0) {
742  0 try {
743  0 if (transport != null) {
744  0 transport.close();
745    }
746    } catch (MessagingException ex) {
747  0 LOGGER.error("MessagingException has occured.", ex);
748    }
749  0 transport = null;
750  0 session = null;
751    }
752    } catch (SendFailedException ex) {
753  0 sendFailedCount++;
754  0 LOGGER.error("SendFailedException has occured.", ex);
755  0 LOGGER.error("Detailed email information" + mail.toString());
756  0 if (emailCount == 1) {
757  0 throw ex;
758    }
759  0 if ((emailCount != 1) && (sendFailedCount > 10)) {
760  0 throw ex;
761    }
762    } catch (MessagingException mex) {
763  0 LOGGER.error("MessagingException has occured.", mex);
764  0 LOGGER.error("Detailed email information" + mail.toString());
765  0 if (emailCount == 1) {
766  0 throw mex;
767    }
768    } catch (XWikiException e) {
769  0 LOGGER.error("XWikiException has occured.", e);
770    } catch (IOException e) {
771  0 LOGGER.error("IOException has occured.", e);
772    }
773    }
774    } finally {
775  4 try {
776  4 if (transport != null) {
777  4 transport.close();
778    }
779    } catch (MessagingException ex) {
780  0 LOGGER.error("MessagingException has occured.", ex);
781    }
782   
783  4 LOGGER.info("sendEmails: Email count = " + emailCount + " sent count = " + count);
784    }
785  4 return true;
786    }
787   
788    /**
789    * Uses an XWiki document to build the message subject and context, based on variables stored in the
790    * VelocityContext. Sends the email.
791    *
792    * @param templateDocFullName Full name of the template to be used (example: XWiki.MyEmailTemplate). The template
793    * needs to have an XWiki.Email object attached
794    * @param from Email sender
795    * @param to Email recipient
796    * @param cc Email Carbon Copy
797    * @param bcc Email Hidden Carbon Copy
798    * @param language Language of the email
799    * @param vcontext Velocity context passed to the velocity renderer
800    * @return True if the email has been sent
801    */
 
802  1 toggle public int sendMailFromTemplate(String templateDocFullName, String from, String to, String cc, String bcc,
803    String language, VelocityContext vcontext, XWikiContext context) throws XWikiException
804    {
805  1 XWikiURLFactory originalURLFactory = context.getURLFactory();
806  1 Locale originalLocale = context.getLocale();
807  1 try {
808  1 context.setURLFactory(new ExternalServletURLFactory(context));
809  1 context.setLocale(LocaleUtils.toLocale(language));
810  1 VelocityContext updatedVelocityContext = prepareVelocityContext(from, to, cc, bcc, vcontext, context);
811  1 XWiki xwiki = context.getWiki();
812  1 XWikiDocument doc = xwiki.getDocument(templateDocFullName, context);
813  1 Document docApi = new Document(doc, context);
814   
815  1 BaseObject obj = doc.getObject(EMAIL_XWIKI_CLASS_NAME, "language", language);
816  1 if (obj == null) {
817  0 obj = doc.getObject(EMAIL_XWIKI_CLASS_NAME, "language", "en");
818    }
819  1 if (obj == null) {
820  0 LOGGER.error("No mail object found in the document " + templateDocFullName);
821  0 return ERROR_TEMPLATE_EMAIL_OBJECT_NOT_FOUND;
822    }
823  1 String subjectContent = obj.getStringValue("subject");
824  1 String txtContent = obj.getStringValue("text");
825  1 String htmlContent = obj.getStringValue("html");
826   
827  1 String subject = evaluate(subjectContent, templateDocFullName, updatedVelocityContext, context);
828  1 String msg = evaluate(txtContent, templateDocFullName, updatedVelocityContext, context);
829  1 String html = evaluate(htmlContent, templateDocFullName, updatedVelocityContext, context);
830   
831  1 Mail mail = new Mail();
832  1 mail.setFrom((String) updatedVelocityContext.get("from.address"));
833  1 mail.setTo((String) updatedVelocityContext.get("to.address"));
834  1 mail.setCc((String) updatedVelocityContext.get("to.cc"));
835  1 mail.setBcc((String) updatedVelocityContext.get("to.bcc"));
836  1 mail.setSubject(subject);
837  1 mail.setTextPart(msg);
838  1 mail.setHtmlPart(html);
839  1 mail.setAttachments(docApi.getAttachmentList());
840   
841  1 try {
842  1 sendMail(mail, context);
843  1 return 0;
844    } catch (Exception e) {
845  0 LOGGER.error("sendEmailFromTemplate: " + templateDocFullName + " vcontext: " + updatedVelocityContext, e);
846  0 return ERROR;
847    }
848    } finally {
849  1 context.setURLFactory(originalURLFactory);
850  1 context.setLocale(originalLocale);
851    }
852    }
853   
 
854  3 toggle private String evaluate(String content, String name, VelocityContext vcontext, XWikiContext context)
855    {
856  3 StringWriter writer = new StringWriter();
857  3 try {
858  3 VelocityManager velocityManager = Utils.getComponent(VelocityManager.class);
859  3 velocityManager.getVelocityEngine().evaluate(vcontext, writer, name, content);
860  3 return writer.toString();
861    } catch (Exception e) {
862  0 LOGGER.error("Error while parsing velocity template namespace [{}]", name, e);
863  0 Object[] args = { name };
864  0 XWikiException xe =
865    new XWikiException(XWikiException.MODULE_XWIKI_RENDERING,
866    XWikiException.ERROR_XWIKI_RENDERING_VELOCITY_EXCEPTION, "Error while parsing velocity page {0}",
867    e, args);
868  0 return Util.getHTMLExceptionMessage(xe, context);
869    }
870    }
871   
872    /**
873    * Uses an XWiki document to build the message subject and context, based on variables stored in a map.
874    * Sends the email.
875    *
876    * @param templateDocFullName Full name of the template to be used (example: XWiki.MyEmailTemplate). The template
877    * needs to have an XWiki.Email object attached
878    * @param from Email sender
879    * @param to Email recipient
880    * @param cc Email Carbon Copy
881    * @param bcc Email Hidden Carbon Copy
882    * @param language Language of the email
883    * @param parameters variables to be passed to the velocity context
884    * @return True if the email has been sent
885    */
 
886  1 toggle public int sendMailFromTemplate(String templateDocFullName, String from, String to, String cc, String bcc,
887    String language, Map<String, Object> parameters, XWikiContext context) throws XWikiException
888    {
889  1 VelocityContext vcontext = prepareVelocityContext(from, to, cc, bcc, parameters, context);
890  1 return sendMailFromTemplate(templateDocFullName, from, to, cc, bcc, language, vcontext, context);
891    }
892    }