/*
 * JBoss, Home of Professional Open Source
 * Copyright 2005, JBoss Inc., and individual contributors as indicated
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This 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 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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 software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.jboss.net.axis.transport.mailto.client;

import java.io.IOException;
import java.io.InputStream;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;

import javax.activation.DataHandler;
import javax.mail.Address;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

import org.apache.axis.AxisFault;
import org.apache.axis.MessageContext;
import org.apache.axis.handlers.BasicHandler;
import org.apache.axis.message.addressing.AddressingHeaders;
import org.apache.axis.message.addressing.Constants;
import org.apache.axis.message.addressing.EndpointReference;
import org.apache.axis.message.addressing.RelatesTo;
import org.apache.log4j.Logger;
import org.jboss.net.axis.transport.mailto.MailConstants;
import org.jboss.net.axis.transport.mailto.MailMessage;
import org.jboss.net.axis.transport.mailto.SOAPDataSource;

/**
 * <dl>
 * <dt><b>Title: </b><dd>Mail Sender</dd>
 * <p>
 * <dt><b>Description: </b><dd>This is the Email Transport's Client Side Pivot. This handler manages sending email
 * and attempting to get a response email from the server.<br>
 * To properly setup this transport you will need a client-deploy.wsdd that contains something like:
 * <pre>
 *  &lt;handler name="MailSender" type="java:org.jboss.net.axis.transport.mail.client.MailSender" &gt;
 *    &lt;parameter name="username" value="user"/&gt;
 *    &lt;parameter name="password" value="pass"/&gt;
 *    &lt;parameter name="timeout" value="120"/&gt;
 *    &lt;!-- any relavent javamail properties may be set here, these are just an example of some posibilities --&gt;
 *    &lt;parameter name="mail.store.protocol" value="pop3"/&gt;
 *    &lt;parameter name="mail.transport.protocol" value="smtp"/&gt;
 *    &lt;parameter name="mail.host" value="mail.someserver.com"/&gt;
 *    &lt;parameter name="mail.user" value="user"/&gt;
 *    &lt;parameter name="mail.from" value="user@someserver.com"/&gt;
 *    &lt;parameter name="mail.debug" value="false"/&gt;
 *  &lt;/handler&gt;
 *  &lt;transport name="mail" pivot="MailSender"/&gt;
 * </pre>
 * The parameters "username" and "password" are optional. If your mail server doesn't require authentication they can
 * be omitted. The parameter "timeout" is to specify the number of minutes the transport will attempt to retrieve a 
 * response from the server. The default is 30 minutes. Any valid Javamail properties may be specified as parameters, 
 * and they will be read and used by the transport.
 * 
 * </dd>
 * </dl>
 * @author <a href="mailto:jasone@greenrivercomputing.com">Jason Essington</a>
 * @version $Revision: 1.1.4.2 $
 */
public class BaseMailSender extends BasicHandler implements MailConstants
{

   public static final String MAIL_PROPS = "MailTransportClient.properties";
   public static final String TRANS_PROPS = "transport.properties";

   public static final String SESSION_NAME = "SessionName";

   protected Logger log = Logger.getLogger(getClass());

   /** If a JavaMail session is not stored in jndi, this field will hold the 
    * JavaMail properties used to create a session. */
   static protected Properties mailProps = null;

   /** The name of a javamail session from jndi that we should use rather than 
    * creating a new one. */
   static protected String mailSessionName = null;

   /* (non-Javadoc)
    * @see org.apache.axis.Handler#invoke(org.apache.axis.MessageContext)
    */
   public void invoke(MessageContext ctx) throws AxisFault
   {

      if (mailSessionName == null && mailProps == null)
      {
         if ((mailSessionName = (String) getOption(SESSION_NAME)) == null)
         {
            mailProps = getJavaMailProperties();
         }
      }

      String msgID = sendMail(ctx);
      
      // hook to store our request for reference while we are waiting for a response.
      archiveMessage(msgID, ctx);

      // hook just incase you want to do email synchronously
      checkResponse(ctx);
   }

