/*
 * 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 org.apache.axis.AxisFault;
import org.apache.axis.AxisProperties;
import org.apache.axis.Constants;
import org.apache.axis.Handler;
import org.apache.axis.InternalException;
import org.apache.axis.Message;
import org.apache.axis.MessageContext;
import org.apache.axis.AxisEngine;
import org.apache.axis.SOAPPart;
import org.apache.axis.attachments.Attachments;
import org.apache.axis.components.logger.LogFactory;
import org.apache.axis.description.FaultDesc;
import org.apache.axis.description.OperationDesc;
import org.apache.axis.description.ParameterDesc;
import org.apache.axis.encoding.DeserializerFactory;
import org.apache.axis.encoding.SerializationContext;
import org.apache.axis.encoding.SerializerFactory;
import org.apache.axis.encoding.TypeMapping;
import org.apache.axis.encoding.TypeMappingRegistry;
import org.apache.axis.encoding.XMLType;
import org.apache.axis.encoding.ser.BaseDeserializerFactory;
import org.apache.axis.encoding.ser.BaseSerializerFactory;
import org.apache.axis.constants.Style;
import org.apache.axis.constants.Use;
import org.apache.axis.handlers.soap.SOAPService;
import org.apache.axis.message.RPCElement;
import org.apache.axis.message.RPCHeaderParam;
import org.apache.axis.message.RPCParam;
import org.apache.axis.message.SOAPBodyElement;
import org.apache.axis.message.SOAPEnvelope;
import org.apache.axis.message.SOAPFault;
import org.apache.axis.message.SOAPHeaderElement;
import org.apache.axis.soap.SOAPConstants;
import org.apache.axis.transport.http.HTTPTransport;
import org.apache.axis.utils.ClassUtils;
import org.apache.axis.utils.JavaUtils;
import org.apache.axis.utils.Messages;
import org.apache.axis.utils.LockableHashtable;
import org.apache.axis.wsdl.symbolTable.BindingEntry;
import org.apache.axis.wsdl.symbolTable.Parameter;
import org.apache.axis.wsdl.symbolTable.Parameters;
import org.apache.axis.wsdl.symbolTable.SymbolTable;
import org.apache.axis.wsdl.symbolTable.FaultInfo;
import org.apache.axis.wsdl.toJava.Utils;
import org.apache.commons.logging.Log;

import javax.wsdl.Binding;
import javax.wsdl.BindingInput;
import javax.wsdl.BindingOperation;
import javax.wsdl.Operation;
import javax.wsdl.extensions.mime.MIMEPart;
import javax.wsdl.extensions.mime.MIMEMultipartRelated;
import javax.wsdl.Part;
import javax.wsdl.Port;
import javax.wsdl.PortType;
import javax.wsdl.extensions.soap.SOAPAddress;
import javax.wsdl.extensions.soap.SOAPBody;
import javax.wsdl.extensions.soap.SOAPOperation;
import javax.xml.namespace.QName;
import javax.xml.rpc.JAXRPCException;
import javax.xml.rpc.ParameterMode;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPMessage;

import java.io.StringWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.Vector;
import java.rmi.RemoteException;

/**
 * Axis' JAXRPC Dynamic Invocation Interface implementation of the Call
 * interface.  This class should be used to actually invoke the Web Service.
 * It can be prefilled by a WSDL document (on the constructor to the Service
 * object) or you can fill in the data yourself.
 * <pre>
 * Standard properties defined by in JAX-RPC's javax..xml.rpc.Call interface:
 *     USERNAME_PROPERTY        - User name for authentication
 *     PASSWORD_PROPERTY        - Password for authentication
 *     SESSION_PROPERTY         - Participate in a session with the endpoint?
 *     OPERATION_STYLE_PROPERTY - "rpc" or "document"
 *     SOAPACTION_USE_PROPERTY  - Should SOAPAction be used?
 *     SOAPACTION_URI_PROPERTY  - If SOAPAction is used, this is that action
 *     ENCODING_STYLE_PROPERTY  - Default is SOAP 1.1:  "http://schemas.xmlsoap.org/soap/encoding/"
 *
 * AXIS properties:
 *     SEND_TYPE_ATTR - Should we send the XSI type attributes (true/false)
 *     TIMEOUT        - Timeout used by transport sender in milliseconds
 *     TRANSPORT_NAME - Name of transport handler to use
 *     ATTACHMENT_ENCAPSULATION_FORMAT- Send attachments as MIME the default, or DIME.
 *     CHARACTER_SET_ENCODING - Character set encoding to use for request
 * </pre>
 *
 * @author Doug Davis (dug@us.ibm.com)
 * @author Steve Loughran
 */

public class Call implements javax.xml.rpc.Call {
    protected static Log log =
        LogFactory.getLog(Call.class.getName());
    private static Log tlog =
        LogFactory.getLog(Constants.TIME_LOG_CATEGORY);

    // The enterprise category is for stuff that an enterprise product might
    // want to track, but in a simple environment (like the AXIS build) would
    // be nothing more than a nuisance.
    protected static Log entLog =
        LogFactory.getLog(Constants.ENTERPRISE_LOG_CATEGORY);

    private boolean            parmAndRetReq   = true ;
    private Service            service         = null ;
    private QName              portName        = null;
    private QName              portTypeName    = null;
    private QName              operationName   = null ;

    private MessageContext     msgContext      = null ;

    // Collection of properties to store and put in MessageContext at
    // invoke() time.  Known ones are stored in actual variables for
    // efficiency/type-consistency.  Unknown ones are in myProperties.
    private LockableHashtable  myProperties    = new LockableHashtable();
    private String             username        = null;
    private String             password        = null;
    private boolean            maintainSession = false;
    private boolean            useSOAPAction   = false;
    private String             SOAPActionURI   = null;
    private Integer            timeout         = null;
    private boolean            useStreaming    = false;

    /** Metadata for the operation associated with this Call */
    private OperationDesc      operation       = null;
    /** This will be true if an OperationDesc is handed to us whole */
    private boolean operationSetManually       = false;

    // Is this a one-way call?
    private boolean invokeOneWay               = false;
    private boolean isMsg                      = false;

    // Our Transport, if any
    private Transport          transport       = null ;
    private String             transportName   = null ;

    // A couple places to store output parameters.
    // As a HashMap, retrievable via QName (for getOutputParams).
    private HashMap            outParams       = null;
    // As a list, retrievable by index (for getOutputValues).
    private ArrayList          outParamsList   = null;

    // A place to store any client-specified headers
    private Vector             myHeaders       = null;

    public static final String SEND_TYPE_ATTR    = AxisEngine.PROP_SEND_XSI;

    /**
     * This is the name of a property to set the transport of the message
     *
     * @see #setProperty
     */
    public static final String TRANSPORT_NAME    = "transport_name" ;

    /**
     * This is the character set encoding to use for the message
     *
     * @see #setProperty
     */
    public static final String CHARACTER_SET_ENCODING = SOAPMessage.CHARACTER_SET_ENCODING;

    /**
     * This is not the name of a property that can be set with
     * setProperty, despite its name.
     */
    public static final String TRANSPORT_PROPERTY= "java.protocol.handler.pkgs";

    /**
     * this is a property set in the message context when the invocation
     * process begins, for the benefit of handlers
     */
    public static final String WSDL_SERVICE      = "wsdl.service";

    /**
     * this is a property set in the message context when the invocation
     * process begins, for the benefit of handlers
     */
    public static final String WSDL_PORT_NAME    = "wsdl.portName";

    /**
     * @deprecated use WSDL_SERVICE instead.
     */
    public static final String JAXRPC_SERVICE    = WSDL_SERVICE;

    /**
     * @deprecated use WSDL_PORT_NAME instead.
     */
    public static final String JAXRPC_PORTTYPE_NAME = WSDL_PORT_NAME;

    /**
     * If this property is true, the code will throw a fault if there is no
     * response message from the server.  Otherwise, the
     * invoke method will return a null.
     */
    public static final String FAULT_ON_NO_RESPONSE = "call.FaultOnNoResponse";

    /**
     * If this property is true, code will enforce must understand check on both
     * the request and the response paths.
     */ 
    public static final String CHECK_MUST_UNDERSTAND = "call.CheckMustUnderstand";
    
    /**
     * Property for setting attachment format.
     * Can be set to either DIME or MIME (default)
     * @see #setProperty
     * @see #ATTACHMENT_ENCAPSULATION_FORMAT_DIME
     * @see #ATTACHMENT_ENCAPSULATION_FORMAT_MIME
     * @see #ATTACHMENT_ENCAPSULATION_FORMAT_MTOM
     */
    public static final String ATTACHMENT_ENCAPSULATION_FORMAT=
      "attachment_encapsulation_format";
    /**
     * Property value for setting attachment format as MIME.
     */
    public static final String ATTACHMENT_ENCAPSULATION_FORMAT_MIME=
      "axis.attachment.style.mime";
    /**
     * Property value for setting attachment format as DIME.
     */
    public static final String ATTACHMENT_ENCAPSULATION_FORMAT_DIME=
      "axis.attachment.style.dime";
    /**
     * Property value for setting attachment format as DIME.
     */
    public static final String ATTACHMENT_ENCAPSULATION_FORMAT_MTOM=
      "axis.attachment.style.mtom";

    /**
     * Timeout property: should be accompanies by an integer
     * @see #setProperty
     */
    public static final String CONNECTION_TIMEOUT_PROPERTY =
            "axis.connection.timeout";

    /**
     * Streaming property: should be accompanied by an boolean
     * (i.e. NO high-fidelity recording, deserialize on the fly)
     * @see #setProperty
     */
    public static final String STREAMING_PROPERTY =
            "axis.streaming";
    
    /**
     * Internal property to indicate a one way call.
     * That will disable processing of response handlers.
     */
    protected static final String ONE_WAY =
        "axis.one.way";

    /**
     * A Hashtable mapping protocols (Strings) to Transports (classes)
     */
    private static Hashtable transports  = new Hashtable();

    static ParameterMode [] modes = new ParameterMode [] { null,
                                            ParameterMode.IN,
                                            ParameterMode.OUT,
                                            ParameterMode.INOUT };

    /** This is true when someone has called setEncodingStyle() */
    private boolean encodingStyleExplicitlySet = false;
    /** This is true when someone has called setOperationUse() */
    private boolean useExplicitlySet = false;

    /**
     * the name of a SOAP service that the call is bound to
     */
    private SOAPService myService = null;

    /**
     * these are our attachments
     */
    protected java.util.Vector attachmentParts = new java.util.Vector();

    /** This is false when invoke() is called. */
    private boolean isNeverInvoked  = true;     

    static {
        initialize();
    }

    /************************************************************************/
    /* Start of core JAX-RPC stuff                                          */
    /************************************************************************/

    /**
     * Default constructor - not much else to say.
     *
     * @param service  the <code>Service</code> this <code>Call</code> will
     *              work with
     */
    public Call(Service service) {
        this.service = service ;
        AxisEngine engine = service.getEngine();
        msgContext = new MessageContext( engine );
        myProperties.setParent(engine.getOptions());
        maintainSession = service.getMaintainSession();
    }

    /**
     * Build a call from a URL string.
     *
     * This is handy so that you don't have to manually call Call.initialize()
     * in order to register custom transports.  In other words, whereas doing
     * a new URL("local:...") would fail, new Call("local:...") works because
     * we do the initialization of our own and any configured custom protocols. 
     *
     * @param url the target endpoint URL
     * @exception MalformedURLException
     */
    public Call(String url) throws MalformedURLException {
        this(new Service());
        setTargetEndpointAddress(new URL(url));
    }

