/*
 * JBoss, the OpenSource J2EE webOS
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 *
 * Created on Mar 16, 2004
 */
package org.jboss.net.axis.transport.mailto;

import javax.mail.AuthenticationFailedException;
import javax.mail.FetchProfile;
import javax.mail.Folder;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.NoSuchProviderException;
import javax.mail.Session;
import javax.mail.Store;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

import org.jboss.system.ServiceMBeanSupport;

/**
 * <dl>
 * <dt><b>Title: </b><dd>Abstract Mail Transport Service</dd>
 * <p>
 * <dt><b>Description: </b><dd>Both the client and server side email transport services are based off of this class.
 * </dd>
 * <p>
 * </dl>
 * @author <a href="mailto:jasone@greenrivercomputing.com">Jason Essington</a>
 * @version $Revision: 1.1 $
 * 
 * @jmx.mbean
 *       description="Abstract Mail Transport Service"
 *       extends="org.jboss.system.ServiceMBean"
 */
public abstract class AbstractMailTransportService extends ServiceMBeanSupport implements MailConstants
{
   public static final String FOLDER_NAME = "INBOX";
   public static final String SESSION_NAME = "java:/Mail";
   public static final String ENGINE_NAME = "jboss.net:service=Axis";

   private String mailFolderName = FOLDER_NAME;
   private String mailSessionName = SESSION_NAME;
   private String EngineName = ENGINE_NAME;

   private boolean deleteMail = true;

   /**
    * The jndi name under which the javax.mail.Session object is stored.
    * @jmx.managed-attribute
    */
   public void setSessionName(String name)
   {
      this.mailSessionName = name;
   }

   /**
    * @jmx.managed-attribute
    *       description = "The JNDI name of the mail session that will be used by this transport"
    *       value = "java:/Mail"
    */
   public String getSessionName()
   {
      return this.mailSessionName;
   }

   /**
    * The name of the folder in which the SOAP messages will reside (likely INBOX).
    * @jmx.managed-attribute
    */
   public void setFolderName(String name)
   {
      this.mailFolderName = name;
   }

   /**
    * @jmx.managed-attribute
    *       description = "Folder in which incoming SOAP messages will be found"
    *       value = "INBOX"
    */
   public String getFolderName()
   {
      return this.mailFolderName;
   }

   /**
    * The jndi name under which the org.apache.axis.server.AxisServer object is stored.
    * @jmx.managed-attribute
    */
   public void setEngineName(String name)
   {
      this.EngineName = name;
   }

   /**
    * @jmx.managed-attribute
    *       description = "The jmx ObjectName of the Axis Server"
    *       value = "jboss.net:service=Axis"
    */
   public String getEngineName()
   {
      return this.EngineName;
   }

   /**
    * Flag instructing the transport to delete processed messages, or not.
    * @jmx.managed-attribute 
    */
   public void setDeleteMail(boolean delete)
   {
      this.deleteMail = delete;
   }

   /**
    * @jmx.managed-attribute 
    *       description = "Should processed messages be deleted?"
    *       value = "true"
    */
   public boolean getDeleteMail()
   {
      return this.deleteMail;
   }

   /**
    * Check our pop mail box for new emails.
    * 
    * maybe an exception of some sort should be thrown if something bad happens? currently all exceptions are logged
    * and execution of this method terminates with nothing else happening.
    * 
    * @jmx.managed-operation
    */
   public void pollMail()
   {
      if (log.isDebugEnabled())
         log.debug("Entering: pollMail()");
      Session mail = getMailSession();

      // bail if there is a problem with our store or folder
      Store store = null;
      if ((store = getMailStore(mail)) == null)
         return;

      Folder folder = null;
      if ((folder = getMailFolder(store)) == null)
         return;

      try
      {
         Message[] msgs = fetchMessages(folder);
         //leave if there are no messages
         if (msgs.length > 0)
            processMessages(msgs);
      }
      finally
      {
         // now we are done, get out of here
         closeFolder(folder);
         closeStore(store);
      }

      if (log.isDebugEnabled())
         log.debug("Leaving: pollMail()");
   }

   protected abstract void processMessages(Message[] msgs);

