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

import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import javax.mail.Flags;
import javax.mail.Message;
import javax.mail.MessagingException;

import org.apache.axis.EngineConfiguration;
import org.apache.axis.MessageContext;
import org.apache.axis.client.Call;
import org.apache.axis.client.Service;
import org.apache.axis.client.async.AsyncCall;
import org.apache.axis.client.async.IAsyncCallback;
import org.apache.axis.client.async.IAsyncResult;
import org.apache.axis.client.async.Status;
import org.apache.axis.message.addressing.AddressingHeaders;
import org.apache.axis.message.addressing.Constants;
import org.apache.axis.message.addressing.MessageID;
import org.jboss.net.axis.server.JMXEngineConfigurationFactory;
import org.jboss.net.axis.transport.mailto.client.AsyncMailClientServiceMBean;
import org.jboss.net.axis.transport.mailto.AbstractMailTransportService;
import org.jboss.net.axis.transport.mailto.MailConstants;

/**
 * <dl>
 * <dt><b>Title: </b><dd>Asynchronous Mail Client Service</dd>
 * <p>
 * <dt><b>Description: </b><dd>This service allows SOAP messages to be sent via email in an asynchronous way.
 * <p>
 * CAUTION: Requires ws-addressing handlers to be installed in the request chain.
 * </dd>
 * <p>
 * </dl>
 * @author <a href="mailto:jasone@greenrivercomputing.com">Jason Essington</a>
 * @version $Revision: 1.1 $
 * 
 * @jmx.mbean
 *       name="jboss.net:service=AsyncMailClient" 
 *       description="Asynchronous mail sender"
 *       extends="org.jboss.net.axis.transport.mailto.AbstractMailTransportServiceMBean"
 */
public class AsyncMailClientService extends AbstractMailTransportService implements AsyncMailClientServiceMBean,
   MailConstants
{

   private HashMap results = new HashMap();

   /**
    * @jmx.managed-operation
    */
   public Service getService()
   {
      EngineConfiguration config = JMXEngineConfigurationFactory.newJMXFactory(getEngineName()).getClientEngineConfig();
      Service service = new Service(config);
      return service;
   }

   /**
    * @jmx.managed-operation
    */
   public void sendAsynchronously(Call call, IAsyncCallback callback, Object[] args)
   {
      AsyncCall aCall = new AsyncCall(call, callback);
      IAsyncResult result = aCall.invoke(args);
      results.put(call.getMessageContext(), result);
   }

   protected void processMessages(Message[] msgs)
   {
      if (log.isDebugEnabled())
         log.debug("Entering: processMessages");

      // first, lets clear out any responses that are done.
      for (Iterator iter = results.entrySet().iterator(); iter.hasNext();)
      {
         Map.Entry entry = (Map.Entry) iter.next();
         IAsyncResult result = (IAsyncResult) entry.getValue();
         StringBuffer buf = new StringBuffer("\nRemoving the following messages:");
         if (result.getStatus() != Status.NONE)
         {
            iter.remove();
            if (log.isDebugEnabled())
            {
               MessageContext mc = (MessageContext) entry.getKey();
               String msgID = getMessageID(mc);
               buf.append("\n\t" + msgID + "\t" + result.getStatus().toString());
            }
         }
         if (log.isDebugEnabled())
         {
            buf.append("\n\t");
            log.debug(buf.toString());
         }
      }

      // build a map of message contexts that have message-ids 
      // if there is no message ID then the message must not be sent yet
      HashMap contexts = new HashMap(results.size());
      for (Iterator iter = results.keySet().iterator(); iter.hasNext();)
      {
         MessageContext mc = (MessageContext) iter.next();
         String msgID = getMessageID(mc);
         StringBuffer buf = new StringBuffer("\nThe following messages are waiting for responses:");
         if (msgID != null)
         {
            if (log.isDebugEnabled())
               buf.append("\n\t" + msgID);
            contexts.put(msgID, mc);
         }
         if (log.isDebugEnabled())
         {
            buf.append("\n");
            log.debug(buf.toString());
         }
      }

      // iterate over all the messages processing them in turn.
      for (int i = 0; i < msgs.length; i++)
      {

         Message message = msgs[i];
         String inReplyTo = null;
         try
         {
            inReplyTo = message.getHeader(HEADER_IN_REPLY_TO)[0];
            if (log.isDebugEnabled())
               log.debug("found a message inReplyTo: " + inReplyTo);
         }
         catch (NullPointerException e)
         {
            // no In-Reply-To: header, so skip this msg
            continue;
         }
         catch (MessagingException e)
         {
            log.warn(e);
            continue;
         }

         if (contexts.containsKey(inReplyTo))
         {
            if (log.isDebugEnabled())
               log.debug("message " + inReplyTo + " correlates to a waiting request.");

            MessageContext ctx = (MessageContext) contexts.get(inReplyTo);
            IAsyncResult result = (IAsyncResult) results.get(ctx);

            try
            {
               org.apache.axis.Message soapMsg = new org.apache.axis.Message(message.getInputStream(), false, message
                  .getContentType(), null);
               ctx.setResponseMessage(soapMsg);
               ctx.setPastPivot(true);
            }
            catch (IOException e)
            {
               log.fatal(e);
            }
            catch (MessagingException e)
            {
               log.fatal(e);
            }
            
            if (getDeleteMail())
            {
               try
               {
                  message.setFlag(Flags.Flag.DELETED, true);
               }
               catch (MessagingException e)
               {
                  log.warn("Unable to flag the message for deletion.", e);
               }
            }

            // This isn't really an abort, it's more like an alarm clock for our sleeping transport.
            result.abort();
         }
      }

      if (log.isDebugEnabled())
         log.debug("Exiting: processMessages");
   }

   private String getMessageID(MessageContext ctx)
   {
      String id = "no message-id";
      
      if (ctx.containsProperty(Constants.ENV_ADDRESSING_REQUEST_HEADERS))
      {
         AddressingHeaders headers = (AddressingHeaders) ctx.getProperty(Constants.ENV_ADDRESSING_REQUEST_HEADERS);
         MessageID msgid = headers.getMessageID();
         if (msgid != null)
         {
            id = msgid.toString();
         }
      }
      return id;
   }


}