    /**
     * Build a call from a URL.
     *
     * @param url the target endpoint URL
     */
    public Call(URL url) {
        this(new Service());
        setTargetEndpointAddress(url);
    }

    ////////////////////////////
    //
    // Properties and the shortcuts for common ones.
    //

    /**
     * Allows you to set a named property to the passed in value.
     * There are a few known properties (like username, password, etc)
     * that are variables in Call.  The rest of the properties are
     * stored in a Hashtable.  These common properties should be
     * accessed via the accessors for speed/type safety, but they may
     * still be obtained via this method.  It's up to one of the
     * Handlers (or the Axis engine itself) to go looking for
     * one of them.
     *
     * There are various well defined properties defined in the
     * JAX-RPC specification and declared in the Call and Stub classes.
     * It is not possible to set any other properties beginning in java. or
     * javax. that are not in the specification.
     * @see javax.xml.rpc.Stub
     * @see javax.xml.rpc.Call
     *
     * There are other properties implemented in this class above and
     * beyond those of the JAX-RPC spec
     * Specifically, ATTACHMENT_ENCAPSULATION_FORMAT, CONNECTION_TIMEOUT_PROPERTY,
     * and TRANSPORT_NAME.
     *
     * It is intended that all future Axis-specific properties will begin
     * with axis. or apache. To ensure integration with future versions Axis,
     * use different prefixes for your own properties.
     *
     * Axis developers: keep this in sync with propertyNames below
     * @see #ATTACHMENT_ENCAPSULATION_FORMAT
     * @see #TRANSPORT_NAME
     * @see #CONNECTION_TIMEOUT_PROPERTY
     * @param name  Name of the property
     * @param value Value of the property
     */
    public void setProperty(String name, Object value) {
        if (name == null || value == null) {
            throw new JAXRPCException(
                    Messages.getMessage(name == null ?
                                         "badProp03" : "badProp04"));
        }
        else if (name.equals(USERNAME_PROPERTY)) {
            verifyStringProperty(name, value);
            setUsername((String) value);
        }
        else if (name.equals(PASSWORD_PROPERTY)) {
            verifyStringProperty(name, value);
            setPassword((String) value);
        }
        else if (name.equals(SESSION_MAINTAIN_PROPERTY)) {
            verifyBooleanProperty(name, value);
            setMaintainSession(((Boolean) value).booleanValue());
        }
        else if (name.equals(OPERATION_STYLE_PROPERTY)) {
            verifyStringProperty(name, value);
            setOperationStyle((String) value);
            if (getOperationStyle() == Style.DOCUMENT ||
                getOperationStyle() == Style.WRAPPED) {
                setOperationUse(Use.LITERAL_STR);
            } else if (getOperationStyle() == Style.RPC) {
                setOperationUse(Use.ENCODED_STR);
            }
        }
        else if (name.equals(SOAPACTION_USE_PROPERTY)) {
            verifyBooleanProperty(name, value);
            setUseSOAPAction(((Boolean) value).booleanValue());
        }
        else if (name.equals(SOAPACTION_URI_PROPERTY)) {
            verifyStringProperty(name, value);
            setSOAPActionURI((String) value);
        }
        else if (name.equals(ENCODINGSTYLE_URI_PROPERTY)) {
            verifyStringProperty(name, value);
            setEncodingStyle((String) value);
        }
        else if (name.equals(Stub.ENDPOINT_ADDRESS_PROPERTY)) {
            verifyStringProperty(name, value);
            setTargetEndpointAddress((String) value);
        }
        else if ( name.equals(TRANSPORT_NAME) ) {
            verifyStringProperty(name, value);
            transportName = (String) value ;
            if (transport != null) {
                transport.setTransportName((String) value);
            }
        }
        else if ( name.equals(ATTACHMENT_ENCAPSULATION_FORMAT) ) {
            verifyStringProperty(name, value);
            if(!value.equals(ATTACHMENT_ENCAPSULATION_FORMAT_MIME ) &&
               !value.equals(ATTACHMENT_ENCAPSULATION_FORMAT_MTOM ) &&
               !value.equals(ATTACHMENT_ENCAPSULATION_FORMAT_DIME ))
                throw new JAXRPCException(
                        Messages.getMessage("badattachmenttypeerr", new String[] {
                        (String) value, ATTACHMENT_ENCAPSULATION_FORMAT_MIME + " "
                        +ATTACHMENT_ENCAPSULATION_FORMAT_MTOM + " "
                        +ATTACHMENT_ENCAPSULATION_FORMAT_DIME  }));
        }
        else if (name.equals(CONNECTION_TIMEOUT_PROPERTY)) {
            verifyIntegerProperty(name,value);
            setTimeout((Integer)value);
        }
        else if (name.equals(STREAMING_PROPERTY)) {
            verifyBooleanProperty(name, value);
            setStreaming(((Boolean) value).booleanValue());
        }
        else if (name.equals(CHARACTER_SET_ENCODING)) {
            verifyStringProperty(name, value);
        }
        else if (name.startsWith("java.") || name.startsWith("javax.")) {
            throw new JAXRPCException(
                    Messages.getMessage("badProp05", name));
        }
        myProperties.put(name, value);
    } // setProperty

    /**
     * Verify that the type of the object is a String, and throw
     * an i18n-ized exception if not
     * @param name
     * @param value
     * @throws JAXRPCException if value is not a String
     */
    private void verifyStringProperty(String name, Object value) {
        if (!(value instanceof String)) {
            throw new JAXRPCException(
                    Messages.getMessage("badProp00", new String[]
                    {name,
                    "java.lang.String",
                    value.getClass().getName()}));
        }
    }

    /**
     * Verify that the type of the object is a Boolean, and throw
     * an i18n-ized exception if not
     * @param name
     * @param value
     * @throws JAXRPCException if value is not a Boolean
     */
    private void verifyBooleanProperty(String name, Object value) {
        if (!(value instanceof Boolean)) {
            throw new JAXRPCException(
                    Messages.getMessage("badProp00", new String[]
                    {name,
                    "java.lang.Boolean",
                    value.getClass().getName()}));
        }
    }

    /**
     * Verify that the type of the object is an Integer, and throw
     * an i18n-ized exception if not
     * @param name
     * @param value
     * @throws JAXRPCException if value is not an Integer
     */
    private void verifyIntegerProperty(String name, Object value) {
        if (!(value instanceof Integer)) {
            throw new JAXRPCException(
                    Messages.getMessage("badProp00", new String[]
                    {name,
                     "java.lang.Integer",
                     value.getClass().getName()}));
        }
    }

    /**
     * Returns the value associated with the named property.
     *
     * @param name the name of the property
     * @return Object value of the property or null if the property is not set
     * @throws JAXRPCException if the requested property is not a supported property
     */
    public Object getProperty(String name) {
        if (name == null || !isPropertySupported(name)) {
            throw new JAXRPCException(name == null ?
                  Messages.getMessage("badProp03") :
                  Messages.getMessage("badProp05", name));
        }
        return myProperties.get(name);
    } // getProperty

    /**
      * Removes (if set) the named property.
      *
      * @param name name of the property to remove
      */
     public void removeProperty(String name) {
         if (name == null || !isPropertySupported(name)) {
            throw new JAXRPCException(name == null ?
                  Messages.getMessage("badProp03") :
                  Messages.getMessage("badProp05", name));
         }
         myProperties.remove(name);
     } // removeProperty

    /**
     * Configurable properties supported by this Call object.
     */
    private static ArrayList propertyNames = new ArrayList();
    static {
        propertyNames.add(USERNAME_PROPERTY);
        propertyNames.add(PASSWORD_PROPERTY);
        propertyNames.add(SESSION_MAINTAIN_PROPERTY);
        propertyNames.add(OPERATION_STYLE_PROPERTY);
        propertyNames.add(SOAPACTION_USE_PROPERTY);
        propertyNames.add(SOAPACTION_URI_PROPERTY);
        propertyNames.add(ENCODINGSTYLE_URI_PROPERTY);
        propertyNames.add(Stub.ENDPOINT_ADDRESS_PROPERTY);
        propertyNames.add(TRANSPORT_NAME);
        propertyNames.add(ATTACHMENT_ENCAPSULATION_FORMAT);
        propertyNames.add(CONNECTION_TIMEOUT_PROPERTY);
        propertyNames.add(CHARACTER_SET_ENCODING);
    }

    public Iterator getPropertyNames() {
        return propertyNames.iterator();
    }

    public boolean isPropertySupported(String name) {
        return propertyNames.contains(name) || (!name.startsWith("java.")
               && !name.startsWith("javax."));
    }

    /**
     * Set the username.
     *
     * @param username  the new user name
     */
    public void setUsername(String username) {
        this.username = username;
    } // setUsername

    /**
     * Get the user name.
     *
     * @return the user name
     */
    public String getUsername() {
        return username;
    } // getUsername

    /**
     * Set the password.
     *
     * @param password  plain-text copy of the password
     */
    public void setPassword(String password) {
        this.password = password;
    } // setPassword

    /**
     * Get the password.
     *
     * @return a plain-text copy of the password
     */
    public String getPassword() {
        return password;
    } // getPassword

    /**
     * Determine whether we'd like to track sessions or not.  This
     * overrides the default setting from the service.
     * This just passes through the value into the MessageContext.
     * Note: Not part of JAX-RPC specification.
     *
     * @param yesno true if session state is desired, false if not.
     */
    public void setMaintainSession(boolean yesno) {
        maintainSession = yesno;
    }

    /**
     * Get the value of maintainSession flag.
     *
     * @return true if session is maintained, false otherwise
     */
    public boolean getMaintainSession() {
        return maintainSession;
    }

    /**
     * Set the operation style: "document", "rpc"
     * @param operationStyle string designating style
     */
    public void setOperationStyle(String operationStyle) {
        Style style = Style.getStyle(operationStyle, Style.DEFAULT);
        setOperationStyle(style);
    } // setOperationStyle

    /**
     * Set the operation style
     *
     * @param operationStyle
     */
    public void setOperationStyle(Style operationStyle) {
        if (operation == null) {
            operation = new OperationDesc();
        }

        operation.setStyle(operationStyle);

        // If no one has explicitly set the use, we should track
        // the style.  If it's non-RPC, default to LITERAL.
        if (!useExplicitlySet) {
            if (operationStyle != Style.RPC) {
                operation.setUse(Use.LITERAL);
            }
        }

        // If no one has explicitly set the encodingStyle, we should
        // track the style.  If it's RPC, default to SOAP-ENC, otherwise
        // default to "".
        if (!encodingStyleExplicitlySet) {
            String encStyle = "";
            if (operationStyle == Style.RPC) {
                // RPC style defaults to encoded, otherwise default to literal
                encStyle = msgContext.getSOAPConstants().getEncodingURI();
            }
            msgContext.setEncodingStyle(encStyle);
        }
    }

    /**
     * Get the operation style.
     *
     * @return the <code>Style</code> of the operation
     */
    public Style getOperationStyle() {
        if (operation != null) {
            return operation.getStyle();
        }
        return Style.DEFAULT;
    } // getOperationStyle

    /**
     * Set the operation use: "literal", "encoded"
     * @param operationUse string designating use
     */
    public void setOperationUse(String operationUse) {
        Use use = Use.getUse(operationUse, Use.DEFAULT);
        setOperationUse(use);
    } // setOperationUse