   /**
    * Fetch the mail session stored in jndi.
    * @return Session
    */
   protected Session getMailSession()
   {
      if (log.isDebugEnabled())
         log.debug("Entering: getMailSession()");

      Context ctx = null;
      Session mail = null;

      try
      {
         ctx = new InitialContext();
         mail = (Session) ctx.lookup(getSessionName());
      }
      catch (NamingException ne)
      {
         /** @TODO we should probably turn around and throw an exception of some sort here
          * since there was some problem fetching the mail session from jndi */
         log.fatal("NamingException: getMailSession()\n", ne);
      }
      finally
      {
         if (ctx != null)
            try
            {
               ctx.close();
            }
            catch (NamingException ne)
            {
            }
      }

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

   protected Store getMailStore(Session mail)
   {
      Store store = null;
      try
      {
         store = mail.getStore();
         if (log.isDebugEnabled())
            log.debug(store.getClass().getName());
      }
      catch (NoSuchProviderException e)
      {
         // Could happen if <parameter name="mail.store.protocol" value="pop3"/> is not set
         log.fatal("The mail session does not have a default provider! Check the mail.store.protocal "
            + "property in the mail-service.xml file", e);
      }

      try
      {
         if (store != null)
            store.connect();
      }
      catch (AuthenticationFailedException e)
      {
         // This could happen if the username and password defined in the mail-service.xml file are wrong or missing
         log.fatal("The User and/or Password defined in the mail-service.xml file could be wrong or missing", e);
         closeStore(store);
         store = null;
      }
      catch (MessagingException e)
      {
         // This is a bad thing. We couldn't connect to the mail store for some reason. 
         log.fatal("Unable to connect to the mail store.", e);
         closeStore(store);
         store = null;
      }
      catch (IllegalStateException e)
      {
         // This means the store is already connected! I suppose since this is what we wanted any way we could
         // go along our merry way?
         log.warn("The store is already connected!", e);
      }

      return store;
   }

   protected Folder getMailFolder(Store store)
   {
      Folder folder = null;
      try
      {
         folder = store.getFolder(getFolderName());

         if (log.isDebugEnabled())
            log.debug(folder.getClass().getName());

         // we can only continue if this folder actually exists!
         if (!folder.exists())
         {
            log.fatal("The folder '" + getFolderName() + "' doe not exist. Check the FolderName attribute in the "
               + "jboss-service.xml file.");
            // This was fatal, lets clean up and leave.
            closeStore(store);
            folder = null;
         }
      }
      catch (MessagingException e)
      {
         log.fatal("Unable to retrieve the folder '" + getFolderName() + "' from the store. Check the FolderName "
            + "attribute in the jboss-service.xml file.", e);
         closeStore(store);
         folder = null;
      }

      try
      {
         folder.open(Folder.READ_WRITE);
      }
      catch (MessagingException e)
      {
         // not sure what would cause this, but if it happens, it can't be good so log and leave
         log.fatal("Failed to open the folder'" + getFolderName() + "'.", e);
         closeStore(store);
         folder = null;
      }

      return folder;
   }

   /**
    * Lets preload some information about our mail messages into message objects.
    * 
    * @param mail
    * @return Message[]
    */
   protected Message[] fetchMessages(Folder folder)
   {
      if (log.isDebugEnabled())
         log.debug("Entering: fetchMessages(Folder folder)");

      Message[] messages = new Message[0];

      Store store = null;

      try
      {
         if (log.isDebugEnabled())
            log.debug("\nMessageCount: " + folder.getMessageCount());

         if (folder.getMessageCount() > 0)
         {
            messages = folder.getMessages();
            FetchProfile fp = new FetchProfile();
            fp.add(FetchProfile.Item.CONTENT_INFO);

            //address information
            fp.add(FetchProfile.Item.ENVELOPE);

            folder.fetch(messages, fp);

            if (log.isDebugEnabled())
            {
               log.debug("getMailMessages(Session mail)\n\t retrieved " + messages.length + " message(s)");
            }
         }
         else if (log.isDebugEnabled())
            log.debug("getMailMessages(Session mail)\n\t no mail!");
      }
      catch (NoSuchProviderException e)
      {
         log.error("NoSuchProviderException: getMailMessages(Session mail)\n", e);
      }
      catch (MessagingException e)
      {
         log.error("MessagingException: getMailMessages(Session mail)\n", e);
      }
      finally
      {
         try
         {
            if (store != null)
               store.close();
         }
         catch (MessagingException e)
         {
         }
      }

      if (log.isDebugEnabled())
         log.debug("Leaving: fetchMessages(Folder folder)");
      return messages;
   }

   /**
    * This closes the mail store and suppresses any exceptions. For use when bailing out due to an error of some sort.
    * 
    * @param store
    */
   protected void closeStore(Store store)
   {
      try
      {
         if (store != null)
            store.close();
      }
      catch (Exception ignore)
      {
      }
      return;
   }

   /**
    * This closes a mail folder and handles exceptions by pretending they didn't happen. 
    * Only do this if you are leaving and the resulting state of the folder is of no consequence to you.
    * 
    * @param folder
    */
   protected void closeFolder(Folder folder)
   {
      try
      {
         // expunge deleted messages on close
         folder.close(true);
      }
      catch (Exception ignore)
      {
      }
      return;
   }
}