dadataset.pas 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563
  1. {
  2. This file is part of the Free Pascal run time library.
  3. Copyright (c) 2018 by Michael Van Canneyt, member of the
  4. Free Pascal development team
  5. Dataset which talks to Remobjects Data Abstract server.
  6. See the file COPYING.FPC, included in this distribution,
  7. for details about the copyright.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  11. **********************************************************************}
  12. unit dadataset;
  13. interface
  14. uses Types, Classes, DB, jsonDataset, JS, rosdk, da, dasdk;
  15. Type
  16. EDADataset = Class(EDatabaseError);
  17. TDAConnection = Class;
  18. { TDADataset }
  19. TDADataset = class(TBaseJSONDataset)
  20. private
  21. FParams: TParams;
  22. FTableName: String;
  23. FDAConnection: TDAConnection;
  24. FWhereClause: String;
  25. function DataTypeToFieldType(s: String): TFieldType;
  26. procedure SetParams(AValue: TParams);
  27. Protected
  28. Procedure MetaDataToFieldDefs; override;
  29. Public
  30. constructor create(aOwner : TComponent); override;
  31. Destructor Destroy; override;
  32. function DoGetDataProxy: TDataProxy; override;
  33. // DA is index based. So create array field mapper.
  34. function CreateFieldMapper : TJSONFieldMapper; override;
  35. Procedure CreateFieldDefs(a : TJSArray);
  36. Property TableName : String Read FTableName Write FTableName;
  37. Property DAConnection : TDAConnection Read FDAConnection Write FDAConnection;
  38. Property Params : TParams Read FParams Write SetParams;
  39. Property WhereClause : String Read FWhereClause Write FWhereClause;
  40. end;
  41. TDADataRequest = Class(TDataRequest)
  42. Public
  43. Procedure doSuccess(res : JSValue) ;
  44. Procedure DoFail(response : TJSOBject; fail : String) ;
  45. End;
  46. { TDADataProxy }
  47. TDADataProxy = class(TDataProxy)
  48. private
  49. FConnection: TDAConnection;
  50. function ConvertParams(DADS: TDADataset): TDADataParameterDataArray;
  51. Protected
  52. Function GetDataRequestClass : TDataRequestClass; override;
  53. Public
  54. Function DoGetData(aRequest : TDataRequest) : Boolean; override;
  55. Function ProcessUpdateBatch(aBatch : TRecordUpdateBatch): Boolean; override;
  56. Property Connection : TDAConnection Read FConnection Write FConnection;
  57. end;
  58. TDAMessageType = (mtAuto, // autodetect from URL
  59. mtBin, // use BinMessage
  60. mtJSON); // Use JSONMessage.
  61. TDAStreamerType = (stJSON,stBin);
  62. { TDAConnection }
  63. TDAConnection = class(TComponent)
  64. private
  65. FDataService: TDADataAbstractService;
  66. FDataserviceName: String;
  67. FLoginService: TDASimpleLoginService;
  68. FLoginServiceName: String;
  69. FMessageType: TDAMessageType;
  70. FMessage : TROmessage;
  71. FChannel : TROHTTPClientChannel;
  72. FOnLoginFailed: TDAFailedEvent;
  73. FOnLogin: TDALoginSuccessEvent;
  74. FOnLogout: TDASuccessEvent;
  75. FOnLogoutailed: TDAFailedEvent;
  76. FOnLogoutFailed: TDAFailedEvent;
  77. FStreamerType: TDAStreamerType;
  78. FURL: String;
  79. procedure ClearConnection;
  80. function GetChannel: TROHTTPClientChannel;
  81. function GetClientID: String;
  82. Function GetDataService : TDADataAbstractService;
  83. function GetLoginService: TDASimpleLoginService;
  84. function GetMessage: TROMessage;
  85. procedure SetDataserviceName(AValue: String);
  86. procedure SetLoginServiceName(AValue: String);
  87. procedure SetMessageType(AValue: TDAMessageType);
  88. procedure SetURL(AValue: String);
  89. Protected
  90. Procedure CreateChannelAndMessage; virtual;
  91. function DetectMessageType(Const aURL: String): TDAMessageType; virtual;
  92. Function CreateDataService : TDADataAbstractService; virtual;
  93. Function CreateLoginService : TDASimpleLoginService; virtual;
  94. Public
  95. Constructor create(aOwner : TComponent); override;
  96. Destructor Destroy; override;
  97. // Returns a non-auto MessageType, but raises exception if it cannot be determined;
  98. Function EnsureMessageType : TDAMessageType;
  99. // Returns DataService, but raises exception if it is nil;
  100. Function EnsureDataservice : TDADataAbstractService;
  101. // Returns SimpleLoginService, but raises exception if it is nil;
  102. Function EnsureLoginservice : TDASimpleLoginService;
  103. // Call this to login. This is an asynchronous call, check the result using OnLoginOK and OnLoginFailed calls.
  104. Procedure Login(aUserName, aPassword : String);
  105. Procedure LoginEx(aLoginString : String);
  106. Procedure Logout;
  107. // You can set this. If you didn't set this, and URL is filled, an instance will be created.
  108. Property DataService : TDADataAbstractService Read GetDataService Write FDataService;
  109. // You can set this. If you didn't set this, and URL is filled, an instance will be created.
  110. Property LoginService : TDASimpleLoginService Read GetLoginService Write FLoginService;
  111. // You can get this to use in other service constructors
  112. Property Channel : TROHTTPClientChannel Read GetChannel;
  113. Property Message : TROMessage Read GetMessage;
  114. // Get client ID
  115. Property ClientID : String Read GetClientID;
  116. Published
  117. // If set, this is the message type that will be used when auto-creating the service. Setting this while dataservice is Non-Nil will remove the reference
  118. Property MessageType : TDAMessageType Read FMessageType Write SetMessageType;
  119. // if set, URL is used to create a DataService. Setting this while dataservice is Non-Nil will remove the reference
  120. Property URL : String Read FURL Write SetURL;
  121. // DataServiceName is used to create a DataService. Setting this while dataservice is Non-Nil will remove the reference
  122. Property DataserviceName : String Read FDataserviceName Write SetDataserviceName;
  123. // LoginServiceName is used to create a login service. Setting this while loginservice is Non-Nil will remove the reference
  124. Property LoginServiceName : String read FLoginServiceName write SetLoginServiceName;
  125. // Called when login call is executed.
  126. Property OnLogin : TDALoginSuccessEvent Read FOnLogin Write FOnLogin;
  127. // Called when login call failed. When call was executed but user is wrong OnLogin is called !
  128. Property OnLoginCallFailed : TDAFailedEvent Read FOnLoginFailed Write FOnLoginFailed;
  129. // Called when logout call is executed.
  130. Property OnLogout : TDASuccessEvent Read FOnLogout Write FOnLogout;
  131. // Called when logout call failed.
  132. Property OnLogOutCallFailed : TDAFailedEvent Read FOnLogoutailed Write FOnLogoutFailed;
  133. // Streamertype : format of the data package in the message.
  134. Property StreamerType : TDAStreamerType Read FStreamerType Write FStreamerType;
  135. end;
  136. implementation
  137. uses strutils, sysutils;
  138. { TDAConnection }
  139. function TDAConnection.GetDataService: TDADataAbstractService;
  140. begin
  141. if (FDataservice=Nil) then
  142. FDataservice:=CreateDataService;
  143. Result:=FDataService;
  144. end;
  145. function TDAConnection.GetLoginService: TDASimpleLoginService;
  146. begin
  147. if (FLoginService=Nil) then
  148. FLoginService:=CreateLoginService;
  149. Result:=FLoginService;
  150. end;
  151. function TDAConnection.GetMessage: TROMessage;
  152. begin
  153. CreateChannelAndMessage;
  154. Result:=FMessage;
  155. end;
  156. procedure TDAConnection.SetDataserviceName(AValue: String);
  157. begin
  158. if FDataserviceName=AValue then Exit;
  159. ClearConnection;
  160. FDataserviceName:=AValue;
  161. end;
  162. procedure TDAConnection.SetLoginServiceName(AValue: String);
  163. begin
  164. if FLoginServiceName=AValue then Exit;
  165. FLoginServiceName:=AValue;
  166. end;
  167. procedure TDAConnection.SetMessageType(AValue: TDAMessageType);
  168. begin
  169. if FMessageType=AValue then Exit;
  170. ClearConnection;
  171. FMessageType:=AValue;
  172. end;
  173. procedure TDAConnection.ClearConnection;
  174. begin
  175. FDataservice:=Nil;
  176. FChannel:=Nil;
  177. FMessage:=Nil;
  178. end;
  179. function TDAConnection.GetChannel: TROHTTPClientChannel;
  180. begin
  181. CreateChannelAndMessage;
  182. Result:=FChannel;
  183. end;
  184. function TDAConnection.GetClientID: String;
  185. begin
  186. if Assigned(FMessage) then
  187. Result:=FMessage.ClientID
  188. else
  189. Result:='';
  190. end;
  191. procedure TDAConnection.SetURL(AValue: String);
  192. begin
  193. if FURL=AValue then Exit;
  194. ClearConnection;
  195. FURL:=AValue;
  196. end;
  197. procedure TDAConnection.CreateChannelAndMessage;
  198. begin
  199. if (FChannel=Nil) then
  200. FChannel:=TROHTTPClientChannel.New(URL);
  201. if (FMessage=Nil) then
  202. Case EnsureMessageType of
  203. mtBin : fMessage:=TROBINMessage.New;
  204. mtJSON : fMessage:=TROJSONMessage.New;
  205. end;
  206. end;
  207. function TDAConnection.DetectMessageType(const aURL: String): TDAMessageType;
  208. Var
  209. S : String;
  210. begin
  211. S:=aURL;
  212. Delete(S,1,RPos('/',S));
  213. case lowercase(S) of
  214. 'bin' : Result:=mtBin;
  215. 'json' : Result:=mtJSON;
  216. else
  217. Raise EDADataset.Create(Name+': Could not determine message type from URL: '+aURL);
  218. end;
  219. end;
  220. function TDAConnection.CreateDataService: TDADataAbstractService;
  221. begin
  222. Result:=Nil;
  223. if URL='' then exit;
  224. CreateChannelAndMessage;
  225. Result:=TDADataAbstractService.New(FChannel,FMessage,DataServiceName);
  226. end;
  227. function TDAConnection.CreateLoginService: TDASimpleLoginService;
  228. begin
  229. Result:=Nil;
  230. if URL='' then exit;
  231. CreateChannelAndMessage;
  232. Result:=TDASimpleLoginService.New(FChannel,FMessage,LoginServiceName);
  233. end;
  234. constructor TDAConnection.create(aOwner: TComponent);
  235. begin
  236. inherited create(aOwner);
  237. FDataServiceName:='DataService';
  238. FLoginServiceName:='LoginService';
  239. end;
  240. destructor TDAConnection.Destroy;
  241. begin
  242. ClearConnection;
  243. inherited Destroy;
  244. end;
  245. function TDAConnection.EnsureMessageType: TDAMessageType;
  246. begin
  247. Result:=MessageType;
  248. if Result=mtAuto then
  249. Result:=DetectMessageType(URL);
  250. end;
  251. function TDAConnection.EnsureDataservice: TDADataAbstractService;
  252. begin
  253. Result:=Dataservice;
  254. if (Result=Nil) then
  255. Raise EDADataset.Create('No data service available. ');
  256. end;
  257. function TDAConnection.EnsureLoginservice: TDASimpleLoginService;
  258. begin
  259. Result:=LoginService;
  260. if (Result=Nil) then
  261. Raise EDADataset.Create('No login service available. ');
  262. end;
  263. procedure TDAConnection.Login(aUserName, aPassword: String);
  264. begin
  265. EnsureLoginService.Login(aUserName,aPassword,FOnLogin,FOnLoginFailed);
  266. end;
  267. procedure TDAConnection.LoginEx(aLoginString: String);
  268. begin
  269. EnsureLoginService.LoginEx(aLoginString,FOnLogin,FOnLoginFailed);
  270. end;
  271. procedure TDAConnection.Logout;
  272. begin
  273. EnsureLoginService.Logout(FOnLogout,FOnLogoutFailed);
  274. end;
  275. { TDADataset }
  276. function TDADataset.DataTypeToFieldType(s : String) : TFieldType;
  277. Const
  278. FieldStrings : Array [TFieldType] of string = (
  279. '','String', 'Integer', 'LargeInt', 'Boolean', 'Float', 'Date',
  280. 'Time', 'DateTime', 'AutoInc', 'Blob', 'Memo', 'FixedChar',
  281. 'Variant','Dataset');
  282. begin
  283. if (Copy(S,1,3)='dat') then
  284. system.Delete(S,1,3);
  285. Result:=High(TFieldType);
  286. While (Result>ftUnknown) and Not SameText(FieldStrings[Result],S) do
  287. Result:=Pred(Result);
  288. if Result=ftUnknown then
  289. case LowerCase(s) of
  290. 'widestring' : result:=ftString;
  291. 'currency' : result:=ftFloat;
  292. end;
  293. end;
  294. procedure TDADataset.SetParams(AValue: TParams);
  295. begin
  296. if FParams=AValue then Exit;
  297. FParams.Assign(AValue);
  298. end;
  299. procedure TDADataset.MetaDataToFieldDefs;
  300. begin
  301. if Not isArray(Metadata['fields']) then
  302. exit;
  303. CreateFieldDefs(TJSArray(Metadata['fields']));
  304. end;
  305. function TDADataset.DoGetDataProxy: TDataProxy;
  306. begin
  307. Result:=TDADataProxy.Create(Self);
  308. TDADataProxy(Result).Connection:=DAConnection;
  309. end;
  310. constructor TDADataset.create(aOwner: TComponent);
  311. begin
  312. inherited;
  313. DataProxy:=nil;
  314. FParams:=TParams.Create(Self);
  315. end;
  316. destructor TDADataset.Destroy;
  317. begin
  318. FreeAndNil(FParams);
  319. Inherited;
  320. end;
  321. procedure TDADataset.CreateFieldDefs(a: TJSArray);
  322. Var
  323. I : Integer;
  324. F : TDAField;
  325. FO : TJSObject absolute F;
  326. fn,dt : string;
  327. fs : Integer;
  328. FT : TFieldType;
  329. req : boolean;
  330. begin
  331. FieldDefs.Clear;
  332. For I:=0 to A.length-1 do
  333. begin
  334. F:=TDAField(A.Elements[i]);
  335. fn:=F.Name;
  336. // The JSON streamer does not create all properties :(
  337. if FO.hasOwnProperty('size') then
  338. fs:=F.Size
  339. else
  340. fs:=0;
  341. if FO.hasOwnProperty('type') then
  342. dt:=F.type_
  343. else
  344. dt:='string';
  345. if FO.hasOwnProperty('required') then
  346. req:=F.Required
  347. else
  348. Req:=false;
  349. Ft:=DataTypeToFieldType(dT);
  350. if (ft=ftBlob) and (fs=0) then
  351. fs:=1;
  352. FieldDefs.Add(fn,ft,fs,Req);
  353. end;
  354. end;
  355. function TDADataset.CreateFieldMapper: TJSONFieldMapper;
  356. begin
  357. Result := TJSONArrayFieldMapper.Create;
  358. end;
  359. { TDADataProxy }
  360. function TDADataProxy.ConvertParams(DADS : TDADataset) : TDADataParameterDataArray;
  361. Var
  362. I : integer;
  363. begin
  364. Result:=Nil;
  365. if DADS.Params.Count=0 then
  366. Exit;
  367. SetLength(Result,DADS.Params.Count);
  368. for I:=0 to DADS.Params.Count-1 do
  369. begin
  370. Result[i].Name:=DADS.Params[i].Name;
  371. Result[i].Value:=DADS.Params[i].Value;
  372. end;
  373. end;
  374. function TDADataProxy.DoGetData(aRequest: TDataRequest): Boolean;
  375. Var
  376. TN : TDAStringArray;
  377. TIA : TDATableRequestInfoArray;
  378. TID : TDATableRequestInfoV5Data;
  379. TI : TDATableRequestInfoV5;
  380. Srt : TDAColumnSortingData;
  381. R : TDADataRequest;
  382. DADS : TDADataset;
  383. PA : TDADataParameterDataArray;
  384. DS : TDADataAbstractService;
  385. begin
  386. // DA does not support this option...
  387. if loAtEOF in aRequest.LoadOptions then
  388. exit(False);
  389. DADS:=aRequest.Dataset as TDADataset;
  390. R:=aRequest as TDADatarequest;
  391. if (Connection=Nil) then
  392. Raise EDADataset.Create(Name+': Cannot get data without connection');
  393. DS:=Connection.EnsureDataservice;
  394. TN:=TDAStringArray.New;
  395. TN.fromObject([DADS.TableName]);
  396. TID.maxRecords:=-1;
  397. TID.IncludeSchema:=True;
  398. Srt.FieldName:='';
  399. Srt.SortDirection:='Ascending';
  400. TID.Sorting:=Srt;
  401. TID.UserFilter:='';
  402. if DADS.WhereClause<>'' then
  403. TID.WhereClause:=DADS.WhereClause;
  404. PA:=ConvertParams(DADS);
  405. if Length(PA)>0 then
  406. TID.Parameters:=Pa;
  407. TIA:=TDATableRequestInfoArray.new;
  408. // We need to manually fill the array
  409. TI:=TDATableRequestInfoV5.New;
  410. TI.FromObject(TID);
  411. TJSArray(TIA.items).push(TI);
  412. DS.GetData(TN,TIA,@R.doSuccess,@R.doFail);
  413. Result:=True;
  414. end;
  415. function TDADataProxy.GetDataRequestClass: TDataRequestClass;
  416. begin
  417. Result:=TDADataRequest;
  418. end;
  419. function TDADataProxy.ProcessUpdateBatch(aBatch: TRecordUpdateBatch): Boolean;
  420. begin
  421. Result:=False;
  422. end;
  423. { TDADataRequest }
  424. procedure TDADataRequest.DoFail(response: TJSOBject; fail: String);
  425. Var
  426. O : TJSOBject;
  427. S : TStringDynArray;
  428. Msg : String;
  429. I : Integer;
  430. begin
  431. if isObject(fail) then
  432. begin
  433. O:=TJSOBject(JSValue(fail));
  434. S:=TJSObject.getOwnPropertyNames(O);
  435. for I:=0 to Length(S)-1 do
  436. begin
  437. msg:=Msg+sLineBreak+S[i];
  438. Msg:=Msg+' : '+String(O[S[i]]);
  439. end;
  440. end
  441. else
  442. Msg:=Fail;
  443. Success:=rrFail;
  444. ErrorMsg:=Msg;
  445. DoAfterRequest;
  446. end;
  447. procedure TDADataRequest.doSuccess(res: JSValue);
  448. Var
  449. S : String;
  450. Rows : TJSArray;
  451. DADS : TDADataset;
  452. DStr : TDADataStreamer;
  453. DT : TDADatatable;
  454. I : Integer;
  455. begin
  456. // Writeln('Data loaded, dataset active: ',Dataset.Active);
  457. DADS:=Dataset as TDADataset;
  458. if not Assigned(DADS.DAConnection) then
  459. Raise EDADataset.Create(DADS.Name+': Cannot process response, connection not available');
  460. S:=String(Res);
  461. if (DADS.DAConnection.EnsureMessageType=mtJSON) then
  462. S:=TROUtil.Frombase64(S);
  463. Case DADS.DAConnection.StreamerType of
  464. stJSON : DStr:=TDAJSONDataStreamer.new;
  465. stBIN: DStr:=TDABIN2DataStreamer.new;
  466. end;
  467. DStr.Stream:=S;
  468. DStr.initializeRead;
  469. DT:=TDADataTable.New;
  470. DT.name:=DADS.TableName;
  471. DStr.ReadDataset(DT);
  472. Rows:=TJSArray.New;
  473. for I:=0 to length(DT.rows)-1 do
  474. Rows.Push(DT.Rows[i].__newValues);
  475. (Dataset as TDADataset).Metadata:=New(['fields',TJSArray(DT.Fields)]);
  476. // Data:=aJSON['data'];
  477. (Dataset as TDADataset).Rows:=Rows;
  478. Success:=rrOK;
  479. DoAfterRequest;
  480. end;
  481. end.