    /**
     * Set the operation use
     * @param operationUse
     */
    public void setOperationUse(Use operationUse) {
        useExplicitlySet = true;

        if (operation == null) {
            operation = new OperationDesc();
        }

        operation.setUse(operationUse);
        if (!encodingStyleExplicitlySet) {
            String encStyle = "";
            if (operationUse == Use.ENCODED) {
                // RPC style defaults to encoded, otherwise default to literal
                encStyle = msgContext.getSOAPConstants().getEncodingURI();
            }
            msgContext.setEncodingStyle(encStyle);
        }
    }

    /**
     * Get the operation use.
     *
     * @return the <code>Use</code> of the operation
     */
    public Use getOperationUse() {
        if (operation != null) {
            return operation.getUse();
        }
        return Use.DEFAULT;
    } // getOperationStyle

    /**
     * Flag to indicate if soapAction should be used.
     *
     * @param useSOAPAction  true if the soapAction header is to be used to
     *              help find the method to invoke, false otherwise
     */
    public void setUseSOAPAction(boolean useSOAPAction) {
        this.useSOAPAction = useSOAPAction;
    } // setUseSOAPAction

    /**
     * Discover if soapAction is being used.
     *
     * @return true if it is, false otherwise
     */
    public boolean useSOAPAction() {
        return useSOAPAction;
    } // useSOAPAction

    /**
     * Set the soapAction URI.
     *
     * @param SOAPActionURI  the new SOAP action URI
     */
    public void setSOAPActionURI(String SOAPActionURI) {
        useSOAPAction = true;
        this.SOAPActionURI = SOAPActionURI;
    } // setSOAPActionURI

    /**
     * Get the soapAction URI.
     *
     * @return the curretn SOAP action URI
     */
    public String getSOAPActionURI() {
        return SOAPActionURI;
    } // getSOAPActionURI

    /**
     * Sets the encoding style to the URL passed in.
     *
     * @param namespaceURI URI of the encoding to use.
     */
    public void setEncodingStyle(String namespaceURI) {
        encodingStyleExplicitlySet = true;
        msgContext.setEncodingStyle(namespaceURI);
    }

    /**
     * Returns the encoding style as a URI that should be used for the SOAP
     * message.
     *
     * @return String URI of the encoding style to use
     */
    public String getEncodingStyle() {
        return msgContext.getEncodingStyle();
    }

    /**
     * Sets the endpoint address of the target service port. This address must
     * correspond to the transport specified in the binding for this Call
     * instance.
     *
     * @param address - Endpoint address of the target service port; specified
     *                  as URI
     */
    public void setTargetEndpointAddress(String address) {
        URL urlAddress;
        try {
            urlAddress = new URL(address);
        }
        catch (MalformedURLException mue) {
            throw new JAXRPCException(mue);
        }
        setTargetEndpointAddress(urlAddress);
    }

    /**
     * Sets the URL of the target Web Service.
     *
     * Note: Not part of JAX-RPC specification.
     *
     * @param address URL of the target Web Service
     */
    public void setTargetEndpointAddress(java.net.URL address) {
        try {
            if ( address == null ) {
                setTransport(null);
                return ;
            }

            String protocol = address.getProtocol();

            // Handle the case where the protocol is the same but we
            // just want to change the URL - if so just set the URL,
            // creating a new Transport object will drop all session
            // data - and we want that stuff to persist between invoke()s.
            // Technically the session data should be in the message
            // context so that it can be persistent across transports
            // as well, but for now the data is in the Transport object.
            ////////////////////////////////////////////////////////////////
            if ( this.transport != null ) {
                String oldAddr = this.transport.getUrl();
                if ( oldAddr != null && !oldAddr.equals("") ) {
                    URL     tmpURL   = new URL( oldAddr );
                    String  oldProto = tmpURL.getProtocol();
                    if ( protocol.equals(oldProto) ) {
                        this.transport.setUrl( address.toString() );
                        return ;
                    }
                }
            }

            // Do we already have a transport for this address?
            Transport transport = service.getTransportForURL(address);
            if (transport != null) {
                setTransport(transport);
            }
            else {
            // We don't already have a transport for this address.  Create one.
                transport = getTransportForProtocol(protocol);
                if (transport == null)
                    throw new AxisFault("Call.setTargetEndpointAddress",
                                 Messages.getMessage("noTransport01",
                                 protocol), null, null);
                transport.setUrl(address.toString());
                setTransport(transport);
                service.registerTransportForURL(address, transport);
            }
        }
        catch( Exception exp ) {
            log.error(Messages.getMessage("exception00"), exp);
            // do what?
            // throw new AxisFault("Call.setTargetEndpointAddress",
            //"Malformed URL Exception: " + e.getMessage(), null, null);
        }
    }

    /**
     * Returns the URL of the target Web Service.
     *
     * @return URL URL of the target Web Service
     */
    public String getTargetEndpointAddress() {
        try {
            if ( transport == null ) return( null );
            return( transport.getUrl() );
        }
        catch( Exception exp ) {
            return( null );
        }
    }

    public Integer getTimeout() {
        return timeout;
    }

    public void setTimeout(Integer timeout) {
        this.timeout = timeout;
    }

    public boolean getStreaming() {
        return useStreaming;
    }

    public void setStreaming(boolean useStreaming) {
        this.useStreaming = useStreaming;
    }
    //
    // end properties code.
    //
    ////////////////////////////

    /**
     * Is the caller required to provide the parameter and return type
     * specification?
     * If true, then
     *  addParameter and setReturnType MUST be called to provide the meta data.
     * If false, then
     *  addParameter and setReturnType SHOULD NOT be called because the
     *  Call object already has the meta data describing the
     *  parameters and return type. If addParameter is called, the specified
     *  parameter is added to the end of the list of parameters.
     */
    public boolean isParameterAndReturnSpecRequired(QName operationName) {
        return parmAndRetReq;
    } // isParameterAndReturnSpecRequired

    /**
     * Adds the specified parameter to the list of parameters for the
     * operation associated with this Call object.
     *
     * Note: Not part of JAX-RPC specification.
     *
     * @param paramName Name that will be used for the parameter in the XML
     * @param xmlType XMLType of the parameter
     * @param parameterMode one of IN, OUT or INOUT
     */
    public void addParameter(QName paramName, QName xmlType,
            ParameterMode parameterMode) {
        Class javaType = null;
        TypeMapping tm = getTypeMapping();
        if (tm != null) {
            javaType = tm.getClassForQName(xmlType);
        }
        addParameter(paramName, xmlType, javaType, parameterMode);
    }

    /**
     * Adds the specified parameter to the list of parameters for the
     * operation associated with this Call object.
     *
     *
     * Note: Not part of JAX-RPC specification.
     *
     * @param paramName Name that will be used for the parameter in the XML
     * @param xmlType XMLType of the parameter
     * @param javaType The Java class of the parameter
     * @param parameterMode one of IN, OUT or INOUT
     */
    public void addParameter(QName paramName, QName xmlType,
            Class javaType, ParameterMode parameterMode) {

        if (operationSetManually) {
            throw new RuntimeException(
                    Messages.getMessage("operationAlreadySet"));
        }

        if (operation == null)
            operation = new OperationDesc();

        ParameterDesc param = new ParameterDesc();
        byte mode = ParameterDesc.IN;
        if (parameterMode == ParameterMode.INOUT) {
            mode = ParameterDesc.INOUT;
            param.setIsReturn(true);
        } else if (parameterMode == ParameterMode.OUT) {
            mode = ParameterDesc.OUT;
            param.setIsReturn(true);
        }
        param.setMode(mode);
        param.setQName(new QName(paramName.getNamespaceURI(),Utils.getLastLocalPart(paramName.getLocalPart())));
        param.setTypeQName( xmlType );
        param.setJavaType( javaType );

        operation.addParameter(param);
        parmAndRetReq = true;
    }

    /**
     * Adds the specified parameter to the list of parameters for the
     * operation associated with this Call object.
     *
     * @param paramName      Name that will be used for the parameter in the XML
     * @param xmlType      XMLType of the parameter
     * @param parameterMode  one of IN, OUT or INOUT
     */
    public void addParameter(String paramName, QName xmlType,
            ParameterMode parameterMode) {
        Class javaType = null;
        TypeMapping tm = getTypeMapping();
        if (tm != null) {
            javaType = tm.getClassForQName(xmlType);
        }
        addParameter(new QName("", paramName), xmlType,
                     javaType, parameterMode);
    }

    /**
     * Adds a parameter type and mode for a specific operation. Note that the
     * client code is not required to call any addParameter and setReturnType
     * methods before calling the invoke method. A Call implementation class
     * can determine the parameter types by using the Java reflection and
     * configured type mapping registry.
     *
     * @param paramName - Name of the parameter
     * @param xmlType - XML datatype of the parameter
     * @param javaType - The Java class of the parameter
     * @param parameterMode - Mode of the parameter-whether IN, OUT or INOUT
     * @exception JAXRPCException - if isParameterAndReturnSpecRequired returns
     *                              false, then addParameter MAY throw
     *                              JAXRPCException....actually Axis allows
     *                              modification in such cases
     */
    public void addParameter(String paramName, QName xmlType,
                             Class javaType, ParameterMode parameterMode) {
        addParameter(new QName("", paramName), xmlType,
                     javaType, parameterMode);
    }

    /**
     * Adds a parameter type as a soap:header.
     *
     * @param paramName     - Name of the parameter
     * @param xmlType       - XML datatype of the parameter
     * @param parameterMode - Mode of the parameter-whether IN, OUT or INOUT
     * @param headerMode    - Mode of the header. Even if this is an INOUT
     *                      parameter, it need not be in the header in both
     *                      directions.
     * @throws JAXRPCException - if isParameterAndReturnSpecRequired returns
     *                         false, then addParameter MAY throw
     *                         JAXRPCException....actually Axis allows
     *                         modification in such cases
     */
    public void addParameterAsHeader(QName paramName, QName xmlType,
                                     ParameterMode parameterMode,
                                     ParameterMode headerMode) {
        Class javaType = null;
        TypeMapping tm = getTypeMapping();
        if (tm != null) {
            javaType = tm.getClassForQName(xmlType);
        }
        addParameterAsHeader(paramName, xmlType, javaType,
                parameterMode, headerMode);
    }
     
    /**

     * Adds a parameter type as a soap:header.
     * @param paramName - Name of the parameter
     * @param xmlType - XML datatype of the parameter
     * @param javaType - The Java class of the parameter
     * @param parameterMode - Mode of the parameter-whether IN, OUT or INOUT
     * @param headerMode - Mode of the header.  Even if this is an INOUT
     *                     parameter, it need not be in the header in both
     *                     directions.
     * @exception JAXRPCException - if isParameterAndReturnSpecRequired returns
     *                              false, then addParameter MAY throw
     *                              JAXRPCException....actually Axis allows
     *                              modification in such cases
     */
    public void addParameterAsHeader(QName paramName, QName xmlType,
            Class javaType, ParameterMode parameterMode,
            ParameterMode headerMode) {
        if (operationSetManually) {
            throw new RuntimeException(
                    Messages.getMessage("operationAlreadySet"));
        }

        if (operation == null)
            operation = new OperationDesc();

        ParameterDesc param = new ParameterDesc();
        param.setQName(new QName(paramName.getNamespaceURI(),Utils.getLastLocalPart(paramName.getLocalPart())));
        param.setTypeQName(xmlType);
        param.setJavaType(javaType);
        if (parameterMode == ParameterMode.IN) {
            param.setMode(ParameterDesc.IN);
        }
        else if (parameterMode == ParameterMode.INOUT) {
            param.setMode(ParameterDesc.INOUT);
        }
        else if (parameterMode == ParameterMode.OUT) {
            param.setMode(ParameterDesc.OUT);
        }
        if (headerMode == ParameterMode.IN) {
            param.setInHeader(true);
        }
        else if (headerMode == ParameterMode.INOUT) {
            param.setInHeader(true);
            param.setOutHeader(true);
        }
        else if (headerMode == ParameterMode.OUT) {
            param.setOutHeader(true);
        }
        operation.addParameter(param);
        parmAndRetReq = true;
    } // addParameterAsHeader

