Advanced java code dealing with real world problems.

Thursday, September 3, 2009

Portable java mail message bean - Part 4

In the first three parts of this series, we have defined a portable message bean comprises of four serializable classes that are used to represent a simple or complex mime message.
Provided in this part is a utility class (as promised) that converts a java mail mime message instance into a portable message bean, or vise verse. A simple static method (mimeToBean) is provided that takes a java mail mime message as input, and returns a portable message bean. Another static method (beanToMime) is provided to return a java mail mime message from a portable message bean.
In order to use this class, a string manipulation utility class is needed, which will be provided in Part 5.
/*
 * blog/javaclue/javamail/MessageBeanUtil.java
 * 
 * Copyright (C) 2009 JackW
* 
 * This program is free software: you can redistribute it and/or modify it under the terms of the
 * GNU Lesser General Public License as published by the Free Software Foundation, either version 3
 * of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License along with this library.
 * If not, see <http://www.gnu.org/licenses/>.
 */
package blog.javaclue.javamail;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.UnknownHostException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.activation.DataHandler;
import javax.mail.Address;
import javax.mail.BodyPart;
import javax.mail.Header;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.Part;
import javax.mail.Session;
import javax.mail.Message.RecipientType;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MailDateFormat;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.mail.util.ByteArrayDataSource;

import org.apache.log4j.Logger;

public final class MessageBeanUtil {
 static final Logger logger = Logger.getLogger(MessageBeanUtil.class);
 static final boolean isDebugEnabled = logger.isDebugEnabled();

 final static String LF = System.getProperty("line.separator", "\n");
 
 static boolean debugSession = false;
 private static String hostName = null;

 public static final String RETURN_PATH = "Return-Path";
 public static final String XHEADER_PRIORITY = "X-Priority";
 public static final String XHEADER_MAILER = "X-Mailer";
 public static final String MESSAGE_TRUNCATED = "=== message truncated ===";

 private MessageBeanUtil() {
 }

 /**
  * convert JavaMail MimeMessage to message bean
  * 
  * @param p -
  *            part
  * @throws MessagingException
  * @throws IOException
  */
 public static MessageBean mimeToBean(Part p) throws IOException,
   MessagingException {
  // make sure it's a message
  if (!(p instanceof Message) && !(p instanceof MimeMessage)) {
   // not a known message type
   try {
    logger.error("mimeToBean() - Unknown Part: " + p.getContentType());
    logger.error("mimeToBean() - ---------------------------");
   }
   catch (Exception e) {
    logger.error("Exception caught", e);
   }
   throw new MessagingException("Part was not a MimeMessage as expected");
  }
  
  if (hostName == null) {
   try {
    hostName = java.net.InetAddress.getLocalHost().getHostName();
   }
   catch (UnknownHostException e) {
    logger.warn("mimeToBean() - UnknownHostException caught, default to localhost", e);
    hostName = "localhost";
   }
  }
  
  MessageBean msgBean = new MessageBean();
  msgBean.clearParameters();
  
  processEnvelope((Message) p, msgBean);

  processAttachment((BodypartBean) msgBean, p, msgBean, 0);
  
  return msgBean;
 }

