// SOAPAutomationStub.cpp: implementation of the WMSOAPAutomationStub class.
//
//////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2000, 2001 Robert Cunnings. All rights reserved.
//
// You may copy, modify, distribute or publish this code free of charge.
//
// NO WARRANTY: This software is provided AS IS, without warranty of any kind.
//
//////////////////////////////////////////////////////////////////////
//
// WMSOAPAutomationStub class - a dispatcher for COM service providers.
// 
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "SOAPAutomationStub.h"
#include "WMSOAPProcessor.h"
#include "WMSOAPWSDLClient.h"
#include "WMXMLMessage.h"

static wchar_t *g_pwszFaultDetailNS = L"http://www.whitemesa.com/wmss/fault/";
static wchar_t *g_pwszFaultDetailHRESULT = L"HRESULT";

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

WMSOAPAutomationStub::WMSOAPAutomationStub()
{
	m_pIDispatch = NULL;
}

WMSOAPAutomationStub::~WMSOAPAutomationStub()
{
	if (m_pIDispatch != NULL)
	{
		m_pIDispatch->Release();
	}
}

bool WMSOAPAutomationStub::InvokeMethod(const wstring& wstrProgID, Iwmsoapmsg2 *pIwmsoapmsg)
{
	bool bRtn = true;
	try
	{
		InvokeSOAPMethod(wstrProgID, pIwmsoapmsg);
	}
	catch (WMSOAPFault *f)
	{
        wstring wstrFaultCode, wstrFaultCodeNS;
        long lSOAPVersion;
        pIwmsoapmsg->GetSOAPVersion(&lSOAPVersion);
        f->GetSoapFaultCode(lSOAPVersion, wstrFaultCode, wstrFaultCodeNS);
		BSTR bstrFaultCode = SysAllocString(wstrFaultCode.c_str());
		BSTR bstrFaultCodeNS = SysAllocString(wstrFaultCodeNS.c_str());
		BSTR bstrFaultString = SysAllocString(f->GetFaultString().c_str());
		VARIANT_BOOL bNeedDetail = (f->NeedDetail() == true ? VARIANT_TRUE : VARIANT_FALSE);
		VARIANT_BOOL bResult;
        if (lSOAPVersion == SOAP_VERSION_1_1)
        {
    		pIwmsoapmsg->SetSOAPFault(bstrFaultCode, bstrFaultCodeNS, bstrFaultString, bNeedDetail, &bResult);
        }
        else
        {
            wstring wstrSubcode, wstrSubcodeNS;
            f->GetFaultSubCode(wstrSubcode, wstrSubcodeNS);
		    BSTR bstrSubcode = SysAllocString(wstrSubcode.c_str());
		    BSTR bstrSubcodeNS = SysAllocString(wstrSubcodeNS.c_str());
		    BSTR bstrLangId = SysAllocString(L"en");
    		pIwmsoapmsg->SetSOAP12Fault(bstrFaultCode, bstrFaultCodeNS, bstrSubcode, bstrSubcodeNS, bstrFaultString, bstrLangId, &bResult);
		    SysFreeString(bstrSubcode);
		    SysFreeString(bstrSubcodeNS);
		    SysFreeString(bstrLangId);
        }
		SysFreeString(bstrFaultCode);
		SysFreeString(bstrFaultCodeNS);
		SysFreeString(bstrFaultString);
		if (bNeedDetail == VARIANT_TRUE)
		{
			BSTR bstrNS = SysAllocString(f->GetDetailNS().c_str());
			BSTR bstrName = SysAllocString(f->GetDetailName().c_str());
			_variant_t varContent = f->GetDetailContent();
			VARIANT_BOOL bResult;
			pIwmsoapmsg->AddFaultDetailElem(bstrNS, bstrName, varContent, &bResult);
			SysFreeString(bstrNS);
			SysFreeString(bstrName);
		}
		delete f;
		bRtn = false;
	}
	return bRtn;
}

