unit RestConnection;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Web, DB;

Type
  

  { TRESTConnection }
  TRestGetURLEvent = Procedure (Sender : TComponent; aRequest : TDataRequest; Var aURL : String) of Object;
  TRestUpdateURLEvent = Procedure (Sender : TComponent; aRequest : TRecordUpdateDescriptor; Var aURL : String) of Object;
  TRESTConnection = Class(TComponent)
  private
    FBaseURL: String;
    FDataProxy : TDataProxy;
    FOnGetURL: TRestGetURLEvent;
    FOnUpdateURL: TRestUpdateURLEvent;
    FPageParam: String;
    function GetDataProxy: TDataProxy;
  Protected
    Function GetUpdateBaseURL : String; virtual;
    Function GetReadBaseURL : String; virtual;
    Function GetPageURL(aRequest : TDataRequest) : String;
    Function GetRecordUpdateURL(aRequest : TRecordUpdateDescriptor) : String;
  Public
    Function DoGetDataProxy : TDataProxy; virtual;
  Public
    Property DataProxy : TDataProxy Read GetDataProxy;
    Property BaseURL : String Read FBaseURL Write FBaseURL;
    Property PageParam : String Read FPageParam Write FPageParam;
    Property OnGetURL : TRestGetURLEvent Read FOnGetURL Write FOnGetURL;
    Property OnUpdateURL : TRestUpdateURLEvent Read FOnUpdateURL Write FOnUpdateURL;
  end;

  { TRESTDataProxy }

  TRESTDataProxy = class(TDataProxy)
  private
    FConnection: TRESTConnection;
  protected
    Procedure CheckBatchComplete(aBatch : TRecordUpdateBatch); virtual;
  Public
    Function GetUpdateDescriptorClass : TRecordUpdateDescriptorClass; override;
    Function ProcessUpdateBatch(aBatch : TRecordUpdateBatch): Boolean; override;
    Function DoGetData(aRequest: TDataRequest): Boolean; override;
    Function GetDataRequest(aOptions: TLoadOptions; aAfterRequest: TDataRequestEvent; aAfterLoad: TDatasetLoadEvent) : TDataRequest; override;
    Constructor Create(AOwner: TComponent);  override;
    Property Connection : TRESTConnection Read FConnection;
  end;

  { TRESTDataRequest }

  TRESTDataRequest = Class(TDataRequest)
  Private
    FXHR : TJSXMLHttpRequest;
  protected
    function onLoad(Event: TEventListenerEvent): boolean; virtual;
    function TransformResult: JSValue; virtual;
  end;

  { TRESTUpdateRequest }

  TRESTUpdateRequest = Class(TRecordUpdateDescriptor)
  Private
    FXHR : TJSXMLHttpRequest;
    FBatch : TRecordUpdateBatch;
  protected
    function onLoad(Event: TEventListenerEvent): boolean; virtual;
  end;

implementation

uses js;

{ TRESTUpdateRequest }

function TRESTUpdateRequest.onLoad(Event: TEventListenerEvent): boolean;
begin
  if (FXHR.Status div 100)=2 then
    begin
    Resolve(FXHR.response);
    Result:=True;
    end
  else
    ResolveFailed(FXHR.StatusText);
  (Proxy as TRestDataProxy).CheckBatchComplete(FBatch);
end;

{ TRESTDataRequest }

function TRESTDataRequest.TransformResult : JSValue;

begin
  Result:=FXHR.responseText;
end;

function TRESTDataRequest.onLoad(Event: TEventListenerEvent): boolean;
begin
  if (FXHR.Status=200) then
    begin
    Data:=TransformResult;
    Success:=rrOK;
    end
  else
    begin
    Data:=Nil;
    if (loAtEOF in LoadOptions) and (FXHR.Status=404) then
      Success:=rrEOF
    else
      begin
      Success:=rrFail;
      ErrorMsg:=FXHR.StatusText;
      end;
    end;
  DoAfterRequest;
  Result:=True;
end;

{ TRESTConnection }

function TRESTConnection.GetDataProxy: TDataProxy;
begin
  if (FDataProxy=Nil) then
    FDataProxy:=DoGetDataProxy;
  Result:=FDataProxy;
end;

function TRESTConnection.GetUpdateBaseURL: String;
begin
  Result:=BaseURL;
end;

function TRESTConnection.GetReadBaseURL: String;
begin
  Result:=BaseURL;
end;

function TRESTConnection.GetPageURL(aRequest: TDataRequest): String;

Var
  URL : String;