    /**
     * Return the QName of the type of the parameters with the given name.
     *
     * @param  paramName  name of the parameter to return
     * @return XMLType    XMLType of paramName, or null if not found.
     */
    public QName getParameterTypeByName(String paramName) {
        QName paramQName = new QName("", paramName);

        return getParameterTypeByQName(paramQName);
    }

    /**
     * Return the QName of the type of the parameters with the given name.
     *
     * Note: Not part of JAX-RPC specification.
     *
     * @param  paramQName  QName of the parameter to return
     * @return XMLType    XMLType of paramQName, or null if not found.
     */
    public QName getParameterTypeByQName(QName paramQName) {
        ParameterDesc param = operation.getParamByQName(paramQName);
        if (param != null) {
            return param.getTypeQName();
        }
        return( null );
    }

    /**
     * Sets the return type of the operation associated with this Call object.
     *
     * @param type QName of the return value type.
     */
    public void setReturnType(QName type) {
        if (operationSetManually) {
            throw new RuntimeException(
                    Messages.getMessage("operationAlreadySet"));
        }

        if (operation == null)
            operation = new OperationDesc();

        // In order to allow any Call to be re-used, Axis
        // chooses to allow setReturnType to be changed when
        // parmAndRetReq==false.  This does not conflict with
        // JSR 101 which indicates an exception MAY be thrown.

        //if (parmAndRetReq) {
        operation.setReturnType(type);
        TypeMapping tm = getTypeMapping();
        operation.setReturnClass(tm.getClassForQName(type));
        parmAndRetReq = true;
        //}
        //else {
        //throw new JAXRPCException(Messages.getMessage("noParmAndRetReq"));
        //}
    }

    /**
     * Sets the return type for a specific operation.
     *
     * @param xmlType - QName of the data type of the return value
     * @param javaType - Java class of the return value
     * @exception JAXRPCException - if isParameterAndReturnSpecRequired returns
     * false, then setReturnType MAY throw JAXRPCException...Axis allows
     * modification without throwing the exception.
     */
    public void setReturnType(QName xmlType, Class javaType) {
        setReturnType(xmlType);
        // Use specified type as the operation return
        operation.setReturnClass(javaType);
    }

    /**
     * Set the return type as a header
     */
    public void setReturnTypeAsHeader(QName xmlType) {
        setReturnType(xmlType);
        operation.setReturnHeader(true);
    } // setReturnTypeAsHeader

    /**
     * Set the return type as a header
     */
    public void setReturnTypeAsHeader(QName xmlType, Class javaType) {
        setReturnType(xmlType, javaType);
        operation.setReturnHeader(true);
    } // setReturnTypeAsHeader

    /**
     * Returns the QName of the type of the return value of this Call - or null
     * if not set.
     *
     * Note: Not part of JAX-RPC specification.
     *
     * @return the XMLType specified for this Call (or null).
     */
    public QName getReturnType() {
        if (operation != null)
            return operation.getReturnType();

        return null;
    }

    /**
     * Set the QName of the return element
     *
     * NOT part of JAX-RPC
     */
    public void setReturnQName(QName qname) {
        if (operationSetManually) {
            throw new RuntimeException(
                    Messages.getMessage("operationAlreadySet"));
        }

        if (operation == null)
            operation = new OperationDesc();

        operation.setReturnQName(qname);
    }
    /**
     * Sets the desired return Java Class.  This is a convenience method
     * which will cause the Call to automatically convert return values
     * into a desired class if possible.  For instance, we return object
     * arrays by default now for SOAP arrays - you could specify:
     *
     * setReturnClass(Vector.class)
     *
     * and you'd get a Vector back from invoke() instead of having to do
     * the conversion yourself.
     *
     * Note: Not part of JAX-RPC specification.  To be JAX-RPC compliant,
     *       use setReturnType(QName, Class).
     *
     * @param cls the desired return class.
     */
    public void setReturnClass(Class cls) {
        if (operationSetManually) {
            throw new RuntimeException(
                    Messages.getMessage("operationAlreadySet"));
        }

        if (operation == null)
            operation = new OperationDesc();

        operation.setReturnClass(cls);
        TypeMapping tm = getTypeMapping();
        operation.setReturnType(tm.getTypeQName(cls));
        parmAndRetReq = true;
    }

    /**
     * Clears the list of parameters.
     * @exception JAXRPCException - if isParameterAndReturnSpecRequired returns
     *  false, then removeAllParameters MAY throw JAXRPCException...Axis allows
     *  modification to the Call object without throwing an exception.
     */
    public void removeAllParameters() {
        //if (parmAndRetReq) {
        operation = new OperationDesc();
        operationSetManually = false;
        parmAndRetReq = true;
        //}
        //else {
        //throw new JAXRPCException(Messages.getMessage("noParmAndRetReq"));
        //}
    }

    /**
     * Returns the operation name associated with this Call object.
     *
     * @return String Name of the operation or null if not set.
     */
    public QName getOperationName() {
        return( operationName );
    }

    /**
     * Sets the operation name associated with this Call object.  This will
     * not check the WSDL (if there is WSDL) to make sure that it's a valid
     * operation name.
     *
     * @param opName Name of the operation.
     */
    public void setOperationName(QName opName) {
        operationName = opName ;
    }

    /**
     * This is a convenience method.  If the user doesn't care about the QName
     * of the operation, the user can call this method, which converts a String
     * operation name to a QName.
     */
    public void setOperationName(String opName) {
        operationName = new QName(opName);
    }

    /**
     * Prefill as much info from the WSDL as it can.
     * Right now it's SOAPAction, operation qname, parameter types
     * and return type of the Web Service.
     *
     * This methods considers that port name and target endpoint address have
     * already been set. This is useful when you want to use the same Call
     * instance for several calls on the same Port
     *
     * Note: Not part of JAX-RPC specification.
     *
     * @param  opName          Operation(method) that's going to be invoked
     * @throws JAXRPCException
     */
    public void setOperation(String opName) {
        if ( service == null ) {
            throw new JAXRPCException( Messages.getMessage("noService04") );
        }

        // remove all settings concerning an operation
        // leave portName and targetEndPoint as they are
        this.setOperationName( opName );
        this.setEncodingStyle( null );
        this.setReturnType( null );
        this.removeAllParameters();

        javax.wsdl.Service wsdlService = service.getWSDLService();
        // Nothing to do is the WSDL is not already set.
        if(wsdlService == null) {
            return;
        }

        Port port = wsdlService.getPort( portName.getLocalPart() );
        if ( port == null ) {
            throw new JAXRPCException( Messages.getMessage("noPort00", "" +
                                                           portName) );
        }

        Binding   binding  = port.getBinding();
        PortType  portType = binding.getPortType();
        if ( portType == null ) {
            throw new JAXRPCException( Messages.getMessage("noPortType00", "" +
                                                           portName) );
        }
        this.setPortTypeName(portType.getQName());
        
        List operations = portType.getOperations();
        if ( operations == null ) {
            throw new JAXRPCException( Messages.getMessage("noOperation01",
                                                           opName) );
        }

        Operation op = null ;
        for ( int i = 0 ; i < operations.size() ; i++, op=null ) {
            op = (Operation) operations.get( i );
            if ( opName.equals( op.getName() ) ) {
                break ;
            }
        }
        if ( op == null ) {
            throw new JAXRPCException( Messages.getMessage("noOperation01",
                                                           opName) );
        }

        // Get the SOAPAction
        ////////////////////////////////////////////////////////////////////
        List list = port.getExtensibilityElements();
        String opStyle = null;
        BindingOperation bop = binding.getBindingOperation(opName,
                                                           null, null);
        if ( bop == null ) {
            throw new JAXRPCException( Messages.getMessage("noOperation02",
                                                            opName ));
        }
        list = bop.getExtensibilityElements();
        for ( int i = 0 ; list != null && i < list.size() ; i++ ) {
            Object obj = list.get(i);
            if ( obj instanceof SOAPOperation ) {
                SOAPOperation sop    = (SOAPOperation) obj ;
                opStyle = ((SOAPOperation) obj).getStyle();
                String action = sop.getSoapActionURI();
                if ( action != null ) {
                    setUseSOAPAction(true);
                    setSOAPActionURI(action);
                }
                else {
                    setUseSOAPAction(false);
                    setSOAPActionURI(null);
                }
                break ;
            }
        }

        // Get the body's namespace URI and encoding style
        ////////////////////////////////////////////////////////////////////
        BindingInput bIn = bop.getBindingInput();
        if ( bIn != null ) {
            list = bIn.getExtensibilityElements();
            for ( int i = 0 ; list != null && i < list.size() ; i++ ) {
                Object obj = list.get(i);
                if( obj instanceof MIMEMultipartRelated){
                  MIMEMultipartRelated mpr=(MIMEMultipartRelated) obj;
                  Object part= null;
                  List l=  mpr.getMIMEParts();
                  for(int j=0; l!= null && j< l.size() && part== null; j++){
                     MIMEPart mp = (MIMEPart)l.get(j);
                     List ll= mp.getExtensibilityElements();
                     for(int k=0; ll != null && k < ll.size() && part == null;
                           k++){
                       part= ll.get(k);
                       if ( !(part instanceof SOAPBody)) {
                           part = null;
                       }
                     }
                  }
                  if(null != part) {
                      obj= part;
                  }
                }

                if ( obj instanceof SOAPBody ) {
                    SOAPBody sBody  = (SOAPBody) obj ;
                    list = sBody.getEncodingStyles();
                    if ( list != null && list.size() > 0 ) {
                        this.setEncodingStyle( (String) list.get(0) );
                    }
                    String ns = sBody.getNamespaceURI();
                    if (ns != null && !ns.equals("")) {
                      setOperationName( new QName( ns, opName ) );
                    }
                    break ;
                }
            }
        }

        Service service = this.getService();
        SymbolTable symbolTable = service.getWSDLParser().getSymbolTable();
        BindingEntry bEntry = symbolTable.getBindingEntry(binding.getQName());
        Parameters parameters = bEntry.getParameters(bop.getOperation());

        // loop over paramters and set up in/out params
        for (int j = 0; j < parameters.list.size(); ++j) {
            Parameter p = (Parameter) parameters.list.get(j);
            // Get the QName representing the parameter type
            QName paramType = Utils.getXSIType(p);

            // checks whether p is an IN or OUT header 
            // and adds it as a header parameter else
            // add it to the body
            ParameterMode mode = modes[p.getMode()];
            if (p.isInHeader() || p.isOutHeader()) {
                this.addParameterAsHeader(p.getQName(), paramType,
                        mode, mode);
            } else {
                this.addParameter(p.getQName(), paramType, mode);
            } 
        }

        Map faultMap = bEntry.getFaults();
        // Get the list of faults for this operation
        ArrayList faults = (ArrayList) faultMap.get(bop);

        // check for no faults
        if (faults == null) {
            return;
        }
        // For each fault, register its information
        for (Iterator faultIt = faults.iterator(); faultIt.hasNext();) {
            FaultInfo info = (FaultInfo) faultIt.next();
            QName qname = info.getQName();
            info.getMessage();

            // if no parts in fault, skip it!
            if (qname == null) {
                continue;
            }

            QName xmlType = info.getXMLType();
            Class clazz = getTypeMapping().getClassForQName(xmlType);
            if (clazz != null) {
                addFault(qname, clazz, xmlType, true);
            } else {
                //we cannot map from the info to a java class
                //In Axis1.1 and before this was silently swallowed. Now we log it

                log.debug(Messages.getMessage("clientNoTypemapping", xmlType.toString()));
            }
        }

        // set output type
        if (parameters.returnParam != null) {
            // Get the QName for the return Type
            QName returnType = Utils.getXSIType(parameters.returnParam);
            QName returnQName = parameters.returnParam.getQName();

            // Get the javaType
            String javaType = null;
            if (parameters.returnParam.getMIMEInfo() != null) {
                javaType = "javax.activation.DataHandler";
            }
            else {
                javaType = parameters.returnParam.getType().getName();
            }
            if (javaType == null) {
                javaType = "";
            }
            else {
                javaType = javaType + ".class";
            }
            this.setReturnType(returnType);
            try {
                Class clazz = ClassUtils.forName(javaType);
                this.setReturnClass(clazz);
            } catch (ClassNotFoundException swallowedException) {
                //log that this lookup failed,
                log.debug(Messages.getMessage("clientNoReturnClass",
                        javaType));
            }
            this.setReturnQName(returnQName);
        }
        else {
            this.setReturnType(org.apache.axis.encoding.XMLType.AXIS_VOID);
        }

        boolean hasMIME = Utils.hasMIME(bEntry, bop);
        Use use = bEntry.getInputBodyType(bop.getOperation());
        setOperationUse(use);
        if (use == Use.LITERAL) {
            // Turn off encoding
            setEncodingStyle(null);
            // turn off XSI types
            setProperty(org.apache.axis.client.Call.SEND_TYPE_ATTR, Boolean.FALSE);
        }
        if (hasMIME || use == Use.LITERAL) {
            // If it is literal, turn off multirefs.
            //
            // If there are any MIME types, turn off multirefs.
            // I don't know enough about the guts to know why
            // attachments don't work with multirefs, but they don't.
            setProperty(org.apache.axis.AxisEngine.PROP_DOMULTIREFS, Boolean.FALSE);
        }

        Style style = Style.getStyle(opStyle, bEntry.getBindingStyle());
        if (style == Style.DOCUMENT && symbolTable.isWrapped()) {
            style = Style.WRAPPED;
        }
        setOperationStyle(style);

        // Operation name
        if (style == Style.WRAPPED) {
            // We need to make sure the operation name, which is what we
            // wrap the elements in, matches the Qname of the parameter
            // element.
            Map partsMap = bop.getOperation().getInput().getMessage().getParts();
            Part p = (Part)partsMap.values().iterator().next();
            QName q = p.getElementName();
            setOperationName(q);
        } else {
            QName elementQName =
                Utils.getOperationQName(bop, bEntry, symbolTable);
            if (elementQName != null) {
                setOperationName(elementQName);
            }
        }

        // Indicate that the parameters and return no longer
        // need to be specified with addParameter calls.
        parmAndRetReq = false;
        return;

    }