bool WMSOAPAutomationStub::InvokeSOAPMethod(const wstring& wstrProgID,
											Iwmsoapmsg2 *pIwmsoapmsg)
{
	// try to instantiate object
	HRESULT hRes;
	CLSID clsid;
	hRes = CLSIDFromProgID(wstrProgID.c_str(), &clsid);
	if (hRes != S_OK)
	{
		char szBuf[64];
		_snprintf(szBuf, 63, "0x%08X", hRes);
        szBuf[63] = 0;
		_variant_t varContent = szBuf;
        throw new WMSOAPFault(  WMSOAPFault::Receiver,
								L"Body: Failed to resolve ProgID for method handler.",
								g_pwszFaultDetailHRESULT,
								g_pwszFaultDetailNS,
								varContent);
	}
	IDispatch *pIDispatch;
	hRes = CoCreateInstance(clsid, NULL, CLSCTX_SERVER, IID_IDispatch, (LPVOID *)&pIDispatch);
	if (hRes != S_OK)
	{
		char szBuf[64];
		_snprintf(szBuf, 63, "0x%08X", hRes);
        szBuf[63] = 0;
		_variant_t varContent = szBuf;
		throw new WMSOAPFault(  WMSOAPFault::Receiver,
								L"Body: Failed to create instance for method handler.",
								g_pwszFaultDetailHRESULT,
								g_pwszFaultDetailNS,
								varContent);
	}
	// method name goes in first
	pIwmsoapmsg->GetMethodName(&m_bstrNames[0]);

	// iterate over parameters
	BSTR bstrNamespaceURI;
	VARIANT_BOOL bIsInParam, bIsOutParam, bResult;
	long lPosition;
	int i = 0;

	for (pIwmsoapmsg->GetFirstMethodParam(  &bstrNamespaceURI,
											&m_bstrNames[i + 1],
											&m_varParamValues[i],
											&bIsInParam,
											&bIsOutParam,
											&lPosition,
											&bResult);

		 bResult == VARIANT_TRUE;
		 pIwmsoapmsg->GetNextMethodParam(   &bstrNamespaceURI,
											&m_bstrNames[i + 1],
											&m_varParamValues[i],
											&bIsInParam,
											&bIsOutParam,
											&lPosition,
											&bResult)
	)
	{
		// is this pass by ref?
		if (bIsOutParam == VARIANT_TRUE)
		{
			// set up reference
			m_varByRefParams[i] = m_varParamValues[i];
			VARIANTARG varArg;
			VariantInit(&varArg);
			varArg.vt = (&m_varByRefParams[i])->vt | VT_BYREF;
			varArg.byref = &((&m_varByRefParams[i])->byref);
			m_varParamValues[i] = &varArg;
			VariantClear(&varArg);
			// put into output param map
			m_mapOutParamOffsets.insert(map<int, wstring>::value_type(i, wstring(m_bstrNames[i + 1])));
		}
		SysFreeString(bstrNamespaceURI);
		++i;
	}
	// now make COM invocation
	hRes = pIDispatch->GetIDsOfNames(IID_NULL, m_bstrNames, i + 1, LOCALE_USER_DEFAULT, m_idDispatchIDs);
	// done with names
	for (int k = 0; k < i + 1; k++)
	{
		SysFreeString(m_bstrNames[k]);
	}
	if (hRes != S_OK)
	{
		pIDispatch->Release();
	    char szBuf[64];
		_snprintf(szBuf, 63, "0x%08X", hRes);
        szBuf[63] = 0;
	    _variant_t varContent = szBuf;
		throw new WMSOAPFault(  WMSOAPFault::Sender,
                                L"BadArguments",
                                g_pwszSoap12RpcNS,
                                L"Body: One or more invalid method parameter names.",
                    		    g_pwszFaultDetailHRESULT,
							    g_pwszFaultDetailNS,
							    varContent);
	}
	DISPID idMethod = m_idDispatchIDs[0];
	m_DispatchParams.rgvarg = m_varParamValues;
	m_DispatchParams.rgdispidNamedArgs = &m_idDispatchIDs[1];
	m_DispatchParams.cArgs = i;
	m_DispatchParams.cNamedArgs = i;

	EXCEPINFO ExceptionInfo;
	UINT dwErrorArg = 0xFFFFFFFF;
	hRes = pIDispatch->Invoke(  idMethod,
								IID_NULL, 
								LOCALE_USER_DEFAULT,
								DISPATCH_METHOD,
								&m_DispatchParams,
								&m_varRetVal,
								&ExceptionInfo,
								&dwErrorArg);
	if (hRes != S_OK)
	{
		pIDispatch->Release();
		char szBuf[64];
		_snprintf(szBuf, 63, "0x%08X", hRes);
        szBuf[63] = 0;
		_variant_t varContent = szBuf;
		if (hRes == DISP_E_BADPARAMCOUNT)
		{
		    throw new WMSOAPFault(  WMSOAPFault::Sender,
                                    L"BadArguments",
                                    g_pwszSoap12RpcNS,
                                    L"Body: Bad parameter count on procedure call.",
                    		        g_pwszFaultDetailHRESULT,
							        g_pwszFaultDetailNS,
							        varContent);
		}
		throw new WMSOAPFault(  WMSOAPFault::Sender,
								L"Body: Method invocation failed.",
								g_pwszFaultDetailHRESULT,
								g_pwszFaultDetailNS,
								varContent);
	}
	// store ptr
	m_pIDispatch = pIDispatch;
	// now update SOAP message contents
	pIwmsoapmsg->SetReturnValue(m_varRetVal);
	pIwmsoapmsg->ClearMethodParams();
	map<int, wstring>::iterator iterOut;
	for (iterOut = m_mapOutParamOffsets.begin(); iterOut != m_mapOutParamOffsets.end(); iterOut++)
	{
		BSTR bstrNS = SysAllocString(L"");
		BSTR bstrName = SysAllocString((*iterOut).second.c_str());

		m_varByRefParams[(*iterOut).first].vt = (m_varParamValues[(*iterOut).first].vt & VT_TYPEMASK);

		pIwmsoapmsg->AddMethodParam(bstrNS,
									bstrName,
									m_varByRefParams[(*iterOut).first],
									VARIANT_FALSE,
									VARIANT_TRUE,
									&bResult);
		SysFreeString(bstrNS);
		SysFreeString(bstrName);
		if (bResult == VARIANT_FALSE)
		{
			throw new WMSOAPFault(  WMSOAPFault::Receiver,
                                    L"Body: Failed to update method [out] params.",
                                    true);
		}
	}
	return true;
}
