/*
 * Copyright 2001-2004 The Apache Software Foundation.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.axis.client;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Vector;

import javax.xml.namespace.QName;
import javax.xml.rpc.holders.Holder;

import org.apache.axis.description.OperationDesc;
import org.apache.axis.description.ParameterDesc;
import org.apache.axis.utils.JavaUtils;

/**
 * Very simple dynamic proxy InvocationHandler class.  This class is
 * constructed with a Call object, and then each time a method is invoked
 * on a dynamic proxy using this invocation handler, we simply turn it into
 * a SOAP request.
 *
 * @author Glen Daniels (gdaniels@apache.org)
 * @author C?dric Chabanois (cchabanois@ifrance.com)
 */
public class AxisClientProxy implements InvocationHandler {

    private Call call;
    private QName portName;

    /**
     * Constructor - package access only (should only really get used
     * in Service.getPort(endpoint, proxyClass).
     * Call can be pre-filled from wsdl
     */
    AxisClientProxy(Call call, QName portName)
    {
        this.call = call;
        this.portName = portName; // can be null
    }


    /**
     * Map between the parameters for the method call and the parameters needed
     * for the <code>Call</code>.
     * <p>
     * Parameters for invoke method are not the same as parameter for Call
     * instance :
     * - Holders must be converted to their mapped java types
     * - only in and inout parameters must be present in call parameters
     *
     * @param proxyParams proxyParameters
     * @return Object[]   Call parameters
     * @throws JavaUtils.HolderException
     */
    private Object[] proxyParams2CallParams(Object[] proxyParams)
        throws JavaUtils.HolderException
    {
        OperationDesc operationDesc = call.getOperation();
        if (operationDesc == null)
        {
            // we don't know which parameters are IN, OUT or INOUT
            // let's suppose they are all in
            return proxyParams;
        }

        Vector paramsCall = new Vector();
        for (int i = 0; proxyParams != null && i < proxyParams.length;i++)
        {
            Object param = proxyParams[i];
            ParameterDesc paramDesc = operationDesc.getParameter(i);

            if (paramDesc.getMode() == ParameterDesc.INOUT) {
                paramsCall.add(JavaUtils.getHolderValue((Holder)param));
            }
            else
            if (paramDesc.getMode() == ParameterDesc.IN) {
                paramsCall.add(param);
            }
        }
        return paramsCall.toArray();
    }

    /**
     * Copy in/out and out parameters (Holder parameters) back to proxyParams.
     *
     * @param proxyParams proxyParameters
     */
    private void callOutputParams2proxyParams(Object[] proxyParams)
        throws JavaUtils.HolderException
    {
        OperationDesc operationDesc = call.getOperation();
        if (operationDesc == null)
        {
            // we don't know which parameters are IN, OUT or INOUT
            // let's suppose they are all in
            return;
        }

        Map outputParams = call.getOutputParams();

        for (int i = 0; i < operationDesc.getNumParams();i++)
        {
            Object param = proxyParams[i];
            ParameterDesc paramDesc = operationDesc.getParameter(i);
            if ((paramDesc.getMode() == ParameterDesc.INOUT) ||
                (paramDesc.getMode() == ParameterDesc.OUT)) {

                  JavaUtils.setHolderValue((Holder)param,
                      outputParams.get(paramDesc.getQName()));
            }
        }
    }

    // fixme: what is o used for?
    /**
     * Handle a method invocation.
     *
     * @param o         the object to invoke relative to
     * @param method    the <code>Method</code> to invoke
     * @param objects   the arguments to the method
     * @return  the result of the method
     * @throws Throwable if anything went wrong in method dispatching or the
     *              execution of the method itself
     */
    public Object invoke(Object o, Method method, Object[] objects)
            throws Throwable {
        // first see if we invoke Stub methods
        if (method.getName().equals("_setProperty")) {
            call.setProperty((String) objects[0], objects[1]);
            return null;
        } else if (method.getName().equals("_getProperty")) {
            return call.getProperty((String) objects[0]);
        } else if (method.getName().equals("_getPropertyNames")) {
            return call.getPropertyNames();
        } else if (Object.class.equals(method.getDeclaringClass())) {
            // if we invoke basic Object methods : delegate to Call instance
            return method.invoke(call, objects);
        } else {
          Object outValue;
          Object[] paramsCall;

          if ((call.getTargetEndpointAddress() != null) &&
              (call.getPortName() != null)) {
              // call object has been prefilled : targetEndPoint and portname
              // are already set. We complete it with method informations
              call.setOperation(method.getName());
              paramsCall = proxyParams2CallParams(objects);
              outValue = call.invoke(paramsCall);
          }
          else if (portName != null)
          {
              // we only know the portName. Try to complete this information
              // from wsdl if available
              call.setOperation(portName,method.getName());
              paramsCall = proxyParams2CallParams(objects);
              outValue = call.invoke(paramsCall);
          }
          else
          {
              // we don't even know the portName (we don't have wsdl)
              paramsCall = objects;
              outValue = call.invoke(method.getName(), paramsCall);
          }
          callOutputParams2proxyParams(objects);
          return outValue;
        }
    }

    /**
     * Returns the current call.
     *
     * @return the current <code>Call</code>
     */
    public Call getCall(){
        return call;
    }
}