 /*
  * process message envelope and headers
  * 
  * @param msg -
  *           a MimeMessage instance
  * @param msgBean -
  *           a MessageBean instance
  * @return - SMTP message id, or null if not found
  * @throws AddressException
  */
 private static String processEnvelope(Message msg, MessageBean msgBean)
   throws AddressException {
  Address[] from = null,
   received_to = null,
   envelope_to = null,
   cc = null,
   bcc = null,
   replyto = null;
  String[] xmailer = null;
  String subject = null;
  String messageId= null;
  java.util.Date receivedTime = null;

  // Received Date
  try {
   receivedTime = msg.getReceivedDate();
   if (receivedTime != null) {
    msgBean.setSentDate(receivedTime);
   }
  }
  catch (MessagingException e) {
   logger.error("MessagingException caught during getReceivedDate()", e);
  }

  // retrieve Message-Id, Return-Path and Received Time from headers
  try {
   Enumeration<?> enu = ((MimeMessage) msg).getAllHeaders();
   while (enu.hasMoreElements()) {
    Header hdr = (Header) enu.nextElement();
    String name = hdr.getName();
    if (isDebugEnabled)
     logger.debug("processEnvelope() - header line: " + name + ": " + hdr.getValue());
    if ("Message-ID".equalsIgnoreCase(name)) {
     messageId= hdr.getValue();
     logger.info("processEnvelope() - >>>>>Message-ID retrieved: " + messageId);
     msgBean.setSmtpMessageId(messageId);
    }
    if (RETURN_PATH.equalsIgnoreCase(name)) {
     msgBean.setReturnPath(hdr.getValue());
    }
    if ("Date".equals(name) && receivedTime == null) {
     receivedTime = getHeaderDate(hdr.getValue()); // SMTP Date
    }
   }
  }
  catch (Exception e) {
   logger.error("Exception caught from getAllHeaderLines()", e);
  }

  Calendar rightNow = Calendar.getInstance();
  // If Received DateTime not found from envelope, use current time
  if (receivedTime == null) {
   msgBean.setSentDate(rightNow.getTime());
  }
  // display Received Date Time
  if (isDebugEnabled) {
   logger.info("processEnvelope() - Email Received Time: "
     + (receivedTime != null ? receivedTime.toString() : "UNKNOWN")
     + ", SERVER-TIME: " + rightNow.getTime().toString());
  }

  String[] received = null;
  try {
   received = ((MimeMessage) msg).getHeader("Received");
  }
  catch (MessagingException e) {
   logger.error("MessagingException caught from getHeader(Received)", e);
  }

  // retrieve TO address from "Received" Headers.
  String real_to = "";
  if (received != null) { // sanity check, should never be null
   // scan received headers for "for" address, starting from the bottom
   // (the oldest) and going up until an email address is found.
   int i;
   String tmp_to = null;
   for (i = received.length - 1; i >= 0; i--) {
    if (isDebugEnabled) {
     logger.debug("processEnvelope() - Received: " + received[i]);
    }
    if ((tmp_to = analyzeReceived(received[i])) != null) {
     real_to = tmp_to;
     logger.info("processEnvelope() - found \"for\" in Received Headers: " + real_to);
     break; // exit loop
    }
   }
  }

  Address[] addr;
  // get FROM from envelope or Return-Path
  try {
   String[] _froms = null;
   if ((addr = msg.getFrom()) != null && addr.length > 0) {
    String addrStr = checkAddr(addr[0].toString());
    for (int j = 1; j < addr.length; j++) {
     addrStr += "," + checkAddr(addr[j].toString());
    }
    from = InternetAddress.parse(addrStr);
   }
   else if ((_froms = msg.getHeader(RETURN_PATH)) != null && _froms.length > 0) {
    logger.warn("processEnvelope() - FROM is missing from envelope, use Return-Path.");
    String addrStr = checkAddr(_froms[0]);
    for (int j = 1; j < _froms.length; j++) {
     addrStr += "," + checkAddr(_froms[j]);
    }
    from = InternetAddress.parse(addrStr);
   }
   else {
    // FROM is empty from envelope and Return-Path
   }
  }
  catch (MessagingException e) {
   logger.error("MessagingException caught from getFrom()", e);
  }
  msgBean.setFrom(from);
  if (isDebugEnabled)
   logger.debug("processEnvelope() - Email From Address: " + msgBean.getFromAsString());

  // get TO from Received Headers
  if (real_to != null && real_to.trim().length() > 0) {
   // found TO address from header
   try {
    received_to = InternetAddress.parse(real_to);
   }
   catch (javax.mail.internet.AddressException e) {
    logger.error("Warning!!! AddressException caught from parsing " + real_to, e);
   }
  }

  // get TO from envelope
  try {
   if ((addr = msg.getRecipients(RecipientType.TO)) != null && addr.length > 0) {
    String addrStr = checkAddr(addr[0].toString());
    for (int j = 1; j < addr.length; j++) {
     addrStr += "," + checkAddr(addr[j].toString());
    }
    envelope_to = InternetAddress.parse(addrStr);
   }
   else {
    // TO is empty from envelope
   }
  }
  catch (MessagingException e) {
   logger.error("MessagingException caught from getRecipients(TO)", e);
  }
  msgBean.setToEnvelope(envelope_to);

  // TO from "Delivered-To" header
  Address[] delivered_to = null;
  try {
   String[] dlvrTo = msg.getHeader("Delivered-To");
   if (dlvrTo != null && dlvrTo.length > 0) {
    String addrStr = checkAddr(dlvrTo[0]);
    for (int j=1; j<dlvrTo.length; j++) {
     addrStr += "," + checkAddr(dlvrTo[j]);
    }
    logger.info("processEnvelope() - \"Delivered-To\" found from header: " + addrStr);
    delivered_to = InternetAddress.parse(addrStr);
   }
  }
  catch (MessagingException e) {
   logger.error("MessagingException caught from msg.getHeader(\"Delivered-To\")", e);
  }
  
  // TO: Received (non-VERP) > Delivered-To > Received (VERP) > Envelope 
  if (received_to != null && received_to.length > 0) {
   String dest = received_to[0] == null ? null : received_to[0].toString();
   if (!StringUtil.isEmpty(dest) && !StringUtil.isVERPAddress(dest)) {
    msgBean.setTo(received_to);
   }
  }
  if (msgBean.getTo() == null) {
   if (delivered_to != null && delivered_to.length > 0) {
    // The real mailbox address this email is delivered to. If the email
    // address is a VERP address, the original address is restored from
    // the VERP address and is assigned to "Delivered-To" header.
    msgBean.setTo(delivered_to);
   }
   else if (received_to != null && received_to.length > 0) {
    // Email address extracted from "Received" header is the real email
    // address. But when VERP is enabled, since the Email Id is embedded
    // in the VERP address, every email received will have its own VERP
    // address. This will cause a disaster to EmailAddr table since all
    // TO addresses are saved to that table.
    String dest = received_to[0] == null ? null : received_to[0].toString();
    if (!StringUtil.isEmpty(dest) && StringUtil.isVERPAddress(dest)) {
     String verpDest = StringUtil.getDestAddrFromVERP(dest);
     try {
      Address[] destAddr = InternetAddress.parse(verpDest);
      msgBean.setTo(destAddr);
     }
     catch (AddressException e) {
      logger.error("AddressException from Received_To: " + dest);
     }
    }
   }
   if (msgBean.getTo() == null) {
    msgBean.setTo(envelope_to);
   }
  }
  logger.info("processEnvelope() - Email To from Delivered-To: " + StringUtil.addrToString(delivered_to, false)
    + ", from Received Header: " + StringUtil.addrToString(received_to, false)
    + ", from Envelope: " + StringUtil.addrToString(envelope_to, false));
  
  // CC
  try {
   if ((addr = msg.getRecipients(RecipientType.CC)) != null && addr.length > 0) {
    cc = addr;
    msgBean.setCc(cc);
    if (isDebugEnabled)
     logger.debug("processEnvelope() - Email CC Address: " + msgBean.getCcAsString());
   }
  }
  catch (MessagingException e) {
   logger.error("MessagingException caught during getRecipients(CC)", e);
  }

  // BCC
  try {
   if ((addr = msg.getRecipients(RecipientType.BCC)) != null && addr.length > 0) {
    bcc = addr;
    msgBean.setBcc(bcc);
    if (isDebugEnabled)
     logger.debug("processEnvelope() - Email BCC Address: " + msgBean.getBccAsString());
   }
  }
  catch (MessagingException e) {
   logger.error("MessagingException caught during getRecipients(BCC)", e);
  }

  // REPLYTO
  try {
   if ((addr = msg.getReplyTo()) != null && addr.length > 0) {
    String addrStr = checkAddr(addr[0].toString());
    for (int j = 1; j < addr.length; j++) {
     addrStr += "," + checkAddr(addr[j].toString());
    }
    replyto = InternetAddress.parse(addrStr);
    msgBean.setReplyto(replyto);
   }
  }
  catch (MessagingException e) {
   logger.error("MessagingException caught during getReplyTo()", e);
  }

  // SUBJECT
  try {
   subject = msg.getSubject();
  }
  catch (MessagingException e) {
   logger.error("MessagingException caught during getSubject()", e);
  }
  msgBean.setSubject(subject);
  if (isDebugEnabled)
   logger.debug("processEnvelope() - Email Subject: [" + subject + "]");

  // X-MAILER
  try {
   String[] hdrs = msg.getHeader(XHEADER_MAILER);
   if (hdrs != null) {
    xmailer = hdrs;
    msgBean.setXmailer(xmailer);
   }
  }
  catch (MessagingException e) {
   logger.error("MessagingException caught during getHeader(X-Mailer)", e);
  }

  // X-Priority: 1 (High), 2 (Normal), 3 (Low)
  try {
   String[] priority = ((MimeMessage) msg).getHeader(XHEADER_PRIORITY);
   if (priority != null) {
    msgBean.setPriority(priority);
   }
  }
  catch (MessagingException e) {
   logger.error("MessagingException caught during getHeader(X-Priority)", e);
  }

  return messageId;
 } // end of processEnvelope

