sqldbrestbridge.pp 75 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434
  1. {
  2. This file is part of the Free Pascal run time library.
  3. Copyright (c) 2019 by the Free Pascal development team
  4. SQLDB REST dispatcher component.
  5. See the file COPYING.FPC, included in this distribution,
  6. for details about the copyright.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  10. **********************************************************************}
  11. unit sqldbrestbridge;
  12. {$mode objfpc}{$H+}
  13. interface
  14. uses
  15. Classes, SysUtils, DB, SQLDB, httpdefs, httproute, fpjson, sqldbrestschema, sqldbrestio, sqldbrestdata, sqldbrestauth;
  16. Type
  17. TRestDispatcherOption = (rdoConnectionInURL, // Route includes connection :Connection/:Resource[/:ID]
  18. rdoExposeMetadata, // expose metadata resource /metadata[/:Resource]
  19. rdoCustomView, // Expose custom view /customview
  20. rdoHandleCORS, // Handle CORS requests
  21. rdoAccessCheckNeedsDB, // Authenticate after connection to database was made.
  22. rdoConnectionResource, // Enable connection managament through /_connection[/:Conn] resource
  23. rdoEmptyCORSDomainToOrigin // if CORSAllowedOrigins is empty CORS requests will mirror Origin instead of *
  24. // rdoServerInfo // Enable querying server info through /_serverinfo resource
  25. );
  26. TRestDispatcherOptions = set of TRestDispatcherOption;
  27. TRestDispatcherLogOption = (rloUser, // Include username in log messages, when available
  28. rtloHTTP, // Log HTTP request (remote, URL)
  29. rloResource, // Log resource requests (operation, resource)
  30. rloConnection, // Log database connections (connect to database)
  31. rloAuthentication, // Log authentication attempt
  32. rloSQL, // Log SQL statements. (not on user-supplied connection)
  33. rloResultStatus // Log result status.
  34. );
  35. TRestDispatcherLogOptions = Set of TRestDispatcherLogOption;
  36. Const
  37. DefaultDispatcherOptions = [rdoExposeMetadata];
  38. AllDispatcherLogOptions = [Low(TRestDispatcherLogOption)..High(TRestDispatcherLogOption)];
  39. DefaultDispatcherLogOptions = AllDispatcherLogOptions-[rloSQL];
  40. DefaultLogSQLOptions = LogAllEvents;
  41. Type
  42. { TSQLDBRestConnection }
  43. TSQLDBRestConnection = Class(TCollectionItem)
  44. private
  45. FCharSet: UTF8String;
  46. FConnection: TSQLConnection;
  47. FConnectionType: String;
  48. FDatabaseName: UTF8String;
  49. FEnabled: Boolean;
  50. FHostName: UTF8String;
  51. FName: UTF8String;
  52. FParams: TStrings;
  53. FPassword: UTF8String;
  54. FPort: Word;
  55. FRole: UTF8String;
  56. FSchemaName: UTF8String;
  57. FUserName: UTF8String;
  58. FNotifier : TComponent;
  59. function GetName: UTF8String;
  60. procedure SetConnection(AValue: TSQLConnection);
  61. procedure SetParams(AValue: TStrings);
  62. Protected
  63. Function GetDisplayName: string; override;
  64. // For use in the REST Connection resource
  65. Property SchemaName : UTF8String Read FSchemaName Write FSchemaName;
  66. Public
  67. constructor Create(ACollection: TCollection); override;
  68. Destructor Destroy; override;
  69. Procedure Assign(Source: TPersistent); override;
  70. Procedure ConfigConnection(aConn : TSQLConnection); virtual;
  71. Published
  72. // Always use this connection instance
  73. Property SingleConnection : TSQLConnection Read FConnection Write SetConnection;
  74. // Allow this connection to be used.
  75. Property Enabled : Boolean Read FEnabled Write FEnabled default true;
  76. // TSQLConnector type
  77. property ConnectionType : String Read FConnectionType Write FConnectionType;
  78. // Name for this connection
  79. Property Name : UTF8String Read GetName Write FName;
  80. // Database user password
  81. property Password : UTF8String read FPassword write FPassword;
  82. // Database username
  83. property UserName : UTF8String read FUserName write FUserName;
  84. // Database character set
  85. property CharSet : UTF8String read FCharSet write FCharSet;
  86. // Database hostname
  87. property HostName : UTF8String Read FHostName Write FHostName;
  88. // Database role
  89. Property Role : UTF8String read FRole write FRole;
  90. // Database database name
  91. property DatabaseName : UTF8String Read FDatabaseName Write FDatabaseName;
  92. // Other parameters
  93. Property Params : TStrings Read FParams Write SetParams;
  94. // Port DB is listening on
  95. Property Port : Word Read FPort Write FPort;
  96. end;
  97. { TSQLDBRestConnectionList }
  98. TSQLDBRestConnectionList = Class(TCollection)
  99. private
  100. function GetConn(aIndex : integer): TSQLDBRestConnection;
  101. procedure SetConn(aIndex : integer; AValue: TSQLDBRestConnection);
  102. Public
  103. // Index of connection by name (case insensitive)
  104. Function IndexOfConnection(const aName : UTF8string) : Integer;
  105. // Find connection by name (case insensitive), nil if none found
  106. Function FindConnection(const aName : UTF8string) : TSQLDBRestConnection;
  107. // Add new instance, setting basic properties. Return new instance
  108. Function AddConnection(Const AType,aHostName,aDatabaseName,aUserName,aPassword : UTF8String) : TSQLDBRestConnection;
  109. // Save connection definitions to JSON file.
  110. Procedure SaveToFile(Const aFileName : UTF8String);
  111. // Save connection definitions to JSON stream
  112. Procedure SaveToStream(Const aStream : TStream);
  113. // Return connection definitions as JSON object.
  114. function AsJSON(const aPropName: UTF8String=''): TJSONData; virtual;
  115. // Load connection definitions from JSON file.
  116. Procedure LoadFromFile(Const aFileName : UTF8String);
  117. // Load connection definitions from JSON stream.
  118. Procedure LoadFromStream(Const aStream : TStream);
  119. // Load connection definitions from JSON Object.
  120. Procedure FromJSON(aData: TJSONData;const aPropName: UTF8String=''); virtual;
  121. // Indexed access to connection definitions
  122. Property Connections [aIndex : integer] : TSQLDBRestConnection Read GetConn Write SetConn; default;
  123. end;
  124. { TSQLDBRestSchemaRef }
  125. TSQLDBRestSchemaRef = Class(TCollectionItem)
  126. Private
  127. FEnabled: Boolean;
  128. Fschema: TSQLDBRestSchema;
  129. FNotifier : TComponent;
  130. procedure SetSchema(AValue: TSQLDBRestSchema);
  131. Protected
  132. Function GetDisplayName: String; override;
  133. Public
  134. Constructor Create(ACollection: TCollection); override;
  135. Destructor Destroy; override;
  136. Procedure Assign(Source: TPersistent); override;
  137. Published
  138. // Schema reference
  139. Property Schema : TSQLDBRestSchema Read FSchema Write SetSchema;
  140. // Allow this schema to be used ?
  141. Property Enabled: Boolean Read FEnabled Write FEnabled default true;
  142. end;
  143. { TSQLDBRestSchemaList }
  144. TSQLDBRestSchemaList = Class(TCollection)
  145. private
  146. function GetSchema(aIndex : Integer): TSQLDBRestSchemaRef;
  147. procedure SetSchema(aIndex : Integer; AValue: TSQLDBRestSchemaRef);
  148. Public
  149. Function AddSchema (aSchema : TSQLDBRestSchema) : TSQLDBRestSchemaRef;
  150. Function IndexOfSchema(aSchemaName : String) : Integer;
  151. Property Schemas[aIndex :Integer] : TSQLDBRestSchemaRef Read GetSchema Write SetSchema;default;
  152. end;
  153. { TSQLDBRestDispatcher }
  154. TResourceAuthorizedEvent = Procedure (Sender : TObject; aRequest : TRequest; Const aResource : UTF8String; var AllowResource : Boolean) of object;
  155. TGetConnectionNameEvent = Procedure(Sender : TObject; aRequest : TRequest; Const AResource : String; var AConnectionName : UTF8String) of object;
  156. TGetConnectionEvent = Procedure(Sender : TObject; aDef : TSQLDBRestConnection; var aConnection : TSQLConnection) of object;
  157. TRestExceptionEvent = Procedure(Sender : TObject; aRequest : TRequest; Const AResource : string; E : Exception) of object;
  158. TRestOperationEvent = Procedure(Sender : TObject; aConn: TSQLConnection; aResource : TSQLDBRestResource) of object;
  159. TRestGetFormatEvent = Procedure(Sender : TObject; aRest : TRequest; var aFormat : String) of object;
  160. TRestLogEvent = Procedure(Sender : TObject; aType : TRestDispatcherLogOption; Const aMessage : UTF8String) of object;
  161. TSQLDBRestDispatcher = Class(TComponent)
  162. Private
  163. Class Var FIOClass : TRestIOClass;
  164. Class Var FDBHandlerClass : TSQLDBRestDBHandlerClass;
  165. private
  166. FAdminUserIDs: TStrings;
  167. FCORSAllowCredentials: Boolean;
  168. FCORSAllowedOrigins: String;
  169. FCORSMaxAge: Integer;
  170. FDBLogOptions: TDBEventTypes;
  171. FDispatchOptions: TRestDispatcherOptions;
  172. FInputFormat: String;
  173. FCustomViewResource : TSQLDBRestResource;
  174. FLogOptions: TRestDispatcherLogOptions;
  175. FMetadataResource : TSQLDBRestResource;
  176. FMetadataDetailResource : TSQLDBRestResource;
  177. FConnectionResource : TSQLDBRestResource;
  178. FActive: Boolean;
  179. FAfterDelete: TRestOperationEvent;
  180. FAfterGet: TRestOperationEvent;
  181. FAfterPost: TRestOperationEvent;
  182. FAfterPut: TRestOperationEvent;
  183. FAuthenticator: TRestAuthenticator;
  184. FBaseURL: UTF8String;
  185. FBeforeDelete: TRestOperationEvent;
  186. FBeforeGet: TRestOperationEvent;
  187. FBeforePost: TRestOperationEvent;
  188. FBeforePut: TRestOperationEvent;
  189. FConnections: TSQLDBRestConnectionList;
  190. FDefaultConnection: UTF8String;
  191. FEnforceLimit: Integer;
  192. FOnAllowResource: TResourceAuthorizedEvent;
  193. FOnBasicAuthentication: TBasicAuthenticationEvent;
  194. FOnException: TRestExceptionEvent;
  195. FOnGetConnection: TGetConnectionEvent;
  196. FOnGetConnectionName: TGetConnectionNameEvent;
  197. FOnGetInputFormat: TRestGetFormatEvent;
  198. FOnGetOutputFormat: TRestGetFormatEvent;
  199. FOnLog: TRestLogEvent;
  200. FOutputFormat: String;
  201. FOutputOptions: TRestOutputoptions;
  202. FSchemas: TSQLDBRestSchemaList;
  203. FListRoute: THTTPRoute;
  204. FItemRoute: THTTPRoute;
  205. FConnectionsRoute: THTTPRoute;
  206. FConnectionItemRoute: THTTPRoute;
  207. FMetadataRoute: THTTPRoute;
  208. FMetadataItemRoute: THTTPRoute;
  209. FStatus: TRestStatusConfig;
  210. FStrings: TRestStringsConfig;
  211. function GetRoutesRegistered: Boolean;
  212. procedure SetActive(AValue: Boolean);
  213. procedure SetAdminUserIDS(AValue: TStrings);
  214. procedure SetAuthenticator(AValue: TRestAuthenticator);
  215. procedure SetConnections(AValue: TSQLDBRestConnectionList);
  216. procedure SetDispatchOptions(AValue: TRestDispatcherOptions);
  217. procedure SetSchemas(AValue: TSQLDBRestSchemaList);
  218. procedure SetStatus(AValue: TRestStatusConfig);
  219. procedure SetStrings(AValue: TRestStringsConfig);
  220. Protected
  221. // Logging
  222. Function MustLog(aLog : TRestDispatcherLogOption) : Boolean; inline;
  223. procedure DoSQLLog(Sender: TObject; EventType: TDBEventType; const Msg: String); virtual;
  224. procedure DoLog(aLog: TRestDispatcherLogOption; IO : TRestIO; const aMessage: UTF8String); virtual;
  225. procedure DoLog(aLog: TRestDispatcherLogOption; IO : TRestIO; const Fmt: UTF8String;
  226. Args: array of const);
  227. // Auxiliary methods.
  228. Procedure Loaded; override;
  229. Procedure Notification(AComponent: TComponent; Operation: TOperation); override;
  230. function FindConnection(IO: TRestIO): TSQLDBRestConnection;
  231. // Factory methods. Override these to customize various helper classes.
  232. function CreateConnection: TSQLConnection; virtual;
  233. Function CreateConnectionList : TSQLDBRestConnectionList; virtual;
  234. Function CreateSchemaList : TSQLDBRestSchemaList; virtual;
  235. function CreateRestStrings: TRestStringsConfig; virtual;
  236. function CreateRestStatusConfig: TRestStatusConfig; virtual;
  237. function CreateDBHandler(IO: TRestIO): TSQLDBRestDBHandler; virtual;
  238. function CreateInputStreamer(IO: TRestIO): TRestInputStreamer; virtual;
  239. function CreateOutputStreamer(IO: TRestIO): TRestOutputStreamer; virtual;
  240. function CreateIO(aRequest: TRequest; aResponse: TResponse): TRestIO; virtual;
  241. function GetInputFormat(IO: TRestIO): String; virtual;
  242. function GetOutputFormat(IO: TRestIO): String; virtual;
  243. function GetConnectionName(IO: TRestIO): UTF8String;
  244. function GetSQLConnection(aConnection: TSQLDBRestConnection; Out aTransaction : TSQLTransaction): TSQLConnection; virtual;
  245. procedure DoneSQLConnection(aConnection: TSQLDBRestConnection; AConn: TSQLConnection; aTransaction : TSQLTransaction); virtual;
  246. // Connections dataset API
  247. procedure ConnectionsToDataset(D: TDataset); virtual;
  248. procedure DoConnectionDelete(DataSet: TDataSet); virtual;
  249. procedure DoConnectionPost(DataSet: TDataSet);virtual;
  250. procedure DatasetToConnection(D: TDataset; C: TSQLDBRestConnection); virtual;
  251. procedure ConnectionToDataset(C: TSQLDBRestConnection; D: TDataset); virtual;
  252. procedure DoConnectionResourceAllowed(aSender: TObject; aContext: TBaseRestContext; var allowResource: Boolean);
  253. // Error handling
  254. procedure CreateErrorContent(IO: TRestIO; aCode: Integer; AExtraMessage: UTF8String); virtual;
  255. procedure HandleException(E: Exception; IO: TRestIO); virtual;
  256. // REST request processing
  257. // Extract REST operation type from request
  258. procedure SetDefaultResponsecode(IO: TRestIO); virtual;
  259. // Must set result code and WWW-Authenticate header when applicable
  260. Function AuthenticateRequest(IO : TRestIO; Delayed : Boolean) : Boolean; virtual;
  261. function ExtractRestOperation(aRequest: TRequest;AccessControl : Boolean = false): TRestoperation; virtual;
  262. function FindRestResource(aResource: UTF8String): TSQLDBRestResource; virtual;
  263. function AllowRestResource(aIO : TRestIO): Boolean; virtual;
  264. function AllowRestOperation(aIO: TRestIO): Boolean; virtual;
  265. // Called twice: once before connection is established, once after.
  266. // checks rdoAccessCheckNeedsDB and availability of connection
  267. function CheckResourceAccess(IO: TRestIO): Boolean;
  268. function ExtractRestResourceName(IO: TRestIO): UTF8String; virtual;
  269. // Override if you want to create non-sqldb based resources
  270. function CreateSpecialResourceDataset(IO: TRestIO; AOwner: TComponent): TDataset; virtual;
  271. function IsSpecialResource(aResource: TSQLDBRestResource): Boolean; virtual;
  272. function FindSpecialResource(IO: TRestIO; aResource: UTF8String): TSQLDBRestResource; virtual;
  273. // Special resources for Metadata handling
  274. function CreateMetadataDataset(IO: TRestIO; AOwner: TComponent): TDataset; virtual;
  275. function CreateMetadataDetailDataset(IO: TRestIO; Const aResourceName : String; AOwner: TComponent): TDataset; virtual;
  276. function CreateConnectionDataset(IO: TRestIO; AOwner: TComponent): TDataset; virtual;
  277. function CreateMetadataDetailResource: TSQLDBRestResource; virtual;
  278. function CreateMetadataResource: TSQLDBRestResource; virtual;
  279. Function CreateConnectionResource : TSQLDBRestResource; virtual;
  280. // Custom view handling
  281. function CreateCustomViewResource: TSQLDBRestResource; virtual;
  282. function CreateCustomViewDataset(IO: TRestIO; const aSQL: String; AOwner: TComponent): TDataset;
  283. procedure ResourceToDataset(R: TSQLDBRestResource; D: TDataset); virtual;
  284. procedure SchemasToDataset(D: TDataset);virtual;
  285. // General HTTP handling
  286. procedure DoRegisterRoutes; virtual;
  287. procedure DoHandleEvent(IsBefore : Boolean;IO: TRestIO); virtual;
  288. function ResolvedCORSAllowedOrigins(aRequest: TRequest): String; virtual;
  289. procedure HandleCORSRequest(aConnection: TSQLDBRestConnection; IO: TRestIO); virtual;
  290. procedure HandleResourceRequest(aConnection : TSQLDBRestConnection; IO: TRestIO); virtual;
  291. procedure DoHandleRequest(IO: TRestIO); virtual;
  292. Public
  293. Class Procedure SetIOClass (aClass: TRestIOClass);
  294. Class Procedure SetDBHandlerClass (aClass: TSQLDBRestDBHandlerClass);
  295. Constructor Create(AOWner : TComponent); override;
  296. Destructor Destroy; override;
  297. procedure RegisterRoutes;
  298. procedure UnRegisterRoutes;
  299. procedure HandleMetadataRequest(aRequest : TRequest; aResponse : TResponse);
  300. procedure HandleConnRequest(aRequest : TRequest; aResponse : TResponse);
  301. procedure HandleRequest(aRequest : TRequest; aResponse : TResponse);
  302. Procedure VerifyPathInfo(aRequest : TRequest);
  303. Function ExposeDatabase(Const aType,aHostName,aDatabaseName,aUserName,aPassword : String; aTables : Array of String; aMinFieldOpts : TRestFieldOptions = []) : TSQLDBRestConnection;
  304. Function ExposeDatabase(Const aType,aHostName,aDatabaseName,aUserName,aPassword : String; aTables : TStrings = nil; aMinFieldOpts : TRestFieldOptions = []) : TSQLDBRestConnection;
  305. Function ExposeConnection(aOwner : TComponent; Const aConnection : TSQLDBRestConnection; aTables : TStrings = nil; aMinFieldOpts : TRestFieldOptions = []) : TSQLDBRestSchema;
  306. Function ExposeConnection(Const aConnection : TSQLDBRestConnection; aTables : TStrings = nil; aMinFieldOpts : TRestFieldOptions = []) : TSQLDBRestSchema;
  307. Property RoutesRegistered : Boolean Read GetRoutesRegistered;
  308. Published
  309. // Register or unregister HTTP routes
  310. Property Active : Boolean Read FActive Write SetActive;
  311. // List of database connections to connect to
  312. Property Connections : TSQLDBRestConnectionList Read FConnections Write SetConnections;
  313. // List of REST schemas to serve
  314. Property Schemas : TSQLDBRestSchemaList Read FSchemas Write SetSchemas;
  315. // Base URL
  316. property BasePath : UTF8String Read FBaseURL Write FBaseURL;
  317. // Default connection to use if none is detected from request/schema
  318. // This connection will also be used to authenticate the user for connection API,
  319. // so it must be set if you use SQL to authenticate the user.
  320. Property DefaultConnection : UTF8String Read FDefaultConnection Write FDefaultConnection;
  321. // Input/Output strings configuration
  322. Property Strings : TRestStringsConfig Read FStrings Write SetStrings;
  323. // HTTP Status codes configuration
  324. Property Statuses : TRestStatusConfig Read FStatus Write SetStatus;
  325. // default Output options, modifiable by query.
  326. Property OutputOptions : TRestOutputOptions Read FOutputOptions Write FOutputOptions Default allOutputOptions;
  327. // Set this to allow only this input format.
  328. Property InputFormat : String Read FInputFormat Write FInputFormat;
  329. // Set this to allow only this output format.
  330. Property OutputFormat : String Read FOutputFormat Write FOutputFormat;
  331. // Dispatcher options
  332. Property DispatchOptions : TRestDispatcherOptions Read FDispatchOptions Write SetDispatchOptions default DefaultDispatcherOptions;
  333. // Authenticator for requests
  334. Property Authenticator : TRestAuthenticator Read FAuthenticator Write SetAuthenticator;
  335. // If >0, Enforce a limit on output results.
  336. Property EnforceLimit : Integer Read FEnforceLimit Write FEnforceLimit;
  337. // Domains that are allowed to use this REST service
  338. Property CORSAllowedOrigins: String Read FCORSAllowedOrigins Write FCORSAllowedOrigins;
  339. // Access-Control-Max-Age header value. Set to zero not to send the header
  340. Property CORSMaxAge : Integer Read FCORSMaxAge Write FCORSMaxAge;
  341. // Access-Control-Allow-Credentials header value. Set to false not to send the header
  342. Property CORSAllowCredentials : Boolean Read FCORSAllowCredentials Write FCORSAllowCredentials;
  343. // UserIDs of the user(s) that are allowed to see and modify the connection resource.
  344. Property AdminUserIDs : TStrings Read FAdminUserIDs Write SetAdminUserIDS;
  345. // Logging options
  346. Property LogOptions : TRestDispatcherLogOptions Read FLogOptions write FLogOptions default DefaultDispatcherLogOptions;
  347. // SQL Log options. Only for connections managed by RestDispatcher
  348. Property LogSQLOptions : TDBEventTypes Read FDBLogOptions write FDBLogOptions default DefaultLogSQLOptions;
  349. // Called when Basic authentication is sufficient.
  350. Property OnBasicAuthentication : TBasicAuthenticationEvent Read FOnBasicAuthentication Write FOnBasicAuthentication;
  351. // Allow a particular resource or not.
  352. Property OnAllowResource : TResourceAuthorizedEvent Read FOnAllowResource Write FonAllowResource;
  353. // Called when determining the connection name for a request.
  354. Property OnGetConnectionName : TGetConnectionNameEvent Read FOnGetConnectionName Write FOnGetConnectionName;
  355. // Called when an exception happened during treatment of request.
  356. Property OnException : TRestExceptionEvent Read FOnException Write FOnException;
  357. // Called to get an actual connection.
  358. Property OnGetConnection : TGetConnectionEvent Read FOnGetConnection Write FOnGetConnection;
  359. // Called to determine input format based on request.
  360. Property OnGetInputFormat : TRestGetFormatEvent Read FOnGetInputFormat Write FOnGetInputFormat;
  361. // Called to determine output format based on request.
  362. Property OnGetOutputFormat : TRestGetFormatEvent Read FOnGetInputFormat Write FOnGetOutputFormat;
  363. // Called before a GET request.
  364. Property BeforeGet : TRestOperationEvent Read FBeforeGet Write FBeforeGet;
  365. // Called After a GET request.
  366. Property AfterGet : TRestOperationEvent Read FAfterGet Write FAfterGet;
  367. // Called before a PUT request.
  368. Property BeforePut : TRestOperationEvent Read FBeforePut Write FBeforePut;
  369. // Called After a PUT request.
  370. Property AfterPut : TRestOperationEvent Read FAfterPut Write FAfterPut;
  371. // Called before a POST request.
  372. Property BeforePost : TRestOperationEvent Read FBeforePost Write FBeforePost;
  373. // Called After a POST request.
  374. Property AfterPost : TRestOperationEvent Read FAfterPost Write FAfterPost;
  375. // Called before a DELETE request.
  376. Property BeforeDelete : TRestOperationEvent Read FBeforeDelete Write FBeforeDelete;
  377. // Called After a DELETE request.
  378. Property AfterDelete : TRestOperationEvent Read FAfterDelete Write FAfterDelete;
  379. // Called when logging
  380. Property OnLog : TRestLogEvent Read FOnLog Write FOnLog;
  381. end;
  382. Const
  383. LogNames : Array[TRestDispatcherLogOption] of string = (
  384. 'User','HTTP','Resource','Connection','Authentication','SQL','Result'
  385. );
  386. implementation
  387. uses uriparser, fpjsonrtti, DateUtils, bufdataset, sqldbrestjson, sqldbrestconst;
  388. Type
  389. { TRestBufDataset }
  390. TRestBufDataset = class (TBufDataset)
  391. protected
  392. procedure LoadBlobIntoBuffer(FieldDef: TFieldDef; ABlobBuf: PBufBlobField); override;
  393. end;
  394. { TSchemaFreeNotifier }
  395. TSchemaFreeNotifier = Class(TComponent)
  396. FRef : TSQLDBRestSchemaRef;
  397. Procedure Notification(AComponent: TComponent; Operation: TOperation); override;
  398. end;
  399. { TConnectionFreeNotifier }
  400. TConnectionFreeNotifier = Class(TComponent)
  401. FRef : TSQLDBRestConnection;
  402. Procedure Notification(AComponent: TComponent; Operation: TOperation); override;
  403. end;
  404. { TRestBufDataset }
  405. procedure TRestBufDataset.LoadBlobIntoBuffer(FieldDef: TFieldDef; ABlobBuf: PBufBlobField);
  406. begin
  407. If (FieldDef=Nil) or (aBlobBuf=Nil) then
  408. exit;
  409. end;
  410. { TConnectionFreeNotifier }
  411. procedure TConnectionFreeNotifier.Notification(AComponent: TComponent; Operation: TOperation);
  412. begin
  413. inherited Notification(AComponent, Operation);
  414. if (Operation=opRemove) and Assigned(FRef) and (Fref.SingleConnection=aComponent) then
  415. Fref.SingleConnection:=Nil;
  416. end;
  417. { TSQLDBRestSchemaList }
  418. function TSQLDBRestSchemaList.GetSchema(aIndex : Integer): TSQLDBRestSchemaRef;
  419. begin
  420. Result:=TSQLDBRestSchemaRef(Items[aIndex]);
  421. end;
  422. procedure TSQLDBRestSchemaList.SetSchema(aIndex : Integer; AValue: TSQLDBRestSchemaRef);
  423. begin
  424. Items[aIndex]:=aValue;
  425. end;
  426. function TSQLDBRestSchemaList.AddSchema(aSchema: TSQLDBRestSchema): TSQLDBRestSchemaRef;
  427. begin
  428. Result:=(Add as TSQLDBRestSchemaRef);
  429. Result.Schema:=aSchema;
  430. Result.Enabled:=True;
  431. end;
  432. function TSQLDBRestSchemaList.IndexOfSchema(aSchemaName: String): Integer;
  433. begin
  434. Result:=Count-1;
  435. While (Result>=0) and Not (Assigned(GetSchema(Result).Schema) and SameText(GetSchema(Result).Schema.Name,aSchemaName)) do
  436. Dec(Result);
  437. end;
  438. { TSQLDBRestDispatcher }
  439. procedure TSQLDBRestDispatcher.SetConnections(AValue: TSQLDBRestConnectionList);
  440. begin
  441. if FConnections=AValue then Exit;
  442. FConnections.Assign(AValue);
  443. end;
  444. procedure TSQLDBRestDispatcher.SetDispatchOptions(AValue: TRestDispatcherOptions);
  445. Var
  446. DeleteConnection : Boolean;
  447. begin
  448. DeleteConnection:=(rdoConnectionInURL in FDispatchOptions) and Not (rdoConnectionInURL in aValue);
  449. if (rdoConnectionResource in aValue) then
  450. if DeleteConnection then // if user disables rdoConnectionInURL, we disable rdoConnectionResource.
  451. exclude(aValue,rdoConnectionResource)
  452. else // else we include rdoConnectionInURL...
  453. Include(aValue,rdoConnectionInURL);
  454. if FDispatchOptions=AValue then Exit;
  455. FDispatchOptions:=AValue;
  456. end;
  457. procedure TSQLDBRestDispatcher.DoConnectionResourceAllowed(aSender: TObject;
  458. aContext: TBaseRestContext; var allowResource: Boolean);
  459. begin
  460. AllowResource:=(AdminUserIDs.Count=0) or (AdminUserIDs.IndexOf(aContext.UserID)<>-1);
  461. end;
  462. procedure TSQLDBRestDispatcher.SetActive(AValue: Boolean);
  463. begin
  464. if FActive=AValue then
  465. Exit;
  466. if Not (csLoading in ComponentState) then
  467. begin
  468. if AValue then
  469. DoRegisterRoutes
  470. else
  471. UnRegisterRoutes;
  472. end;
  473. FActive:=AValue;
  474. end;
  475. function TSQLDBRestDispatcher.GetRoutesRegistered: Boolean;
  476. begin
  477. Result:=FItemRoute<>Nil;
  478. end;
  479. procedure TSQLDBRestDispatcher.SetAdminUserIDS(AValue: TStrings);
  480. begin
  481. if FAdminUserIDs=AValue then Exit;
  482. FAdminUserIDs.Assign(AValue);
  483. end;
  484. procedure TSQLDBRestDispatcher.SetAuthenticator(AValue: TRestAuthenticator);
  485. begin
  486. if FAuthenticator=AValue then Exit;
  487. if Assigned(FAuthenticator) then
  488. FAuthenticator.RemoveFreeNotification(Self);
  489. FAuthenticator:=AValue;
  490. if Assigned(FAuthenticator) then
  491. FAuthenticator.FreeNotification(Self);
  492. end;
  493. procedure TSQLDBRestDispatcher.SetSchemas(AValue: TSQLDBRestSchemaList);
  494. begin
  495. if FSchemas=AValue then Exit;
  496. FSchemas.Assign(AValue);
  497. end;
  498. procedure TSQLDBRestDispatcher.SetStatus(AValue: TRestStatusConfig);
  499. begin
  500. if FStatus=AValue then Exit;
  501. FStatus.Assign(AValue);
  502. end;
  503. procedure TSQLDBRestDispatcher.SetStrings(AValue: TRestStringsConfig);
  504. begin
  505. if FStrings=AValue then Exit;
  506. FStrings.Assign(AValue);
  507. end;
  508. function TSQLDBRestDispatcher.MustLog(aLog: TRestDispatcherLogOption): Boolean;
  509. begin
  510. Result:=aLog in FLogOptions;
  511. end;
  512. procedure TSQLDBRestDispatcher.DoSQLLog(Sender: TObject; EventType: TDBEventType; const Msg: String);
  513. Const
  514. EventNames : Array [TDBEventType] of string =
  515. ('Custom','Prepare', 'Execute', 'Fetch', 'Commit', 'RollBack', 'ParamValue', 'ActualSQL');
  516. Var
  517. aMsg : UTF8String;
  518. begin
  519. if not MustLog(rloSQl) then // avoid string ops
  520. exit;
  521. aMsg:=EventNames[EventType]+': '+Msg;
  522. if Sender is TRestIO then
  523. DoLog(rloSQL,TRestIO(Sender),aMsg)
  524. else
  525. DoLog(rloSQL,Nil,aMsg)
  526. end;
  527. procedure TSQLDBRestDispatcher.DoLog(aLog: TRestDispatcherLogOption; IO: TRestIO; const aMessage: UTF8String);
  528. Var
  529. aMsg : UTF8String;
  530. begin
  531. aMsg:='';
  532. if MustLog(aLog) and Assigned(FOnLog) then
  533. begin
  534. if MustLog(rloUser) and Assigned(IO) then
  535. begin
  536. if IO.UserID='' then
  537. aMsg:='(User: ?) '
  538. else
  539. aMsg:=Format('(User: %s) ',[IO.UserID]);
  540. end;
  541. aMsg:=aMsg+aMessage;
  542. FOnLog(Self,aLog,aMsg);
  543. end;
  544. end;
  545. procedure TSQLDBRestDispatcher.DoLog(aLog: TRestDispatcherLogOption;IO: TRestIO;
  546. const Fmt: UTF8String; Args: array of const);
  547. Var
  548. S : UTF8string;
  549. begin
  550. if not MustLog(aLog) then exit; // avoid expensive format
  551. try
  552. S:=Format(fmt,Args); // Encode ?
  553. except
  554. on E : exception do
  555. S:=Format('Error "%s" formatting "%s" with %d arguments: %s',[E.ClassName,Fmt,Length(Args),E.Message])
  556. end;
  557. DoLog(aLog,IO,S);
  558. end;
  559. procedure TSQLDBRestDispatcher.Loaded;
  560. begin
  561. inherited Loaded;
  562. if FActive then
  563. RegisterRoutes;
  564. end;
  565. procedure TSQLDBRestDispatcher.HandleConnRequest(aRequest : TRequest; aResponse : TResponse);
  566. begin
  567. aRequest.RouteParams['resource']:=Strings.ConnectionResourceName;
  568. HandleRequest(aRequest,aResponse);
  569. end;
  570. procedure TSQLDBRestDispatcher.HandleMetadataRequest(aRequest: TRequest;aResponse: TResponse);
  571. Var
  572. LogMsg,UN : UTF8String;
  573. begin
  574. if MustLog(rtloHTTP) then
  575. begin
  576. LogMsg:='';
  577. With aRequest do
  578. begin
  579. UN:=RemoteHost;
  580. if (UN='') then
  581. UN:=RemoteAddr;
  582. if (UN<>'') then
  583. LogMsg:='From: '+UN+'; ';
  584. LogMsg:=LogMsg+'URL: '+URL;
  585. end;
  586. UN:=TRestBasicAuthenticator.ExtractUserName(aRequest);
  587. if (UN<>'?') then
  588. LogMsg:='User: '+UN+LogMsg;
  589. DoLog(rtloHTTP,Nil,LogMsg);
  590. end;
  591. aRequest.RouteParams['resource']:=Strings.MetadataResourceName;
  592. HandleRequest(aRequest,aResponse);
  593. end;
  594. procedure TSQLDBRestDispatcher.DoRegisterRoutes;
  595. Var
  596. Res,C : UTF8String;
  597. begin
  598. Res:=IncludeHTTPPathDelimiter(BasePath);
  599. if (rdoConnectionResource in DispatchOptions) then
  600. begin
  601. C:=Strings.GetRestString(rpConnectionResourceName);
  602. FConnectionsRoute:=HTTPRouter.RegisterRoute(res+C,@HandleConnRequest);
  603. FConnectionItemRoute:=HTTPRouter.RegisterRoute(res+C+'/:id',@HandleConnRequest);
  604. end;
  605. if (rdoConnectionInURL in DispatchOptions) then
  606. begin
  607. // Both connection/metadata and /metadata must work.
  608. // connection/metadata is handled by HandleRequest (FindSpecialResource)
  609. // /metadata must be handled here.
  610. if (rdoExposeMetadata in DispatchOptions) then
  611. begin
  612. C:=Strings.GetRestString(rpMetadataResourceName);
  613. FMetadataRoute:=HTTPRouter.RegisterRoute(res+C,@HandleMetaDataRequest);
  614. FMetadataItemRoute:=HTTPRouter.RegisterRoute(res+C+'/:id',@HandleMetaDataRequest);
  615. end;
  616. Res:=Res+':connection/';
  617. end;
  618. Res:=Res+':resource';
  619. FListRoute:=HTTPRouter.RegisterRoute(res,@HandleRequest);
  620. FItemRoute:=HTTPRouter.RegisterRoute(Res+'/:id',@HandleRequest);
  621. end;
  622. function TSQLDBRestDispatcher.GetInputFormat(IO : TRestIO) : String;
  623. // Order is: InputFormat setting, Content-type, input format, output format if it exists as input
  624. Var
  625. U : UTF8String;
  626. D : TStreamerDef;
  627. begin
  628. Result:=InputFormat;
  629. if (Result='') then
  630. begin
  631. if Result='' then
  632. if IO.GetVariable(Fstrings.GetRestString(rpInputFormat),U,[vsQuery])<>vsNone then
  633. Result:=U;
  634. if (Result='') and (IO.Request.ContentType<>'') then
  635. begin
  636. D:=TStreamerFactory.Instance.FindStreamerByContentType(rstInput,IO.Request.ContentType);
  637. if D<>Nil then
  638. Result:=D.MyName;
  639. end;
  640. if (Result='') then
  641. if IO.GetVariable(Fstrings.GetRestString(rpOutputFormat),U,[vsQuery])<>vsNone then
  642. begin
  643. if TStreamerFactory.Instance.FindStreamerByName(rstInput,U)<>Nil then
  644. Result:=U;
  645. end;
  646. end;
  647. If Assigned(FOnGetInputFormat) then
  648. FOnGetInputFormat(Self,IO.Request,Result)
  649. end;
  650. function TSQLDBRestDispatcher.GetOutputFormat(IO : TRestIO) : String;
  651. // Order is: OutputFormat setting, output format, input Content-type, input format if it exists as output
  652. Var
  653. U : UTF8String;
  654. D : TStreamerDef;
  655. begin
  656. Result:=OutputFormat;
  657. if (Result='') then
  658. begin
  659. if IO.GetVariable(Fstrings.GetRestString(rpOutputFormat),U,[vsQuery])<>vsNone then
  660. Result:=U;
  661. if (Result='') and (IO.Request.ContentType<>'') then
  662. begin
  663. D:=TStreamerFactory.Instance.FindStreamerByContentType(rstOutput,IO.Request.ContentType);
  664. if D<>Nil then
  665. Result:=D.MyName;
  666. end;
  667. if Result='' then
  668. if IO.GetVariable(Fstrings.GetRestString(rpInputFormat),U,[vsQuery])<>vsNone then
  669. begin
  670. if TStreamerFactory.Instance.FindStreamerByName(rstOutput,U)<>Nil then
  671. Result:=U;
  672. end;
  673. end;
  674. If Assigned(FOnGetOutputFormat) then
  675. FOnGetOutputFormat(Self,IO.Request,Result)
  676. end;
  677. function TSQLDBRestDispatcher.CreateInputStreamer(IO : TRestIO): TRestInputStreamer;
  678. Var
  679. D : TStreamerDef;
  680. aName : String;
  681. begin
  682. aName:=GetInputFormat(IO);
  683. if aName='' then
  684. aName:='json';
  685. D:=TStreamerFactory.Instance.FindStreamerByName(rstInput,aName);
  686. if (D=Nil) then
  687. Raise ESQLDBRest.CreateFmt(FStatus.GetStatusCode(rsInvalidParam),SErrUnknownOrUnSupportedFormat,[aName]);
  688. Result:=TRestInputStreamer(D.MyClass.Create(IO.RequestContentStream,Fstrings,FStatus,@IO.DoGetVariable));
  689. end;
  690. function TSQLDBRestDispatcher.CreateOutputStreamer(IO : TRestIO): TRestOutputStreamer;
  691. Var
  692. D : TStreamerDef;
  693. aName : String;
  694. begin
  695. aName:=GetOutputFormat(IO);
  696. if aName='' then
  697. aName:='json';
  698. D:=TStreamerFactory.Instance.FindStreamerByName(rstOutput,aName);
  699. if (D=Nil) then
  700. Raise ESQLDBRest.CreateFmt(FStatus.GetStatusCode(rsInvalidParam),SErrUnknownOrUnSupportedFormat,[aName]);
  701. Result:=TRestOutputStreamer(D.MyClass.Create(IO.Response.ContentStream,Fstrings,FStatus,@IO.DoGetVariable));
  702. end;
  703. function TSQLDBRestDispatcher.CreateIO(aRequest: TRequest; aResponse: TResponse): TRestIO;
  704. Var
  705. aInput : TRestInputStreamer;
  706. aOutput : TRestOutputStreamer;
  707. begin
  708. aInput:=Nil;
  709. aOutput:=Nil;
  710. Result:=FIOClass.Create(aRequest,aResponse);
  711. try
  712. // Set up output
  713. Result.Response.ContentStream:=TMemoryStream.Create;
  714. Result.Response.FreeContentStream:=True;
  715. Result.SetRestStatuses(FStatus);
  716. Result.SetRestStrings(FStrings);
  717. aInput:=CreateInputStreamer(Result);
  718. aoutPut:=CreateOutPutStreamer(Result);
  719. Result.SetIO(aInput,aOutput);
  720. aInput:=Nil;
  721. aOutput:=Nil;
  722. aResponse.ContentType:=Result.RestOutput.GetContentType;
  723. Result.RestOutput.OutputOptions:=Result.GetRequestOutputOptions(OutputOptions);
  724. except
  725. On E : Exception do
  726. begin
  727. FreeAndNil(aInput);
  728. FreeAndNil(aOutput);
  729. FreeAndNil(Result);
  730. Raise;
  731. end;
  732. end;
  733. end;
  734. procedure TSQLDBRestDispatcher.CreateErrorContent(IO : TRestIO; aCode : Integer; AExtraMessage: UTF8String);
  735. begin
  736. IO.Response.Code:=aCode;
  737. IO.Response.CodeText:=aExtraMessage;
  738. IO.RestOutput.CreateErrorContent(aCode,aExtraMessage);
  739. IO.RESTOutput.FinalizeOutput;
  740. IO.Response.ContentStream.Position:=0;
  741. IO.Response.ContentLength:=IO.Response.ContentStream.Size;
  742. IO.Response.SendResponse;
  743. end;
  744. class procedure TSQLDBRestDispatcher.SetIOClass(aClass: TRestIOClass);
  745. begin
  746. FIOClass:=aClass;
  747. if FIOClass=Nil then
  748. FIOClass:=TRestIO;
  749. end;
  750. class procedure TSQLDBRestDispatcher.SetDBHandlerClass(aClass: TSQLDBRestDBHandlerClass);
  751. begin
  752. FDBHandlerClass:=aClass;
  753. if FDBHandlerClass=Nil then
  754. FDBHandlerClass:=TSQLDBRestDBHandler;
  755. end;
  756. constructor TSQLDBRestDispatcher.Create(AOWner: TComponent);
  757. begin
  758. inherited Create(AOWner);
  759. FStrings:=CreateRestStrings;
  760. FConnections:=CreateConnectionList;
  761. FSchemas:=CreateSchemaList;
  762. FOutputOptions:=allOutputOptions;
  763. FDispatchOptions:=DefaultDispatcherOptions;
  764. FLogOptions:=DefaultDispatcherLogOptions;
  765. FDBLogOptions:=DefaultLogSQLOptions;
  766. FStatus:=CreateRestStatusConfig;
  767. FCORSMaxAge:=SecsPerDay;
  768. FCORSAllowCredentials:=True;
  769. FAdminUserIDs:=TStringList.Create;
  770. end;
  771. destructor TSQLDBRestDispatcher.Destroy;
  772. begin
  773. if RoutesRegistered then
  774. UnregisterRoutes;
  775. Authenticator:=Nil;
  776. FreeAndNil(FAdminUserIDs);
  777. FreeAndNil(FCustomViewResource);
  778. FreeAndNil(FMetadataResource);
  779. FreeAndNil(FMetadataDetailResource);
  780. FreeAndNil(FConnectionResource);
  781. FreeAndNil(FSchemas);
  782. FreeAndNil(FConnections);
  783. FreeAndNil(FStrings);
  784. FreeAndNil(FStatus);
  785. inherited Destroy;
  786. end;
  787. function TSQLDBRestDispatcher.CreateRestStrings : TRestStringsConfig;
  788. begin
  789. Result:=TRestStringsConfig.Create
  790. end;
  791. function TSQLDBRestDispatcher.CreateRestStatusConfig: TRestStatusConfig;
  792. begin
  793. Result:=TRestStatusConfig.Create;
  794. end;
  795. function TSQLDBRestDispatcher.ExtractRestResourceName(IO: TRestIO): UTF8String;
  796. begin
  797. Result:=IO.Request.RouteParams['resource'];
  798. if (Result='') then
  799. Result:=IO.Request.QueryFields.Values[Strings.ResourceParam];
  800. end;
  801. function TSQLDBRestDispatcher.AllowRestResource(aIO: TRestIO): Boolean;
  802. begin
  803. Result:=aIO.Resource.AllowResource(aIO.RestContext);
  804. if Assigned(FOnAllowResource) then
  805. FOnAllowResource(Self,aIO.Request,aIO.ResourceName,Result);
  806. end;
  807. function TSQLDBRestDispatcher.CreateCustomViewResource: TSQLDBRestResource;
  808. begin
  809. Result:=TCustomViewResource.Create(Nil);
  810. Result.ResourceName:=FStrings.GetRestString(rpCustomViewResourceName);
  811. if rdoHandleCORS in DispatchOptions then
  812. Result.AllowedOperations:=[roGet,roOptions,roHead]
  813. else
  814. Result.AllowedOperations:=[roGet,roHead];
  815. end;
  816. function TSQLDBRestDispatcher.CreateMetadataResource: TSQLDBRestResource;
  817. Var
  818. O : TRestOperation;
  819. S : String;
  820. begin
  821. Result:=TSQLDBRestResource.Create(Nil);
  822. Result.ResourceName:=Strings.GetRestString(rpMetadataResourceName);
  823. if rdoHandleCORS in DispatchOptions then
  824. Result.AllowedOperations:=[roGet,roOptions,roHead]
  825. else
  826. Result.AllowedOperations:=[roGet,roHead];
  827. Result.Fields.AddField('name',rftString,[foRequired]).MaxLen:=255;
  828. Result.Fields.AddField('schemaName',rftString,[foRequired]).MaxLen:=255;
  829. for O in TRestOperation do
  830. if O<>roUnknown then
  831. begin
  832. Str(O,S);
  833. delete(S,1,2);
  834. Result.Fields.AddField(S,rftBoolean,[foRequired]);
  835. end;
  836. end;
  837. function TSQLDBRestDispatcher.CreateConnectionResource: TSQLDBRestResource;
  838. Var
  839. Def : TRestFieldOptions;
  840. begin
  841. Def:=[foInInsert,foInUpdate,foFilter];
  842. Result:=TSQLDBRestResource.Create(Nil);
  843. Result.ResourceName:=Strings.GetRestString(rpConnectionResourceName);
  844. Result.AllowedOperations:=[roGet,roPut,roPost,roDelete];
  845. if rdoHandleCORS in DispatchOptions then
  846. Result.AllowedOperations:=Result.AllowedOperations+[roOptions,roHead];
  847. Result.Fields.AddField('name',rftString,Def+[foInKey,foRequired]);
  848. Result.Fields.AddField('dbType',rftString,Def+[foRequired]);
  849. Result.Fields.AddField('dbName',rftString,Def+[foRequired]);
  850. Result.Fields.AddField('dbHostName',rftString,Def);
  851. Result.Fields.AddField('dbUserName',rftString,Def);
  852. Result.Fields.AddField('dbPassword',rftString,Def);
  853. Result.Fields.AddField('dbCharSet',rftString,Def);
  854. Result.Fields.AddField('dbRole',rftString,Def);
  855. Result.Fields.AddField('dbPort',rftInteger,Def);
  856. Result.Fields.AddField('enabled',rftBoolean,Def);
  857. Result.Fields.AddField('expose',rftBoolean,Def);
  858. Result.Fields.AddField('exposeSchemaName',rftString,Def);
  859. Result.OnResourceAllowed:=@DoConnectionResourceAllowed;
  860. end;
  861. function TSQLDBRestDispatcher.CreateMetadataDetailResource: TSQLDBRestResource;
  862. Var
  863. O : TRestFieldOption;
  864. S : String;
  865. begin
  866. Result:=TSQLDBRestResource.Create(Nil);
  867. Result.ResourceName:='metaDataField';
  868. if rdoHandleCORS in DispatchOptions then
  869. Result.AllowedOperations:=[roGet,roOptions,roHead]
  870. else
  871. Result.AllowedOperations:=[roGet,roHead];
  872. Result.Fields.AddField('name',rftString,[]).MaxLen:=255;
  873. Result.Fields.AddField('type',rftString,[]).MaxLen:=20;
  874. Result.Fields.AddField('maxlen',rftInteger,[]);
  875. Result.Fields.AddField('format',rftString,[]).MaxLen:=50;
  876. for O in TRestFieldOption do
  877. begin
  878. Str(O,S);
  879. delete(S,1,2);
  880. Result.Fields.AddField(S,rftBoolean,[]);
  881. end;
  882. end;
  883. function TSQLDBRestDispatcher.FindSpecialResource(IO : TRestIO; aResource: UTF8String): TSQLDBRestResource;
  884. Function IsCustomView : Boolean;inline;
  885. begin
  886. Result:=(rdoCustomView in DispatchOptions)
  887. and SameText(aResource,Strings.GetRestString(rpCustomViewResourceName));
  888. end;
  889. Function IsMetadata : Boolean;inline;
  890. begin
  891. Result:=(rdoExposeMetadata in DispatchOptions)
  892. and SameText(aResource,Strings.GetRestString(rpMetaDataResourceName));
  893. end;
  894. Function IsConnection : Boolean;inline;
  895. begin
  896. Result:=(rdoConnectionResource in DispatchOptions)
  897. and SameText(aResource,Strings.GetRestString(rpConnectionResourceName));
  898. end;
  899. Var
  900. N : UTF8String;
  901. begin
  902. Result:=Nil;
  903. If isCustomView then
  904. begin
  905. if FCustomViewResource=Nil then
  906. FCustomViewResource:=CreateCustomViewResource;
  907. Result:=FCustomViewResource;
  908. end
  909. else if IsConnection then
  910. begin
  911. if FConnectionResource=Nil then
  912. FConnectionResource:=CreateConnectionResource;
  913. Result:=FConnectionResource;
  914. end
  915. else If isMetadata then
  916. if (IO.GetVariable('ID',N,[vsRoute,vsQuery])=vsNone) then
  917. begin
  918. if FMetadataResource=Nil then
  919. FMetadataResource:=CreateMetadataResource;
  920. Result:=FMetadataResource;
  921. end
  922. else
  923. begin
  924. if FindRestResource(N)<>Nil then
  925. begin
  926. if FMetadataDetailResource=Nil then
  927. FMetadataDetailResource:=CreateMetadataDetailResource;
  928. Result:=FMetadataDetailResource;
  929. end;
  930. end
  931. end;
  932. function TSQLDBRestDispatcher.FindRestResource(aResource: UTF8String): TSQLDBRestResource;
  933. Var
  934. I : integer;
  935. begin
  936. Result:=Nil;
  937. I:=0;
  938. While (Result=Nil) and (I<Schemas.Count) do
  939. begin
  940. if Schemas[i].Enabled then
  941. Result:=Schemas[i].Schema.Resources.FindResourceByName(aResource);
  942. Inc(I);
  943. end;
  944. end;
  945. function TSQLDBRestDispatcher.ExtractRestOperation(aRequest: TRequest;AccessControl : Boolean = false): TRestoperation;
  946. Var
  947. M : String;
  948. begin
  949. Result:=roUnknown;
  950. if not AccessControl then
  951. M:=aRequest.Method
  952. else
  953. M:=aRequest.CustomHeaders.Values['Access-Control-Request-Method'];
  954. Case lowercase(M) of
  955. 'get' : Result:=roGet;
  956. 'put' : Result:=roPut;
  957. 'post' : Result:=roPost;
  958. 'delete' : Result:=roDelete;
  959. 'options' : Result:=roOptions;
  960. 'head' : Result:=roHead;
  961. end;
  962. end;
  963. Type
  964. { TRestSQLConnector }
  965. { THackSQLConnector }
  966. THackSQLConnector = Class(TSQLConnection)
  967. Public
  968. function DoGetNextValueSQL(const SequenceName: string; IncrementBy: Integer): string;
  969. end;
  970. TRestSQLConnector = Class(TSQLConnector)
  971. Private
  972. FUse : Integer;
  973. FRequestCount : INteger;
  974. Protected
  975. function GetNextValueSQL(const SequenceName: string; IncrementBy: Integer): string; override;
  976. Procedure StartUsing;
  977. Function DoneUsing : Boolean;
  978. end;
  979. { THackSQLConnector }
  980. function THackSQLConnector.DoGetNextValueSQL(const SequenceName: string; IncrementBy: Integer): string;
  981. begin
  982. Result:=GetNextValueSQL(SequenceName,IncrementBy);
  983. end;
  984. { TRestSQLConnector }
  985. function TRestSQLConnector.GetNextValueSQL(const SequenceName: string; IncrementBy: Integer): string;
  986. begin
  987. Result:=THackSQLConnector(Proxy).DoGetNextValueSQL(SequenceName, IncrementBy);
  988. end;
  989. procedure TRestSQLConnector.StartUsing;
  990. begin
  991. InterLockedIncrement(FUse);
  992. Inc(FRequestCount);
  993. end;
  994. function TRestSQLConnector.DoneUsing: Boolean;
  995. begin
  996. InterLockedDecrement(Fuse);
  997. Result:=(FRequestCount>100) and (FUse=0);
  998. end;
  999. function TSQLDBRestDispatcher.CreateConnection : TSQLConnection;
  1000. begin
  1001. Result:=TRestSQLConnector.Create(Self);
  1002. end;
  1003. function TSQLDBRestDispatcher.GetSQLConnection(
  1004. aConnection: TSQLDBRestConnection; out aTransaction: TSQLTransaction
  1005. ): TSQLConnection;
  1006. begin
  1007. Result:=Nil;
  1008. aTransaction:=Nil;
  1009. if aConnection=Nil then
  1010. exit;
  1011. Result:=aConnection.SingleConnection;
  1012. if (Result=Nil) then
  1013. begin
  1014. if Assigned(OnGetConnection) then
  1015. OnGetConnection(Self,aConnection,Result);
  1016. if (Result=Nil) then
  1017. begin
  1018. Result:=CreateConnection;
  1019. aConnection.ConfigConnection(Result);
  1020. aConnection.SingleConnection:=Result;
  1021. end;
  1022. end;
  1023. If (Result is TRestSQLConnector) then
  1024. TRestSQLConnector(Result).StartUsing;
  1025. aTransaction:=TSQLTransaction.Create(Self);
  1026. aTransaction.Database:=Result;
  1027. end;
  1028. procedure TSQLDBRestDispatcher.DoHandleEvent(IsBefore: Boolean; IO: TRestIO);
  1029. Var
  1030. R : TRestOperationEvent;
  1031. begin
  1032. R:=Nil;
  1033. if isBefore then
  1034. Case IO.Operation of
  1035. roGet : R:=FBeforeGet;
  1036. roPut : R:=FBeforePut;
  1037. roPost : R:=FBeforePost;
  1038. roDelete : R:=FBeforeDelete;
  1039. end
  1040. else
  1041. Case IO.Operation of
  1042. roGet : R:=FAfterGet;
  1043. roPut : R:=FAfterPut;
  1044. roPost : R:=FAfterPost;
  1045. roDelete : R:=FAfterDelete;
  1046. end;
  1047. If Assigned(R) then
  1048. R(Self,IO.Connection,IO.Resource)
  1049. end;
  1050. procedure TSQLDBRestDispatcher.DoneSQLConnection(
  1051. aConnection: TSQLDBRestConnection; AConn: TSQLConnection;
  1052. aTransaction: TSQLTransaction);
  1053. Var
  1054. NeedNil : Boolean;
  1055. begin
  1056. FreeAndNil(aTransaction);
  1057. if (aConn is TRestSQLConnector) then
  1058. begin
  1059. NeedNil:= (aConnection.SingleConnection=aConn) ;
  1060. if TRestSQLConnector(aConn).DoneUsing then
  1061. FreeAndNil(aConn);
  1062. If NeedNil then
  1063. aConnection.SingleConnection:=Nil;
  1064. end;
  1065. end;
  1066. function TSQLDBRestDispatcher.CreateDBHandler(IO: TRestIO): TSQLDBRestDBHandler;
  1067. begin
  1068. Result:=FDBHandlerClass.Create(Self) ;
  1069. Result.Init(IO,FStrings,TSQLQuery);
  1070. Result.EnforceLimit:=Self.EnforceLimit;
  1071. end;
  1072. procedure TSQLDBRestDispatcher.SetDefaultResponsecode(IO : TRestIO);
  1073. Const
  1074. DefaultCodes : Array[TRestOperation] of TRestStatus = (rsError,rsGetOK,rsPOSTOK,rsPUTOK,rsDeleteOK,rsCORSOK,rsGetOK);
  1075. DefaultTexts : Array[TRestOperation] of string = ('Internal Error','OK','Created','OK','No content','OK','OK');
  1076. Var
  1077. aCode : TRestStatus;
  1078. aText : String;
  1079. begin
  1080. aCode:=DefaultCodes[IO.Operation];
  1081. aText:=DefaultTexts[IO.Operation];
  1082. if IO.Response.Code=0 then
  1083. IO.Response.Code:=FStatus.GetStatusCode(aCode);
  1084. if (IO.Response.CodeText='') then
  1085. IO.Response.CodeText:=aText;
  1086. end;
  1087. function TSQLDBRestDispatcher.IsSpecialResource(aResource: TSQLDBRestResource
  1088. ): Boolean;
  1089. begin
  1090. Result:=(aResource<>Nil);
  1091. if not Result then exit;
  1092. Result:=(aResource=FMetadataResource) or
  1093. (aResource=FMetadataDetailResource) or
  1094. (aResource=FConnectionResource) or
  1095. (aResource=FCustomViewResource);
  1096. end;
  1097. procedure TSQLDBRestDispatcher.SchemasToDataset(D: TDataset);
  1098. Var
  1099. S : TSQLDBRestSchema;
  1100. R : TSQLDBRestResource;
  1101. O : TRestOperation;
  1102. I,J : Integer;
  1103. SO : String;
  1104. FName,FSchema : TField;
  1105. FOperations : Array[TRestOperation] of TField;
  1106. begin
  1107. FName:=D.FieldByName('name');
  1108. FSchema:=D.FieldByName('schemaName');
  1109. for O in TRestOperation do
  1110. if O<>roUnknown then
  1111. begin
  1112. Str(O,SO);
  1113. delete(SO,1,2);
  1114. FOperations[O]:=D.FieldByName(SO);
  1115. end;
  1116. For I:=0 to Schemas.Count-1 do
  1117. if Schemas[I].Enabled then
  1118. begin
  1119. S:=Schemas[I].Schema;
  1120. For J:=0 to S.Resources.Count-1 do
  1121. begin
  1122. R:=S.Resources[J];
  1123. if R.Enabled and R.InMetadata then
  1124. begin
  1125. D.Append;
  1126. FName.AsString:=R.ResourceName;
  1127. FSchema.AsString:=S.Name;
  1128. for O in TRestOperation do
  1129. if O<>roUnknown then
  1130. FOperations[O].AsBoolean:=O in R.AllowedOperations;
  1131. end;
  1132. D.Post;
  1133. end;
  1134. end;
  1135. end;
  1136. function TSQLDBRestDispatcher.CreateMetadataDataset(IO: TRestIO;
  1137. AOwner: TComponent): TDataset;
  1138. Var
  1139. BD : TRestBufDataset;
  1140. O : TRestOperation;
  1141. SO : String;
  1142. begin
  1143. if IO=Nil then exit;
  1144. BD:=TRestBufDataset.Create(aOwner);
  1145. try
  1146. Result:=BD;
  1147. Result.FieldDefs.Add('name',ftString,255,False);
  1148. Result.FieldDefs.Add('schemaName',ftString,255,False);
  1149. for O in TRestOperation do
  1150. if O<>roUnknown then
  1151. begin
  1152. Str(O,SO);
  1153. delete(SO,1,2);
  1154. Result.FieldDefs.Add(SO,ftBoolean,0,False);
  1155. end;
  1156. BD.CreateDataset;
  1157. SchemasToDataset(BD);
  1158. BD.First;
  1159. except
  1160. BD.Free;
  1161. Raise;
  1162. end;
  1163. end;
  1164. procedure TSQLDBRestDispatcher.ResourceToDataset(R: TSQLDBRestResource;
  1165. D: TDataset);
  1166. Var
  1167. F : TSQLDBRestField;
  1168. O : TRestFieldOption;
  1169. I : Integer;
  1170. SO : String;
  1171. FName,FType,fMaxLen,fFormat : TField;
  1172. FOptions : Array[TRestFieldOption] of TField;
  1173. begin
  1174. FName:=D.FieldByName('name');
  1175. FType:=D.FieldByName('type');
  1176. FMaxLen:=D.FieldByName('maxlen');
  1177. FFormat:=D.FieldByName('format');
  1178. for O in TRestFieldOption do
  1179. begin
  1180. Str(O,SO);
  1181. delete(SO,1,2);
  1182. FOptions[O]:=D.FieldByName(SO);
  1183. end;
  1184. For I:=0 to R.Fields.Count-1 do
  1185. begin
  1186. F:=R.Fields[i];
  1187. D.Append;
  1188. FName.AsString:=F.PublicName;
  1189. Ftype.AsString:=TypeNames[F.FieldType];
  1190. FMaxLen.AsInteger:=F.MaxLen;
  1191. Case F.FieldType of
  1192. rftDate : FFormat.AsString:=FStrings.GetRestString(rpDateFormat);
  1193. rftDateTime : FFormat.AsString:=FStrings.GetRestString(rpDatetimeFormat);
  1194. rftTime : FFormat.AsString:=FStrings.GetRestString(rpTimeFormat);
  1195. end;
  1196. for O in TRestFieldOption do
  1197. FOptions[O].AsBoolean:=O in F.Options;
  1198. D.Post;
  1199. end;
  1200. end;
  1201. function TSQLDBRestDispatcher.CreateMetadataDetailDataset(IO: TRestIO;
  1202. const aResourceName: String; AOwner: TComponent): TDataset;
  1203. Var
  1204. BD : TRestBufDataset;
  1205. O : TRestFieldOption;
  1206. SO : String;
  1207. R : TSQLDBRestResource;
  1208. begin
  1209. if IO=Nil then exit;
  1210. BD:=TRestBufDataset.Create(aOwner);
  1211. try
  1212. Result:=BD;
  1213. Result.FieldDefs.Add('name',ftString,255,False);
  1214. Result.FieldDefs.Add('type',ftString,255,False);
  1215. Result.FieldDefs.Add('maxlen',ftInteger,0,false);
  1216. Result.FieldDefs.Add('format',ftString,50,false);
  1217. for O in TRestFieldOption do
  1218. begin
  1219. Str(O,SO);
  1220. delete(SO,1,2);
  1221. Result.FieldDefs.Add(SO,ftBoolean,0,False);
  1222. end;
  1223. BD.CreateDataset;
  1224. R:=FindRestResource(aResourceName);
  1225. ResourceToDataset(R,BD);
  1226. BD.First;
  1227. except
  1228. BD.Free;
  1229. Raise;
  1230. end;
  1231. end;
  1232. procedure TSQLDBRestDispatcher.DatasetToConnection(D: TDataset; C : TSQLDBRestConnection);
  1233. begin
  1234. C.Name:=UTF8Encode(D.FieldByName('name').AsWideString);
  1235. C.ConnectionType:=D.FieldByName('dbType').AsString;
  1236. C.DatabaseName:=UTF8Encode(D.FieldByName('dbName').AsWideString);
  1237. C.HostName:=D.FieldByName('dbHostName').AsString;
  1238. C.UserName:=UTF8Encode(D.FieldByName('dbUserName').AsWideString);
  1239. C.Password:=UTF8Encode(D.FieldByName('dbPassword').AsWideString);
  1240. C.CharSet:=D.FieldByName('dbCharSet').AsString;
  1241. C.Role:=D.FieldByName('dbRole').AsString;
  1242. C.Port:=D.FieldByName('dbPort').AsInteger;
  1243. C.Enabled:=D.FieldByName('enabled').AsBoolean;
  1244. if D.FieldByName('expose').AsBoolean then
  1245. C.SchemaName:=D.FieldByName('exposeSchemaName').AsString;
  1246. end;
  1247. procedure TSQLDBRestDispatcher.ConnectionToDataset(C : TSQLDBRestConnection;D: TDataset);
  1248. begin
  1249. D.FieldByName('key').AsWideString:=UTF8Decode(C.Name);
  1250. D.FieldByName('name').AsWideString:=UTF8Decode(C.Name);
  1251. D.FieldByName('dbType').AsString:=C.ConnectionType;
  1252. D.FieldByName('dbName').AsWideString:=UTF8Decode(C.DatabaseName);
  1253. D.FieldByName('dbHostName').AsString:=C.HostName;
  1254. D.FieldByName('dbUserName').AsWideString:=UTF8Decode(C.UserName);
  1255. D.FieldByName('dbPassword').AsWideString:=UTF8Decode(C.Password);
  1256. D.FieldByName('dbCharSet').AsString:=C.CharSet;
  1257. D.FieldByName('dbRole').AsString:=C.Role;
  1258. D.FieldByName('dbPort').AsInteger:=C.Port;
  1259. D.FieldByName('enabled').AsBoolean:=C.Enabled;
  1260. D.FieldByName('expose').AsBoolean:=(C.SchemaName<>'');
  1261. D.FieldByName('exposeSchemaName').AsString:=C.SchemaName;
  1262. end;
  1263. procedure TSQLDBRestDispatcher.ConnectionsToDataset(D: TDataset);
  1264. Var
  1265. C : TSQLDBRestConnection;
  1266. I : Integer;
  1267. begin
  1268. For I:=0 to Connections.Count-1 do
  1269. begin
  1270. C:=Connections[i];
  1271. D.Append;
  1272. ConnectionToDataset(C,D);
  1273. D.Post;
  1274. end;
  1275. end;
  1276. procedure TSQLDBRestDispatcher.DoConnectionDelete(DataSet: TDataSet);
  1277. Var
  1278. I,J : Integer;
  1279. C : TSQLDBRestConnection;
  1280. begin
  1281. I:=Connections.IndexOfConnection(UTF8Encode(Dataset.FieldByName('name').AsWideString));
  1282. if I<>-1 then
  1283. begin
  1284. C:=Connections[i];
  1285. if C.SingleConnection<>Nil then
  1286. DoneSQLConnection(C,C.SingleConnection,Nil);
  1287. if C.SchemaName<>'' then
  1288. begin
  1289. J:=Schemas.IndexOfSchema(C.SchemaName);
  1290. if J<>-1 then
  1291. begin
  1292. Schemas[J].Schema.Free;
  1293. Schemas[J].Schema:=Nil;
  1294. end;
  1295. Schemas.Delete(J);
  1296. end;
  1297. Connections.Delete(I);
  1298. end
  1299. else
  1300. Raise ESQLDBRest.Create(404,'NOT FOUND');
  1301. end;
  1302. procedure TSQLDBRestDispatcher.DoConnectionPost(DataSet: TDataSet);
  1303. Var
  1304. isNew : Boolean;
  1305. C : TSQLDBRestConnection;
  1306. N : UTF8String;
  1307. UN : UnicodeString;
  1308. S : TSQLDBRestSchema;
  1309. begin
  1310. IsNew:=Dataset.State=dsInsert;
  1311. if IsNew then
  1312. C:=Connections.Add as TSQLDBRestConnection
  1313. else
  1314. begin
  1315. UN:=UTF8Decode(Dataset.FieldByName('key').AsString);
  1316. // C:=Connections[Dataset.RecNo-1];
  1317. C:=Connections.FindConnection(Utf8Encode(UN));
  1318. if (C=Nil) then
  1319. Raise ESQLDBRest.Create(404,'NOT FOUND');
  1320. end;
  1321. if Assigned(C.SingleConnection) then
  1322. DoneSQLConnection(C,C.SingleConnection,Nil);
  1323. DatasetToConnection(Dataset,C);
  1324. if (Dataset.FieldByName('expose').AsBoolean) and isNew then
  1325. begin
  1326. N:=C.SchemaName;
  1327. if N='' then
  1328. N:=C.Name+'schema';
  1329. if (Schemas.IndexOfSchema(N)<>-1) then
  1330. Raise ESQLDBRest.Create(400,'DUPLICATE SCHEMA');
  1331. try
  1332. S:=ExposeConnection(C,Nil);
  1333. except
  1334. if IsNew then
  1335. C.Free;
  1336. Raise;
  1337. end;
  1338. S.Name:=N;
  1339. end;
  1340. end;
  1341. function TSQLDBRestDispatcher.CreateConnectionDataset(IO: TRestIO; AOwner: TComponent): TDataset;
  1342. Var
  1343. BD : TRestBufDataset;
  1344. begin
  1345. if IO=Nil then exit;
  1346. BD:=TRestBufDataset.Create(aOwner);
  1347. try
  1348. Result:=BD;
  1349. // Key field is not exposed
  1350. Result.FieldDefs.add('key',ftWidestring,255);
  1351. Result.FieldDefs.add('name',ftWidestring,255);
  1352. Result.FieldDefs.add('dbType',ftString,20);
  1353. Result.FieldDefs.add('dbName',ftWideString,255);
  1354. Result.FieldDefs.add('dbHostName',ftString,255);
  1355. Result.FieldDefs.add('dbUserName',ftWideString,255);
  1356. Result.FieldDefs.add('dbPassword',ftWideString,255);
  1357. Result.FieldDefs.add('dbCharSet',ftString,50);
  1358. Result.FieldDefs.add('dbRole',ftString,255);
  1359. Result.FieldDefs.add('dbPort',ftInteger,0);
  1360. Result.FieldDefs.add('enabled',ftBoolean,0);
  1361. Result.FieldDefs.add('expose',ftBoolean,0);
  1362. Result.FieldDefs.add('exposeSchemaName',ftWideString,255);
  1363. BD.CreateDataset;
  1364. ConnectionsToDataset(BD);
  1365. BD.IndexDefs.Add('uName','name',[ixUnique]);
  1366. BD.IndexName:='uName';
  1367. BD.First;
  1368. BD.BeforePost:=@DoConnectionPost;
  1369. BD.BeforeDelete:=@DoConnectionDelete;
  1370. except
  1371. BD.Free;
  1372. Raise;
  1373. end;
  1374. end;
  1375. function TSQLDBRestDispatcher.CreateCustomViewDataset(IO: TRestIO;
  1376. const aSQL: String; AOwner: TComponent): TDataset;
  1377. Var
  1378. Q : TRestSQLQuery;
  1379. ST : TStatementType;
  1380. begin
  1381. ST:=IO.Connection.GetStatementInfo(aSQL).StatementType;
  1382. if (st<>stSelect) then
  1383. raise ESQLDBRest.Create(FStatus.GetStatusCode(rsInvalidParam), SErrOnlySELECTSQLAllowedInCustomView); // Should never happen.
  1384. Q:=TRestSQLQuery.Create(aOwner);
  1385. try
  1386. Q.DataBase:=IO.Connection;
  1387. Q.Transaction:=IO.Transaction;
  1388. Q.ParseSQL:=True;
  1389. Q.SQL.Text:=aSQL;
  1390. Result:=Q;
  1391. except
  1392. Q.Free;
  1393. Raise;
  1394. end;
  1395. end;
  1396. function TSQLDBRestDispatcher.CreateSpecialResourceDataset(IO: TRestIO;
  1397. AOwner: TComponent): TDataset;
  1398. Var
  1399. RN : UTF8String;
  1400. begin
  1401. Result:=Nil;
  1402. if (IO.Resource=FMetadataResource) then
  1403. Result:=CreateMetadataDataset(IO,AOwner)
  1404. else if (IO.Resource=FConnectionResource) then
  1405. Result:=CreateConnectionDataset(IO,AOwner)
  1406. else if (IO.Resource=FMetadataDetailResource) then
  1407. begin
  1408. if IO.GetVariable('ID',RN,[vsRoute,vsQuery])=vsNone then
  1409. raise ESQLDBRest.Create(FStatus.GetStatusCode(rsError), SErrCouldNotFindResourceName); // Should never happen.
  1410. Result:=CreateMetadataDetailDataset(IO,RN,AOwner)
  1411. end
  1412. else if (IO.Resource=FCustomViewResource) then
  1413. begin
  1414. if IO.GetVariable(FStrings.GetRestString(rpCustomViewSQLParam),RN,[vsRoute,vsQuery])=vsNone then
  1415. raise ESQLDBRest.Create(FStatus.GetStatusCode(rsInvalidParam), SErrNoSQLStatement); // Should never happen.
  1416. Result:=CreateCustomViewDataset(IO,RN,aOwner);
  1417. end
  1418. end;
  1419. function TSQLDBRestDispatcher.ResolvedCORSAllowedOrigins(aRequest : TRequest): String;
  1420. Var
  1421. URl : String;
  1422. uri : TURI;
  1423. begin
  1424. Result:=FCORSAllowedOrigins;
  1425. if Result='' then
  1426. begin
  1427. // Sent with CORS request
  1428. Result:=aRequest.GetCustomHeader('Origin');
  1429. if (Result='') and (rdoEmptyCORSDomainToOrigin in DispatchOptions) then
  1430. begin
  1431. // Fallback
  1432. URL:=aRequest.Referer;
  1433. if (URL<>'') then
  1434. begin
  1435. uri:=ParseURI(URL,'http',0);
  1436. Result:=Format('%s://%s',[URI.Protocol,URI.Host]);
  1437. if (URI.Port<>0) then
  1438. Result:=Result+':'+IntToStr(URI.Port);
  1439. end;
  1440. end;
  1441. end;
  1442. if Result='' then
  1443. Result:='*';
  1444. end;
  1445. procedure TSQLDBRestDispatcher.HandleCORSRequest(aConnection : TSQLDBRestConnection; IO : TRestIO);
  1446. Var
  1447. S : String;
  1448. Allowed : Boolean;
  1449. begin
  1450. Allowed:=(rdoHandleCORS in DispatchOptions) and (roOptions in IO.Resource.AllowedOperations);
  1451. if Allowed then
  1452. Allowed:=(ExtractRestOperation(IO.Request,True) in ([roUnknown]+IO.Resource.AllowedOperations));
  1453. if not Allowed then
  1454. begin
  1455. IO.Response.Code:=FStatus.GetStatusCode(rsCORSNotAllowed);
  1456. IO.Response.CodeText:='FORBIDDEN';
  1457. IO.CreateErrorResponse;
  1458. end
  1459. else
  1460. begin
  1461. IO.Response.SetCustomHeader('Access-Control-Allow-Origin',ResolvedCORSAllowedOrigins(IO.Request));
  1462. S:=IO.Resource.GetHTTPAllow;
  1463. IO.Response.SetCustomHeader('Access-Control-Allow-Methods',S);
  1464. IO.Response.SetCustomHeader('Access-Control-Allow-Headers','x-requested-with, content-type, authorization');
  1465. if CorsMaxAge>0 then
  1466. IO.Response.SetCustomHeader('Access-Control-Max-Age',IntToStr(CorsMaxAge));
  1467. IO.Response.SetCustomHeader('Access-Control-Allow-Credentials',BoolToStr(CORSAllowCredentials,'true','false'));
  1468. IO.Response.Code:=FStatus.GetStatusCode(rsCORSOK);
  1469. IO.Response.CodeText:='OK';
  1470. end;
  1471. end;
  1472. procedure TSQLDBRestDispatcher.HandleResourceRequest(aConnection : TSQLDBRestConnection; IO : TRestIO);
  1473. Var
  1474. Conn : TSQLConnection;
  1475. TR : TSQLTransaction;
  1476. H : TSQLDBRestDBHandler;
  1477. l,o : Int64;
  1478. begin
  1479. if MustLog(rloResource) then
  1480. DoLog(rloResource,IO,'Resource: %s; Operation: %s',[IO.ResourceName,RestMethods[IO.Operation]]);
  1481. H:=Nil;
  1482. Conn:=GetSQLConnection(aConnection,Tr);
  1483. try
  1484. IO.SetConn(Conn,TR);
  1485. Try
  1486. if MustLog(rloConnection) then
  1487. if Assigned(Conn) then
  1488. DoLog(rloConnection,IO,'Using connection to Host: %s; Database: %s',[Conn.HostName,Conn.DatabaseName])
  1489. else
  1490. DoLog(rloConnection,IO,'Resource %s does not require connection',[IO.ResourceName]);
  1491. if assigned(Conn) and MustLog(rloSQL) then
  1492. begin
  1493. Conn.LogEvents:=LogSQLOptions;
  1494. Conn.OnLog:[email protected];
  1495. end;
  1496. if (rdoHandleCORS in DispatchOptions) then
  1497. begin
  1498. IO.Response.SetCustomHeader('Access-Control-Allow-Origin',ResolvedCORSAllowedOrigins(IO.Request));
  1499. IO.Response.SetCustomHeader('Access-Control-Allow-Credentials',BoolToStr(CORSAllowCredentials,'true','false'));
  1500. end;
  1501. if not AuthenticateRequest(IO,True) then
  1502. exit;
  1503. if Not CheckResourceAccess(IO) then
  1504. exit;
  1505. DoHandleEvent(True,IO);
  1506. H:=CreateDBHandler(IO);
  1507. if IsSpecialResource(IO.Resource) then
  1508. begin
  1509. H.ExternalDataset:=CreateSpecialResourceDataset(IO,H);
  1510. if (IO.Resource=FCustomViewResource) then
  1511. H.DeriveResourceFromDataset:=True;
  1512. H.EmulateOffsetLimit:=IO.GetLimitOffset(EnforceLimit,l,o);
  1513. end;
  1514. H.ExecuteOperation;
  1515. DoHandleEvent(False,IO);
  1516. if Assigned(TR) then
  1517. TR.Commit;
  1518. SetDefaultResponseCode(IO);
  1519. except
  1520. TR.RollBack;
  1521. Raise;
  1522. end;
  1523. finally
  1524. IO.SetConn(Nil,Nil);
  1525. DoneSQLConnection(aConnection,Conn,Tr);
  1526. end;
  1527. end;
  1528. function TSQLDBRestDispatcher.GetConnectionName(IO: TRestIO): UTF8String;
  1529. Var
  1530. N : UTF8String;
  1531. R : TSQLDBRestResource;
  1532. begin
  1533. R:=IO.Resource;
  1534. N:='';
  1535. if (N='') then
  1536. N:=R.ConnectionName;
  1537. if (N='') and assigned(R.GetSchema) then
  1538. N:=R.GetSchema.ConnectionName;
  1539. if (N='') then
  1540. IO.GetVariable(Strings.ConnectionParam,N,[vsQuery]);
  1541. if (N='') and (rdoConnectionInURL in DispatchOptions) then
  1542. IO.GetVariable(Strings.ConnectionParam,N,[vsQuery]);
  1543. If Assigned(FOnGetConnectionName) then
  1544. FOnGetConnectionName(Self,IO.Request,R.ResourceName,N);
  1545. if (N='') then
  1546. N:=DefaultConnection;
  1547. Result:=N;
  1548. end;
  1549. function TSQLDBRestDispatcher.FindConnection(IO: TRestIO): TSQLDBRestConnection;
  1550. Var
  1551. N : UTF8String;
  1552. begin
  1553. N:=GetConnectionName(IO);
  1554. // If we have a name, look for it
  1555. if (N<>'') then
  1556. begin
  1557. Result:=Connections.FindConnection(N);
  1558. if Assigned(Result) and not (Result.Enabled) then
  1559. Result:=Nil;
  1560. end
  1561. else if Connections.Count=1 then
  1562. Result:=Connections[0]
  1563. else
  1564. Result:=Nil;
  1565. end;
  1566. function TSQLDBRestDispatcher.CreateConnectionList: TSQLDBRestConnectionList;
  1567. begin
  1568. Result:=TSQLDBRestConnectionList.Create(TSQLDBRestConnection);
  1569. end;
  1570. function TSQLDBRestDispatcher.CreateSchemaList: TSQLDBRestSchemaList;
  1571. begin
  1572. Result:=TSQLDBRestSchemaList.Create(TSQLDBRestSchemaRef);
  1573. end;
  1574. function TSQLDBRestDispatcher.AllowRestOperation(aIO: TRestIO): Boolean;
  1575. begin
  1576. Result:=(aIO.Operation in aIO.Resource.GetAllowedOperations(aIO.RestContext));
  1577. end;
  1578. function TSQLDBRestDispatcher.CheckResourceAccess(IO: TRestIO): Boolean;
  1579. Var
  1580. NeedDB : Boolean;
  1581. begin
  1582. NeedDB:=(rdoAccessCheckNeedsDB in DispatchOptions);
  1583. Result:=NeedDB<>Assigned(IO.Connection);
  1584. if Result then
  1585. exit;
  1586. Result:=AllowRestResource(IO);
  1587. if not Result then
  1588. CreateErrorContent(IO,FStatus.GetStatusCode(rsResourceNotAllowed),'FORBIDDEN')
  1589. else
  1590. begin
  1591. Result:=AllowRestOperation(IO);
  1592. if not Result then
  1593. CreateErrorContent(IO,FStatus.GetStatusCode(rsRestOperationNotAllowed),'METHOD NOT ALLOWED')
  1594. end;
  1595. end;
  1596. procedure TSQLDBRestDispatcher.DoHandleRequest(IO : TRestIO);
  1597. var
  1598. ResourceName : UTF8String;
  1599. Operation : TRestOperation;
  1600. Resource : TSQLDBRestResource;
  1601. Connection : TSQLDBRestConnection;
  1602. begin
  1603. Operation:=ExtractRestOperation(IO.Request);
  1604. if (Operation=roUnknown) then
  1605. CreateErrorContent(IO,FStatus.GetStatusCode(rsInvalidMethod),'INVALID METHOD')
  1606. else
  1607. begin
  1608. IO.SetOperation(Operation);
  1609. ResourceName:=ExtractRestResourceName(IO);
  1610. if (ResourceName='') then
  1611. CreateErrorContent(IO,FStatus.GetStatusCode(rsNoResourceSpecified),'INVALID RESOURCE')
  1612. else
  1613. begin
  1614. Resource:=FindSpecialResource(IO,ResourceName);
  1615. If Resource=Nil then
  1616. Resource:=FindRestResource(ResourceName);
  1617. if Resource=Nil then
  1618. CreateErrorContent(IO,FStatus.GetStatusCode(rsUnknownResource),'NOT FOUND')
  1619. else
  1620. begin
  1621. IO.SetResource(Resource);
  1622. Connection:=FindConnection(IO);
  1623. if (Connection=Nil) and not IsSpecialResource(Resource) then
  1624. begin
  1625. if (rdoConnectionInURL in DispatchOptions) then
  1626. CreateErrorContent(IO,FStatus.GetStatusCode(rsNoConnectionSpecified),Format(SErrNoconnection,[GetConnectionName(IO)]))
  1627. else
  1628. CreateErrorContent(IO,FStatus.GetStatusCode(rsError), Format(SErrNoconnection,[GetConnectionName(IO)]));
  1629. end
  1630. else if CheckResourceAccess(IO) then
  1631. if Operation=roOptions then
  1632. HandleCORSRequest(Connection,IO)
  1633. else
  1634. HandleResourceRequest(Connection,IO);
  1635. end;
  1636. end;
  1637. end;
  1638. end;
  1639. procedure TSQLDBRestDispatcher.UnRegisterRoutes;
  1640. Procedure Un(Var a : THTTPRoute);
  1641. begin
  1642. if A=Nil then
  1643. exit;
  1644. HTTPRouter.DeleteRoute(A);
  1645. A:=Nil;
  1646. end;
  1647. begin
  1648. Un(FListRoute);
  1649. Un(FItemRoute);
  1650. Un(FConnectionItemRoute);
  1651. Un(FConnectionsRoute);
  1652. Un(FMetadataItemRoute);
  1653. Un(FMetadataRoute);
  1654. end;
  1655. procedure TSQLDBRestDispatcher.RegisterRoutes;
  1656. begin
  1657. if (FListRoute<>Nil) then
  1658. UnRegisterRoutes;
  1659. DoRegisterRoutes;
  1660. end;
  1661. procedure TSQLDBRestDispatcher.HandleException(E : Exception; IO : TRestIO);
  1662. Function StripCR(S : String) : String;
  1663. begin
  1664. Result:=StringReplace(S,#13#10,' ',[rfReplaceAll]);
  1665. Result:=StringReplace(Result,#13,' ',[rfReplaceAll]);
  1666. Result:=StringReplace(Result,#10,' ',[rfReplaceAll]);
  1667. end;
  1668. Var
  1669. Code : Integer;
  1670. Msg : String;
  1671. begin
  1672. try
  1673. if Assigned(FOnException) then
  1674. FOnException(Self,IO.Request,IO.ResourceName,E);
  1675. if not IO.Response.ContentSent then
  1676. begin
  1677. Code:=0;
  1678. If E is ESQLDBRest then
  1679. begin
  1680. Code:=ESQLDBRest(E).ResponseCode;
  1681. Msg:=E.Message;
  1682. end;
  1683. if (Code=0) then
  1684. begin
  1685. Code:=FStatus.GetStatusCode(rsError);
  1686. Msg:=Format(SErrUnexpectedException,[E.ClassName,E.Message]);
  1687. end;
  1688. IO.Response.Code:=Code;
  1689. IO.Response.CodeText:=StripCR(Msg);
  1690. if (IO.Response.Code=405) and Assigned(IO.Resource) then
  1691. IO.Response.Allow:=IO.Resource.GetHTTPAllow; // ([rmHead,rmOptions]) ?
  1692. IO.RESTOutput.CreateErrorContent(Code,Msg);
  1693. end;
  1694. except
  1695. on Ex : exception do
  1696. begin
  1697. IO.Response.Code:=FStatus.GetStatusCode(rsError);
  1698. IO.Response.CodeText:=Format('Unexpected exception %s while handling original exception %s : "%s" (Original: "%s")',[Ex.ClassName,E.ClassName,Ex.Message,E.Message]);
  1699. end;
  1700. end;
  1701. end;
  1702. function TSQLDBRestDispatcher.AuthenticateRequest(IO: TRestIO; Delayed : Boolean): Boolean;
  1703. Var
  1704. B : TRestBasicAuthenticator;
  1705. A : TRestAuthenticator;
  1706. begin
  1707. A:=Nil;
  1708. B:=Nil;
  1709. If Assigned(FAuthenticator) then
  1710. A:=FAuthenticator
  1711. else If Assigned(FOnBAsicAuthentication) then
  1712. begin
  1713. B:=TRestBasicAuthenticator.Create(Self);
  1714. A:=B;
  1715. B.OnBasicAuthentication:=Self.OnBasicAuthentication;
  1716. end;
  1717. try
  1718. Result:=A=Nil;
  1719. if Not Result Then
  1720. begin
  1721. Result:=(A.NeedConnection<>Delayed);
  1722. If Not Result then
  1723. begin
  1724. Result:=A.AuthenticateRequest(IO);
  1725. if MustLog(rloAuthentication) then
  1726. if Result then
  1727. DoLog(rloAuthentication,IO,'Authenticated user: %s',[IO.UserID])
  1728. else
  1729. DoLog(rloAuthentication,IO,'Authentication failed for user: %s',[TRestBasicAuthenticator.ExtractUserName(IO.Request)]);
  1730. end;
  1731. end;
  1732. finally
  1733. if Assigned(B) then
  1734. B.Free;
  1735. end;
  1736. end;
  1737. procedure TSQLDBRestDispatcher.Notification(AComponent: TComponent;
  1738. Operation: TOperation);
  1739. begin
  1740. inherited Notification(AComponent, Operation);
  1741. if Operation=opRemove then
  1742. begin
  1743. if AComponent=FAuthenticator then
  1744. FAuthenticator:=Nil
  1745. end;
  1746. end;
  1747. procedure TSQLDBRestDispatcher.HandleRequest(aRequest: TRequest; aResponse: TResponse);
  1748. Var IO : TRestIO;
  1749. begin
  1750. aResponse.Code:=0; // Sentinel
  1751. IO:=CreateIO(aRequest,aResponse);
  1752. try
  1753. try
  1754. // Call initstreaming only here, so IO has set var callback.
  1755. // First output, then input
  1756. IO.RestOutput.InitStreaming;
  1757. IO.RestInput.InitStreaming;
  1758. IO.OnSQLLog:[email protected];
  1759. if SameText('OPTIONS',aRequest.Method) or AuthenticateRequest(IO,False) then
  1760. DoHandleRequest(IO)
  1761. except
  1762. On E : Exception do
  1763. HandleException(E,IO);
  1764. end;
  1765. Finally
  1766. // Make sure there is a document in case of error
  1767. if (aResponse.ContentStream.Size=0) and Not ((aResponse.Code div 100)=2) then
  1768. IO.RESTOutput.CreateErrorContent(aResponse.Code,aResponse.CodeText);
  1769. if Not ((IO.Operation in [roOptions,roHEAD]) or aResponse.ContentSent) then
  1770. IO.RestOutput.FinalizeOutput;
  1771. aResponse.ContentStream.Position:=0;
  1772. aResponse.ContentLength:=aResponse.ContentStream.Size;
  1773. if not aResponse.ContentSent then
  1774. aResponse.SendContent;
  1775. if MustLog(rloResultStatus) then
  1776. DoLog(rloResultStatus,IO,'Resource: %s; Operation: %s; Status: %d; Text: %s',[IO.ResourceName,RestMethods[IO.Operation],aResponse.Code,aResponse.CodeText]);
  1777. IO.Free;
  1778. end;
  1779. end;
  1780. procedure TSQLDBRestDispatcher.VerifyPathInfo(aRequest: TRequest);
  1781. Var
  1782. Full,Path : String;
  1783. BasePaths : TStringArray;
  1784. I : Integer;
  1785. begin
  1786. // Check & discard basepath parts of the URL
  1787. Path:=aRequest.GetNextPathInfo;
  1788. Full:=BasePath;
  1789. BasePaths:=Full.Split('/',TStringSplitOptions.ExcludeEmpty);
  1790. I:=0;
  1791. While (I<Length(BasePaths)) and SameText(Path,BasePaths[i]) do
  1792. begin
  1793. inc(I);
  1794. Path:=aRequest.GetNextPathInfo;
  1795. end;
  1796. if (I<Length(BasePaths)) then
  1797. Raise ESQLDBRest.Create(400,'NOT FOUND');
  1798. // Path1 is now either connection or resource
  1799. if (rdoConnectionInURL in DispatchOptions) then
  1800. begin
  1801. // Both /metadata and /connection/metadata are possible
  1802. if not ((rdoExposeMetadata in DispatchOptions) and SameText(Path,Strings.getRestString(rpMetadataResourceName))) then
  1803. begin
  1804. aRequest.RouteParams['connection']:=Path;
  1805. Path:=aRequest.GetNextPathInfo;
  1806. end;
  1807. end;
  1808. aRequest.RouteParams['resource']:=Path;
  1809. // Next part is ID
  1810. Path:=aRequest.GetNextPathInfo;
  1811. if (Path<>'') then
  1812. aRequest.RouteParams['ID']:=Path;
  1813. end;
  1814. function TSQLDBRestDispatcher.ExposeDatabase(const aType, aHostName, aDatabaseName, aUserName, aPassword: String;
  1815. aTables: array of String; aMinFieldOpts: TRestFieldOptions): TSQLDBRestConnection;
  1816. Var
  1817. L : TStringList;
  1818. S : String;
  1819. begin
  1820. L:=TStringList.Create;
  1821. try
  1822. L.Capacity:=Length(aTables);
  1823. For S in aTables do
  1824. L.Add(S);
  1825. L.Sorted:=True;
  1826. Result:=ExposeDatabase(aType, aHostName, aDatabaseName, aUserName, aPassword,L, aMinFieldOpts);
  1827. finally
  1828. l.Free;
  1829. end;
  1830. end;
  1831. function TSQLDBRestDispatcher.ExposeDatabase(const aType, aHostName, aDatabaseName, aUserName, aPassword: String; aTables : TStrings = nil; aMinFieldOpts : TRestFieldOptions = []): TSQLDBRestConnection;
  1832. begin
  1833. Result:=Connections.AddConnection(aType,aHostName,aDatabaseName,aUserName,aPassword);
  1834. ExposeConnection(Result,aTables,aMinFieldOpts);
  1835. end;
  1836. function TSQLDBRestDispatcher.ExposeConnection(aOwner: TComponent;
  1837. const aConnection: TSQLDBRestConnection; aTables: TStrings;
  1838. aMinFieldOpts: TRestFieldOptions): TSQLDBRestSchema;
  1839. Var
  1840. Conn : TSQLConnection;
  1841. TR : TSQLTransaction;
  1842. S : TSQLDBRestSchema;
  1843. begin
  1844. Conn:=GetSQLConnection(aConnection,TR);
  1845. S:=TSQLDBRestSchema.Create(aOwner);
  1846. S.Name:='Schema'+aConnection.Name;
  1847. S.PopulateResources(Conn,aTables,aMinFieldOpts);
  1848. if not (rdoConnectionInURL in DispatchOptions) then
  1849. S.ConnectionName:=aConnection.Name;
  1850. Schemas.AddSchema(S).Enabled:=true;
  1851. Result:=S;
  1852. end;
  1853. function TSQLDBRestDispatcher.ExposeConnection(
  1854. const aConnection: TSQLDBRestConnection; aTables: TStrings;
  1855. aMinFieldOpts: TRestFieldOptions): TSQLDBRestSchema;
  1856. begin
  1857. Result:=ExposeConnection(Self,aConnection,aTables,aMinFieldOpts);
  1858. end;
  1859. { TSchemaFreeNotifier }
  1860. procedure TSchemaFreeNotifier.Notification(AComponent: TComponent; Operation: TOperation);
  1861. begin
  1862. inherited Notification(AComponent, Operation);
  1863. if (Operation=opRemove) and Assigned(FRef) and (Fref.Schema=aComponent) then
  1864. Fref.Schema:=nil;
  1865. end;
  1866. { TSQLDBRestSchemaRef }
  1867. procedure TSQLDBRestSchemaRef.SetSchema(AValue: TSQLDBRestSchema);
  1868. begin
  1869. if (FSchema=AValue) then Exit;
  1870. if Assigned(FSchema) then
  1871. FSchema.RemoveFreeNotification(FNotifier);
  1872. FSchema:=AValue;
  1873. if Assigned(FSchema) then
  1874. FSchema.FreeNotification(FNotifier);
  1875. end;
  1876. function TSQLDBRestSchemaRef.GetDisplayName: String;
  1877. begin
  1878. if Assigned(FSchema) then
  1879. Result:=FSchema.Name
  1880. else
  1881. Result:=inherited GetDisplayName;
  1882. end;
  1883. constructor TSQLDBRestSchemaRef.Create(ACollection: TCollection);
  1884. begin
  1885. inherited Create(ACollection);
  1886. FNotifier:=TSchemaFreeNotifier.Create(Nil);
  1887. TSchemaFreeNotifier(FNotifier).FRef:=Self;
  1888. FEnabled:=True;
  1889. end;
  1890. destructor TSQLDBRestSchemaRef.Destroy;
  1891. begin
  1892. FreeAndNil(FNotifier);
  1893. inherited Destroy;
  1894. end;
  1895. procedure TSQLDBRestSchemaRef.Assign(Source: TPersistent);
  1896. Var
  1897. R : TSQLDBRestSchemaRef;
  1898. begin
  1899. if (Source is TSQLDBRestSchemaRef) then
  1900. begin
  1901. R:=Source as TSQLDBRestSchemaRef;
  1902. Schema:=R.Schema;
  1903. Enabled:=R.Enabled;
  1904. end
  1905. else
  1906. inherited Assign(Source);
  1907. end;
  1908. { TSQLDBRestConnectionList }
  1909. function TSQLDBRestConnectionList.GetConn(aIndex : integer): TSQLDBRestConnection;
  1910. begin
  1911. Result:=TSQLDBRestConnection(Items[aIndex]);
  1912. end;
  1913. procedure TSQLDBRestConnectionList.SetConn(aIndex : integer; AValue: TSQLDBRestConnection);
  1914. begin
  1915. Items[aIndex]:=aValue;
  1916. end;
  1917. function TSQLDBRestConnectionList.IndexOfConnection(const aName: UTF8string
  1918. ): Integer;
  1919. begin
  1920. Result:=Count-1;
  1921. While (Result>=0) and not SameText(GetConn(Result).Name,aName) do
  1922. Dec(Result);
  1923. end;
  1924. function TSQLDBRestConnectionList.FindConnection(const aName: UTF8string): TSQLDBRestConnection;
  1925. Var
  1926. Idx : Integer;
  1927. begin
  1928. Idx:=IndexOfConnection(aName);
  1929. if Idx=-1 then
  1930. Result:=Nil
  1931. else
  1932. Result:=GetConn(Idx);
  1933. end;
  1934. function TSQLDBRestConnectionList.AddConnection(const AType, aHostName, aDatabaseName, aUserName, aPassword: UTF8String): TSQLDBRestConnection;
  1935. Var
  1936. Idx : Integer;
  1937. N : String;
  1938. begin
  1939. Result:=Add as TSQLDBRestConnection;
  1940. IDX:=Result.ID;
  1941. Repeat
  1942. N:='Connection'+IntToStr(IDX);
  1943. Inc(Idx);
  1944. Until IndexOfConnection(N)=-1;
  1945. Result.Name:=N;
  1946. Result.ConnectionType:=aType;
  1947. Result.HostName:=aHostName;
  1948. Result.DatabaseName:=aDatabaseName;
  1949. Result.UserName:=aUserName;
  1950. Result.Password:=aPassword;
  1951. end;
  1952. procedure TSQLDBRestConnectionList.SaveToFile(const aFileName: UTF8String);
  1953. Var
  1954. F : TFileStream;
  1955. begin
  1956. F:=TFileStream.Create(aFileName,fmCreate);
  1957. try
  1958. SaveToStream(F);
  1959. finally
  1960. F.Free;
  1961. end;
  1962. end;
  1963. procedure TSQLDBRestConnectionList.SaveToStream(const aStream: TStream);
  1964. Var
  1965. D : TJSONData;
  1966. S : TJSONStringType;
  1967. begin
  1968. D:=asJSON(JSONConnectionsRoot);
  1969. try
  1970. S:=D.FormatJSON();
  1971. finally
  1972. D.Free;
  1973. end;
  1974. aStream.WriteBuffer(S[1],Length(S)*SizeOf(TJSONCharType));
  1975. end;
  1976. function TSQLDBRestConnectionList.AsJSON(const aPropName: UTF8String): TJSONData;
  1977. Var
  1978. S : TJSONStreamer;
  1979. A : TJSONArray;
  1980. begin
  1981. S:=TJSONStreamer.Create(Nil);
  1982. try
  1983. A:=S.StreamCollection(Self);
  1984. finally
  1985. S.Free;
  1986. end;
  1987. if aPropName='' then
  1988. Result:=A
  1989. else
  1990. Result:=TJSONObject.Create([aPropName,A]);
  1991. end;
  1992. procedure TSQLDBRestConnectionList.LoadFromFile(const aFileName: UTF8String);
  1993. Var
  1994. F : TFileStream;
  1995. begin
  1996. F:=TFileStream.Create(aFileName,fmOpenRead or fmShareDenyWrite);
  1997. try
  1998. LoadFromStream(F);
  1999. finally
  2000. F.Free;
  2001. end;
  2002. end;
  2003. procedure TSQLDBRestConnectionList.LoadFromStream(const aStream: TStream);
  2004. Var
  2005. D : TJSONData;
  2006. begin
  2007. D:=GetJSON(aStream);
  2008. try
  2009. FromJSON(D,JSONConnectionsRoot);
  2010. finally
  2011. D.Free;
  2012. end;
  2013. end;
  2014. procedure TSQLDBRestConnectionList.FromJSON(aData: TJSONData; const aPropName: UTF8String);
  2015. Var
  2016. A : TJSONArray;
  2017. D : TJSONDestreamer;
  2018. begin
  2019. if (aPropName<>'') then
  2020. A:=(aData as TJSONObject).Arrays[aPropName]
  2021. else
  2022. A:=aData as TJSONArray;
  2023. D:=TJSONDestreamer.Create(Nil);
  2024. try
  2025. Clear;
  2026. D.JSONToCollection(A,Self);
  2027. finally
  2028. D.Free;
  2029. end;
  2030. end;
  2031. { TSQLDBRestConnection }
  2032. procedure TSQLDBRestConnection.SetParams(AValue: TStrings);
  2033. begin
  2034. if FParams=AValue then Exit;
  2035. FParams.Assign(AValue);
  2036. end;
  2037. function TSQLDBRestConnection.GetDisplayName: string;
  2038. begin
  2039. Result:=Name;
  2040. end;
  2041. procedure TSQLDBRestConnection.SetConnection(AValue: TSQLConnection);
  2042. begin
  2043. if FConnection=AValue then Exit;
  2044. if Assigned(FConnection) then
  2045. FConnection.RemoveFreeNotification(FNotifier);
  2046. FConnection:=AValue;
  2047. if Assigned(FConnection) then
  2048. FConnection.FreeNotification(FNotifier);
  2049. end;
  2050. function TSQLDBRestConnection.GetName: UTF8String;
  2051. begin
  2052. Result:=FName;
  2053. if (Result='') and Assigned(SingleConnection) then
  2054. Result:=SingleConnection.Name;
  2055. if (Result='') then
  2056. Result:='Connection'+IntToStr(ID);
  2057. end;
  2058. constructor TSQLDBRestConnection.Create(ACollection: TCollection);
  2059. begin
  2060. inherited Create(ACollection);
  2061. FParams:=TStringList.Create;
  2062. FNotifier:=TConnectionFreeNotifier.Create(Nil);
  2063. TConnectionFreeNotifier(FNotifier).FRef:=Self;
  2064. FEnabled:=True;
  2065. end;
  2066. destructor TSQLDBRestConnection.Destroy;
  2067. begin
  2068. TConnectionFreeNotifier(FNotifier).FRef:=Nil;
  2069. FreeAndNil(FNotifier);
  2070. FreeAndNil(FParams);
  2071. inherited Destroy;
  2072. end;
  2073. procedure TSQLDBRestConnection.Assign(Source: TPersistent);
  2074. Var
  2075. C : TSQLDBRestConnection;
  2076. begin
  2077. if (Source is TSQLDBRestConnection) then
  2078. begin
  2079. C:=Source as TSQLDBRestConnection;
  2080. Password:=C.Password;
  2081. UserName:=C.UserName;
  2082. CharSet :=C.CharSet;
  2083. HostName:=C.HostName;
  2084. Role:=C.Role;
  2085. DatabaseName:=C.DatabaseName;
  2086. ConnectionType:=C.ConnectionType;
  2087. Port:=C.Port;
  2088. Name:=C.Name;
  2089. SchemaName:=C.SchemaName;
  2090. Params.Assign(C.Params);
  2091. end
  2092. else
  2093. inherited Assign(Source);
  2094. end;
  2095. procedure TSQLDBRestConnection.ConfigConnection(aConn: TSQLConnection);
  2096. begin
  2097. aConn.CharSet:=Self.CharSet;
  2098. aConn.HostName:=Self.HostName;
  2099. aConn.DatabaseName:=Self.DatabaseName;
  2100. aConn.UserName:=Self.UserName;
  2101. aConn.Password:=Self.Password;
  2102. aConn.Params:=Self.Params;
  2103. if aConn is TSQLConnector then
  2104. TSQLConnector(aConn).ConnectorType:=Self.ConnectionType;
  2105. end;
  2106. Procedure InitSQLDBRest;
  2107. begin
  2108. TSQLDBRestDispatcher.SetIOClass(TRestIO);
  2109. TSQLDBRestDispatcher.SetDBHandlerClass(TSQLDBRestDBHandler);
  2110. TSQLDBRestResource.DefaultFieldListClass:=TSQLDBRestFieldList;
  2111. TSQLDBRestResource.DefaultFieldClass:=TSQLDBRestField;
  2112. end;
  2113. Initialization
  2114. InitSQLDBRest;
  2115. end.