/*
 * JBoss, Home of Professional Open Source
 * Copyright 2005, JBoss Inc., and individual contributors as indicated
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.jboss.net.axis;

import org.jboss.axis.ConfigurationException;
import org.jboss.axis.EngineConfiguration;
import org.jboss.axis.EngineConfigurationFactory;
import org.jboss.axis.client.Call;
import org.jboss.axis.client.Service;
import org.jboss.axis.configuration.EngineConfigurationFactoryFinder;

import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

/**
 * <p>
 * An invocation handler that allows typed and persistent 
 * client access to remote SOAP/Axis services. 
 * Adds method/interface to name resolution
 * to the axis client engine. Unfortunately the 
 * AxisClientProxy has a package-protected
 * constructor, otherwise we could inherit from there. 
 * </p>
 * @deprecated Due to the inherent deficiencies in 
 * using pure reflection meat-data to access web services, we recommend
 * using the full-blown jaxrpc/wsdl approach.
 * @created  1. Oktober 2001, 09:29
 * @author <a href="mailto:Christoph.Jung@infor.de">Christoph G. Jung</a>
 * @version $Revision: 1.8.2.6 $
 */

public class AxisInvocationHandler implements InvocationHandler, Serializable
{
   static final long serialVersionUID = -8250523938712824229L;

   //
   // Attributes
   //
	
   /** mapping of methods to interface names */
   protected Map methodToInterface;
   /** mapping of methods to method names */
   protected Map methodToName;
   /** the call object to which we delegate */
   transient protected Call call;
   /** if attached to a server-engine, we can reattach */
   protected String rootContext;
   /** the endpoint to which we are attached */
   protected String endPoint;
	
   //
   // Constructors
   //
	
   /** 
    * Creates a new AxisInvocationHandler and 
    * save some origin information to re-attach to the
    * engine after being serialized.
    * @param call the Axis call object
    * @param methodMap a map of Java method to service method names
    * @param interfaceMap a map of Java interface to service names 
    */
   public AxisInvocationHandler(Call call, Map methodMap, Map interfaceMap)
   {
      this.call = call;
      this.methodToInterface = interfaceMap;
      this.methodToName = methodMap;
      // we obtain the configuration of the service
      EngineConfiguration myEngineConfig = call.getService().getEngine().getConfig();

      try
      {
         rootContext = (String)myEngineConfig.getGlobalOptions().get(Constants.CONFIGURATION_CONTEXT);
      }
      catch (ConfigurationException e)
      {
// access problem
      }
      catch (NullPointerException e)
      {
// no global options
      }
      
// if the endpoint has already been specified,
      // save it for re-attachement
      if (call.getTargetEndpointAddress() != null)
      {
         endPoint = call.getTargetEndpointAddress().toString();
      }
   }

   /** 
    * Creates a new AxisInvocationHandler 
    * @param endpoint target address of the service
    * @param service an Axis service object
    * @param methodMap a map of Java method to service method names
    * @param interfaceMap a map of Java interface to service names 
    */
   public AxisInvocationHandler(URL endpoint,
                                String soapAction,
                                Service service,
                                Map methodMap,
                                Map interfaceMap)
   {
      this(new Call(service), methodMap, interfaceMap);
      call.setTargetEndpointAddress(endpoint);
      call.setSOAPActionURI(soapAction);
      setBasicAuthentication(endpoint);
      endPoint = endpoint.toString();
   }

   /** 
    * Creates a new AxisInvocationHandler 
    * @param endPoint target url of the web service
    * @param methodMap a map of Java method to service method names
    * @param interfaceMap a map of Java interface to service names 
    * @param maintainSession a flag that indicates whether this handler 
    * 	should keep a persistent session with the service endpoint
    */
   public AxisInvocationHandler(URL endPoint,
                                String soapAction,
                                Map methodMap,
                                Map interfaceMap,
                                boolean maintainSession)
   {
      this(endPoint, soapAction, new Service(), methodMap, interfaceMap);
      call.setMaintainSession(maintainSession);
   }

   /** 
    * Creates a new AxisInvocationHandler that keeps a persistent session with 
    * the service endpoint
    * @param endPoint target url of the web service
    * @param methodMap a map of Java method to service method names
    * @param interfaceMap a map of Java interface to service names 
    */
   public AxisInvocationHandler(URL endPoint, String soapAction, Map methodMap, Map interfaceMap)
   {
      this(endPoint, soapAction, methodMap, interfaceMap, true);
   }

   /** 
    * Creates a new AxisInvocationHandler that keeps a persistent session with 
    * the service endpoint. The unqualified name of the 
    * intercepted Java interface will be the used service name.
    * @param endPoint target url of the web service
    * @param methodMap a map of Java method to service method names
    */
   public AxisInvocationHandler(URL endPoint, String soapAction, Map methodMap)
   {
      this(endPoint, soapAction, methodMap, new DefaultInterfaceMap());
   }

   /** 
    * Creates a new AxisInvocationHandler that keeps a persistent session with 
    * the service endpoint. The unqualified name of the 
    * intercepted Java interface will be the used service name. 
    * Intercepted methods are mapped straightforwardly to web service names.
    * @param endPoint target url of the web service
    * @param methodMap a map of Java method to service method names
    */
   public AxisInvocationHandler(URL endPoint, String soapAction)
   {
      this(endPoint, soapAction, new DefaultMethodMap());
   }

   //
   // Helpers
   //
	