 /*
  * recursively build up an attachment tree structure from a MultiPart
  * message.
  * 
  * @param aNode -
  *            current BodypartBean
  * @param p -
  *            JavaMail part
  * @param msgBean -
  *            root message bean
  * @param level -
  *            attachment level
  */
 private static void processAttachment(BodypartBean aNode, Part p, MessageBean msgBean,
   int level) {
  String disp = null, desc = null, contentType = "text/plain";
  String dispOrig = null, descOrig = null;
  String fileName = null;
  // initialize part size
  int partSize = 0;
  // get content type
  try {
   contentType = p.getContentType();
  }
  catch (Exception e) {
   contentType = "text/plain"; // failed to get content type, use default
   logger.error("Exception caught during getContentType()", e);
  }
  // get disposition
  try {
   dispOrig = p.getDisposition();
   /*
    * disposition may look like:
     * 1) inline 
     * 2) attachment 
     * 3) attachment|inline; filename=xxxxx
    * I believe JavaMail is taking care of this. However the code
    * stays here just for safety.
    */ 
   if (dispOrig != null && dispOrig.indexOf(";") > 0) {
    disp = dispOrig.substring(0, dispOrig.indexOf(";"));
   }
   else {
    disp = dispOrig;
   }
  }
  catch (Exception e) {
   logger.error("Exception caught during getDisposition()", e);
  }
  // get description
  try {
   descOrig = p.getDescription();
   // to make use of "desc" field by saving attachment file name on it.
   if (descOrig == null) {
    // if null, get attachment filename from content type field
    desc = getFileName(contentType);
    // JavaMail appends file name to content type field if one is
    // found from disposition field
   }
   else {
    desc = descOrig;
   }
  }
  catch (Exception e) {
   logger.error("Exception caught during getDescription()", e);
  }
  // get file name
  try {
   fileName = p.getFileName();
  }
  catch (Exception e) {
   logger.error("Exception caught during getFileName()", e);
  }
  // get part size
  try {
   partSize = p.getSize();
  }
  catch (Exception e) {
   logger.error("Exception caught during getSize()", e);
  }
  // display some key information
  if (isDebugEnabled) {
   logger.info("processAttachment() - getDisposition(): " + dispOrig);
   logger.info("processAttachment() - getDescription(): " + descOrig);
   logger.info("processAttachment() - getContentType(): " + contentType + ", level:" + level + ", size:"
     + partSize);
  }
  if (fileName != null && isDebugEnabled) {
   logger.debug("processAttachment() - getFileName() = " + fileName);
  }
  // build mime tree
  try {
   aNode.setDisposition(disp);
   aNode.setDescription(desc);
   aNode.setFileName(fileName);
   // update attachment count
   if (Part.ATTACHMENT.equalsIgnoreCase(disp) 
     || (Part.INLINE.equalsIgnoreCase(disp) && desc != null)
     || getFileName(contentType) != null) {
    // update attachment count
    msgBean.updateAttachCount(1);
   }
   // set content type and header fields
   aNode.setContentType(contentType);
   aNode.setHeaders(p);
   aNode.setSize(partSize);
   /*
    * Using isMimeType to determine the content type.
    */
   if (p.isMimeType("text/plain") || p.isMimeType("text/html")) {
    logger.info("processAttachment(): level " + level + ", text message: " + contentType);
    aNode.setValue((String) p.getContent());
    msgBean.getComponentsSize().add(Integer.valueOf(aNode.getSize()));
   }
   else if (p.isMimeType("multipart/*")) {
    logger.info("processAttachment(): level " + level + ", Recursive Multipart: " + contentType);
    Multipart mp = (Multipart) p.getContent();
    int count = mp.getCount();
    for (int i = 0; i < count; i++) {
     Part p1 = mp.getBodyPart(i);
     // call itself to build up a child attachment tree
     if (p1 != null) {
      BodypartBean subNode = new BodypartBean();
      processAttachment(subNode, p1, msgBean, level+1);
      aNode.put(subNode);
     }
    }
   }
   else if (p.isMimeType("message/rfc822")) {
    // nested message type
    logger.info("processAttachment(): level " + level + ", RFC822 Message: " + contentType);
    Part p1 = (Part) p.getContent();
    if (p1 != null) {
     BodypartBean subNode = new BodypartBean();
     processAttachment(subNode, p1, msgBean, level+1);
     aNode.put(subNode);
    }
   }
   else {
    /*
     * other mime type. could be application, image, audio, video, message, etc.
     */
    Object o = p.getContent();
    /*
     * unknown mine type section. check its java type.
     */
    if (o instanceof String) {
     // text type of section
     logger.info("processAttachment(): level " + level + ", String Content " + contentType);
     aNode.setValue((String) o);
     if (aNode.getValue() != null) {
      msgBean.getComponentsSize().add(Integer.valueOf(((byte[]) aNode.getValue()).length));
     }
    }
    else if (o instanceof InputStream) {
     // stream type of section
     logger.info("processAttachment(): level " + level + ", InputStream Content " + contentType);
     InputStream is = (InputStream) o;
     aNode.setValue((InputStream) is);
     if (aNode.getValue() != null) {
      msgBean.getComponentsSize().add(Integer.valueOf(((byte[]) aNode.getValue()).length));
     }
    }
    else if (o != null) {
     // unknown Java type, write it out as a string anyway.
     logger.error("processAttachment(): level " + level + ", Unknown type: " + o.toString());
     aNode.setValue((String) o.toString());
     if (aNode.getValue() != null) {
      msgBean.getComponentsSize().add(Integer.valueOf(((byte[]) aNode.getValue()).length));
     }
    }
    else {
     // no content
     logger.error("processAttachment(): level " + level + ", Content is null");
     aNode.setValue((Object)null);
    }
   }
  } // end of the try block
  catch (IndexOutOfBoundsException e) {
   /* thrown from mp.getBodyPart(i), should never happen */
   logger.error("processAttachment(): IndexOutOfBoundsException caught: " + contentType);
   logger.error("IndexOutOfBoundsException caught", e);
   aNode.setValue("001: IndexOutOfBoundsException caught during process.");
   BodypartBean subNode = new BodypartBean("text/plain");
   aNode.put(subNode);
   setAnodeValue(subNode, p, "002: IndexOutOfBoundsException thrown from mp.getBodyPart(i).");
   if (subNode.getValue() != null) {
    msgBean.getComponentsSize().add(Integer.valueOf(((byte[]) subNode.getValue()).length));
   }
   subNode.setDisposition(aNode.getDisposition());
   subNode.setDescription(aNode.getDescription());
  }
  catch (MessagingException e) {
   /*
    * JavaMail failed to read the message body, use its raw data instead
    */
   logger.error("processAttachment(): MessagingException caught: " + contentType);
   logger.error("MessagingException caught", e);
   if (contentType.trim().toLowerCase().startsWith("multipart/")
     || contentType.trim().toLowerCase().startsWith("message/rfc822")) {
    aNode.setValue("003: MessagingException caught during process.");
    BodypartBean subNode = new BodypartBean("text/plain");
    aNode.put(subNode);
    setAnodeValue(subNode, p);
    if (subNode.getValue() != null) {
     msgBean.getComponentsSize().add(Integer.valueOf(((byte[]) subNode.getValue()).length));
    }
    subNode.setDisposition(aNode.getDisposition());
    subNode.setDescription(aNode.getDescription());
   }
   else {
    setAnodeValue(aNode, p);
    if (aNode.getValue() != null) {
     msgBean.getComponentsSize().add(Integer.valueOf(((byte[]) aNode.getValue()).length));
    }
   }
  }
  catch (UnsupportedEncodingException e) {
   /* unsupported encoding found, use its raw data instead */
   logger.error("processAttachment(): UnsupportedEncodingException caught: " + contentType);
   logger.error("UnsupportedEncodingException caught", e);
   if (contentType.trim().toLowerCase().startsWith("multipart/")
     || contentType.trim().toLowerCase().startsWith("message/rfc822")) {
    aNode.setValue("004: UnsupportedEncodingException caught during process.");
    BodypartBean subNode = new BodypartBean("text/plain");
    aNode.put(subNode);
    setAnodeValue(subNode, p);
    if (subNode.getValue() != null) {
     msgBean.getComponentsSize().add(Integer.valueOf(((byte[]) subNode.getValue()).length));
    }
    subNode.setDisposition(aNode.getDisposition());
    subNode.setDescription(aNode.getDescription());
   }
   else {
    setAnodeValue(aNode, p);
    if (aNode.getValue() != null) {
     msgBean.getComponentsSize().add(Integer.valueOf(((byte[]) aNode.getValue()).length));
    }
   }
  }
  catch (IOException e) {
   /*
    * IOException caught during decoding, couldn't read the message
    * body. Use "-- Message body has been omitted --" as body text
    */
   logger.error("processAttachment(): IOException caught: " + contentType);
   logger.error("IOException caught", e);
   if (contentType.trim().toLowerCase().startsWith("multipart/")
     || contentType.trim().toLowerCase().startsWith("message/rfc822")) {
    aNode.setValue("005: IOException caught during process.");
    BodypartBean subNode = new BodypartBean("text/plain");
    aNode.put(subNode);
    subNode.setValue("-- Message body has been omitted --");
    subNode.setDisposition(aNode.getDisposition());
    subNode.setDescription(aNode.getDescription());
   }
   else {
    aNode.setValue("-- Message body has been omitted --");
   }
  }
  catch (Exception e) {
   /* all other unchecked exceptions */
   logger.error("processAttachment(): Exception caught: " + contentType);
   logger.error("Exception caught", e);
   if (contentType.trim().toLowerCase().startsWith("multipart/")
     || contentType.trim().toLowerCase().startsWith("message/rfc822")) {
    aNode.setValue("006: Exception caught during process.");
    BodypartBean subNode = new BodypartBean("text/plain");
    aNode.put(subNode);
    setAnodeValue(subNode, p, "Unchecked Exception caught: " + e.toString());
    subNode.setDisposition(aNode.getDisposition());
    subNode.setDescription(aNode.getDescription());
   }
   else {
    setAnodeValue(aNode, p, "Unchecked Exception caught: " + e.toString());
   }
  }
 } // end of processAttachment

