/*
 * JBoss, the OpenSource J2EE webOS
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */

// $Id: CallImpl.java,v 1.9.2.5 2005/03/02 14:32:13 tdiesler Exp $
package org.jboss.webservice.client;

// $Id: CallImpl.java,v 1.9.2.5 2005/03/02 14:32:13 tdiesler Exp $

import org.jboss.axis.Message;
import org.jboss.logging.Logger;
import org.jboss.webservice.deployment.OperationDescription;
import org.jboss.webservice.deployment.ServiceDescription;

import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.activation.URLDataSource;
import javax.mail.internet.MimeMultipart;
import javax.xml.soap.AttachmentPart;
import javax.xml.transform.Source;
import java.net.MalformedURLException;
import java.net.URL;
import java.rmi.RemoteException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;

/**
 * A Call object that is ws4ee aware.
 * <p/>
 * It takes the jaxrpc-mapping into account when setting up the call.
 *
 * @author Thomas.Diesler@jboss.org
 * @since 29-May-2004
 */
public class CallImpl extends org.jboss.axis.client.Call
{
   // provide logging
   private static final Logger log = Logger.getLogger(CallImpl.class);

   private ServiceImpl jaxrpcService;
   private OperationDescription opDescription;

   // Attachment objects keyed by contentID
   private Map attachments = new HashMap();

   /**
    * Constructs a new Call object for a given jaxrpcService
    */
   public CallImpl(ServiceImpl service)
   {
      super(service);
      this.jaxrpcService = service;
   }

   /**
    * Build a call from a URL string
    *
    * @param url the target endpoint URL
    * @throws java.net.MalformedURLException
    */
   public CallImpl(Object url) throws MalformedURLException
   {
      this(new ServiceImpl());
      setTargetEndpointAddress(new URL(url.toString()));
   }

   /**
    * The super implementation fills in as much as it can from the wsdl.
    * This is the main entry point for axis to setup the call, so far we have nothing to add.
    * The only thing we do, is helping axis to find the wsdl operation.
    * This has been stubbed out, see below.
    */
   public void setOperation(String javaOpName)
   {
      super.setOperation(javaOpName);

      org.jboss.axis.description.OperationDesc axisOp = getOperation();
      String wsdlOpName = getOperationName().getLocalPart();

      // Find the operation in our description
      String portName = (getPortName() != null ? getPortName().getLocalPart() : null);
      ServiceDescription serviceDesc = jaxrpcService.getServiceDescription(portName);
      Properties callProperties = serviceDesc.getCallProperties();
      if (callProperties != null)
      {
         // Set the default call properties
         Iterator keys = callProperties.keySet().iterator();
         while (keys.hasNext())
         {
            String key = (String)keys.next();
            String value = callProperties.getProperty(key);
            this.setProperty(key, value);
         }
      }

      // Reset the operation description
      this.opDescription = null;

      Iterator itOp = serviceDesc.getOperations();
      while (opDescription == null && itOp.hasNext())
      {
         OperationDescription operation = (OperationDescription)itOp.next();
         if (operation.getWsdlName().equals(wsdlOpName))
            opDescription = operation;
      }

      if (opDescription != null)
      {
         if (serviceDesc.getStyle().equals(axisOp.getStyle()) == false)
         {
            log.debug("Fixing style: [was=" + axisOp.getStyle() + ",is=" + serviceDesc.getStyle() + "]");
            axisOp.setStyle(serviceDesc.getStyle());
         }

         if (serviceDesc.getUse().equals(axisOp.getUse()) == false)
         {
            log.debug("Fixing use: [was=" + axisOp.getUse() + ",is=" + serviceDesc.getUse() + "]");
            axisOp.setUse(serviceDesc.getUse());
         }
      }
      else
      {
         log.warn("Cannot find operation description for: " + wsdlOpName);
      }
   }

