/*
 * JBoss, the OpenSource J2EE webOS
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */
package org.jboss.webservice.client;

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

import org.jboss.axis.client.AxisClientProxy;
import org.jboss.logging.Logger;

import javax.xml.rpc.JAXRPCException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

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

   // The underlying jaxrpc call
   private Remote port;
   private CallImpl call;

   // The methods from the SEI
   private List seiMethods = new ArrayList();
   // The methods from java.lang.Object
   private List objectMethods = new ArrayList();
   // The methods from javax.xml.rpc.Stub
   private List stubMethods = new ArrayList();

   // The stub property methods
   private Method getPropertyMethod;
   private Method setPropertyMethod;

   /**
    * Construct a client side call proxy.
    * <p/>
    * This proxy implements the (generated) service interface and the service endpoint interface
    * for each port component ref.
    *
    * @param port     The underlying proxy to the service endpoint
    * @param seiClass The service endpoint interface
    */
   public PortProxy(Remote port, Class seiClass)
   {
      this.port = port;

      // Get the underlying call object
      AxisClientProxy axisClientProxy = (AxisClientProxy)Proxy.getInvocationHandler(port);
      this.call = (CallImpl)axisClientProxy.getCall();

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

      // initialize SEI methods
      seiMethods.addAll(Arrays.asList(seiClass.getMethods()));

      // initialize javax.xml.rpc.Stub methods
      stubMethods.addAll(Arrays.asList(org.jboss.webservice.client.Stub.class.getMethods()));

      // initialize javax.xml.rpc.Stub property methods
      try
      {
         getPropertyMethod = Stub.class.getMethod("_getProperty", new Class[]{String.class});
         setPropertyMethod = Stub.class.getMethod("_setProperty", new Class[]{String.class, Object.class});
      }
      catch (NoSuchMethodException ignore)
      {
      }
   }

   /**
    * 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 (seiMethods.contains(method))
         {
            log.debug("Invoke on service endpoint interface: " + methodName);

            retObj = method.invoke(port, args);
            return retObj;
         }
         if (stubMethods.contains(method))
         {
            log.debug("Invoke on stub interface: " + methodName);

            // Handle standard properties
            if ("getUsername".equals(methodName))
               retObj = getPropertyMethod.invoke(port, new Object[]{Stub.USERNAME_PROPERTY});
            else if ("setUsername".equals(methodName))
               retObj = setPropertyMethod.invoke(port, new Object[]{Stub.USERNAME_PROPERTY, args[0]});
            else if ("getPassword".equals(methodName))
               retObj = getPropertyMethod.invoke(port, new Object[]{Stub.PASSWORD_PROPERTY});
            else if ("setPassword".equals(methodName))
               retObj = setPropertyMethod.invoke(port, new Object[]{Stub.PASSWORD_PROPERTY, args[0]});
            else if ("getEndpointAddress".equals(methodName))
               retObj = getPropertyMethod.invoke(port, new Object[]{Stub.ENDPOINT_ADDRESS_PROPERTY});
            else if ("setEndpointAddress".equals(methodName))
               retObj = setPropertyMethod.invoke(port, new Object[]{Stub.ENDPOINT_ADDRESS_PROPERTY, args[0]});
            else if ("getSessionMaintain".equals(methodName))
               retObj = getPropertyMethod.invoke(port, new Object[]{Stub.SESSION_MAINTAIN_PROPERTY});
            else if ("setSessionMaintain".equals(methodName))
               retObj = setPropertyMethod.invoke(port, new Object[]{Stub.SESSION_MAINTAIN_PROPERTY, args[0]});

            // Handle non standard timeout
            else if ("getTimeout".equals(methodName))
               retObj = call.getTimeout();
            else if ("setTimeout".equals(methodName))
               call.setTimeout((Integer)args[0]);
            else if (getPropertyMethod.equals(method) && Stub.CLIENT_TIMEOUT_PROPERTY.equals(args[0]))
               retObj = call.getTimeout();
            else if (setPropertyMethod.equals(method) && Stub.CLIENT_TIMEOUT_PROPERTY.equals(args[0]))
               call.setTimeout((Integer)args[1]);

            // Handle non standard attachments
            else if ("addAttachment".equals(methodName))
               call.addAttachment((String)args[0], args[1]);
            else if ("removeAttachment".equals(methodName))
               call.removeAttachment((String)args[0]);
            else if ("getAttachments".equals(methodName))
               retObj = call.getAttachmentIdentifiers();
            else if ("getAttachment".equals(methodName))
               retObj = call.getAttachment((String)args[0]);

            // It's a standard Stub method 
            else
               retObj = method.invoke(port, args);

            return retObj;
         }
         if (objectMethods.contains(method))
         {
            log.debug("Invoke on object: " + methodName);

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

         throw new JAXRPCException("Don't know how to invoke: " + method);
      }
      catch (Exception e)
      {
         Throwable th = processException(e);

         // On the client side, the JAXRPCException or runtime exception is propagated to the
         // client code as a RemoteException or its subtype.
         if (seiMethods.contains(method))
         {
            if (th instanceof JAXRPCException || th instanceof RuntimeException)
            {
               RemoteException re = new RemoteException(th.getMessage(), th);
               th = re;
            }
         }

         throw th;
      }
   }

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

      log.error("Port error", th);
      return th;
   }
}