begin
  URL:=GetReadBaseURL;
  if (PageParam<>'') then
    begin
    if Pos('?',URL)<>0 then
      URL:=URL+'&'
    else
      URL:=URL+'?';
    URL:=URL+PageParam+'='+IntToStr(ARequest.RequestID-1);
    end;
  if Assigned(FOnGetURL) then
    FOnGetURL(Self,aRequest,URL);
  Result:=URL;
end;

function TRESTConnection.GetRecordUpdateURL(aRequest: TRecordUpdateDescriptor): String;

Var
  I : integer;
  Base,KeyField : String;

begin
  KeyField:='';
  Result:='';
  Base:=GetUpdateBaseURL;
  if aRequest.Status in [usModified,usDeleted] then
    begin
    I:=aRequest.Dataset.Fields.Count-1;
    While (KeyField='') and (I>=0) do
      begin
      if pfInKey in aRequest.Dataset.Fields[i].ProviderFlags then
        KeyField:=aRequest.Dataset.Fields[i].FieldName;
      Dec(I);
      end;
    if (KeyField='') then
      DatabaseError('No key field',aRequest.Dataset);
    end;
  if (KeyField<>'') and (Base<>'') and (Base[Length(Base)]<>'/') then
    Base:=Base+'/';
  Case aRequest.Status of
    usModified,
    usDeleted: Result:=Base+TJSJSON.stringify(TJSObject(aRequest.Data)[KeyField]);
    usInserted : Result:=Base;
  end;
  If Assigned(FOnUpdateURL) then
    FOnUpdateURL(Self,aRequest,Result);
end;

function TRESTConnection.DoGetDataProxy: TDataProxy;
begin
  Result:=TRESTDataProxy.Create(Self);
end;

{ TRESTDataProxy }

procedure TRESTDataProxy.CheckBatchComplete(aBatch: TRecordUpdateBatch);

Var
  BatchOK : Boolean;
  I : Integer;

begin
  BatchOK:=True;
  I:=aBatch.List.Count-1;
  While BatchOK and (I>=0) do
    begin
    BatchOK:=aBatch.List[I].Status in [usResolved,usResolveFailed];
    Dec(I);
    end;
  If BatchOK and Assigned(aBatch.OnResolve) then
    aBatch.OnResolve(Self,aBatch);
end;

function TRESTDataProxy.GetUpdateDescriptorClass: TRecordUpdateDescriptorClass;
begin
  Result:=TRESTUpdateRequest;
end;

function TRESTDataProxy.ProcessUpdateBatch(aBatch: TRecordUpdateBatch): Boolean;

Var
  R : TRESTUpdateRequest;
  i : Integer;
  Method,URl : String;

begin
  Result:=False;
  For I:=0 to aBatch.List.Count-1 do
    begin
    R:=aBatch.List[i] as TRESTUpdateRequest;
    R.FBatch:=aBatch;
    R.FXHR:=TJSXMLHttpRequest.New;
    R.FXHR.AddEventListener('load',@R.onLoad);
    URL:=FConnection.GetRecordUpdateURL(R);
    Case R.Status of
      usInserted :
        Method:='POST';
      usModified:
        Method:='PUT';
      usDeleted:
        Method:='DELETE';
    end;
    R.FXHR.open(Method,URL);
    R.FXHR.setRequestHeader('content-type','application/json');
    if R.Status in [usInserted,usModified] then
      R.FXHR.Send(TJSJSON.Stringify(R.Data))
    else
      R.FXHR.Send;
    end;
  Result:=True;
end;

function TRESTDataProxy.DoGetData(aRequest: TDataRequest): Boolean;

Var
  R : TRestDataRequest;
  URL : String;

begin
  Result:=False;
  R:=aRequest as TRestDataRequest;
  R.FXHR:=TJSXMLHttpRequest.New;
  R.FXHR.AddEventListener('load',@R.onLoad);
  URL:=Connection.GetPageURL(aRequest);
  if (URL='') then
    begin
    if loAtEOF in R.LoadOptions then
      R.Success:=rrEOF
    else
      begin
      R.Success:=rrFail;
      R.ErrorMsg:='No URL to get data';
      R.DoAfterRequest; // This will free request !
      end;
    end
  else
    begin
    R.FXHR.open('GET',URL,true);
    R.FXHR.send;
    Result:=True;
    end;
end;

function TRESTDataProxy.GetDataRequest(aOptions: TLoadOptions; aAfterRequest: TDataRequestEvent; aAfterLoad: TDatasetLoadEvent): TDataRequest;

begin
  Result:=TRestDataRequest.Create(Self,aOptions, aAfterRequest,aAfterLoad);
end;

constructor TRESTDataProxy.Create(AOwner: TComponent);
begin
  Inherited;
  If AOwner is TRestConnection then
    FConnection:=TRestConnection(aOwner);
end;

end.

