// WMSOAPHeaderDisp.cpp: implementation of the WMSOAPHeaderDisp class.
//////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2003 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.
//
//////////////////////////////////////////////////////////////////////
//
// WMSOAPHeaderDisp class - a SOAP header processor dispatcher.
// 
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "WMSOAPHeaderDisp.h"

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

WMSOAPHeaderDisp::WMSOAPHeaderDisp()
{

}

WMSOAPHeaderDisp::~WMSOAPHeaderDisp()
{
	Cleanup();
}

void WMSOAPHeaderDisp::Cleanup()
{
	list<IDispatch *>::iterator iter;
	for (iter = m_listHeaderProcs.begin(); iter != m_listHeaderProcs.end(); iter++)
	{
		(*iter)->Release();
	}
	m_listHeaderProcs.clear();
}


bool WMSOAPHeaderDisp::Initialize(  list<wstring>& listProgIDs,
									Iwmsoapmsg2 *pIwmsoapmsg)
{
    list<wstring>::iterator iL;
    for (iL = listProgIDs.begin(); iL != listProgIDs.end(); iL++)
	{
		try
		{
			CLSID clsid;
			HRESULT hRes = CLSIDFromProgID((*iL).c_str(), &clsid);
			if (hRes != S_OK)
			{
				throw new WMSOAPFault(
                    WMSOAPFault::Receiver, L"Headers: Failed to resolve ProgID for header handler.", false);
			}
			IDispatch *pIDispatch;
			hRes = CoCreateInstance(clsid, NULL, CLSCTX_SERVER, IID_IDispatch, (LPVOID *)&pIDispatch);
			if (hRes != S_OK)
			{
				throw new WMSOAPFault(
                    WMSOAPFault::Receiver, L"Headers: Failed to create instance for header handler.", false);
			}
			m_listHeaderProcs.push_back(pIDispatch);
		}
		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);
			delete f;
			return false;
		}
	}
	return true;
}

