Author: bspeakmon Date: Sat Jun 9 16:46:00 2007 New Revision: 545815 URL: http://svn.apache.org/viewvc?view=rev&rev=545815 Log: - EMAIL-35: Embedding raw DataSource Attachments - added embed methods for File objects - Javadoc fixes - delete temp files created during test runs
Modified: jakarta/commons/proper/email/trunk/src/java/org/apache/commons/mail/HtmlEmail.java jakarta/commons/proper/email/trunk/src/test/org/apache/commons/mail/HtmlEmailTest.java Modified: jakarta/commons/proper/email/trunk/src/java/org/apache/commons/mail/HtmlEmail.java URL: http://svn.apache.org/viewvc/jakarta/commons/proper/email/trunk/src/java/org/apache/commons/mail/HtmlEmail.java?view=diff&rev=545815&r1=545814&r2=545815 ============================================================================== --- jakarta/commons/proper/email/trunk/src/java/org/apache/commons/mail/HtmlEmail.java (original) +++ jakarta/commons/proper/email/trunk/src/java/org/apache/commons/mail/HtmlEmail.java Sat Jun 9 16:46:00 2007 @@ -16,6 +16,7 @@ */ package org.apache.commons.mail; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; @@ -25,6 +26,8 @@ import java.util.Map; import javax.activation.DataHandler; +import javax.activation.DataSource; +import javax.activation.FileDataSource; import javax.activation.URLDataSource; import javax.mail.BodyPart; import javax.mail.MessagingException; @@ -38,17 +41,41 @@ * can also be set for HTML unaware email clients, such as text-based * email clients. * - * <p>This class also inherits from MultiPartEmail, so it is easy to + * <p>This class also inherits from [EMAIL PROTECTED] MultiPartEmail}, so it is easy to * add attachments to the email. * - * <p>To send an email in HTML, one should create a HtmlEmail, then - * use the setFrom, addTo, etc. methods. The HTML content can be set - * with the setHtmlMsg method. The alternative text content can be set - * with setTextMsg. + * <p>To send an email in HTML, one should create a <code>HtmlEmail</code>, then + * use the [EMAIL PROTECTED] #setFrom(String)}, [EMAIL PROTECTED] #addTo(String)} etc. methods. + * The HTML content can be set with the [EMAIL PROTECTED] #setHtmlMsg(String)} method. The + * alternative text content can be set with [EMAIL PROTECTED] #setTextMsg(String)}. * * <p>Either the text or HTML can be omitted, in which case the "main" * part of the multipart becomes whichever is supplied rather than a - * multipart/alternative. + * <code>multipart/alternative</code>. + * + * <h3>Embedding Images and Media</h3> + * + * <p>It is also possible to embed URLs, files, or arbitrary + * <code>DataSource</code>s directly into the body of the mail: + * <pre><code> + * HtmlEmail he = new HtmlEmail(); + * File img = new File("my/image.gif"); + * PNGDataSource png = new PNGDataSource(decodedPNGOutputStream); // a custom class + * StringBuffer msg = new StringBuffer(); + * msg.append("<html><body>"); + * msg.append("<img src=cid:").append(he.embed(img)).append(">"); + * msg.append("<img src=cid:").append(he.embed(png)).append(">"); + * msg.append("</body></html>"); + * he.setHtmlMsg(msg.toString()); + * // code to set the other email fields (not shown) + * </pre></code> + * + * <p>Embedded entities are tracked by their name, which for <code>File</code>s is + * the filename itself and for <code>URL</code>s is the canonical path. It is + * an error to bind the same name to more than one entity, and this class will + * attempt to validate that for <code>File</code>s and <code>URL</code>s. When + * embedding a <code>DataSource</code>, the code uses the <code>equals()</code> + * method defined on the <code>DataSource</code>s to make the determination. * * @since 1.0 * @author <a href="mailto:unknown">Regis Koenig</a> @@ -76,8 +103,8 @@ protected String html; /** - * Embedded images Map<String,InlineImages> where the key is the - * user-defined image name + * Embedded images Map<String, InlineImage> where the key is the + * user-defined image name. */ protected Map inlineImages = new HashMap(); @@ -97,7 +124,7 @@ throw new EmailException("Invalid message supplied"); } - this.text = aText; + this.text = aText; return this; } @@ -124,16 +151,16 @@ /** * Set the message. * - * <p>This method overrides the MultiPartEmail setMsg() method in - * order to send an HTML message instead of a full text message in + * <p>This method overrides [EMAIL PROTECTED] MultiPartEmail#setMsg(String)} in + * order to send an HTML message instead of a plain text message in * the mail body. The message is formatted in HTML for the HTML - * part of the message, it is let as is in the alternate text + * part of the message; it is left as is in the alternate text * part. * - * @param msg A String. - * @return An Email. - * @throws EmailException see javax.mail.internet.MimeBodyPart - * for definitions + * @param msg the message text to use + * @return this <code>HtmlEmail</code> + * @throws EmailException if msg is null or empty; + * see javax.mail.internet.MimeBodyPart for definitions * @since 1.0 */ public Email setMsg(String msg) throws EmailException @@ -161,22 +188,23 @@ } /** - * Embeds an URL in the HTML. + * Attempts to parse the specified <code>String</code> as a URL that will + * then be embedded in the message. * - * @param url The URL of the file. + * @param urlString String representation of the URL. * @param name The name that will be set in the filename header field. - * @return A String with the Content-ID of the file. - * @throws EmailException when URL supplied is invalid - * also see javax.mail.internet.MimeBodyPart for definitions + * @return A String with the Content-ID of the URL. + * @throws EmailException when URL supplied is invalid or if <code> is null + * or empty; also see [EMAIL PROTECTED] javax.mail.internet.MimeBodyPart} for definitions * * @see #embed(URL, String) * @since 1.1 */ - public String embed(String url, String name) throws EmailException + public String embed(String urlString, String name) throws EmailException { try { - return embed(new URL(url), name); + return embed(new URL(urlString), name); } catch (MalformedURLException e) { @@ -187,20 +215,19 @@ /** * Embeds an URL in the HTML. * - * <p>This method allows to embed a file located by an URL into - * the mail body. It allows, for instance, to add inline images + * <p>This method embeds a file located by an URL into + * the mail body. It allows, for instance, to add inline images * to the email. Inline files may be referenced with a * <code>cid:xxxxxx</code> URL, where xxxxxx is the Content-ID * returned by the embed function. It is an error to bind the same name - * to more than one URL. + * to more than one URL; if the same URL is embedded multiple times, the + * same Content-ID is guaranteed to be returned. * - * <p>Example of use:<br><code><pre> - * HtmlEmail he = new HtmlEmail(); - * he.setHtmlMsg("<html><img src=cid:" + - * embed(new URL("file:/my/image.gif"),"image.gif") + - * "></html>"); - * // code to set the others email fields (not shown) - * </pre></code> + * <p>While functionally the same as passing <code>URLDataSource</code> to + * [EMAIL PROTECTED] #embed(DataSource, String, String)}, this method attempts + * to validate the URL before embedding it in the message and will throw + * <code>EmailException</code> if the validation fails. In this case, the + * <code>HtmlEmail</code> object will not be changed. * * <p> * NOTE: Clients should take care to ensure that different URLs are bound to @@ -213,29 +240,33 @@ * @param name The name that will be set in the filename header * field. * @return A String with the Content-ID of the file. - * @throws EmailException when URL supplied is invalid - * also see javax.mail.internet.MimeBodyPart for definitions + * @throws EmailException when URL supplied is invalid or if <code> is null + * or empty; also see [EMAIL PROTECTED] javax.mail.internet.MimeBodyPart} for definitions * @since 1.0 */ public String embed(URL url, String name) throws EmailException { - InlineImage ii = null; + if (EmailUtils.isEmpty(name)) + { + throw new EmailException("name cannot be null or empty"); + } - // check if the URL contents have already been attached; + // check if a URLDataSource for this name has already been attached; // if so, return the cached CID value. if (inlineImages.containsKey(name)) { - ii = (InlineImage) inlineImages.get(name); + InlineImage ii = (InlineImage) inlineImages.get(name); + URLDataSource urlDataSource = (URLDataSource) ii.getDataSource(); // make sure the supplied URL points to the same thing // as the one already associated with this name. - if (url.equals(ii.getURL())) + if (url.equals(urlDataSource.getURL())) { return ii.getCid(); } else { - throw new EmailException("embedded file name '" + name - + " is already bound to URL " + ii.getURL().toString() + throw new EmailException("embedded name '" + name + + "' is already bound to URL " + urlDataSource.getURL() + "; existing names cannot be rebound"); } // NOTE: Comparing URLs with URL.equals() is known to be @@ -245,27 +276,207 @@ } // verify that the URL is valid + InputStream is = null; try { - InputStream is = url.openStream(); - is.close(); + is = url.openStream(); } catch (IOException e) { throw new EmailException("Invalid URL", e); } + finally + { + try + { + if (is != null) + { + is.close(); + } + } + catch (IOException ioe) { /* sigh */ } + } + + return embed(new URLDataSource(url), name); + } + + /** + * Embeds a file in the HTML. This implementation delegates to + * [EMAIL PROTECTED] #embed(File, String)}. + * + * @param file The <code>File</code> object to embed + * @return A String with the Content-ID of the file. + * @throws EmailException when the supplied <code>File</code> cannot be + * used; also see [EMAIL PROTECTED] javax.mail.internet.MimeBodyPart} for definitions + * + * @see #embed(File, String) + * @since 1.1 + */ + public String embed(File file) throws EmailException + { + String cid = EmailUtils.randomAlphabetic(HtmlEmail.CID_LENGTH).toLowerCase(); + return embed(file, cid); + } + + /** + * Embeds a file in the HTML. + * + * <p>This method embeds a file located by an URL into + * the mail body. It allows, for instance, to add inline images + * to the email. Inline files may be referenced with a + * <code>cid:xxxxxx</code> URL, where xxxxxx is the Content-ID + * returned by the embed function. Files are bound to their names, which is + * the value returned by [EMAIL PROTECTED] java.io.File#getName()}. If the same file + * is embedded multiple times, the same CID is guaranteed to be returned. + * + * <p>While functionally the same as passing <code>FileDataSource</code> to + * [EMAIL PROTECTED] #embed(DataSource, String, String)}, this method attempts + * to validate the file before embedding it in the message and will throw + * <code>EmailException</code> if the validation fails. In this case, the + * <code>HtmlEmail</code> object will not be changed. + * + * @param file The <code>File</code> to embed + * @param cid the Content-ID to use for the embedded <code>File</code> + * @return A String with the Content-ID of the file. + * @throws EmailException when the supplied <code>File</code> cannot be used + * or if the file has already been embedded; + * also see [EMAIL PROTECTED] javax.mail.internet.MimeBodyPart} for definitions + * @since 1.1 + */ + public String embed(File file, String cid) throws EmailException + { + if (EmailUtils.isEmpty(file.getName())) + { + throw new EmailException("file name cannot be null or empty"); + } + + // verify that the File can provide a canonical path + String filePath = null; + try + { + filePath = file.getCanonicalPath(); + } + catch (IOException ioe) + { + throw new EmailException("couldn't get canonical path for " + + file.getName(), ioe); + } + + // check if a FileDataSource for this name has already been attached; + // if so, return the cached CID value. + if (inlineImages.containsKey(file.getName())) + { + InlineImage ii = (InlineImage) inlineImages.get(file.getName()); + FileDataSource fileDataSource = (FileDataSource) ii.getDataSource(); + // make sure the supplied file has the same canonical path + // as the one already associated with this name. + String existingFilePath = null; + try + { + existingFilePath = fileDataSource.getFile().getCanonicalPath(); + } + catch (IOException ioe) + { + throw new EmailException("couldn't get canonical path for file " + + fileDataSource.getFile().getName() + + "which has already been embedded", ioe); + } + if (filePath.equals(existingFilePath)) + { + return ii.getCid(); + } + else + { + throw new EmailException("embedded name '" + file.getName() + + "' is already bound to file " + existingFilePath + + "; existing names cannot be rebound"); + } + } + + // verify that the file is valid + if (!file.exists()) + { + throw new EmailException("file " + filePath + " doesn't exist"); + } + if (!file.isFile()) + { + throw new EmailException("file " + filePath + " isn't a normal file"); + } + if (!file.canRead()) + { + throw new EmailException("file " + filePath + " isn't readable"); + } + + return embed(new FileDataSource(file), file.getName()); + } + + /** + * Embeds the specified <code>DataSource</code> in the HTML using a + * randomly generated Content-ID. Returns the generated Content-ID string. + * + * @param dataSource the <code>DataSource</code> to embed + * @param name the name that will be set in the filename header field + * @return the generated Content-ID for this <code>DataSource</code> + * @throws EmailException if the embedding fails or if <code>name</code> is + * null or empty + * @see #embed(DataSource, String, String) + * @since 1.1 + */ + public String embed(DataSource dataSource, String name) throws EmailException + { + // check if the DataSource has already been attached; + // if so, return the cached CID value. + if (inlineImages.containsKey(name)) + { + InlineImage ii = (InlineImage) inlineImages.get(name); + // make sure the supplied URL points to the same thing + // as the one already associated with this name. + if (dataSource.equals(ii.getDataSource())) + { + return ii.getCid(); + } + else + { + throw new EmailException("embedded DataSource '" + name + + "' is already bound to name " + ii.getDataSource().toString() + + "; existing names cannot be rebound"); + } + } + + String cid = EmailUtils.randomAlphabetic(HtmlEmail.CID_LENGTH).toLowerCase(); + return embed(dataSource, name, cid); + } + + /** + * Embeds the specified <code>DataSource</code> in the HTML using the + * specified Content-ID. Returns the specified Content-ID string. + * + * @param dataSource the <code>DataSource</code> to embed + * @param name the name that will be set in the filename header field + * @param cid the Content-ID to use for this <code>DataSource</code> + * @return the supplied Content-ID for this <code>DataSource</code> + * @throws EmailException if the embedding fails or if <code>name</code> is + * null or empty + * @since 1.1 + */ + public String embed(DataSource dataSource, String name, String cid) + throws EmailException + { + if (EmailUtils.isEmpty(name)) + { + throw new EmailException("name cannot be null or empty"); + } MimeBodyPart mbp = new MimeBodyPart(); try { - mbp.setDataHandler(new DataHandler(new URLDataSource(url))); + mbp.setDataHandler(new DataHandler(dataSource)); mbp.setFileName(name); mbp.setDisposition("inline"); - String cid = EmailUtils.randomAlphabetic(HtmlEmail.CID_LENGTH).toLowerCase(); mbp.setContentID("<" + cid + ">"); - ii = new InlineImage(cid, url, mbp); + InlineImage ii = new InlineImage(cid, dataSource, mbp); this.inlineImages.put(name, ii); return cid; @@ -382,8 +593,8 @@ { /** content id */ private String cid; - /** URL that points to the content */ - private URL url; + /** <code>DataSource</code> for the content */ + private DataSource dataSource; /** the <code>MimeBodyPart</code> that contains the encoded data */ private MimeBodyPart mbp; @@ -391,14 +602,14 @@ * Creates an InlineImage object to represent the * specified content ID and <code>MimeBodyPart</code>. * @param cid the generated content ID - * @param url the URL that points to the content + * @param dataSource the <code>DataSource</code> that represents the content * @param mbp the <code>MimeBodyPart</code> that contains the encoded * data */ - public InlineImage(String cid, URL url, MimeBodyPart mbp) + public InlineImage(String cid, DataSource dataSource, MimeBodyPart mbp) { this.cid = cid; - this.url = url; + this.dataSource = dataSource; this.mbp = mbp; } @@ -412,12 +623,12 @@ } /** - * Returns the URL that points to the encoded content. - * @return the URL pointing to the encoded content + * Returns the <code>DataSource</code> that represents the encoded content. + * @return the <code>DataSource</code> representing the encoded content */ - public URL getURL() + public DataSource getDataSource() { - return url; + return dataSource; } /** Modified: jakarta/commons/proper/email/trunk/src/test/org/apache/commons/mail/HtmlEmailTest.java URL: http://svn.apache.org/viewvc/jakarta/commons/proper/email/trunk/src/test/org/apache/commons/mail/HtmlEmailTest.java?view=diff&rev=545815&r1=545814&r2=545815 ============================================================================== --- jakarta/commons/proper/email/trunk/src/test/org/apache/commons/mail/HtmlEmailTest.java (original) +++ jakarta/commons/proper/email/trunk/src/test/org/apache/commons/mail/HtmlEmailTest.java Sat Jun 9 16:46:00 2007 @@ -20,6 +20,8 @@ import java.io.IOException; import java.net.URL; +import javax.activation.FileDataSource; + import org.apache.commons.mail.mocks.MockHtmlEmailConcrete; import org.apache.commons.mail.settings.EmailConfiguration; @@ -154,7 +156,7 @@ * * @throws Exception Exception */ - public void testEmbed() throws Exception + public void testEmbedUrl() throws Exception { // ==================================================================== // Test Success @@ -205,6 +207,87 @@ } } + public void testEmbedFile() throws Exception + { + // ==================================================================== + // Test Success + // ==================================================================== + + File file = File.createTempFile("testEmbedFile", "txt"); + file.deleteOnExit(); + String strEmbed = this.email.embed(file); + assertNotNull(strEmbed); + assertEquals("generated CID has wrong length", + HtmlEmail.CID_LENGTH, strEmbed.length()); + + // if we embed the same file again, do we get the same content ID + // back? + String testCid = + this.email.embed(file); + assertEquals("didn't get same CID after embedding same file twice", + strEmbed, testCid); + + // if we embed a new file, is the content ID unique? + File otherFile = File.createTempFile("testEmbedFile2", "txt"); + otherFile.deleteOnExit(); + String newCid = this.email.embed(otherFile); + assertFalse("didn't get unique CID from embedding new file", + strEmbed.equals(newCid)); + } + + public void testEmbedUrlAndFile() throws Exception + { + File tmpFile = File.createTempFile("testfile", "txt"); + tmpFile.deleteOnExit(); + String fileCid = this.email.embed(tmpFile); + + URL fileUrl = tmpFile.toURL(); + String urlCid = this.email.embed(fileUrl, "urlName"); + + assertFalse("file and URL cids should be different even for same resource", + fileCid.equals(urlCid)); + } + + public void testEmbedDataSource() throws Exception + { + File tmpFile = File.createTempFile("testEmbedDataSource", "txt"); + tmpFile.deleteOnExit(); + FileDataSource dataSource = new FileDataSource(tmpFile); + + // does embedding a datasource without a name fail? + try + { + this.email.embed(dataSource, ""); + fail("embedding with an empty string for a name should fail"); + } + catch (EmailException e) + { + // expected + } + + // properly embed the datasource + String cid = this.email.embed(dataSource, "testname"); + + // does embedding the same datasource under the same name return + // the original cid? + String sameCid = this.email.embed(dataSource, "testname"); + assertEquals("didn't get same CID for embedding same datasource twice", + cid, sameCid); + + // does embedding another datasource under the same name fail? + File anotherFile = File.createTempFile("testEmbedDataSource2", "txt"); + anotherFile.deleteOnExit(); + FileDataSource anotherDS = new FileDataSource(anotherFile); + try + { + this.email.embed(anotherDS, "testname"); + } + catch (EmailException e) + { + // expected + } + } + /** * @throws EmailException when bad addresses and attachments are used * @throws IOException if creating a temp file, URL or sending fails @@ -216,6 +299,7 @@ /** File to used to test file attachments (Must be valid) */ testFile = File.createTempFile("commons-email-testfile", ".txt"); + testFile.deleteOnExit(); // ==================================================================== // Test Success --------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]