 private static void setAnodeValue(BodypartBean anode, Part p) {
  setAnodeValue(anode, p, "-- Message body has been omitted. Exception thrown from p.getInputStream() --");
 }

 private static void setAnodeValue(BodypartBean anode, Part p, String errmsg) {
  try {
   anode.setValue((InputStream) p.getInputStream());
  }
  catch (Exception e) {
   anode.setValue(errmsg);
  }
 }

 final static MailDateFormat mailDateFormat = new MailDateFormat();
 private static java.util.Date getHeaderDate(String text) {
  if (StringUtil.isEmpty(text)) return null;
  try {
   java.util.Date date = mailDateFormat.parse(text);
   return date;
  }
  catch (ParseException e) {
   logger.warn("getHeaderDate() - ParseException caught parsing: " + text);
  }
  return null;
 }

 /*
  * check and reformat email address
  * 
  * @param s -
  *            email address
  * @return reformatted address
  */
 private static String checkAddr(String s) {
  // do not append domain name by default
  return checkAddr(s, false);
 }

 /*
  * check and reformat email address
  * 
  * @param s -
  *            email address
  * @param needDomain -
  *            true requires domain
  * @return reformatted address
  */
 private static String checkAddr(String s, boolean needDomain) {
  if (s == null || s.trim().length() == 0) {
   return s;
  }
  try {
   InternetAddress.parse(s);
  }
  catch (javax.mail.internet.AddressException e) {
   logger.error("AddressException caught during parsing", e);
   return null;
  }

  String addr = s;
  // is this a name only address?
  if (needDomain && addr.indexOf("@") < 0) {
   int pos;
   if ((pos = addr.indexOf(">")) < 0) {
    // does it look like <user>? no - append default domain name
    addr += "@" + hostName;
   }
   else {
    addr = addr.substring(0, pos) + "@" + hostName + ">";
   }
  }
  return addr;
 }

