{ +-------------------------------------------------------------+ }
{ |                                                             | }
{ |   GM-Software                                               | }
{ |   ===========                                               | }
{ |                                                             | }
{ |   Project: All Projects                                     | }
{ |                                                             | }
{ |   Description: Simple SOAP implementations.                 | }
{ |                                                             | }
{ |                                                             | }
{ |   Copyright (C) - 2010 - Gerrit Moeller.                    | }
{ |                                                             | }
{ |   Source code distributed under MIT license.                | }
{ |                                                             | }
{ |   See: https://www.gm-software.de                           | }
{ |                                                             | }
{ +-------------------------------------------------------------+ }

{$INCLUDE GMCompilerSettings.inc}

unit GMSoap;

interface

uses {$IFDEF JEDIAPI}jwaWinType,{$ELSE}Windows,{$ENDIF}
     GMActiveX, GMStrDef, GMCollections, GMIntf, GMCommon, GMXml, GMHttp, TypInfo;

const

  cDfltSoapPropTypeKinds = [tkInteger, tkChar, tkFloat, tkString,
                            tkWChar, tkLString, tkWString, tkVariant, tkInt64];
                            // tkEnumeration, tkSet


type

  //
  // Include RTTI to access properties at runtime
  //
  {$IFOPT M-} {$DEFINE RTTI_OFF} {$M+} {$ENDIF}
  TGMSoapValueCarrierObj = class(TGMRefCountedObj)
   public
    constructor Create(const ARefLifeTime: Boolean = False); reintroduce; virtual;
  end;
  {$IFDEF RTTI_OFF} {$M-} {$UNDEF RTTI_OFF} {$ENDIF}

  TGMSoapDataObjClass = class of TGMSoapValueCarrierObj;


  IGMLoadProperties = interface(IUNknown)
    ['{590F0CE0-A6C1-4796-B870-4BCC0A68B3C2}']
    procedure LoadProperties(Node: IGMXmlNode; const ForceExist: Boolean = True); stdcall;
  end;


  IGMStoreProperties = interface(IUnknown)
    ['{BDBCD58A-8B7E-4a29-8F2D-5AA7B5C3C0B7}']
    procedure StoreProperties(Node: IGMXmlNode); stdcall;
  end;


  EGMSoapException = class(EGMException);

  EGMSoapExceptionClass = class of EGMSoapException;


  TGMSoapMethodData = record
    NameSpace: TGMString;
    MethodName: TGMString;
  end;


  TGMSoapCallBase = class(TGMRefCountedObj)
   protected
    FTransportLayer: ISequentialStream;
    FXmlParseAttributes: TGMXmlParseAttributes;
    FOperationNS: TGMXmlNamedValueData;

    function InsertSoapEnvelope(const AParentNode: IGMXmlNode; const AAddOperationNS: Boolean = True): IGMXmlNode; virtual;
    function InsertSoapBody(const AParentNode: IGMXmlNode): IGMXmlNode; virtual;
    procedure AddEnvelopeAttributes(const AEnvelopeNode: IGMXmlNode; const AAddOperationNS: Boolean = True); virtual;
    function XmlTreeCreateClass: TGMXmlTreeClass; virtual;

   public
    constructor Create(const ATransportLayer: ISequentialStream; const ASoapPortTypeURL: TGMString; const AXmlParseAttributes: TGMXmlParseAttributes = cDfltXmlParseAttributes; const ARefLifeTime: Boolean = True); reintroduce; overload;
  end;


  TGMSoapClientCall = class(TGMSoapCallBase)
   protected
    FTypeNameSpace: TGMString;

    function WrongContentExceptionCreateClass: EGMExceptionClass; virtual;
    function ValueAsSoapString(const AValue: OleVariant): TGMString; virtual;
    function AddTypeAttributes: Boolean; virtual;

    procedure PrepareHttpRequest(const ARequest: IUnknown); virtual;

    function CreateSoapFaultExceptObj(const ASoapFaultNode: IGMXmlNode; const ACallingName: TGMString): EGMSoapException; virtual; abstract;
    procedure RaiseSoapFault(const ASoapFaultNode: IGMXmlNode; const ACallingName: TGMString); virtual;

    procedure CheckResponseXml(const AResponseXml: IGMXmlTree; const ACallingName: TGMString); virtual;
    procedure CheckResponseContentType(const AResponseContent: ISequentialStream; const AContentType, ACallingName: TGMString); virtual;
    function CheckResponseContent(const AResponseContent: ISequentialStream; const AContentType: TGMString; const ACallingName: TGMString): IGMXmlTree; virtual;
    //function ExtractSoapFaultMessage(const AResponseContent: IStream; var AXml: IGMXmlTree): TGMString;
    procedure AddEnvelopeAttributes(const AEnvelopeNode: IGMXmlNode; const AAddOperationNS: Boolean = True); override;

   public
//  constructor Create(const ATransportLayer: ISequentialStream; const ASoapPortTypeURL: TGMString; const AXmlParseAttributes: TGMXmlParseAttributes = cDfltXmlParseAttributes; const ARefLifeTime: Boolean = True); reintroduce;
    constructor Create(const ARefLifeTime: Boolean = True); override;
    function CreateSOAPCallXml(const ASoapMethodName: TGMString; var ANode: IGMXmlNode): IGMXmlTree;
    function AddSoapValue(const AParentNode: IGMXmlNode; const AName: TGMString; const AValue: OleVariant): IGMXmlNode;
    procedure StoreObjProps(const Obj: TObject; Node: IGMXmlNode; const ClassNodeName: TGMString; const TypeKinds: TTypeKinds);
    procedure LoadObjProps(const Obj: TObject; Node: IGMXmlNode; const ForceExist: Boolean; const TypeKinds: TTypeKinds);

    function ExecSoapCall(const ASession: IGMHttpClientSession; const AXML: IGMXmlTree; const ACallingName: TGMString = ''): IGMXmlTree; virtual;
  end;


  TGMSoapServerPort = class;

  IGMSoapServerPort = interface(IUnknown)
    ['{083F2619-5846-461F-B27A-5F78FD34D6B7}']
    function Obj: TGMSoapServerPort;
  end;


  TGMSoapMethod = procedure (const ARequestMethodNode, AResponseBodyNode: IGMXmlNode) of object;

  TGMSoapMethodEntry = class(TGMRefCountedObj, IGMGetName)
   public
    SoapMethod: TGMSoapMethod;
    SoapMethodName: TGMString;
    constructor Create(const AMethodName: TGMString; const ASoapMethod: TGMSoapMethod; const ARefLifeTime: Boolean = False); reintroduce;
    function GetName: TGMString; stdcall;
  end;


  TGMSoapRequestResult = record
   HttpStatusCode: DWORD;
   LogMessage: AnsiString;
   ContentType: AnsiString;
   SOAPMethodName: TGMString;
  end;


  TGMSoapServerPort = class(TGMSoapCallBase, IGMSoapServerPort)
   protected
    FRegisteredSoapMethods: IGMObjArrayCollection;
    FSoapPortName: TGMString;

    function DoTracing: Boolean; virtual;
    function TracePrefix: TGMString; virtual;

    function InsertFaultDetailNode(const AFaultNode: IGMXmlNode; const AExceptObject: TObject): TGMString; virtual; abstract;
    function InsertFaultNode(const ABodyNode: IGMXmlNode; const AExceptObject: TObject): TGMString;

    procedure RegisterSOAPMethod(const AMethodName: AnsiString; const AMethod: TGMSoapMethod);
    function FindSoapMethod(const AMethodName: TGMString): TGMSoapMethod; virtual;

   public
    constructor Create(const ARefLifeTime: Boolean = True); overload; override;
    constructor Create(const ATransportLayer: ISequentialStream; const ASoapPortName, ASoapPortTypeURL: TGMString; const AXmlParseAttributes: TGMXmlParseAttributes = cDfltXmlParseAttributes; const ARefLifeTime: Boolean = True); reintroduce; overload;
    function Obj: TGMSoapServerPort;
//  function ProcessRequest(const AReadStrm, AWriteStrm: ISequentialStream): TGMSoapRequestResult;
    function ProcessRequest: TGMSoapRequestResult; virtual;
//  procedure SendResponseContent(const ATransportLayer: ISequentialStream); virtual;
  end;

  TGMSoapServerCallClass = class of TGMSoapServerPort;


// TSMSUnkResultStateType(GMCheckGetEnumValFromName(TypeInfo(TSMSUnkResultStateType), GMCheckFindXmlSubValue(Node, cSoapResultState)));
// GetEnumName(TypeInfo(SMSHelpTopicType), Ord(ATopic))

function GMSoapStrToInt(const AValue: TGMString; const ADefaultValue: LongInt = 0): LongInt; // ; ACaller: TObject = nil; const ACallingName: TGMString = ''
function GMSoapBoolToStr(const AValue: Boolean): TGMString;
//function GMIso8601DateTimeToStr(const ALocalTime: TDateTime): TGMString;

//function GMAddSoapValue(const AParent: IGMXmlNode; const AName, ANameSpace: TGMString; const AValue: OleVariant): IGMXmlNode;
function GMSoapTypeName(const AVType: LongInt): TGMString;

procedure GMStoreObjProps(const Obj: TObject; Node: IGMXmlNode;
                          const ClassNodeName: TGMString = '';
                          const TypeKinds: TTypeKinds = cDfltSoapPropTypeKinds);


procedure GMLoadObjProps(const AObj: TObject; ANode: IGMXmlNode; const AForceExist: Boolean = True; const ATypeKinds: TTypeKinds = cDfltSoapPropTypeKinds);


const

  cStrEnvNS = 'SOAP-ENV';
//cStrEncNS = 'SOAP-ENC';

  cStrSoapEnv = 'Envelope';
  cStrSoapHeader = 'Header';
  cStrSoapBody = 'Body';
  cStrSoapFault = 'Fault';
  cStrSoapFaultString = 'faultstring'; // <- should be lowercase!
  cStrSoapFaultCode = 'faultcode';     // <- should be lowercase!
  cStrSoapFaultDetail = 'detail';      // <- should be lowercase!

  cStrExec = 'exec';

  cStrXsd = 'xsd';
  cStrXsi = 'xsi';

  cStrType = 'type';
  //cStrHtmlBody = cStrBody;
  //cStrArray = 'Array';

  cStrSoap = 'SOAP';


implementation

uses Classes, SysUtils, GMWinInetAPI, GMINetBase
     {$IFDEF DELPHI6},Variants{$ENDIF}
     ;


const

  cStrHttpContentHtml = 'text/html';

  
resourcestring

  //RStrNoHttpStatusCode = 'The server did not transfer a http status code';
  RStrNoSoapFaultMsg = 'No SOAP fault message transmitted by the server';
  //RStrResponseContentStream = 'The response content stream';
  RStrResonseContentNotXml = 'The response content is not XML/HTML';
  RStrNoSOAPMethodNode = 'No SOAP method XML node found';
  RStrEmptySoapMethodName = 'Empty Soap method name';
  RStrSoapMethodNotImpl = 'SOAP method "%s" not implemented';
//RStrSOAPCallLogMsg = 'WFM SOAP call';
  RStrException = 'Exception';
  RStrUsingSoapPort = 'Using SOAP port';


  //RStrInvalidIntFmt = 'Invalid integer value: %s';


{ ------------------------- }
{ ---- Global Routines ---- }
{ ------------------------- }

function GMSoapStrToInt(const AValue: TGMString; const ADefaultValue: LongInt): LongInt; // ; ACaller: TObject; const ACallingName: TGMString
begin
  if Length(GMStrip(AValue, cWhiteSpace)) <= 0 then Result := ADefaultValue else Result := GMStrToInt(AValue);
  //Result := GMStrToInt(GMMakeDezInt(AValue));
//  try
//   Result := GMStrToInt(AValue);
//except
// raise EGMException.ObjError(GMFormat(RStrInvalidIntFmt, [AValue]), ACaller, ACallingName);
//end;
end;

//function GMSoapStrToInt(const AValue: TGMString; ACaller: TObject): LongInt;
//begin
//end;

function GMSoapTypeName(const AVType: LongInt): TGMString;
begin
  case VarTYpeMask and AVType of
   varInteger: Result := 'int';
   varDate:    Result := 'dateTime';
   varBoolean: Result := 'boolean';
   else Result := 'TGMString';
  end;
end;

function GMSoapBoolToStr(const AValue: Boolean): TGMString;
begin
  Result := GMBoolToStr(AValue, 'false', 'true');
end;

function GMSoapVarToStr(const AValue: OleVariant): TGMString;
begin
  case VarType(AValue) of
   varBoolean: Result := GMSoapBoolToStr(AValue);
   varDate: Result := GMIso8601DateTimeToStr(AValue);
   else Result := AValue;
  end;
end;

function GMAddSoapValue(const AParent: IGMXmlNode; const AName, ANameSpace: TGMString; const AValue: OleVariant): IGMXmlNode;
begin
  if AParent <> nil then
     Result := AParent.Obj.Owner.CreateNewNode(AParent, AName, AValue, ANameSpace);
end;

procedure GMStoreObjProps(const Obj: TObject; Node: IGMXmlNode; const ClassNodeName: TGMString; const TypeKinds: TTypeKinds);
var Count, i: Integer; PropList: PPropList; PIStore: IGMStoreProperties;
begin
  if (Obj = nil) or (Node = nil) then Exit;
  if Obj.ClassInfo = nil then raise EGMSoapException.ObjError(GMFormat(RStrNeedRTTI, [Obj.ClassName]), Obj, 'GMStoreObjProps');
  {ToDo: Klassentyp f?r XML Knoten?}
  if ClassNodeName <> '' then Node := GMCreateXmlNode(Node, ClassNodeName);
  Count := GetTypeData(PTypeInfo(Obj.ClassInfo))^.PropCount;
  if Count > 0 then
   begin
    GetMem(PropList, Count * SizeOf(Pointer));
    try
     GetPropInfos(PTypeInfo(Obj.ClassInfo), PropList);
     for i:=0 to Count-1 do
      if PropList^[i].PropType^{$IFNDEF FPC}^{$ENDIF}.Kind in TypeKinds then
         GMAddSoapValue(Node, PropList^[i].Name, '', GetPropValue(Obj, PropList^[i].Name, False));
    finally
     FreeMem(PropList);
    end;
   end;
  if Obj.GetInterface(IGMStoreProperties, PIStore) then PIStore.StoreProperties(Node);
end;

procedure GMLoadObjProps(const AObj: TObject; ANode: IGMXmlNode; const AForceExist: Boolean; const ATypeKinds: TTypeKinds);
var Count, i: Integer; PropList: PPropList; ValNode: IGMXmlNode; PILoad: IGMLoadProperties;
begin
  if (AObj = nil) or (ANode = nil) then Exit;
  if AObj.ClassInfo = nil then raise EGMSoapException.ObjError(GMFormat(RStrNeedRTTI, [AObj.ClassName]), AObj, 'GMLoadObjProps');
  Count := GetTypeData(PTypeInfo(AObj.ClassInfo))^.PropCount;
  if Count > 0 then
   begin
    GetMem(PropList, Count * SizeOf(Pointer));
    try
     GetPropInfos(PTypeInfo(AObj.ClassInfo), PropList);
     for i:=0 to Count-1 do
      if PropList^[i].PropType^{$IFNDEF FPC}^{$ENDIF}.Kind in ATypeKinds then
       begin
        if AForceExist then
         ValNode := ANode.Obj.CheckFindSubNode(PropList^[i].Name)
        else
         ValNode := ANode.Obj.FindSubNode(PropList^[i].Name);

        if (ValNode <> nil) and (ValNode.Obj.StrValue <> '') then SetPropValue(AObj, PropList^[i].Name, ValNode.Obj.StrValue)
        else
         case PropList^[i].PropType^.Kind of
          tkChar, tkString, tkWChar, tkLString, tkWString: SetPropValue(AObj, PropList^[i].Name, '');
          tkInteger, tkFloat, tkInt64: SetPropValue(AObj, PropList^[i].Name, 0);
          tkVariant: SetPropValue(AObj, PropList^[i].Name, Null);
          //tkClass, tkInterface: SetPropValue(AObj, PropList^[i].Name, nil);
         end;
       end;
    finally
     FreeMem(PropList);
    end;
   end;
  if AObj.GetInterface(IGMLoadProperties, PILoad) then PILoad.LoadProperties(ANode, AForceExist);
end;


{ -------------------------------- }
{ ---- TGMSoapValueCarrierObj ---- }
{ -------------------------------- }

constructor TGMSoapValueCarrierObj.Create(const ARefLifeTime: Boolean);
begin
  inherited Create(ARefLifeTime);
end;


{ ------------------------- }
{ ---- TGMSoapCallBase ---- }
{ ------------------------- }

constructor TGMSoapCallBase.Create(const ATransportLayer: ISequentialStream; const ASoapPortTypeURL: TGMString; const AXmlParseAttributes: TGMXmlParseAttributes; const ARefLifeTime: Boolean);
begin
  Create(ARefLifeTime);
  FTransportLayer := ATransportLayer;
  FXmlParseAttributes := AXmlParseAttributes;
  FOperationNS := GMXmlNamedValueData(cStrExec, ASoapPortTypeURL);
//FOperationNS.Value := URLExctractResourcePath(ASoapPortTypeURL);
end;

function TGMSoapCallBase.XmlTreeCreateClass: TGMXmlTreeClass;
begin
  Result := TGMXmlTree;
end;

function TGMSoapCallBase.InsertSoapEnvelope(const AParentNode: IGMXmlNode; const AAddOperationNS: Boolean): IGMXmlNode;
begin
  if AParentNode = nil then Exit;
  Result := GMCreateXmlNode(AParentNode, cStrSoapEnv, '', cStrEnvNS);
  AddEnvelopeAttributes(Result, AAddOperationNS);
end;

function TGMSoapCallBase.InsertSoapBody(const AParentNode: IGMXmlNode): IGMXmlNode;
begin
  if AParentNode = nil then Exit;
  Result := GMCreateXmlNode(AParentNode, cStrSoapBody, '', cStrEnvNS);
end;

procedure TGMSoapCallBase.AddEnvelopeAttributes(const AEnvelopeNode: IGMXmlNode; const AAddOperationNS: Boolean);
begin
  if AEnvelopeNode <> nil then
   with AEnvelopeNode.Obj do
    begin
     Attributes.Add(AttributeCreateClass.Create(GMXmlQualifiedName(cStrXmlns, cStrEnvNS), '"http://schemas.xmlsoap.org/soap/envelope/"'));
     if AAddOperationNS and (Length(FOperationNS.Name) > 0) and (Length(FOperationNS.Value) > 0) then
        Attributes.Add(AttributeCreateClass.Create(GMXmlQualifiedName(cStrXmlns, FOperationNS.Name), GMXmlAttrQuote(FOperationNS.Value)));
    end;
end;


{ --------------------------- }
{ ---- TGMSoapClientCall ---- }
{ --------------------------- }

constructor TGMSoapClientCall.Create(const ARefLifeTime: Boolean);
begin
  inherited Create(ARefLifeTime);
  FTypeNameSpace := cStrXsi;
end;

function TGMSoapClientCall.AddTypeAttributes: Boolean;
begin
  Result := Length(FTypeNameSpace) > 0;
end;

function TGMSoapClientCall.WrongContentExceptionCreateClass: EGMExceptionClass;
begin
  Result := EGMSoapException;
end;

function TGMSoapClientCall.ValueAsSoapString(const AValue: OleVariant): TGMString;
begin
  Result := GMSoapVarToStr(AValue);
end;

function TGMSoapClientCall.AddSoapValue(const AParentNode: IGMXmlNode; const AName: TGMString; const AValue: OleVariant): IGMXmlNode;
begin
  if AParentNode = nil then Exit;
  Result := GMCreateXmlNode(AParentNode, AName, ValueAsSoapString(AValue));
  if AddTypeAttributes then
     Result.Obj.Attributes.Add(Result.Obj.AttributeCreateClass.Create(GMXmlQualifiedName(FTypeNameSpace, cStrType), GMXmlAttrQuote(GMXmlQualifiedName(cStrXsd, GMSoapTypeName(VarType(AValue))))));
end;

procedure TGMSoapClientCall.AddEnvelopeAttributes(const AEnvelopeNode: IGMXmlNode; const AAddOperationNS: Boolean);
begin
  inherited;
  if AEnvelopeNode <> nil then
   with AEnvelopeNode.Obj do
    if AddTypeAttributes then Attributes.Add(AttributeCreateClass.Create(GMXmlQualifiedName(cStrXmlns, FTypeNameSpace), '"http://www.w3.org/2001/XMLSchema-instance"'));

// with AEnvelopeNode.Obj do
//  begin
//   //Attributes.Add(AttributeCreateClass.Create(GMXmlQualifiedName(cStrXmlns, cStrEnvNS), '"http://schemas.xmlsoap.org/soap/envelope/"'));
//   if AddTypeAttributes then Attributes.Add(AttributeCreateClass.Create(GMXmlQualifiedName(cStrXmlns, FTypeNameSpace), '"http://www.w3.org/2001/XMLSchema-instance"'));
////   Attributes.Add(AttributeCreateClass.Create(GMXmlQualifiedName(cStrXmlns, cStrXsd), '"http://www.w3.org/2001/XMLSchema"'));
////   Attributes.Add(AttributeCreateClass.Create(GMXmlQualifiedName(cStrXmlns, cStrXsi), '"http://www.w3.org/2001/XMLSchema-instance"'));
////   Attributes.Add(AttributeCreateClass.Create(GMXmlQualifiedName(cStrXmlns, cStrEncNS), '"http://schemas.xmlsoap.org/soap/encoding/"'));
//  end;
end;

procedure TGMSoapClientCall.StoreObjProps(const Obj: TObject; Node: IGMXmlNode; const ClassNodeName: TGMString; const TypeKinds: TTypeKinds);
var Count, i: Integer; PropList: PPropList; PIStore: IGMStoreProperties;
begin
  if (Obj = nil) or (Node = nil) then Exit;
  if Obj.ClassInfo = nil then raise EGMSoapException.ObjError(GMFormat(RStrNeedRTTI, [Obj.ClassName]), Obj, 'StoreObjProps');
  {ToDo: Klassentyp f?r XML Knoten?}
  if ClassNodeName <> '' then Node := GMCreateXmlNode(Node, ClassNodeName, '');
  Count := GetTypeData(PTypeInfo(Obj.ClassInfo))^.PropCount;
  if Count > 0 then
   begin
    GetMem(PropList, Count * SizeOf(Pointer));
    try
     GetPropInfos(PTypeInfo(Obj.ClassInfo), PropList);
     for i:=0 to Count-1 do
      if PropList^[i].PropType^{$IFNDEF FPC}^{$ENDIF}.Kind in TypeKinds then
       AddSoapValue(Node, PropList^[i].Name, GetPropValue(Obj, PropList^[i].Name, False));
    finally
     FreeMem(PropList);
    end;
   end;
  if Obj.GetInterface(IGMStoreProperties, PIStore) then PIStore.StoreProperties(Node);
end;

procedure TGMSoapClientCall.LoadObjProps(const Obj: TObject; Node: IGMXmlNode; const ForceExist: Boolean; const TypeKinds: TTypeKinds);
var Count, i: Integer; PropList: PPropList; ValNode: IGMXmlNode; PILoad: IGMLoadProperties;
begin
  if (Obj = nil) or (Node = nil) then Exit;
  if Obj.ClassInfo = nil then raise EGMSoapException.ObjError(GMFormat(RStrNeedRTTI, [Obj.ClassName]), Obj, 'GMLoadObjProps');
  Count := GetTypeData(PTypeInfo(Obj.ClassInfo))^.PropCount;
  if Count > 0 then
   begin
    GetMem(PropList, Count * SizeOf(Pointer));
    try
     GetPropInfos(PTypeInfo(Obj.ClassInfo), PropList);
     for i:=0 to Count-1 do
      if PropList^[i].PropType^{$IFNDEF FPC}^{$ENDIF}.Kind in TypeKinds then
       begin
        if ForceExist then
         ValNode := Node.Obj.CheckFindSubNode(PropList^[i].Name)
        else
         ValNode := Node.Obj.FindSubNode(PropList^[i].Name);

        if (ValNode <> nil) and (ValNode.Obj.StrValue <> '') then SetPropValue(Obj, PropList^[i].Name, ValNode.Obj.StrValue)
        else
         case PropList^[i].PropType^.Kind of
          tkChar, tkString, tkWChar, tkLString, tkWString: SetPropValue(Obj, PropList^[i].Name, '');
          tkInteger, tkFloat, tkInt64: SetPropValue(Obj, PropList^[i].Name, 0);
          tkVariant: SetPropValue(Obj, PropList^[i].Name, Null);
          //tkClass, tkInterface: SetPropValue(Obj, PropList^[i].Name, nil);
         end;
       end;
    finally
     FreeMem(PropList);
    end;
   end;
  if Obj.GetInterface(IGMLoadProperties, PILoad) then PILoad.LoadProperties(Node, ForceExist);
end;

function TGMSoapClientCall.CreateSOAPCallXml(const ASoapMethodName: TGMString; var ANode: IGMXmlNode): IGMXmlTree;
begin
  Result := XmlTreeCreateClass.CreateWrite;
  ANode := GMCreateXmlNode(InsertSoapBody(InsertSoapEnvelope(Result.Obj.RootNode)), GMXmlQualifiedName(FOperationNS.Name, ASoapMethodName));
end;

procedure TGMSoapClientCall.RaiseSoapFault(const ASoapFaultNode: IGMXmlNode; const ACallingName: TGMString);
  function BuildFaultMsg: TGMString;
  var Node: IGMXmlNode;
  begin
    if (ASoapFaultNode <> nil) and ASoapFaultNode.Obj.FindSubNodeIntoVar(cStrSoapFaultString, nil, Node) then Result := GMStrip(Node.Obj.StrValue);
    if Length(Result) <= 0 then Result := RStrNoSoapFaultMsg;
  end;
var SoapExceptObj: EGMSoapException;
begin
  SoapExceptObj := nil;
  try
   SoapExceptObj := CreateSoapFaultExceptObj(ASoapFaultNode, ACallingName);
   if SoapExceptObj = nil then SoapExceptObj := EGMSoapException.ObjError(BuildFaultMsg, Self, BuildCallingName(ACallingName, {$I %CurrentRoutine%}));
  except
   GMFreeAndNil(SoapExceptObj);
  end;

  if SoapExceptObj <> nil then
   begin
    if Length(SoapExceptObj.Message) <= 0 then SoapExceptObj.Message := BuildFaultMsg;
    raise SoapExceptObj;
   end;
end;

procedure TGMSoapClientCall.CheckResponseXml(const AResponseXml: IGMXmlTree; const ACallingName: TGMString);
var node: IGMXmlNode;
begin
  if AResponseXml = nil then Exit;

  if GMGetXmlNodeByPath(AResponseXml.Obj.RootNode, [cStrSoapEnv, cStrSoapBody, cStrSoapFault], node) then
     RaiseSoapFault(node, BuildCallingName(ACallingName, {$I %CurrentRoutine%}));

//if AResponseXml.Obj.RootNode.Obj.FindSubNode(cStrSoapEnv, node, 1) then
// if node.Obj.FindSubNode(cStrSoapBody, node, 1) then
//  if node.Obj.FindSubNode(cStrSoapFault, node, 1) then RaiseSoapFault(node, BuildCallingName(ACallingName, {$I %CurrentRoutine%}));
end;

procedure TGMSoapClientCall.CheckResponseContentType(const AResponseContent: ISequentialStream; const AContentType, ACallingName: TGMString);
begin
  if CompareText(cStrHttpContentXml, AContentType) <> 0 then
   raise WrongContentExceptionCreateClass.ObjError(GMStringJoin(GMTerminateStr(GMFormat(RStrWrongContentType, [AContentType, cStrHttpContentXml])), c2NewLine,
         GMExtractAnyTextResponse(AResponseContent, AContentType)), Self, BuildCallingName(ACallingName, {$I %CurrentRoutine%}));
end;

function TGMSoapClientCall.CheckResponseContent(const AResponseContent: ISequentialStream; const AContentType: TGMString; const ACallingName: TGMString): IGMXmlTree;
//var StreamPosKeeper: IUnknown;
begin
  if AResponseContent = nil then Exit; // begin Result := XmlTreeCreateClass.CreateRead(AResponseContent); Exit; end;
   //raise EGMException.ObjError(MsgPointerIsNil(RStrResponseContentStream), Self, {$I %CurrentRoutine%});
//StreamPosKeeper := TGMIStreamPosKeeper.Create(AResponseContent);

//if not GMIsXmlContent(AResponseContent) then
// raise WrongContentExceptionCreateClass.ObjError(RStrResonseContentNotXml, Self, BuildCallingName(ACallingName, {$I %CurrentRoutine%}));

  if CompareText(cStrHttpContentXml, AContentType) = 0 then Result := XmlTreeCreateClass.CreateRead(AResponseContent, FXmlParseAttributes)
  else
  //if CompareText(cStrHttpContentHtml, AContentType) <> 0 then Result := TGMHtmlTree.CreateRead(AResponseContent, FXmlParseAttributes - [paCheckHasXmlToken]);
  if CompareText(cStrHttpContentHtml, AContentType) = 0 then Result := TGMHtmlTree.CreateRead(AResponseContent, cRelaxedHtmlParseAttributes);

//else
// raise WrongContentExceptionCreateClass.ObjError(GMStringJoin(GMTerminateStr(GMFormat(RStrWrongContentType, [AContentType, cStrHttpContentXml])), c2NewLine,
//        GMExtractAnyTextResponse(AResponseContent, AContentType)), Self, BuildCallingName(ACallingName, {$I %CurrentRoutine%}));

  //
  // When Result is nil a later check of the content type will fail!
  //
  if Result <> nil then CheckResponseXml(Result, ACallingName);
end;

procedure TGMSoapClientCall.PrepareHttpRequest(const ARequest: IUnknown);
begin
  {ToDo: Add "SOAPAction" header here!}

//   soapaction    = "SOAPAction" ":" [ <"> URI-reference <"> ]
//   URI-reference = <as defined in RFC 2396 [4]>

  // Nothing! May be used by derived classes to add request headers.
end;

function TGMSoapClientCall.ExecSoapCall(const ASession: IGMHttpClientSession; const AXML: IGMXmlTree; const ACallingName: TGMString): IGMXmlTree;
var responseStrm: ISequentialStream; xmlStrm: IStream; request: IGMHttpClientRequest; uriParts: RGMUriComponents;
    contentType: TGMString;
begin
  if (ASession = nil) or (AXML = nil) then Exit;

  xmlStrm := TGMMemoryIStream.Create;
  //SrcStrm := TGMStreamAdapter.Create(xmlStrm);
  AXML.Obj.SaveToStream(xmlStrm);

  uriParts := GMParseUri(FOperationNS.Value);
//request := FConnection.CreateHttpRequest(cStrHttpPost, FSoapPortURL);
  request := TGMHttpClientRequest.Create(cGMHttpAgent);
  PrepareHttpRequest(request);

  //request.AddHeaders(GMFormat('%s: %s', [cStrHdrUserAgent, cStrUserAgent]));
//request.Obj.SendRequest('', xmlStrm.Obj.Memory, xmlStrm.Obj.Size, cGMTracePrefixes[tpXml]);
  responseStrm := ASession.ExecuteRequest(request, GMBuildUri('', '', '', '', '', uriParts.Path, uriParts.Query, uriParts.Fragment),
                                          cHttpMethodPOST, xmlStrm, cStrHttpContentXml);

//responseStrm := TGMMemoryIStream.Create;
//request.Obj.ReadResponseContent(responseStrm); // <- read responseStrm before HttpCheck to show it in the trace

//contentType := GMStrip(GMFirstWord(request.Obj.GetStrHeader(HTTP_QUERY_CONTENT_TYPE), ';,'), cWhiteSpace);
  contentType := GMStrip(GMFirstWord(GMGetINetHeaderStrValue(request.Obj.ReceivedHeaders, cHttpContentType), ';,'), cWhiteSpace);

//httpCode := request.Obj.GetIntHeader(HTTP_QUERY_STATUS_CODE, -1);

  //
  // Check the content before checking the content-type, because SOAP faults may be delivered as text/html by the server!
  //
  Result := CheckResponseContent(responseStrm, contentType, {$I %CurrentRoutine%});
  CheckResponseContentType(responseStrm, contentType, {$I %CurrentRoutine%});

  //if httpCode = -1 then raise EGMException.ObjError(RStrNoHttpStatusCode, Self, {$I %CurrentRoutine%});

  //
  // SOAP faults come with HTTP code 500 - server error, so check for SOAP fault before raising a normal HTTP server error!
  //
//if httpCode <> HTTP_STATUS_OK then
// HttpCheck(httpCode, [HTTP_STATUS_OK], GMExtractAnyTextResponse(responseStrm, contentType), Self, {$I %CurrentRoutine%});

  //GMCheckHTTPResponseContentType(request, cStrHttpContentXml, Self, {$I %CurrentRoutine%}, SoapFaultMsg);
  if Result = nil then Result := XmlTreeCreateClass.CreateRead(responseStrm, FXmlParseAttributes);
end;


{ ---------------------------- }
{ ---- TGMSoapMethodEntry ---- }
{ ---------------------------- }

constructor TGMSoapMethodEntry.Create(const AMethodName: TGMString; const ASoapMethod: TGMSoapMethod; const ARefLifeTime: Boolean);
begin
  inherited Create(ARefLifeTime);
  SoapMethodName := AMethodName;
  SoapMethod := ASoapMethod;
end;

function TGMSoapMethodEntry.GetName: TGMString;
begin
  Result := SoapMethodName;
end;


{ --------------------------- }
{ ---- TGMSoapServerPort ---- }
{ --------------------------- }

constructor TGMSoapServerPort.Create(const ARefLifeTime: Boolean);
begin
  inherited Create(ARefLifeTime);
  FRegisteredSoapMethods := TGMObjArrayCollection.Create(True, False, True, GMCompareByName, True);
end;

constructor TGMSoapServerPort.Create(const ATransportLayer: ISequentialStream; const ASoapPortName, ASoapPortTypeURL: TGMString;
  const AXmlParseAttributes: TGMXmlParseAttributes; const ARefLifeTime: Boolean);
begin
  inherited Create(ATransportLayer, ASoapPortTypeURL, AXmlParseAttributes, ARefLifeTime);
  FSoapPortName := ASoapPortName;
end;

function TGMSoapServerPort.Obj: TGMSoapServerPort;
begin
  Result := Self;
end;

function TGMSoapServerPort.DoTracing: Boolean;
begin
  Result := {$IFDEF DEBUG}True{$ELSE}False{$ENDIF};
end;

function TGMSoapServerPort.TracePrefix: TGMString;
begin
  Result := cStrSoap;
end;

procedure TGMSoapServerPort.RegisterSOAPMethod(const AMethodName: AnsiString; const AMethod: TGMSoapMethod);
begin
  FRegisteredSoapMethods.Add(TGMSoapMethodEntry.Create(AMethodName, AMethod));
end;

function TGMSoapServerPort.FindSoapMethod(const AMethodName: TGMString): TGMSoapMethod;
var nameObj: IUnknown; methodEntry: TGMSoapMethodEntry;
begin
  nameObj := TGMNameObj.Create(AMethodName);
  if FRegisteredSoapMethods.Find(nameObj, methodEntry) then Result := methodEntry.SoapMethod else Result := nil;
end;

function TGMSoapServerPort.InsertFaultNode(const ABodyNode: IGMXmlNode; const AExceptObject: TObject): TGMString;
var faultNode: IGMXmlNode;
begin
  if (ABodyNode = nil) or (AExceptObject = nil) then Exit;
  faultNode := GMCreateXmlNode(ABodyNode, cStrSoapFault, '', cStrEnvNS);

  GMCreateXmlNode(faultNode, cStrSoapFaultCode, AExceptObject.ClassName);
  GMCreateXmlNode(faultNode, cStrSoapFaultString, RStrException); // GMMsgFromExceptObj(AExceptObject, False)

  Result := InsertFaultDetailNode(faultNode, AExceptObject);
end;

//function TGMSoapServerPort.ProcessRequest(const AReadStrm, AWriteStrm: ISequentialStream): TGMSoapRequestResult;
function TGMSoapServerPort.ProcessRequest: TGMSoapRequestResult;
var requestXml, responseXml: IGMXmlTree; responseBodyNode, requestBodyNode, requestMethodNode: IGMXmlNode; // responseEnvNode
    errMsg: TGMString; soapMethod: TGMSoapMethod;
begin
  if DoTracing then vfGMTrace(RStrUsingSoapPort + ': "' + FSoapPortName+'"', TracePrefix);

  responseXml := XmlTreeCreateClass.CreateWrite;
//responseEnvNode := InsertSoapEnvelope(responseXml.Obj.RootNode);
//GMCreateXmlNode(responseEnvNode, cStrSoapHeader, '', cStrEnvNS);
  responseBodyNode := InsertSoapBody(InsertSoapEnvelope(responseXml.Obj.RootNode));
  try
   // Check request content type, or just read the requestXml? Would cause requestXml related exception if other content was sent

   //
   // Parsing the calling XML should already be wrapped by SOAP fault handling
   //
   requestXml := XmlTreeCreateClass.CreateRead(FTransportLayer, FXmlParseAttributes);
   requestBodyNode := GMCheckGetXmlNodeByPath(requestXml.Obj.RootNode, [cStrSoapEnv, cStrSoapBody]);

   GMQueryInterface(requestBodyNode.Obj.SubNodes.First, IGMXmlNode, requestMethodNode);
   if requestMethodNode = nil then raise EGMSoapException.ObjError(RStrNoSOAPMethodNode, Self, {$I %CurrentRoutine%});

   Result.SOAPMethodName := requestMethodNode.Obj.Name;
   Result.LogMessage := TracePrefix + ' "' + requestMethodNode.Obj.Name + '"';

   if Length(requestMethodNode.Obj.Name) <= 0 then raise EGMSoapException.ObjError(RStrEmptySoapMethodName, Self, {$I %CurrentRoutine%});

   soapMethod := FindSoapMethod(requestMethodNode.Obj.Name);
   if not Assigned(soapMethod) then raise EGMSoapException.ObjError(GMFormat(RStrSoapMethodNotImpl, [requestMethodNode.Obj.Name]), Self, {$I %CurrentRoutine%});
   soapMethod(requestMethodNode, responseBodyNode);

   Result.HttpStatusCode := HTTP_STATUS_OK;
  except
// if DoTracing then GMTraceException(GMExceptObject);
   responseXml := XmlTreeCreateClass.CreateWrite;
// responseXml.Obj.RootNode.Obj.SubNodes.Clear;
   errMsg := InsertFaultNode(InsertSoapBody(InsertSoapEnvelope(responseXml.Obj.RootNode, False)), ExceptObject);
   if Length(Result.LogMessage) <= 0 then Result.LogMessage := errMsg;
   Result.HttpStatusCode := HTTP_STATUS_SERVER_ERROR;
  end;

  responseXml.Obj.SaveToStream(FTransportLayer);
end;

//procedure TGMSoapServerPort.SendResponseContent(const ATransportLayer: ISequentialStream);
//begin
//
//end;


end.