KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > tigris > scarab > util > Email


1 package org.tigris.scarab.util;
2
3 /* ================================================================
4  * Copyright (c) 2000 CollabNet. All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions are
8  * met:
9  *
10  * 1. Redistributions of source code must retain the above copyright
11  * notice, this list of conditions and the following disclaimer.
12  *
13  * 2. Redistributions in binary form must reproduce the above copyright
14  * notice, this list of conditions and the following disclaimer in the
15  * documentation and/or other materials provided with the distribution.
16  *
17  * 3. The end-user documentation included with the redistribution, if
18  * any, must include the following acknowlegement: "This product includes
19  * software developed by CollabNet (http://www.collab.net/)."
20  * Alternately, this acknowlegement may appear in the software itself, if
21  * and wherever such third-party acknowlegements normally appear.
22  *
23  * 4. The hosted project names must not be used to endorse or promote
24  * products derived from this software without prior written
25  * permission. For written permission, please contact info@collab.net.
26  *
27  * 5. Products derived from this software may not use the "Tigris" name
28  * nor may "Tigris" appear in their names without prior written
29  * permission of CollabNet.
30  *
31  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
32  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
33  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
34  * IN NO EVENT SHALL COLLAB.NET OR ITS CONTRIBUTORS BE LIABLE FOR ANY
35  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
36  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
37  * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
38  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
39  * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
40  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42  *
43  * ====================================================================
44  *
45  * This software consists of voluntary contributions made by many
46  * individuals on behalf of CollabNet.
47  */

48
49 import java.util.ArrayList JavaDoc;
50 import java.util.Collection JavaDoc;
51 import java.util.HashMap JavaDoc;
52 import java.util.Iterator JavaDoc;
53 import java.util.List JavaDoc;
54 import java.util.Locale JavaDoc;
55 import java.util.Map JavaDoc;
56 import java.util.StringTokenizer JavaDoc;
57
58 import javax.mail.SendFailedException JavaDoc;
59 import javax.mail.internet.InternetAddress JavaDoc;
60
61 import org.apache.fulcrum.ServiceException;
62 import org.apache.fulcrum.template.TemplateContext;
63 import org.apache.fulcrum.template.TemplateEmail;
64 import org.apache.fulcrum.velocity.ContextAdapter;
65 import org.apache.turbine.Turbine;
66 import org.tigris.scarab.om.GlobalParameter;
67 import org.tigris.scarab.om.GlobalParameterManager;
68 import org.tigris.scarab.om.Module;
69 import org.tigris.scarab.om.ScarabUser;
70 import org.tigris.scarab.services.email.VelocityEmail;
71 import org.tigris.scarab.tools.ScarabLocalizationTool;
72 import org.tigris.scarab.tools.localization.L10NKeySet;
73
74 /**
75  * Sends a notification email.
76  *
77  * @author <a HREF="mailto:jon@collab.net">Jon Scott Stevens</a>
78  * @author <a HREF="mailto:elicia@collab.net">Elicia David</a>
79  * @author <a HREF="mailto:jmcnally@collab.net">John McNally</a>
80  * @version $Id: Email.java 9505 2005-03-21 01:58:11Z dabbous $
81  */

