Wednesday 9 June 2010

JavaMail, Commons Mail or Spring Mail - Which does the job?

I thought since the previous culprit focuses on e-mail, it would be essential if I hit the nail on the head with choosing the right API for your e-mail function. For a starter, building e-mail function can be a little fustrating task.

If you are like most developers, you delve in JavaMail API thinking that is it, you have found a boiler plate code which does the job. You then go ahead to customize it to suite your enviroment, finally deployed it to realise your pain has just began.

Take for for instance below JavaMail API generic code:

Email Interface
  public interface EmailEngine
  {
    public void MailTransport(String from, String to, String cc, String subject,
     String message, String doc, String bcc) throws Exception;
  }

Actual Implementation
 public class EmailEngineImpl implements EmailEngine

    public final void MailTransport(String from, String to, String cc, String subject,
     String message, String doc, String bcc)
    {
      Properties props = new Properties();
      props.put(PropsFactory.PropsLookup("mail.host.props"), 
               PropsFactory.PropsLookup("mail.host"));
      Session session = Session.getInstance(props);
      try
      {
        Message msg = new MimeMessage(session);
        MimeMultipart mpRoot = new MimeMultipart();
        MimeMultipart mpAl = new MimeMultipart("alternative");
        MimeBodyPart content = new MimeBodyPart();

        msg.setSubject(subject);
        msg.setSentDate(Today.getDate());
        msg.setFrom(new InternetAddress(from));
        msg.setRecipients(Message.RecipientType.TO, getAddress(to));

        if (cc != null)
        {
          msg.setRecipients(Message.RecipientType.CC, getAddress(cc));
        }
        if (!(bcc == null))
        {
          msg.setRecipients(Message.RecipientType.BCC, getAddress(bcc));
        }
        
        if (doc != null)
        {
          MimeBodyPart p1 = new MimeBodyPart();
          Resource file = new ClassPathResource(doc);
          FileDataSource fds = new FileDataSource(file.getFile())
          {
            public String getContentType()
            {
              return "application/octet-stream";
            }
          };

          p1.setDataHandler(new DataHandler(fds));
          p1.setFileName(fds.getName());
          mpRoot.addBodyPart(p1);
        }
        MimeBodyPart p2 = new MimeBodyPart();
        p2.setText(message);
        mpAl.addBodyPart(p2);

        content.setContent(mpAl);
        mpRoot.addBodyPart(content);

        msg.setContent(mpRoot);
        msg.saveChanges();

        Transport.send(msg);
      }
      catch (Exception ex)
      {
        ex.printStackTrace();
      }
    }
  }
Everything looks fine except you deploy it to realise, Some Email clients typically outlook will work fine but little issues with others. And so you dig around to uncover you'll have to follow the MimeMultiPart structure, WHAP!

You have little time to waste therefore decided to try native Spring Email API. Bravo! Take time to read the API document, did you see the line which says:


Warning regarding multipart mails: Simple MIME messages that just contain HTML text but no inline elements or attachments will work on more or less any email client that is capable of HTML rendering. However, inline elements and attachments are still a major compatibility issue between email clients: It's virtually impossible to get inline elements and attachments working across Microsoft Outlook, Lotus Notes and Mac Mail. Consider choosing a specific multipart mode for your needs: The javadoc on the MULTIPART_MODE constants contains more detailed information.

You avoid the warning and implement Spring Generic Email API as shown below, perhaps tweaking static MULTIPART_MODE to see if it does support various multi email clients. WGL!
NB. Inject org.springframework.mail.javamail.JavaMailSender

  public final void MailTransport(String from, String to, String cc, 
   String subject, String content, String doc, String bcc)
  {
    try
    {
      MimeMessage message = mailSender.createMimeMessage();
      // use the true flag to indicate you need a multipart message
      MimeMessageHelper helper = new MimeMessageHelper(message, true);
      helper.setFrom(from);
      helper.setTo(getAddress(to));
      helper.setSubject(subject.toString());

      if (!(cc == null))
      {
        helper.setCc(getAddress(cc));
      }
      if (!(bcc == null))
      {
        helper.setBcc(getAddress(bcc));
      }

      if (!(doc == null))
      {
        Resource file = new ClassPathResource(doc);
        helper.addAttachment(file.getFilename(), file);
      }

      helper.setText(content);
      mailSender.send(message);
    }
    catch (Exception ex)
    {
      ex.printStackTrace();
    }
  }

If you do have luck with either Generic JavaMail or Spring implementation, I suppose your first reaction will be, why bother trying Apache Commons Email! You want MHO, go ahead and try it. It saves time and does the job. Well each of these API has it flaws, but TMHO Apache Commons Email ticks most boxes. Get the dependency or the jar file onto your classpath and try this;


  public class EmailEngineImpl implements EmailEngine
  {
    public final void MailTransport(String from, String to, String cc, String subject, String message, String doc, String bcc)
    {
      // Create Multi-Part document
      MultiPartEmail email = new MultiPartEmail();
      try
      {
        if (!(doc == null))
        {
          // Create the attachment
          EmailAttachment attachment = new EmailAttachment();
          Resource file = new ClassPathResource(doc);
          
          attachment.setURL(file.getURL());
          attachment.setDisposition(EmailAttachment.ATTACHMENT);
          attachment.setName(file.getFilename());

          // add the attachment
          email.attach(attachment);
        }

        // Set Mail server location
        email.setHostName(PropsFactory.PropsLookup("mail.host"));
        email.setTo(getAddress(to));
        email.setFrom(from);

        if (!(cc == null))
        {
          email.setCc(getAddress(cc));
        }

        if (!(bcc == null))
        {
          email.setBcc(getAddress(bcc));
        }
        email.setSubject(subject);
        email.setMsg(message);

        // Send message
        email.send();
      }
      catch (Exception ex)
      {
        ex.printStackTrace();
      }
    }
  }
