Package nexj.core.rpc.mail

Source Code of nexj.core.rpc.mail.MailSender$InputDataSource

// Copyright 2010 NexJ Systems Inc. This software is licensed under the terms of the Eclipse Public License 1.0
package nexj.core.rpc.mail;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.mail.Address;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.Transport;
import javax.mail.Message.RecipientType;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.mail.internet.MimePart;

import nexj.core.integration.IntegrationException;
import nexj.core.integration.Sender;
import nexj.core.integration.io.ObjectOutput;
import nexj.core.meta.integration.Message;
import nexj.core.monitoring.ThreadLocalCounter;
import nexj.core.rpc.TransferObject;
import nexj.core.util.Binary;
import nexj.core.util.Logger;
import nexj.core.util.MIMEUtil;
import nexj.core.util.PropertyIterator;
import nexj.core.util.SysUtil;

/**
* Mail sender.
* The keys of the sending TransferObject must be from Key inner class.
*/
public class MailSender implements Sender
{
   // attributes

   /**
    * The default outgoing e-mail charset.
    */
   protected final static String DEFAULT_CHARSET = "UTF-8";

   // associations

   /**
    * The channel metadata object.
    */
   protected nexj.core.meta.integration.channel.mail.Mail m_channel;

   /**
    * The Mail connection factory.
    */
   protected MailConnectionFactoryLocator m_factory;

   /**
    * The class logger.
    */
   protected final static Logger s_logger = Logger.getLogger(MailSender.class);

   /**
    * Counter of messages sent since the creation of this component
    */
   protected ThreadLocalCounter m_sentCounter = new ThreadLocalCounter();

   // operations

   /**
    * Add Addresses to a list.
    * @param list The list to add Addresses to.
    * @param addresses The addresses to add, as a String, TransferObject, or List<TransferObject>
    * @returns The address list being filled.
    */
   protected static List/*<Address>*/ addAddresses(List/*<Address>*/ list, Object addresses)
   {
      try
      {
         if (addresses instanceof TransferObject)
         {
            addresses = new InternetAddress(
               (String)((TransferObject)addresses).getValue(Mail.ADDRESS), // required
               (String)((TransferObject)addresses).findValue(Mail.PERSONAL), // optional
               DEFAULT_CHARSET);
         }
         else if (addresses instanceof String)
         {
            addresses = InternetAddress.parse((String)addresses, false);
         }
      }
      catch (Exception e)
      {
         throw new IntegrationException("err.rpc.mailAddress", e);
      }

      if (addresses instanceof Address)
      {
         list.add(addresses);
      }
      else if (addresses instanceof Object[])
      {
         for (int i = 0, nCount = ((Object[])addresses).length; i < nCount; ++i)
         {
            addAddresses(list, ((Object[])addresses)[i]); // might cause expansion of nested lists
         }
      }
      else if (addresses instanceof List)
      {
         for (int i = 0, nCount = ((List)addresses).size(); i < nCount; ++i)
         {
            addAddresses(list, ((List)addresses).get(i)); // might cause expansion of nested lists
         }
      }
      else if (addresses != null)
      {
         throw new IntegrationException("err.rpc.mailAddress"); // unknown object type
      }

      return list;
   }

   /**
    * @see nexj.core.integration.Sender#createOutput()
    */
   public ObjectOutput createOutput()
   {
      return new ObjectOutput();
   }

   /**
    * @see nexj.core.integration.Sender#getSentCount()
    */
   public long getSentCount()
   {
      return m_sentCounter.get();
   }

