KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > sape > carbon > services > email > SynchronousMailService


1 /*
2  * The contents of this file are subject to the Sapient Public License
3  * Version 1.0 (the "License"); you may not use this file except in compliance
4  * with the License. You may obtain a copy of the License at
5  * http://carbon.sf.net/License.html.
6  *
7  * Software distributed under the License is distributed on an "AS IS" basis,
8  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
9  * the specific language governing rights and limitations under the License.
10  *
11  * The Original Code is The Carbon Component Framework.
12  *
13  * The Initial Developer of the Original Code is Sapient Corporation
14  *
15  * Copyright (C) 2003 Sapient Corporation. All Rights Reserved.
16  */

17
18 package org.sape.carbon.services.email;
19
20
21 import java.io.UnsupportedEncodingException JavaDoc;
22 import java.net.MalformedURLException JavaDoc;
23 import java.net.URL JavaDoc;
24 import java.util.ArrayList JavaDoc;
25 import java.util.Collection JavaDoc;
26 import java.util.HashMap JavaDoc;
27 import java.util.Iterator JavaDoc;
28 import java.util.Map JavaDoc;
29 import java.util.Properties JavaDoc;
30
31 import javax.activation.DataHandler JavaDoc;
32 import javax.activation.DataSource JavaDoc;
33 import javax.activation.URLDataSource JavaDoc;
34 import javax.mail.BodyPart JavaDoc;
35 import javax.mail.Message JavaDoc;
36 import javax.mail.MessagingException JavaDoc;
37 import javax.mail.Multipart JavaDoc;
38 import javax.mail.Part JavaDoc;
39 import javax.mail.Session JavaDoc;
40 import javax.mail.Transport JavaDoc;
41 import javax.mail.internet.InternetAddress JavaDoc;
42 import javax.mail.internet.MimeBodyPart JavaDoc;
43 import javax.mail.internet.MimeMessage JavaDoc;
44 import javax.mail.internet.MimeMultipart JavaDoc;
45 import javax.mail.internet.MimeUtility JavaDoc;
46
47 import org.sape.carbon.core.component.ComponentConfiguration;
48 import org.sape.carbon.core.component.lifecycle.Configurable;
49 import org.sape.carbon.core.component.lifecycle.Startable;
50 import org.sape.carbon.core.config.InvalidConfigurationException;
51 import org.sape.carbon.core.exception.InvalidParameterException;
52 import org.sape.carbon.services.email.util.MailAttachment;
53 import org.sape.carbon.services.email.util.MailContentTypeEnum;
54
55 import org.apache.commons.logging.Log;
56 import org.apache.commons.logging.LogFactory;
57
58 /**
59  * <p>This is a synchronous implementation for sending emails over JavaMail API.
60  * </p>
61  * @since carbon 1.0
62  * @stereotype implementation
63  * @author Nitin Gulati, June 2002
64  * @version $Revision: 1.15 $($Author: dvoet $ / $Date: 2003/05/05 21:21:29 $)
65  * <br>Copyright 2002 Sapient
66  */