 /*
  * analyze "Received" header and retrieve address from the header
  * 
  * @param received -
  *            header
  * @return "for" address if found, null otherwise.
  */
 private static String analyzeReceived(String received) {
  int semicolon_pos = -1;
  if (received != null && (semicolon_pos = received.indexOf(";")) > 0) {
   received = received.substring(0, semicolon_pos);
   received = received.replace('\n', ' ').replace('\r', ' ').replace('\t', ' ');

   // required fields
   int from_pos = received.indexOf("from ");
   int by_pos = received.indexOf(" by ", from_pos + 1);
   int low_pos = Math.min(from_pos, by_pos);
   int high_pos = Math.max(from_pos, by_pos);
   int max_pos = high_pos;

   // check optional fields
   int via_pos = received.indexOf(" via ", max_pos + 1);
   max_pos = Math.max(max_pos, via_pos);
   int with_pos = received.indexOf(" with ", max_pos + 1);
   max_pos = Math.max(max_pos, with_pos);
   int id_pos = received.indexOf(" id ", max_pos + 1);
   max_pos = Math.max(max_pos, id_pos);

   int for_pos = received.indexOf(" for ", max_pos + 1);
   if (low_pos >= 0 && for_pos > high_pos) {
    return received.substring(for_pos + 4);
   }
   else if (by_pos >= 0 && with_pos > by_pos && for_pos > with_pos) {
    // AOL or Google - "received" could only contain "by" and
    // "with", but no "from"
    return received.substring(for_pos + 4);
   }
   else if (low_pos >= 0 && max_pos > high_pos) {
    // found optional field
    // address may have a display name and the display name may
    // contain one of the search keys used by the optional fields
    // (indexOf())
    for_pos = received.lastIndexOf(" for ");
    if (for_pos > high_pos) {
     return received.substring(for_pos + 4);
    }
   }
  }

  return null;
 }

