org.apache.commons.mail.HtmlEmail Maven / Gradle / Ivy
Show all versions of commons-email Show documentation
/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.commons.mail; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; import java.net.URL; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Locale; 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; import javax.mail.internet.MimeBodyPart; import javax.mail.internet.MimeMultipart; /** * An HTML multipart email. * *
* *This class is used to send HTML formatted email. A text message * can also be set for HTML unaware email clients, such as text-based * email clients. * *
This class also inherits from {@link MultiPartEmail}, so it is easy to * add attachments to the email. * *
To send an email in HTML, one should create a
HtmlEmail
, then * use the {@link #setFrom(String)}, {@link #addTo(String)} etc. methods. * The HTML content can be set with the {@link #setHtmlMsg(String)} method. The * alternative text content can be set with {@link #setTextMsg(String)}. * *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
. * *Embedding Images and Media
* *It is also possible to embed URLs, files, or arbitrary *
DataSource
s directly into the body of the mail: ** 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) *
Embedded entities are tracked by their name, which for
File
s is * the filename itself and forURL
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 forFile
s andURL
s. When * embedding aDataSource
, the code uses theequals()
* method defined on theDataSource
s to make the determination. * * @since 1.0 * @author Regis Koenig * @author Sean Legassick * @version $Id: HtmlEmail.java 1421492 2012-12-13 20:25:53Z tn $ */ public class HtmlEmail extends MultiPartEmail { /** Definition of the length of generated CID's. */ public static final int CID_LENGTH = 10; /** prefix for default HTML mail. */ private static final String HTML_MESSAGE_START = ""; /** suffix for default HTML mail. */ private static final String HTML_MESSAGE_END = ""; /** * Text part of the message. This will be used as alternative text if * the email client does not support HTML messages. */ protected String text; /** Html part of the message. */ protected String html; /** * @deprecated As of commons-email 1.1, no longer used. Inline embedded * objects are now stored in {@link #inlineEmbeds}. */ @Deprecated protected ListinlineImages; /** * Embedded images Map where the key is the * user-defined image name. */ protected Map inlineEmbeds = new HashMap (); /** * Set the text content. * * @param aText A String. * @return An HtmlEmail. * @throws EmailException see javax.mail.internet.MimeBodyPart * for definitions * @since 1.0 */ public HtmlEmail setTextMsg(String aText) throws EmailException { if (EmailUtils.isEmpty(aText)) { throw new EmailException("Invalid message supplied"); } this.text = aText; return this; } /** * Set the HTML content. * * @param aHtml A String. * @return An HtmlEmail. * @throws EmailException see javax.mail.internet.MimeBodyPart * for definitions * @since 1.0 */ public HtmlEmail setHtmlMsg(String aHtml) throws EmailException { if (EmailUtils.isEmpty(aHtml)) { throw new EmailException("Invalid message supplied"); } this.html = aHtml; return this; } /** * Set the message. * * This method overrides {@link 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 left as is in the alternate text * part. * * @param msg the message text to use * @return this
HtmlEmail
* @throws EmailException if msg is null or empty; * see javax.mail.internet.MimeBodyPart for definitions * @since 1.0 */ @Override public Email setMsg(String msg) throws EmailException { if (EmailUtils.isEmpty(msg)) { throw new EmailException("Invalid message supplied"); } setTextMsg(msg); StringBuffer htmlMsgBuf = new StringBuffer( msg.length() + HTML_MESSAGE_START.length() + HTML_MESSAGE_END.length() ); htmlMsgBuf.append(HTML_MESSAGE_START) .append(msg) .append(HTML_MESSAGE_END); setHtmlMsg(htmlMsgBuf.toString()); return this; } /** * Attempts to parse the specifiedString
as a URL that will * then be embedded in the message. * * @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 URL. * @throws EmailException when URL supplied is invalid or if {@code name} is null * or empty; also see {@link javax.mail.internet.MimeBodyPart} for definitions * * @see #embed(URL, String) * @since 1.1 */ public String embed(String urlString, String name) throws EmailException { try { return embed(new URL(urlString), name); } catch (MalformedURLException e) { throw new EmailException("Invalid URL", e); } } /** * Embeds an URL in the HTML. * *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 *
cid:xxxxxx
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; if the same URL is embedded multiple times, the * same Content-ID is guaranteed to be returned. * *While functionally the same as passing
URLDataSource
to * {@link #embed(DataSource, String, String)}, this method attempts * to validate the URL before embedding it in the message and will throw *EmailException
if the validation fails. In this case, the *HtmlEmail
object will not be changed. * ** NOTE: Clients should take care to ensure that different URLs are bound to * different names. This implementation tries to detect this and throw *
EmailException
. However, it is not guaranteed to catch * all cases, especially when the URL refers to a remote HTTP host that * may be part of a virtual host cluster. * * @param url The URL of the file. * @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 or if {@code name} is null * or empty; also see {@link javax.mail.internet.MimeBodyPart} for definitions * @since 1.0 */ public String embed(URL url, String name) throws EmailException { if (EmailUtils.isEmpty(name)) { throw new EmailException("name cannot be null or empty"); } // check if a URLDataSource for this name has already been attached; // if so, return the cached CID value. if (inlineEmbeds.containsKey(name)) { InlineImage ii = inlineEmbeds.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. // NOTE: Comparing URLs with URL.equals() is a blocking operation // in the case of a network failure therefore we use // url.toExternalForm().equals() here. if (url.toExternalForm().equals(urlDataSource.getURL().toExternalForm())) { return ii.getCid(); } else { throw new EmailException("embedded name '" + name + "' is already bound to URL " + urlDataSource.getURL() + "; existing names cannot be rebound"); } } // verify that the URL is valid InputStream is = null; try { is = url.openStream(); } catch (IOException e) { throw new EmailException("Invalid URL", e); } finally { try { if (is != null) { is.close(); } } catch (IOException ioe) // NOPMD { /* sigh */ } } return embed(new URLDataSource(url), name); } /** * Embeds a file in the HTML. This implementation delegates to * {@link #embed(File, String)}. * * @param file TheFile
object to embed * @return A String with the Content-ID of the file. * @throws EmailException when the suppliedFile
cannot be * used; also see {@link 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(Locale.ENGLISH); return embed(file, cid); } /** * Embeds a file in the HTML. * *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 *
cid:xxxxxx
URL, where xxxxxx is the Content-ID * returned by the embed function. Files are bound to their names, which is * the value returned by {@link java.io.File#getName()}. If the same file * is embedded multiple times, the same CID is guaranteed to be returned. * *While functionally the same as passing
FileDataSource
to * {@link #embed(DataSource, String, String)}, this method attempts * to validate the file before embedding it in the message and will throw *EmailException
if the validation fails. In this case, the *HtmlEmail
object will not be changed. * * @param file TheFile
to embed * @param cid the Content-ID to use for the embeddedFile
* @return A String with the Content-ID of the file. * @throws EmailException when the suppliedFile
cannot be used * or if the file has already been embedded; * also see {@link 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 (inlineEmbeds.containsKey(file.getName())) { InlineImage ii = inlineEmbeds.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(), cid); } /** * Embeds the specifiedDataSource
in the HTML using a * randomly generated Content-ID. Returns the generated Content-ID string. * * @param dataSource theDataSource
to embed * @param name the name that will be set in the filename header field * @return the generated Content-ID for thisDataSource
* @throws EmailException if the embedding fails or ifname
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 (inlineEmbeds.containsKey(name)) { InlineImage ii = inlineEmbeds.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 specifiedDataSource
in the HTML using the * specified Content-ID. Returns the specified Content-ID string. * * @param dataSource theDataSource
to embed * @param name the name that will be set in the filename header field * @param cid the Content-ID to use for thisDataSource
* @return the URL encoded Content-ID for thisDataSource
* @throws EmailException if the embedding fails or ifname
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 { // url encode the cid according to rfc 2392 cid = EmailUtils.encodeUrl(cid); mbp.setDataHandler(new DataHandler(dataSource)); mbp.setFileName(name); mbp.setDisposition(EmailAttachment.INLINE); mbp.setContentID("<" + cid + ">"); InlineImage ii = new InlineImage(cid, dataSource, mbp); this.inlineEmbeds.put(name, ii); return cid; } catch (MessagingException me) { throw new EmailException(me); } catch (UnsupportedEncodingException uee) { throw new EmailException(uee); } } /** * Does the work of actually building the MimeMessage. Please note that * a user rarely calls this method directly and only if he/she is * interested in the sending the underlying MimeMessage without * commons-email. * * @exception EmailException if there was an error. * @since 1.0 */ @Override public void buildMimeMessage() throws EmailException { try { build(); } catch (MessagingException me) { throw new EmailException(me); } super.buildMimeMessage(); } /** * @throws EmailException EmailException * @throws MessagingException MessagingException */ private void build() throws MessagingException, EmailException { MimeMultipart rootContainer = this.getContainer(); MimeMultipart bodyEmbedsContainer = rootContainer; MimeMultipart bodyContainer = rootContainer; BodyPart msgHtml = null; BodyPart msgText = null; rootContainer.setSubType("mixed"); // determine how to form multiparts of email if (EmailUtils.isNotEmpty(this.html) && this.inlineEmbeds.size() > 0) { //If HTML body and embeds are used, create a related container and add it to the root container bodyEmbedsContainer = new MimeMultipart("related"); bodyContainer = bodyEmbedsContainer; this.addPart(bodyEmbedsContainer, 0); //If TEXT body was specified, create a alternative container and add it to the embeds container if (EmailUtils.isNotEmpty(this.text)) { bodyContainer = new MimeMultipart("alternative"); BodyPart bodyPart = createBodyPart(); try { bodyPart.setContent(bodyContainer); bodyEmbedsContainer.addBodyPart(bodyPart, 0); } catch (MessagingException me) { throw new EmailException(me); } } } else if (EmailUtils.isNotEmpty(this.text) && EmailUtils.isNotEmpty(this.html)) { //If both HTML and TEXT bodies are provided, create a alternative container and add it to the root container bodyContainer = new MimeMultipart("alternative"); this.addPart(bodyContainer, 0); } if (EmailUtils.isNotEmpty(this.html)) { msgHtml = new MimeBodyPart(); bodyContainer.addBodyPart(msgHtml, 0); // apply default charset if one has been set if (EmailUtils.isNotEmpty(this.charset)) { msgHtml.setContent( this.html, EmailConstants.TEXT_HTML + "; charset=" + this.charset); } else { msgHtml.setContent(this.html, EmailConstants.TEXT_HTML); } Iteratoriter = this.inlineEmbeds.values().iterator(); while (iter.hasNext()) { InlineImage ii = iter.next(); bodyEmbedsContainer.addBodyPart(ii.getMbp()); } } if (EmailUtils.isNotEmpty(this.text)) { msgText = new MimeBodyPart(); bodyContainer.addBodyPart(msgText, 0); // apply default charset if one has been set if (EmailUtils.isNotEmpty(this.charset)) { msgText.setContent( this.text, EmailConstants.TEXT_PLAIN + "; charset=" + this.charset); } else { msgText.setContent(this.text, EmailConstants.TEXT_PLAIN); } } } /** * Private bean class that encapsulates data about URL contents * that are embedded in the final email. * @since 1.1 */ private static class InlineImage { /** content id. */ private String cid; /** DataSource
for the content. */ private DataSource dataSource; /** theMimeBodyPart
that contains the encoded data. */ private MimeBodyPart mbp; /** * Creates an InlineImage object to represent the * specified content ID andMimeBodyPart
. * @param cid the generated content ID * @param dataSource theDataSource
that represents the content * @param mbp theMimeBodyPart
that contains the encoded * data */ public InlineImage(String cid, DataSource dataSource, MimeBodyPart mbp) { this.cid = cid; this.dataSource = dataSource; this.mbp = mbp; } /** * Returns the unique content ID of this InlineImage. * @return the unique content ID of this InlineImage */ public String getCid() { return cid; } /** * Returns theDataSource
that represents the encoded content. * @return theDataSource
representing the encoded content */ public DataSource getDataSource() { return dataSource; } /** * Returns theMimeBodyPart
that contains the * encoded InlineImage data. * @return theMimeBodyPart
containing the encoded * InlineImage data */ public MimeBodyPart getMbp() { return mbp; } // equals()/hashCode() implementations, since this class // is stored as a entry in a Map. /** * {@inheritDoc} * @return true if the other object is also an InlineImage with the same cid. */ @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof InlineImage)) { return false; } InlineImage that = (InlineImage) obj; return this.cid.equals(that.cid); } /** * {@inheritDoc} * @return the cid hashCode. */ @Override public int hashCode() { return cid.hashCode(); } } }