   /** helper to transfer url authentication information into engine */
   protected void setBasicAuthentication(URL target)
   {
      String userInfo = target.getUserInfo();
      if (userInfo != null)
      {
         java.util.StringTokenizer tok = new java.util.StringTokenizer(userInfo, ":");
         if (tok.hasMoreTokens())
         {
            call.setUsername(tok.nextToken());
            if (tok.hasMoreTokens())
            {
               call.setPassword(tok.nextToken());
            }
         }
      }
   }

   //
   // Invocationhandling API
   //
	
   /** invoke given namespace+method+args */
   public Object invoke(String serviceName, String methodName, Object[] args)
           throws java.rmi.RemoteException
   {
      try
      {
         return call.invoke(serviceName, methodName, args);
      }
      finally
      {
         call.setReturnType(null);
         call.removeAllParameters();
      }
   }

   /** invoke with additional method parameter signature */
   public Object invoke(String serviceName,
                        String methodName,
                        Object[] args,
                        Class[] parameters)
           throws java.rmi.RemoteException
   {
      // classes are normally ignored
      return invoke(serviceName, methodName, args);
   }

   /** generic invocation method */
   public java.lang.Object invoke(java.lang.Object target,
                                  java.lang.reflect.Method method,
                                  java.lang.Object[] args)
           throws java.lang.Throwable
   {
      return invoke((String)methodToInterface.get(method),
              (String)methodToName.get(method),
              args,
              method.getParameterTypes());
   }


   //
   // Static API
   //
	
   /** default creation of service */
   public static Object createAxisService(Class _interface, URL endpoint, String soapAction)
   {
      return createAxisService(_interface, new AxisInvocationHandler(endpoint, soapAction));
   }

   /** default creation of service */
   public static Object createAxisService(Class _interface,
                                          URL endpoint, String soapAction, Service service)
   {
      return createAxisService(_interface,
              new AxisInvocationHandler(endpoint, soapAction, service, new DefaultMethodMap(), new DefaultInterfaceMap()));
   }

   /** default creation of service */
   public static Object createAxisService(Class _interface,
                                          Call call)
   {
      return createAxisService(_interface,
              new AxisInvocationHandler(call, new DefaultMethodMap(), new DefaultInterfaceMap()));
   }

   /** default creation of service */
   public static Object createAxisService(Class _interface,
                                          AxisInvocationHandler handler)
   {
      return Proxy.newProxyInstance(_interface.getClassLoader(),
              new Class[]{_interface},
              handler);
   }

   /**
    * a tiny helper that does some default mapping of methods to interface names
    * we do not hash the actual reflection information because
    * we could collide with proxy serialisation holding fucking
    * old classes
    */

   public static class DefaultInterfaceMap extends HashMap
   {

      /** no entries is the default */
      public DefaultInterfaceMap()
      {
         super(0);
      }

      /** returns default interface if no mapping of method/interface is defined */
      public Object get(Object key)
      {

         // first try a direct lookup
         Object result = super.get(((Method)key).getName());

         if (result == null)
         {
            // if that is unsuccessful, we try to
            // lookup the class/interface itself
            result = super.get(((Method)key).getDeclaringClass().getName());

            // if that is not specified, we simply extract the
            // un-qualified classname
            if (result == null)
            {
               String sresult = ((Method)key).getDeclaringClass().getName();
               if (sresult.indexOf(".") != -1)
                  sresult = sresult.substring(sresult.lastIndexOf(".") + 1);
               result = sresult;
            }
         }

         return result;
      }

      /** registers an interface name for a given class or method */
      public Object put(Object key, Object value)
      {
         if (key instanceof Method)
         {
            return super.put(((Method)key).getName(), value);
         }
         else if (key instanceof Class)
         {
            return super.put(((Class)key).getName(), value);
         }
         else
         {
            return super.put(key, value);
         }
      }

   }

   /**
    * a tiny helper that does some default mapping of methods to method names
    * we do not hash the actual reflection information because
    * we could collide with proxy serialisation holding fucking
    * old classes
    */

   public static class DefaultMethodMap extends HashMap
   {

      /** no entries is the default */
      public DefaultMethodMap()
      {
         super(0);
      }

      /** returns default interface if no mapping is defined */
      public Object get(Object key)
      {

         Object result = super.get(((Method)key).getName());

         if (result == null)
         {
            result = ((Method)key).getName();
         }

         return result;
      }

      /** registers a new method */
      public Object put(Object key, Object value)
      {
         if (key instanceof Method)
         {
            return super.put(((Method)key).getName(), value);
         }
         else
         {
            return super.put(key, value);
         }
      }
   }

   /** 
    * serialization helper to reattach to engine, must be private
    * to be called correctly 
    */
   private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException
   {
      stream.defaultReadObject();
		
      // try to find the engine that we were attached to
      try
      {
         EngineConfigurationFactory factory = EngineConfigurationFactoryFinder.
                 newFactory(new ObjectName(rootContext));

         EngineConfiguration engine = null;

         if (factory != null)
         {
            engine = factory.getClientEngineConfig();
         }

         if (engine != null)
         {
            call = new Call(new Service(engine));
         }
         else
         {
            // not there, try the default config
            call = new Call(new Service());
         }
      }
      catch (MalformedObjectNameException e)
      {
         throw new IOException("Could not contact jmx configuration factory." + e);
      }

      URL endpoint = new URL(endPoint);
      call.setTargetEndpointAddress(endpoint);
      setBasicAuthentication(endpoint);
   }

}
