Using Spring 3.0, I wrote an interface to hide from the application which template engine I was using for sending mail. The interface is
public interface MailSender {
public void send(String[] to, String from, String subject, String template,
Map<Object, Object> model, boolean isHtml,
Map<String, Resource> attachments,
Map<String, Resource> inline) throws MailException;
}
Then the Velocity implementation is
public class MailSenderVelocity implements MailSender {
private JavaMailSender sender;
private VelocityEngine engine;
@Override
public void send(final String[] to, final String from,
final String subject, final String template,
final Map<Object, Object> model, final boolean isHtml,
final Map<String, Resource> attachments,
final Map<String, Resource> inline)
throws com.engrid.mail.MailException {
try {
// prepare the message to send
MimeMessagePreparator preparator = new MimeMessagePreparator() {
@Override
public void prepare(MimeMessage mimeMessage) throws Exception {
// determine if the message needs to be multipart or not
boolean multipart = (attachments != null && !attachments.isEmpty()) || (inline != null && !inline.isEmpty());
// create the message
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, multipart);
// populate header fields
helper.setTo(to);
helper.setFrom(from);
helper.setSubject(subject);
// fetch template and merge model
String text = VelocityEngineUtils.mergeTemplateIntoString(engine, template, model);
helper.setText(text, isHtml);
// add inline attachments
if (inline != null) {
for (Entry<String, Resource> entry : inline.entrySet()) {
helper.addInline(entry.getKey(), entry.getValue());
}
}
// add other attachments
if (attachments != null) {
for (Entry<String, Resource> entry : attachments.entrySet()) {
helper.addAttachment(entry.getKey(), entry.getValue());
}
}
}
};
// send the email
this.sender.send(preparator);
} catch(MailException e) {
// error preparing or sending the message
e.printStackTrace();
throw new com.engrid.mail.MailException(e);
}
}
public void setSender(JavaMailSender sender) {
this.sender = sender;
}
public void setEngine(VelocityEngine engine) {
this.engine = engine;
}
}
Being a good programmer, I wrote test cases for different scenarios to ensure everything works correctly. One of my cases is when an attachment is specified but the file itself does not exist, specifically
@Test(expected=MailException.class)
public void testSend_MissingAttachment() throws IOException, MailException {
// create new object
MailSenderVelocity testSender = new MailSenderVelocity();
testSender.setEngine(engine);
testSender.setSender(sender);
// assign properties
String subject = "testSend_MissingAttachment";
String template = "simple.vm";
Map<Object, Object> model = new HashMap<Object, Object>();
model.put("user", "User");
boolean isHtml = true;
Map<String, Resource> attachments = new HashMap<String, Resource>();
String file = "notext.txt";
attachments.put(file, templateDirectory.createRelative(file));
Map<String, Resource> inline = null;
// send a message -- will fail because it won't find the file to attach
testSender.send(this.to, this.from, subject, template, model, isHtml, attachments, inline);
}
Now, the test results in the expected exception being thrown
org.springframework.mail.MailSendException: Failed messages: javax.mail.MessagingException: IOException while sending message;
nested exception is:
java.io.FileNotFoundException: class path resource [com/company/mail/template/notext.txt] cannot be opened because it does not exist; message exception details (1) are:
Failed message 1:
javax.mail.MessagingException: IOException while sending message;
nested exception is:
java.io.FileNotFoundException: class path resource [com/company/mail/template/notext.txt] cannot be opened because it does not exist
at com.sun.mail.smtp.SMTPTransport.sendMessage(SMTPTransport.java:625)
at org.springframework.mail.javamail.JavaMailSenderImpl.doSend(JavaMailSenderImpl.java:416)
at org.springframework.mail.javamail.JavaMailSenderImpl.send(JavaMailSenderImpl.java:340)
at org.springframework.mail.javamail.JavaMailSenderImpl.send(JavaMailSenderImpl.java:355)
at org.springframework.mail.javamail.JavaMailSenderImpl.send(JavaMailSenderImpl.java:344)
at com.company.mail.MailSenderVelocity.send(MailSenderVelocity.java:103)
at com.company.mail.MailSenderVelocity.send(MailSenderVelocity.java:53)
at com.company.mail.MailSenderVelocityTest.testSend_MissingAttachment(MailSenderVelocityTest.java:136)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:48)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:37)
at java.lang.reflect.Method.invoke(Method.java:600)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
at org.junit.internal.runners.statements.ExpectException.evaluate(ExpectException.java:22)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:74)
at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:31)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:82)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:72)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:240)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:46)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:180)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:41)
at org.junit.runners.ParentRunner$1.evaluate(ParentRunner.java:173)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:31)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner.run(ParentRunner.java:220)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:180)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:45)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:460)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:673)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:386)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:196)
Caused by: java.io.FileNotFoundException: class path resource [com/company/mail/template/notext.txt] cannot be opened because it does not exist
at org.springframework.core.io.ClassPathResource.getInputStream(ClassPathResource.java:158)
at org.springframework.mail.javamail.MimeMessageHelper$1.getInputStream(MimeMessageHelper.java:1086)
at javax.activation.DataHandler.writeTo(DataHandler.java:302)
at javax.mail.internet.MimeBodyPart.writeTo(MimeBodyPart.java:1350)
at javax.mail.internet.MimeBodyPart.writeTo(MimeBodyPart.java:845)
at javax.mail.internet.MimeMultipart.writeTo(MimeMultipart.java:361)
at com.sun.mail.handlers.multipart_mixed.writeTo(multipart_mixed.java:85)
at javax.activation.ObjectDataContentHandler.writeTo(DataHandler.java:881)
at javax.activation.DataHandler.writeTo(DataHandler.java:314)
at javax.mail.internet.MimeBodyPart.writeTo(MimeBodyPart.java:1350)
at javax.mail.internet.MimeMessage.writeTo(MimeMessage.java:1683)
at com.sun.mail.smtp.SMTPTransport.sendMessage(SMTPTransport.java:585)
... 38 more
2010-11-11 13:50:30,261
However, an email message still gets sent out to the listed recipient from the listed sender. All other things (subject, body, etc) are blank. How do I stop the email being sent when an exception is generated while building the email.
Interesting. It seems that Sun’s internal JavaMail implementation is tolerating exceptions being thrown by the attachment handlers, and is carrying on regardless.
The easiest solution I can think of is to do a bit of up-front validation of your attachment resources, e.g.
This will eagerly check that the
Resourceis available. You can also useResource.exists()for this sort of thing if you choose.