001: /**
002: * $RCSfile$
003: * $Revision: 6207 $
004: * $Date: 2006-11-21 17:36:11 -0800 (Tue, 21 Nov 2006) $
005: *
006: * Copyright (C) 2003-2005 Jive Software. All rights reserved.
007: *
008: * This software is published under the terms of the GNU Public License (GPL),
009: * a copy of which is included in this distribution.
010: */package org.jivesoftware.util;
011:
012: import java.security.Security;
013: import java.text.SimpleDateFormat;
014: import java.util.*;
015:
016: import javax.mail.Address;
017: import javax.mail.*;
018: import javax.mail.internet.*;
019:
020: /**
021: * A service to send email.<p>
022: *
023: * This class has a few factory methods you can use to return message objects
024: * or to add messages into a queue to be sent. Using these methods, you can
025: * send emails in the following couple of ways:<p>
026: * <pre>
027: * EmailService.sendMessage(
028: * "Joe Bloe", "jbloe@place.org",
029: * "Jane Doe", "jane@doe.com",
030: * "Hello...",
031: * "This is the body of the email...",
032: * null
033: * );
034: * </pre>
035: * or
036: * <pre>
037: * Message message = EmailService.createMimeMessage();
038: * // call setters on the message object
039: * // .
040: * // .
041: * // .
042: * emailService.sendMessage(message);
043: * </pre><p>
044: *
045: * This class is configured with a set of Jive properties:<ul>
046: * <li><tt>mail.smtp.host</tt> -- the host name of your mail server, i.e.
047: * mail.yourhost.com. The default value is "localhost".
048: * <li><tt>mail.smtp.port</tt> -- an optional property to change the smtp
049: * port used from the default of 25.
050: * <li><tt>mail.smtp.username</tt> -- an optional property to change the
051: * username used to connect to the smtp server. Default is no username.
052: * <li><tt>mail.smtp.password</tt> -- an optional property to change the
053: * password used to connect to the smtp server. Default is no password.
054: * <li><tt>mail.smtp.ssl</tt> -- an optional property to set whether to use
055: * SSL to connect to the smtp server or not. Default is false.
056: * <li><tt>mail.debugEnabled</tt> -- true if debug information should written out.
057: * Default is false.
058: * </ul>
059: */
060: public class EmailService {
061:
062: private static final String SSL_FACTORY = "org.jivesoftware.util.SimpleSSLSocketFactory";
063:
064: private static EmailService instance = new EmailService();
065:
066: public static EmailService getInstance() {
067: return instance;
068: }
069:
070: private String host;
071: private int port;
072: private String username;
073: private String password;
074: private boolean sslEnabled;
075: private boolean debugEnabled;
076:
077: private Session session = null;
078:
079: /**
080: * Constructs a new EmailService instance.
081: */
082: private EmailService() {
083: host = JiveGlobals.getProperty("mail.smtp.host", "localhost");
084: port = JiveGlobals.getIntProperty("mail.smtp.port", 25);
085: username = JiveGlobals.getProperty("mail.smtp.username");
086: password = JiveGlobals.getProperty("mail.smtp.password");
087: sslEnabled = JiveGlobals.getBooleanProperty("mail.smtp.ssl");
088: debugEnabled = JiveGlobals.getBooleanProperty("mail.debug");
089: }
090:
091: /**
092: * Factory method to return a blank JavaMail message. You should use the
093: * object returned and set desired message properties. When done, pass the
094: * object to the addMessage(Message) method.
095: *
096: * @return a new JavaMail message.
097: */
098: public MimeMessage createMimeMessage() {
099: if (session == null) {
100: createSession();
101: }
102: return new MimeMessage(session);
103: }
104:
105: /**
106: * Sends a JavaMail message. To create a message, use the
107: * {@link #createMimeMessage()} method.
108: *
109: * @param message the message to send.
110: */
111: public void sendMessage(MimeMessage message) {
112: if (message != null) {
113: sendMessages(Collections.singletonList(message));
114: } else {
115: Log.error("Cannot add null email message to queue.");
116: }
117: }
118:
119: /**
120: * Send a collection of messages. To create a message, use the
121: * {@link #createMimeMessage()} method.
122: *
123: * @param messages a collection of the messages to send.
124: */
125: public void sendMessages(Collection<MimeMessage> messages) {
126: // If there are no messages, do nothing.
127: if (messages.size() == 0) {
128: return;
129: }
130: TaskEngine.getInstance().submit(new EmailTask(messages));
131: }
132:
133: /**
134: * Sends a message, specifying all of its fields.<p>
135: *
136: * To have more advanced control over the message sent, use the
137: * {@link #sendMessage(MimeMessage)} method.<p>
138: *
139: * Both a plain text and html body can be specified. If one of the values is null,
140: * only the other body type is sent. If both body values are set, a multi-part
141: * message will be sent. If parts of the message are invalid (ie, the toEmail is null)
142: * the message won't be sent.
143: *
144: * @param toName the name of the recipient of this email.
145: * @param toEmail the email address of the recipient of this email.
146: * @param fromName the name of the sender of this email.
147: * @param fromEmail the email address of the sender of this email.
148: * @param subject the subject of the email.
149: * @param textBody plain text body of the email, which can be <tt>null</tt> if the
150: * html body is not null.
151: * @param htmlBody html body of the email, which can be <tt>null</tt> if the text body
152: * is not null.
153: */
154: public void sendMessage(String toName, String toEmail,
155: String fromName, String fromEmail, String subject,
156: String textBody, String htmlBody) {
157: // Check for errors in the given fields:
158: if (toEmail == null || fromEmail == null || subject == null
159: || (textBody == null && htmlBody == null)) {
160: Log
161: .error("Error sending email: Invalid fields: "
162: + ((toEmail == null) ? "toEmail " : "")
163: + ((fromEmail == null) ? "fromEmail " : "")
164: + ((subject == null) ? "subject " : "")
165: + ((textBody == null && htmlBody == null) ? "textBody or htmlBody "
166: : ""));
167: } else {
168: try {
169: String encoding = MimeUtility.mimeCharset("iso-8859-1");
170: MimeMessage message = createMimeMessage();
171: Address to;
172: Address from;
173:
174: if (toName != null) {
175: to = new InternetAddress(toEmail, toName, encoding);
176: } else {
177: to = new InternetAddress(toEmail, "", encoding);
178: }
179:
180: if (fromName != null) {
181: from = new InternetAddress(fromEmail, fromName,
182: encoding);
183: } else {
184: from = new InternetAddress(fromEmail, "", encoding);
185: }
186:
187: // Set the date of the message to be the current date
188: SimpleDateFormat format = new SimpleDateFormat(
189: "EEE, dd MMM yyyy HH:mm:ss Z",
190: java.util.Locale.US);
191: format.setTimeZone(JiveGlobals.getTimeZone());
192: message.setHeader("Date", format.format(new Date()));
193: message.setHeader("Content-Transfer-Encoding", "8bit");
194: message.setRecipient(Message.RecipientType.TO, to);
195: message.setFrom(from);
196: message.setSubject(StringUtils.replace(subject, "\n",
197: ""), encoding);
198: // Create HTML, plain-text, or combination message
199: if (textBody != null && htmlBody != null) {
200: MimeMultipart content = new MimeMultipart(
201: "alternative");
202: // Plain-text
203: MimeBodyPart text = new MimeBodyPart();
204: text.setText(textBody, encoding);
205: text.setDisposition(Part.INLINE);
206: content.addBodyPart(text);
207: // HTML
208: MimeBodyPart html = new MimeBodyPart();
209: html.setContent(htmlBody, "text/html");
210: html.setDisposition(Part.INLINE);
211: content.addBodyPart(html);
212: // Add multipart to message.
213: message.setContent(content);
214: message.setDisposition(Part.INLINE);
215: sendMessage(message);
216: } else if (textBody != null) {
217: MimeBodyPart bPart = new MimeBodyPart();
218: bPart.setText(textBody, encoding);
219: bPart.setDisposition(Part.INLINE);
220: MimeMultipart mPart = new MimeMultipart();
221: mPart.addBodyPart(bPart);
222: message.setContent(mPart);
223: message.setDisposition(Part.INLINE);
224: // Add the message to the send list
225: sendMessage(message);
226: } else if (htmlBody != null) {
227: MimeBodyPart bPart = new MimeBodyPart();
228: bPart.setContent(htmlBody, "text/html");
229: bPart.setDisposition(Part.INLINE);
230: MimeMultipart mPart = new MimeMultipart();
231: mPart.addBodyPart(bPart);
232: message.setContent(mPart);
233: message.setDisposition(Part.INLINE);
234: // Add the message to the send list
235: sendMessage(message);
236: }
237: } catch (Exception e) {
238: Log.error(e);
239: }
240: }
241: }
242:
243: /**
244: * Sends a collection of email messages. This method differs from
245: * {@link #sendMessages(Collection)} in that messages are sent
246: * before this method returns rather than queueing the messages to be sent later.
247: *
248: * @param messages the messages to send.
249: * @throws MessagingException if an error occurs.
250: */
251: public void sendMessagesImmediately(Collection<MimeMessage> messages)
252: throws MessagingException {
253: EmailTask task = new EmailTask(messages);
254: task.sendMessages();
255: }
256:
257: /**
258: * Returns the SMTP host (e.g. mail.example.com). The default value is "localhost".
259: *
260: * @return the SMTP host.
261: */
262: public String getHost() {
263: return host;
264: }
265:
266: /**
267: * Sets the SMTP host (e.g. mail.example.com). The default value is "localhost".
268: *
269: * @param host the SMTP host.
270: */
271: public void setHost(String host) {
272: this .host = host;
273: JiveGlobals.setProperty("mail.smtp.host", host);
274: session = null;
275: }
276:
277: /**
278: * Returns the port number used when connecting to the SMTP server. The default
279: * port is 25.
280: *
281: * @return the SMTP port.
282: */
283: public int getPort() {
284: return port;
285: }
286:
287: /**
288: * Sets the port number that will be used when connecting to the SMTP
289: * server. The default is 25, the standard SMTP port number.
290: *
291: * @param port the SMTP port number.
292: */
293: public void setPort(int port) {
294: if (port < 0) {
295: throw new IllegalArgumentException("Invalid port value: "
296: + port);
297: }
298: this .port = port;
299: JiveGlobals.setProperty("mail.smtp.port", Integer
300: .toString(port));
301: session = null;
302: }
303:
304: /**
305: * Returns the username used to connect to the SMTP server. If the username
306: * is <tt>null</tt>, no username will be used when connecting to the server.
307: *
308: * @return the username used to connect to the SMTP server, or <tt>null</tt> if
309: * there is no username.
310: */
311: public String getUsername() {
312: return username;
313: }
314:
315: /**
316: * Sets the username that will be used when connecting to the SMTP
317: * server. The default is <tt>null</tt>, or no username.
318: *
319: * @param username the SMTP username.
320: */
321: public void setUsername(String username) {
322: this .username = username;
323: if (username == null) {
324: JiveGlobals.deleteProperty("mail.smtp.username");
325: } else {
326: JiveGlobals.setProperty("mail.smtp.username", username);
327: }
328: session = null;
329: }
330:
331: /**
332: * Returns the password used to connect to the SMTP server. If the password
333: * is <tt>null</tt>, no password will be used when connecting to the server.
334: *
335: * @return the password used to connect to the SMTP server, or <tt>null</tt> if
336: * there is no password.
337: */
338: public String getPassword() {
339: return password;
340: }
341:
342: /**
343: * Sets the password that will be used when connecting to the SMTP
344: * server. The default is <tt>null</tt>, or no password.
345: *
346: * @param password the SMTP password.
347: */
348: public void setPassword(String password) {
349: this .password = password;
350: if (password == null) {
351: JiveGlobals.deleteProperty("mail.smtp.password");
352: } else {
353: JiveGlobals.setProperty("mail.smtp.password", password);
354: }
355: session = null;
356: }
357:
358: /**
359: * Returns true if SMTP debugging is enabled. Debug information is
360: * written to <tt>System.out</tt> by the underlying JavaMail provider.
361: *
362: * @return true if SMTP debugging is enabled.
363: */
364: public boolean isDebugEnabled() {
365: return debugEnabled;
366: }
367:
368: /**
369: * Enables or disables SMTP transport layer debugging. Debug information is
370: * written to <tt>System.out</tt> by the underlying JavaMail provider.
371: *
372: * @param debugEnabled true if SMTP debugging should be enabled.
373: */
374: public void setDebugEnabled(boolean debugEnabled) {
375: this .debugEnabled = debugEnabled;
376: JiveGlobals.setProperty("mail.debug", Boolean
377: .toString(debugEnabled));
378: session = null;
379: }
380:
381: /**
382: * Returns true if SSL is enabled for SMTP connections.
383: *
384: * @return true if SSL is enabled.
385: */
386: public boolean isSSLEnabled() {
387: return sslEnabled;
388: }
389:
390: /**
391: * Sets whether the SMTP connection is configured to use SSL or not.
392: * Typically, the port should be 465 when using SSL with SMTP.
393: *
394: * @param sslEnabled true if ssl should be enabled, false otherwise.
395: */
396: public void setSSLEnabled(boolean sslEnabled) {
397: this .sslEnabled = sslEnabled;
398: JiveGlobals.setProperty("mail.smtp.ssl", Boolean
399: .toString(sslEnabled));
400: session = null;
401: }
402:
403: /**
404: * Creates a Javamail session.
405: */
406: private synchronized void createSession() {
407: if (host == null) {
408: throw new IllegalArgumentException("Host cannot be null.");
409: }
410:
411: Properties mailProps = new Properties();
412: mailProps.setProperty("mail.smtp.host", host);
413: mailProps.setProperty("mail.smtp.port", String.valueOf(port));
414: // Allow messages with a mix of valid and invalid recipients to still be sent.
415: mailProps.setProperty("mail.smtp.sendpartial", "true");
416: mailProps.setProperty("mail.debug", String
417: .valueOf(debugEnabled));
418:
419: // Methology from an article on www.javaworld.com (Java Tip 115)
420: // We will attempt to failback to an insecure connection
421: // if the secure one cannot be made
422: if (sslEnabled) {
423: // Register with security provider.
424: Security.setProperty("ssl.SocketFactory.provider",
425: SSL_FACTORY);
426:
427: mailProps.setProperty("mail.smtp.socketFactory.class",
428: SSL_FACTORY);
429: mailProps.setProperty("mail.smtp.socketFactory.fallback",
430: "true");
431: }
432:
433: // If a username is defined, use SMTP authentication.
434: if (username != null) {
435: mailProps.put("mail.smtp.auth", "true");
436: }
437: session = Session.getInstance(mailProps, null);
438: }
439:
440: /**
441: * Task to send one or more emails via the SMTP server.
442: */
443: private class EmailTask implements Runnable {
444:
445: private Collection<MimeMessage> messages;
446:
447: public EmailTask(Collection<MimeMessage> messages) {
448: this .messages = messages;
449: }
450:
451: public void run() {
452: try {
453: sendMessages();
454: } catch (MessagingException me) {
455: Log.error(me);
456: }
457: }
458:
459: public void sendMessages() throws MessagingException {
460: Transport transport = null;
461: try {
462: URLName url = new URLName("smtp", host, port, "",
463: username, password);
464: if (session == null) {
465: createSession();
466: }
467: transport = new com.sun.mail.smtp.SMTPTransport(
468: session, url);
469: transport.connect(host, port, username, password);
470: for (MimeMessage message : messages) {
471: // Attempt to send message, but catch exceptions caused by invalid
472: // addresses so that other messages can continue to be sent.
473: try {
474: transport
475: .sendMessage(
476: message,
477: message
478: .getRecipients(MimeMessage.RecipientType.TO));
479: } catch (AddressException ae) {
480: Log.error(ae);
481: } catch (SendFailedException sfe) {
482: Log.error(sfe);
483: }
484: }
485: } finally {
486: if (transport != null) {
487: try {
488: transport.close();
489: } catch (MessagingException e) { /* ignore */
490: }
491: }
492: }
493: }
494: }
495: }
|