    /**
     * prefill as much info from the WSDL as it can.
     * Right now it's target URL, SOAPAction, Parameter types,
     * and return type of the Web Service.
     *
     * If wsdl is not present, this function set port name and operation name
     * and does not modify target endpoint address.
     *
     * Note: Not part of JAX-RPC specification.
     *
     * @param  portName        PortName in the WSDL doc to search for
     * @param  opName          Operation(method) that's going to be invoked
     */
    public void setOperation(QName portName, String opName) {
    	setOperation(portName, new QName(opName));
    }

    
    /**
     * prefill as much info from the WSDL as it can.
     * Right now it's target URL, SOAPAction, Parameter types,
     * and return type of the Web Service.
     *
     * If wsdl is not present, this function set port name and operation name
     * and does not modify target endpoint address.
     *
     * Note: Not part of JAX-RPC specification.
     *
     * @param  portName        PortName in the WSDL doc to search for
     * @param  opName          Operation(method) that's going to be invoked
     */
    public void setOperation(QName portName, QName opName) {
        if ( service == null )
            throw new JAXRPCException( Messages.getMessage("noService04") );

        // Make sure we're making a fresh start.
        this.setPortName( portName );
        this.setOperationName( opName );
        this.setReturnType( null );
        this.removeAllParameters();

        javax.wsdl.Service wsdlService = service.getWSDLService();
        // Nothing to do is the WSDL is not already set.
        if(wsdlService == null) {
            return;
        }

        // we reinitialize target endpoint only if we have wsdl
        this.setTargetEndpointAddress( (URL) null );

        Port port = wsdlService.getPort( portName.getLocalPart() );
        if ( port == null ) {
            throw new JAXRPCException( Messages.getMessage("noPort00", "" +
                                                           portName) );
        }

        // Get the URL
        ////////////////////////////////////////////////////////////////////
        List list = port.getExtensibilityElements();
        for ( int i = 0 ; list != null && i < list.size() ; i++ ) {
            Object obj = list.get(i);
            if ( obj instanceof SOAPAddress ) {
                try {
                    SOAPAddress addr = (SOAPAddress) obj ;
                    URL         url  = new URL(addr.getLocationURI());
                    this.setTargetEndpointAddress(url);
                }
                catch(Exception exp) {
                    throw new JAXRPCException(
                            Messages.getMessage("cantSetURI00", "" + exp) );
                }
            }
        }

        setOperation(opName.getLocalPart());
    }

    /**
     * Returns the fully qualified name of the port for this Call object
     * (if there is one).
     *
     * @return QName Fully qualified name of the port (or null if not set)
     */
    public QName getPortName() {
        return( portName );
    } // getPortName

    /**
     * Sets the port name of this Call object.  This call will not set
     * any additional fields, nor will it do any checking to verify that
     * this port name is actually defined in the WSDL - for now anyway.
     *
     * @param portName Fully qualified name of the port
     */
    public void setPortName(QName portName) {
        this.portName = portName;
    } // setPortName

    /**
     * Returns the fully qualified name of the port type for this Call object
     * (if there is one).
     *
     * @return QName Fully qualified name of the port type
     */
    public QName getPortTypeName() {
        return portTypeName == null ? new QName("") : portTypeName;
    }

    /**
     * Sets the port type name of this Call object.  This call will not set
     * any additional fields, nor will it do any checking to verify that
     * this port type is actually defined in the WSDL - for now anyway.
     *
     * @param portType Fully qualified name of the portType
     */
    public void setPortTypeName(QName portType) {
        this.portTypeName = portType;
    }

    /**
     * Allow the user to set the default SOAP version.  For SOAP 1.2, pass
     * SOAPConstants.SOAP12_CONSTANTS.
     *
     * @param soapConstants the SOAPConstants object representing the correct
     *                      version
     */
    public void setSOAPVersion(SOAPConstants soapConstants) {
        msgContext.setSOAPConstants(soapConstants);
    }

    /**
     * Invokes a specific operation using a synchronous request-response interaction mode. The invoke method takes
     * as parameters the object values corresponding to these defined parameter types. Implementation of the invoke
     * method must check whether the passed parameter values correspond to the number, order and types of parameters
     * specified in the corresponding operation specification.
     *
     * @param operationName - Name of the operation to invoke
     * @param params  - Parameters for this invocation
     *
     * @return the value returned from the other end.
     *
     * @throws java.rmi.RemoteException - if there is any error in the remote method invocation or if the Call
     * object is not configured properly.
     */
    public Object invoke(QName operationName, Object[] params)
      throws java.rmi.RemoteException {
        QName origOpName = this.operationName;
        this.operationName = operationName;
        try {
            return this.invoke(params);
        }
        catch (AxisFault af) {
            this.operationName = origOpName;
            if(af.detail != null && af.detail instanceof RemoteException) {
                throw ((RemoteException)af.detail);
            }
            throw af;
        }
        catch (java.rmi.RemoteException re) {
            this.operationName = origOpName;
            throw re;
        }
        catch (RuntimeException re) {
            this.operationName = origOpName;
            throw re;
        }
        catch (Error e) {
            this.operationName = origOpName;
            throw e;
        }
    } // invoke

    /**
     * Invokes the operation associated with this Call object using the
     * passed in parameters as the arguments to the method.
     *
     * For Messaging (ie. non-RPC) the params argument should be an array
     * of SOAPBodyElements.  <b>All</b> of them need to be SOAPBodyElements,
     * if any of them are not this method will default back to RPC.  In the
     * Messaging case the return value will be a vector of SOAPBodyElements.
     *
     * @param  params Array of parameters to invoke the Web Service with
     * @return Object Return value of the operation/method - or null
     * @throws java.rmi.RemoteException if there's an error
     */
    public Object invoke(Object[] params) throws java.rmi.RemoteException {
        long t0=0, t1=0;
        if( tlog.isDebugEnabled() ) {
            t0=System.currentTimeMillis();
        }
        /* First see if we're dealing with Messaging instead of RPC.        */
        /* If ALL of the params are SOAPBodyElements then we're doing       */
        /* Messaging, otherwise just fall through to normal RPC processing. */
        /********************************************************************/
        SOAPEnvelope  env = null ;
        int i ;

        for ( i = 0 ; params != null && i < params.length ; i++ )
            if ( !(params[i] instanceof SOAPBodyElement) ) break ;

        if ( params != null && params.length > 0 && i == params.length ) {
            /* ok, we're doing Messaging, so build up the message */
            /******************************************************/
            isMsg = true ;
            env = new SOAPEnvelope(msgContext.getSOAPConstants(),
                                   msgContext.getSchemaVersion());

            for (i = 0; i < params.length; i++) {
                env.addBodyElement((SOAPBodyElement) params[i]);
            }

            Message msg = new Message( env );
            setRequestMessage(msg);

            invoke();

            msg = msgContext.getResponseMessage();
            if (msg == null) {
              if (msgContext.isPropertyTrue(FAULT_ON_NO_RESPONSE, false)) {
                throw new AxisFault(Messages.getMessage("nullResponse00"));
              } else {
                return null;
              }
            }

            env = msg.getSOAPEnvelope();
            return( env.getBodyElements() );
        }


        if ( operationName == null ) {
            throw new AxisFault( Messages.getMessage("noOperation00") );
        }
        try {
            Object res=this.invoke(operationName.getNamespaceURI(),
                    operationName.getLocalPart(), params);
            if( tlog.isDebugEnabled() ) {
                t1=System.currentTimeMillis();
                tlog.debug("axis.Call.invoke: " + (t1-t0)  + " " + operationName);
            }
            return res;
        }
        catch( AxisFault af) {
            if(af.detail != null && af.detail instanceof RemoteException) {
                throw ((RemoteException)af.detail);
            }
            throw af;
        }
        catch( Exception exp ) {
            entLog.debug(Messages.getMessage("toAxisFault00"), exp);
            throw AxisFault.makeFault(exp);
        }
    }

    /**
     * Invokes the operation associated with this Call object using the passed
     * in parameters as the arguments to the method.  This will return
     * immediately rather than waiting for the server to complete its
     * processing.
     *
     * NOTE: the return immediately part isn't implemented yet
     *
     * @param  params Array of parameters to invoke the Web Service with
     * @throws JAXRPCException is there's an error
     */
    public void invokeOneWay(Object[] params) {
        try {
            invokeOneWay = true;
            invoke( params );
        } catch( Exception exp ) {
            throw new JAXRPCException( exp.toString() );
        } finally {
            invokeOneWay = false;
        }
    }