   /**
    * The default implementation simply returns the java method name.
    * A ws4ee implementation would take the jaxrpc-mapping file into consideration
    * and return the corresponding wsdl operation
    */
   protected String getWsdlOpName(String javaOpName)
   {
      String wsdlOpName = javaOpName;

      ServiceDescription serviceDesc = getServiceDescription();
      if (serviceDesc != null)
      {
         Iterator it = serviceDesc.getOperations();
         while (it.hasNext())
         {
            OperationDescription operation = (OperationDescription)it.next();
            if (javaOpName.equals(operation.getJavaName()))
            {
               if (wsdlOpName.equals(operation.getWsdlName()) == false)
               {
                  wsdlOpName = operation.getWsdlName();
                  log.debug("Replacing operation name '" + javaOpName + "' with '" + wsdlOpName + "'");
               }
            }
         }
      }

      return wsdlOpName;
   }

   /** Get the ServiceDescription for this Call */
   private ServiceDescription getServiceDescription()
   {
      ServiceDescription serviceDesc = null;
      if (jaxrpcService != null)
      {
         String portName = (getPortName() != null ? getPortName().getLocalPart() : null);
         serviceDesc = jaxrpcService.getServiceDescription(portName);
      }
      return serviceDesc;
   }

   /** Add an attachment with a given contentID
    *
    * See <code>addAttachmentParts</code> for a list of supported types
    *
    * @param contentID the attachments contentID
    * @param mimepart the attachment part
    */
   public void addAttachment(String contentID, Object mimepart)
   {
      attachments.put(contentID, mimepart);
   }

   /** Get an iterator over the available contentIDs */
   public Iterator getAttachmentIdentifiers()
   {
      return Collections.unmodifiableSet(attachments.keySet()).iterator();
   }

   /** Get the attachment for the given contentID. */
   public Object getAttachment(String contentID)
   {
      return attachments.get(contentID);
   }

   /** Remove the attachment for the given contentID. */
   public void removeAttachment(String contentID)
   {
      attachments.remove(contentID);
   }

   /** Add attachment parts to the SOAP message
    */
   protected void addAttachmentParts(Message msg)
   {
      Iterator it = getAttachmentIdentifiers();
      while (it.hasNext())
      {
         String contentID = (String)it.next();
         Object part = getAttachment(contentID);

         AttachmentPart ap = null;
         if (part instanceof String)
         {
            ap = msg.createAttachmentPart(part, "text/plain");
         }
         else if (part instanceof Source)
         {
            ap = msg.createAttachmentPart(part, "application/xml");
         }
         else if (part instanceof URL)
         {
            DataSource ds = new URLDataSource((URL)part);
            ap = msg.createAttachmentPart(new DataHandler(ds));
         }
         else if (part instanceof DataHandler)
         {
            ap = msg.createAttachmentPart((DataHandler)part);
         }
         else if (part instanceof MimeMultipart)
         {
            ap = msg.createAttachmentPart((MimeMultipart)part, "multipart/mixed");
         }

         if (ap == null)
            throw new IllegalArgumentException("Unsupported attachment part: " + part);

         ap.setContentId(contentID);

         // Add the part to the axis call
         attachmentParts.add(ap);
      }

      super.addAttachmentParts(msg);
      attachments.clear();
   }

   /** Calls the super implementation with either rpc or one-way call semantics.
    */
   public Object invoke(Object[] params) throws RemoteException
   {
      try
      {
         if (opDescription != null && opDescription.isOneWay())
         {
            log.debug("Using one-way call semantics for: " + getOperationName());
            super.invokeOneWay(params);
            return null;
         }
         else
         {
            return super.invoke(params);
         }
      }
      finally
      {
         // Release the message context. This cannot be done in the super method since
         // the Axis generated stubs extractAttachments from the call after invoke returns.
         msgContext = null;

         // Clear the headers. Maybe this should be done in Axis, but again this might break generated stubs.
         clearHeaders();
      }
   }
}