dadataset.pas 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508
  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. FStreamerType: TDAStreamerType;
  75. FURL: String;
  76. procedure ClearConnection;
  77. Function GetDataService : TDADataAbstractService;
  78. function GetLoginService: TDASimpleLoginService;
  79. procedure SetDataserviceName(AValue: String);
  80. procedure SetLoginServiceName(AValue: String);
  81. procedure SetMessageType(AValue: TDAMessageType);
  82. procedure SetURL(AValue: String);
  83. Protected
  84. Procedure CreateChannelAndMessage; virtual;
  85. function DetectMessageType(Const aURL: String): TDAMessageType; virtual;
  86. Function CreateDataService : TDADataAbstractService; virtual;
  87. Function CreateLoginService : TDASimpleLoginService; virtual;
  88. Public
  89. Constructor create(aOwner : TComponent); override;
  90. Destructor Destroy; override;
  91. // Returns a non-auto MessageType, but raises exception if it cannot be determined;
  92. Function EnsureMessageType : TDAMessageType;
  93. // Returns DataService, but raises exception if it is nil;
  94. Function EnsureDataservice : TDADataAbstractService;
  95. // Returns SimpleLoginService, but raises exception if it is nil;
  96. Function EnsureLoginservice : TDASimpleLoginService;
  97. // Call this to login. This is an asynchronous call, check the result using OnLoginOK and OnLoginFailed calls.
  98. Procedure Login(aUserName, aPassword : String);
  99. Procedure LoginEx(aLoginString : String);
  100. // You can set this. If you didn't set this, and URL is filled, an instance will be created.
  101. Property DataService : TDADataAbstractService Read GetDataService Write FDataService;
  102. // You can set this. If you didn't set this, and URL is filled, an instance will be created.
  103. Property LoginService : TDASimpleLoginService Read GetLoginService Write FLoginService;
  104. Published
  105. // 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
  106. Property MessageType : TDAMessageType Read FMessageType Write SetMessageType;
  107. // if set, URL is used to create a DataService. Setting this while dataservice is Non-Nil will remove the reference
  108. Property URL : String Read FURL Write SetURL;
  109. // DataServiceName is used to create a DataService. Setting this while dataservice is Non-Nil will remove the reference
  110. Property DataserviceName : String Read FDataserviceName Write SetDataserviceName;
  111. // LoginServiceName is used to create a login service. Setting this while loginservice is Non-Nil will remove the reference
  112. Property LoginServiceName : String read FLoginServiceName write SetLoginServiceName;
  113. // Called when login call is executed.
  114. Property OnLogin : TDALoginSuccessEvent Read FOnLogin Write FOnLogin;
  115. // Called when login call failed. When call was executed but user is wrong OnLogin is called !
  116. Property OnLoginCallFailed : TDAFailedEvent Read FOnLoginFailed Write FOnLoginFailed;
  117. // Streamertype : format of the data package in the message.
  118. Property StreamerType : TDAStreamerType Read FStreamerType Write FStreamerType;
  119. end;
  120. implementation
  121. uses strutils, sysutils;
  122. { TDAConnection }
  123. function TDAConnection.GetDataService: TDADataAbstractService;
  124. begin
  125. if (FDataservice=Nil) then
  126. FDataservice:=CreateDataService;
  127. Result:=FDataService;
  128. end;
  129. function TDAConnection.GetLoginService: TDASimpleLoginService;
  130. begin
  131. if (FLoginService=Nil) then
  132. FLoginService:=CreateLoginService;
  133. Result:=FLoginService;
  134. end;
  135. procedure TDAConnection.SetDataserviceName(AValue: String);
  136. begin
  137. if FDataserviceName=AValue then Exit;
  138. ClearConnection;
  139. FDataserviceName:=AValue;
  140. end;
  141. procedure TDAConnection.SetLoginServiceName(AValue: String);
  142. begin
  143. if FLoginServiceName=AValue then Exit;
  144. FLoginServiceName:=AValue;
  145. end;
  146. procedure TDAConnection.SetMessageType(AValue: TDAMessageType);
  147. begin
  148. if FMessageType=AValue then Exit;
  149. ClearConnection;
  150. FMessageType:=AValue;
  151. end;
  152. procedure TDAConnection.ClearConnection;
  153. begin
  154. FDataservice:=Nil;
  155. FChannel:=Nil;
  156. FMessage:=Nil;
  157. end;
  158. procedure TDAConnection.SetURL(AValue: String);
  159. begin
  160. if FURL=AValue then Exit;
  161. ClearConnection;
  162. FURL:=AValue;
  163. end;
  164. procedure TDAConnection.CreateChannelAndMessage;
  165. begin
  166. if (FChannel=Nil) then
  167. FChannel:=TROHTTPClientChannel.New(URL);
  168. if (FMessage=Nil) then
  169. Case EnsureMessageType of
  170. mtBin : fMessage:=TROBINMessage.New;
  171. mtJSON : fMessage:=TROJSONMessage.New;
  172. end;
  173. end;
  174. function TDAConnection.DetectMessageType(Const aURL : String) : TDAMessageType;
  175. Var
  176. S : String;
  177. begin
  178. S:=aURL;
  179. Delete(S,1,RPos('/',S));
  180. case lowercase(S) of
  181. 'bin' : Result:=mtBin;
  182. 'json' : Result:=mtJSON;
  183. else
  184. Raise EDADataset.Create(Name+': Could not determine message type from URL: '+aURL);
  185. end;
  186. end;
  187. function TDAConnection.CreateDataService: TDADataAbstractService;
  188. begin
  189. Result:=Nil;
  190. if URL='' then exit;
  191. CreateChannelAndMessage;
  192. Result:=TDADataAbstractService.New(FChannel,FMessage,DataServiceName);
  193. end;
  194. function TDAConnection.CreateLoginService: TDASimpleLoginService;
  195. begin
  196. Result:=Nil;
  197. if URL='' then exit;
  198. CreateChannelAndMessage;
  199. Result:=TDASimpleLoginService.New(FChannel,FMessage,LoginServiceName);
  200. end;
  201. constructor TDAConnection.create(aOwner: TComponent);
  202. begin
  203. inherited create(aOwner);
  204. FDataServiceName:='DataService';
  205. FLoginServiceName:='LoginService';
  206. end;
  207. destructor TDAConnection.Destroy;
  208. begin
  209. ClearConnection;
  210. inherited Destroy;
  211. end;
  212. function TDAConnection.EnsureMessageType: TDAMessageType;
  213. begin
  214. Result:=MessageType;
  215. if Result=mtAuto then
  216. Result:=DetectMessageType(URL);
  217. end;
  218. function TDAConnection.EnsureDataservice: TDADataAbstractService;
  219. begin
  220. Result:=Dataservice;
  221. if (Result=Nil) then
  222. Raise EDADataset.Create('No data service available. ');
  223. end;
  224. function TDAConnection.EnsureLoginservice: TDASimpleLoginService;
  225. begin
  226. Result:=LoginService;
  227. if (Result=Nil) then
  228. Raise EDADataset.Create('No login service available. ');
  229. end;
  230. procedure TDAConnection.Login(aUserName, aPassword: String);
  231. begin
  232. EnsureLoginService.Login(aUserName,aPassword,FOnLogin,FOnLoginFailed);
  233. end;
  234. procedure TDAConnection.LoginEx(aLoginString: String);
  235. begin
  236. EnsureLoginService.LoginEx(aLoginString,FOnLogin,FOnLoginFailed);
  237. end;
  238. { TDADataset }
  239. function TDADataset.DataTypeToFieldType(s : String) : TFieldType;
  240. Const
  241. FieldStrings : Array [TFieldType] of string = (
  242. '','String', 'Integer', 'LargeInt', 'Boolean', 'Float', 'Date',
  243. 'Time', 'DateTime', 'AutoInc', 'Blob', 'Memo', 'FixedChar',
  244. 'Variant','Dataset');
  245. begin
  246. if (Copy(S,1,3)='dat') then
  247. system.Delete(S,1,3);
  248. Result:=High(TFieldType);
  249. While (Result>ftUnknown) and Not SameText(FieldStrings[Result],S) do
  250. Result:=Pred(Result);
  251. if Result=ftUnknown then
  252. case LowerCase(s) of
  253. 'widestring' : result:=ftString;
  254. 'currency' : result:=ftFloat;
  255. end;
  256. end;
  257. procedure TDADataset.SetParams(AValue: TParams);
  258. begin
  259. if FParams=AValue then Exit;
  260. FParams.Assign(AValue);
  261. end;
  262. procedure TDADataset.MetaDataToFieldDefs;
  263. begin
  264. if Not isArray(Metadata['fields']) then
  265. exit;
  266. CreateFieldDefs(TJSArray(Metadata['fields']));
  267. end;
  268. function TDADataset.DoGetDataProxy: TDataProxy;
  269. begin
  270. Result:=TDADataProxy.Create(Self);
  271. TDADataProxy(Result).Connection:=DAConnection;
  272. end;
  273. constructor TDADataset.create(aOwner: TComponent);
  274. begin
  275. inherited;
  276. DataProxy:=nil;
  277. FParams:=TParams.Create(Self);
  278. end;
  279. destructor TDADataset.Destroy;
  280. begin
  281. FreeAndNil(FParams);
  282. Inherited;
  283. end;
  284. procedure TDADataset.CreateFieldDefs(a: TJSArray);
  285. Var
  286. I : Integer;
  287. F : TDAField;
  288. fn,dt : string;
  289. fs : Integer;
  290. FT : TFieldType;
  291. req : boolean;
  292. begin
  293. FieldDefs.Clear;
  294. For I:=0 to A.length-1 do
  295. begin
  296. F:=TDAField(A.Elements[i]);
  297. fn:=F.Name;
  298. fs:=F.Size;
  299. dt:=F.type_;
  300. req:=F.Required;
  301. Ft:=DataTypeToFieldType(dT);
  302. if (ft=ftBlob) and (fs=0) then
  303. fs:=1;
  304. FieldDefs.Add(fn,ft,fs,Req);
  305. end;
  306. end;
  307. function TDADataset.CreateFieldMapper: TJSONFieldMapper;
  308. begin
  309. Result := TJSONArrayFieldMapper.Create;
  310. end;
  311. { TDADataProxy }
  312. function TDADataProxy.ConvertParams(DADS : TDADataset) : TDADataParameterDataArray;
  313. Var
  314. I : integer;
  315. begin
  316. Result:=Nil;
  317. if DADS.Params.Count=0 then
  318. Exit;
  319. SetLength(Result,DADS.Params.Count);
  320. for I:=0 to DADS.Params.Count-1 do
  321. begin
  322. Result[i].Name:=DADS.Params[i].Name;
  323. Result[i].Value:=DADS.Params[i].Value;
  324. end;
  325. end;
  326. function TDADataProxy.DoGetData(aRequest: TDataRequest): Boolean;
  327. Var
  328. TN : TDAStringArray;
  329. TIA : TDATableRequestInfoArray;
  330. TID : TDATableRequestInfoV5Data;
  331. TI : TDATableRequestInfoV5;
  332. Srt : TDAColumnSortingData;
  333. R : TDADataRequest;
  334. DADS : TDADataset;
  335. PA : TDADataParameterDataArray;
  336. DS : TDADataAbstractService;
  337. begin
  338. // DA does not support this option...
  339. if loAtEOF in aRequest.LoadOptions then
  340. exit(False);
  341. DADS:=aRequest.Dataset as TDADataset;
  342. R:=aRequest as TDADatarequest;
  343. if (Connection=Nil) then
  344. Raise EDADataset.Create(Name+': Cannot get data without connection');
  345. DS:=Connection.EnsureDataservice;
  346. TN:=TDAStringArray.New;
  347. TN.fromObject([DADS.TableName]);
  348. TID.maxRecords:=-1;
  349. TID.IncludeSchema:=True;
  350. Srt.FieldName:='';
  351. Srt.SortDirection:='Ascending';
  352. TID.Sorting:=Srt;
  353. TID.UserFilter:='';
  354. if DADS.WhereClause<>'' then
  355. TID.WhereClause:=DADS.WhereClause;
  356. PA:=ConvertParams(DADS);
  357. if Length(PA)>0 then
  358. TID.Parameters:=Pa;
  359. TIA:=TDATableRequestInfoArray.new;
  360. // We need to manually fill the array
  361. TI:=TDATableRequestInfoV5.New;
  362. TI.FromObject(TID);
  363. TJSArray(TIA.items).push(TI);
  364. DS.GetData(TN,TIA,@R.doSuccess,@R.doFail);
  365. Result:=True;
  366. end;
  367. function TDADataProxy.GetDataRequestClass: TDataRequestClass;
  368. begin
  369. Result:=TDADataRequest;
  370. end;
  371. function TDADataProxy.ProcessUpdateBatch(aBatch: TRecordUpdateBatch): Boolean;
  372. begin
  373. Result:=False;
  374. end;
  375. { TDADataRequest }
  376. procedure TDADataRequest.DoFail(response: TJSOBject; fail: String);
  377. Var
  378. O : TJSOBject;
  379. S : TStringDynArray;
  380. Msg : String;
  381. I : Integer;
  382. begin
  383. if isObject(fail) then
  384. begin
  385. O:=TJSOBject(JSValue(fail));
  386. S:=TJSObject.getOwnPropertyNames(O);
  387. for I:=0 to Length(S)-1 do
  388. begin
  389. msg:=Msg+sLineBreak+S[i];
  390. Msg:=Msg+' : '+String(O[S[i]]);
  391. end;
  392. end
  393. else
  394. Msg:=Fail;
  395. Success:=rrFail;
  396. end;
  397. procedure TDADataRequest.doSuccess(res: JSValue);
  398. Var
  399. S : String;
  400. Rows : TJSArray;
  401. DADS : TDADataset;
  402. DStr : TDADataStreamer;
  403. DT : TDADatatable;
  404. I : Integer;
  405. begin
  406. // Writeln('Data loaded, dataset active: ',Dataset.Active);
  407. DADS:=Dataset as TDADataset;
  408. if not Assigned(DADS.DAConnection) then
  409. Raise EDADataset.Create(DADS.Name+': Cannot process response, connection not available');
  410. S:=String(Res);
  411. if (DADS.DAConnection.EnsureMessageType=mtJSON) then
  412. S:=TROUtil.Frombase64(S);
  413. Case DADS.DAConnection.StreamerType of
  414. stJSON : DStr:=TDABIN2DataStreamer.new;
  415. stBIN: DStr:=TDABIN2DataStreamer.new;
  416. end;
  417. DStr.Stream:=S;
  418. DStr.initializeRead;
  419. DT:=TDADataTable.New;
  420. DStr.ReadDataset(DT);
  421. Rows:=TJSArray.New;
  422. for I:=0 to length(DT.rows)-1 do
  423. Rows.Push(DT.Rows[i].__newValues);
  424. (Dataset as TDADataset).Metadata:=New(['fields',TJSArray(DT.Fields)]);
  425. // Data:=aJSON['data'];
  426. (Dataset as TDADataset).Rows:=Rows;
  427. Success:=rrOK;
  428. DoAfterRequest;
  429. end;
  430. end.