 /*
  * locate the file name from content-type.
  * 
  * @param ctype -
  *            content type
  * @return file name extracted from the content type
  */
 private static String getFileName(String ctype) {
  String desc = null;
  if (ctype != null && ctype.indexOf("name=") >= 0) {
   desc = ctype.substring(ctype.indexOf("name=") + 5);
   if (desc != null && desc.indexOf(";") > 0)
    desc = desc.substring(0, desc.indexOf(";"));
  }
  return desc;
 }

 /**
  * convert MessageBean to JavaMail MimeMessage
  * 
  * @param msgBean -
  *            a MessageBean object
  * @return JavaMail Message
  * @throws MessagingException
  * @throws IOException 
  */
 public static Message beanToMime(MessageBean msgBean) 
   throws MessagingException, IOException {
  javax.mail.Session session = Session.getDefaultInstance(System.getProperties());
  if (debugSession)
   session.setDebug(true);
  Message msg = new MimeMessage(session);

  // First Set All Headers from a header List
  List<MsgHeader> headers = msgBean.getHeaders();
  if (headers != null) {
   for (int i = 0; i < headers.size(); i++) {
    MsgHeader header = headers.get(i);
    if (!getReservedHeaders().contains(header.getName())) {
     msg.setHeader(header.getName(), header.getValue());
    }
    if (isDebugEnabled) {
     logger.debug("beanToMime() - Header Line - " + header.getName() + ": "
       + header.getValue());
    }
   }
  }
  
  // override certain headers with the data from MesssageBean
  if (msgBean.getFrom() != null) {
   for (int i = 0; i < msgBean.getFrom().length; i++) {
    // just for safety
    if (msgBean.getFrom()[i] != null) {
     msg.setFrom(msgBean.getFrom()[i]);
     break;
    }
   }
  }
  else {
   logger.warn("beanToMime() - MessageBean.getFrom() returns a null");
   msg.setFrom();
  }
  if (msgBean.getTo() != null) {
   msg.setRecipients(Message.RecipientType.TO, msgBean.getTo());
  }
  else {
   logger.warn("beanToMime() - MessageBean.getTo() returns a null");
  }
  if (msgBean.getCc() != null) {
   msg.setRecipients(Message.RecipientType.CC, msgBean.getCc());
  }
  if (msgBean.getBcc() != null) {
   msg.setRecipients(Message.RecipientType.BCC, msgBean.getBcc());
  }
  if (msgBean.getReplyto() != null) {
   msg.setReplyTo(msgBean.getReplyto());
  }
  
  if (msgBean.getReturnPath() != null && msgBean.getReturnPath().trim().length() > 0) {
   msg.setHeader(RETURN_PATH, msgBean.getReturnPath());
  }
  msg.setHeader(XHEADER_PRIORITY, getMsgPriority(msgBean.getPriority()));
  if (msgBean.getXmailer() != null && msgBean.getXmailer().length > 0) {
   msg.setHeader(XHEADER_MAILER, msgBean.getXmailer()[0]);
  }
  msg.setSentDate(new Date());

  msg.setSubject(msgBean.getSubject() == null ? "" : msgBean.getSubject());

  // construct message body part
  List<BodypartBean> aNodes = msgBean.getNodes();
  if (msgBean.getMimeType().startsWith("multipart")) {
   Multipart mp = new MimeMultipart(msgBean.getMimeSubType());
   msg.setContent(mp);
   constructMultiPart(mp, (BodypartBean) msgBean, 0);
  }
  else if (aNodes != null && aNodes.size() > 0) {
   Multipart mp = new MimeMultipart("mixed"); // make up a default
   msg.setContent(mp);
   if (msgBean.getValue()!=null) {
    BodyPart bp = new MimeBodyPart();
    mp.addBodyPart(bp);
    constructSinglePart(bp, (BodypartBean) msgBean, 0);
   }
   constructMultiPart(mp, (BodypartBean) msgBean, 0);
  }
  else {
   constructSinglePart(msg, (BodypartBean) msgBean, 0);
  }
  msg.saveChanges(); // please remember to save the message
  
  return msg;
 }
 
