KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > blandware > atleap > service > util > MailEngine


1 /*
2  * Copyright 2004 Blandware (http://www.blandware.com)
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 package com.blandware.atleap.service.util;
17
18 import com.blandware.atleap.common.Constants;
19 import com.blandware.atleap.common.parsers.html.HTMLPlainTextExtractor;
20 import com.blandware.atleap.common.util.ConvertUtil;
21 import com.blandware.atleap.common.util.RegExUtil;
22 import com.blandware.atleap.common.util.StringUtil;
23 import com.blandware.atleap.model.core.ContentField;
24 import com.blandware.atleap.model.core.ContentFieldValue;
25 import com.blandware.atleap.model.core.ContentLocale;
26 import com.blandware.atleap.model.core.ContentResource;
27 import com.blandware.atleap.model.core.MailTemplate;
28 import com.blandware.atleap.persistence.core.ContentLocaleDAO;
29 import com.blandware.atleap.persistence.core.ContentResourceDAO;
30 import com.blandware.atleap.persistence.core.MailTemplateDAO;
31 import com.blandware.atleap.service.exception.BeanNotFoundException;
32 import com.blandware.atleap.service.exception.MailAddressException;
33 import org.apache.commons.logging.Log;
34 import org.apache.commons.logging.LogFactory;
35 import org.apache.oro.text.regex.MalformedPatternException;
36 import org.apache.velocity.app.VelocityEngine;
37 import org.apache.velocity.exception.ResourceNotFoundException;
38 import org.apache.velocity.exception.VelocityException;
39 import org.springframework.core.io.InputStreamSource;
40 import org.springframework.mail.MailPreparationException;
41 import org.springframework.mail.javamail.JavaMailSender;
42 import org.springframework.mail.javamail.MimeMessageHelper;
43 import org.springframework.ui.velocity.VelocityEngineUtils;
44
45 import javax.mail.MessagingException JavaDoc;
46 import javax.mail.internet.InternetAddress JavaDoc;
47 import javax.mail.internet.MimeMessage JavaDoc;
48 import java.io.ByteArrayInputStream JavaDoc;
49 import java.io.IOException JavaDoc;
50 import java.io.InputStream JavaDoc;
51 import java.util.Date JavaDoc;
52 import java.util.HashMap JavaDoc;
53 import java.util.HashSet JavaDoc;
54 import java.util.Iterator JavaDoc;
55 import java.util.Map JavaDoc;
56 import java.util.Set JavaDoc;
57
58
59 /**
60  * <p>Mail engine used to send e-mails via Spring Mail abstraction layer
61  * </p>
62  * <p><a HREF="MailEngine.java.htm"><i>View Source</i></a>
63  * </p>
64  *
65  * @author Sergey Zubtcovskii <a HREF="mailto:sergey.zubtcovskii@blandware.com">&lt;sergey.zubtcovskii@blandware.com&gt;</a>
66  * @version $Revision: 1.14 $ $Date: 2006/03/16 11:09:39 $
67  */

