Tuesday 21 August 2012

How to use JavaMail to Send EMail with Embedded Images

A lot of us use email clients that are setup to not display external images by default.  We do this so that those senders of spam don't know that we have read the email.  The downside is that a lot of our incoming email looks rubbish.

I assume that most of the email that gets through the spam filter is not trying to track me, and that the use of external images is simply a lack of understanding of how to use embedded images.


Here is my example of using JavaMail to send email with embedded inline images:
// HTML we want to send.
// Note the "cid:" that precedes the image reference.
String html =
        "<html>" +
        "  <head>" +
        "    <style type='text/css'>" +
        "      body {background-color:blue}" +
        "      p {color:white}" +
        "    </style>" +
        "  </head>" +
        "  <body>" +
        "    <p><img src='cid:icon'></p>" +
        "    <p>Dear Bill</p>\n" +
        "    <p>This is a test mail.</p>" +
        "  </body>" +
        "</html>";

// Create Email content
// Use related (rather than mixed) mime subtype,
// otherwise Thunderbird will not display images inline.
Multipart multipart = new MimeMultipart("related");

// Create the HTML message part and add it to the email content.
MimeBodyPart messageBodyPart = new MimeBodyPart();
messageBodyPart.setContent(html, "text/html");
multipart.addBodyPart(messageBodyPart);

// Read the image from a file and add it to the email content.
// Note the "<>" added around the content ID used in the HTML above.
// Setting Content-Type does not seem to be required.
// I guess it is determined from the file type
MimeBodyPart iconBodyPart = new MimeBodyPart();
DataSource iconDataSource = new FileDataSource(new File("./icon.jpg"));
iconBodyPart.setDataHandler(new DataHandler(iconDataSource));
iconBodyPart.setDisposition(Part.INLINE);
iconBodyPart.setContentID("<icon>");
iconBodyPart.addHeader("Content-Type", "image/jpeg");
multipart.addBodyPart(iconBodyPart);

// Create the connection to the SMTP server to send the email.
final String smtpAccountUsername = "myUsername";
final String smtpAccountPassword = "myPassword";
final String smtpServer = "smtp.mymailserver.com";
final String smtpServerPort = "587";

Properties props = new Properties();
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.starttls.enable", "true");
props.put("mail.smtp.host", smtpServer);
props.put("mail.smtp.port", smtpServerPort);

Session session = Session.getInstance(props,
    new javax.mail.Authenticator() {
        @Override
        protected PasswordAuthentication getPasswordAuthentication() {
            return new PasswordAuthentication(
                    smtpAccountUsername, smtpAccountPassword);
        }
    });

// Create email, set the content and send.
MimeMessage mail = new MimeMessage(session);
mail.setRecipients(RecipientType.TO, InternetAddress.parse("bill@example.com"));
mail.setSubject("Mail Subject Line");
mail.setContent(multipart);

Transport.send(mail);

Sunday 19 August 2012

How to use XInclude in XSLT stylesheets

XInclude is not enabled by default when using the built-in XSLT processor in Java 1.6.  The underlying Apache Xalan uses Apache Xerces which does support it, but this is not directly accessible through the standard API.

Here is an example of how we would normally use XSLT:

// During app startup
TransformerFactory transformerFactory =
                TransformerFactory.newInstance();
Templates stylesheet = transformerFactory.newTemplates(
                new StreamSource("stylesheet.xsl"));

// Transformation
Source source = new StreamSource(new File("input.xml"));
Result result = new StreamResult(new File("output.xml"));
stylesheet.newTransformer().transform(source, result);
To use XInclude within an XSLT stylesheet we need to read it using a DocumentBuilder which has been created a DocumentBuilderFactory.  This has the methods that allows us to enable XInclude.
// During app startup
DocumentBuilderFactory documentBuilderFactory =
                DocumentBuilderFactory.newInstance(); documentBuilderFactory.setXIncludeAware(true);
documentBuilderFactory.setNamespaceAware(true);
// We don't want xml:base and xml:lang attributes in the output.
documentBuilderFactory.setFeature(
                "http://apache.org/xml/features/xinclude/fixup-base-uris", false);
documentBuilderFactory.setFeature(
                "http://apache.org/xml/features/xinclude/fixup-language", false);

DocumentBuilder docBuilder =
                documentBuilderFactory.newDocumentBuilder();
Document stylesheetDoc = docBuilder.parse("stylesheet.xsl");
Source stylesheetSource = new DOMSource(stylesheetDoc);

TransformerFactory transformerFactory =
                TransformerFactory.newInstance();
Templates stylesheet =
                transformerFactory.newTemplates(stylesheetSource);

// Transformation
Source source = new StreamSource(new File("input.xml"));
Result result = new StreamResult(new File("output.xml"));
stylesheet.newTransformer().transform(source, result);
It is bit more verbose than the orginal, but this is the only way I have found to do this.
The stylesheet this uses might look like this:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
      xmlns:xi="http://www.w3.org/2001/XInclude"
      exclude-result-prefixes="xi xsl xml">
  <xsl:output method="html" />

  <xsl:template match="/myRootElement">
        <xi:include href="mail.html" parse="xml" />
  </xsl:template>

</xsl:stylesheet>
And the included sub-stylesheet that uses the data elements within the myRootElements might be:
  <html xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   <body>
     <p><xsl:value-of select="name"/></p>
     <p><xsl:value-of select="email"/></p>
     <ul>
       <xsl:for-each select="list/a">
         <li><xsl:value-of select="."/></li>
       </xsl:for-each>
     </ul>
   </body>
  </html>
Of course you may wish to use XInclude within the input XML, in which case you need to build a DOMSource for the transformation in the same way.