 /**
  * create MessageBean from SMTP raw stream
  * @param mailStream
  * @return a MessageBean
  * @throws MessagingException
  */
 public static MessageBean createMessageBeanFromStream(byte[] mailStream)
   throws MessagingException {
  Message msg = createMimeMessageFromStream(mailStream);
  try {
   MessageBean msgBean = mimeToBean(msg);
   return msgBean;
  }
  catch (IOException e) {
   logger.error("IOException caught", e);
   throw new MessagingException(e.toString());
  }
 }

 /**
  * create JavaMail Message from SMTP raw stream
  * 
  * @param mailStream
  * @return a JavaMail Message
  * @throws MessagingException
  */
 public static Message createMimeMessageFromStream(byte[] mailStream) 
   throws MessagingException {
  javax.mail.Session session = Session.getDefaultInstance(System.getProperties());
  session.setDebug(true);
  ByteArrayInputStream bais = new ByteArrayInputStream(mailStream);
  Message msg = new MimeMessage(session, bais);
  msg.saveChanges();
  session.setDebug(debugSession);
  return msg;
 }
 
 private static void constructMultiPart(Multipart mp, BodypartBean aNode, int level)
   throws MessagingException, IOException {
  
  if (isDebugEnabled) {
   logger.debug("constructMultiPart() - MultipartHL - " + StringUtil.getPeriods(level)
     + "Content Type: " + mp.getContentType());
  }
  List<BodypartBean> aNodes = aNode.getNodes();
  for (int i = 0; aNodes != null && i < aNodes.size(); i++) {
   BodypartBean subNode = aNodes.get(i);
   if (subNode.getMimeType().startsWith("multipart")) {
    Multipart subMp = new MimeMultipart(subNode.getMimeSubType());
    BodyPart multiBody = new MimeBodyPart();
    multiBody.setContent(subMp);
    mp.addBodyPart(multiBody);
    constructMultiPart(subMp, subNode, level + 1);
   }
   else {
    BodyPart bodyPart = new MimeBodyPart();
    mp.addBodyPart(bodyPart);
    constructSinglePart(bodyPart, subNode, level + 1);
   }
  }
 }

 private static final Set<String> reservedHeaders = new HashSet<String>();
 private static Set<String> getReservedHeaders() {
  if (reservedHeaders.isEmpty()) {
   reservedHeaders.add("Delivered-To");
   reservedHeaders.add("Received");
   reservedHeaders.add("Message-ID");
   reservedHeaders.add("Subject");
   reservedHeaders.add("Return-Path");
   //reservedHeaders.add("User-Agent");
  }
  return reservedHeaders;
 }
 
