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

// $Id: ServiceProxy.java,v 1.6.4.2 2005/04/22 11:28:32 tdiesler Exp $
package org.jboss.webservice.client;

// $Id: ServiceProxy.java,v 1.6.4.2 2005/04/22 11:28:32 tdiesler Exp $

import org.jboss.logging.Logger;
import org.jboss.webservice.metadata.jaxrpcmapping.JavaWsdlMapping;
import org.jboss.webservice.util.WSDLUtilities;

import javax.wsdl.Definition;
import javax.xml.rpc.JAXRPCException;
import javax.xml.rpc.Service;
import javax.xml.rpc.ServiceException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.rmi.Remote;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * This is the proxy that implements the service interface .
 * <p/>
 * Additionally it handles some ws4ee functionality that is not part of jaxrpc behaviour.
 *
 * @author Thomas.Diesler@jboss.org
 * @since 15-May-2004
 */
public class ServiceProxy implements InvocationHandler
{
   // provide logging
   private static final Logger log = Logger.getLogger(ServiceProxy.class);

   // The underlying jaxrpc service
   private ServiceImpl jaxrpcService;

   // The methods from java.lang.Object
   private List objectMethods = new ArrayList();
   // The methods from javax.xml.rpc.Service
   private List jaxrpcServiceMethods = new ArrayList();
   // The methods from the service interface, in case of javax.xml.rpc.Service it is empty
   private List serviceInterfaceMethods = new ArrayList();
   // The SEI classes that have corresponding port types
   private List endorsedServiceEndpointClasses = new ArrayList();

   // The cached getPort method
   private Method getPortMethod;

   /**
    * Construct a client side service proxy.
    * <p/>
    * This proxy implements the (generated) service interface.
    *
    * @param service The underlying {@link javax.xml.rpc.Service}
    * @param siClass The service interface, a subclass of {@link javax.xml.rpc.Service}
    */
   public ServiceProxy(ServiceImpl service, Class siClass)
   {
      this.jaxrpcService = service;

      // initialize java.lang.Object methods
      objectMethods.addAll(Arrays.asList(java.lang.Object.class.getMethods()));

      // initialize javax.xml.rpc.Service methods
      jaxrpcServiceMethods.addAll(Arrays.asList(javax.xml.rpc.Service.class.getMethods()));

      // initialize SI methods
      if (siClass.getName().equals("javax.xml.rpc.Service") == false)
         serviceInterfaceMethods.addAll(Arrays.asList(siClass.getDeclaredMethods()));

      // initialize special ws4ee methods
      try
      {
         getPortMethod = Service.class.getMethod("getPort", new Class[]{Class.class});
      }
      catch (NoSuchMethodException e)
      {
         throw new JAXRPCException(e.toString());
      }
   }

   /**
    * Processes a method invocation on a proxy instance and returns
    * the result.
    */
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
   {
      String methodName = method.getName();

      try
      {
         Object retObj = null;
         if (jaxrpcServiceMethods.contains(method))
         {
            log.debug("Invoke on jaxrpc service: " + methodName);

            if (method.getName().equals("getPort"))
            {
               Class seiClass = (Class)(args.length == 1 ? args[0] : args[1]);
               endorseServiceEndpointClass(seiClass);

               Remote port = (Remote)method.invoke(jaxrpcService, args);
               return port;
            }
            else
            {
               retObj = method.invoke(jaxrpcService, args);
               return retObj;
            }
         }
         if (serviceInterfaceMethods.contains(method))
         {
            log.debug("Invoke on service interface: " + methodName);

            Class seiClass = method.getReturnType();
            retObj = getPortMethod.invoke(jaxrpcService, new Object[]{seiClass});
            return retObj;
         }
         if (objectMethods.contains(method))
         {
            log.debug("Invoke on object: " + methodName);

            retObj = method.invoke(jaxrpcService, args);
            return retObj;
         }

         throw new JAXRPCException("Don't know how to invoke: " + method);
      }
      catch (Exception e)
      {
         handleException(e);
         return null;
      }
   }

   /**
    * Log the client side exception
    */
   private void handleException(Exception ex) throws Throwable
   {
      Throwable th = ex;
      if (ex instanceof InvocationTargetException)
         th = ((InvocationTargetException)ex).getTargetException();

      log.error("Service error", th);

      throw th;
   }

   /**
    * Check if we can find a wsdl PortType that has all SEI methods
    */
   private void endorseServiceEndpointClass(Class seiClass) throws ServiceException
   {
      // return if we already endorsed this SEI
      if (endorsedServiceEndpointClasses.contains(seiClass))
         return;

      Definition wsdlDefinition = jaxrpcService.getWsdlDefinition();
      JavaWsdlMapping jaxrpcMapping = jaxrpcService.getJavaWsdlMapping();
      WSDLUtilities.endorseServiceEndpointInterface(wsdlDefinition, seiClass, jaxrpcMapping);

      endorsedServiceEndpointClasses.add(seiClass);
   }
}