68 public class MailEngine {
69
70     /**
71      * Log instance for our class
72      */

73     protected transient final Log log = LogFactory.getLog(MailEngine.class);
74
75     /**
76      * Mail sender to use
77      */

78     protected JavaMailSender mailSender;
79
80     /**
81      * Velocity engine to merge templates into string
82      */

83     protected VelocityEngine velocityEngine;
84
85     /**
86      * DAO for work with mail templates
87      */

88     protected MailTemplateDAO mailTemplateDAO;
89
90     /**
91      * DAO for work with content locales
92      */

93     protected ContentLocaleDAO contentLocaleDAO;
94
95     /**
96      * DAO for work with content resources
97      */

98     protected ContentResourceDAO contentResourceDAO;
99
100     /**
101      * Sets a mail sender to use
102      *
103      * @param mailSender the mail sender to set
104      */

105     public void setMailSender(JavaMailSender mailSender) {
106         this.mailSender = mailSender;
107     }
108
109     /**
110      * Sets the Velocity engine to use
111      *
112      * @param velocityEngine the Velocity engine to set
113      */

114     public void setVelocityEngine(VelocityEngine velocityEngine) {
115         this.velocityEngine = velocityEngine;
116     }
117
118     /**
119      * Sets the mail template DAO that will be used to work with the persistance
120      * layer
121      *
122      * @param mailTemplateDAO the mail template DAO to set
123      */

124     public void setMailTemplateDAO(MailTemplateDAO mailTemplateDAO) {
125         this.mailTemplateDAO = mailTemplateDAO;
126     }
127
128     /**
129      * Sets the content locale DAO that will be used to work with the persistance
130      * layer
131      *
132      * @param contentLocaleDAO the content locale DAO to set
133      */

134     public void setContentLocaleDAO(ContentLocaleDAO contentLocaleDAO) {
135         this.contentLocaleDAO = contentLocaleDAO;
136     }
137
138     /**
139      * Sets the content resource DAO that will be used to work with the persistance
140      * layer
141      *
142      * @param contentResourceDAO the content resource DAO to set
143      */

144     public void setContentResourceDAO(ContentResourceDAO contentResourceDAO) {
145         this.contentResourceDAO = contentResourceDAO;
146     }
147
148     /**
149      * Sends message to several recipients
150      *
151      * @param from Sender of the message
152      * @param subject Subject of the message
153      * @param to Recipients of message
154      * @param cc Carbon copy - list of additional addresses to set
155      * @param bcc Blind carbon copy - list of additional addresses, hidden from other recipients
156      * @param templateIdentifier Identifier of template to use
157      * @param locale Language to send message on
158      * @param model Mappings of variables used in template to their values
159      * @throws MailPreparationException if mail message could not be prepared properly
160      */

161     public void sendMessage(String JavaDoc from, String JavaDoc subject, String JavaDoc[] to, String JavaDoc[] cc, String JavaDoc[] bcc, String JavaDoc templateIdentifier, String JavaDoc locale, Map JavaDoc model) {
162
163         if ( to == null || to.length == 0 ) {
164             throw new MailPreparationException("No recipient specified");
165         }
166
167         MimeMessage JavaDoc message = null;
168         try {
169             message = createMessage(from, subject, to, cc, bcc, templateIdentifier, locale, model);
170         } catch ( Exception JavaDoc e ) {
171             throw new MailPreparationException(e);
172         }
173         send(message);
174     }
175
176     /**
177      * Sends message to several recipients. 'From' and 'subject' will be taken
178      * from the template.
179      *
180      * @param to Recipients of message
181      * @param cc Carbon copy - list of additional addresses to set
182      * @param bcc Blind carbon copy - list of additional addresses, hided from other recipients
183      * @param templateIdentifier Identifier of template to use
184      * @param locale Language to send message on
185      * @param model Mappings of variables used in template to their values
186      * @throws MailPreparationException if mail message could not be prepared properly
187      */

188     public void sendMessage(String JavaDoc[] to, String JavaDoc[] cc, String JavaDoc[] bcc, String JavaDoc templateIdentifier, String JavaDoc locale, Map JavaDoc model) {
189         sendMessage(null, null, to, cc, bcc, templateIdentifier, locale, model);
190     }
191
192     /**
193      * Sends message to several recipients. 'From' and 'subject' will be taken
194      * from the template.
195      *
196      * @param to Recipients of message
197      * @param templateIdentifier Identifier of template to use
198      * @param locale Language to send message on
199      * @param model Mappings of variables used in template to their values
200      * @throws MailPreparationException if mail message could not be prepared properly
201      */

202     public void sendMessage(String JavaDoc[] to, String JavaDoc templateIdentifier, String JavaDoc locale, Map JavaDoc model) {
203         sendMessage(to, null, null, templateIdentifier, locale, model);
204     }
205
206     /**
207      * Sends message to recipient. 'From' and 'subject' will be taken
208      * from the template.
209      *
210      * @param to Recipient of message
211      * @param templateIdentifier Identifier of template to use
212      * @param locale Language to send message on
213      * @param model Mappings of variables used in template to their values
214      * @throws MailPreparationException if mail message could not be prepared properly
215      */

216     public void sendMessage(String JavaDoc to, String JavaDoc templateIdentifier, String JavaDoc locale, Map JavaDoc model) {
217         sendMessage(new String JavaDoc[]{to}, templateIdentifier, locale, model);
218     }
219
220     /**
221      * Creates simple or MIME message according to template's MIME-type
222      *
223      * @param from Sender of the message
224      * @param subject Subject of the message
225      * @param to Recipients of message
226      * @param cc Carbon copy - list of additional addresses to set
227      * @param bcc Blind carbon copy - list of additional addresses, hided from other recipients
228      * @param templateIdentifier Identifier of template to use
229      * @param locale Language to send message on
230      * @param model Mappings of variables used in template to their values
231      * @return created message
232      */

233     protected MimeMessage JavaDoc createMessage(String JavaDoc from, String JavaDoc subject, String JavaDoc[] to, String JavaDoc[] cc, String JavaDoc[] bcc, String JavaDoc templateIdentifier, String JavaDoc locale, Map JavaDoc model) throws BeanNotFoundException, VelocityException {
234         MailTemplate mailTemplate = mailTemplateDAO.findMailTemplateByIdentifier(templateIdentifier);
235         if ( mailTemplate == null ) {
236             throw new BeanNotFoundException("No template with identifier '" + templateIdentifier + "' could be found");
237         }
238         MimeMessageHelper messageHelper = null;
239         String JavaDoc charset = (String JavaDoc) mailTemplate.getCharset().get(locale);
240         if ( charset == null || charset.length() == 0 ) {
241             charset = Constants.DEFAULT_ENCODING;
242         }
243
244         boolean plain = mailTemplate.isPlain();
245
246         if ( from == null || from.length() == 0 ) {
247             from = mergeTemplate(templateIdentifier, "from", locale, model);
248         }
249
250         if ( subject == null || subject.length() == 0 ) {
251             subject = mergeTemplate(templateIdentifier, "subject", locale, model);
252         }
253
254         String JavaDoc text = mergeTemplate(templateIdentifier, "body", locale, model);
255
256         // replace encoded line breaks (&lt;br /&gt;) with normal tags (<br />);
257

258         try {
259             text = RegExUtil.replaceAll(text, "&lt;br\040*/?&gt;", "<br />");
260         } catch ( MalformedPatternException e ) {
261             // ignore
262
}
263
264         InternetAddress JavaDoc fromAddress = prepareAddress(from, charset);
265         InternetAddress JavaDoc[] toAddresses = prepareAddresses(to, charset);
266
267         try {
268             messageHelper = createEmptyMessageHelper(charset, !plain, false);
269             messageHelper.setFrom(fromAddress);
270             messageHelper.setReplyTo(fromAddress);
271             messageHelper.setSubject(subject);
272             messageHelper.setTo(toAddresses);
273             if ( cc != null ) {
274                 InternetAddress JavaDoc[] ccAddresses = prepareAddresses(cc, charset);
275                 messageHelper.setCc(ccAddresses);
276             }
277             if ( bcc != null ) {
278                 InternetAddress JavaDoc[] bccAddresses = prepareAddresses(bcc, charset);
279                 messageHelper.setBcc(bccAddresses);
280             }
281             messageHelper.setSentDate(new Date JavaDoc());
282             if ( !plain ) {
283                 prepareMimeMessage(messageHelper, text);
284             } else {
285                 // html flag set to tru, because all encoding of HTML-sensitive characters has been performed during
286
// preparation of body text
287
messageHelper.setText(text, true);
288             }
289         } catch ( MessagingException JavaDoc e ) {
290             throw new MailPreparationException(e);
291         }
292
293         return messageHelper.getMimeMessage();
294     }
295
296     /**
297      * Creates simple plain message
298      *
299      * @param charset Charset
300      * @param multipart Whether the message is multipart
301      * @param validate Whether or not to validate
302      * @return Empty MIME message
303      */

304     protected MimeMessageHelper createEmptyMessageHelper(String JavaDoc charset, boolean multipart, boolean validate) throws MessagingException JavaDoc {
305         MimeMessage JavaDoc message = mailSender.createMimeMessage();
306         MimeMessageHelper messageHelper = new MimeMessageHelper(message, multipart, charset);
307         messageHelper.setValidateAddresses(validate);
308         return messageHelper;
309     }
310
311     /**
312      * Adds inline resources and attachements to MIME message
313      *
314      * @param helper MIME message helper used to add inline resources and attachements
315      * @param text Body text of the message
316      */

317     protected void prepareMimeMessage(MimeMessageHelper helper, String JavaDoc text) {
318         try {
319             HTMLPlainTextExtractor extractor = new HTMLPlainTextExtractor();
320             Set JavaDoc resourceUris = new HashSet JavaDoc(extractor.extractInlineResources(new ByteArrayInputStream JavaDoc(ConvertUtil.convertToByteArray(text)), Constants.DEFAULT_ENCODING));
321             Map JavaDoc resourcesMap = new HashMap JavaDoc();
322             for ( Iterator JavaDoc i = resourceUris.iterator(); i.hasNext(); ) {
323                 String JavaDoc src = (String JavaDoc) i.next();
324                 String JavaDoc prefix = Constants.RESOURCES_URI_PREFIX;
325                 if ( prefix.startsWith("/") ) {
326                     prefix = prefix.substring(1);
327                 }
328
329                 int k = src.indexOf(prefix);
330                 if ( k == -1 ) {
331                     // that's not a link to inline resource, so skip it
332
continue;
333                 }
334
335                 String JavaDoc resourceUri = src.substring(k);
336                 if ( !resourceUri.startsWith("/") ) {
337                     resourceUri = "/" + resourceUri;
338                 }
339
340                 ContentResource resource = contentResourceDAO.findContentResourceByUri(resourceUri);
341                 if ( resource != null ) {
342                     String JavaDoc cid = "resource" + resource.getId();
343                     resourcesMap.put(cid, resource);
344                     text = text.replaceAll("=\"" + src + "\"", "=\"cid:" + cid + "\"");
345                 }
346             }
347
348             helper.setText(text, true);
349
350             // add resources
351
for ( Iterator JavaDoc i = resourcesMap.entrySet().iterator(); i.hasNext(); ) {
352                 Map.Entry JavaDoc entry = (Map.Entry JavaDoc) i.next();
353                 String JavaDoc cid = (String JavaDoc) entry.getKey();
354                 ContentResource resource = (ContentResource) entry.getValue();
355                 helper.addInline(cid, new InputStreamContentResource(resource), resource.getMimeType());
356             }
357
358         } catch ( Exception JavaDoc e ) {
359             throw new MailPreparationException(e);
360         }
361
362     }
363
364     /**
365      * Creates instance of <code>javax.mail.internet.InternetAddress</code> with correctly encoded personal
366      *
367      * @param address String representation of address
368      * @param charset Charset to use to encode personal
369      * @return Instance of <code>javax.mail.internet.InternetAddress</code> with correctly encoded personal
370      * @see javax.mail.internet.InternetAddress
371      */

372     public InternetAddress JavaDoc prepareAddress(String JavaDoc address, String JavaDoc charset) {
373         return prepareAddresses(new String JavaDoc[]{address}, charset)[0];
374     }
375
376     /**
377      * Creates array of instances of of <code>javax.mail.internet.InternetAddress</code> with correctly encoded personals. <br />
378      * Wrap all exceptions in our runtime equivalent.
379      *
380      * @param addresses Array of string representation of addresses
381      * @param charset Charset to use to encode personal
382      * @return Array of instances of of <code>javax.mail.internet.InternetAddress</code> with correctly encoded personals
383      * @see javax.mail.internet.InternetAddress
384      * @see com.blandware.atleap.service.exception.MailAddressException
385      */

386     public InternetAddress JavaDoc[] prepareAddresses(String JavaDoc[] addresses, String JavaDoc charset) {
387         InternetAddress JavaDoc[] internetAddresses = new InternetAddress JavaDoc[addresses.length];
388         try {
389             for ( int i = 0; i < addresses.length; i++ ) {
390                 String JavaDoc address = addresses[i];
391                 InternetAddress JavaDoc internetAddress = new InternetAddress JavaDoc(address);
392                 internetAddress.setPersonal(internetAddress.getPersonal(), charset);
393                 internetAddresses[i] = internetAddress;
394             }
395         } catch ( Exception JavaDoc e ) {
396             throw new MailAddressException(e.getLocalizedMessage());
397         }
398         return internetAddresses;
399     }
400
401     /**
402      * Sends specified message using mail sender
403      *
404      * @param msg Message (simple or MIME) to send
405      * @throws org.springframework.mail.MailException
406      * if mail sending failed
407      */

408     protected void send(MimeMessage JavaDoc msg) {
409         mailSender.send(msg);
410     }
411
412     /**
413      * Merges template replacing HTML-sensitive characters with their entity equivalents
414      *
415      * @param identifier Identifier of mail template
416      * @param field Name of field (from, subject or body)
417      * @param locale Identifier of locale
418      * @param model Mappings of variables used in template to their values
419      * @return Merged template
420      */

421     public String JavaDoc mergeTemplate(String JavaDoc identifier, String JavaDoc field, String JavaDoc locale, Map JavaDoc model) throws VelocityException {
422         return mergeTemplate(identifier, field, locale, model, true);
423     }
424
425
426     /**
427      * Merges template
428      *
429      * @param identifier Identifier of mail template
430      * @param field Name of field (from, subject or body)
431      * @param locale Identifier of locale
432      * @param model Mappings of variables used in template to their values
433      * @param encodeModel Whether or not to replace HTML-sensitive characters with their entity equivalents
434      * @return Merged template
435      */

436     public String JavaDoc mergeTemplate(String JavaDoc identifier, String JavaDoc field, String JavaDoc locale, Map JavaDoc model, boolean encodeModel) throws VelocityException {
437         String JavaDoc result = null;
438
439         // retrieve template
440
MailTemplate mailTemplate = mailTemplateDAO.findMailTemplateByIdentifier(identifier);
441         if ( mailTemplate == null ) {
442             throw new ResourceNotFoundException("Mail template with identifier '" + identifier + "' could not be found");
443         }
444
445         ContentField requestedField = (ContentField) mailTemplate.getContentFieldsMap().get(field);
446         if ( requestedField == null ) {
447             throw new ResourceNotFoundException("Content field with identifier '" + field + "' could not be found on mail template '" + identifier + "' (ID=" + mailTemplate.getId() + ")");
448         }
449
450         Map JavaDoc contentFieldValues = requestedField.getContentFieldValuesMap();
451         ContentFieldValue templateValue = null;
452         if ( contentFieldValues != null && !contentFieldValues.isEmpty() ) {
453             // try to search by given locale
454
templateValue = (ContentFieldValue) contentFieldValues.get(locale);
455
456             // if not found, search by default locale
457
if ( templateValue == null || isEmpty(templateValue) ) {
458
459                 if ( log.isWarnEnabled() ) {
460                     log.warn("No value of content field with ID=" + requestedField.getId() + " could be found for locale '" + locale + "' Trying to search by default locale...");
461                 }
462
463                 ContentLocale defaultLocale = contentLocaleDAO.findDefaultContentLocale();
464                 templateValue = (ContentFieldValue) contentFieldValues.get(defaultLocale.getIdentifier());
465
466                 // if still not found get first value in map
467
if ( templateValue == null || isEmpty(templateValue) ) {
468                     if ( log.isWarnEnabled() ) {
469                         log.warn("No value for default locale could be found. Getting the first one...");
470                     }
471                     templateValue = (ContentFieldValue) contentFieldValues.values().iterator().next();
472                 }
473             }
474         } else {
475             throw new ResourceNotFoundException("No content field value could be found on field with ID=" + requestedField.getId());
476         }
477
478         // first look for binary data
479
String JavaDoc template = ConvertUtil.convertToString(templateValue.getValue());
480
481         // if not found, look for simple value
482
if ( template == null ) {
483             template = templateValue.getSimpleValue();
484         }
485
486         template = "mailTemplate: " + template;
487
488         if ( encodeModel ) {
489             model = StringUtil.htmlEncodeModel(model);
490         }
491         result = VelocityEngineUtils.mergeTemplateIntoString(velocityEngine, template, model);
492
493         return result;
494     }
495
496     /**
497      * Checks CFV for emptyness
498      *
499      * @param value Content field value to check for emptyness
500      * @return <code>true</code> if simple value is null or an empty string and value is null or an empty array. Otherwise, returns <code>false</code>
501      */

502     protected boolean isEmpty(ContentFieldValue value) {
503         return (value.getSimpleValue() == null || value.getSimpleValue().length() == 0) && (value.getValue() == null || value.getValue().length == 0);
504     }
505
506     /**
507      * Wrapper class for content resource which can return resource as input stream
508      */

509     protected static class InputStreamContentResource implements InputStreamSource {
510
511         /**
512          * Wrapped content resource
513          */

514         protected ContentResource contentResource;
515
516         /**
517          * Creates new instance and wraps specified content resource
518          *
519          * @param contentResource Content resource to wrap
520          */

521         public InputStreamContentResource(ContentResource contentResource) {
522             this.contentResource = contentResource;
523         }
524
525         /**
526          * @see org.springframework.core.io.InputStreamSource#getInputStream()
527          */

528         public InputStream JavaDoc getInputStream() throws IOException JavaDoc {
529             InputStream JavaDoc stream = null;
530             if ( contentResource != null ) {
531                 stream = new ByteArrayInputStream JavaDoc(contentResource.getResourceData().getData());
532             }
533             return stream;
534         }
535     }
536 }
537
Popular Tags