   /**
    * Normalize the MIME type.
    * @param sMIMEType The known MIME type of object.
    * @param obj The object to examine during MIME type normalization.
    * @return The normalized MIME type.
    * @throws IntegrationException if MIME Type could not be determined.
    */
   protected static String normalizeMIMEType(String sMIMEType, Object obj)
      throws IntegrationException
   {
      // Try to determine the MIME type automatically
      if (sMIMEType == null)
      {
         if (obj instanceof List/*<TransferObject>*/)
         {
            sMIMEType = "multipart/mixed";
         }
         else if (obj instanceof Multipart)
         {
            sMIMEType = ((Multipart)obj).getContentType();
         }
         else if (obj instanceof Reader && ((Reader)obj).markSupported())
         {
            Reader reader = (Reader)obj;
            char[] cbuf = new char[512]; // arbitrary sized header to examine for MIME type
            int nLength;

            try
            {
               reader.mark(cbuf.length);
               nLength = reader.read(cbuf);
               reader.reset();
            }
            catch (IOException e)
            {
               throw new IntegrationException("err.rpc.mailType", e); // MIME type search failure
            }

            if (nLength > 0)
            {
               sMIMEType = MIMEUtil.getMIMEType(new String(cbuf, 0, nLength));
            }
         }
         else if (!(obj instanceof Binary || obj instanceof InputStream))
         {
            sMIMEType = MIMEUtil.getMIMEType(obj);
         }
      }

      if (sMIMEType == null)
      {
         throw new IntegrationException("err.rpc.mailType"); // MIME type could not be determined
      }

      // Determine the character set for text MIME types
      if (sMIMEType.startsWith("text/") && sMIMEType.indexOf("charset") < 0)
      {
         if (sMIMEType.equals("text/html") && (obj instanceof CharSequence))
         {
            String sCharset = MIMEUtil.getHTMLCharSet((CharSequence)obj);

            sMIMEType += "; charset=" + ((sCharset == null) ? DEFAULT_CHARSET : sCharset);
         }
         else
         {
            sMIMEType += "; charset=" + DEFAULT_CHARSET;
         }
      }

      return sMIMEType;
   }

   /**
    * @see nexj.core.integration.Sender#prepare(nexj.core.rpc.TransferObject, nexj.core.rpc.TransferObject, nexj.core.meta.integration.Message)
    */
   public void prepare(TransferObject raw, TransferObject tobj, Message message)
      throws IntegrationException
   {
   }

   /**
    * @see nexj.core.integration.Sender#send(nexj.core.rpc.TransferObject)
    */
   public void send(TransferObject tobj) throws IntegrationException
   {
      if (!m_channel.isSendable())
      {
         throw new IntegrationException("err.rpc.notSender", new Object[]{m_channel.getName()});
      }

      if (s_logger.isDebugEnabled())
      {
         s_logger.debug("Sending a message on channel \"" + m_channel.getName() + "\"");
         s_logger.dump(tobj);
      }

      MailConnection con = null;

      try
      {
         con = m_factory.openConnection(tobj);

         MimeMessage msg = con.createMessage();
         List/*<Address>*/ list = new ArrayList/*<Address>*/(1);

         if (addAddresses(list, tobj.findValue(Mail.FROM, m_channel.getFrom())).isEmpty())
         {
            msg.setFrom();
         }
         else if (list.size() == 1)
         {
            msg.setFrom((Address)list.get(0));
         }
         else // FROM field cannot have more than 1 value
         {
            throw new IntegrationException("err.rpc.mailFromCount");
         }

         list.clear();

         if (addAddresses(list, tobj.findValue(Mail.TO)).isEmpty()) // TO field required
         {
            throw new IntegrationException("err.rpc.mailToMissing");
         }

         msg.setRecipients(javax.mail.Message.RecipientType.TO,
                           (Address[])list.toArray(new Address[list.size()]));
         list.clear();

         if (!addAddresses(list, tobj.findValue(Mail.CC)).isEmpty())
         {
            msg.setRecipients(javax.mail.Message.RecipientType.CC,
                              (Address[])list.toArray(new Address[list.size()]));
         }

         list.clear();

         if (!addAddresses(list, tobj.findValue(Mail.BCC)).isEmpty())
         {
            msg.setRecipients(javax.mail.Message.RecipientType.BCC,
                              (Address[])list.toArray(new Address[list.size()]));
         }

         list.clear();

         if (!addAddresses(list, tobj.findValue(Mail.REPLY)).isEmpty())
         {
            msg.setReplyTo((Address[])list.toArray(new Address[list.size()]));
         }

         String sSubject = (String)tobj.findValue(Mail.SUBJECT);

         if (sSubject != null)
         {
            msg.setSubject(sSubject);
         }

         msg.setSentDate(new Date());
         setContent(msg, tobj);

         if (s_logger.isDebugEnabled())
         {
            s_logger.debug("Sending an e-mail to " + msg.getRecipients(RecipientType.TO) +
                           " from " + msg.getFrom());

            if (s_logger.isDumpEnabled())
            {
               Object headers = tobj.findValue(Mail.HEADERS);

               s_logger.dump(
                  "Subject: \"" + msg.getSubject() + "\"" + SysUtil.LINE_SEP +
                  ((headers != null) ? "Headers: " + headers + SysUtil.LINE_SEP : "") +
                  tobj.findValue(Mail.BODY));
            }
         }

         Transport.send(msg);
         m_sentCounter.add(1);
      }
      catch (Exception e)
      {
         throw new IntegrationException("err.rpc.mail", e);
      }
      finally
      {
         if (con != null)
         {
            con.close();
         }
      }
   }

