1144 lines
34 KiB
C++
1144 lines
34 KiB
C++
|
//---------------------------------------------------------------------------
|
||
|
//
|
||
|
// Microsoft Windows
|
||
|
// Copyright (C) Microsoft Corporation, 1997 - 2000.
|
||
|
//
|
||
|
// File: DBERROR.CXX
|
||
|
//
|
||
|
// Contents: Ole DB Error implementation for CI
|
||
|
//
|
||
|
// History: 28-Apr-97 KrishnaN Created
|
||
|
//
|
||
|
//----------------------------------------------------------------------------
|
||
|
|
||
|
#include <pch.cxx>
|
||
|
#pragma hdrstop
|
||
|
|
||
|
#include <mssql.h> // parser errors
|
||
|
#include <parserr.h> // IDS_ values of parser errors (mc generated header)
|
||
|
|
||
|
//#include <initguid.h>
|
||
|
#define DBINITCONSTANTS
|
||
|
#include <msdaguid.h>
|
||
|
|
||
|
|
||
|
#define ERROR_MESSAGE_SIZE 512
|
||
|
|
||
|
extern long gulcInstances;
|
||
|
|
||
|
// Implementation of CCIOleDBError
|
||
|
|
||
|
//+---------------------------------------------------------------------------
|
||
|
//
|
||
|
// Member: CCIOleDBError::CCIOleDBError, public
|
||
|
//
|
||
|
// Synopsis: Constructor. Gets the class factory for error object.
|
||
|
//
|
||
|
// Arguments: [rUnknown] - Controlling unknown.
|
||
|
//
|
||
|
// History: 28-Apr-97 KrishnaN Created
|
||
|
//----------------------------------------------------------------------------
|
||
|
//
|
||
|
CCIOleDBError::CCIOleDBError ( IUnknown & rUnknown, CMutexSem & mutex ) :
|
||
|
_mutex( mutex ),
|
||
|
_rUnknown(rUnknown),
|
||
|
_pErrClassFact (0)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
//+---------------------------------------------------------------------------
|
||
|
//
|
||
|
// Member: CCIOleDBError::~CCIOleDBError, public
|
||
|
//
|
||
|
// Synopsis: Releases class factory.
|
||
|
//
|
||
|
// Arguments:
|
||
|
//
|
||
|
// History: 05-May-97 KrishnaN Created
|
||
|
//----------------------------------------------------------------------------
|
||
|
|
||
|
CCIOleDBError::~CCIOleDBError()
|
||
|
{
|
||
|
if ( 0 != _pErrClassFact )
|
||
|
_pErrClassFact->Release();
|
||
|
}
|
||
|
|
||
|
//+---------------------------------------------------------------------------
|
||
|
//
|
||
|
// Member: CCIOleDBError::QueryInterface, public
|
||
|
//
|
||
|
// Synopsis: Supports IID_IUnknown and IID_ISupportErrorInfo
|
||
|
//
|
||
|
// History: 28-Apr-97 KrishnaN Created
|
||
|
//----------------------------------------------------------------------------
|
||
|
|
||
|
STDMETHODIMP CCIOleDBError::QueryInterface(REFIID riid, void **ppvObject)
|
||
|
{
|
||
|
return _rUnknown.QueryInterface(riid, ppvObject);
|
||
|
|
||
|
} //QueryInterface
|
||
|
|
||
|
//+---------------------------------------------------------------------------
|
||
|
//
|
||
|
// Member: CCIOleDBError::AddRef, public
|
||
|
//
|
||
|
// History: 17-Mar-97 KrishnaN Created
|
||
|
//
|
||
|
//----------------------------------------------------------------------------
|
||
|
|
||
|
STDMETHODIMP_(ULONG) CCIOleDBError::AddRef()
|
||
|
{
|
||
|
return _rUnknown.AddRef();
|
||
|
} //AddRef
|
||
|
|
||
|
//+---------------------------------------------------------------------------
|
||
|
//
|
||
|
// Member: CCIOleDBError::Release, public
|
||
|
//
|
||
|
// History: 17-Mar-97 KrishnaN Created
|
||
|
//
|
||
|
//----------------------------------------------------------------------------
|
||
|
|
||
|
STDMETHODIMP_(ULONG) CCIOleDBError::Release()
|
||
|
{
|
||
|
return _rUnknown.Release();
|
||
|
|
||
|
} //Release
|
||
|
|
||
|
// ISupportErrorInfo method
|
||
|
|
||
|
//+---------------------------------------------------------------------------
|
||
|
//
|
||
|
// Member: CCIOleDBError::InterfaceSupportsErrorInfo, public
|
||
|
//
|
||
|
// Synopsis: Checks if error reporting on the specified interface is supported
|
||
|
//
|
||
|
// Arguments: [riid] - The interface in question
|
||
|
//
|
||
|
// History: 28-Apr-97 KrishnaN Created
|
||
|
//----------------------------------------------------------------------------
|
||
|
|
||
|
STDMETHODIMP CCIOleDBError::InterfaceSupportsErrorInfo(REFIID riid)
|
||
|
{
|
||
|
ULONG ul;
|
||
|
|
||
|
// See if the interface asked about, actually
|
||
|
// creates an error object.
|
||
|
for(ul=0; ul < _cErrInt; ul++)
|
||
|
{
|
||
|
if( *(_rgpErrInt[ul]) == riid )
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
return S_FALSE;
|
||
|
} // InterfaceSupportsErrorInfo
|
||
|
|
||
|
|
||
|
//+---------------------------------------------------------------------------
|
||
|
//
|
||
|
// Member: CCIOleDBError::GetErrorInterfaces, private
|
||
|
//
|
||
|
// Synopsis: Gets the error interfaces, IErrorInfo and IErrorRecords.
|
||
|
//
|
||
|
// Arguments: [ppIErrorInfo] - Pointer to hold IErrorInfo i/f pointer
|
||
|
// [ppIErrorRecords] - Pointer to hold IErrorRecords i/f pointer
|
||
|
//
|
||
|
// History: 28-Apr-97 KrishnaN Created
|
||
|
//----------------------------------------------------------------------------
|
||
|
//
|
||
|
HRESULT CCIOleDBError::GetErrorInterfaces(IErrorInfo** ppIErrorInfo,
|
||
|
IErrorRecords** ppIErrorRecords)
|
||
|
{
|
||
|
if (0 == ppIErrorInfo || 0 == ppIErrorRecords)
|
||
|
return E_INVALIDARG;
|
||
|
|
||
|
*ppIErrorInfo = 0;
|
||
|
*ppIErrorRecords = 0;
|
||
|
|
||
|
if FAILED(_GetErrorClassFact())
|
||
|
return E_NOINTERFACE;
|
||
|
|
||
|
//
|
||
|
// Do we have a class factory on CLSID_EXTENDEDERROR ?
|
||
|
//
|
||
|
if (0 == _pErrClassFact)
|
||
|
return E_NOINTERFACE;
|
||
|
|
||
|
HRESULT hr = S_OK;
|
||
|
|
||
|
//
|
||
|
// Obtain the error object or create a new one if none exists
|
||
|
//
|
||
|
|
||
|
GetErrorInfo(0, ppIErrorInfo);
|
||
|
if ( !*ppIErrorInfo )
|
||
|
{
|
||
|
if( FAILED(hr = _pErrClassFact->CreateInstance(NULL,
|
||
|
IID_IErrorInfo, (LPVOID*)ppIErrorInfo)) )
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Obtain the IErrorRecord Interface
|
||
|
//
|
||
|
|
||
|
hr = (*ppIErrorInfo)->QueryInterface(IID_IErrorRecords,
|
||
|
(LPVOID*)ppIErrorRecords);
|
||
|
|
||
|
//
|
||
|
// On a failure retrieving IErrorRecords, we need to release
|
||
|
// the IErrorInfo interface
|
||
|
//
|
||
|
|
||
|
if( FAILED(hr) && *ppIErrorInfo )
|
||
|
{
|
||
|
(*ppIErrorInfo)->Release();
|
||
|
*ppIErrorInfo = NULL;
|
||
|
}
|
||
|
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
//+---------------------------------------------------------------------------
|
||
|
//
|
||
|
// Member: CCIOleDBError::PostHResult, public
|
||
|
//
|
||
|
// Synopsis: Post an HRESULT to be looked up in ole-db sdk's error
|
||
|
// collection OR CI provided error lookup service.
|
||
|
//
|
||
|
// Arguments: [hrErr] - Code returned by the method that caused the error.
|
||
|
// [piid] - Interface where the error occurred.
|
||
|
//
|
||
|
// Returns: The incoming hrErr is echoed back to simplify error reporting
|
||
|
// in the calling code. So the caller can simply say something like
|
||
|
// "return PostHResult(E_INVALIDARG, &IID_ICommand);" instead of:
|
||
|
// "PostHResult(E_INVALIDARG, &IID_ICommand); return E_INVALIDARG;".
|
||
|
//
|
||
|
// History: 28-Apr-97 KrishnaN Created
|
||
|
//----------------------------------------------------------------------------
|
||
|
|
||
|
HRESULT CCIOleDBError::PostHResult(HRESULT hrErr, const IID & refiid)
|
||
|
{
|
||
|
SCODE hr = S_OK;
|
||
|
ERRORINFO ErrorInfo;
|
||
|
|
||
|
//
|
||
|
// Obtain the error object or create a new one if none exists
|
||
|
//
|
||
|
|
||
|
XInterface<IErrorInfo> xErrorInfo;
|
||
|
XInterface<IErrorRecords> xErrorRecords;
|
||
|
hr = GetErrorInterfaces((IErrorInfo **)xErrorInfo.GetQIPointer(),
|
||
|
(IErrorRecords **)xErrorRecords.GetQIPointer());
|
||
|
if (FAILED(hr))
|
||
|
return hrErr;
|
||
|
|
||
|
//
|
||
|
// Content Index methods sometimes throw NTSTATUS errors. So check for
|
||
|
// those and translate them to HRESULTs, just as is done in GetOleError()
|
||
|
//
|
||
|
|
||
|
switch (hrErr)
|
||
|
{
|
||
|
case STATUS_NO_MEMORY:
|
||
|
case HRESULT_FROM_WIN32( ERROR_COMMITMENT_LIMIT ):
|
||
|
case HRESULT_FROM_WIN32( ERROR_NO_SYSTEM_RESOURCES ):
|
||
|
case STG_E_TOOMANYOPENFILES:
|
||
|
case STG_E_INSUFFICIENTMEMORY:
|
||
|
case STATUS_INSUFFICIENT_RESOURCES:
|
||
|
hrErr = E_OUTOFMEMORY;
|
||
|
break;
|
||
|
|
||
|
case HRESULT_FROM_WIN32( ERROR_SEM_TIMEOUT ):
|
||
|
case HRESULT_FROM_WIN32( ERROR_PIPE_BUSY ):
|
||
|
hrErr = CI_E_TIMEOUT;
|
||
|
break;
|
||
|
|
||
|
case HRESULT_FROM_WIN32( ERROR_FILE_NOT_FOUND ):
|
||
|
hrErr = CI_E_NOT_RUNNING;
|
||
|
break;
|
||
|
|
||
|
case STATUS_NOT_FOUND:
|
||
|
hrErr = CI_E_NOT_FOUND;
|
||
|
break;
|
||
|
|
||
|
case STATUS_INVALID_PARAMETER:
|
||
|
hrErr = E_INVALIDARG;
|
||
|
break;
|
||
|
|
||
|
case STATUS_ACCESS_DENIED:
|
||
|
hrErr = E_ACCESSDENIED;
|
||
|
break;
|
||
|
|
||
|
case STATUS_INVALID_PARAMETER_MIX:
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Check to see if we have already posted this error
|
||
|
//
|
||
|
|
||
|
if ( NeedToSetError(hrErr, xErrorInfo.GetPointer(), xErrorRecords.GetPointer()) )
|
||
|
{
|
||
|
//
|
||
|
// Assign static information across each error record added
|
||
|
//
|
||
|
|
||
|
ErrorInfo.clsid = CLSID_CI_PROVIDER;
|
||
|
ErrorInfo.dispid = NULL;
|
||
|
ErrorInfo.hrError = hrErr;
|
||
|
ErrorInfo.iid = refiid;
|
||
|
ErrorInfo.dwMinor = 0;
|
||
|
|
||
|
//
|
||
|
// If this is a CI error, then add it with the lookup code IDENTIFIER_CI_ERROR
|
||
|
// If not, then it must be a Ole DB error or a Windows error. In either
|
||
|
// case, the default Ole DB sdk error lookup service will handle it. So
|
||
|
// post non-CI errors with IDENTIFIER_SDK_ERROR lookup id.
|
||
|
//
|
||
|
|
||
|
DWORD dwLookupId = IsCIError(hrErr) ? IDENTIFIER_CI_ERROR : IDENTIFIER_SDK_ERROR;
|
||
|
|
||
|
//
|
||
|
// Add the record to the Error Service Object
|
||
|
//
|
||
|
|
||
|
hr = xErrorRecords->AddErrorRecord(&ErrorInfo, dwLookupId, NULL, NULL, 0);
|
||
|
|
||
|
//
|
||
|
// Pass the error object to the Ole Automation DLL
|
||
|
//
|
||
|
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
hr = SetErrorInfo(0, xErrorInfo.GetPointer());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Release the interfaces to transfer ownership to
|
||
|
// the Ole Automation DLL. This will happen when
|
||
|
// xErrorInfo and xErrorRecords destruct, at the
|
||
|
// exit point of this method.
|
||
|
//
|
||
|
|
||
|
return hrErr;
|
||
|
}
|
||
|
|
||
|
//+---------------------------------------------------------------------------
|
||
|
//
|
||
|
// Member: CCIOleDBError::PostHResult, public
|
||
|
//
|
||
|
// Synopsis: Post an HRESULT to be looked up in ole-db sdk's error
|
||
|
// collection OR CI provided error lookup service.
|
||
|
//
|
||
|
// Arguments: [e] - CException object containing error code.
|
||
|
// [piid] - Interface where the error occurred.
|
||
|
//
|
||
|
// Returns: The incoming hrErr is echoed back to simplify error reporting
|
||
|
// in the calling code. So the caller can simply say something like
|
||
|
// "return PostHResult(E_INVALIDARG, &IID_ICommand);" instead of:
|
||
|
// "PostHResult(E_INVALIDARG, &IID_ICommand); return E_INVALIDARG;".
|
||
|
//
|
||
|
// This override allows for posting two error records in the case
|
||
|
// where the SCODE is converted into a less informative error code
|
||
|
// such as E_FAIL.
|
||
|
//
|
||
|
// History: 01-04-97 DanLeg Created
|
||
|
//----------------------------------------------------------------------------
|
||
|
|
||
|
HRESULT CCIOleDBError::PostHResult(CException &e, const IID & refiid)
|
||
|
{
|
||
|
SCODE sc = e.GetErrorCode();
|
||
|
SCODE scOLE = GetOleError(e);
|
||
|
|
||
|
if ( sc != scOLE )
|
||
|
{
|
||
|
PostHResult( sc, refiid );
|
||
|
sc = scOLE;
|
||
|
}
|
||
|
|
||
|
PostHResult( sc, refiid );
|
||
|
|
||
|
return sc;
|
||
|
}
|
||
|
|
||
|
//+---------------------------------------------------------------------------
|
||
|
//
|
||
|
// Member: CCIOleDBError::PostParserError, public
|
||
|
//
|
||
|
// Synopsis: This method is used to post static strings and DISPPARAMS to
|
||
|
// the error objects. The static strings are stored in the resource
|
||
|
// fork, and thus an id needs to be specified. This method receives
|
||
|
// in dwIds Monarch's error ids. Needs to change them to our
|
||
|
// resource ids (dwIdPostError). dwIdPost error is marked with flag
|
||
|
// (ERR_MONARCH_STATIC), so that GetErrorDescription may take the
|
||
|
// proper parameters.
|
||
|
//
|
||
|
// NOTE: If the error object is not our implementation of IID_IErrorInfo,
|
||
|
// we will not be able to load IErrorRecord and add our records.
|
||
|
//
|
||
|
// Arguments: [hrErr] - HRESULT to associate
|
||
|
// [dwIds] - string ID
|
||
|
// [ppdispparams] - dispatch params
|
||
|
//
|
||
|
// Returns: HResult indicating status
|
||
|
// S_OK | Success
|
||
|
// E_FAIL | OLE DB Error service object missing
|
||
|
//
|
||
|
//
|
||
|
// History: 11-03-97 danleg Created from Monarch
|
||
|
//----------------------------------------------------------------------------
|
||
|
|
||
|
HRESULT CCIOleDBError::PostParserError
|
||
|
(
|
||
|
HRESULT hrErr, //@parm IN | HResult to associate
|
||
|
DWORD dwIds, //@parm IN | String id
|
||
|
DISPPARAMS **ppdispparams //@parm IN/OUT | Dispatch Params
|
||
|
)
|
||
|
{
|
||
|
SCODE sc = S_OK;
|
||
|
DWORD dwIdPostError;
|
||
|
|
||
|
// Translation array from MONSQL values to IDS values
|
||
|
static const UINT s_rgTranslate[] = {
|
||
|
IDS_MON_PARSE_ERR_2_PARAM, // MONSQL_PARSE_ERROR w/ 2 parameter
|
||
|
IDS_MON_PARSE_ERR_1_PARAM, // MONSQL_PARSE_ERROR w/ 1 parameter
|
||
|
IDS_MON_ILLEGAL_PASSTHROUGH, // MONSQL_CITEXTTOSELECTTREE_FAILED
|
||
|
IDS_MON_DEFAULT_ERROR, // MONSQL_PARSE_STACK_OVERFLOW
|
||
|
IDS_MON_DEFAULT_ERROR, // MONSQL_CANNOT_BACKUP_PARSER
|
||
|
IDS_MON_SEMI_COLON, // MONSQL_SEMI_COLON
|
||
|
IDS_MON_ORDINAL_OUT_OF_RANGE, // MONSQL_ORDINAL_OUT_OF_RANGE
|
||
|
IDS_MON_VIEW_NOT_DEFINED, // MONSQL_VIEW_NOT_DEFINED
|
||
|
IDS_MON_BUILTIN_VIEW, // MONSQL_BUILTIN_VIEW
|
||
|
IDS_MON_COLUMN_NOT_DEFINED, // MONSQL_COLUMN_NOT_DEFINED
|
||
|
IDS_MON_OUT_OF_MEMORY, // MONSQL_OUT_OF_MEMORY
|
||
|
IDS_MON_SELECT_STAR, // MONSQL_SELECT_STAR
|
||
|
IDS_MON_OR_NOT, // MONSQL_OR_NOT
|
||
|
IDS_MON_CANNOT_CONVERT, // MONSQL_CANNOT_CONVERT
|
||
|
IDS_MON_OUT_OF_RANGE, // MONSQL_OUT_OF_RANGE
|
||
|
IDS_MON_RELATIVE_INTERVAL, // MONSQL_RELATIVE_INTERVAL
|
||
|
IDS_MON_NOT_COLUMN_OF_VIEW, // MONSQL_NOT_COLUMN_OF_VIEW
|
||
|
IDS_MON_BUILTIN_PROPERTY, // MONSQL_BUILTIN_PROPERTY
|
||
|
IDS_MON_WEIGHT_OUT_OF_RANGE, // MONSQL_WEIGHT_OUT_OF_RANGE
|
||
|
IDS_MON_MATCH_STRING, // MONSQL_MATCH_STRING
|
||
|
IDS_MON_PROPERTY_NAME_IN_VIEW, // MONSQL_PROPERTY_NAME_IN_VIEW
|
||
|
IDS_MON_VIEW_ALREADY_DEFINED, // MONSQL_VIEW_ALREADY_DEFINED
|
||
|
IDS_MON_INVALID_CATALOG, // MONSQL_INVALID_CATALOG
|
||
|
};
|
||
|
|
||
|
|
||
|
Win4Assert( ppdispparams );
|
||
|
|
||
|
// special fixup for MONSQL_PARSE_ERROR
|
||
|
if ( dwIds == MONSQL_PARSE_ERROR )
|
||
|
{
|
||
|
Win4Assert( *ppdispparams && (((*ppdispparams)->cArgs == 1) || ((*ppdispparams)->cArgs == 2)) );
|
||
|
|
||
|
if ( ((*ppdispparams) != NULL) &&
|
||
|
((*ppdispparams)->cArgs == 2) )
|
||
|
{
|
||
|
dwIds = 0; //Change to point to index of 2 parameter parse error
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( dwIds < NUMELEM( s_rgTranslate ) )
|
||
|
dwIdPostError = s_rgTranslate[dwIds];
|
||
|
else
|
||
|
dwIdPostError= IDS_MON_DEFAULT_ERROR;
|
||
|
|
||
|
sc = PostError(hrErr, IID_ICommandText, dwIdPostError, *ppdispparams);
|
||
|
|
||
|
// free dispparams in case of error
|
||
|
if ( (*ppdispparams) != NULL )
|
||
|
{
|
||
|
if ( ((*ppdispparams)->cArgs > 0) &&
|
||
|
((*ppdispparams)->rgvarg!= NULL) )
|
||
|
{
|
||
|
for (ULONG ul=0; ul<(*ppdispparams)->cArgs; ul++)
|
||
|
VariantClear(&((*ppdispparams)->rgvarg[ul]));
|
||
|
|
||
|
CoTaskMemFree(((*ppdispparams)->rgvarg));
|
||
|
(*ppdispparams)->rgvarg= NULL;
|
||
|
(*ppdispparams)->cArgs = 0;
|
||
|
};
|
||
|
|
||
|
CoTaskMemFree((*ppdispparams));
|
||
|
*ppdispparams = NULL;
|
||
|
}
|
||
|
|
||
|
return sc;
|
||
|
}
|
||
|
|
||
|
|
||
|
//+---------------------------------------------------------------------------
|
||
|
//
|
||
|
// Member: CCIOleDBError::PostError, public
|
||
|
//
|
||
|
// Synopsis: This method is used to post static strings to the error objects.
|
||
|
// The static strings are stored in the resource fork, and thus an
|
||
|
// id needs to be specified.
|
||
|
//
|
||
|
// @devnote If the error object is not our implementation of
|
||
|
// IID_IErrorInfo, we will not be able to load IErrorRecord and add
|
||
|
// our records.
|
||
|
//
|
||
|
//
|
||
|
// Arguments: [hrErr] - HRESULT to associate
|
||
|
// [refiid] - IID of interface with error.
|
||
|
// [dwIds] - String id
|
||
|
// [pdispparams] - Parameters for the static string
|
||
|
//
|
||
|
// Returns: HResult indicating status
|
||
|
// S_OK | Success
|
||
|
// E_FAIL | OLE DB Error service object missing
|
||
|
//
|
||
|
//
|
||
|
// History: 11-03-97 danleg Created from Monarch
|
||
|
//----------------------------------------------------------------------------//-----------------------------------------------------------------------------
|
||
|
//
|
||
|
// @mfunc
|
||
|
// @rdesc HResult indicating status
|
||
|
// @flags S_OK | Success
|
||
|
// @flags E_FAIL | OLE DB Error service object missing
|
||
|
//
|
||
|
HRESULT CCIOleDBError::PostError
|
||
|
(
|
||
|
HRESULT hrErr,
|
||
|
const IID & refiid,
|
||
|
DWORD dwIds,
|
||
|
DISPPARAMS* pdispparams
|
||
|
)
|
||
|
{
|
||
|
SCODE sc = S_OK;
|
||
|
ERRORINFO ErrorInfo;
|
||
|
IErrorInfo* pIErrorInfo = NULL;
|
||
|
IErrorRecords* pIErrorRecords = NULL;
|
||
|
|
||
|
// Obtain the error object or create a new one if none exists
|
||
|
sc = GetErrorInterfaces( &pIErrorInfo, &pIErrorRecords );
|
||
|
if ( FAILED(sc) )
|
||
|
goto EXIT_PROCESS_ERRORS;
|
||
|
|
||
|
// Assign static information across each error record added
|
||
|
ErrorInfo.clsid = CLSID_CI_PROVIDER;
|
||
|
ErrorInfo.hrError = hrErr;
|
||
|
ErrorInfo.iid = refiid;
|
||
|
ErrorInfo.dispid = NULL;
|
||
|
ErrorInfo.dwMinor = 0;
|
||
|
|
||
|
// Add the record to the Error Service Object
|
||
|
sc = pIErrorRecords->AddErrorRecord( &ErrorInfo,
|
||
|
dwIds,
|
||
|
pdispparams,
|
||
|
NULL,
|
||
|
0 );
|
||
|
if ( FAILED(sc) )
|
||
|
goto EXIT_PROCESS_ERRORS;
|
||
|
|
||
|
// Pass the error object to the Ole Automation DLL
|
||
|
sc = SetErrorInfo(0, pIErrorInfo);
|
||
|
|
||
|
// Release the interfaces to transfer ownership to
|
||
|
// the Ole Automation DLL
|
||
|
EXIT_PROCESS_ERRORS:
|
||
|
if ( pIErrorRecords )
|
||
|
pIErrorRecords->Release();
|
||
|
if ( pIErrorInfo )
|
||
|
pIErrorInfo->Release();
|
||
|
return sc;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
//
|
||
|
// Member: CCIOleDBError::NeedToSetError - private
|
||
|
//
|
||
|
// Synopsis: Determine if error needs to be set.
|
||
|
//
|
||
|
// Arguments: [scError] - Error code to look for
|
||
|
//
|
||
|
// Returns: TRUE if the error needs to be set. FALSE, if it already
|
||
|
// exists and has a valid description string.
|
||
|
//
|
||
|
// Notes:
|
||
|
//
|
||
|
// History: 15 Jan 1998 KrishnaN Created
|
||
|
// 03-01-98 danleg adopted from ixsso with few changes
|
||
|
//
|
||
|
//-----------------------------------------------------------------------------
|
||
|
|
||
|
BOOL CCIOleDBError::NeedToSetError
|
||
|
(
|
||
|
SCODE scError,
|
||
|
IErrorInfo * pErrorInfo,
|
||
|
IErrorRecords * pErrorRecords
|
||
|
)
|
||
|
{
|
||
|
BOOL fFound = FALSE;
|
||
|
|
||
|
if ( 0 == pErrorInfo )
|
||
|
return TRUE;
|
||
|
|
||
|
XBStr xDescription;
|
||
|
BSTR pDescription = xDescription.GetPointer();
|
||
|
|
||
|
if (0 == pErrorRecords)
|
||
|
{
|
||
|
// No error records. Do we at least have the top level description set?
|
||
|
// If so, that indicates an automation client called SetErrorInfo before us
|
||
|
// and we should not overwrite them.
|
||
|
pErrorInfo->GetDescription(&pDescription);
|
||
|
fFound = (BOOL)(pDescription != 0);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
ULONG cErrRecords;
|
||
|
SCODE sc = pErrorRecords->GetRecordCount(&cErrRecords);
|
||
|
Win4Assert(!fFound);
|
||
|
|
||
|
// look for the target error code. stop when one is found
|
||
|
ERRORINFO ErrorInfo;
|
||
|
for (ULONG i = 0; i < cErrRecords; i++)
|
||
|
{
|
||
|
sc = pErrorRecords->GetBasicErrorInfo(i, &ErrorInfo);
|
||
|
Win4Assert(S_OK == sc);
|
||
|
|
||
|
if (scError == ErrorInfo.hrError)
|
||
|
{
|
||
|
pErrorInfo->GetDescription(&pDescription);
|
||
|
fFound = (BOOL)(pDescription != 0);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!fFound)
|
||
|
return TRUE;
|
||
|
|
||
|
// we found the error code and it has a description.
|
||
|
// no need to set this error again, but we have to
|
||
|
// put this error info back so the client can find it.
|
||
|
SetErrorInfo(0, pErrorInfo);
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
//+---------------------------------------------------------------------------
|
||
|
//
|
||
|
// Member: CCIOleDBError::_GetErrorClassFact, private
|
||
|
//
|
||
|
// Synopsis: Initializes error class factory.
|
||
|
//
|
||
|
// Returns: Success code.
|
||
|
//
|
||
|
// History: 28-Apr-97 KrishnaN Created
|
||
|
//----------------------------------------------------------------------------
|
||
|
|
||
|
SCODE CCIOleDBError::_GetErrorClassFact()
|
||
|
{
|
||
|
SCODE sc = S_OK;
|
||
|
|
||
|
CLock lck( _mutex );
|
||
|
//
|
||
|
// If we have failed once, we should not be
|
||
|
// attempting again. No point in doing that.
|
||
|
//
|
||
|
|
||
|
if ( 0 == _pErrClassFact )
|
||
|
{
|
||
|
//
|
||
|
// We don't have an error class factory.
|
||
|
//
|
||
|
|
||
|
sc = CoGetClassObject(CLSID_EXTENDEDERRORINFO,
|
||
|
CLSCTX_INPROC_SERVER,
|
||
|
NULL,
|
||
|
IID_IClassFactory,
|
||
|
(void **) &_pErrClassFact);
|
||
|
|
||
|
if (FAILED(sc))
|
||
|
{
|
||
|
vqDebugOut((DEB_ITRACE, "No class factory is available "
|
||
|
" for CLSID_EXTENDEDERROR.\n"));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return sc;
|
||
|
}
|
||
|
|
||
|
//+---------------------------------------------------------------------------
|
||
|
//
|
||
|
// Member: CErrorLookup::QueryInterface, public
|
||
|
//
|
||
|
// Synopsis: Supports IID_IUnknown and IID_IErrorLookup
|
||
|
//
|
||
|
// History: 28-Apr-97 KrishnaN Created
|
||
|
// 01-30-98 danleg E_INVALIDARG if ppvObject is bad
|
||
|
//----------------------------------------------------------------------------
|
||
|
|
||
|
STDMETHODIMP CErrorLookup::QueryInterface(REFIID riid, void **ppvObject)
|
||
|
{
|
||
|
if ( !ppvObject )
|
||
|
return E_INVALIDARG;
|
||
|
|
||
|
if (IID_IUnknown == riid)
|
||
|
{
|
||
|
*ppvObject = (void *)((IUnknown *)this);
|
||
|
AddRef();
|
||
|
return S_OK;
|
||
|
}
|
||
|
else if (IID_IErrorLookup == riid)
|
||
|
{
|
||
|
*ppvObject = (void *)((IErrorLookup *)this);
|
||
|
AddRef();
|
||
|
return S_OK;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
*ppvObject = 0;
|
||
|
return E_NOINTERFACE;
|
||
|
}
|
||
|
|
||
|
} //QueryInterface
|
||
|
|
||
|
//+---------------------------------------------------------------------------
|
||
|
//
|
||
|
// Member: CErrorLookup::AddRef, public
|
||
|
//
|
||
|
// History: 17-Mar-97 KrishnaN Created
|
||
|
//
|
||
|
//----------------------------------------------------------------------------
|
||
|
|
||
|
STDMETHODIMP_(ULONG) CErrorLookup::AddRef()
|
||
|
{
|
||
|
InterlockedIncrement(&_cRefs);
|
||
|
|
||
|
return _cRefs;
|
||
|
} //AddRef
|
||
|
|
||
|
//+---------------------------------------------------------------------------
|
||
|
//
|
||
|
// Member: CErrorLookup::Release, public
|
||
|
//
|
||
|
// History: 17-Mar-97 KrishnaN Created
|
||
|
//
|
||
|
//----------------------------------------------------------------------------
|
||
|
|
||
|
STDMETHODIMP_(ULONG) CErrorLookup::Release()
|
||
|
{
|
||
|
Win4Assert(_cRefs > 0);
|
||
|
LONG refCount = InterlockedDecrement(&_cRefs);
|
||
|
|
||
|
if ( refCount <= 0 )
|
||
|
delete this;
|
||
|
|
||
|
return refCount;
|
||
|
|
||
|
} //Release
|
||
|
|
||
|
// IErrorLookup methods
|
||
|
|
||
|
//+---------------------------------------------------------------------------
|
||
|
//
|
||
|
// Member: CErrorLookup::GetErrorDescription, public
|
||
|
//
|
||
|
// Synopsis: Composes the error description for the specifed error.
|
||
|
//
|
||
|
// Arguments: [hrError] - Code returned by the method that caused
|
||
|
// the error.
|
||
|
// [dwLookupId] - Provider-specific number of the error.
|
||
|
// [pdispparams] - Params of the error. If there are no
|
||
|
// params, this is a NULL pointer.
|
||
|
// [lcid] - Locale ID for which to return the
|
||
|
// description and the sources.
|
||
|
// [pbstrSource] - Pointer to memory in which to return a
|
||
|
// pointer to the name of the component
|
||
|
// that generated the error.
|
||
|
// [pbstrDescription]- Pointer to memory in which to return a
|
||
|
// string that describes the error.
|
||
|
//
|
||
|
// History: 28-Apr-97 KrishnaN Created
|
||
|
//----------------------------------------------------------------------------
|
||
|
|
||
|
STDMETHODIMP CErrorLookup::GetErrorDescription (HRESULT hrError,
|
||
|
DWORD dwLookupId,
|
||
|
DISPPARAMS* pdispparams,
|
||
|
LCID lcid,
|
||
|
BSTR* pbstrSource,
|
||
|
BSTR* pbstrDescription)
|
||
|
{
|
||
|
SCODE sc = S_OK;
|
||
|
|
||
|
// Check the Arguments
|
||
|
if( 0 == pbstrSource || 0 == pbstrDescription )
|
||
|
return E_INVALIDARG;
|
||
|
|
||
|
*pbstrSource = *pbstrDescription = 0;
|
||
|
|
||
|
TRANSLATE_EXCEPTIONS;
|
||
|
TRY
|
||
|
{
|
||
|
//
|
||
|
// If we encounter IDENTIFIER_SDK_ERROR, make sure we return S_OK;
|
||
|
//
|
||
|
BOOL fGetDescription = (IDENTIFIER_SDK_ERROR != dwLookupId);
|
||
|
BOOL fGetSource = TRUE;
|
||
|
|
||
|
XBStr xbstrDescription;
|
||
|
XBStr xbstrSource;
|
||
|
|
||
|
|
||
|
// We only support lookup of CI generated errors and those handled
|
||
|
// by the default error lookup service!
|
||
|
|
||
|
if ( (IDENTIFIER_SDK_ERROR != dwLookupId) && !IsCIError(hrError) )
|
||
|
{
|
||
|
if( IsParserError(dwLookupId) )
|
||
|
{
|
||
|
hrError = dwLookupId;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
fGetDescription = fGetSource = FALSE;
|
||
|
sc = DB_E_BADHRESULT;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (fGetSource)
|
||
|
{
|
||
|
|
||
|
// Fix for bug# 83593: Set source string even when the default
|
||
|
// lookup service is providing the description
|
||
|
|
||
|
xbstrSource.SetText( L"Microsoft OLE DB Provider for Indexing Service" );
|
||
|
}
|
||
|
|
||
|
|
||
|
if (fGetDescription)
|
||
|
{
|
||
|
DWORD_PTR rgdwArguments[2];
|
||
|
DWORD dwFlags = FORMAT_MESSAGE_FROM_HMODULE;
|
||
|
|
||
|
if (pdispparams)
|
||
|
{
|
||
|
dwFlags |= FORMAT_MESSAGE_ARGUMENT_ARRAY;
|
||
|
Win4Assert(pdispparams->cArgs == 2 || pdispparams->cArgs == 1 || pdispparams->cArgs == 0);
|
||
|
for (UINT c=0; c < pdispparams->cArgs; c++)
|
||
|
{
|
||
|
rgdwArguments[c] = (DWORD_PTR)(LPWSTR)pdispparams->rgvarg[c].bstrVal;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
RtlZeroMemory( rgdwArguments, sizeof(rgdwArguments) );
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Load the error string from the appropriate DLL
|
||
|
//
|
||
|
|
||
|
WCHAR wszBuffer[ERROR_MESSAGE_SIZE];
|
||
|
|
||
|
//
|
||
|
// Don't pass a specific lang id to FormatMessage since it will
|
||
|
// fail if there's no message in that language. Instead set
|
||
|
// the thread locale, which will get FormatMessage to use a search
|
||
|
// algorithm to find a message of the appropriate language or
|
||
|
// use a reasonable fallback msg if there's none.
|
||
|
//
|
||
|
|
||
|
LCID SaveLCID = GetThreadLocale();
|
||
|
SetThreadLocale(lcid);
|
||
|
|
||
|
// CLEANCODE: Since we could have differently named dlls (query.dll
|
||
|
// or oquery.dll) we should be able to look up in the registry
|
||
|
// and determine which one to get. Or just get the module name.
|
||
|
|
||
|
// All messages are in querymsg.mc, which is in query.dll.
|
||
|
|
||
|
HMODULE hModule = GetModuleHandle(L"query.dll");
|
||
|
|
||
|
if (! FormatMessage( dwFlags | FORMAT_MESSAGE_MAX_WIDTH_MASK,
|
||
|
hModule,
|
||
|
hrError,
|
||
|
0,
|
||
|
wszBuffer,
|
||
|
ERROR_MESSAGE_SIZE,
|
||
|
(va_list*) rgdwArguments ) )
|
||
|
{
|
||
|
vqDebugOut(( DEB_ERROR, "Format message failed with error 0x%x\n", GetLastError() ));
|
||
|
|
||
|
swprintf( wszBuffer,
|
||
|
L"Unable to format message for error 0x%X caught in Indexing Service.\n",
|
||
|
hrError );
|
||
|
}
|
||
|
|
||
|
SetThreadLocale(SaveLCID);
|
||
|
|
||
|
//
|
||
|
// Convert the loaded string to a BSTR
|
||
|
//
|
||
|
|
||
|
xbstrDescription.SetText(wszBuffer);
|
||
|
}
|
||
|
|
||
|
*pbstrSource = xbstrSource.GetPointer();
|
||
|
*pbstrDescription = xbstrDescription.GetPointer();
|
||
|
|
||
|
xbstrSource.Acquire();
|
||
|
xbstrDescription.Acquire();
|
||
|
}
|
||
|
CATCH( CException, e )
|
||
|
{
|
||
|
vqDebugOut(( DEB_ERROR, "Exception %08x in CCIOleDBError::GetErrorDescription \n",
|
||
|
e.GetErrorCode() ));
|
||
|
sc = GetOleError(e);
|
||
|
}
|
||
|
END_CATCH
|
||
|
UNTRANSLATE_EXCEPTIONS;
|
||
|
|
||
|
return sc;
|
||
|
}
|
||
|
|
||
|
//+---------------------------------------------------------------------------
|
||
|
//
|
||
|
// Member: CErrorLookup::GetHelpInfo, public
|
||
|
//
|
||
|
// Synopsis: Composes the error description for the specifed error.
|
||
|
//
|
||
|
// Arguments: [hrError] - Code returned by the method that caused
|
||
|
// the error.
|
||
|
// [dwLookupId] - Provider-specific number of the error.
|
||
|
// [lcid] - Locale Id for which to return the Help
|
||
|
// file path and Context ID.
|
||
|
// [pbstrHelpFile] - Pointer to memory in which to return a
|
||
|
// pointer the fully path of the Help file.
|
||
|
//
|
||
|
// [pdwHelpContext]- Pointer to memory in which to return the
|
||
|
// Help Context ID for the error.
|
||
|
//
|
||
|
// History: 28-Apr-97 KrishnaN Created
|
||
|
//----------------------------------------------------------------------------
|
||
|
|
||
|
STDMETHODIMP CErrorLookup::GetHelpInfo (HRESULT hrError,
|
||
|
DWORD dwLookupId,
|
||
|
LCID lcid,
|
||
|
BSTR* pbstrHelpFile,
|
||
|
DWORD* pdwHelpContext)
|
||
|
{
|
||
|
if ( 0 == pbstrHelpFile || 0 == pdwHelpContext )
|
||
|
return E_INVALIDARG;
|
||
|
|
||
|
*pbstrHelpFile = 0;
|
||
|
*pdwHelpContext = 0;
|
||
|
|
||
|
//
|
||
|
// Currently we do not return any help file
|
||
|
// context or names, so we will just return S_OK
|
||
|
//
|
||
|
|
||
|
// NEWFEATURE: We can, if we choose to, return help file
|
||
|
// and context for the query project.
|
||
|
|
||
|
if ( lcid != GetUserDefaultLCID() )
|
||
|
return DB_E_NOLOCALE;
|
||
|
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
//+---------------------------------------------------------------------------
|
||
|
//
|
||
|
// Member: CErrorLookup::ReleaseErrors, public
|
||
|
//
|
||
|
// Synopsis: Releases dynamic errors.
|
||
|
//
|
||
|
// Arguments: [dwDynamicErrorId] - ID of the dynamic error info to release.
|
||
|
//
|
||
|
// History: 28-Apr-97 KrishnaN Created
|
||
|
//----------------------------------------------------------------------------
|
||
|
|
||
|
STDMETHODIMP CErrorLookup::ReleaseErrors (const DWORD dwDynamicErrorId)
|
||
|
{
|
||
|
Win4Assert(!"Currently we don't support dynamic errors.");
|
||
|
|
||
|
if (0 == dwDynamicErrorId)
|
||
|
return E_INVALIDARG;
|
||
|
|
||
|
//
|
||
|
// We don't support dynamic errors, so nothing to do.
|
||
|
//
|
||
|
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
//+-------------------------------------------------------------------------
|
||
|
//
|
||
|
// Method: CErrorLookupCF::CErrorLookupCF, public
|
||
|
//
|
||
|
// Synopsis: CErrorLookup class factory constructor
|
||
|
//
|
||
|
// History: 25-Mar-1997 KrishnaN Created
|
||
|
//
|
||
|
//--------------------------------------------------------------------------
|
||
|
|
||
|
CErrorLookupCF::CErrorLookupCF()
|
||
|
: _cRefs( 1 )
|
||
|
{
|
||
|
InterlockedIncrement( &gulcInstances );
|
||
|
}
|
||
|
|
||
|
//+-------------------------------------------------------------------------
|
||
|
//
|
||
|
// Method: CErrorLookupCF::~CErrorLookupCF
|
||
|
//
|
||
|
// Synopsis: Text IFilter class factory constructor
|
||
|
//
|
||
|
// History: 25-Mar-1997 KrishnaN Created
|
||
|
//
|
||
|
//--------------------------------------------------------------------------
|
||
|
|
||
|
CErrorLookupCF::~CErrorLookupCF()
|
||
|
{
|
||
|
InterlockedDecrement( &gulcInstances );
|
||
|
}
|
||
|
|
||
|
//+-------------------------------------------------------------------------
|
||
|
//
|
||
|
// Method: CErrorLookupCF::QueryInterface, public
|
||
|
//
|
||
|
// Synopsis: Rebind to other interface
|
||
|
//
|
||
|
// Arguments: [riid] -- IID of new interface
|
||
|
// [ppvObject] -- New interface * returned here
|
||
|
//
|
||
|
// Returns: S_OK if bind succeeded, E_NOINTERFACE if bind failed
|
||
|
//
|
||
|
// History: 25-Mar-1997 KrishnaN Created
|
||
|
// 01-31-98 danleg E_INVALIDARG for bad ppvObject
|
||
|
//
|
||
|
//--------------------------------------------------------------------------
|
||
|
|
||
|
SCODE STDMETHODCALLTYPE CErrorLookupCF::QueryInterface( REFIID riid,
|
||
|
void ** ppvObject )
|
||
|
{
|
||
|
if ( 0 == ppvObject )
|
||
|
return E_INVALIDARG;
|
||
|
|
||
|
*ppvObject = 0;
|
||
|
|
||
|
SCODE sc = S_OK;
|
||
|
|
||
|
if ( IID_IClassFactory == riid )
|
||
|
*ppvObject = (IUnknown *)(IClassFactory *)this;
|
||
|
else if ( IID_IUnknown == riid )
|
||
|
*ppvObject = (IUnknown *)this;
|
||
|
else if ( IID_ITypeLib == riid )
|
||
|
sc = E_NOINTERFACE;
|
||
|
else
|
||
|
sc = E_NOINTERFACE;
|
||
|
|
||
|
if ( SUCCEEDED( sc ) )
|
||
|
AddRef();
|
||
|
|
||
|
return sc;
|
||
|
}
|
||
|
|
||
|
//+-------------------------------------------------------------------------
|
||
|
//
|
||
|
// Method: CErrorLookupCF::AddRef, public
|
||
|
//
|
||
|
// Synopsis: Increments refcount
|
||
|
//
|
||
|
// History: 25-Mar-1997 KrishnaN Created
|
||
|
//
|
||
|
//--------------------------------------------------------------------------
|
||
|
|
||
|
ULONG STDMETHODCALLTYPE CErrorLookupCF::AddRef()
|
||
|
{
|
||
|
return InterlockedIncrement( &_cRefs );
|
||
|
}
|
||
|
|
||
|
//+-------------------------------------------------------------------------
|
||
|
//
|
||
|
// Method: CErrorLookupCF::Release, public
|
||
|
//
|
||
|
// Synopsis: Decrement refcount. Delete if necessary.
|
||
|
//
|
||
|
// History: 25-Mar-1997 KrishnaN Created
|
||
|
//
|
||
|
//--------------------------------------------------------------------------
|
||
|
|
||
|
ULONG STDMETHODCALLTYPE CErrorLookupCF::Release()
|
||
|
{
|
||
|
unsigned long uTmp = InterlockedDecrement( &_cRefs );
|
||
|
|
||
|
if ( 0 == uTmp )
|
||
|
delete this;
|
||
|
|
||
|
return(uTmp);
|
||
|
}
|
||
|
|
||
|
|
||
|
//+-------------------------------------------------------------------------
|
||
|
//
|
||
|
// Method: CErrorLookupCF::CreateInstance, public
|
||
|
//
|
||
|
// Synopsis: Creates new CIndexer object
|
||
|
//
|
||
|
// Arguments: [pUnkOuter] -- 'Outer' IUnknown
|
||
|
// [riid] -- Interface to bind
|
||
|
// [ppvObject] -- Interface returned here
|
||
|
//
|
||
|
// History: 25-Mar-1997 KrishnaN Created
|
||
|
//
|
||
|
//--------------------------------------------------------------------------
|
||
|
|
||
|
SCODE STDMETHODCALLTYPE CErrorLookupCF::CreateInstance( IUnknown * pUnkOuter,
|
||
|
REFIID riid,
|
||
|
void * * ppvObject )
|
||
|
{
|
||
|
CErrorLookup * pIUnk = 0;
|
||
|
SCODE sc = S_OK;
|
||
|
|
||
|
TRY
|
||
|
{
|
||
|
pIUnk = new CErrorLookup();
|
||
|
sc = pIUnk->QueryInterface( riid , ppvObject );
|
||
|
|
||
|
pIUnk->Release(); // Release extra refcount from QueryInterface
|
||
|
}
|
||
|
CATCH( CException, e )
|
||
|
{
|
||
|
Win4Assert( 0 == pIUnk );
|
||
|
sc = GetOleError(e);
|
||
|
}
|
||
|
END_CATCH
|
||
|
|
||
|
return (sc);
|
||
|
}
|
||
|
|
||
|
//+-------------------------------------------------------------------------
|
||
|
//
|
||
|
// Method: CErrorLookupCF::LockServer, public
|
||
|
//
|
||
|
// Synopsis: Force class factory to remain loaded
|
||
|
//
|
||
|
// Arguments: [fLock] -- TRUE if locking, FALSE if unlocking
|
||
|
//
|
||
|
// Returns: S_OK
|
||
|
//
|
||
|
// History: 25-Mar-1997 KrishnaN Created
|
||
|
//
|
||
|
//--------------------------------------------------------------------------
|
||
|
|
||
|
SCODE STDMETHODCALLTYPE CErrorLookupCF::LockServer(BOOL fLock)
|
||
|
{
|
||
|
if(fLock)
|
||
|
InterlockedIncrement( &gulcInstances );
|
||
|
else
|
||
|
InterlockedDecrement( &gulcInstances );
|
||
|
|
||
|
return(S_OK);
|
||
|
}
|
||
|
|