/*
 * 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.providers;

import org.apache.axis.AxisFault;
import org.apache.axis.Constants;
import org.apache.axis.Message;
import org.apache.axis.MessageContext;
import org.apache.axis.components.bridge.COMBridge;
import org.apache.axis.deployment.wsdd.providers.WSDDComProvider;
import org.apache.axis.description.OperationDesc;
import org.apache.axis.description.ParameterDesc;
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.soap.SOAPConstants;
import org.apache.axis.utils.JavaUtils;
import org.apache.axis.utils.Messages;

import javax.xml.namespace.QName;
import java.util.Hashtable;
import java.util.Vector;

public class ComProvider extends BasicProvider {


    public void invoke(MessageContext msgContext) throws AxisFault {
        try {
            SOAPService service = msgContext.getService();
            String progID = (String) service.getOption(WSDDComProvider.OPTION_PROGID);
            String threadingModel = (String) service.getOption(WSDDComProvider.OPTION_THREADING_MODEL);

            if (log.isDebugEnabled()) {
                log.debug("Enter: COMProvider.processMessage()");
            }

            OperationDesc operation = msgContext.getOperation();

            Vector bodies = msgContext.getRequestMessage().getSOAPEnvelope().getBodyElements();
            if (log.isDebugEnabled()) {
                log.debug(Messages.getMessage("bodyElems00", "" + bodies.size()));
                log.debug(Messages.getMessage("bodyIs00", "" + bodies.get(0)));
            }

            RPCElement body = null;
            
            // Find the first "root" body element, which is the RPC call.
            for (int bNum = 0; body == null && bNum < bodies.size(); bNum++) {
                // If this is a regular old SOAPBodyElement, and it's a root,
                // we're probably a non-wrapped doc/lit service.  In this case,
                // we deserialize the element, and create an RPCElement "wrapper"
                // around it which points to the correct method.
                // FIXME : There should be a cleaner way to do this...
                if (!(bodies.get(bNum) instanceof RPCElement)) {
                    SOAPBodyElement bodyEl = (SOAPBodyElement) bodies.get(bNum);
                    // igors: better check if bodyEl.getID() != null
                    // to make sure this loop does not step on SOAP-ENC objects
                    // that follow the parameters! FIXME?
                    if (bodyEl.isRoot() && operation != null && bodyEl.getID() == null) {
                        ParameterDesc param = operation.getParameter(bNum);
                        // at least do not step on non-existent parameters!
                        if (param != null) {
                            Object val = bodyEl.getValueAsType(param.getTypeQName());
                            body = new RPCElement("",
                                    operation.getName(),
                                    new Object[]{val});
                        }
                    }
                } else {
                    body = (RPCElement) bodies.get(bNum);
                }
            }

            String methodName = body.getMethodName();
            Vector args = body.getParams();
            int numArgs = args.size();

            Vector argValues = new Vector();
            
            // Put the values contained in the RPCParams into an array
            // suitable for passing to java.lang.reflect.Method.invoke()
            // Make sure we respect parameter ordering if we know about it
            // from metadata, and handle whatever conversions are necessary
            // (values -> Holders, etc)
            for (int i = 0; i < numArgs; i++) {
                RPCParam rpcParam = (RPCParam) args.get(i);
                Object value = rpcParam.getObjectValue();

                // first check the type on the paramter
                ParameterDesc paramDesc = rpcParam.getParamDesc();

                // if we found some type info try to make sure the value type is
                // correct.  For instance, if we deserialized a xsd:dateTime in
                // to a Calendar and the service takes a Date, we need to convert
                if (paramDesc != null && paramDesc.getJavaType() != null) {

                    // Get the type in the signature (java type or its holder)
                    Class sigType = paramDesc.getJavaType();

                    // Convert the value into the expected type in the signature
                    value = JavaUtils.convert(value,
                            sigType);

                    rpcParam.setObjectValue(value);
                }
                argValues.add(value);
            }

            COMBridge bridge = new COMBridge();
            Hashtable props = new Hashtable();
            props.put("progid", progID);
            if (threadingModel != null)
                props.put("threadmodel", threadingModel);

            Object result = bridge.execute(methodName, argValues, props);

            RPCElement resBody = new RPCElement(methodName + "Response");
            resBody.setPrefix(body.getPrefix());
            resBody.setNamespaceURI(body.getNamespaceURI());
            resBody.setEncodingStyle(msgContext.getEncodingStyle());

            Message resMsg = msgContext.getResponseMessage();
            SOAPEnvelope resEnv;

            // If we didn't have a response message, make sure we set one up
            if (resMsg == null) {
                resEnv = new SOAPEnvelope(msgContext.getSOAPConstants());

                resMsg = new Message(resEnv);
                msgContext.setResponseMessage(resMsg);
            } else {
                resEnv = resMsg.getSOAPEnvelope();
            }

            QName returnQName = operation.getReturnQName();
            if (returnQName == null) {
                returnQName = new QName("", methodName + "Return");
            }

            // For SOAP 1.2, add a result
            if (msgContext.getSOAPConstants() ==
                    SOAPConstants.SOAP12_CONSTANTS) {
                returnQName = Constants.QNAME_RPC_RESULT;
            }

            RPCParam param = new RPCParam(returnQName, result);
            param.setParamDesc(operation.getReturnParamDesc());
            if (!operation.isReturnHeader()) {
                resBody.addParam(param);
            } else {
                resEnv.addHeader(new RPCHeaderParam(param));
            }

            resEnv.addBodyElement(resBody);

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

    public void initServiceDesc(SOAPService service, MessageContext msgContext)
            throws AxisFault {
    }
}