I did try these and realised, most little issues like multi client and attachment was dealt with. Like I said this is my opinion let me know what you think. And oh if you wondering WTF is the method getAddress(..), see below:

Implementation for Apache Commons
  private Collection getAddress(String arg)
  {
    Collection address = new ArrayList();
    try
    {
      ArrayList ccArray = new ArrayList();
      StringTokenizer st = new StringTokenizer(arg, ",");
      while (st.hasMoreTokens())
      {
        ccArray.add(new InternetAddress(st.nextToken()));
      }

      for (int i = 0; i < ccArray.size(); i++)
      {
        address.add(ccArray.get(i));
      }

    }
    catch (Exception ex)
    {
      ex.printStackTrace();
    }
    return address;
  }
Implementation for JavaMail or Spring
 private InternetAddress[] getAddress(String arg)
  {
    InternetAddress[] address = null;
    try
    {
      ArrayList ccArray = new ArrayList();
      StringTokenizer st = new StringTokenizer(arg, ",");

      while (st.hasMoreTokens())
      {
        ccArray.add(new InternetAddress(st.nextToken()));
      }

      int size = ccArray.size();
      address = new InternetAddress[size];
      for (int i = 0; i < size; i++)
      {
        address[i] = new InternetAddress(ccArray.get(i).toString());
      }

    }
    catch (Exception ex)
    {
      ex.printStackTrace();
    }
    return address;
  }

Drop me a comment to let me know what you think.

Monday 7 June 2010

Commons mail, JavaMail or Spring Mail with Maven2 and servlet container dependency issue.

If you are working on Spring Web project with Axis2, Axiom or Apache CXF 2 dependency, unless the application do not have concerns with e-mail confirmation/notification functionality. Else you are likely to run into this culprit.
How do you know you've got this issue? You execute a test application from your IDE to see if an e-mail will be sent, to your surprise everything works perfect. But when you deploy the application to either Tomcat, Jboss or Glassfish server, the message is sent with header and footer details as part of the mesage body. The massage body typically resembles below;
------=_Part_0_25002283.1275298567928
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit

Hello Mark Doe,

This message is found as a culprit.

Regards,

Joe Blahh

------=_Part_0_25002283.1275298567928

Also if you are attaching documents to this email message, the attachment is sent with base64 encode as part of the message body. Typical attachment message will look like this;

------=_Part_0_25002283.1275298567928
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit

Hello Mark Doe,

This message is found as a culprit.

Regards,

Joe Blahh

------=_Part_0_25002283.1275298567928
Content-Type: application/pdf; name="file.pdf"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; 
filename="file.pdf"
Content-Description: Some attachment

JVBERi0xLjQNJeLjz9MNCjYzIDAgb2JqDTw8L0xpbmVhcml6ZWQgMS9MIDMxMzE4Mi9PIDY1L0Ug
Mjg2NjY5L04gMS9UIDMxMTgwMi9IIFsgMjgzNiAzNzZdPj4NZW5kb2JqDSAgICAgICAgICAgICAg
DQp4cmVmDQo2MyAxMjcNCjAwMDAwMDAwMTYgMDAwMDAgbg0KMDAwMDAwMzM4MCAwMDAwMCBuDQow
MDAwMDAzNTIzIDAwMDAwIG4NCjAwMDAwMDQzMDcgMDAwMDAgbg0KMDAwMDAwNTEwOSAwMDAwMCBu
DQowMDAwMDA2Mjc5IDAwMDAwIG4NCjAwMDAwMDY0MTAgMDAwMDAgbg0KMDAwMDAwNjU0NiAwMDAw
MCBuDQowMDAwMDA3OTY3IDAwMDAwIG4NCjAwMDAwMDkwMjMgMDAwMDAgbg0KMDAwMDAwOTk0OSAw
MDAwMCBuDQowMDAwMDExMDAwIDAwMDAwIG4NCjAwMDAwMTIwNTkgMDAwMDAgbg0KMDAwMDAxMjky
MCAwMDAwMCBuDQowMDAwMDEyOTU0IDAwMDAwIG4NCjAwMDAwMTI5ODIgMDAwMDAgbg0KMDAwMDAx
.......
CnN0YXJ0eHJlZg0KMTE2DQolJUVPRg0K
------=_Part_0_25002283.1275298567928--

If you've run into this culprit, don't worry too much. Go grab a cuppa coffee, and while you're relaxed look through your Maven dependency to see if you have geronimo-javamail_1.4_spec-1.2 along with geronimo-activation_1.1_spec, because your project build will rely on these. You can do this by issuing below Maven2 commands;
mvn dependency:tree
Optionally, the output parameter can be specified to divert the output to a file:
mvn dependency:tree -Doutput=/path/to/file

Causes:
The described issue is caused by transitive dependencies of Apache CXF 2 or Axiom.

SOLUTION:
To resolve this issue, exclude geronimo-javamail_1.4_spec from the build, and just rely on javax's mail-1.4.x.jar.

    
  org.apache.cxf   
  cxf-rt-frontend-jaxws    
  2.2.6    
            
                  
       org.apache.geronimo.specs            
       geronimo-javamail_1.4_spec        
      
      
      org.apache.geronimo.specs 
      geronimo-activation_1.1_spec
         
 



  
  org.apache.ws.commons.axiom  
  axiom-api  
  1.2.8  
           
      
      org.apache.geronimo.specs 
      geronimo-activation_1.1_spec
     
     
      org.apache.geronimo.specs 
      geronimo-javamail_1.4_spec