001: /* ====================================================================
002: * The Jcorporate Apache Style Software License, Version 1.2 05-07-2002
003: *
004: * Copyright (c) 1995-2002 Jcorporate Ltd. All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions
008: * are met:
009: *
010: * 1. Redistributions of source code must retain the above copyright
011: * notice, this list of conditions and the following disclaimer.
012: *
013: * 2. Redistributions in binary form must reproduce the above copyright
014: * notice, this list of conditions and the following disclaimer in
015: * the documentation and/or other materials provided with the
016: * distribution.
017: *
018: * 3. The end-user documentation included with the redistribution,
019: * if any, must include the following acknowledgment:
020: * "This product includes software developed by Jcorporate Ltd.
021: * (http://www.jcorporate.com/)."
022: * Alternately, this acknowledgment may appear in the software itself,
023: * if and wherever such third-party acknowledgments normally appear.
024: *
025: * 4. "Jcorporate" and product names such as "Expresso" must
026: * not be used to endorse or promote products derived from this
027: * software without prior written permission. For written permission,
028: * please contact info@jcorporate.com.
029: *
030: * 5. Products derived from this software may not be called "Expresso",
031: * or other Jcorporate product names; nor may "Expresso" or other
032: * Jcorporate product names appear in their name, without prior
033: * written permission of Jcorporate Ltd.
034: *
035: * 6. No product derived from this software may compete in the same
036: * market space, i.e. framework, without prior written permission
037: * of Jcorporate Ltd. For written permission, please contact
038: * partners@jcorporate.com.
039: *
040: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
041: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
042: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
043: * DISCLAIMED. IN NO EVENT SHALL JCORPORATE LTD OR ITS CONTRIBUTORS
044: * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
045: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
046: * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
047: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
048: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
049: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
050: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
051: * SUCH DAMAGE.
052: * ====================================================================
053: *
054: * This software consists of voluntary contributions made by many
055: * individuals on behalf of the Jcorporate Ltd. Contributions back
056: * to the project(s) are encouraged when you make modifications.
057: * Please send them to support@jcorporate.com. For more information
058: * on Jcorporate Ltd. and its products, please see
059: * <http://www.jcorporate.com/>.
060: *
061: * Portions of this software are based upon other open source
062: * products and are subject to their respective licenses.
063: */
064:
065: /**
066: * EMailSender.java
067: *
068: * Copyright 2001 Jcorporate Ltd.
069: */package com.jcorporate.expresso.core.misc;
070:
071: import com.jcorporate.expresso.core.db.DBException;
072: import com.jcorporate.expresso.services.dbobj.Setup;
073: import org.apache.log4j.Logger;
074:
075: import javax.activation.DataHandler;
076: import javax.activation.FileDataSource;
077: import javax.mail.Message;
078: import javax.mail.Session;
079: import javax.mail.Transport;
080: import javax.mail.internet.AddressException;
081: import javax.mail.internet.InternetAddress;
082: import javax.mail.internet.MimeBodyPart;
083: import javax.mail.internet.MimeMessage;
084: import javax.mail.internet.MimeMultipart;
085: import java.util.ArrayList;
086: import java.util.Date;
087: import java.util.Iterator;
088: import java.util.List;
089: import java.util.Properties;
090: import java.util.Vector;
091:
092: /**
093: * EMailSender is a class that hides the details of sending email through an SMTP server
094: * using the JavaMail API and the Java Activation framework (JAF).
095: * This class logs using the log4j category "expresso.core.misc.EMailSender"
096: * (info, error, debug)
097: * This class uses a DB-context property, mailDebug=Y to dump SMTP debug on the console
098: * <p/>
099: * default number of retries = 0;
100: * <p/>
101: * Creation date: (2/6/01 2:09:18 PM)
102: *
103: * @author Shash Chatterjee
104: */
105: public class EMailSender {
106: private static final String this Class = EMailSender.class.getName();
107: private static transient final Logger log = Logger
108: .getLogger(EMailSender.class);
109:
110: protected String to = null;
111: protected String from = null;
112: protected String personal = null;
113: protected String host = null;
114: protected String user = null;
115: protected String password = null;
116: protected String subject = null;
117: protected String text = null;
118: protected String dbName = null;
119: protected List attachments = null;
120: protected List dataSourceAttachments = null;
121:
122: protected boolean htmlFormat = false; // add boolean property for html format emails
123:
124: private int maxConnectRetries = 0;
125:
126: private long connectRetryDelay = 10000; // 10 sec
127:
128: /**
129: * EMailSender constructor
130: */
131: public EMailSender() {
132: super ();
133: } /* EMailSender() */
134:
135: /**
136: * @return
137: */
138: private String messageDetails() {
139: int aCount = 0;
140: int dsACount = 0;
141:
142: if (attachments != null) {
143: aCount = attachments.size();
144: }
145: if (attachments != null) {
146: dsACount = dataSourceAttachments.size();
147: }
148:
149: return ("Message Details:\n" + "To '" + StringUtil.notNull(to)
150: + "'\n" + "From '" + StringUtil.notNull(from) + "'\n"
151: + "Via host '" + StringUtil.notNull(host) + "'\n"
152: + "User '" + StringUtil.notNull(user) + "'\n"
153: + "Subject '" + StringUtil.notNull(subject) + "'\n"
154: + "Message '" + StringUtil.notNull(text) + "'\n"
155: + "Db/Context '" + StringUtil.notNull(dbName) + "'\n"
156: + "With " + aCount + " file attachments and "
157: + dsACount + " data source attachments.");
158: } /* messageDetails() */
159:
160: /**
161: * This method adds an attachment to the email to be sent. Multiple calls can
162: * be made to this method to add multiple attachments
163: *
164: * @param name java.lang.String The fully-qualified name of a file to send as
165: * attachment.
166: */
167: public void addFileAttachment(String name) {
168:
169: // Lazy instantiation of the list of filenames, if needed
170: if (attachments == null) {
171: attachments = new ArrayList(4);
172: }
173:
174: // Add the attachment to the list of attachments
175: attachments.add(name);
176: } /* addFileAttachments(String) */
177:
178: /**
179: * This method provides a Vector of String names of files to be sent as attachments
180: *
181: * @param fileNames java.util.Vector Vector of fully-qualified file-names (String)
182: */
183: public void addFileAttachments(Vector fileNames) {
184:
185: // Lazy instantiation of the list of filenames, if needed
186: if (attachments == null) {
187: attachments = new ArrayList(4);
188: }
189: // Loop through each file-name, adding it to the list of files to send
190: for (Iterator itor = fileNames.iterator(); itor.hasNext();) {
191: attachments.add(itor.next());
192: }
193: } /* addFileAttachments(Vector) */
194:
195: /**
196: * This method adds an attachment to the email to be sent. Multiple calls can
197: * be made to this method to add multiple attachments
198: *
199: * @param dataSource byte array data source object that represents
200: * an virtual attachment with a known content type (MIME type).
201: */
202: public void addDataSourceAttachment(ByteArrayDataSource dataSource) {
203:
204: // Lazy instantiation of the list of filenames, if needed
205: if (dataSourceAttachments == null) {
206: dataSourceAttachments = new ArrayList(4);
207: }
208:
209: // Add the attachment to the list of attachments
210: dataSourceAttachments.add(dataSource);
211: }
212:
213: /**
214: * This method accepts a list of byte array data source collection
215: * to be sent as attachments.
216: *
217: * @param dataSourceList a list of collection of byte array data
218: * source objects that represents an virtual attachment with a
219: * known content type (MIME type).
220: */
221: public void addDataSourceAttachments(List dataSourceList) {
222:
223: // Lazy instantiation of the list of filenames, if needed
224: if (attachments == null) {
225: attachments = new ArrayList(4);
226: }
227:
228: // Loop through each file-name, adding it to the list of files to send
229: for (Iterator itor = dataSourceList.iterator(); itor.hasNext();) {
230: ByteArrayDataSource dataSource = (ByteArrayDataSource) itor
231: .next();
232: dataSourceAttachments.add(dataSource);
233: }
234: }
235:
236: /**
237: * This method returns the name of the DB context, either preset by the caller,
238: * or "default" if not set.
239: *
240: * @return the data context
241: */
242: public String getDBName() {
243: String db = dbName;
244:
245: if ((db == null) || (db.trim().equals(""))) {
246: db = "default";
247: }
248:
249: dbName = db;
250:
251: return db;
252: } /* getDBName() */
253:
254: /**
255: * This method returns the email address to be shown in the "From" header of the
256: * email. It will return the address set by the setFromAddress() method. If null,
257: * it will retrieve the from address from the Setup configuration.
258: *
259: * @return internet from address
260: * @throws Exception If a value for MAILFrom is not provided in Setup
261: */
262: private String getFromAddress() throws Exception {
263: String addr = from;
264:
265: try {
266: if (addr == null) {
267: addr = Setup.getValueRequired(getDBName(), "MAILFrom");
268: }
269: } catch (DBException dex) {
270: log.error("DB exception getting MAILFrom:"
271: + messageDetails(), dex);
272: throw dex;
273: }
274:
275: from = addr;
276:
277: return addr;
278: } /* getFromAddress() */
279:
280: /**
281: * This method returns the name of the SMTP server to use to send email.
282: * First it returns the value contained in an instance variable. If null,
283: * the val;ue is retrieved from the setting of the MAILServer Setup parameter.
284: *
285: * @return the SMTP host
286: * @throws Exception If MAILServer parameter is not provided in Setup
287: */
288: private String getSMTPHost() throws Exception {
289: String this Method = this Class + "getSMTPHost()";
290: String myHost = StringUtil.notNull(host);
291:
292: try {
293: if (myHost.equals("")) {
294: myHost = Setup.getValueRequired(getDBName(),
295: "MAILServer");
296: }
297: } catch (DBException dex) {
298: log.error("DB exception getting MAILServer:"
299: + messageDetails(), dex);
300: throw new Exception(this Method
301: + ":DB exception getting MAILServer:"
302: + dex.getMessage());
303: }
304:
305: host = myHost;
306:
307: return myHost;
308: } /* getSMTPHost() */
309:
310: /**
311: * This method returns the SMTP password to use for an authenticated SMTP session
312: * First it returns the value contained in an instance variable. If null,
313: * the value is retrieved from the setting of the MAILPassword Setup parameter.
314: *
315: * @return SMTP password
316: */
317: private String getSMTPPassword() {
318: String myPassword = password;
319:
320: try {
321: if (myPassword == null) {
322: myPassword = Setup.getValueRequired(getDBName(),
323: "MAILPassword");
324: }
325: } catch (DBException dex) {
326: myPassword = null;
327: }
328:
329: password = myPassword;
330:
331: return myPassword;
332: } /* getSMTPPassword() */
333:
334: /**
335: * This method returns the SMTP username to use for an authenticated SMTP session
336: * First it returns the value contained in an instance variable. If null,
337: * the value is retrieved from the setting of the MAILUserName Setup parameter.
338: *
339: * @return SMTP username
340: */
341: private String getSMTPUser() {
342: String myUser = StringUtil.notNull(user);
343:
344: try {
345: if (myUser.equals("")) {
346: myUser = Setup.getValueRequired(getDBName(),
347: "MAILUserName");
348: }
349: } catch (DBException dex) {
350: myUser = null;
351: }
352:
353: user = myUser;
354:
355: return myUser;
356: } /* getSMTPUser() */
357:
358: /**
359: * This method does all the dirty work of actually sending the email.
360: *
361: * @throws Exception if the internet mailing service fails
362: */
363: public void send() throws Exception {
364: // The From header value
365: String myFrom = getFromAddress();
366:
367: // The SMTP server name
368: String myHost = getSMTPHost();
369:
370: // The SMTP user name - if null, authenticated session is not used
371: String myUser = getSMTPUser();
372:
373: // The SMTP password
374: String myPassword = getSMTPPassword();
375:
376: String myPersonal = getPersonal();
377:
378: // To use authenticated SMTP session, or not
379: boolean useAuth = false;
380: StringUtil.assertNotBlank(myHost,
381: "Host cannot be blank when sending email");
382:
383: if (log.isDebugEnabled()) {
384: log.debug("Sending email:" + messageDetails());
385: }
386: // Make sure we have a "@" in the email address...this filters addresses like "None" etc.
387: if (to.indexOf("@") == -1) {
388: log.warn("Invalid to address, does not contain \"@\" - "
389: + to);
390:
391: //Don't thorw exception .... simply log and return...
392: return;
393: }
394: // If SMTP username is null or empty, use a "plain" non-authenticated
395: // SMTP sessions
396: if (myUser != null && !myUser.trim().equals("")) {
397: useAuth = true;
398: }
399:
400: Session session = null;
401: Transport trans = null;
402: InternetAddress[] fromAddr = null;
403: InternetAddress[] toAddr = null;
404:
405: // Parse out the From address for name, email, etc.
406: try {
407: fromAddr = InternetAddress.parse(myFrom, false);
408: } catch (AddressException aex) {
409: log.error("Invalid from address - '" + from + "':"
410: + messageDetails(), aex);
411: throw aex;
412: }
413: // Parse out the To address for name, email etc.
414: try {
415: toAddr = InternetAddress.parse(to, false);
416: } catch (AddressException aex) {
417: log.error("Invalid to address - '" + to + "':"
418: + messageDetails(), aex);
419: throw aex;
420: }
421:
422: //***********************************************************************
423: // Create a session
424: //***********************************************************************
425: // Setup properties for the SMTP session
426: Properties props = new Properties();
427: props.put("mail.smtp.host", myHost); // The name of the SMTP server
428:
429: try {
430: if (useAuth) {
431:
432: // Must use an authenticated session
433: if (log.isDebugEnabled()) {
434: log.debug("Setting up secure email session, host="
435: + myHost + " user=" + myUser);
436: }
437:
438: props.put("mail.smtp.auth", "true");
439:
440: EMailAuthenticator auth = new EMailAuthenticator();
441:
442: // Force the user/password values, otherwise EMailAuthenticator will
443: // attempt to look them up
444: auth.setUser(myUser);
445: auth.setPassword(myPassword);
446:
447: // Create the session
448: session = Session.getInstance(props, auth);
449: } else {
450:
451: // Create a "normal" session
452: if (log.isDebugEnabled()) {
453: log.debug("Setting up normal email session, host="
454: + myHost);
455: }
456:
457: session = Session.getInstance(props, null);
458: }
459: // Prints debug output to the console if "mailDebug=Y" in DB context
460: // properties file
461: if (ConfigManager.getContext(getDBName()).mailDebug()) {
462: session.setDebug(true);
463: }
464: } catch (Exception e) {
465: log.error("Unable to create mail session:"
466: + messageDetails(), e);
467: throw e;
468: }
469:
470: //***********************************************************************
471: // Format the message and attachments
472: //***********************************************************************
473: // Create a container for the entire message
474: MimeMessage msg = null;
475:
476: try {
477:
478: // The entire container is a MIME messgae
479: msg = new MimeMessage(session);
480:
481: // set the Personal value
482: fromAddr[0].setPersonal(myPersonal);
483: // Setup headers
484: msg.setFrom(fromAddr[0]); // The From header
485: msg.setRecipients(Message.RecipientType.TO, toAddr); // The To header
486: msg.setSubject(subject); // The Subject header
487: msg.setSentDate(new Date()); // The Date header
488:
489: // A message contains a multi-part MIME container
490: // The multi-part MIME container contains multiple MIME body parts
491: MimeMultipart mp = new MimeMultipart();
492:
493: // The first body part is the text message - create and add it to the
494: // multi-part
495: MimeBodyPart mbp = new MimeBodyPart();
496: mbp.setText(text);
497: if (htmlFormat) {
498: mbp.setContent(text, "text/html"); //Set HTML format if desired
499: }
500:
501: mp.addBodyPart(mbp);
502:
503: // Iterate through each attachment name, creating a body part for each
504: // and attaching the
505: // body part to the multi-part
506: String oneFileName = null;
507:
508: if (attachments != null) {
509: // Take care of the filename attachments. We must
510: // create a FileDataSource for each filename in the
511: // list collection int order create MIME body parts.
512: //
513: for (Iterator itor = attachments.iterator(); itor
514: .hasNext();) {
515: oneFileName = (String) itor.next();
516:
517: MimeBodyPart mbp2 = new MimeBodyPart();
518: FileDataSource fds = new FileDataSource(oneFileName);
519: mbp2.setDataHandler(new DataHandler(fds));
520: mbp2.setFileName(fds.getName());
521: mp.addBodyPart(mbp2);
522: }
523: }
524:
525: if (dataSourceAttachments != null) {
526: // Take care of the byte array data source
527: // attachments. We only need to create the MIME body
528: // part from the data source.
529: for (Iterator itor = dataSourceAttachments.iterator(); itor
530: .hasNext();) {
531: ByteArrayDataSource bads = (ByteArrayDataSource) itor
532: .next();
533:
534: MimeBodyPart mbp3 = new MimeBodyPart();
535: mbp3.setDataHandler(new DataHandler(bads));
536: mbp3.setFileName(bads.getName());
537: mp.addBodyPart(mbp3);
538: }
539: }
540:
541: // Finally add the multi-part to the MIME message
542: msg.setContent(mp);
543: } catch (Exception e) {
544: log.error("Error in creating MIME message:"
545: + messageDetails(), e);
546: throw e;
547: }
548: //***********************************************************************
549: // Now send the message
550: //***********************************************************************
551: try {
552:
553: // Get the transport from the session
554: trans = session.getTransport(toAddr[0]);
555:
556: // Connect to the SMTP server using the SMTP transport
557: int connectRetries = 0;
558: do { // try once, then start counting retries up to max
559: try {
560: trans.connect();
561: } catch (Exception e) {
562: if (e.getMessage().equals("already connected")) {
563: break;
564: }
565: if (connectRetries >= maxConnectRetries) {
566: throw e;
567: } else {
568: log.debug("Retry (wait=" + connectRetryDelay
569: + "ms) on SMTP connect, count="
570: + connectRetries + ", error="
571: + e.getMessage() + ", detail="
572: + messageDetails());
573: Thread.sleep(connectRetryDelay);
574: }
575: }
576: } while (connectRetries++ < maxConnectRetries);
577:
578: // Actually send the message
579: trans.sendMessage(msg, toAddr);
580:
581: // Close the connection
582: trans.close();
583: } catch (Exception e) {
584: log.error("Error sending email:" + messageDetails(), e);
585: throw e;
586: }
587: } /* send() */
588:
589: /**
590: * This is a convinience method to wrap the send() method.
591: *
592: * @param to java.lang.String The address to send email to
593: * @param subject java.lang.String The subject of the message
594: * @param text java.lang.String The text of the message to send
595: * @throws Exception Propagated exception from the send() method
596: */
597: public void send(String to, String subject, String text)
598: throws Exception {
599: setToAddress(to);
600: setSubject(subject);
601: setText(text);
602: send();
603: } /* send(String, String, String) */
604:
605: /**
606: * This method sets the DB context name, used for looking up several Setup parameters
607: *
608: * @param newDBName String Name of DB context
609: */
610: public void setDBName(String newDBName) {
611: dbName = StringUtil.notNull(newDBName);
612:
613: if (dbName.equals("")) {
614: dbName = "default";
615: }
616: } /* setDBName(String) */
617:
618: /**
619: * This method sets the address to be used in the From header
620: *
621: * @param from java.lang.String The from address
622: */
623: public void setFromAddress(String from) {
624: this .from = from;
625: } /* setFromAddress(String) */
626:
627: /**
628: * Tells the EMail sender to send HTML mail only
629: */
630: public void setEmailHtmlFormat() {
631: // call this when EMailSender object is created
632: htmlFormat = true;
633: }
634:
635: /**
636: * This method sets the address to be used in the From header
637: *
638: * @param from java.lang.String The from address
639: */
640: public void setPersonal(String personal) {
641: this .personal = personal;
642: } /* setFromAddress(String) */
643:
644: /**
645: * This method sets the address to be used in the From header
646: *
647: * @param from java.lang.String The from address
648: */
649: public String getPersonal() {
650: if (personal == null) {
651: return new String("");
652: } else {
653: return this .personal;
654: }
655: } /* setFromAddress(String) */
656:
657: /**
658: * This method sets the name of the SMTP server to use
659: *
660: * @param host java.lang.String Name of SMTP server
661: */
662: public void setSMTPHost(String host) {
663: this .host = host;
664: } /* setSMTPHost(String) */
665:
666: /**
667: * This method sets the SMTP password to use for authentication
668: *
669: * @param password java.lang.String SMTP password to use for authentication
670: */
671: public void setSMTPPassword(String password) {
672: this .password = password;
673: } /* setSMTPPassword(String) */
674:
675: /**
676: * This method sets the username to use for SMTP authentication
677: *
678: * @param user java.lang.String Username for SMTP authentication
679: */
680: public void setSMTPUser(String user) {
681: this .user = user;
682: } /* setSMTPUser(String) */
683:
684: /**
685: * This method sets the value to use for the Subject header
686: *
687: * @param subject java.lang.String The subject header contents
688: */
689: public void setSubject(String subject) {
690: this .subject = subject;
691: } /* setSubject(String) */
692:
693: /**
694: * Thiis method sets the content string of the email message
695: *
696: * @param text java.lang.String Email message content
697: */
698: public void setText(String text) {
699: this .text = text;
700: } /* setText(String) */
701:
702: /**
703: * This method sets the address to be used in the To header
704: *
705: * @param to java.lang.String The address to use in To header
706: */
707: public void setToAddress(String to) {
708: this .to = to;
709: } /* setToAddress(String) */
710:
711: /**
712: * Sets the connectRetryDelay.
713: *
714: * @param connectRetryDelay The connectRetryDelay to set
715: */
716: public void setConnectRetryDelay(long connectRetryDelay) {
717: this .connectRetryDelay = connectRetryDelay;
718: }
719:
720: /**
721: * Sets the maxConnectRetries.
722: *
723: * @param maxConnectRetries The maxConnectRetries to set
724: */
725: public void setMaxConnectRetries(int maxConnectRetries) {
726: this .maxConnectRetries = maxConnectRetries;
727: }
728:
729: } /* EMailSender */
|