sqldbrestbridge.pp 86 KB

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