82 public class Email extends TemplateEmail
83 {
84     private static final int TO = 0;
85     private static final int CC = 1;
86
87     /**
88      * Sends email to a single recipient. Throws an Excetion,
89      * if it fails to send the email for any reason.
90      */

91     public static void sendEmail(EmailContext context, Module module,
92                                     Object JavaDoc fromUser, Object JavaDoc replyToUser,
93                                     ScarabUser toUser, String JavaDoc template)
94             throws Exception JavaDoc
95     {
96         Collection JavaDoc toUsers = new ArrayList JavaDoc(2);
97         toUsers.add(toUser);
98         sendEmail(context, module, fromUser, replyToUser, toUsers, null,
99                 template);
100     }
101
102     /**
103      * Sends email to multiple recipients. Throws an Exception,
104      * if it fails to send the email for any reason.
105      */

106     public static void sendEmail(EmailContext context, Module module,
107                                     Object JavaDoc fromUser, Object JavaDoc replyToUser,
108                                     Collection JavaDoc toUsers, Collection JavaDoc ccUsers,
109                                     String JavaDoc template)
110         throws Exception JavaDoc
111     {
112         if (!GlobalParameterManager.getBoolean(GlobalParameter.EMAIL_ENABLED,
113                 module))
114         {
115             return;
116         }
117
118         //
119
// To avoid any NullPointerExceptions, create
120
// empty lists of to: and cc: users if the
121
// collections are null.
122
//
123
if (toUsers == null)
124         {
125             toUsers = new ArrayList JavaDoc();
126         }
127
128         if (ccUsers == null)
129         {
130             ccUsers = new ArrayList JavaDoc();
131         }
132
133         //
134
// Remove duplicate addresses from the cc: list
135
//
136
ccUsers.removeAll(toUsers);
137
138         String JavaDoc archiveEmail = module.getArchiveEmail();
139         if (archiveEmail != null && archiveEmail.trim().length() == 0)
140         {
141             archiveEmail = null;
142         }
143
144         Map JavaDoc userLocaleMap = groupAddressesByLocale(module, toUsers, ccUsers,
145                 archiveEmail);
146
147         for (Iterator JavaDoc i = userLocaleMap.keySet().iterator(); i.hasNext();)
148         {
149             Locale JavaDoc locale = (Locale JavaDoc) i.next();
150             List JavaDoc[] toAndCC = (List JavaDoc[]) userLocaleMap.get(locale);
151             List JavaDoc to = toAndCC[TO];
152             List JavaDoc cc = toAndCC[CC];
153
154             sendEmailInLocale(context, module, fromUser, replyToUser, to, cc,
155                               template, locale);
156         }
157
158     }
159
160     /** Sends email in a specific locale. */
161     private static void sendEmailInLocale(EmailContext context, Module module,
162                                           Object JavaDoc fromUser, Object JavaDoc replyToUser,
163                                           List JavaDoc toAddresses, List JavaDoc ccAddresses,
164                                           String JavaDoc template, Locale JavaDoc locale)
165         throws Exception JavaDoc
166     {
167         Log.get().debug("Sending email for locale=" + locale);
168
169         // get reference to l10n tool, so we can alter the locale per email
170
ScarabLocalizationTool l10n = new ScarabLocalizationTool();
171         context.setLocalizationTool(l10n);
172         l10n.init(locale);
173
174         Email te = getEmail(context, module, fromUser, replyToUser, template);
175         te.setCharset(getCharset(locale));
176
177         boolean atLeastOneTo = false;
178         for (Iterator JavaDoc iTo = toAddresses.iterator(); iTo.hasNext();)
179         {
180             InternetAddress JavaDoc a = (InternetAddress JavaDoc) iTo.next();
181             te.addTo(a.getAddress(), a.getPersonal());
182             atLeastOneTo = true;
183             Log.get().debug("Added To: " + a.getAddress());
184         }
185         for (Iterator JavaDoc iCC = ccAddresses.iterator(); iCC.hasNext();)
186         {
187             InternetAddress JavaDoc a = (InternetAddress JavaDoc) iCC.next();
188             String JavaDoc email = a.getAddress();
189             String JavaDoc name = a.getPersonal();
190
191             // template email requires a To: user, it does seem possible
192
// to send emails with only a CC: user, so not sure if this
193
// is a bug to be fixed in TemplateEmail. Might not be good
194
// form anyway. So if there are no To: users, upgrade CC's.
195
if (atLeastOneTo)
196             {
197                 te.addCc(email, name);
198             }
199             else
200             {
201                 te.addTo(email, name);
202                 // We've added one To: user and TemplateEmail should be
203
// happy. No need to move all CC: into TO:
204
atLeastOneTo = true;
205             }
206             Log.get().debug("Added CC: " + email);
207         }
208
209         try{
210             te.sendMultiple();
211         }
212         catch(SendFailedException JavaDoc sfe)
213         {
214             Throwable JavaDoc t = sfe.getNextException();
215             throw new ScarabException(L10NKeySet.ExceptionEmailFailure,t);
216         }
217     }
218
219     private static List JavaDoc expandMultipleAddresses(String JavaDoc addresses)
220     {
221         List JavaDoc expanded = new ArrayList JavaDoc();
222         StringTokenizer JavaDoc st = new StringTokenizer JavaDoc(addresses, ",;");
223         while (st.hasMoreTokens())
224             expanded.add(st.nextToken().trim());
225         return expanded;
226     }
227     
228     /**
229      * Creates a map of Locale objects -> List[2], where the first
230      * element of the list array is a list of "To:" addresses, and
231      * the second is a list of "Cc:" addresses. For example, if
232      * user "Pierre" is in <code>toUsers</code> and requires emails
233      * in french, his email address will be in
234      * <code>userLocaleMap[Locale.FRANCE][TO]</code>. The same applies
235      * to "Cc:" addresses, while the archive email address is associated
236      * with the default module locale.
237      */

238     private static Map JavaDoc groupAddressesByLocale(Module module,
239                                               Collection JavaDoc toUsers,
240                                               Collection JavaDoc ccUsers,
241                                               String JavaDoc archiveEmail)
242         throws Exception JavaDoc
243     {
244         Map JavaDoc result = new HashMap JavaDoc();
245         for (Iterator JavaDoc iter = toUsers.iterator(); iter.hasNext();)
246         {
247             fileUser(result, (ScarabUser) iter.next(), module, TO);
248         }
249
250         for (Iterator JavaDoc iter = ccUsers.iterator(); iter.hasNext();)
251         {
252             fileUser(result, (ScarabUser) iter.next(), module, CC);
253         }
254         if (archiveEmail != null)
255         {
256             List JavaDoc expandedArchive = expandMultipleAddresses(archiveEmail);
257             for (Iterator JavaDoc iter = expandedArchive.iterator(); iter.hasNext(); )
258                 fileAddress(result, new InternetAddress JavaDoc((String JavaDoc)iter.next()),
259                         chooseLocale(null, module), CC);
260         }
261         return result;
262     }
263
264     private static void fileAddress(Map JavaDoc userLocaleMap, InternetAddress JavaDoc address,
265                                     Locale JavaDoc locale, int toOrCC)
266     {
267         List JavaDoc[] toAndCC = (List JavaDoc[]) userLocaleMap.get(locale);
268         if (toAndCC == null)
269         {
270             toAndCC = new List JavaDoc[2];
271             toAndCC[0] = new ArrayList JavaDoc();
272             toAndCC[1] = new ArrayList JavaDoc();
273             userLocaleMap.put(locale, toAndCC);
274         }
275         toAndCC[toOrCC].add(address);
276     }
277
278     private static void fileUser(Map JavaDoc userLocaleMap, ScarabUser user,
279                                  Module module, int toOrCC) throws Exception JavaDoc
280     {
281         fileAddress(userLocaleMap, new InternetAddress JavaDoc(user.getEmail(), user
282                 .getName()), chooseLocale(user, module), toOrCC);
283     }
284
285     /**
286      * Override the super.handleRequest() and process the template
287      * our own way.
288      * This could have been handled in a more simple way, which was
289      * to create a new service and associate the emails with a different
290      * file extension which would have prevented the need to override
291      * this method, however, that was discovered after the fact and it
292      * also seemed to be a bit more work to change the file extension.
293      */

294     protected String JavaDoc handleRequest() throws ServiceException
295     {
296         String JavaDoc result = null;
297         try
298         {
299             result = VelocityEmail.handleRequest(new ContextAdapter(
300                     getContext()), getTemplate());
301         }
302         catch (Exception JavaDoc e)
303         {
304             throw new ServiceException(e); //EXCEPTION
305
}
306         return result;
307     }
308
309     /**
310      * @param context The context in which to send mail, or
311      * <code>null</code> to create a new context.
312      * @param fromUser Can be any of the following: ScarabUser, two
313      * element String[] composed of name and address, base portion of
314      * the key used for a name and address property lookup.
315      * @param replyToUser Can be any of the following: ScarabUser, two
316      * element String[] composed of name and address, base portion of
317      * the key used for a name and address property lookup.
318      */

319     private static Email getEmail(EmailContext context, Module module,
320                                   Object JavaDoc fromUser, Object JavaDoc replyToUser,
321                                   String JavaDoc template) throws Exception JavaDoc
322     {
323         Email te = new Email();
324         if (context == null)
325         {
326             context = new EmailContext();
327         }
328         te.setContext(context);
329
330         EmailLink el = EmailLinkFactory.getInstance(module);
331         context.setLinkTool(el);
332
333         String JavaDoc[] nameAndAddr = getNameAndAddress(fromUser);
334         te.setFrom(nameAndAddr[0], nameAndAddr[1]);
335
336         nameAndAddr = getNameAndAddress(replyToUser);
337         te.addReplyTo(nameAndAddr[0], nameAndAddr[1]);
338
339         if (template == null)
340         {
341             template = Turbine.getConfiguration().getString(
342                     "scarab.email.default.template", "Default.vm");
343         }
344         te.setTemplate(prependDir(template));
345         String JavaDoc subjectTemplate = context.getSubjectTemplate();
346         if (subjectTemplate == null)
347         {
348             int templateLength = template.length();
349             // The magic number 7 represents "Subject"
350
StringBuffer JavaDoc templateSB = new StringBuffer JavaDoc(templateLength + 7);
351             // The magic number 3 represents ".vm"
352
templateSB.append(template.substring(0, templateLength - 3));
353             subjectTemplate = templateSB.append("Subject.vm").toString();
354         }
355
356         te.setSubject(getSubject(context, subjectTemplate));
357         return te;
358     }
359
360     /**
361      * Leverages the <code>fromName</code> and
362      * <code>fromAddress</code> properties when <code>input</code> is
363      * neither a <code>ScarabUser</code> nor <code>String[]</code>.
364      */

365     private static String JavaDoc[] getNameAndAddress(Object JavaDoc input)
366     {
367         String JavaDoc[] nameAndAddr;
368         if (input instanceof ScarabUser)
369         {
370             ScarabUser u = (ScarabUser) input;
371             nameAndAddr = new String JavaDoc[]{u.getName(), u.getEmail()};
372         }
373         else if (input instanceof String JavaDoc[])
374         {
375             nameAndAddr = (String JavaDoc[]) input;
376         }
377         else
378         {
379             // Assume we want a property lookup, and the base portion
380
// of the key to use for that lookup was passed in.
381
String JavaDoc keyBase = (String JavaDoc) input;
382             if (keyBase == null)
383             {
384                 keyBase = "scarab.email.default";
385             }
386
387             // TODO: Discover a better sending host/domain than
388
// "localhost"
389

390             nameAndAddr = new String JavaDoc[2];
391             nameAndAddr[0] = Turbine.getConfiguration().getString(
392                     keyBase + ".fromName", "Scarab System");
393             nameAndAddr[1] = Turbine.getConfiguration().getString(
394                     keyBase + ".fromAddress", "help@localhost");
395         }
396         return nameAndAddr;
397     }
398
399     private static String JavaDoc getSubject(TemplateContext context, String JavaDoc template)
400     {
401         template = prependDir(template);
402         String JavaDoc result = null;
403         try
404         {
405             // render the template
406
result = VelocityEmail.handleRequest(new ContextAdapter(context),
407                     template);
408             if (result != null)
409             {
410                 result = result.trim();
411             }
412             // in some of the more complicated templates, we set a context
413
// variable so that there is not a whole bunch of whitespace
414
// that can make it into the subject...
415
String JavaDoc subject = (String JavaDoc) context.get("emailSubject");
416             if (subject != null)
417             {
418                 result = subject.trim();
419             }
420         }
421         catch (Exception JavaDoc e)
422         {
423             Log.get()
424                     .error("Error rendering subject for " + template + ". ", e);
425             result = "Scarab System Notification";
426         }
427         return result;
428     }
429
430     private static String JavaDoc prependDir(String JavaDoc template)
431     {
432         boolean b = false;
433         try
434         {
435             b = GlobalParameterManager
436                     .getBoolean(GlobalParameter.EMAIL_INCLUDE_ISSUE_DETAILS);
437         }
438         catch (Exception JavaDoc e)
439         {
440             Log.get().debug("", e);
441             // use the basic email
442
}
443         return b ? "email/" + template : "basic_email/" + template;
444     }
445
446     /**
447      * Returns a charset for the given locale that is generally
448      * preferred by email clients. If not specified by the property
449      * named by {@link
450      * org.tigris.scarab.util.ScarabConstants#DEFAULT_EMAIL_ENCODING_KEY},
451      * ask the <code>MimeTypeService</code> for a good value (except
452      * for Japanese, which always uses the encoding
453      * <code>ISO-2022-JP</code>).
454      *
455      * @param locale a <code>Locale</code> value
456      * @return a <code>String</code> value
457      */

458     public static String JavaDoc getCharset(Locale JavaDoc locale)
459     {
460         String JavaDoc charset = Turbine.getConfiguration().getString(
461                 ScarabConstants.DEFAULT_EMAIL_ENCODING_KEY, "").trim();
462         if (charset.length() == 0 || "native".equalsIgnoreCase(charset))
463         {
464             if ("ja".equals(locale.getLanguage()))
465             {
466                 charset = "ISO-2022-JP";
467             }
468             else
469             {
470                 charset = ComponentLocator.getMimeTypeService().getCharSet(locale);
471             }
472         }
473
474         return charset;
475     }
476
477     private static Locale JavaDoc chooseLocale(ScarabUser user, Module module)
478     {
479         Locale JavaDoc locale = null;
480         if (user != null)
481         {
482             try
483             {
484                 locale = user.getPreferredLocale();
485             }
486             catch (Exception JavaDoc e)
487             {
488                 Log.get().error(
489                         "Couldn't determine locale for user " + user
490                                 .getUserName(), e);
491             }
492         }
493         if (locale == null)
494         {
495             if (module != null && module.getLocale() != null)
496             {
497                 locale = module.getLocale();
498             }
499             else
500             {
501                 locale = ScarabConstants.DEFAULT_LOCALE;
502             }
503         }
504         return locale;
505     }
506 }
507
Popular Tags