 private static void constructSinglePart(Part part, BodypartBean aNode, int level)
   throws MessagingException, IOException {
  // Set All Headers
  List<MsgHeader> headers = aNode.getHeaders();
  if (headers != null) {
   for (int i = 0; i < headers.size(); i++) {
    MsgHeader header = headers.get(i);
    if (!getReservedHeaders().contains(header.getName())) {
     part.setHeader(header.getName(), header.getValue());
    }
    if (isDebugEnabled) {
     logger.debug("constructSinglePart() - Header Line - "
       + StringUtil.getPeriods(level) + header.getName() + ": "
       + header.getValue());
    }
   }
  }

  part.setDisposition(aNode.getDisposition());
  part.setDescription(aNode.getDescription());

  if (aNode.getMimeType().startsWith("text")) {
   part.setContent(new String(aNode.getValue()), aNode.getContentType());
   if (aNode.getMimeType().startsWith("text/html")) {
    if (aNode.getDisposition() == null) {
     // part.setDisposition(Part.INLINE);
     /* 
      Do not uncomment above line, as some SMTP server will insert 
      "-----Inline Attachment Follows-----" at the beginning of the message.
      */ 
    }
   }
  }
  else {
   if (aNode.getDescription() == null) {
    // not sure why do this, consistency?
    part.setDescription(getFileName(aNode.getContentType()));
   }
   ByteArrayDataSource bads = new ByteArrayDataSource(aNode.getValue(), aNode
     .getContentType());
   part.setDataHandler(new DataHandler(bads));
  }
 }
 
 private static String getMsgPriority(String[] priority) {
  String outPriority = "2 (Normal)";
  if (priority != null && priority[0] != null) {
   String in_p = priority[0].trim();
   if (in_p.equalsIgnoreCase("HIGH"))
    outPriority = "1 (High)";
   else if (in_p.equalsIgnoreCase("NORM"))
    outPriority = "2 (Normal)";
   else if (in_p.equalsIgnoreCase("LOW"))
    outPriority = "3 (Low)";
  }
  return (outPriority);
 }
 

 private static List<String> getMessageBeanMethodNames() {
  Method methods[] = MessageBean.class.getMethods();
  List<String> methodNameList = new ArrayList<String>();
  
  for (int i = 0; i < methods.length; i++) {
   Method method = (Method) methods[i];
   Class<?> parmTypes[] = method.getParameterTypes();
   int mod = method.getModifiers();
   if (Modifier.isPublic(mod) && !Modifier.isAbstract(mod) && !Modifier.isStatic(mod)) {
    if (method.getName().length() > 3 && method.getName().startsWith("get")
      && parmTypes.length == 0) {
     String name = method.getName().substring(3);
     
     if (method.getReturnType().getName().equals("java.lang.String")
       || method.getReturnType().getName().equals("java.lang.Long")) {
      
      // ignore following methods
      if ("BodyContentType".equals(name)) continue;
      if (name.startsWith("Dsn")) continue;
      if (name.startsWith("Des")) continue;
      if (name.startsWith("Dia")) continue;
      if (name.startsWith("Dis")) continue;
      if (name.endsWith("AsString")) continue;
      // end of ignore
      
      methodNameList.add(name);
     }
     else if (method.getReturnType().getCanonicalName().equals("javax.mail.Address[]")) {
      methodNameList.add(name);
     }
    }
   }
  }
  Collections.sort(methodNameList);
  return methodNameList;
 }
 
 private static String invokeMethod(MessageBean msgBean, String name) {
  if (msgBean == null || name == null) {
   logger.warn("invokeMethod() - Either msgBean or name is null.");
   return null;
  }
  
  if (name.startsWith("Email_")) {
   // strip off prefix
   name = name.substring(6); 
  }
  name = "get" + name;
  
  try {
   if (isDebugEnabled)
    logger.debug("invoking method: " + name + "()");
   Method method = msgBean.getClass().getMethod(name, (Class[])null);
   Object obj =  method.invoke(msgBean, (Object[])null);
   if (obj instanceof String) {
    return (String) obj;
   }
   else if (obj instanceof Long) {
    return ((Long)obj).toString();
   }
   else if (obj instanceof Address[]) {
    return StringUtil.addrToString((Address[])obj);
   }
   else if (obj != null) {
    logger.warn("invokeMethod() - invalid return type: " + obj.getClass().getName());
   }
  }
  catch (Exception e) {
   logger.error("invokeMethod() - Exception caught", e);
  }
  return null;
 }
 
 public static void main(String[] args) {
  List<String> methodNameList = getMessageBeanMethodNames();
  StringBuffer sb = new StringBuffer();
  for (int i=0; i<methodNameList.size(); i++) {
   sb.append(methodNameList.get(i) + LF);
  }
  System.out.println(sb.toString());
  
  MessageBean msgBean = new MessageBean();
  msgBean.setSubject("test subject");
  msgBean.setBody("test body text");
  System.out.println("Invoke getBody(): " + invokeMethod(msgBean, "Body"));
  System.out.println("Invoke getSubject(): " + invokeMethod(msgBean, "Subject"));
  try {
   Message msg = beanToMime(msgBean);
   MessageBean bean = mimeToBean(msg);
   System.out.println("########## MessageBean Before:");
   System.out.println(msgBean);
   System.out.println("########## MessageBean After:");
   System.out.println(bean);
  }
  catch (Exception e) {
   e.printStackTrace();
  }
 }
}

1 comment:

  1. getFileName(String ctype) needs to be protected, not private, since it's used by BodypartUtil

    ReplyDelete

Followers

About Me

An IT professional with more than 20 years of experience in enterprise computing. An Audio enthusiast designed and built DIY audio gears and speakers.