diff --git a/common/source/java/ch/systemsx/cisd/common/mail/MailClient.java b/common/source/java/ch/systemsx/cisd/common/mail/MailClient.java index d7aae010938f97fbf6aa8b909561f8438db2a5f6..8caa0cb2727a6a112b794827b8938b429f124b5f 100644 --- a/common/source/java/ch/systemsx/cisd/common/mail/MailClient.java +++ b/common/source/java/ch/systemsx/cisd/common/mail/MailClient.java @@ -16,22 +16,27 @@ package ch.systemsx.cisd.common.mail; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.util.Arrays; import java.util.Properties; +import javax.activation.DataHandler; import javax.mail.Address; import javax.mail.Authenticator; import javax.mail.Message; import javax.mail.MessagingException; +import javax.mail.Multipart; import javax.mail.PasswordAuthentication; import javax.mail.SendFailedException; import javax.mail.Session; import javax.mail.Transport; import javax.mail.internet.AddressException; import javax.mail.internet.InternetAddress; +import javax.mail.internet.MimeBodyPart; import javax.mail.internet.MimeMessage; +import javax.mail.internet.MimeMultipart; import org.apache.log4j.Logger; @@ -159,8 +164,63 @@ public final class MailClient extends Authenticator implements IMailClient * * @param recipients list of recipients (of type <code>Message.RecipientType.TO</code>) */ - public final void sendMessage(String subject, String content, String replyTo, From fromOrNull, - String... recipients) throws EnvironmentFailureException + public final void sendMessage(final String subject, final String content, final String replyTo, + final From fromOrNull, final String... recipients) throws EnvironmentFailureException + { + IMessagePreparer messagePreparer = new IMessagePreparer() + { + public void prepareMessage(MimeMessage msg) throws MessagingException + { + msg.setText(content); + } + }; + privateSendMessage(messagePreparer, subject, content, replyTo, fromOrNull, recipients); + } + + /** + * Sends a mail with given <var>subject</var> and <var>content</var> to given + * <var>recipients</var>, includig the given <var>attachment</var> + * + * @param recipients list of recipients (of type <code>Message.RecipientType.TO</code>) + */ + public final void sendMessageWithAttachment(final String subject, final String content, + final String filename, final DataHandler attachmentContent, final String replyTo, + final From fromOrNull, final String... recipients) throws EnvironmentFailureException + { + IMessagePreparer messagePreparer = new IMessagePreparer() + { + + public void prepareMessage(MimeMessage msg) throws MessagingException + { + // Create a MIME message with 2 parts: text + attachments + Multipart multipart = new MimeMultipart(); + + // Create the text + MimeBodyPart messageText = new MimeBodyPart(); + messageText.setText(content); + multipart.addBodyPart(messageText); + + // Create the attachment + MimeBodyPart messageAttachment = new MimeBodyPart(); + messageAttachment.setDataHandler(attachmentContent); + messageAttachment.setFileName(filename); + multipart.addBodyPart(messageAttachment); + + msg.setContent(multipart); + } + }; + privateSendMessage(messagePreparer, subject, content, replyTo, fromOrNull, recipients); + } + + /** + * Sends a mail with given <var>subject</var> and <var>content</var> to given + * <var>recipients</var>. + * + * @param recipients list of recipients (of type <code>Message.RecipientType.TO</code>) + */ + private final void privateSendMessage(IMessagePreparer messagePreparerOrNull, String subject, + String content, String replyTo, From fromOrNull, String[] recipients) + throws EnvironmentFailureException { String fromPerMail = fromOrNull != null ? fromOrNull.getValue() : from; if (operationLog.isInfoEnabled()) @@ -186,7 +246,10 @@ public final class MailClient extends Authenticator implements IMailClient } msg.addRecipients(Message.RecipientType.TO, internetAddresses); msg.setSubject(subject, UNICODE_CHARSET); - msg.setText(content, UNICODE_CHARSET); + if (null != messagePreparerOrNull) + { + messagePreparerOrNull.prepareMessage(msg); + } send(msg); } catch (MessagingException ex) { @@ -220,43 +283,60 @@ public final class MailClient extends Authenticator implements IMailClient { if (smtpHost.startsWith(FILE_PREFIX)) { - File emailFolder = new File(smtpHost.substring(FILE_PREFIX.length())); - if (emailFolder.exists()) + // We don't have a real SMTP server + writeMessageToFile(msg); + } else + { + // We are dealing with a real SMTP server -- use the Transport + Transport.send(msg); + } + } + + private void writeMessageToFile(MimeMessage msg) throws MessagingException + { + File emailFolder = new File(smtpHost.substring(FILE_PREFIX.length())); + if (emailFolder.exists()) + { + if (emailFolder.isDirectory() == false) { - if (emailFolder.isDirectory() == false) - { - throw new EnvironmentFailureException( - "There exists already a file but not a folder with path '" - + emailFolder.getAbsolutePath() + "'."); - } - } else + throw new EnvironmentFailureException( + "There exists already a file but not a folder with path '" + + emailFolder.getAbsolutePath() + "'."); + } + } else + { + if (emailFolder.mkdirs() == false) { - if (emailFolder.mkdirs() == false) - { - throw new EnvironmentFailureException("Couldn't create email folder '" - + emailFolder.getAbsolutePath() + "'."); - } + throw new EnvironmentFailureException("Couldn't create email folder '" + + emailFolder.getAbsolutePath() + "'."); } - File file = FileUtilities.createNextNumberedFile(new File(emailFolder, "email"), null); - StringBuilder builder = new StringBuilder(); - builder.append("Subj: ").append(msg.getSubject()).append('\n'); - builder.append("From: ").append(renderAddresses(msg.getFrom())).append('\n'); - builder.append("To: ").append(renderAddresses(msg.getAllRecipients())).append('\n'); - builder.append("Reply-To: ").append(renderAddresses(msg.getReplyTo())).append('\n'); - builder.append("Content:\n"); - try + } + File file = FileUtilities.createNextNumberedFile(new File(emailFolder, "email"), null); + StringBuilder builder = new StringBuilder(); + builder.append("Subj: ").append(msg.getSubject()).append('\n'); + builder.append("From: ").append(renderAddresses(msg.getFrom())).append('\n'); + builder.append("To: ").append(renderAddresses(msg.getAllRecipients())).append('\n'); + builder.append("Reply-To: ").append(renderAddresses(msg.getReplyTo())).append('\n'); + builder.append("Content:\n"); + try + { + Object content = msg.getContent(); + // If this is a mime message, handle the printing a bit differently + if (content instanceof Multipart) { - Object content = msg.getContent(); - builder.append(content); - } catch (IOException ex) + Multipart multipart = (Multipart) content; + ByteArrayOutputStream os = new ByteArrayOutputStream(); + multipart.writeTo(os); + builder.append(os.toString()); + } else { - throw CheckedExceptionTunnel.wrapIfNecessary(ex); + builder.append(content); } - FileUtilities.writeToFile(file, builder.toString()); - } else + } catch (IOException ex) { - Transport.send(msg); + throw CheckedExceptionTunnel.wrapIfNecessary(ex); } + FileUtilities.writeToFile(file, builder.toString()); } private String renderAddresses(Address[] addresses) @@ -285,4 +365,14 @@ public final class MailClient extends Authenticator implements IMailClient { return new PasswordAuthentication(smtpUsername, smtpPassword); } + + /** + * Interface for closures that prepare and email messages. + * + * @author Chandrasekhar Ramakrishnan + */ + private static interface IMessagePreparer + { + void prepareMessage(MimeMessage msg) throws MessagingException; + } } diff --git a/common/sourceTest/java/ch/systemsx/cisd/common/mail/MailClientTest.java b/common/sourceTest/java/ch/systemsx/cisd/common/mail/MailClientTest.java index 820c41aac1ad126110e3ae16aa8d68372a045a89..b37e81f19084021f71cac9c2cd6b859c7822074c 100644 --- a/common/sourceTest/java/ch/systemsx/cisd/common/mail/MailClientTest.java +++ b/common/sourceTest/java/ch/systemsx/cisd/common/mail/MailClientTest.java @@ -19,6 +19,8 @@ package ch.systemsx.cisd.common.mail; import java.io.File; import java.util.Arrays; +import javax.activation.DataHandler; + import org.testng.annotations.Test; import ch.systemsx.cisd.base.tests.AbstractFileSystemTestCase; @@ -76,4 +78,44 @@ public final class MailClientTest extends AbstractFileSystemTestCase } + @Test + public final void testAttachments() + { + String path = workingDirectory.getPath() + "/emails"; + File emailFolder = new File(path); + assert emailFolder.exists() == false; + + MailClient mailClient = new MailClient("sender", "file://" + path); + + DataHandler attachment = + new DataHandler("name.first = First Name\nname.last = Last Name", + "application/octet-stream"); + mailClient.sendMessageWithAttachment("some message", "Hello world\nHow are you today?", + "file.properties", attachment, "user@reply.com", null, "a@b.c", "d@e.f"); + + assert emailFolder.exists(); + assert emailFolder.isDirectory(); + File[] files = emailFolder.listFiles(); + assertEquals(1, files.length); + assertEquals("email", files[0].getName()); + String fileContent = FileUtilities.loadToString(files[0]); + + // Split the file into lines and check one line at a time + String[] lines = fileContent.split("\n+"); + assertEquals(lines.length, 13); + assertEquals(lines[0], "Subj: some message"); + assertEquals(lines[1], "From: sender"); + assertEquals(lines[2], "To: a@b.c, d@e.f"); + assertEquals(lines[3], "Reply-To: user@reply.com"); + assertEquals(lines[4], "Content:"); + assertTrue(lines[5].startsWith("------=_Part_0")); + assertEquals(lines[6], "Hello world"); + assertEquals(lines[7], "How are you today?"); + + assertTrue(lines[8].startsWith("------=_Part_0")); + assertEquals(lines[9], "Content-Disposition: attachment; filename=file.properties"); + assertEquals(lines[10], "name.first = First Name"); + assertEquals(lines[11], "name.last = Last Name"); + assertTrue(lines[12].startsWith("------=_Part_0")); + } }