    /************************************************************************/
    /* End of core JAX-RPC stuff                                            */
    /************************************************************************/

    /**
     * Invoke the service with a custom Message.
     * This method simplifies invoke(SOAPEnvelope).
     * <p>
     * Note: Not part of JAX-RPC specification.
     *
     * @param msg a Message to send
     * @throws AxisFault if there is any failure
     */
    public SOAPEnvelope invoke(Message msg) throws AxisFault {
        try {
            setRequestMessage( msg );
            invoke();
            msg = msgContext.getResponseMessage();
            if (msg == null) {
                if (msgContext.isPropertyTrue(FAULT_ON_NO_RESPONSE, false)) {
                    throw new AxisFault(Messages.getMessage("nullResponse00"));
                } else {
                    return null;
                }
            }
            SOAPEnvelope res = null;
            res = msg.getSOAPEnvelope();
            return res;
        }
        catch (Exception exp) {
            if (exp instanceof AxisFault) {
                throw (AxisFault) exp ;
            }
            entLog.debug(Messages.getMessage("toAxisFault00"), exp);
            throw new AxisFault(
                    Messages.getMessage("errorInvoking00", "\n" + exp));
        }
    }

    /**
     * Invoke the service with a custom SOAPEnvelope.
     * <p>
     * Note: Not part of JAX-RPC specification.
     *
     * @param env a SOAPEnvelope to send
     * @throws AxisFault if there is any failure
     */
    public SOAPEnvelope invoke(SOAPEnvelope env) throws AxisFault {
        try {
            Message msg = new Message( env );
            if (getProperty(CHARACTER_SET_ENCODING) != null) {
                msg.setProperty(SOAPMessage.CHARACTER_SET_ENCODING, getProperty(CHARACTER_SET_ENCODING));
            } else if (msgContext.getProperty(CHARACTER_SET_ENCODING) != null) {
                msg.setProperty(CHARACTER_SET_ENCODING, msgContext.getProperty(CHARACTER_SET_ENCODING));
            }
            setRequestMessage( msg );
            invoke();
            msg = msgContext.getResponseMessage();
            if (msg == null) {
              if (msgContext.isPropertyTrue(FAULT_ON_NO_RESPONSE, false)) {
                throw new AxisFault(Messages.getMessage("nullResponse00"));
              } else {
                return null;
              }
            }
            return( msg.getSOAPEnvelope() );
        }
        catch( Exception exp ) {
            if ( exp instanceof AxisFault ) {
                throw (AxisFault) exp ;
            }

            entLog.debug(Messages.getMessage("toAxisFault00"), exp);
            throw AxisFault.makeFault(exp);
        }
    }


    /** Register a Transport that should be used for URLs of the specified
     * protocol.
     *
     * Note: Not part of JAX-RPC specification.
     *
     * @param protocol the URL protocol (i.e. "tcp" for "tcp://" urls)
     * @param transportClass the class of a Transport type which will be used
     *                       for matching URLs.
     */
    public static void setTransportForProtocol(String protocol,
                                               Class transportClass) {
        if (Transport.class.isAssignableFrom(transportClass)) {
            transports.put(protocol, transportClass);
        }
        else {
            throw new InternalException(transportClass.toString());
        }
    }

    /**
     * Set up the default transport URL mappings.
     *
     * This must be called BEFORE doing non-standard URL parsing (i.e. if you
     * want the system to accept a "local:" URL).  This is why the Options class
     * calls it before parsing the command-line URL argument.
     *
     * Note: Not part of JAX-RPC specification.
     */
    public static synchronized void initialize() {
        addTransportPackage("org.apache.axis.transport");

        setTransportForProtocol("java",
                org.apache.axis.transport.java.JavaTransport.class);
        setTransportForProtocol("local",
                org.apache.axis.transport.local.LocalTransport.class);
        setTransportForProtocol("http", HTTPTransport.class);
        setTransportForProtocol("https", HTTPTransport.class);
    }

    /**
     * Cache of transport packages we've already added to the system
     * property.
     */
    private static ArrayList transportPackages = null;

    /** Add a package to the system protocol handler search path.  This
     * enables users to create their own URLStreamHandler classes, and thus
     * allow custom protocols to be used in Axis (typically on the client
     * command line).
     *
     * For instance, if you add "samples.transport" to the packages property,
     * and have a class samples.transport.tcp.Handler, the system will be able
     * to parse URLs of the form "tcp://host:port..."
     *
     * Note: Not part of JAX-RPC specification.
     *
     * @param packageName the package in which to search for protocol names.
     */
    public static synchronized void addTransportPackage(String packageName) {
        if (transportPackages == null) {
            transportPackages = new ArrayList();
            String currentPackages =
                    AxisProperties.getProperty(TRANSPORT_PROPERTY);
            if (currentPackages != null) {
                StringTokenizer tok = new StringTokenizer(currentPackages,
                                                          "|");
                while (tok.hasMoreTokens()) {
                    transportPackages.add(tok.nextToken());
                }
            }
        }

        if (transportPackages.contains(packageName)) {
            return;
        }

        transportPackages.add(packageName);

        StringBuffer currentPackages = new StringBuffer();
        for (Iterator i = transportPackages.iterator(); i.hasNext();) {
            String thisPackage = (String) i.next();
            currentPackages.append(thisPackage);
            currentPackages.append('|');
        }

        final String transportProperty = currentPackages.toString();
        java.security.AccessController.doPrivileged(new java.security.PrivilegedAction() {
            public Object run() {
                try {
                    System.setProperty(TRANSPORT_PROPERTY, transportProperty);
                } catch (SecurityException se){
                }
                return null;
            }
        });
    }

    /**
     * Convert the list of objects into RPCParam's based on the paramNames,
     * paramXMLTypes and paramModes variables.  If those aren't set then just
     * return what was passed in.
     *
     * @param  params   Array of parameters to pass into the operation/method
     * @return Object[] Array of parameters to pass to invoke()
     */
    private Object[] getParamList(Object[] params) {
        int  numParams = 0 ;

        // If we never set-up any names... then just return what was passed in
        //////////////////////////////////////////////////////////////////////
        if (log.isDebugEnabled()) {
            log.debug( "operation=" + operation);
            if (operation != null) {
                log.debug("operation.getNumParams()=" +
                          operation.getNumParams());
            }
        }
        if ( operation == null || operation.getNumParams() == 0 ) {
            return( params );
        }

        // Count the number of IN and INOUT params, this needs to match the
        // number of params passed in - if not throw an error
        /////////////////////////////////////////////////////////////////////
        numParams = operation.getNumInParams();

        if ( params == null || numParams != params.length ) {
            throw new JAXRPCException(
                    Messages.getMessage(
                              "parmMismatch00",
                              (params == null) ? "no params" : "" + params.length, 
                              "" + numParams
                   ) 
           );
        }

        log.debug( "getParamList number of params: " + params.length);

        // All ok - so now produce an array of RPCParams
        //////////////////////////////////////////////////
        Vector result = new Vector();
        int    j = 0 ;
        ArrayList parameters = operation.getParameters();

        for (int i = 0; i < parameters.size(); i++) {
            ParameterDesc param = (ParameterDesc)parameters.get(i);
            if (param.getMode() != ParameterDesc.OUT) {
                QName paramQName = param.getQName();

                // Create an RPCParam if param isn't already an RPCParam.
                RPCParam rpcParam = null;
                Object p = params[j++];
                if(p instanceof RPCParam) {
                    rpcParam = (RPCParam)p;
                } else {
                    rpcParam = new RPCParam(paramQName.getNamespaceURI(),
                                            paramQName.getLocalPart(),
                                            p);
                }
                // Attach the ParameterDescription to the RPCParam
                // so that the serializer can use the (javaType, xmlType)
                // information.
                rpcParam.setParamDesc(param);

                // Add the param to the header or vector depending
                // on whether it belongs in the header or body.
                if (param.isInHeader()) {
                    addHeader(new RPCHeaderParam(rpcParam));
                } else {
                    result.add(rpcParam);
                }
            }
        }
        return( result.toArray() );
    }

    /**
     * Set the Transport
     *
     * Note: Not part of JAX-RPC specification.
     *
     * @param trans the Transport object we'll use to set up
     *                  MessageContext properties.
     */
    public void setTransport(Transport trans) {
        transport = trans;
        if (log.isDebugEnabled())
            log.debug(Messages.getMessage("transport00", "" + transport));
    }

    /** Get the Transport registered for the given protocol.
     *
     * Note: Not part of JAX-RPC specification.
     *
     * @param protocol a protocol such as "http" or "local" which may
     *                 have a Transport object associated with it.
     * @return the Transport registered for this protocol, or null if none.
     */
    public Transport getTransportForProtocol(String protocol)
    {
        Class transportClass = (Class)transports.get(protocol);
        Transport ret = null;
        if (transportClass != null) {
            try {
                ret = (Transport)transportClass.newInstance();
            } catch (InstantiationException e) {
            } catch (IllegalAccessException e) {
            }
        }
        return ret;
    }

    /**
     * Directly set the request message in our MessageContext.
     *
     * This allows custom message creation.
     *
     * Note: Not part of JAX-RPC specification.
     *
     * @param msg the new request message.
     * @throws RuntimeException containing the text of an AxisFault, if any
     * AxisFault was thrown
     */
    public void setRequestMessage(Message msg) {
         String attachformat= (String)getProperty(
           ATTACHMENT_ENCAPSULATION_FORMAT);

         if(null != attachformat) {
              Attachments attachments=msg.getAttachmentsImpl();
              if(null != attachments) {
                  if( ATTACHMENT_ENCAPSULATION_FORMAT_MIME.equals(attachformat)) {
                    attachments.setSendType(Attachments.SEND_TYPE_MIME);
                  } else if ( ATTACHMENT_ENCAPSULATION_FORMAT_MTOM.equals(attachformat)) {
                    attachments.setSendType(Attachments.SEND_TYPE_MTOM);
                  } else if ( ATTACHMENT_ENCAPSULATION_FORMAT_DIME.equals(attachformat)) {
                    attachments.setSendType(Attachments.SEND_TYPE_DIME);
                  }
              }
         }

        if(null != attachmentParts && !attachmentParts.isEmpty()){
            try{
                Attachments attachments= msg.getAttachmentsImpl();
                if(null == attachments) {
                  throw new RuntimeException(
                          Messages.getMessage("noAttachments"));
                }

                attachments.setAttachmentParts(attachmentParts);
            }catch(AxisFault ex){
              log.info(Messages.getMessage("axisFault00"), ex);
              throw new RuntimeException(ex.getMessage());
            }
        }

        msgContext.setRequestMessage(msg);
        attachmentParts.clear();
    }

    /**
     * Directly get the response message in our MessageContext.
     *
     * Shortcut for having to go thru the msgContext
     *
     * Note: Not part of JAX-RPC specification.
     *
     * @return the response Message object in the msgContext
     */
    public Message getResponseMessage() {
        return msgContext.getResponseMessage();
    }

    /**
     * Obtain a reference to our MessageContext.
     *
     * Note: Not part of JAX-RPC specification.
     *
     * @return the MessageContext.
     */
    public MessageContext getMessageContext () {
        return msgContext;
    }

