diff --git a/api/src/main/java/org/sakaiproject/email/api/Attachment.java b/api/src/main/java/org/sakaiproject/email/api/Attachment.java index 79e8653..1006b7f 100644 --- a/api/src/main/java/org/sakaiproject/email/api/Attachment.java +++ b/api/src/main/java/org/sakaiproject/email/api/Attachment.java @@ -17,42 +17,94 @@ package org.sakaiproject.email.api; import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import javax.activation.DataSource; import javax.activation.FileDataSource; /** * Holds an attachment for an email message. The attachment will be included with the message. * - * TODO: Make available for attachments to be stored in CHS. - * + * @see javax.activation.DataSource + * @see javax.activation.FileDataSource * @author Carl Hall */ public class Attachment { /** - * files to associated to this attachment + * file to associated to this attachment + */ + private final DataSource dataSource; + + /** + * The Content-Type and Content-Disposition MIME headers to be sent with the attachment. + * Can be null. + */ + private final String contentDisposition; + private final String contentType; + + public enum ContentDisposition {INLINE, ATTACHMENT} + + /** + * Creates an Attachment with some of the MIME headers specified. + * + * @param dataSource the data source + * @param contentType the Content-Type header, can be null + * @param disposition the Content-Disposition header, can be null + */ + public Attachment(DataSource dataSource, String contentType, ContentDisposition disposition) + { + this.dataSource = dataSource; + this.contentType = contentType; + this.contentDisposition = disposition == null ? null : disposition.toString().toLowerCase(); + } + + /** + * Creates an Attachment. + * + * @param dataSource The data source to use for the attachment. */ - private DataSource dataSource; - private final String filename; + public Attachment(DataSource dataSource) + { + this(dataSource, null, null); + } + /** + * Creates a attachment based on a file. + * @param file The file to load the contents of the attachement from and to get the mimetype + * from. + * @param filename The filename to call the attachment when sent out, doesn't have to match + * the file from which the content is loaded. + * @deprecated {@link org.sakaiproject.email.api.Attachment#Attachment(javax.activation.DataSource)} + */ public Attachment(File file, String filename) { - this(new FileDataSource(file),filename); + this(new RenamedDataSource(new FileDataSource(file), filename)); } - public Attachment(DataSource dataSource,String filename) { - this.dataSource = dataSource; - this.filename = filename; + + /** + * Creates an attachment supplying an different filename. + * @param dataSource The data source. + * @param filename The alternative filename. + */ + public Attachment(DataSource dataSource, String filename) + { + this(new RenamedDataSource(dataSource, filename), null, null); } + /** * Get the file associated to this attachment * - * @return file associated with this attachment (created from the DataSource) + * @return file associated with this attachment (created from the DataSource) or null if there + * isn't an underlying file for this attachment. + * @deprecated As not all Attachments will have an underlying file this method shouldn't be used. */ public File getFile() { - return ((FileDataSource)dataSource).getFile(); + return (dataSource instanceof FileDataSource)?((FileDataSource)dataSource).getFile():null; } /** @@ -62,17 +114,73 @@ public class Attachment */ public String getFilename() { - return filename; + return dataSource.getName(); } - + + /** + * The Content-Type MIME header for the attachment, can be null. + * This does not return the mime type of the data source as you may wish to supply a different one + * or no content-type at all. + * + * @return the Content-Type header + */ + public String getContentTypeHeader() + { + return contentType; + } + /** * Get the datasource of the attachment * * @return datasource of the attachment */ - public DataSource getDataSource() - { - return dataSource; + public DataSource getDataSource() + { + return dataSource; } -} \ No newline at end of file + /** + * The Content-Disposition MIME header for the attachment, can be null. + * + * @return the Content-Disposition header + */ + public String getContentDispositionHeader() + { + return contentDisposition; + } + + /** + * Class which can be used when you wish to rename a file when adding it as an attachment. + * All calls are passed through to the original data source apart from the name. + */ + public static class RenamedDataSource implements DataSource { + + private final DataSource dataSource; + private final String name; + + public RenamedDataSource(DataSource dataSource, String name) { + this.dataSource = dataSource; + this.name = name; + } + + @Override + public InputStream getInputStream() throws IOException { + return dataSource.getInputStream(); + } + + @Override + public OutputStream getOutputStream() throws IOException { + return dataSource.getOutputStream(); + } + + @Override + public String getContentType() { + return dataSource.getContentType(); + } + + @Override + public String getName() { + return name; + } + } +} diff --git a/api/src/main/java/org/sakaiproject/email/api/EmailHeaders.java b/api/src/main/java/org/sakaiproject/email/api/EmailHeaders.java index c16e792..33fa88d 100644 --- a/api/src/main/java/org/sakaiproject/email/api/EmailHeaders.java +++ b/api/src/main/java/org/sakaiproject/email/api/EmailHeaders.java @@ -21,4 +21,5 @@ public interface EmailHeaders String IN_REPLY_TO = "In-Reply-To"; String LIST_ID = "List-Id"; String MESSAGE_ID = "Message-Id"; + String MULTIPART_SUBTYPE = "Multipart-Subtype"; } \ No newline at end of file diff --git a/kernel-impl/src/main/java/org/sakaiproject/email/impl/BasicEmailService.java b/kernel-impl/src/main/java/org/sakaiproject/email/impl/BasicEmailService.java index ecbd05a..3645419 100644 --- a/kernel-impl/src/main/java/org/sakaiproject/email/impl/BasicEmailService.java +++ b/kernel-impl/src/main/java/org/sakaiproject/email/impl/BasicEmailService.java @@ -507,6 +507,11 @@ public class BasicEmailService implements EmailService // Content-Type: text/plain; charset=windows-1252; format=flowed String contentTypeHeader = null; + // If we need to force the container to use a certain multipart subtype + // e.g. 'alternative' + // then sneak it through in the additionalHeaders + String multipartSubtype = null; + // set the additional headers on the message // but treat Content-Type specially as we need to check the charset // and we already dealt with the message id @@ -516,6 +521,8 @@ public class BasicEmailService implements EmailService { if (header.toLowerCase().startsWith(EmailHeaders.CONTENT_TYPE.toLowerCase() + ": ")) contentTypeHeader = header; + else if (header.toLowerCase().startsWith(EmailHeaders.MULTIPART_SUBTYPE.toLowerCase() + ": ")) + multipartSubtype = header.substring(header.indexOf(":") + 1).trim(); else if (!header.toLowerCase().startsWith(EmailHeaders.MESSAGE_ID.toLowerCase() + ": ")) msg.addHeaderLine(header); } @@ -588,7 +595,7 @@ public class BasicEmailService implements EmailService int colonPos = contentTypeHeader.indexOf(":"); contentType = contentTypeHeader.substring(colonPos + 1).trim(); } - setContent(content, attachments, msg, contentType, charset); + setContent(content, attachments, msg, contentType, charset, multipartSubtype); // if we have a full Content-Type header, set it NOW // (after setting the body of the message so that format=flowed is preserved) @@ -1198,15 +1205,10 @@ public class BasicEmailService implements EmailService /** * Sets the content for a message. Also attaches files to the message. - * - * @param content - * @param attachments - * @param msg - * @param charset * @throws MessagingException */ protected void setContent(String content, List attachments, MimeMessage msg, - String contentType, String charset) throws MessagingException + String contentType, String charset, String multipartSubtype) throws MessagingException { ArrayList embeddedAttachments = new ArrayList(); if (attachments != null && attachments.size() > 0) @@ -1232,7 +1234,7 @@ public class BasicEmailService implements EmailService else { // create a multipart container - Multipart multipart = new MimeMultipart(); + Multipart multipart = (multipartSubtype != null) ? new MimeMultipart(multipartSubtype) : new MimeMultipart(); // create a body part for the message text MimeBodyPart msgBodyPart = new MimeBodyPart(); @@ -1257,8 +1259,7 @@ public class BasicEmailService implements EmailService /** * Attaches a file as a body part to the multipart message - * - * @param multipart + * * @param attachment * @throws MessagingException */ @@ -1266,8 +1267,18 @@ public class BasicEmailService implements EmailService { DataSource source = attachment.getDataSource(); MimeBodyPart attachPart = new MimeBodyPart(); + attachPart.setDataHandler(new DataHandler(source)); attachPart.setFileName(attachment.getFilename()); + + if (attachment.getContentTypeHeader() != null) { + attachPart.setHeader("Content-Type", attachment.getContentTypeHeader()); + } + + if (attachment.getContentDispositionHeader() != null) { + attachPart.setHeader("Content-Disposition", attachment.getContentDispositionHeader()); + } + return attachPart; }