//{$UNDEF WST_DBG}
//{$DEFINE WST_DBG}

(*WST_BROKER enable the service brokering :
  if enabled, this module just forwards the request to the
  implementation libraries contained in the WstRootPath path.
  WST load these libraries in the local file system folder
  configured by the value of WstRootPath.
  WstRootPath is a configuration directive which must be
  in the wst "Location" scope.
  Example :
  
    <Location /wst>
      SetHandler wst-handler
      WstRootPath "C:/Programmes/lazarus/wst/trunk/samples/library_server/"
    </Location>

 Services can then be invoked through the following addressing schema
   http://127.0.0.1:8080/wst/services/lib_server/UserService
   
     lib_server   : the library name ( without extension )
     UserService  : the target service
     wst/services : constant.
*)
//{$DEFINE WST_BROKER}

unit wst_apache_binding;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils,
  httpd, apr, apriconv, aprutil;

const
  sWST_ROOT = 'wst';
  sSEPARATOR = '/';
  sSERVICES_PREFIXE = 'services';
  sWSDL = 'WSDL';
  sHTTP_BINARY_CONTENT_TYPE = 'application/octet-stream';
  sCONTENT_TYPE = 'Content-Type';
  
{$IFDEF WST_BROKER}  
  sWstRootPath = 'WstRootPath'; // The WST local file system path configure in apache
  sWST_LIBRARY_EXTENSION = '.dll';
  
type
  PWstConfigData = ^TWstConfigData;
  TWstConfigData = record
    Dir : PChar;
    BasePath : PChar;
  end;
{$ENDIF WST_BROKER}
  
var
  wst_module_ptr : Pmodule = nil;
{$IFDEF WST_BROKER}  
  WstConfigData : PWstConfigData = nil;
  WstCommandStructArray : array[0..1] of command_rec = (
    ( name    : sWstRootPath;
      func      : ( func_take1 : @ap_set_string_slot );
      cmd_data  : ( nil {@WstConfigData^.BasePath} );
      req_override : OR_ALL;
      args_how     : TAKE1;
      errmsg       : 'usage : WstRootPath <path>' + LineEnding + '  path is the path to the WST root path.';
    ),
    ()
  );
{$ENDIF WST_BROKER}

  function wst_RequestHandler(r: Prequest_rec): Integer;

{$IFDEF WST_BROKER}    
  function wst_create_dir_config(p: Papr_pool_t; dir: PChar) : Pointer;cdecl;
{$ENDIF WST_BROKER}
  
implementation
uses base_service_intf,
     server_service_intf, server_service_imputils,
     server_service_soap, server_binary_formatter, server_service_xmlrpc,
     metadata_repository, metadata_wsdl,
     imp_utils, binary_streamer, library_base_intf, library_imp_utils,
     DOM, XMLWrite,
     metadata_service, metadata_service_binder, metadata_service_imp;

procedure wst_initialize();
begin
  RegisterStdTypes();
  Server_service_RegisterBinaryFormat();
  Server_service_RegisterSoapFormat();
  //Server_service_RegisterXmlRpcFormat();

  RegisterWSTMetadataServiceImplementationFactory();
  Server_service_RegisterWSTMetadataServiceService();
end;