bool WMSOAPHeaderDisp::PreProcessMUHeaders(Iwmsoapmsg2 *pIwmsoapmsg)
{
    _variant_t varHeaders;
    // get 2 dimensional array of {namespace, localname} pairs
    pIwmsoapmsg->GetMUHeaderBlocks(&varHeaders);
    if ((varHeaders.vt & (VT_ARRAY | VT_VARIANT)) == false ||
        varHeaders.parray == NULL ||
        SafeArrayGetDim(varHeaders.parray) != 2)
    {
        return false;
    }
	SAFEARRAY *pArray = varHeaders.parray;
	const long lNumDims = 2;
	long arrIndex[2];
	// lock array
	SafeArrayLock(pArray);
	// array format: [rows][cols] i == rows, j == cols
	// leftmost dimension (rows) is in rgsabound[0]
	for (int i = pArray->rgsabound[0].lLbound;
			i < (pArray->rgsabound[0].lLbound + (long)pArray->rgsabound[0].cElements);
			i++)
	{
        BSTR bstrLocalName, bstrNamespace;
		// leftmost dimension (rows) is arrIndex[1] (opposite of rgsabound[])
		arrIndex[1] = i;
		// rightmost dimension (cols) is in rgsabound[1]
		for (int j = pArray->rgsabound[1].lLbound;
			j < (pArray->rgsabound[1].lLbound + (long)pArray->rgsabound[1].cElements);
			j++)
		{
			// rightmost dimension (cols) is arrIndex[0]
			arrIndex[0] = j;
        	VARIANT *pVar = NULL;
			SafeArrayPtrOfIndex(pArray, arrIndex, (void **)&pVar);
			// get data
			int nOffset = j - pArray->rgsabound[1].lLbound;
			if (nOffset == 0)
			{
				bstrNamespace = pVar->bstrVal;
			}
			else
			{
				bstrLocalName = pVar->bstrVal;
			}
		}
        // now poll header processors to see if they understand this header block
	    list<IDispatch *>::iterator iter;
        bool bUnderstood = false;
	    for (iter = m_listHeaderProcs.begin(); iter != m_listHeaderProcs.end(); iter++)
	    {
			if (TestMUHeaderBlock(*iter, bstrNamespace, bstrLocalName, pIwmsoapmsg) == true)
			{
                // yes, understood
				bUnderstood = true;
                break;
			}
        }
        if (bUnderstood == false)
        {
            // none of the header processors claims to understand this header block
        	SafeArrayUnlock(pArray);
            long lSOAPVersion;
            pIwmsoapmsg->GetSOAPVersion(&lSOAPVersion);
		    BSTR bstrFaultCode = SysAllocString(L"MustUnderstand");
            BSTR bstrFaultCodeNS = NULL;
            BSTR bstrFaultString = SysAllocString(L"Headers: Mandatory header block not understood.");
		    VARIANT_BOOL bNeedDetail = VARIANT_FALSE;
		    VARIANT_BOOL bResult;
            if (lSOAPVersion == SOAP_VERSION_1_1)
            {
		        bstrFaultCodeNS = SysAllocString(g_pwszSoapEnvelopeNS);
    		    pIwmsoapmsg->SetSOAPFault(bstrFaultCode, bstrFaultCodeNS, bstrFaultString, bNeedDetail, &bResult);
            }
            else
            {
		        bstrFaultCodeNS = SysAllocString(g_pwszSoap12EnvelopeNS);
		        BSTR bstrSubcode = SysAllocString(L"");
		        BSTR bstrSubcodeNS = SysAllocString(L"");
		        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 (lSOAPVersion == SOAP_VERSION_1_2)
            {
	            // create 'NotUnderstood' header block in fault message
	            WMXMLElement *pBlock = new WMXMLElement(wstring(L"SOAP-ENV:NotUnderstood"));
	            if (pBlock != NULL)
	            {
		            pBlock->SetNSPrefix(wstring(L"SOAP-ENV"), wstring(g_pwszSoap12EnvelopeNS));
		            pBlock->SetNSPrefix(wstring(L"ns"), wstring(bstrNamespace));
                    wstring wstrAttr(L"ns:");
                    wstrAttr += bstrLocalName;
		            pBlock->SetAttribute(wstring(L"qname"), wstrAttr);
                    BSTR bstrXML = pBlock->Serialize(NULL);
	                delete pBlock;
                    BSTR bstrNS = SysAllocString(g_pwszSoap12EnvelopeNS);
                    BSTR bstrName = SysAllocString(L"NotUnderstood");
                    BSTR bstrActor = SysAllocString(L"");
                    BSTR bstrEnc = SysAllocString(L"");
                    VARIANT_BOOL bMU = VARIANT_FALSE;
                    VARIANT_BOOL bResult = VARIANT_FALSE;
                    pIwmsoapmsg->AddFaultHeaderElemLiteral(bstrNamespace, bstrLocalName, bstrXML, bMU, bstrActor, bstrEnc, &bResult);
                    SysFreeString(bstrNS);
                    SysFreeString(bstrName);
                    SysFreeString(bstrXML);
                    SysFreeString(bstrActor);
                    SysFreeString(bstrEnc);
	            }
            }
            return false;
        }
	}
	SafeArrayUnlock(pArray);
    return true;
}

bool WMSOAPHeaderDisp::TestMUHeaderBlock(   IDispatch *pIDispatch,
                                            BSTR bstrNamespace,
                                            BSTR bstrLocalName,
                                            Iwmsoapmsg2 *pIwmsoapmsg)
{
	// MU header block check
	_variant_t	varRetVal;
	DISPPARAMS	DispatchParams;
	DISPID		idDispatchIDs[3];
	_variant_t	varParamValues[2];
	BSTR		bstrNames[3];

	// method name goes in first
	bstrNames[0] = SysAllocString(L"OnTestMUHeaderBlock");
	bstrNames[1] = SysAllocString(L"bstrNamespace");
	bstrNames[2] = SysAllocString(L"bstrLocalName");

    // parameter values
	varParamValues[0] = bstrNamespace;
	varParamValues[1] = bstrLocalName;

    HRESULT hRes;
	hRes = pIDispatch->GetIDsOfNames(   IID_NULL,
										bstrNames,
										3,
										LOCALE_USER_DEFAULT,
										idDispatchIDs);
	// done with bstr's
	SysFreeString(bstrNames[0]);
	SysFreeString(bstrNames[1]);
	SysFreeString(bstrNames[2]);
    // success?
	if (hRes != S_OK)
	{
		return false;
	}
	// make call
	DISPID idMethod = idDispatchIDs[0];
	DispatchParams.rgvarg = varParamValues;
	DispatchParams.rgdispidNamedArgs = &idDispatchIDs[1];
	DispatchParams.cArgs = 2;
	DispatchParams.cNamedArgs = 2;

	EXCEPINFO ExceptionInfo;
	UINT dwErrorArg = 0xFFFFFFFF;
	hRes = pIDispatch->Invoke(  idMethod,
								IID_NULL, 
								LOCALE_USER_DEFAULT,
								DISPATCH_METHOD,
								&DispatchParams,
								&varRetVal,
								&ExceptionInfo,
								&dwErrorArg);
	if (hRes != S_OK)
	{
		return false;
	}
	return (varRetVal.boolVal == VARIANT_TRUE ? true : false);
}

bool WMSOAPHeaderDisp::ProcessHeader(Iwmsoapmsg2 *pIwmsoapmsg)
{
	if (PreProcessMUHeaders(pIwmsoapmsg) == false)
	{
		return false;
	}
	list<IDispatch *>::iterator iter;
	for (iter = m_listHeaderProcs.begin(); iter != m_listHeaderProcs.end(); iter++)
	{
		try
		{
			if (CallHeaderHandler(*iter, pIwmsoapmsg) == false)
			{
				return false;
			}
		}
		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);
			delete f;
			return false;
		}
	}
	return true;
}