   protected String sendMail(MessageContext ctx) throws AxisFault
   {
      MailMessage mailMsg;
      org.apache.axis.Message soapMsg = ctx.getRequestMessage();
      Session session = getMailSession();
      String msgID = null;
      
      AddressingHeaders headers = (AddressingHeaders) ctx.getProperty(Constants.ENV_ADDRESSING_REQUEST_HEADERS);

      try
      {
         mailMsg = new MailMessage(session);

         mailMsg.setRecipients(Message.RecipientType.TO, new Address[]{new InternetAddress(headers.getTo().getPath())});

         
         try
         {
            // from must be a reasonable email address
            EndpointReference from = headers.getFrom();
            if (from != null && !Constants.NS_URI_ANONYMOUS.equals(from.getAddress().toString()))
            {
               mailMsg.setFrom(new InternetAddress(from.getAddress().getPath()));
            }
            else
            {
               mailMsg.setFrom(InternetAddress.getLocalAddress(session));
            }
         }
         catch (AddressException ae)
         {
            // Our from address not a valid email address, so just use the from address defined in the mail session
            mailMsg.setFrom(InternetAddress.getLocalAddress(session));
         }

         if (headers.getMessageID() != null)
         {
            mailMsg.setMessageID(headers.getMessageID().toString());
         }
         if (headers.getRelatesTo() != null)
         {
            List relatesTo = headers.getRelatesTo();
            for (Iterator iter = relatesTo.iterator(); iter.hasNext();)
            {
               RelatesTo rt = (RelatesTo) iter.next();
               if (Constants.QNAME_RESPONSE.equals(rt.getType()))
               {
                  mailMsg.setHeader(HEADER_IN_REPLY_TO, rt.getURI().toString());
               }  
            }
         }
         
         // TODO do we need to set any more headers?
         mailMsg.setDataHandler(new DataHandler(new SOAPDataSource(soapMsg)));

         Transport.send(mailMsg);

         msgID = mailMsg.getMessageID();

      } catch (MessagingException e)
      {
         log.fatal("There was a problem creating the request email message", e);
         throw new AxisFault("Unable to create request message");
      }

      if (log.isDebugEnabled())
         log.debug("message-id: " + msgID);

      return msgID;
   }

   /**
    * Override this method if you need to store outgoing messages.<br>
    * Note: If web service security handlers are in the handler chain,
    * the message will be signed/encrypted here.
    * @param msgID
    * @param msgCtx
    */
   protected void archiveMessage(String msgID, MessageContext msgCtx)
   {
   }

   /**
    * Override this method if you want the client to block until it recieves a response.<br>
    * In reality, this is probably only usefull for testing since email is not really a synchronous operation.
    * @param ctx
    * @throws AxisFault
    */
   protected void checkResponse(MessageContext ctx) throws AxisFault
   {
   }

   /**
    * This handler expects the JavaMail properties to be specified in the wsdd (if no SessionName is set).
    * If the properties aren't there it will look for a parameter by the name of transport.properties that holds the 
    * name of a properties file (to be loaded by the classloader) with the required properties (only javamail properties
    * will be loaded from this file, timeout and authentication information, if included, will be ignored). As a last 
    * resort the handler will search the classpath for MailTransportClient.properties.
    * 
    * The precidence is transport.properties, properties in the options, then default file.
    * @return
    */
   protected Properties getJavaMailProperties() throws AxisFault
   {
      Properties props = null;
      Hashtable opts = getOptions();
      if (opts != null)
      {
         for (Iterator iter = opts.keySet().iterator(); iter.hasNext();)
         {
            String key = (String) iter.next();
            if (key != null && key.startsWith("mail."))
            {
               if (props == null)
                  props = new Properties();
               props.setProperty(key, (String) opts.get(key));
            }
         }
      }

      if (props == null)
      {
         String propfileName = (String) getOption(TRANS_PROPS);

         // o.k. we made it this far without properties so lets try to load them from a file
         // either the file specified by the transport.properties option or our default file
         propfileName = propfileName == null ? MAIL_PROPS : propfileName;

         InputStream is = getClass().getClassLoader().getResourceAsStream(propfileName);
         if (is == null)
            throw new AxisFault(propfileName + " not found.");

         props = new Properties();
         try
         {
            props.load(is);
         } catch (IOException e)
         {
            throw new AxisFault("Unable to load mail properties.", e);
         }
      }

      return props;
   }

   /**
    * Fetch a mail session.
    * @return Session
    */
   protected Session getMailSession() throws AxisFault
   {
      if (log.isDebugEnabled())
         log.debug("Entering: getMailSession()");

      Context ctx = null;
      Session mail = null;

      if (mailSessionName == null)
      {
         mail = Session.getInstance(mailProps);
      } else
      {
         try
         {
            ctx = new InitialContext();
            mail = (Session) ctx.lookup(mailSessionName);
         } catch (NamingException ne)
         {
            log.fatal("NamingException: getMailSession()\n", ne);
            throw new AxisFault("Unable to find Email Session.");
         } finally
         {
            if (ctx != null)
               try
               {
                  ctx.close();
               } catch (NamingException ne)
               {
               }
         }
      }

      if (log.isDebugEnabled())
         log.debug("Leaving: getMailSession()");
      return mail;
   }
}