{$IFDEF WST_BROKER}  
function wst_create_dir_config(p: Papr_pool_t; dir: PChar) : Pointer; cdecl;
begin
  WstConfigData := PWstConfigData(apr_palloc(p,SizeOf(TWstConfigData)));
  FillChar(WstConfigData^,SizeOf(TWstConfigData),#0);
  WstConfigData^.Dir := apr_pstrdup(p,dir);
  Result := WstConfigData;
end;

function GetWstPath(): PChar;inline;
begin
  Result := WstConfigData^.BasePath;
end;
{$ENDIF WST_BROKER}  

type
  PRequestArgument = ^TRequestArgument;
  TRequestArgument = record
    Name  : shortstring;
    Value : shortstring;
    Next  : PRequestArgument;
  end;
  TRequestInfo = record
    InnerRequest : Pointer;
    Root         : string;
    URI          : string;
    ContentType  : string;
    Buffer       : string;
    Arguments    : string;
    ArgList      : PRequestArgument;
  end;
  
  TResponseInfo = record
    ContentText : string;
    ContentType : string;
  end;

function ParseArgs(
  const APool      : Papr_pool_t;
  const AArgs      : string;
  const ASeparator : Char = '&'
) : PRequestArgument;
var
  locBuffer, locArg : string;
  locPrev, locTmpArg : PRequestArgument;
begin
  Result := nil;
  locBuffer := Trim(AArgs);
  if not IsStrEmpty(locBuffer) then begin
    locTmpArg := nil;
    locPrev := nil;
    while True do begin
      locArg := GetToken(locBuffer,ASeparator);
      if IsStrEmpty(locArg) then
        Break;
      locPrev := locTmpArg;
      locTmpArg := PRequestArgument(apr_palloc(APool,SizeOf(TRequestArgument)));
      FillChar(locTmpArg^,SizeOf(TRequestArgument),#0);
      if ( Result = nil ) then begin
        Result := locTmpArg;
      end else begin
        locPrev^.Next := locTmpArg;
      end;
      locTmpArg^.Name := GetToken(locArg,'=');
      locTmpArg^.Value := locArg;
    end;
  end;
end;

function FindArg(const AArgs : PRequestArgument; const AName : string) : PRequestArgument;
var
  p : PRequestArgument;
begin
  Result := nil;
  p := AArgs;
  while Assigned(p) do begin
    if AnsiSameText(AName,AArgs^.Name) then begin
      Result := p;
      Break;
    end;
    p := p^.Next;
  end;
end;

{$IFDEF WST_DBG}
procedure SaveStringToFile(const AStr,AFile:string;const AKeepExisting : Boolean);
begin
  with TMemoryStream.Create() do try
    if AKeepExisting and FileExists(AFile) then begin
      LoadFromFile(AFile);
      Position := Size;
    end;
    if ( Length(AStr) > 0 ) then
      Write(AStr[1],Length(AStr));
    SaveToFile(AFile);
  finally
    Free();
  end;
end;
{$ENDIF WST_DBG}

function ReadBuffer(r : Prequest_rec; out rbuf : string ) : Integer;
var
  argsbuffer : string;
  rsize, len_read, rpos : Integer;
  loc_length : Integer;
begin
  rbuf := '';
  Result := ap_setup_client_block(r, REQUEST_CHUNKED_ERROR);
  if ( Result <> OK ) then
    Exit;

  if ( ap_should_client_block(r) <> 0 ) then begin
    SetLength(argsbuffer,HUGE_STRING_LEN);
    FillChar(argsbuffer[1],Length(argsbuffer),0);
    rsize := 0; len_read := 0; rpos := 0;
    loc_length := r^.remaining;
    SetLength(rbuf, loc_length );

     while True do begin
       len_read := ap_get_client_block(r, @(argsbuffer[1]), Length(argsbuffer));
       if ( len_read <= 0 ) then
         Exit;
        if ( ( rpos + len_read ) > loc_length ) then
          rsize := loc_length - rpos
        else
          rsize := len_read;
        Move(argsbuffer[1],rbuf[ ( 1 + rpos ) ], rsize);
        Inc(rpos,rsize);
    end;
  end;
end;

function ExtractNextPathElement(var AFullPath : string):string;
var
  i : SizeInt;
begin
  Result := '';
  if ( Length(AFullPath) > 0 ) then begin
    while ( Length(AFullPath) > 0 ) and ( AFullPath[1] = sSEPARATOR ) do begin
      Delete(AFullPath,1,1);
    end;
    i := Pos(sSEPARATOR,AFullPath);
    if ( i < 1 ) then begin
      Result := AFullPath;
      AFullPath := '';
    end else begin
      Result := Copy(AFullPath,1,Pred(i));
      Delete(AFullPath,1,i);
    end;
  end;
end;

function GetWSDL(const ARepName, ARootAddress: shortstring):string;
var
  strm : TMemoryStream;
  rep : PServiceRepository;
  doc :TXMLDocument;
  i : SizeInt;
  s : string;
begin
  Result := '';
  rep := nil;
  doc := Nil;
  i := GetModuleMetadataMngr().IndexOfName(ARepName);
  if ( i < 0 ) then
    Exit;
  strm := TMemoryStream.Create();
  try
    s := GetModuleMetadataMngr().GetRepositoryName(i);
    GetModuleMetadataMngr().LoadRepositoryName(s,ARootAddress,rep);
    strm.Clear();
    doc := TXMLDocument.Create();
    GenerateWSDL(rep,doc);
    WriteXMLFile(doc,strm);
    i := strm.Size;
    if ( i > 0 ) then begin
      SetLength(Result,i);
      Move(strm.memory^,Result[1],i);
    end;
  finally
    doc.Free();
    strm.Free();
    GetModuleMetadataMngr().ClearRepository(rep);
  end;
end;

function GenerateWSDLTable(): string;
var
  r : IModuleMetadataMngr;
  i : Integer;
begin
  r := GetModuleMetadataMngr();
  Result := '<html>' +
              '<head>'+
                '<title>'+
                  'The Web Service Toolkit generated Metadata table XXXXX'+
                '</title>'+
                '<body>' +
                  '<p BGCOLOR="#DDEEFF"><FONT FACE="Arial" COLOR="#0000A0" SIZE="+2">The following repositories has available. Click on the link to view the corresponding WSDL.</FONT></p>'+
                  '<table width="100%">';

  for i := 0 to Pred(r.GetCount()) do
    Result := Result + '<tr>' +
                        '<td>' +
                            Format('<a href="%s">',[sSEPARATOR+sWST_ROOT+sSEPARATOR+sSERVICES_PREFIXE+sSEPARATOR+sWSDL+sSEPARATOR+r.GetRepositoryName(i)])+
                            r.GetRepositoryName(i) +
                            '</a>'+
                        '</td>' +
                      '</tr>';

  Result := Result +
                    '</tr>'+
                  '</table>'+
                '</body>'+
              '</head>'+
            '</html>';
end;

procedure ProcessWSDLRequest(
  const ARequestInfo    : TRequestInfo;
  out   AResponseInfo   : TResponseInfo
);
var
  locRepName, strBuff, locPath : string;
  i : Integer;
begin
  FillChar(AResponseInfo,SizeOf(TResponseInfo),#0);
  locPath := ARequestInfo.URI;
  locRepName := ExtractNextPathElement(locPath);
  if AnsiSameText(sWSDL,locRepName) then
    locRepName := ExtractNextPathElement(locPath);
  strBuff := GetWSDL(locRepName,ARequestInfo.Root);
  i := Length(strBuff);
  if ( i > 0 ) then begin
    AResponseInfo.ContentType := 'text/xml';
    AResponseInfo.ContentText := strBuff;
    Exit;
  end;
  AResponseInfo.ContentText := GenerateWSDLTable();
  AResponseInfo.ContentType := 'text/html';
end;

function ProcessServiceRequest(
  const ARequestInfo    : TRequestInfo;
  out   AResponseInfo   : TResponseInfo
):Boolean;
var
  trgt,ctntyp, loc_path : string;
  rqst : IRequestBuffer;
  inStream, outStream: TMemoryStream;
  i : Integer;
begin
  FillChar(AResponseInfo,SizeOf(TResponseInfo),#0);
  loc_path := ARequestInfo.URI;
  trgt := ExtractNextPathElement(loc_path);
  Result := False;
  if AnsiSameText(sWSDL,trgt) then
    Exit;
  Result := True;
  inStream := nil;
  outStream := nil;
  try
    inStream := TMemoryStream.Create();
    outStream := TMemoryStream.Create();

    ctntyp := ARequestInfo.ContentType;
    i := Length(ARequestInfo.Buffer);
    if ( i > 0 ) then
      inStream.Write(ARequestInfo.Buffer[1],i);
    inStream.Position := 0;
    if AnsiSameText(sBINARY_CONTENT_TYPE,ctntyp) then
      AResponseInfo.ContentType := sHTTP_BINARY_CONTENT_TYPE
    else
      AResponseInfo.ContentType := ctntyp;
    rqst := TRequestBuffer.Create(trgt,ctntyp,inStream,outStream,ARequestInfo.ContentType);
    HandleServiceRequest(rqst);
    i := outStream.Size;
    if ( i > 0 ) then begin
      SetLength(AResponseInfo.ContentText,i);
      Move(outStream.Memory^,AResponseInfo.ContentText[1],i);
    end;
  finally
    outStream.Free();
    inStream.Free();
    {$IFDEF WST_DBG}
      SaveStringToFile('RequestInfo.Arguments=' + ARequestInfo.Arguments + LineEnding,'c:\log.log',False);
      SaveStringToFile('ResponseInfo.ContentText=' + AResponseInfo.ContentText + LineEnding,'c:\log.log',False);
    {$ENDIF}
  end;
end;

{$IFDEF WST_BROKER}  
const MAX_ERR_LEN = 500;
function ProcessServiceRequestLibrary(
  const ARequestInfo    : TRequestInfo;
  out   AResponseInfo   : TResponseInfo
) : Boolean;
var
  loc_path, ctntyp : string;
  targetModuleName, targetFormat, targetService : string;
  wrtr : IDataStore;
  buffStream : TMemoryStream;
  strBuff : string;
  intfBuffer : IwstStream;
  bl : LongInt;
  targetModule : IwstModule;
  handlerProc : TwstLibraryHandlerFunction;
  pArg : PRequestArgument;
  i : Integer;
begin
  try
    FillChar(AResponseInfo,SizeOf(TResponseInfo),#0);
    loc_path := ARequestInfo.URI;
    targetModuleName := ExtractNextPathElement(loc_path);
    Result := False; 
    targetModule := LibraryManager.Get(GetWstPath() + targetModuleName + sWST_LIBRARY_EXTENSION);
    handlerProc := TwstLibraryHandlerFunction(targetModule.GetProc(WST_LIB_HANDLER));
    if not Assigned(handlerProc) then
      Exit;
    targetService := ExtractNextPathElement(loc_path);
    if AnsiSameText(sWSDL,targetService) then
      Exit;
    pArg := FindArg(ARequestInfo.ArgList,'format');
    if Assigned(pArg) then
      targetFormat := pArg^.Value;
    if IsStrEmpty(targetFormat) then
      targetFormat := ARequestInfo.ContentType;
    buffStream := TMemoryStream.Create();
    try
      wrtr := CreateBinaryWriter(buffStream);
      wrtr.WriteInt32S(0);
      wrtr.WriteAnsiStr(targetService);
      wrtr.WriteAnsiStr(ARequestInfo.ContentType);
      wrtr.WriteAnsiStr(targetFormat);
      wrtr.WriteAnsiStr(ARequestInfo.Buffer);
      buffStream.Position := 0;
      wrtr.WriteInt32S(buffStream.Size-4);

      buffStream.Position := 0;
      intfBuffer := TwstStream.Create(buffStream);
      bl := MAX_ERR_LEN;
      strBuff := StringOfChar(#0,bl);
      i := handlerProc(intfBuffer,Pointer(strBuff),bl);
      if ( i <> RET_OK ) then
        raise Exception.CreateFmt('Library server error :'#13'Code : %d'#13'Message : %s',[i,strBuff]);

      if AnsiSameText(sBINARY_CONTENT_TYPE,ARequestInfo.ContentType) then
        AResponseInfo.ContentType := sHTTP_BINARY_CONTENT_TYPE
      else
        AResponseInfo.ContentType := ARequestInfo.ContentType;
      buffStream.Position := 0;
      if ( buffStream.Size > 0 ) then begin
        SetLength(AResponseInfo.ContentText,buffStream.Size);
        buffStream.Read(AResponseInfo.ContentText[1],Length(AResponseInfo.ContentText));
      end else begin
        AResponseInfo.ContentText := '';
      end;
    finally
      buffStream.Free();
    end;

    Result := True;
  except
    on e : Exception do begin
      Result := False;
      ap_log_rerror(PCHAR('wst_apache_binding'),392,APLOG_ERR,0,Prequest_rec(ARequestInfo.InnerRequest),PCHAR(e.Message),[]);
    end;
  end;
end;
{$ENDIF WST_BROKER}  

function wst_RequestHandler(r: Prequest_rec): Integer;

  function FillRequestInfo(var ARequestInfo    : TRequestInfo):Integer;
  begin
    ARequestInfo.InnerRequest := r;
    ARequestInfo.ContentType := apr_table_get(r^.headers_in,sCONTENT_TYPE);
    ARequestInfo.Root := ap_get_server_name(r) + sSEPARATOR + sWST_ROOT + sSEPARATOR;
    ARequestInfo.URI := r^.uri;
    ARequestInfo.Arguments := r^.args;
    ARequestInfo.ArgList := ParseArgs(r^.pool,ARequestInfo.Arguments);
    Result := ReadBuffer(r,ARequestInfo.Buffer);
  end;

var
  loc_RequestInfo    : TRequestInfo;
  loc_ResponseInfo   : TResponseInfo;
begin
  Result := FillRequestInfo(loc_RequestInfo);
  if not AnsiSameText(sWST_ROOT,ExtractNextPathElement(loc_RequestInfo.URI)) then
    Result := DECLINED;
  if ( Result <> OK ) then
    Exit;

  try
    if AnsiSameText(sSERVICES_PREFIXE,ExtractNextPathElement(loc_RequestInfo.URI)) then begin

{$IFDEF WST_BROKER}
      if not ProcessServiceRequestLibrary(loc_RequestInfo,loc_ResponseInfo) then
        ProcessWSDLRequest(loc_RequestInfo,loc_ResponseInfo);
{$ELSE}
      if not ProcessServiceRequest(loc_RequestInfo,loc_ResponseInfo) then
        ProcessWSDLRequest(loc_RequestInfo,loc_ResponseInfo);
{$ENDIF}
    end else begin
      ProcessWSDLRequest(loc_RequestInfo,loc_ResponseInfo);
    end;

    ap_set_content_type(r, PCHAR(loc_ResponseInfo.ContentType));
//    if AnsiSameText(sHTTP_BINARY_CONTENT_TYPE,loc_ResponseInfo.ContentType) then begin
      ap_set_content_length(r,Length(loc_ResponseInfo.ContentText));
      ap_rwrite(@(loc_ResponseInfo.ContentText[1]),Length(loc_ResponseInfo.ContentText),r);
      ap_rflush(r);
  {  end else begin
      ap_rputs(PCHAR(loc_ResponseInfo.ContentText), r);
      ap_rflush(r);
    end;}
    Result := OK;
  except
    on e : Exception do begin
      ap_set_content_type(r, 'text/html');
      ap_rputs('<HTML><HEAD> <TITLE>Error</TITLE></HEAD>' + LineEnding, r);
      ap_rputs('<BODY></BODY></HTML>',r);
      ap_rprintf(r, '<BODY><H1>"%s"</H1></BODY></HTML>' + LineEnding, [PCHAR(e.Message)]);
      ap_rflush(r);
      Exit;
    end;
  end;
end;

initialization
  wst_initialize();
  
end.