    /**
     * Add a header which should be inserted into each outgoing message
     * we generate.
     *
     * Note: Not part of JAX-RPC specification.
     *
     * @param header a SOAPHeaderElement to be inserted into messages
     */
    public void addHeader(SOAPHeaderElement header)
    {
        if (myHeaders == null) {
            myHeaders = new Vector();
        }
        myHeaders.add(header);
    }

    /**
     * Clear the list of headers which we insert into each message
     *
     * Note: Not part of JAX-RPC specification.
     */
    public void clearHeaders()
    {
        myHeaders = null;
    }

    public TypeMapping getTypeMapping()
    {
        // Get the TypeMappingRegistry
        TypeMappingRegistry tmr = msgContext.getTypeMappingRegistry();

        // If a TypeMapping is not available, add one.
        return tmr.getOrMakeTypeMapping(getEncodingStyle());
    }

    /**
     * Register type mapping information for serialization/deserialization
     *
     * Note: Not part of JAX-RPC specification.
     *
     * @param javaType is  the Java class of the data type.
     * @param xmlType the xsi:type QName of the associated XML type.
     * @param sf/df are the factories (or the Class objects of the factory).
     */
    public void registerTypeMapping(Class javaType, QName xmlType,
                                    SerializerFactory sf,
                                    DeserializerFactory df) {
        registerTypeMapping(javaType, xmlType, sf, df, true);
    }

    /**
     * Register type mapping information for serialization/deserialization
     *
     * Note: Not part of JAX-RPC specification.
     *
     * @param javaType is  the Java class of the data type.
     * @param xmlType the xsi:type QName of the associated XML type.
     * @param sf/df are the factories (or the Class objects of the factory).
     * @param force Indicates whether to add the information if already registered.
     */
    public void registerTypeMapping(Class javaType, QName xmlType,
                                    SerializerFactory sf,
                                    DeserializerFactory df,
                                    boolean force) {
        TypeMapping tm = getTypeMapping();
        if (!force && tm.isRegistered(javaType, xmlType)) {
            return;
        }

        // Register the information
        tm.register(javaType, xmlType, sf, df);
    }

    /**
     * register this type matting
     * @param javaType
     * @param xmlType
     * @param sfClass
     * @param dfClass
     */
    public void registerTypeMapping(Class javaType, QName xmlType,
                                    Class sfClass, Class dfClass) {
        registerTypeMapping(javaType, xmlType, sfClass, dfClass, true);
    }

    /**
     * register a type. This only takes place if either the serializer or
     * deserializer factory could be created.
     * @param javaType java type to handle
     * @param xmlType XML mapping
     * @param sfClass class of serializer factory
     * @param dfClass class of deserializer factory
     * @param force
     */
    public void registerTypeMapping(Class javaType,
                                    QName xmlType,
                                    Class sfClass,
                                    Class dfClass,
                                    boolean force) {
        // Instantiate the factory using introspection.
        SerializerFactory   sf =
                BaseSerializerFactory.createFactory(sfClass, javaType, xmlType);
        DeserializerFactory df =
                BaseDeserializerFactory.createFactory(dfClass,
                                                      javaType,
                                                      xmlType);
        if (sf != null || df != null) {
            registerTypeMapping(javaType, xmlType, sf, df, force);
        }
    }

    /************************************************
     * Invocation
     */

    /** Invoke an RPC service with a method name and arguments.
     *
     * This will call the service, serializing all the arguments, and
     * then deserialize the return value.
     *
     * Note: Not part of JAX-RPC specification.
     *
     * @param namespace the desired namespace URI of the method element
     * @param method the method name
     * @param args an array of Objects representing the arguments to the
     *             invoked method.  If any of these objects are RPCParams,
     *             Axis will use the embedded name of the RPCParam as the
     *             name of the parameter.  Otherwise, we will serialize
     *             each argument as an XML element called "arg<n>".
     * @return a deserialized Java Object containing the return value
     * @exception AxisFault
     */
    public Object invoke(String namespace, String method, Object[] args)
                    throws AxisFault {

        if (log.isDebugEnabled()) {
            log.debug("Enter: Call::invoke(ns, meth, args)");
        }

        /**
         * Since JAX-RPC requires us to specify all or nothing, if setReturnType
         * was called (returnType != null) and we have args but addParameter
         * wasn't called (paramXMLTypes == null), then toss a fault.
         */
        if (getReturnType() != null && args != null && args.length != 0
                && operation.getNumParams() == 0) {
            throw new AxisFault(Messages.getMessage("mustSpecifyParms"));
        }

        RPCElement body = new RPCElement(namespace, method, getParamList(args));

        Object ret = invoke( body );

        if (log.isDebugEnabled()) {
            log.debug("Exit: Call::invoke(ns, meth, args)");
        }

        return ret;
    }

    /** Convenience method to invoke a method with a default (empty)
     * namespace.  Calls invoke() above.
     *
     * Note: Not part of JAX-RPC specification.
     *
     * @param method the method name
     * @param args an array of Objects representing the arguments to the
     *             invoked method.  If any of these objects are RPCParams,
     *             Axis will use the embedded name of the RPCParam as the
     *             name of the parameter.  Otherwise, we will serialize
     *             each argument as an XML element called "arg<n>".
     * @return a deserialized Java Object containing the return value
     * @exception AxisFault
     */
    public Object invoke( String method, Object [] args ) throws AxisFault
    {
        return invoke("", method, args);
    }

    /** Invoke an RPC service with a pre-constructed RPCElement.
     *
     * Note: Not part of JAX-RPC specification.
     *
     * @param body an RPCElement containing all the information about
     *             this call.
     * @return a deserialized Java Object containing the return value
     * @exception AxisFault
     */
    public Object invoke( RPCElement body ) throws AxisFault {
        if (log.isDebugEnabled()) {
            log.debug("Enter: Call::invoke(RPCElement)");
        }

        /**
         * Since JAX-RPC requires us to specify a return type if we've set
         * parameter types, check for this case right now and toss a fault
         * if things don't look right.
         */
        if (!invokeOneWay && operation != null &&
                operation.getNumParams() > 0 && getReturnType() == null) {
            // TCK:
            // Issue an error if the return type was not set, but continue processing.
            //throw new AxisFault(Messages.getMessage("mustSpecifyReturnType"));
            log.error(Messages.getMessage("mustSpecifyReturnType"));
        }

        SOAPEnvelope         reqEnv =
                new SOAPEnvelope(msgContext.getSOAPConstants(),
                                 msgContext.getSchemaVersion());
        SOAPEnvelope         resEnv = null ;
        Message              reqMsg = new Message( reqEnv );
        Message              resMsg = null ;
        Vector               resArgs = null ;
        Object               result = null ;

        // Clear the output params
        outParams = new HashMap();
        outParamsList = new ArrayList();

        // Set both the envelope and the RPCElement encoding styles
        try {
            body.setEncodingStyle(getEncodingStyle());

            setRequestMessage(reqMsg);

            reqEnv.addBodyElement(body);
            reqEnv.setMessageType(Message.REQUEST);

            invoke();
        } catch (Exception e) {
            entLog.debug(Messages.getMessage("toAxisFault00"), e);
            throw AxisFault.makeFault(e);
        }

        resMsg = msgContext.getResponseMessage();

        if (resMsg == null) {
          if (msgContext.isPropertyTrue(FAULT_ON_NO_RESPONSE, false)) {
            throw new AxisFault(Messages.getMessage("nullResponse00"));
          } else {
            return null;
          }
        }

        resEnv = resMsg.getSOAPEnvelope();
        SOAPBodyElement bodyEl = resEnv.getFirstBody();
        if (bodyEl == null) {
            return null;
        }

        if (bodyEl instanceof RPCElement) {
            try {
                resArgs = ((RPCElement) bodyEl).getParams();
            } catch (Exception e) {
                log.error(Messages.getMessage("exception00"), e);
                throw AxisFault.makeFault(e);
            }

            if (resArgs != null && resArgs.size() > 0) {

                // If there is no return, then we start at index 0 to create the outParams Map.
                // If there IS a return, then we start with 1.
                int outParamStart = 0;

                // If we have resArgs and the returnType is specified, then the first
                // resArgs is the return.  If we have resArgs and neither returnType
                // nor paramXMLTypes are specified, then we assume that the caller is
                // following the non-JAX-RPC AXIS shortcut of not having to specify
                // the return, in which case we again assume the first resArgs is
                // the return.
                // NOTE 1:  the non-JAX-RPC AXIS shortcut allows a potential error
                // to escape notice.  If the caller IS NOT following the non-JAX-RPC
                // shortcut but instead intentionally leaves returnType and params
                // null (ie., a method that takes no parameters and returns nothing)
                // then, if we DO receive something it should be an error, but this
                // code passes it through.  The ideal solution here is to require
                // this caller to set the returnType to void, but there's no void
                // type in XML.
                // NOTE 2:  we should probably verify that the resArgs element
                // types match the expected returnType and paramXMLTypes, but I'm not
                // sure how to do that since the resArgs value is a Java Object
                // and the returnType and paramXMLTypes are QNames.

                // GD 03/15/02 : We're now checking for invalid metadata
                // config at the top of this method, so don't need to do it
                // here.  Check for void return, though.

                boolean findReturnParam = false;
                QName returnParamQName = null;
                if (operation != null) {
                    returnParamQName = operation.getReturnQName();
                }

                if (!XMLType.AXIS_VOID.equals(getReturnType())) {
                    if (returnParamQName == null) {
                        // Assume the first param is the return
                        RPCParam param = (RPCParam)resArgs.get(0);
                        result = param.getObjectValue();
                        outParamStart = 1;
                    } else {
                        // If the QName of the return value was given to us, look
                        // through the result arguments to find the right name
                        findReturnParam = true;
                    }
                }

                // The following loop looks at the resargs and
                // converts the value to the appropriate return/out parameter
                // value.  If the return value is found, is value is
                // placed in result.  The remaining resargs are
                // placed in the outParams list (note that if a resArg
                // is found that does not match a operation parameter qname,
                // it is still placed in the outParms list).
                for (int i = outParamStart; i < resArgs.size(); i++) {
                    RPCParam param = (RPCParam) resArgs.get(i);

                    Class javaType = getJavaTypeForQName(param.getQName());
                    Object value = param.getObjectValue();

                    // Convert type if needed
                    if (javaType != null && value != null &&
                           !javaType.isAssignableFrom(value.getClass())) {
                        value = JavaUtils.convert(value, javaType);
                    }

                    // Check if this parameter is our return
                    // otherwise just add it to our outputs
                    if (findReturnParam &&
                          returnParamQName.equals(param.getQName())) {
                        // found it!
                        result = value;
                        findReturnParam = false;
                    } else {
                        outParams.put(param.getQName(), value);
                        outParamsList.add(value);
                    }
                }

                // added by scheu:
                // If the return param is still not found, that means
                // the returned value did not have the expected qname.
                // The soap specification indicates that this should be
                // accepted (and we also fail interop tests if we are strict here).
                // Look through the outParms and find one that
                // does not match one of the operation parameters.
                if (findReturnParam) {
                    Iterator it = outParams.keySet().iterator();
                    while (findReturnParam && it.hasNext()) {
                        QName qname = (QName) it.next();
                        ParameterDesc paramDesc =
                            operation.getOutputParamByQName(qname);
                        if (paramDesc == null) {
                            // Doesn't match a paramter, so use this for the return
                            findReturnParam = false;
                            result = outParams.remove(qname);
                        }
                    }
                }

                // If we were looking for a particular QName for the return and
                // still didn't find it, throw an exception
                if (findReturnParam) {
                    String returnParamName = returnParamQName.toString();
                    throw new AxisFault(Messages.getMessage("noReturnParam",
                                                            returnParamName));
                }
            }
        } else {
            // This is a SOAPBodyElement, try to treat it like a return value
            try {
                result = bodyEl.getValueAsType(getReturnType());
            } catch (Exception e) {
                // just return the SOAPElement
                result = bodyEl;
            }

        }

        if (log.isDebugEnabled()) {
            log.debug("Exit: Call::invoke(RPCElement)");
        }

        // Convert type if needed
        if (operation != null && operation.getReturnClass() != null) {
            result = JavaUtils.convert(result, operation.getReturnClass());
        }

        return( result );
    }