   /**
    * @see nexj.core.integration.Sender#send(java.util.Collection)
    */
   public void send(Collection/*<TransferObject>*/ col) throws IntegrationException
   {
      for (Iterator/*<TransferObject>*/ itr = col.iterator(); itr.hasNext();)
      {
         send((TransferObject)itr.next());
      }
   }

   /**
    * Set the body of the mimePart.
    * @param MimePart The object to set the body for.
    * @param body The message body.
    * @param sContentType The content type of the body.
    * @param sDescription The description/name of the body.
    */
   protected static void setBody(
      MimePart part, Object body, String sContentType, String sDescription)
   {
      String sName = (sDescription == null) ? "noname" : sDescription;
      String sMIMEType = normalizeMIMEType(sContentType, body);

      try // set the body content
      {
         if (body == null)
         {
            part.setContent((sMIMEType.startsWith("text/")) ? (Object)"" : new byte[0], sMIMEType);
         }
         else if (body instanceof String)
         {
            part.setContent(body, sMIMEType);
         }
         else if (body instanceof Binary)
         {
            part.setDataHandler(
               new DataHandler(new InputDataSource(sName, sMIMEType, ((Binary)body).getData())));
         }
         else if (body instanceof Reader)
         {
            part.setContent(body, sMIMEType);
         }
         else if (body instanceof InputStream)
         {
            part.setDataHandler(
               new DataHandler(new InputDataSource(sName, sMIMEType, (InputStream)body)));
         }
         else if (body instanceof Multipart)
         {
            part.setContent((Multipart)body);
         }
         else if (body instanceof List/*<TransferObject>*/) // interpret as a multi-part collection
         {
            MimeMultipart multipart;
            int nPos = sMIMEType.indexOf('/');

            if (nPos < 0)
            {
               multipart = new MimeMultipart();
            }
            else
            {
               sMIMEType = sMIMEType.substring(nPos + 1);
               nPos = sMIMEType.indexOf(';');
               multipart = new MimeMultipart((nPos < 0) ? sMIMEType : sMIMEType.substring(0, nPos));
            }

            for (int i = 0, nCount = ((List)body).size(); i < nCount; ++i)
            {
               MimeBodyPart bodyPart = new MimeBodyPart();

               setContent(bodyPart, (TransferObject)((List)body).get(i));
               multipart.addBodyPart(bodyPart);
            }

            part.setContent(multipart); // set the message content to the MultiPart
         }
         else
         {
            throw new IntegrationException("err.rpc.mailMessage");//i.e. unknown/unhandled body type
         }
      }
      catch (Exception e) // MessagingException, IOException
      {
         throw new IntegrationException("err.rpc.mailMessage", e);
      }
   }

   /**
    * Sets the channel metadata object.
    * @param channel The channel metadata object to set.
    */
   public void setChannel(nexj.core.meta.integration.channel.mail.Mail channel)
   {
      m_channel = channel;
   }

   /**
    * Sets the connection factory to query for Mail connections.
    * @param connectionFactory The connection factory to set.
    */
   public void setConnectionFactory(MailConnectionFactoryLocator connectionFactory)
   {
      m_factory = connectionFactory;
   }