67 public class SynchronousMailService
68     implements MailService, Configurable, Startable {
69
70     /**
71      * Provides a handle to Apache-commons logger
72      */

73     private Log log = LogFactory.getLog(this.getClass());
74
75     /**
76      * <p>Holds the SMTP host name or IP address.</p>
77      */

78     private String JavaDoc smtpHost;
79
80
81     /**
82      *<p>A boolean property indicating whether or not the email service will
83      * create and keep open a connection to the SMTP host for its lifetime.
84      * If set to true, the connection will not be closed until the email
85      * service is shutdown. If set to false a new connection will be opened
86      * for each email sent.</p>
87      */

88     private boolean holdConnection;
89
90
91     /**
92      * <p>This indicates the number of retry attempts to be made with SMTP
93      * server to send an email.When trying to send an email the service may
94      * receive an exception back from JavaMail. This is usually the result
95      * of failure to connect to the email server. In this case the service
96      * will execute the following steps n (retry attempts) times.
97      * <ul>
98      * <li>Close the connection to the email server. </li>
99      * <li>Sleep for some time (configurable, default is half a second)</li>
100      * <li>Reopen the connection to the email server. </li>
101      * <li>Try to send the email again. </li>
102      * </ul></p>
103      */

104     private int retryAttempts;
105
106
107     /**
108      * <p>Holds the session object required to create the <code>MimeMessage
109      * </code>.</p>
110      */

111     private Session JavaDoc session;
112
113
114     /**
115      * <p>Holds the reference to the <code>Transport</code> obejct which
116      * does the actual job of sending the message.</p>
117      */

118     private Transport JavaDoc transport;
119
120
121     /**
122      * <p>Time in milliseconds waited between send attempts when failure
123      * occurs.</p>
124      */

125     private long sleepRetryTime;
126
127     /** Content type key for headers. */
128     protected static final String JavaDoc CONTENT_TYPE = "Content-Type";
129
130     /** An empty string. */
131     protected static final String JavaDoc BLANK_STRING = "";
132
133     /**
134      * @see MailService#sendMail(MailDataObject mailDataObject)
135      *
136      * @param mailDataObject the mail data object to send
137      * @exception MailFailureException Throws when the mail was not sent
138      * because of various reasons listed below :
139      * <ul>
140      * <li>Invalid configuration E.g wrong ip of smtp server.</li>
141      * <li>The path or URL of any of the attachment does not exist.</li>
142      * <li>The charset passed for encoding the message is not supported.</li>
143      * <li>Unable to connect to the smtp server due to network issues.</li>
144      * </ul>
145      */

146     public void sendMail(MailDataObject mailDataObject)
147         throws MailFailureException {
148
149         // Get all the parameters specified in mail data object.
150
Map JavaDoc to = mailDataObject.getToMap();
151         Map JavaDoc cc = mailDataObject.getCCMap();
152         Map JavaDoc bcc = mailDataObject.getBCCMap();
153         Map JavaDoc headers = mailDataObject.getHeaders();
154         String JavaDoc fromPersonal = mailDataObject.getFromName();
155         String JavaDoc fromEmail = mailDataObject.getFromEmail();
156         String JavaDoc subject = mailDataObject.getSubject();
157         String JavaDoc body = mailDataObject.getBody();
158         MailContentTypeEnum bodyType = mailDataObject.getBodyType();
159         String JavaDoc charset = mailDataObject.getCharset();
160         MailAttachment[] attachments = mailDataObject.getAttachments();
161
162         // Check the mandatory fields before attempting to create the
163
// Mime message.
164
Collection JavaDoc errors = checkRequiredFields(to, cc, bcc, fromEmail,
165             bodyType);
166         if (!errors.isEmpty()) {
167             throw new InvalidParameterException(this.getClass(),
168                 "Some of the required parameters are not specified : "
169                     + processRequiredFieldErrors(errors));
170             }
171         // Create the Mime Message.
172
MimeMessage JavaDoc message = new MimeMessage JavaDoc(this.session);
173
174         try {
175             // Set the from field.
176
message.setFrom(new InternetAddress JavaDoc(fromEmail,
177                 encodeText(fromPersonal, charset, null)));
178
179             // Set the TO addresses for the email.
180
if (to != null) {
181                 setAddresses(message, to, Message.RecipientType.TO, charset);
182             }
183
184             // Set the CC addresses for the email.
185
if (cc != null) {
186                 setAddresses(message, cc, Message.RecipientType.CC, charset);
187             }
188
189             // Set the BCC addresses for the email.
190
if (bcc != null) {
191                 setAddresses(message, bcc, Message.RecipientType.BCC, charset);
192             }
193
194             message.setSubject(subject, charset);
195
196             Iterator JavaDoc iter = null;
197             Map.Entry JavaDoc entry = null;
198
199             // Set headers.
200
if (headers != null) {
201                 iter = headers.entrySet().iterator();
202                 while (iter.hasNext()) {
203                     entry = (Map.Entry JavaDoc) iter.next();
204                     message.addHeader((String JavaDoc) entry.getKey(),
205                                                (String JavaDoc) entry.getValue());
206                 }
207             }
208
209             // Set attachments.
210
if (attachments != null) {
211                 processAttachments(message, body, bodyType, charset,
212                     attachments);
213             } else {
214                 // Fill the message
215
message.setText(body, charset);
216
217                 // Set the contentType of the message in the internet header.
218
setContentTypeHeader(message, bodyType, charset);
219             }
220
221         } catch (UnsupportedEncodingException JavaDoc uee) {
222             throw new MailFailureException(this.getClass(),
223                 "The charset passed to encode the message is not supported."
224                 , uee);
225         } catch (MalformedURLException JavaDoc mue) {
226             throw new MailFailureException(this.getClass(),
227                 "Failed to locate one of the attachment passed. "
228                 + "Check the path or URL for each attachment.", mue);
229         } catch (MessagingException JavaDoc me) {
230             throw new MailFailureException(this.getClass(),
231                 "Failed to create the Mime Message", me);
232         }
233
234         // Now send the message
235
try {
236             send(message);
237         } catch (MessagingException JavaDoc me) {
238             throw new MailFailureException(this.getClass(),
239                 "Failed to send the mail", me);
240         }
241     }
242
243
244     /**
245      * <p>Used internally to attach the attachments in the the <code>
246      * MimeMessage</code>
247      *
248      * @param message message to add the attachments to
249      * @param body The body content of the message
250      * @param bodyType type of body (plain/html/etc)
251      * @param charset character set of the message
252      * @param attachments array of attachments to add to the message
253      *
254      * @throws MalformedURLException indicates the path or URL of
255      * attachment does not exist.
256      * @throws UnsupportedEncodingException indicates the encoding given
257      * was not valid
258      * @throws MessagingException indicates a generic exception
259      * attempting to add the attachment
260      */

261     protected void processAttachments(MimeMessage JavaDoc message, String JavaDoc body,
262         MailContentTypeEnum bodyType, String JavaDoc charset,
263         MailAttachment[] attachments) throws MessagingException JavaDoc,
264         UnsupportedEncodingException JavaDoc, MalformedURLException JavaDoc {
265
266         // Create the message part
267
MimeBodyPart JavaDoc messageBodyPart = new MimeBodyPart JavaDoc();
268
269         // Fill the message
270
messageBodyPart.setText(body, charset);
271
272         // Set the contentType of the message in the internet header.
273
setContentTypeHeader(messageBodyPart, bodyType, charset);
274
275         // Create the MultiPart.
276
Multipart JavaDoc multipart = new MimeMultipart JavaDoc();
277
278         multipart.addBodyPart(messageBodyPart);
279
280         for (int k = 0; k < attachments.length; k++) {
281             multipart.addBodyPart(attach(attachments[k], charset));
282         }
283
284         message.setContent(multipart);
285     }
286
287
288     /**
289      * <p> This method is used internally to check the required parameters
290      * before sending an email. </p>
291      *
292      * @param to A Map containing the mapping of toEmail to toPersonal.
293      * @param cc A Map containing the mapping of ccEmail to ccPersonal.
294      * @param bcc A Map containing the mapping of bccEmail to bccPersonal.
295      * @param fromEmail sender's email id.
296      * @param bodyType the type of body content (plain/html/etc)
297      * @return Collection A collection containing all the errors.
298      */

299     protected Collection JavaDoc checkRequiredFields(Map JavaDoc to, Map JavaDoc cc, Map JavaDoc bcc,
300         String JavaDoc fromEmail, MailContentTypeEnum bodyType) {
301
302         ArrayList JavaDoc errors = new ArrayList JavaDoc();
303
304         // Check the presence of fromEmail
305
if (fromEmail == null) {
306              errors.add("You must specify the sender's email id.");
307         }
308
309         // Check the presence of body type.
310
if (bodyType == null) {
311             errors.add("You must specify the body type.");
312         }
313
314         // Check the presence of atleast one recipient.
315
HashMap JavaDoc allRecipients = new HashMap JavaDoc();
316
317         // added validation for key (email address) to be null or
318
// blank string for to, cc and bcc Maps. This will prevent
319
// an invalid request to be sent to SMTP server
320
if (to != null) {
321             if (!(to.containsKey(null))
322                     && (!(to.containsKey(BLANK_STRING)))) {
323
324                 allRecipients.putAll(to);
325             }
326         }
327
328         if (cc != null) {
329             if (!(cc.containsKey(null))
330                     && (!(cc.containsKey(BLANK_STRING)))) {
331
332                 allRecipients.putAll(cc);
333             }
334         }
335
336         if (bcc != null) {
337             if (!(bcc.containsKey(null))
338                     && (!(bcc.containsKey(BLANK_STRING)))) {
339
340                 allRecipients.putAll(bcc);
341             }
342         }
343
344         if (allRecipients.isEmpty()) {
345             errors.add("You must specify atleast one recipient");
346         }
347
348         return errors;
349     }
350
351
352     /**
353      * <p> Used internally to process the validation errors. </p>
354      *
355      * @param errors collection of validation error to process
356      *
357      * @return The String representation of all the errors.
358      */

359     protected String JavaDoc processRequiredFieldErrors(Collection JavaDoc errors) {
360
361         Iterator JavaDoc iterator = errors.iterator();
362         StringBuffer JavaDoc errorBuffer = new StringBuffer JavaDoc();
363
364         while (iterator.hasNext()) {
365             errorBuffer.append(iterator.next()).append("\n");
366         }
367         return errorBuffer.toString();
368     }
369
370
371     /**
372      * <p> This method sets the Content type header. </p>
373      *
374      * @param part The message or body part.
375      * @param bodyType The body type of the message or body part.
376      * @param charset The charset to be used for encoding.
377      * @exception MessagingException If this part is read-only.
378      */

379     protected void setContentTypeHeader(Part JavaDoc part, MailContentTypeEnum
380         bodyType, String JavaDoc charset) throws MessagingException JavaDoc {
381
382         if (charset != null) {
383             part.addHeader(SynchronousMailService.CONTENT_TYPE,
384                 bodyType.getName() + "; charset=" + charset);
385         } else {
386             part.addHeader(SynchronousMailService.CONTENT_TYPE,
387                 bodyType.getName());
388         }
389     }
390
391
392     /**
393      * <p>Encode a RFC 822 "text" token into mail-safe form as per RFC 2047.
394      * </p>
395      *
396      * @param text The text to be encoded.
397      * @param charset The charset. If this parameter is null, the JVM's
398      * default charset is used.
399      * @param encodingType The encoding to be used. Currently supported
400      * values by Java Mail are "B" and "Q". If this parameter is
401      * null, then the "Q" encoding is used if most of characters
402      * to be encoded are in the ASCII charset, otherwise "B"
403      * encoding is used.
404      *
405      * @return Unicode string containing only US-ASCII characters
406      *
407      * @throws UnsupportedEncodingException Thrown when the text passed
408      * is non-ascii and the charset or encodingType is not supported.
409      */

410     protected String JavaDoc encodeText(String JavaDoc text, String JavaDoc charset,
411         String JavaDoc encodingType) throws UnsupportedEncodingException JavaDoc {
412
413         String JavaDoc encodedText = null;
414
415         /* In case of null the MimeUtility.encodeText would throw a
416          * NullPointerException. Because this method is used also to
417          * encode non-mandatory fields we will allow <code>null</code> values.
418          */

419         if (text != null) {
420             encodedText = MimeUtility.encodeText(text, charset, encodingType);
421         }
422
423         return encodedText;
424     }
425
426
427     /** Constant for the file:// protocol. */
428     private static final String JavaDoc FILE = "file";
429
430     /** Constant for the localhost address. */
431     private static final String JavaDoc LOCALHOST = "localhost";
432
433
434     /**
435      * Attaches the <code>MailAttachment</code>.
436      *
437      * @param attachment A <code>MailAttachment</code>.
438      * @param charset the character of the attachment
439      * @return A Multipart.
440      *
441      * @throws MalformedURLException indicates the path or URL of
442      * attachment does not exist.
443      * @throws UnsupportedEncodingException indicates the encoding given
444      * was not valid
445      * @throws MessagingException indicates a generic exception
446      * attempting to add the attachment
447      */

448      protected BodyPart JavaDoc attach(MailAttachment attachment, String JavaDoc charset)
449          throws MalformedURLException JavaDoc, UnsupportedEncodingException JavaDoc,
450          MessagingException JavaDoc {
451
452          // Get the url
453
URL JavaDoc url = attachment.getURL();
454
455          String JavaDoc path = null;
456
457          // If url is null try constructing a url with the path specified.
458
if (url == null) {
459              path = attachment.getPath();
460              url = new URL JavaDoc(FILE, LOCALHOST, path);
461          }
462
463          // Now Create the MimeMultipart.
464
MimeBodyPart JavaDoc mimeBodyPart = new MimeBodyPart JavaDoc();
465
466          // Create the data source for the URL
467
DataSource JavaDoc source = new URLDataSource JavaDoc(url);
468
469          // Set the data handler
470
mimeBodyPart.setDataHandler(new DataHandler JavaDoc(source));
471
472          mimeBodyPart.setFileName(encodeText(attachment.getName(), charset,
473              null));
474
475          mimeBodyPart.setDescription(
476              encodeText(attachment.getDescription(), charset, null));
477
478          return mimeBodyPart;
479     }
480
481
482     /**
483      * <p> Used internally to set the addresses in <code>MimeMessage</code>
484      * from a Map structure. </p>
485      *
486      * @param message message to add the addresses to
487      * @param addresses map of addresses to add to the message
488      * @param recipientType type of message recipient
489      * @param charset character set of the address
490      *
491      * @throws MessagingException indicates a generic error trying to
492      * set the address
493      * @throws UnsupportedEncodingException indicates an unsupported
494      * encoding was given to the system.
495      */

496     protected void setAddresses(MimeMessage JavaDoc message, Map JavaDoc addresses,
497         Message.RecipientType JavaDoc recipientType, String JavaDoc charset)
498         throws MessagingException JavaDoc, UnsupportedEncodingException JavaDoc {
499
500         Iterator JavaDoc iterator = null;
501         Map.Entry JavaDoc entry = null;
502         InternetAddress JavaDoc address = null;
503
504         iterator = addresses.entrySet().iterator();
505         while (iterator.hasNext()) {
506             entry = (Map.Entry JavaDoc) iterator.next();
507             address = new InternetAddress JavaDoc((String JavaDoc) entry.getKey(),
508                encodeText((String JavaDoc) entry.getValue(), charset, null));
509             message.addRecipient(recipientType, address);
510         }
511     }
512
513     /** Key for mail session property of protocol. */
514     private static final String JavaDoc MAIL_PROTOCOL_KEY = "mail.transport.protocol";
515
516     /** Key for mail session property of mail host. */
517     private static final String JavaDoc MAIL_HOST = "mail.host";
518
519     /** SMPT Value for mail session property of protocol. */
520     private static final String JavaDoc SMTP = "smtp";
521
522
523     /**
524      * @see Configurable#configure(ComponentConfiguration)
525      */

526     public void configure(ComponentConfiguration config)
527         throws Exception JavaDoc {
528
529
530         MailConfiguration mailConfig = (MailConfiguration) config;
531
532         // Get all the configuration parameters.
533
this.smtpHost = mailConfig.getSmtpHost();
534         this.holdConnection = mailConfig.getHoldConnection();
535         this.retryAttempts = mailConfig.getRetryAttempts();
536
537         // Check for -ve retry attempts.
538
if (this.retryAttempts < 0) {
539             throw new InvalidConfigurationException(this.getClass(),
540                 mailConfig.getConfigurationName(),
541                 "RetryAttempts", "Retry Attempts should be >= 0");
542         }
543
544         // If retry Attempts is 0 then we don't need the sleep time.
545
if (this.retryAttempts > 0) {
546             this.sleepRetryTime = mailConfig.getSleepTimeInMilliSecs();
547         }
548
549         // Check for -ve sleep time
550
if (sleepRetryTime < 0) {
551             throw new InvalidConfigurationException(this.getClass(),
552                 mailConfig.getConfigurationName(),
553                 "SleepTimeInMilliSecs", "sleep time should be >= 0");
554         }
555
556         Properties JavaDoc props = new Properties JavaDoc();
557         // Get all the properties required to create session.
558
props.put(MAIL_PROTOCOL_KEY, SMTP);
559         props.put(MAIL_HOST, this.smtpHost);
560
561         /* Create the session object required to create messages.
562          * Note, do not use Session.getDefaultInstance to create
563          * Session here because there might be other email components
564          * with different configurations in the system OR you might
565          * want to change the configuration of the component at run time.
566          * In both the cases the Session.getDefaultInstance will return
567          * the session object with previous configuration.
568          */

569         this.session = Session.getInstance(props, null);
570         this.session.setDebug(mailConfig.getJavaMailDebugMode());
571
572         // Initilize transport object, which does the actual job of
573
// sending messages.
574
this.transport = this.session.getTransport();
575     }
576
577
578     /**
579      * <p>Sends a single email message.</p>
580      *
581      * @param message The message to be sent.
582      * @throws MessagingException indicates an error sending the message
583      */

584     protected void send(MimeMessage JavaDoc message) throws MessagingException JavaDoc {
585
586         int attempts = 0;
587         boolean messageSent = false;
588         MessagingException JavaDoc exception = null;
589
590         while (attempts < (this.retryAttempts + 1) && !messageSent) {
591             try {
592                 sendInternal(message);
593                 messageSent = true;
594             } catch (MessagingException JavaDoc me) {
595                 // Close the connection, if Open
596
if (this.transport.isConnected()) {
597                     this.transport.close();
598                 }
599                 // Sleep for some time.
600
try {
601                     Thread.sleep(this.sleepRetryTime);
602                 } catch (InterruptedException JavaDoc ie) {
603                     /* Do Nothing */
604                 }
605
606                 // Retain the reference of the exception.
607
exception = me;
608                 attempts++;
609             }
610         }
611
612         if (!messageSent) {
613             if (log.isWarnEnabled()) {
614                 log.warn("unable to send the message :" + message);
615             }
616
617             throw exception;
618         } else {
619             if (log.isInfoEnabled()) {
620                 log.info("sent the message:" + message);
621             }
622         }
623     }
624
625
626     /**
627      * <p>Does the actual job of sending a message. The call to this method
628      * is synchronized on this.transport because different threads can access
629      * the same component to send emails. If the call is not synchronized then
630      * there might be the case when one thread opens a connection, the
631      * second thread sends an email and closes the connection. Now first
632      * threads wakes up and try to send an email but would get an
633      * exception as the connection is closed. </p>
634      *
635      * @param message The message to be sent.
636      * @throws MessagingException indicates an error sending the message
637      */

638     protected void sendInternal(MimeMessage JavaDoc message)
639         throws MessagingException JavaDoc {
640
641         synchronized (this.transport) {
642             // Double check the connection
643
if (!this.transport.isConnected()) {
644                 this.transport.connect();
645             }
646             // Send the message
647
this.transport.sendMessage(message, message.getAllRecipients());
648             // Check for hold connection
649
if (!this.holdConnection) {
650                 this.transport.close();
651             }
652         }
653     }
654
655
656     /**
657      * @see Startable#stop()
658      */

659     public void stop() throws Exception JavaDoc {
660
661         // Close the transport if already opened.
662
if (this.transport != null && this.transport.isConnected()) {
663             transport.close();
664         }
665     }
666
667
668     /**
669      * @see Startable#start()
670      */

671     public void start() {
672         // No work to do at this point of time.
673
}
674 }
675
Popular Tags