bool WMSOAPHeaderDisp::CallHeaderHandler(IDispatch *pIDispatch,
										 Iwmsoapmsg2 *pIwmsoapmsg)
{
	// header processing
	_variant_t	varRetValHdr;
	DISPPARAMS	DispatchParamsHdr;
	DISPID		idDispatchIDsHdr[2];
	_variant_t	varParamValuesHdr[2];
	BSTR		bstrNamesHdr[2];

	// get dispatch interface for message object
	IDispatch *pIDispatchMsg;
	HRESULT hRes = pIwmsoapmsg->QueryInterface(IID_IDispatch, (void **)&pIDispatchMsg);
	if (hRes != S_OK)
	{
		throw new WMSOAPFault(
            WMSOAPFault::Receiver, L"Headers: Failed to get interface to message object.", false);
	}

	// method name goes in first
	bstrNamesHdr[0] = SysAllocString(L"OnProcessHeader");
	bstrNamesHdr[1] = SysAllocString(L"SOAPMsgInterface");

    // parameter value
	varParamValuesHdr[0] = pIDispatchMsg;

	hRes = pIDispatch->GetIDsOfNames(   IID_NULL,
										bstrNamesHdr,
										2,
										LOCALE_USER_DEFAULT,
										idDispatchIDsHdr);
	// done with bstr's
	SysFreeString(bstrNamesHdr[0]);
	SysFreeString(bstrNamesHdr[1]);
    // success?
	if (hRes != S_OK)
	{
		pIDispatchMsg->Release();
		throw new WMSOAPFault(
			WMSOAPFault::Receiver, L"Headers: Failed to resolve header handler call parameters.", false);
	}
	// make call
	DISPID idMethod = idDispatchIDsHdr[0];
	DispatchParamsHdr.rgvarg = varParamValuesHdr;
	DispatchParamsHdr.rgdispidNamedArgs = &idDispatchIDsHdr[1];
	DispatchParamsHdr.cArgs = 1;
	DispatchParamsHdr.cNamedArgs = 1;

	EXCEPINFO ExceptionInfo;
	UINT dwErrorArg = 0xFFFFFFFF;
	hRes = pIDispatch->Invoke(  idMethod,
								IID_NULL, 
								LOCALE_USER_DEFAULT,
								DISPATCH_METHOD,
								&DispatchParamsHdr,
								&varRetValHdr,
								&ExceptionInfo,
								&dwErrorArg);
	if (hRes != S_OK)
	{
		pIDispatchMsg->Release();
		throw new WMSOAPFault(
			WMSOAPFault::Receiver, L"Headers: Invocation of header handler failed.", false);
	}
	// done with object dispatch interface!
	pIDispatchMsg->Release();
	return (varRetValHdr.boolVal == VARIANT_TRUE ? true : false);
}