    /**
     * Get the javaType for a given parameter.
     *
     * @param name  the QName of the parameter
     * @return the class associated with that parameter
     */
    private Class getJavaTypeForQName(QName name) {
        if (operation == null) {
            return null;
        }
        ParameterDesc param = operation.getOutputParamByQName(name);
        return param == null ? null : param.getJavaType();
    }

    /**
     * Set engine option.
     *
     * Note: Not part of JAX-RPC specification.
     */
    public void setOption(String name, Object value) {
        service.getEngine().setOption(name, value);
    }

    /**
     * Invoke this Call with its established MessageContext
     * (perhaps because you called this.setRequestMessage())
     *
     * Note: Not part of JAX-RPC specification.
     *
     * @exception AxisFault
     */
    public void invoke() throws AxisFault {
        if (log.isDebugEnabled()) {
            log.debug("Enter: Call::invoke()");
        }

        isNeverInvoked  = false;

        Message      reqMsg  = null ;
        SOAPEnvelope reqEnv  = null ;

        msgContext.reset();
        msgContext.setResponseMessage(null);
        msgContext.setProperty( MessageContext.CALL, this );
        msgContext.setProperty( WSDL_SERVICE, service );
        msgContext.setProperty( WSDL_PORT_NAME, getPortName() );
        if ( isMsg ) {
            msgContext.setProperty( MessageContext.IS_MSG, "true" );
        }

        if (username != null) {
            msgContext.setUsername(username);
        }
        if (password != null) {
            msgContext.setPassword(password);
        }
        msgContext.setMaintainSession(maintainSession);

        if (operation != null) {
            msgContext.setOperation(operation);
            operation.setStyle(getOperationStyle());
            operation.setUse(getOperationUse());
        }

        if (useSOAPAction) {
            msgContext.setUseSOAPAction(true);
        }
        if (SOAPActionURI != null) {
            msgContext.setSOAPActionURI(SOAPActionURI);
        } else {
            msgContext.setSOAPActionURI(null);
        }
        if (timeout != null) {
            msgContext.setTimeout(timeout.intValue());
        }
        msgContext.setHighFidelity(!useStreaming);

        // Determine client target service
        if (myService != null) {
            // If we have a SOAPService kicking around, use that directly
            msgContext.setService(myService);
        } else {
            if (portName != null) {
                // No explicit service.  If we have a target service name,
                // try that.
                msgContext.setTargetService(portName.getLocalPart());
            } else {
                // No direct config, so try the namespace of the first body.
                reqMsg = msgContext.getRequestMessage();
                
                boolean isStream = ((SOAPPart)reqMsg.getSOAPPart()).isBodyStream();
                
                if (reqMsg != null && !isStream) {
                    reqEnv = reqMsg.getSOAPEnvelope();

                    SOAPBodyElement body = reqEnv.getFirstBody();

                    if (body != null) {
                        if ( body.getNamespaceURI() == null ) {
                            throw new AxisFault("Call.invoke",
                                                Messages.getMessage("cantInvoke00", body.getName()),
                                                null, null);
                        } else {
                            msgContext.setTargetService(body.getNamespaceURI());
                        }
                    }
                }
            }
        }
        if (log.isDebugEnabled()) {
            log.debug(Messages.getMessage("targetService",
                    msgContext.getTargetService()));
        }

        Message requestMessage = msgContext.getRequestMessage();
        if (requestMessage != null) {
            try {
                msgContext.setProperty(SOAPMessage.CHARACTER_SET_ENCODING, requestMessage.getProperty(SOAPMessage.CHARACTER_SET_ENCODING));
            } catch (SOAPException e) {
            }
            
            if(myHeaders != null) {
                reqEnv = requestMessage.getSOAPEnvelope();
    
                // If we have headers to insert, do so now.
                for (int i = 0 ; myHeaders != null && i < myHeaders.size() ; i++ ) {
                    reqEnv.addHeader((SOAPHeaderElement)myHeaders.get(i));
                }
            }
        }

        // set up transport if there is one
        if (transport != null) {
            transport.setupMessageContext(msgContext, this, service.getEngine());
        }
        else {
            msgContext.setTransportName( transportName );
        }

        SOAPService svc = msgContext.getService();
        if (svc != null) {
            svc.setPropertyParent(myProperties);
        } else {
            msgContext.setPropertyParent(myProperties);
        }

        // For debugging - print request message
        if (log.isDebugEnabled()) {
            StringWriter writer = new StringWriter();
            try {
                SerializationContext ctx = new SerializationContext(writer,
                                                                   msgContext);
                requestMessage.getSOAPEnvelope().output(ctx);
                writer.close();
            } catch (Exception e) {
                throw AxisFault.makeFault(e);
            } finally {
                log.debug(writer.getBuffer().toString());
            }
        }

        if(!invokeOneWay) {
            invokeEngine(msgContext);
        } else {
            invokeEngineOneWay(msgContext);
        }

        if (log.isDebugEnabled()) {
            log.debug("Exit: Call::invoke()");
        }
    }

    /**
     * Invoke the message on the current engine and do not wait for a response.
     *
     * @param msgContext  the <code>MessageContext</code> to use
     * @throws AxisFault if the invocation raised a fault
     */
    private void invokeEngine(MessageContext msgContext) throws AxisFault {
        service.getEngine().invoke( msgContext );

        if (transport != null) {
            transport.processReturnedMessageContext(msgContext);
        }

        Message resMsg = msgContext.getResponseMessage();

        if (resMsg == null) {
          if (msgContext.isPropertyTrue(FAULT_ON_NO_RESPONSE, false)) {
            throw new AxisFault(Messages.getMessage("nullResponse00"));
          } else {
            return;
          }
        }

        /** This must happen before deserialization...
         */
        resMsg.setMessageType(Message.RESPONSE);

        SOAPEnvelope resEnv = resMsg.getSOAPEnvelope();

        SOAPBodyElement respBody = resEnv.getFirstBody();
        if (respBody instanceof SOAPFault) {
            //we got a fault
            if(operation == null ||
                    operation.getReturnClass() == null ||
                    operation.getReturnClass() !=
                        javax.xml.soap.SOAPMessage.class) {
                //unless we don't care about the return value or we want
                //a raw message back
                //get the fault from the body and throw it
                throw ((SOAPFault)respBody).getFault();
            }
        }
    }

    /**
     * Implement async invocation by running the request in a new thread
     * @param msgContext
     */
    private void invokeEngineOneWay(final MessageContext msgContext) {
        //TODO: this is not a good way to do stuff, as it has no error reporting facility
        //create a new class
        Runnable runnable = new Runnable(){
            public void run() {
                msgContext.setProperty(Call.ONE_WAY, Boolean.TRUE);
                try {
                    service.getEngine().invoke( msgContext );
                } catch (AxisFault af){
                    //TODO: handle errors properly
                    log.debug(Messages.getMessage("exceptionPrinting"), af);
                }
                msgContext.removeProperty(Call.ONE_WAY);
            }
        };
        //create a thread to run it
        Thread thread = new Thread(runnable);
        //run it
        thread.start();
    }

    /**
     * Get the output parameters (if any) from the last invocation.
     * 
     * This allows named access - if you need sequential access, use
     * getOutputValues().
     *
     * @return a Map containing the output parameter values, indexed by QName
     */
    public Map getOutputParams()
    {
        if (isNeverInvoked) {
            throw new JAXRPCException(
                    Messages.getMessage("outputParamsUnavailable"));
        }
        return this.outParams;
    }

    /**
     * Returns a List values for the output parameters of the last
     * invoked operation.
     *
     * @return Values for the output parameters. An empty List is
     *         returned if there are no output values.
     *
     * @throws JAXRPCException - If this method is invoked for a
     *                           one-way operation or is invoked
     *                           before any invoke method has been called.
     */
    public List getOutputValues() {
        if (isNeverInvoked) {
            throw new JAXRPCException(
                    Messages.getMessage("outputParamsUnavailable"));
        }
        return outParamsList;
    }

    /**
     * Get the Service object associated with this Call object.
     *
     * Note: Not part of JAX-RPC specification.
     *
     * @return Service the Service object this Call object is associated with
     */
    public Service getService()
    {
        return this.service;
    }

    /**
     *
     * Set the service so that it defers missing property gets to the
     * Call.  So when client-side Handlers get at the MessageContext,
     * the property scoping will be MC -> SOAPService -> Call
     */
    public void setSOAPService(SOAPService service)
    {
        myService = service;
        if (service != null) {
            // Set the service so that it defers missing property gets to the
            // Call.  So when client-side Handlers get at the MessageContext,
            // the property scoping will be MC -> SOAPService -> Call -> Engine
            
            // THE ORDER OF THESE TWO CALLS IS IMPORTANT, since setting the
            // engine on a service will set the property parent for the service
            service.setEngine(this.service.getAxisClient());
            service.setPropertyParent(myProperties);
        }
    }

    /**
     * Sets the client-side request and response Handlers.  This is handy
     * for programatically setting up client-side work without deploying
     * via WSDD or the EngineConfiguration mechanism.
     */
    public void setClientHandlers(Handler reqHandler, Handler respHandler)
    {
        // Create a SOAPService which will be used as the client-side service
        // handler.
        setSOAPService(new SOAPService(reqHandler, null, respHandler));
    }


    /**
     * This method adds an attachment.
     * <p>
     * Note: Not part of JAX-RPC specification.
     *
     * @param attachment the <code>Object</code> to attach
     * @exception RuntimeException if there is no support for attachments
     *
     */
     public void addAttachmentPart( Object attachment){
         attachmentParts.add(attachment);
     }

    /**
     * Add a fault for this operation.
     * <p>
     * Note: Not part of JAX-RPC specificaion.
     *
     * @param qname     qname of the fault
     * @param cls       class of the fault
     * @param xmlType   XML type of the fault
     * @param isComplex true if xmlType is a complex type, false otherwise
     */
    public void addFault(QName qname, Class cls,
                         QName xmlType, boolean isComplex) {
        if (operationSetManually) {
            throw new RuntimeException(
                    Messages.getMessage("operationAlreadySet"));
        }

        if (operation == null) {
            operation = new OperationDesc();
        }

        FaultDesc fault = new FaultDesc();
        fault.setQName(qname);
        fault.setClassName(cls.getName());
        fault.setXmlType(xmlType);
        fault.setComplex(isComplex);
        operation.addFault(fault);
    }

    /**
     * Hand a complete OperationDesc to the Call, and note that this was
     * done so that others don't try to mess with it by calling addParameter,
     * setReturnType, etc.
     *
     * @param operation the OperationDesc to associate with this call.
     */
    public void setOperation(OperationDesc operation) {
        this.operation = operation;
        operationSetManually = true;
    }

    public OperationDesc getOperation()
    {
        return operation;
    }

    public void clearOperation() {
        operation = null;
        operationSetManually = false;
    }
}