pas2js/packages/fcl-db/sqldbrestdataset.pp

417 lines
11 KiB
ObjectPascal

{
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 SQLDBRESTBridge JSON dataset component and connection.
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 sqldbrestdataset;
{$mode objfpc}
interface
uses
Classes, SysUtils, JS, web, db, JSONDataset, restconnection;
Type
{ TSQLDBRestConnection }
TSQLDBRestConnection = Class(TRestConnection)
private
FDataProperty: String;
FmetaDataProperty: String;
FMetaDataResourceName: String;
FonGetResources: TNotifyEvent;
FPassword: String;
FResourceList: TStrings;
FUserName: String;
procedure DoResources(Sender: TObject);
function DoStoreDataProp: Boolean;
function DoStoreMetadata: Boolean;
function DoStoreMetadataProp: Boolean;
Protected
Procedure SetupRequest(aXHR : TJSXMLHttpRequest); override;
Function GetUpdateBaseURL(aRequest: TRecordUpdateDescriptor): String; override;
Function GetReadBaseURL(aRequest: TDataRequest): String; Override;
Public
Constructor create(aOwner : TComponent); override;
Destructor Destroy; override;
Procedure GetResources(OnResult : TNotifyEvent = Nil);
Property ResourceList : TStrings Read FResourceList;
Published
Property OnGetResources : TNotifyEvent Read FonGetResources Write FOnGetResources;
Property metaDataProperty : String read FmetaDataProperty Write FmetaDataProperty Stored DoStoreMetadataProp;
Property DataProperty : String read FDataProperty Write FDataProperty Stored DoStoreDataProp;
Property MetaDataResourceName : String Read FMetaDataResourceName Write FMetaDataResourceName Stored DoStoreMetadata;
Property UserName : String Read FUserName Write FUserName;
Property Password : String Read FPassword Write FPassword;
end;
{ TSQLDBRestDataset }
TSQLDBRestDataset = Class(TJSONDataset)
private
FConnection: TSQLDBRestConnection;
FResourceName: String;
procedure SetConnection(AValue: TSQLDBRestConnection);
procedure SetResourceName(AValue: String);
Protected
function DataPacketReceived(ARequest: TDataRequest): Boolean; override;
function GetStringFieldLength(F: TJSObject; AName: String; AIndex: Integer): integer;virtual;
function StringToFieldType(S: String): TFieldType; virtual;
Function DoGetDataProxy: TDataProxy; override;
Procedure MetaDataToFieldDefs; override;
Public
Property Connection: TSQLDBRestConnection Read FConnection Write SetConnection;
Property ResourceName : String Read FResourceName Write SetResourceName;
end;
implementation
Type
{ TServiceRequest }
TServiceRequest = Class(TObject)
Private
FOnMyDone,
FOnDone : TNotifyEvent;
FXHR: TJSXMLHttpRequest;
function GetResult: String;
function GetResultJSON: TJSObject;
function GetStatusCode: Integer;
function onLoad(Event{%H-}: TEventListenerEvent): boolean;
Public
Constructor Create(Const aMethod,aURL,aUserName,aPassword : String; aOnDone1 : TNotifyEvent; aOnDone2 : TNotifyEvent = Nil);
Procedure Execute;
Property RequestResult : String read GetResult;
Property ResultJSON : TJSObject read GetResultJSON;
Property OnDone : TNotifyEvent Read FOnDone;
Property StatusCode : Integer Read GetStatusCode;
end;
{ TServiceRequest }
constructor TServiceRequest.Create(const aMethod,aURL, aUserName, aPassword: String; aOnDone1 : TNotifyEvent; aOnDone2 : TNotifyEvent = Nil);
begin
FOnMyDone:=aOnDone1;
FOnDone:=aOnDone2;
FXHR:=TJSXMLHttpRequest.New;
FXHR.AddEventListener('load',@onLoad);
FXHR.open(aMethod,aURL,true);
(* else
begin
// FXHR.withCredentials := true;
FXHR.open(aMethod,aURL,true,aUserName,aPassword);
end;*)
FXHR.setRequestHeader('Content-Type', 'application/json');
FXHR.setRequestHeader('Authorization', 'Basic '+window.btoa(aUserName+':'+aPassword));
end;
procedure TServiceRequest.Execute;
begin
FXHR.send;
end;
function TServiceRequest.GetResult: String;
begin
Result:=FXHR.responseText;
end;
function TServiceRequest.GetResultJSON: TJSObject;
begin
if SameText(FXHR.getResponseHeader('Content-Type'),'application/json') then
Result:=TJSJSON.parseObject(GetResult)
else
Result:=nil;
end;
function TServiceRequest.GetStatusCode: Integer;
begin
Result:=FXHR.Status;
end;
function TServiceRequest.onLoad(Event: TEventListenerEvent): boolean;
begin
if Assigned(FOnMyDone) then
FOnMyDone(Self);
end;
{ TSQLDBRestConnection }
function TSQLDBRestConnection.DoStoreMetadata: Boolean;
begin
Result:=(FMetadataResourceName<>'metadata');
end;
function TSQLDBRestConnection.DoStoreMetadataProp: Boolean;
begin
Result:=(FMetaDataProperty<>'metaData');
end;
procedure TSQLDBRestConnection.SetupRequest(aXHR: TJSXMLHttpRequest);
begin
inherited SetupRequest(aXHR);
aXHR.setRequestHeader('Content-Type', 'application/json');
aXHR.setRequestHeader('Accept', 'application/json');
if (UserName<>'') then
aXHR.setRequestHeader('Authorization', 'Basic '+window.btoa(UserName+':'+Password));
end;
function TSQLDBRestConnection.GetUpdateBaseURL(aRequest: TRecordUpdateDescriptor): String;
begin
Result:=inherited GetUpdateBaseURL(aRequest);
Result:=IncludeTrailingPathDelimiter(Result)+TSQLDBRestDataset(aRequest.Dataset).ResourceName;
end;
function TSQLDBRestConnection.GetReadBaseURL(aRequest: TDataRequest): String;
begin
Result:=inherited GetReadBaseURL(aRequest);
Result:=IncludeTrailingPathDelimiter(Result)+TSQLDBRestDataset(aRequest.Dataset).ResourceName;
end;
procedure TSQLDBRestConnection.DoResources(Sender: TObject);
Var
R : TServiceRequest absolute Sender;
J,Res : TJSObject;
A : TJSArray;
i : Integer;
begin
FResourceList.Clear;
if (R.StatusCode=200) then
begin
J:=R.ResultJSON;
if J=Nil then
exit;
A:=TJSArray(J.Properties['data']);
For I:=0 to A.Length-1 do
begin
Res:=TJSObject(A[i]);
FResourceList.Add(String(Res.Properties['name']));
end;
end;
If Assigned(R.OnDone) then
R.OnDone(Self);
If Assigned(OnGetResources) then
OnGetResources(Self);
end;
function TSQLDBRestConnection.DoStoreDataProp: Boolean;
begin
Result:=(FDataProperty<>'data');
end;
constructor TSQLDBRestConnection.create(aOwner: TComponent);
begin
inherited create(aOwner);
FResourceList:=TStringList.Create;
FMetaDataResourceName:='metadata';
FmetaDataProperty:='metaData';
FDataProperty:='data';
TStringList(FResourceList).Sorted:=true;
end;
destructor TSQLDBRestConnection.Destroy;
begin
FreeAndNil(FResourceList);
inherited Destroy;
end;
procedure TSQLDBRestConnection.GetResources(OnResult: TNotifyEvent);
Var
aURL : String;
R : TServiceRequest;
begin
aURL:=IncludeTrailingPathDelimiter(BaseURL)+MetaDataResourceName+'?fmt=json';
R:=TServiceRequest.Create('GET',aURL,Self.UserName,Self.Password,@DoResources,OnResult);
R.Execute;
end;
{ TSQLDBRestDataset }
procedure TSQLDBRestDataset.SetConnection(AValue: TSQLDBRestConnection);
begin
if FConnection=AValue then Exit;
if Assigned(FConnection) then
FConnection.RemoveFreeNotification(Self);
FConnection:=AValue;
if Assigned(FConnection) then
FConnection.FreeNotification(Self);
end;
procedure TSQLDBRestDataset.SetResourceName(AValue: String);
begin
if FResourceName=AValue then Exit;
CheckInactive;
FResourceName:=AValue;
end;
function TSQLDBRestDataset.DoGetDataProxy: TDataProxy;
begin
Result:=Connection.DataProxy;
end;
function TSQLDBRestDataset.StringToFieldType(S: String): TFieldType;
begin
if (s='int') then
Result:=ftInteger
else if (s='bigint') then
Result:=ftLargeInt
else if (s='float') then
Result:=ftFloat
else if (s='bool') then
Result:=ftBoolean
else if (s='date') then
Result:=ftDate
else if (s='datetime') then
Result:=ftDateTime
else if (s='time') then
Result:=ftTime
else if (s='blob') then
Result:=ftBlob
else if (s='string') then
Result:=ftString
else
if MapUnknownToStringType then
Result:=ftString
else
Raise EJSONDataset.CreateFmt('Unknown JSON data type : %s',[s]);
end;
function TSQLDBRestDataset.GetStringFieldLength(F: TJSObject; AName: String;
AIndex: Integer): integer;
Var
I,L : Integer;
D : JSValue;
begin
Result:=0;
D:=F.Properties['maxLen'];
if Not jsIsNan(toNumber(D)) then
begin
Result:=Trunc(toNumber(D));
if (Result<=0) then
Raise EJSONDataset.CreateFmt('Invalid maximum length specifier for field %s',[AName])
end
else
begin
For I:=0 to Rows.Length-1 do
begin
D:=FieldMapper.GetJSONDataForField(Aname,AIndex,Rows[i]);
if isString(D) then
begin
l:=Length(String(D));
if L>Result then
Result:=L;
end;
end;
end;
if (Result=0) then
Result:=20;
end;
procedure TSQLDBRestDataset.MetaDataToFieldDefs;
Var
A : TJSArray;
F : TJSObject;
I,FS : Integer;
N: String;
ft: TFieldType;
D : JSValue;
begin
FieldDefs.Clear;
D:=Metadata.Properties['fields'];
if Not IsArray(D) then
Raise EJSONDataset.Create('Invalid metadata object');
A:=TJSArray(D);
For I:=0 to A.Length-1 do
begin
If Not isObject(A[i]) then
Raise EJSONDataset.CreateFmt('Field definition %d in metadata is not an object',[i]);
F:=TJSObject(A[i]);
D:=F.Properties['name'];
If Not isString(D) then
Raise EJSONDataset.CreateFmt('Field definition %d in has no or invalid name property',[i]);
N:=String(D);
D:=F.Properties['type'];
If IsNull(D) or isUndefined(D) then
ft:=ftstring
else If Not isString(D) then
begin
Raise EJSONDataset.CreateFmt('Field definition %d in has invalid type property',[i])
end
else
begin
ft:=StringToFieldType(String(D));
end;
if (ft=ftString) then
fs:=GetStringFieldLength(F,N,I)
else
fs:=0;
FieldDefs.Add(N,ft,fs);
end;
end;
function TSQLDBRestDataset.DataPacketReceived(ARequest: TDataRequest): Boolean;
Var
O : TJSObject;
A : TJSArray;
smetadata,sroot : String;
begin
Result:=False;
If isNull(aRequest.Data) then
exit;
If isString(aRequest.Data) then
O:=TJSOBject(TJSJSON.Parse(String(aRequest.Data)))
else if isObject(aRequest.Data) then
O:=TJSOBject(aRequest.Data)
else
DatabaseError('Cannot handle data packet');
sRoot:=Connection.DataProperty;
sMetaData:=Connection.metaDataProperty;
if (sroot='') then
sroot:='data';
if (smetadata='') then
smetadata:='metaData';
{ if (IDField='') then
idField:='id';}
if O.hasOwnProperty(sMetaData) and isObject(o[sMetaData]) then
begin
if not Active then // Load fields from metadata
metaData:=TJSObject(o[SMetaData]);
{ if metaData.hasOwnProperty('idField') and isString(metaData['idField']) then
IDField:=string(metaData['idField']);}
end;
if O.hasOwnProperty(sRoot) and isArray(o[sRoot]) then
begin
A:=TJSArray(o[sRoot]);
Result:=A.Length>0;
AddToRows(A);
end;
end;
end.