View Javadoc
1   /*
2    * This file is part of ***  M y C o R e  ***
3    * See http://www.mycore.de/ for details.
4    *
5    * MyCoRe is free software: you can redistribute it and/or modify
6    * it under the terms of the GNU General Public License as published by
7    * the Free Software Foundation, either version 3 of the License, or
8    * (at your option) any later version.
9    *
10   * MyCoRe 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
13   * GNU General Public License for more details.
14   *
15   * You should have received a copy of the GNU General Public License
16   * along with MyCoRe.  If not, see <http://www.gnu.org/licenses/>.
17   */
18  
19  package org.mycore.common;
20  
21  import java.io.IOException;
22  import java.net.URL;
23  import java.util.ArrayList;
24  import java.util.Collections;
25  import java.util.Date;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.Optional;
29  import java.util.Properties;
30  import java.util.stream.Collectors;
31  
32  import org.apache.logging.log4j.LogManager;
33  import org.apache.logging.log4j.Logger;
34  import org.jdom2.Document;
35  import org.jdom2.Element;
36  import org.jdom2.JDOMException;
37  import org.jdom2.output.Format;
38  import org.jdom2.output.XMLOutputter;
39  import org.jdom2.transform.JDOMSource;
40  import org.mycore.common.MCRMailer.EMail.MessagePart;
41  import org.mycore.common.config.MCRConfiguration2;
42  import org.mycore.common.config.MCRConfigurationException;
43  import org.mycore.common.content.MCRContent;
44  import org.mycore.common.content.MCRJAXBContent;
45  import org.mycore.common.content.MCRJDOMContent;
46  import org.mycore.common.content.transformer.MCRXSL2XMLTransformer;
47  import org.mycore.common.xsl.MCRParameterCollector;
48  import org.mycore.frontend.servlets.MCRServlet;
49  import org.mycore.frontend.servlets.MCRServletJob;
50  import org.xml.sax.SAXParseException;
51  
52  import jakarta.activation.DataHandler;
53  import jakarta.activation.DataSource;
54  import jakarta.activation.URLDataSource;
55  import jakarta.mail.Authenticator;
56  import jakarta.mail.Message;
57  import jakarta.mail.Multipart;
58  import jakarta.mail.PasswordAuthentication;
59  import jakarta.mail.Session;
60  import jakarta.mail.Transport;
61  import jakarta.mail.internet.InternetAddress;
62  import jakarta.mail.internet.MimeBodyPart;
63  import jakarta.mail.internet.MimeMessage;
64  import jakarta.mail.internet.MimeMultipart;
65  import jakarta.xml.bind.JAXBContext;
66  import jakarta.xml.bind.JAXBException;
67  import jakarta.xml.bind.Unmarshaller;
68  import jakarta.xml.bind.annotation.XmlAttribute;
69  import jakarta.xml.bind.annotation.XmlElement;
70  import jakarta.xml.bind.annotation.XmlEnum;
71  import jakarta.xml.bind.annotation.XmlEnumValue;
72  import jakarta.xml.bind.annotation.XmlRootElement;
73  import jakarta.xml.bind.annotation.XmlType;
74  import jakarta.xml.bind.annotation.XmlValue;
75  
76  /**
77   * This class provides methods to send emails from within a MyCoRe application.
78   * 
79   * @author Marc Schluepmann
80   * @author Frank L\u00FCtzenkirchen
81   * @author Werner Greßhoff
82   * @author Ren\u00E9 Adler (eagle)
83   * 
84   * @version $Revision$ $Date$
85   */
86  public class MCRMailer extends MCRServlet {
87  
88      private static final Logger LOGGER = LogManager.getLogger(MCRMailer.class);
89  
90      private static final String DELIMITER = "\n--------------------------------------\n";
91  
92      private static Session mailSession;
93  
94      protected static final String ENCODING;
95  
96      /** How often should MCRMailer try to send mail? */
97      protected static int numTries;
98  
99      private static final long serialVersionUID = 1L;
100 
101     @Override
102     protected void doGetPost(MCRServletJob job) throws Exception {
103         String goTo = job.getRequest().getParameter("goto");
104         String xsl = job.getRequest().getParameter("xsl");
105 
106         Document input = (Document) (job.getRequest().getAttribute("MCRXEditorSubmission"));
107         MCRMailer.sendMail(input, xsl);
108 
109         job.getResponse().sendRedirect(goTo);
110     }
111 
112     static {
113         ENCODING = MCRConfiguration2.getStringOrThrow("MCR.Mail.Encoding");
114 
115         Properties mailProperties = new Properties();
116 
117         try {
118             Authenticator auth = null;
119 
120             numTries = MCRConfiguration2.getOrThrow("MCR.Mail.NumTries", Integer::parseInt);
121             if (MCRConfiguration2.getString("MCR.Mail.User").isPresent()
122                 && MCRConfiguration2.getString("MCR.Mail.Password").isPresent()) {
123                 auth = new SMTPAuthenticator();
124                 mailProperties.setProperty("mail.smtp.auth", "true");
125             }
126             String starttsl = MCRConfiguration2.getString("MCR.Mail.STARTTLS").orElse("disabled");
127             if ("enabled".equals(starttsl)) {
128                 mailProperties.setProperty("mail.smtp.starttls.enabled", "true");
129             } else if ("required".equals(starttsl)) {
130                 mailProperties.setProperty("mail.smtp.starttls.enabled", "true");
131                 mailProperties.setProperty("mail.smtp.starttls.required", "true");
132             }
133             mailProperties.setProperty("mail.smtp.host", MCRConfiguration2.getStringOrThrow("MCR.Mail.Server"));
134             mailProperties.setProperty("mail.transport.protocol",
135                 MCRConfiguration2.getStringOrThrow("MCR.Mail.Protocol"));
136             mailProperties.setProperty("mail.smtp.port", MCRConfiguration2.getString("MCR.Mail.Port").orElse("25"));
137             mailSession = Session.getDefaultInstance(mailProperties, auth);
138             mailSession.setDebug(MCRConfiguration2.getOrThrow("MCR.Mail.Debug", Boolean::parseBoolean));
139         } catch (MCRConfigurationException mcrx) {
140             String msg = "Missing e-mail configuration data.";
141             LOGGER.fatal(msg, mcrx);
142         }
143     }
144 
145     /**
146      * This method sends a simple plaintext email with the given parameters.
147      * 
148      * @param sender
149      *            the sender of the email
150      * @param recipient
151      *            the recipient of the email
152      * @param subject
153      *            the subject of the email
154      * @param body
155      *            the textbody of the email
156      */
157     public static void send(String sender, String recipient, String subject, String body) {
158         LOGGER.debug("Called plaintext send method with single recipient.");
159 
160         ArrayList<String> recipients = new ArrayList<>();
161         recipients.add(recipient);
162         send(sender, null, recipients, null, subject, body, null);
163     }
164 
165     /**
166      * This method sends a simple plaintext email to more than one recipient. If
167      * flag BCC is true, the sender will also get the email as BCC recipient.
168      * 
169      * @param sender
170      *            the sender of the email
171      * @param recipients
172      *            the recipients of the email as a List of Strings
173      * @param subject
174      *            the subject of the email
175      * @param body
176      *            the textbody of the email
177      * @param bcc
178      *            if true, sender will also get a copy as cc recipient
179      */
180     public static void send(String sender, List<String> recipients, String subject, String body, boolean bcc) {
181         LOGGER.debug("Called plaintext send method with multiple recipients.");
182 
183         List<String> bccList = null;
184 
185         if (bcc) {
186             bccList = new ArrayList<>();
187             bccList.add(sender);
188         }
189 
190         send(sender, null, recipients, bccList, subject, body, null);
191     }
192 
193     /**
194      * This method sends a multipart email with the given parameters.
195      * 
196      * @param sender
197      *            the sender of the email
198      * @param recipient
199      *            the recipient of the email
200      * @param subject
201      *            the subject of the email
202      * @param parts
203      *            a List of URL strings which should be added as parts
204      * @param body
205      *            the textbody of the email
206      */
207     public static void send(String sender, String recipient, String subject, String body, List<String> parts) {
208         LOGGER.debug("Called multipart send method with single recipient.");
209 
210         ArrayList<String> recipients = new ArrayList<>();
211         recipients.add(recipient);
212         send(sender, null, recipients, null, subject, body, parts);
213     }
214 
215     /**
216      * This method sends a multipart email to more than one recipient. If flag
217      * BCC is true, the sender will also get the email as BCC recipient.
218      * 
219      * @param sender
220      *            the sender of the email
221      * @param recipients
222      *            the recipients of the email as a List of Strings
223      * @param subject
224      *            the subject of the email
225      * @param body
226      *            the textbody of the email
227      * @param parts
228      *            a List of URL strings which should be added as parts
229      * @param bcc
230      *            if true, sender will also get a copy as bcc recipient
231      */
232     public static void send(String sender, List<String> recipients, String subject, String body, List<String> parts,
233         boolean bcc) {
234         LOGGER.debug("Called multipart send method with multiple recipients.");
235 
236         List<String> bccList = null;
237 
238         if (bcc) {
239             bccList = new ArrayList<>();
240             bccList.add(sender);
241         }
242 
243         send(sender, null, recipients, bccList, subject, body, parts);
244     }
245 
246     /**
247      * Send email from a given XML document. See the sample mail below:
248      * <pre>
249      * &lt;email&gt;
250      *   &lt;from&gt;bingo@bongo.com&lt;/from&gt;
251      *   &lt;to&gt;jim.knopf@lummerland.de&lt;/to&gt;
252      *   &lt;bcc&gt;frau.waas@lummerland.de&lt;/bcc&gt;
253      *   &lt;subject&gt;Grüße aus der Stadt der Drachen&lt;/subject&gt;
254      *   &lt;body&gt;Es ist recht bewölkt. Alles Gute, Jim.&lt;/body&gt;
255      *   &lt;body type="html"&gt;Es ist recht bewölkt. Alles Gute, Jim.&lt;/body&gt;
256      *   &lt;part&gt;http://upload.wikimedia.org/wikipedia/de/f/f7/JimKnopf.jpg&lt;/part&gt;
257      * &lt;/email&gt;
258      * </pre>
259      * @param email the email as JDOM element.
260      */
261     public static void send(Element email) {
262         try {
263             send(email, false);
264         } catch (Exception e) {
265             LOGGER.error(e.getMessage());
266         }
267     }
268 
269     /**
270      * Send email from a given XML document. See the sample mail below:
271      * <pre>
272      * &lt;email&gt;
273      *   &lt;from&gt;bingo@bongo.com&lt;/from&gt;
274      *   &lt;to&gt;jim.knopf@lummerland.de&lt;/to&gt;
275      *   &lt;bcc&gt;frau.waas@lummerland.de&lt;/bcc&gt;
276      *   &lt;subject&gt;Grüße aus der Stadt der Drachen&lt;/subject&gt;
277      *   &lt;body&gt;Es ist recht bewölkt. Alles Gute, Jim.&lt;/body&gt;
278      *   &lt;body type="html"&gt;Es ist recht bewölkt. Alles Gute, Jim.&lt;/body&gt;
279      *   &lt;part&gt;http://upload.wikimedia.org/wikipedia/de/f/f7/JimKnopf.jpg&lt;/part&gt;
280      * &lt;/email&gt;
281      * </pre>
282      * @param email the email as JDOM element.
283      * @param allowException allow to throw exceptions if set to <code>true</code>
284      * @throws Exception 
285      */
286     public static void send(Element email, Boolean allowException) throws Exception {
287         EMail mail = EMail.parseXML(email);
288 
289         if (allowException) {
290             if (mail.to == null || mail.to.isEmpty()) {
291                 throw new MCRException("No receiver defined for mail\n" + mail + '\n');
292             }
293 
294             trySending(mail);
295         } else {
296             send(mail);
297         }
298     }
299 
300     /**
301      * Sends email. When sending email fails (for example, outgoing mail server
302      * is not responding), sending will be retried after five minutes. This is
303      * done up to 10 times.
304      * 
305      * 
306      * @param from
307      *            the sender of the email
308      * @param replyTo
309      *            the reply-to addresses as a List of Strings, may be null
310      * @param to
311      *            the recipients of the email as a List of Strings
312      * @param bcc
313      *            the bcc recipients of the email as a List of Strings, may be
314      *            null
315      * @param subject
316      *            the subject of the email
317      * @param body
318      *            the text of the email
319      * @param parts
320      *            a List of URL strings which should be added as parts, may be
321      *            null
322      */
323     public static void send(final String from, final List<String> replyTo, final List<String> to,
324         final List<String> bcc, final String subject, final String body, final List<String> parts) {
325         EMail mail = new EMail();
326 
327         mail.from = from;
328         mail.replyTo = replyTo;
329         mail.to = to;
330         mail.bcc = bcc;
331         mail.subject = subject;
332 
333         mail.msgParts = new ArrayList<>();
334         mail.msgParts.add(new MessagePart(body));
335 
336         mail.parts = parts;
337 
338         send(mail);
339     }
340 
341     /**
342      * Sends email. When sending email fails (for example, outgoing mail server
343      * is not responding), sending will be retried after five minutes. This is
344      * done up to 10 times.
345      * 
346      * @param mail the email
347      */
348     public static void send(EMail mail) {
349         if (mail.to == null || mail.to.isEmpty()) {
350             throw new MCRException("No receiver defined for mail\n" + mail + '\n');
351         }
352 
353         try {
354             if (numTries > 0) {
355                 trySending(mail);
356             }
357         } catch (Exception ex) {
358             LOGGER.info("Sending e-mail failed: ", ex);
359             if (numTries < 2) {
360                 return;
361             }
362 
363             Thread t = new Thread(() -> {
364                 for (int i = numTries - 1; i > 0; i--) {
365                     LOGGER.info("Retrying in 5 minutes...");
366                     try {
367                         Thread.sleep(300000); // wait 5 minutes
368                     } catch (InterruptedException ignored) {
369                     }
370 
371                     try {
372                         trySending(mail);
373                         LOGGER.info("Successfully resended e-mail.");
374                         break;
375                     } catch (Exception ex1) {
376                         LOGGER.info("Sending e-mail failed: ", ex1);
377                     }
378                 }
379             });
380             t.start(); // Try to resend mail in separate thread
381         }
382     }
383 
384     private static void trySending(EMail mail) throws Exception {
385         MimeMessage msg = new MimeMessage(mailSession);
386         msg.setFrom(EMail.buildAddress(mail.from));
387 
388         Optional<List<InternetAddress>> toList = EMail.buildAddressList(mail.to);
389         if (toList.isPresent()) {
390             msg.addRecipients(Message.RecipientType.TO, toList.get().toArray(new InternetAddress[toList.get().size()]));
391         }
392 
393         Optional<List<InternetAddress>> replyToList = EMail.buildAddressList(mail.replyTo);
394         if (replyToList.isPresent()) {
395             msg.setReplyTo((replyToList.get().toArray(new InternetAddress[replyToList.get().size()])));
396         }
397 
398         Optional<List<InternetAddress>> bccList = EMail.buildAddressList(mail.bcc);
399         if (bccList.isPresent()) {
400             msg.addRecipients(Message.RecipientType.BCC,
401                 bccList.get().toArray(new InternetAddress[bccList.get().size()]));
402         }
403 
404         msg.setSentDate(new Date());
405         msg.setSubject(mail.subject, ENCODING);
406 
407         if (mail.parts != null && !mail.parts.isEmpty() || mail.msgParts != null && mail.msgParts.size() > 1) {
408             Multipart multipart = new MimeMultipart();
409             // Create the message part
410             MimeBodyPart messagePart = new MimeBodyPart();
411 
412             if (mail.msgParts.size() > 1) {
413                 multipart = new MimeMultipart("mixed");
414                 MimeMultipart alternative = new MimeMultipart("alternative");
415 
416                 for (MessagePart m : mail.msgParts) {
417                     messagePart = new MimeBodyPart();
418                     messagePart.setText(m.message, ENCODING, m.type.value());
419                     alternative.addBodyPart(messagePart);
420                 }
421 
422                 messagePart = new MimeBodyPart();
423                 messagePart.setContent(alternative);
424                 multipart.addBodyPart(messagePart);
425             } else {
426                 Optional<MessagePart> plainMsg = mail.getTextMessage();
427                 if (plainMsg.isPresent()) {
428                     messagePart.setText(plainMsg.get().message, ENCODING);
429                     multipart.addBodyPart(messagePart);
430                 }
431             }
432 
433             if (mail.parts != null && !mail.parts.isEmpty()) {
434                 for (String part : mail.parts) {
435                     messagePart = new MimeBodyPart();
436 
437                     URL url = new URL(part);
438                     DataSource source = new URLDataSource(url);
439                     messagePart.setDataHandler(new DataHandler(source));
440 
441                     String fileName = url.getPath();
442                     if (fileName.contains("\\")) {
443                         fileName = fileName.substring(fileName.lastIndexOf("\\") + 1);
444                     } else if (fileName.contains("/")) {
445                         fileName = fileName.substring(fileName.lastIndexOf("/") + 1);
446                     }
447                     messagePart.setFileName(fileName);
448 
449                     multipart.addBodyPart(messagePart);
450                 }
451             }
452 
453             msg.setContent(multipart);
454         } else {
455             Optional<MessagePart> plainMsg = mail.getTextMessage();
456             if (plainMsg.isPresent()) {
457                 msg.setText(plainMsg.get().message, ENCODING);
458             }
459         }
460 
461         LOGGER.info("Sending e-mail to {}", mail.to);
462         Transport.send(msg);
463     }
464 
465     /**
466      * Generates e-mail from the given input document by transforming it with an xsl stylesheet,
467      * and sends the e-mail afterwards.
468      * 
469      * @param input the xml input document
470      * @param stylesheet the xsl stylesheet that will generate the e-mail, without the ending ".xsl" 
471      * @param parameters the optionally empty table of xsl parameters
472      * @return the generated e-mail
473      * 
474      * @see org.mycore.common.MCRMailer
475      */
476     public static Element sendMail(Document input, String stylesheet, Map<String, String> parameters) throws Exception {
477         LOGGER.info("Generating e-mail from {} using {}.xsl", input.getRootElement().getName(), stylesheet);
478         if (LOGGER.isDebugEnabled()) {
479             debug(input.getRootElement());
480         }
481 
482         Element eMail = transform(input, stylesheet, parameters).getRootElement();
483         if (LOGGER.isDebugEnabled()) {
484             debug(eMail);
485         }
486 
487         if (eMail.getChildren("to").isEmpty()) {
488             LOGGER.warn("Will not send e-mail, no 'to' address specified");
489         } else {
490             LOGGER.info("Sending e-mail to {}: {}", eMail.getChildText("to"), eMail.getChildText("subject"));
491             MCRMailer.send(eMail);
492         }
493 
494         return eMail;
495     }
496 
497     /**
498      * Generates e-mail from the given input document by transforming it with an xsl stylesheet,
499      * and sends the e-mail afterwards.
500      * 
501      * @param input the xml input document
502      * @param stylesheet the xsl stylesheet that will generate the e-mail, without the ending ".xsl" 
503      * @return the generated e-mail
504      * 
505      * @see org.mycore.common.MCRMailer
506      */
507     public static Element sendMail(Document input, String stylesheet) throws Exception {
508         return sendMail(input, stylesheet, Collections.emptyMap());
509     }
510 
511     /** 
512      * Transforms the given input element using xsl stylesheet.
513      * 
514      * @param input the input document to transform.
515      * @param stylesheet the name of the xsl stylesheet to use, without the ".xsl" ending.
516      * @param parameters the optionally empty table of xsl parameters
517      * @return the output document generated by the transformation process
518      */
519     private static Document transform(Document input, String stylesheet, Map<String, String> parameters)
520         throws Exception {
521         MCRJDOMContent source = new MCRJDOMContent(input);
522         MCRXSL2XMLTransformer transformer = MCRXSL2XMLTransformer.getInstance("xsl/" + stylesheet + ".xsl");
523         MCRParameterCollector parameterCollector = MCRParameterCollector.getInstanceFromUserSession();
524         parameterCollector.setParameters(parameters);
525         MCRContent result = transformer.transform(source, parameterCollector);
526         return result.asXML();
527     }
528 
529     /** Outputs xml to the LOGGER for debugging */
530     private static void debug(Element xml) {
531         XMLOutputter xout = new XMLOutputter(Format.getPrettyFormat());
532         LOGGER.debug(DELIMITER + "{}" + DELIMITER, xout.outputString(xml));
533     }
534 
535     @XmlRootElement(name = "email")
536     public static class EMail {
537 
538         private static final JAXBContext JAXB_CONTEXT = initContext();
539 
540         @XmlElement
541         public String from;
542 
543         @XmlElement
544         public List<String> replyTo;
545 
546         @XmlElement
547         public List<String> to;
548 
549         @XmlElement
550         public List<String> bcc;
551 
552         @XmlElement
553         public String subject;
554 
555         @XmlElement(name = "body")
556         public List<MessagePart> msgParts;
557 
558         @XmlElement(name = "part")
559         public List<String> parts;
560 
561         private static JAXBContext initContext() {
562             try {
563                 return JAXBContext.newInstance(EMail.class.getPackage().getName(), EMail.class.getClassLoader());
564             } catch (final JAXBException e) {
565                 throw new MCRException("Could not instantiate JAXBContext.", e);
566             }
567         }
568 
569         /**
570          * Parse a email from given {@link Element}.
571          * 
572          * @param xml the email
573          * @return the {@link EMail} object
574          */
575         public static EMail parseXML(final Element xml) {
576             try {
577                 final Unmarshaller unmarshaller = JAXB_CONTEXT.createUnmarshaller();
578                 return (EMail) unmarshaller.unmarshal(new JDOMSource(xml));
579             } catch (final JAXBException e) {
580                 throw new MCRException("Exception while transforming Element to EMail.", e);
581             }
582         }
583 
584         /**
585          * Builds email address from a string. The string may be a single email
586          * address or a combination of a personal name and address, like "John Doe"
587          * &lt;john@doe.com&gt;
588          * 
589          * @param s the email address string
590          * @return a {@link InternetAddress}
591          * @throws Exception throws AddressException or UnsupportedEncodingException
592          */
593         private static InternetAddress buildAddress(String s) throws Exception {
594             if (!s.endsWith(">")) {
595                 return new InternetAddress(s.trim());
596             }
597 
598             String name = s.substring(0, s.lastIndexOf("<")).trim();
599             String addr = s.substring(s.lastIndexOf("<") + 1, s.length() - 1).trim();
600 
601             if (name.startsWith("\"") && name.endsWith("\"")) {
602                 name = name.substring(1, name.length() - 1);
603             }
604 
605             return new InternetAddress(addr, name);
606         }
607 
608         /**
609          * Builds a list of email addresses from a string list.
610          * 
611          * @param addresses the list with email addresses
612          * @return a list of {@link InternetAddress}s
613          * @see MCRMailer.EMail#buildAddress(String)
614          */
615         private static Optional<List<InternetAddress>> buildAddressList(final List<String> addresses) {
616             return addresses != null ? Optional.ofNullable(addresses.stream().map(address -> {
617                 try {
618                     return buildAddress(address);
619                 } catch (Exception ex) {
620                     return null;
621                 }
622             }).collect(Collectors.toList())) : Optional.empty();
623         }
624 
625         /**
626          * Returns the text message part.
627          * 
628          * @return the text message part
629          */
630         public Optional<MessagePart> getTextMessage() {
631             return msgParts != null ? Optional.ofNullable(msgParts).get().stream()
632                 .filter(m -> m.type.equals(MessageType.TEXT)).findFirst() : Optional.empty();
633         }
634 
635         /**
636          * Returns the HTML message part.
637          * 
638          * @return the HTML message part
639          */
640         public Optional<MessagePart> getHTMLMessage() {
641             return msgParts != null ? Optional.ofNullable(msgParts).get().stream()
642                 .filter(m -> m.type.equals(MessageType.HTML)).findFirst() : Optional.empty();
643         }
644 
645         /**
646          * Returns the {@link EMail} as XML.
647          * 
648          * @return the XML
649          */
650         public Document toXML() {
651             final MCRJAXBContent<EMail> content = new MCRJAXBContent<>(JAXB_CONTEXT, this);
652             try {
653                 return content.asXML();
654             } catch (final SAXParseException | JDOMException | IOException e) {
655                 throw new MCRException("Exception while transforming EMail to JDOM document.", e);
656             }
657         }
658 
659         /* (non-Javadoc)
660          * @see java.lang.Object#toString()
661          */
662         @Override
663         public String toString() {
664             final int maxLen = 10;
665             StringBuilder builder = new StringBuilder();
666             builder.append("EMail [");
667             if (from != null) {
668                 builder.append("from=");
669                 builder.append(from);
670                 builder.append(", ");
671             }
672             if (replyTo != null) {
673                 builder.append("replyTo=");
674                 builder.append(replyTo.subList(0, Math.min(replyTo.size(), maxLen)));
675                 builder.append(", ");
676             }
677             if (to != null) {
678                 builder.append("to=");
679                 builder.append(to.subList(0, Math.min(to.size(), maxLen)));
680                 builder.append(", ");
681             }
682             if (bcc != null) {
683                 builder.append("bcc=");
684                 builder.append(bcc.subList(0, Math.min(bcc.size(), maxLen)));
685                 builder.append(", ");
686             }
687             if (subject != null) {
688                 builder.append("subject=");
689                 builder.append(subject);
690                 builder.append(", ");
691             }
692             if (msgParts != null) {
693                 builder.append("msgParts=");
694                 builder.append(msgParts.subList(0, Math.min(msgParts.size(), maxLen)));
695                 builder.append(", ");
696             }
697             if (parts != null) {
698                 builder.append("parts=");
699                 builder.append(parts.subList(0, Math.min(parts.size(), maxLen)));
700             }
701             builder.append("]");
702             return builder.toString();
703         }
704 
705         @XmlRootElement(name = "body")
706         public static class MessagePart {
707 
708             @XmlAttribute
709             public MessageType type = MessageType.TEXT;
710 
711             @XmlValue
712             public String message;
713 
714             MessagePart() {
715             }
716 
717             public MessagePart(final String message) {
718                 this.message = message;
719             }
720 
721             public MessagePart(final String message, final MessageType type) {
722                 this.message = message;
723                 this.type = type;
724             }
725 
726             /* (non-Javadoc)
727              * @see java.lang.Object#toString()
728              */
729             @Override
730             public String toString() {
731                 final int maxLen = 50;
732                 StringBuilder builder = new StringBuilder();
733                 builder.append("MessagePart [");
734                 if (type != null) {
735                     builder.append("type=");
736                     builder.append(type);
737                     builder.append(", ");
738                 }
739                 if (message != null) {
740                     builder.append("message=");
741                     builder.append(message, 0, Math.min(message.length(), maxLen));
742                 }
743                 builder.append("]");
744                 return builder.toString();
745             }
746         }
747 
748         @XmlType(name = "mcrmailer-messagetype")
749         @XmlEnum
750         public enum MessageType {
751             @XmlEnumValue("text")
752             TEXT("text"),
753 
754             @XmlEnumValue("html")
755             HTML("html");
756 
757             private final String value;
758 
759             MessageType(String v) {
760                 value = v;
761             }
762 
763             public String value() {
764                 return value;
765             }
766 
767             public static MessageType fromValue(String v) {
768                 for (MessageType t : MessageType.values()) {
769                     if (t.value.equals(v)) {
770                         return t;
771                     }
772                 }
773                 throw new IllegalArgumentException(v);
774             }
775         }
776     }
777 
778     private static class SMTPAuthenticator extends jakarta.mail.Authenticator {
779 
780         public PasswordAuthentication getPasswordAuthentication() {
781             return new PasswordAuthentication(MCRConfiguration2.getStringOrThrow("MCR.Mail.User"),
782                 MCRConfiguration2.getStringOrThrow("MCR.Mail.Password"));
783         }
784     }
785 }