分类: C/C++
2008-05-07 09:19:20
Let's define an interface for mailers based on a template engine (such as Velocity and FreeMarker).
import java.util.Map;
import java.util.List;
public interface TemplateMailer {
/**
* Send a mail with both a text and a HTML version.
* @param email the email address where to send the email
* @param context a {@link Map} of objects to expose to the template engine
* @param templatePrefix the prefix of the templates to use
*/
void mail(String email, Map context, String templatePrefix);
/**
* Send a mass-mailing with both a text anda HTML version.
* @param emails a {@link List} of email addresses where to send emails
* @param contexts a {@link List} of {@link Map}s of objects to expose to the template engine
* @param templatePrefix the prefix of the templates to us
*/
void massMail(List emails, List contexts, String templatePrefix);
}
The template based mailer should be able to send a multipart mail with both a text and HTML version of the mail, so that old email clients (or the ones not able to display HTML) will still be able to read the email.
The following article (http://java.sun.com/developer/EJTechTips/2004/tt0625.html#1) is a prerequisite for the comprehension of the code.
Spring already provides a FreeMarkerConfigurer class that gives access to FreeMarker's Configuration object, so the implementation will have a dependency on a JavaMailSender (required in order to be able to send multipart emails) and the FreeMarkerConfigurer.
As explained in the reference documentation, a MimeMessagePreparator will do the job.
MimeMessagePreparator preparator = new MimeMessagePreparator() {
public void prepare(MimeMessage mimeMessage) throws MessagingException, IOException {
mimeMessage.setRecipient(Message.RecipientType.TO, new InternetAddress(email));
mimeMessage.setFrom(new InternetAddress("test@test.com"));
mimeMessage.setSubject("Wonderful subject!");
// this is where the templating stuff has to be done
}
};
mailSender.send(preparator);
The mailer should be able to send both a text and HTML version, so it will need a template for each version.
Let's prepare the parts of the message to send:
Multipart mp = new MimeMultipart("alternative");
// Create a "text" Multipart message
BodyPart textPart = new MimeBodyPart();
textPart.setDataHandler(new DataHandler(new DataSource() {
public InputStream getInputStream() throws IOException {
return new StringBufferInputStream(textWriter.toString());
}
public OutputStream getOutputStream() throws IOException {
throw new IOException("Read-only data");
}
public String getContentType() {
return "text/plain";
}
public String getName() {
return "main";
}
}));
mp.addBodyPart(textPart);
// Create a "HTML" Multipart message
Multipart htmlContent = new MimeMultipart("related");
BodyPart htmlPage = new MimeBodyPart();
htmlPage.setDataHandler(new DataHandler(new DataSource() {
public InputStream getInputStream() throws IOException {
return new StringBufferInputStream(htmlWriter.toString());
}
public OutputStream getOutputStream() throws IOException {
throw new IOException("Read-only data");
}
public String getContentType() {
return "text/html";
}
public String getName() {
return "main";
}
}));
htmlContent.addBodyPart(htmlPage);
BodyPart htmlPart = new MimeBodyPart();
htmlPart.setContent(htmlContent);
mp.addBodyPart(htmlPart);
mimeMessage.setContent(mp);
In order to keep the interface simple, the mailer assumes that there is a common prefix for both templates names. If you give the prefix XXX, the mailer will look for a template named XXX-text.ftl and another named XXX-html.ftl. If you look carefully at the previous example, you will notice that we need to fill two StringWriters (one for each version). This is the template engine that will do it for us:
Template textTemplate = configuration.getTemplate(templatePrefix + "-text.ftl");
final StringWriter textWriter = new StringWriter();
try {
textTemplate.process(context, textWriter);
} catch (TemplateException e) {
throw new MailPreparationException("Can't generate text subscription mail", e);
}
The same work has to be done for the HTML version.
The client code will simply have to define a dependency on the template-based mailer.
Suppose we wish to send an email with a confirmation of user subscription to a service:
// mail the subscription
Map context = new HashMap();
context.put("user", user);
templateMailer.mail(user.getEmail(), context, "/users-subscription");
You'll have to write a users-subscription-text.ftl and users-subscription-html.ftl template.
Here are some possible candidates:
Thank you ${user.firstName} ${user.lastName} for your subscription to our website.
Thank you ${user.firstName} ${user.lastName} for your subscription to our website.
MimeMessagePreparator preparator = new MimeMessagePreparator() {
public void prepare(MimeMessage mimeMessage) throws MessagingException, IOException {
mimeMessage.setRecipient(Message.RecipientType.TO, new InternetAddress(email));
mimeMessage.setFrom(new InternetAddress("info@kalixia.com"));
mimeMessage.setSubject("Votre inscription au site kalixia.com");
Multipart mp = new MimeMultipart("alternative");
// Create a "text" Multipart message
BodyPart textPart = new MimeBodyPart();
Template textTemplate = configuration.getTemplate(templatePrefix + "-text.ftl");
final StringWriter textWriter = new StringWriter();
try {
textTemplate.process(context, textWriter);
} catch (TemplateException e) {
throw new MailPreparationException("Can't generate text subscription mail", e);
}
textPart.setDataHandler(new DataHandler(new DataSource() {
public InputStream getInputStream() throws IOException {
return new StringBufferInputStream(textWriter.toString());
}
public OutputStream getOutputStream() throws IOException {
throw new IOException("Read-only data");
}
public String getContentType() {
return "text/plain";
}
public String getName() {
return "main";
}
}));
mp.addBodyPart(textPart);
// Create a "HTML" Multipart message
Multipart htmlContent = new MimeMultipart("related");
BodyPart htmlPage = new MimeBodyPart();
Template htmlTemplate = configuration.getTemplate(templatePrefix + "-html.ftl");
final StringWriter htmlWriter = new StringWriter();
try {
htmlTemplate.process(context, htmlWriter);
} catch (TemplateException e) {
throw new MailPreparationException("Can't generate HTML subscription mail", e);
}
htmlPage.setDataHandler(new DataHandler(new DataSource() {
public InputStream getInputStream() throws IOException {
return new StringBufferInputStream(htmlWriter.toString());
}
public OutputStream getOutputStream() throws IOException {
throw new IOException("Read-only data");
}
public String getContentType() {
return "text/html";
}
public String getName() {
return "main";
}
}));
htmlContent.addBodyPart(htmlPage);
BodyPart htmlPart = new MimeBodyPart();
htmlPart.setContent(htmlContent);
mp.addBodyPart(htmlPart);
mimeMessage.setContent(mp);
}
};
mailSender.send(preparator);
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessagePreparator;
import org.springframework.mail.MailPreparationException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMultipart;
import javax.mail.internet.MimeBodyPart;
import javax.mail.MessagingException;
import javax.mail.Message;
import javax.mail.Multipart;
import javax.mail.BodyPart;
import javax.activation.DataHandler;
import javax.activation.DataSource;
import java.util.Map;
import java.util.Iterator;
import java.util.List;
import java.io.*;
/**
* @spring.bean id="templateMailer"
*/
public class FreeMarkerTemplateMailerImpl implements TemplateMailer {
private JavaMailSender mailSender = null;
private Configuration configuration = null;
private static final Log LOGGER = LogFactory.getLog(FreeMarkerTemplateMailerImpl.class);
public void mail(final String email, final Map context, final String templatePrefix) {
MimeMessagePreparator preparator = new MimeMessagePreparator() {
public void prepare(MimeMessage mimeMessage) throws MessagingException, IOException {
mimeMessage.setRecipient(Message.RecipientType.TO, new InternetAddress(email));
mimeMessage.setFrom(new InternetAddress("info@kalixia.com"));
mimeMessage.setSubject("Votre inscription au site kalixia.com");
Multipart mp = new MimeMultipart("alternative");
// Create a "text" Multipart message
BodyPart textPart = new MimeBodyPart();
Template textTemplate = configuration.getTemplate(templatePrefix + "-text.ftl");
final StringWriter textWriter = new StringWriter();
try {
textTemplate.process(context, textWriter);
} catch (TemplateException e) {
throw new MailPreparationException("Can't generate text subscription mail", e);
}
textPart.setDataHandler(new DataHandler(new DataSource() {
public InputStream getInputStream() throws IOException {
return new StringBufferInputStream(textWriter.toString());
}
public OutputStream getOutputStream() throws IOException {
throw new IOException("Read-only data");
}
public String getContentType() {
return "text/plain";
}
public String getName() {
return "main";
}
}));
mp.addBodyPart(textPart);
// Create a "HTML" Multipart message
Multipart htmlContent = new MimeMultipart("related");
BodyPart htmlPage = new MimeBodyPart();
Template htmlTemplate = configuration.getTemplate(templatePrefix + "-html.ftl");
final StringWriter htmlWriter = new StringWriter();
try {
htmlTemplate.process(context, htmlWriter);
} catch (TemplateException e) {
throw new MailPreparationException("Can't generate HTML subscription mail", e);
}
htmlPage.setDataHandler(new DataHandler(new DataSource() {
public InputStream getInputStream() throws IOException {
return new StringBufferInputStream(htmlWriter.toString());
}
public OutputStream getOutputStream() throws IOException {
throw new IOException("Read-only data");
}
public String getContentType() {
return "text/html";
}
public String getName() {
return "main";
}
}));
htmlContent.addBodyPart(htmlPage);
BodyPart htmlPart = new MimeBodyPart();
htmlPart.setContent(htmlContent);
mp.addBodyPart(htmlPart);
mimeMessage.setContent(mp);
}
};
mailSender.send(preparator);
}
public void massMail(List emails, List contexts, String templatePrefix) {
int i = 0;
for (Iterator iterator = emails.iterator(); iterator.hasNext(); i++) {
String email = (String) iterator.next();
mail(email, (Map) contexts.get( i ), templatePrefix);
}
}
/** @spring.property ref="mailSender" */
public void setMailSender(JavaMailSender mailSender) {
this.mailSender = mailSender;
}
/** @spring.property ref="freemarkerConfig" */
public void setFreeMarkerConfigurer(FreeMarkerConfigurer freeMarkerConfigurer) {
this.configuration = freeMarkerConfigurer.getConfiguration();
}
}