123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336 |
- {
- This file is part of the Free Pascal run time library.
- Copyright (c) 2019 by Michael Van Canneyt, member of the
- Free Pascal development team
- Simple REST connection component for use with Datasets.
- See the file COPYING.FPC, included in this distribution,
- for details about the copyright.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- **********************************************************************}
- unit RestConnection;
- {$mode objfpc}
- 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
- Procedure SetupRequest(aXHR : TJSXMLHttpRequest); virtual;
- Function GetUpdateBaseURL(aRequest: TRecordUpdateDescriptor) : String; virtual;
- Function GetReadBaseURL(aRequest: TDataRequest) : String; virtual;
- Function GetPageURL(aRequest : TDataRequest) : String; virtual;
- 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{%H-}: TEventListenerEvent): boolean; virtual;
- function TransformResult: JSValue; virtual;
- end;
- { TRESTUpdateRequest }
- TRESTUpdateRequest = Class(TRecordUpdateDescriptor)
- Private
- FXHR : TJSXMLHttpRequest;
- FBatch : TRecordUpdateBatch;
- protected
- function onLoad(Event{%H-}: 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;
- procedure TRESTConnection.SetupRequest(aXHR: TJSXMLHttpRequest);
- begin
- // Do nothing
- if aXHR=nil then ;
- end;
- function TRESTConnection.GetUpdateBaseURL(aRequest: TRecordUpdateDescriptor): String;
- begin
- Result:=BaseURL;
- if aRequest=nil then ;
- end;
- function TRESTConnection.GetReadBaseURL(aRequest: TDataRequest): String;
- begin
- Result:=BaseURL;
- if aRequest=nil then ;
- end;
- function TRESTConnection.GetPageURL(aRequest: TDataRequest): String;
- Var
- URL : String;
- begin
- URL:=GetReadBaseURL(aRequest);
- 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(aRequest);
- 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].ResolveStatus in [rsResolved,rsResolveFailed];
- 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');
- Connection.SetupRequest(R.FXHR);
- 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
- if (loAtEOF in R.LoadOptions) and (Connection.PageParam='') then
- R.Success:=rrEOF
- else
- begin
- R.FXHR.open('GET',URL,true);
- Connection.SetupRequest(R.FXHR);
- R.FXHR.send;
- Result:=True;
- end;
- 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.
|