   /**
    * Set the content of the mimePart from the TransferObject description.
    * @param mimePart The object to set the content for.
    * @param msg The message description containing the content.
    */
   protected static void setContent(MimePart part, TransferObject content)
   {
      TransferObject headers = (TransferObject)content.findValue(Mail.HEADERS);
      String sDescription = null;
      String sContentType = null;

      if (headers != null)
      {
         for (PropertyIterator itr = headers.getIterator(); itr.hasNext();)
         {
            itr.next();

            String sKey = itr.getName();
            Object value = itr.getValue();

            if (value instanceof String)
            {
               if ("Content-Type".equalsIgnoreCase(sKey))
               {
                  sContentType = (String)value;
               }
               else
               {
                  if ("Content-Description".equalsIgnoreCase(sKey))
                  {
                     sDescription = (String)value;
                  }

                  try
                  {
                     part.addHeader(sKey, (String)value);
                  }
                  catch (MessagingException e)
                  {
                     throw new IntegrationException("err.rpc.mailHeaderFormat", e);
                  }
               }
            }
            else // only String->String headers are valid
            {
               throw new IntegrationException("err.rpc.mailHeaderFormat");
            }
         }
      }

      setBody(part, content.findValue(Mail.BODY), sContentType, sDescription);
   }

   /**
    * DataSource for input only, internally stores data as a byte[].
    * NOTE: DataSource specification requires getInputStream()/getOutputStream() to return new
    *       stream objects for every call.
    */
   protected static class InputDataSource implements DataSource
   {
      /**
       * The input data stream.
       */
      protected byte[] m_input;

      /**
       * The valid byte length of m_data from m_nOffset.
       */
      protected int m_nLength;

      /**
       * The starting position of the data in m_data.
       */
      protected int m_nOffset;

      /**
       * The stream MIME Type.
       */
      protected String m_sMIMEType;

      /**
       * The stream name.
       */
      protected String m_sName;

      /**
       * Constructor.
       * @param sName The data stream name.
       * @param sMIMEType The data stream MIME type.
       * @param input The input data stream.
       * @throws IOException On input data stream read error.
       */
      public InputDataSource(String sName, String sMIMEType, InputStream input) throws IOException
      {
         m_input = new byte[2048];
         m_nLength = 0;
         m_nOffset = 0;

         for (;;)
         {
            if (m_input.length - m_nLength < 2048)
            {
               byte[] buf = new byte[m_input.length << 1];

               System.arraycopy(m_input, 0, buf, 0, m_nLength);
               m_input = buf;
            }

            int nFree = m_input.length - m_nLength;
            int nCount = input.read(m_input, m_nLength, nFree);

            if (nCount > 0)
            {
               m_nLength += nCount;
            }

            if (nCount < nFree)
            {
               break; // read last chunk, i.e. chunk smaller then buffer size allocated for it
            }
         }
      }

      /**
       * Constructor.
       * @param sName The data stream name.
       * @param sMIMEType The data stream MIME type.
       * @param input The input data stream.
       */
      public InputDataSource(String sName, String sMIMEType, byte[] input)
      {
         this(sName, sMIMEType, input, 0, input.length);
      }

      /**
       * Constructor.
       * @param sName The data stream name.
       * @param sMIMEType The data stream MIME type.
       * @param input The input data stream.
       * @param nOffset The starting position of the data in input.
       * @param nLength The valid byte length of m_data from nOffset.
       */
      public InputDataSource(
         String sName, String sMIMEType, byte[] input, int nOffset, int nLength)
      {
         m_sName = sName;
         m_sMIMEType = sMIMEType;
         m_input = input;
         m_nOffset = nOffset;
         m_nLength = nLength;
      }

      /**
       * @see javax.activation.DataSource#getContentType()
       */
      public String getContentType()
      {
         return m_sMIMEType;
      }

      /**
       * @see javax.activation.DataSource#getInputStream()
       */
      public InputStream getInputStream() throws IOException
      {
         // DataSource specification requires returning a new stream object for every call
         return new ByteArrayInputStream(m_input, m_nOffset, m_nLength);
      }

      /**
       * @see javax.activation.DataSource#getName()
       */
      public String getName()
      {
         return m_sName;
      }

      /**
       * @see javax.activation.DataSource#getOutputStream()
       */
      public OutputStream getOutputStream() throws IOException
      {
         throw new UnsupportedOperationException();
      }
   }
}
TOP

Related Classes of nexj.core.rpc.mail.MailSender$InputDataSource

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.