IdHL7.pas 48 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608
  1. { $HDR$}
  2. {**********************************************************************}
  3. { Unit archived using Team Coherence }
  4. { Team Coherence is Copyright 2002 by Quality Software Components }
  5. { }
  6. { For further information / comments, visit our WEB site at }
  7. { http://www.TeamCoherence.com }
  8. {**********************************************************************}
  9. {}
  10. { $Log: 10187: IdHL7.pas
  11. {
  12. { Rev 1.3 30/6/2003 15:07:54 GGrieve
  13. { Remove kdeVersionMark (legacy internal code it Kestral)
  14. }
  15. {
  16. { Rev 1.2 20/6/2003 11:16:36 GGrieve
  17. { fix compile problem
  18. }
  19. {
  20. { Rev 1.1 20/6/2003 08:59:28 GGrieve
  21. { connection in events, and fix problem with singleThread mode
  22. }
  23. {
  24. Indy HL7 Minimal Lower Layer Protocol TIdHL7
  25. Original author Grahame Grieve
  26. This code was donated by HL7Connect.com
  27. For more HL7 open source code see
  28. http://www.hl7connect.com/tools
  29. This unit implements support for the Standard HL7 minimal Lower Layer
  30. protocol. For further details, consult the HL7 standard (www.hl7.org).
  31. Before you can use this component, you must set the following properties:
  32. CommunicationMode
  33. Address (if you want to be a client)
  34. Port
  35. isListener
  36. and hook the appropriate events (see below)
  37. This component will operate as either a server or a client depending on
  38. the configuration
  39. }
  40. {
  41. Version History:
  42. 20/06/2003 Grahame Grieve Add Connection to events. (break existing code, sorry)
  43. 05/09/2002 Grahame Grieve Fixed SingleThread Timeout Issues + WaitForConnection
  44. 23/01/2002 Grahame Grieve Fixed for network changes to TIdTCPxxx
  45. wrote DUnit testing,
  46. increased assertions
  47. change OnMessageReceive - added VHandled parameter
  48. 07/12/2001 Grahame Grieve Various fixes for cmSingleThread mode
  49. 05/11/2001 Grahame Grieve Merge into Indy
  50. 03/09/2001 Grahame Grieve Prepare for Indy
  51. }
  52. (* note: Events are structurally important for this component. However there is
  53. a bug in SyncObjs for Linux under Kylix 1 and 2 where TEvent.WaitFor cannot be
  54. used with timeouts. If you compile your own RTL, then you can fix the routine
  55. like this:
  56. function TEvent.WaitFor(Timeout: LongWord): TWaitResult;
  57. {$IFDEF LINUX}
  58. var ts : TTimeSpec;
  59. begin
  60. ts.tv_sec := timeout div 1000;
  61. ts.tv_nsec := (timeout mod 1000) * 1000000;
  62. if sem_timedwait(FSem, ts) = 0 then
  63. result := wrSignaled
  64. else
  65. result := wrTimeOut;
  66. {$ENDIF}
  67. and then disable this define: *)
  68. { this is a serious issue - unless you fix the RTL, this component does not
  69. function properly on Linux at the present time. This may be fixed in a
  70. future version }
  71. unit IdHL7;
  72. interface
  73. uses
  74. Classes,
  75. IdBaseComponent,
  76. IdException,
  77. IdGlobal,
  78. IdTCPClient,
  79. IdTCPConnection,
  80. IdTCPServer,
  81. SyncObjs,
  82. SysUtils;
  83. const
  84. MSG_START = #$0B; {do not localize}
  85. MSG_END = #$1C#$0D; {do not localize}
  86. BUFFER_SIZE_LIMIT = 1024 * 1024; // buffer is allowed to grow to this size without any
  87. // valid messages. Will be truncated with no notice (DoS protection)
  88. WAIT_STOP = 5000; // nhow long we wait for things to shut down cleanly
  89. type
  90. EHL7CommunicationError = class(EIdException)
  91. Protected
  92. FInterfaceName: String;
  93. Public
  94. constructor Create(AnInterfaceName, AMessage: String);
  95. property InterfaceName: String Read FInterfaceName;
  96. end;
  97. THL7CommunicationMode = (cmUnknown, // not valid - default setting must be changed by application
  98. cmAsynchronous, // see comments below for meanings of the other parameters
  99. cmSynchronous,
  100. cmSingleThread);
  101. TSendResponse = (srNone, // internal use only - never returned
  102. srError, // internal use only - never returned
  103. srNoConnection, // you tried to send but there was no connection
  104. srSent, // you asked to send without waiting, and it has been done
  105. srOK, // sent ok, and response returned
  106. srTimeout); // we sent but there was no response (connection will be dropped internally
  107. TIdHL7Status = (isStopped, // not doing anything
  108. isNotConnected, // not Connected (Server state)
  109. isConnecting, // Client is attempting to connect
  110. isWaitReconnect, // Client is in delay loop prior to attempting to connect
  111. isConnected, // connected OK
  112. isUnusable // Not Usable - stop failed
  113. );
  114. const
  115. { default property values }
  116. DEFAULT_ADDRESS = ''; {do not localize}
  117. DEFAULT_PORT = 0;
  118. DEFAULT_TIMEOUT = 30000;
  119. DEFAULT_RECEIVE_TIMEOUT = 30000;
  120. NULL_IP = '0.0.0.0'; {do not localize}
  121. DEFAULT_CONN_LIMIT = 1;
  122. DEFAULT_RECONNECT_DELAY = 15000;
  123. DEFAULT_COMM_MODE = cmUnknown;
  124. DEFAULT_IS_LISTENER = True;
  125. MILLISECOND_LENGTH = (1 / (24 * 60 * 60 * 1000));
  126. type
  127. // the connection is provided in these events so that applications can obtain information about the
  128. // the peer. It's never OK to write to these connections
  129. TMessageArriveEvent = procedure(ASender: TObject; AConnection: TIdTCPConnection; AMsg: String) of object;
  130. TMessageReceiveEvent = procedure(ASender: TObject; AConnection: TIdTCPConnection; AMsg: String; var VHandled: Boolean; var VReply: String) of object;
  131. TReceiveErrorEvent = procedure(ASender: TObject; AConnection: TIdTCPConnection; AMsg: String; AException: Exception; var VReply: String; var VDropConnection: Boolean) of object;
  132. TIdHL7 = class;
  133. TIdHL7ConnCountEvent = procedure(ASender: TIdHL7; AConnCount: Integer) of object;
  134. TIdHL7PeerThread = class(TIdPeerThread)
  135. Protected
  136. FBuffer: String;
  137. Public
  138. constructor Create(ACreateSuspended: Boolean = True); Override;
  139. destructor Destroy; Override;
  140. end;
  141. TIdHL7ClientThread = class(TThread)
  142. Protected
  143. FClient: TIdTCPClient;
  144. FCloseEvent: TIdLocalEvent;
  145. FOwner: TIdHL7;
  146. procedure Execute; Override;
  147. procedure PollStack;
  148. Public
  149. constructor Create(aOwner: TIdHL7);
  150. destructor Destroy; Override;
  151. end;
  152. TIdHL7 = class(TIdBaseComponent)
  153. Protected
  154. FLock: TCriticalSection;
  155. FStatus: TIdHL7Status;
  156. FStatusDesc: String;
  157. // these queues hold messages when running in singlethread mode
  158. FMsgQueue: TList;
  159. FHndMsgQueue: TList;
  160. FAddress: String;
  161. FCommunicationMode: THL7CommunicationMode;
  162. FConnectionLimit: Word;
  163. FIPMask: String;
  164. FIPRestriction: String;
  165. FIsListener: Boolean;
  166. FObject: TObject;
  167. FPreStopped: Boolean;
  168. FPort: Word;
  169. FReconnectDelay: Cardinal;
  170. FTimeOut: Cardinal;
  171. FReceiveTimeout: Cardinal;
  172. FOnConnect: TNotifyEvent;
  173. FOnDisconnect: TNotifyEvent;
  174. FOnConnCountChange: TIdHL7ConnCountEvent;
  175. FOnMessageArrive: TMessageArriveEvent;
  176. FOnReceiveMessage: TMessageReceiveEvent;
  177. FOnReceiveError: TReceiveErrorEvent;
  178. FIsServer: Boolean;
  179. // current connection count (server only) (can only exceed 1 when mode is not
  180. // asynchronous and we are listening)
  181. FConnCount: Integer;
  182. FServer: TIdTCPServer;
  183. // if we are a server, and the mode is not asynchronous, and we are not listening, then
  184. // we will track the current server connection with this, so we can initiate sending on it
  185. FServerConn: TIdTCPServerConnection;
  186. // A thread exists to connect and receive incoming tcp traffic
  187. FClientThread: TIdHL7ClientThread;
  188. FClient: TIdTCPClient;
  189. // these fields are used for handling message response in synchronous mode
  190. FWaitingForAnswer: Boolean;
  191. FWaitStop: TDatetime;
  192. FMsgReply: String;
  193. FReplyResponse: TSendResponse;
  194. FWaitEvent: TIdLocalEvent;
  195. procedure SetAddress(const AValue: String);
  196. procedure SetConnectionLimit(const AValue: Word);
  197. procedure SetIPMask(const AValue: String);
  198. procedure SetIPRestriction(const AValue: String);
  199. procedure SetPort(const AValue: Word);
  200. procedure SetReconnectDelay(const AValue: Cardinal);
  201. procedure SetTimeOut(const AValue: Cardinal);
  202. procedure SetCommunicationMode(const AValue: THL7CommunicationMode);
  203. procedure SetIsListener(const AValue: Boolean);
  204. function GetStatus: TIdHL7Status;
  205. function GetStatusDesc: String;
  206. procedure InternalSetStatus(const AStatus: TIdHL7Status; ADesc: String);
  207. procedure CheckServerParameters;
  208. procedure StartServer;
  209. procedure StopServer;
  210. procedure DropServerConnection;
  211. procedure ServerConnect(AThread: TIdPeerThread);
  212. procedure ServerExecute(AThread: TIdPeerThread);
  213. procedure ServerDisconnect(AThread: TIdPeerThread);
  214. procedure CheckClientParameters;
  215. procedure StartClient;
  216. procedure StopClient;
  217. procedure DropClientConnection;
  218. procedure HandleIncoming(var VBuffer: String; AConnection: TIdTCPConnection);
  219. function HandleMessage(const AMsg: String; AConn: TIdTCPConnection; var VReply: String): Boolean;
  220. Public
  221. constructor Create(Component: TComponent); Override;
  222. destructor Destroy; Override;
  223. procedure EnforceWaitReplyTimeout;
  224. function Going: Boolean;
  225. // for the app to use to hold any related object
  226. property ObjTag: TObject Read FObject Write FObject;
  227. // status
  228. property Status: TIdHL7Status Read GetStatus;
  229. property StatusDesc: String Read GetStatusDesc;
  230. function Connected: Boolean;
  231. property IsServer: Boolean Read FIsServer;
  232. procedure Start;
  233. procedure PreStop; // call this in advance to start the shut down process. You do not need to call this
  234. procedure Stop;
  235. procedure WaitForConnection(AMaxLength: Integer); // milliseconds
  236. // asynchronous.
  237. function AsynchronousSend(AMsg: String): TSendResponse;
  238. property OnMessageArrive: TMessageArriveEvent Read FOnMessageArrive Write FOnMessageArrive;
  239. // synchronous
  240. function SynchronousSend(AMsg: String; var VReply: String): TSendResponse;
  241. property OnReceiveMessage: TMessageReceiveEvent Read FOnReceiveMessage Write FOnReceiveMessage;
  242. procedure CheckSynchronousSendResult(AResult: TSendResponse; AMsg: String);
  243. // single thread
  244. procedure SendMessage(AMsg: String);
  245. // you can't call SendMessage again without calling GetReply first
  246. function GetReply(var VReply: String): TSendResponse;
  247. function GetMessage(var VMsg: String): pointer; // return nil if no messages
  248. // if you don't call SendReply then no reply will be sent.
  249. procedure SendReply(AMsgHnd: pointer; AReply: String);
  250. Published
  251. // basic properties
  252. property Address: String Read FAddress Write SetAddress; // leave blank and we will be server
  253. property Port: Word Read FPort Write SetPort Default DEFAULT_PORT;
  254. // milliseconds - message timeout - how long we wait for other system to reply
  255. property TimeOut: Cardinal Read FTimeOut Write SetTimeOut Default DEFAULT_TIMEOUT;
  256. // milliseconds - message timeout. When running cmSingleThread, how long we wait for the application to process an incoming message before giving up
  257. property ReceiveTimeout: Cardinal Read FReceiveTimeout Write FReceiveTimeout Default DEFAULT_RECEIVE_TIMEOUT;
  258. // server properties
  259. property ConnectionLimit: Word Read FConnectionLimit Write SetConnectionLimit Default DEFAULT_CONN_LIMIT; // ignored if isListener is false
  260. property IPRestriction: String Read FIPRestriction Write SetIPRestriction;
  261. property IPMask: String Read FIPMask Write SetIPMask;
  262. // client properties
  263. // milliseconds - how long we wait after losing connection to retry
  264. property ReconnectDelay: Cardinal Read FReconnectDelay Write SetReconnectDelay Default DEFAULT_RECONNECT_DELAY;
  265. // message flow
  266. // Set this to one of 4 possibilities:
  267. //
  268. // cmUnknown
  269. // Default at start up. You must set a value before starting
  270. //
  271. // cmAsynchronous
  272. // Send Messages with AsynchronousSend. does not wait for
  273. // remote side to respond before returning
  274. // Receive Messages with OnMessageArrive. Message may
  275. // be response or new message
  276. // The application is responsible for responding to the remote
  277. // application and dropping the link as required
  278. // You must hook the OnMessageArrive Event before setting this mode
  279. // The property IsListener has no meaning in this mode
  280. //
  281. // cmSynchronous
  282. // Send Messages with SynchronousSend. Remote applications response
  283. // will be returned (or timeout). Only use if IsListener is false
  284. // Receive Messages with OnReceiveMessage. Only if IsListener is
  285. // true
  286. // In this mode, the object will wait for a response when sending,
  287. // and expects the application to reply when a message arrives.
  288. // In this mode, the interface can either be the listener or the
  289. // initiator but not both. IsListener controls which one.
  290. // note that OnReceiveMessage must be thread safe if you allow
  291. // more than one connection to a server
  292. //
  293. // cmSingleThread
  294. // Send Messages with SendMessage. Poll for answer using GetReply.
  295. // Only if isListener is false
  296. // Receive Messages using GetMessage. Return a response using
  297. // SendReply. Only if IsListener is true
  298. // This mode is the same as cmSynchronous, but the application is
  299. // assumed to be single threaded. The application must poll to
  300. // find out what is happening rather than being informed using
  301. // an event in a different thread
  302. property CommunicationMode: THL7CommunicationMode Read FCommunicationMode Write SetCommunicationMode Default DEFAULT_COMM_MODE;
  303. // note that IsListener is not related to which end is client. Either end
  304. // may make the connection, and thereafter only one end will be the initiator
  305. // and one end will be the listener. Generally it is recommended that the
  306. // listener be the server. If the client is listening, network conditions
  307. // may lead to a state where the client has a phantom connection and it will
  308. // never find out since it doesn't initiate traffic. In this case, restart
  309. // the interface if there isn't traffic for a period
  310. property IsListener: Boolean Read FIsListener Write SetIsListener Default DEFAULT_IS_LISTENER;
  311. // useful for application
  312. property OnConnect: TNotifyEvent Read FOnConnect Write FOnConnect;
  313. property OnDisconnect: TNotifyEvent Read FOnDisconnect Write FOnDisconnect;
  314. // this is called whenever OnConnect and OnDisconnect are called, and at other times, but only when server
  315. // it will be called after OnConnect and before OnDisconnect
  316. property OnConnCountChange: TIdHL7ConnCountEvent Read FOnConnCountChange Write FOnConnCountChange;
  317. // this is called when an unhandled exception is generated by the
  318. // hl7 object or the application. It allows the application to
  319. // construct a useful return error, log the exception, and drop the
  320. // connection if it wants
  321. property OnReceiveError: TReceiveErrorEvent Read FOnReceiveError Write FOnReceiveError;
  322. end;
  323. implementation
  324. uses
  325. IdResourceStrings;
  326. type
  327. TQueuedMessage = class(TInterfacedObject)
  328. Private
  329. FEvent: TIdLocalEvent;
  330. FMsg: String;
  331. FTimeOut: Cardinal;
  332. FReply: String;
  333. procedure Wait;
  334. Public
  335. constructor Create(aMsg: String; ATimeOut: Cardinal);
  336. destructor Destroy; Override;
  337. function _AddRef: Integer; Stdcall;
  338. function _Release: Integer; Stdcall;
  339. end;
  340. { TQueuedMessage }
  341. constructor TQueuedMessage.Create(aMsg: String; ATimeOut: Cardinal);
  342. begin
  343. assert(aMsg <> '', 'Attempt to queue an empty message');
  344. assert(ATimeout <> 0, 'Attempt to queue a message with a 0 timeout');
  345. inherited Create;
  346. FEvent := TIdLocalEvent.Create(False, False);
  347. FMsg := aMsg;
  348. FTimeOut := ATimeOut;
  349. end;
  350. destructor TQueuedMessage.Destroy;
  351. begin
  352. assert(self <> NIL);
  353. FreeAndNil(FEvent);
  354. inherited;
  355. end;
  356. procedure TQueuedMessage.Wait;
  357. begin
  358. assert(self <> NIL);
  359. assert(assigned(FEvent));
  360. FEvent.WaitFor(FTimeOut);
  361. end;
  362. function TQueuedMessage._AddRef: Integer;
  363. begin
  364. Result := inherited _AddRef;
  365. end;
  366. function TQueuedMessage._Release: Integer;
  367. begin
  368. Result := inherited _Release;
  369. end;
  370. { EHL7CommunicationError }
  371. constructor EHL7CommunicationError.Create(AnInterfaceName, AMessage: String);
  372. begin
  373. // assert(AInterfaceName <> '', 'Attempt to create an exception for an unnamed interface')
  374. // assert(AMessage <> '', 'Attempt to create an exception with an empty message')
  375. // actually, we do not enforce either of these conditions, though they should both be true,
  376. // since we are already raising an exception
  377. FInterfaceName := AnInterfaceName;
  378. if FInterfaceName <> '' then {do not localize}
  379. begin
  380. inherited Create('[' + AnInterfaceName + '] ' + AMessage)
  381. end
  382. else
  383. begin
  384. inherited Create(AMessage);
  385. end
  386. end;
  387. { TIdHL7 }
  388. constructor TIdHL7.Create;
  389. begin
  390. inherited Create(Component);
  391. // partly redundant initialization of properties
  392. FIsListener := DEFAULT_IS_LISTENER;
  393. FCommunicationMode := DEFAULT_COMM_MODE;
  394. FTimeOut := DEFAULT_TIMEOUT;
  395. FReconnectDelay := DEFAULT_RECONNECT_DELAY;
  396. FReceiveTimeout := DEFAULT_RECEIVE_TIMEOUT;
  397. FConnectionLimit := DEFAULT_CONN_LIMIT;
  398. FIPMask := NULL_IP;
  399. FIPRestriction := NULL_IP;
  400. FAddress := DEFAULT_ADDRESS;
  401. FPort := DEFAULT_PORT;
  402. FOnReceiveMessage := NIL;
  403. FOnConnect := NIL;
  404. FOnDisconnect := NIL;
  405. FObject := NIL;
  406. // initialise status
  407. FStatus := IsStopped;
  408. FStatusDesc := RSHL7StatusStopped;
  409. // build internal infrastructure
  410. Flock := TCriticalSection.Create;
  411. FConnCount := 0;
  412. FServer := NIL;
  413. FServerConn := NIL;
  414. FClientThread := NIL;
  415. FClient := NIL;
  416. FMsgQueue := TList.Create;
  417. FHndMsgQueue := TList.Create;
  418. FWaitingForAnswer := False;
  419. FMsgReply := ''; {do not localize}
  420. FReplyResponse := srNone;
  421. FWaitEvent := TIdLocalEvent.Create(False, False);
  422. end;
  423. destructor TIdHL7.Destroy;
  424. begin
  425. assert(assigned(self));
  426. try
  427. if Going then
  428. begin
  429. Stop;
  430. end;
  431. finally
  432. FreeAndNil(FMsgQueue);
  433. FreeAndNil(FHndMsgQueue);
  434. FreeAndNil(FWaitEvent);
  435. FreeAndNil(FLock);
  436. inherited;
  437. end;
  438. end;
  439. {==========================================================
  440. Property Servers
  441. ==========================================================}
  442. procedure TIdHL7.SetAddress(const AValue: String);
  443. begin
  444. assert(assigned(self));
  445. // we don't make any assertions about AValue - will be '' if we are a server
  446. if Going then
  447. begin
  448. raise EHL7CommunicationError.Create(Name, Format(RSHL7NotWhileWorking, ['Address'])); {do not localize??}
  449. end;
  450. FAddress := AValue;
  451. end;
  452. procedure TIdHL7.SetConnectionLimit(const AValue: Word);
  453. begin
  454. assert(assigned(self));
  455. // no restrictions on AValue
  456. if Going then
  457. begin
  458. raise EHL7CommunicationError.Create(Name, Format(RSHL7NotWhileWorking, ['ConnectionLimit'])); {do not localize??}
  459. end;
  460. FConnectionLimit := AValue;
  461. end;
  462. procedure TIdHL7.SetIPMask(const AValue: String);
  463. begin
  464. assert(assigned(self));
  465. // to do: enforce that AValue is a valid Subnet mask
  466. if Going then
  467. begin
  468. raise EHL7CommunicationError.Create(Name, Format(RSHL7NotWhileWorking, ['IP Mask'])); {do not localize??}
  469. end;
  470. FIPMask := AValue;
  471. end;
  472. procedure TIdHL7.SetIPRestriction(const AValue: String);
  473. begin
  474. assert(assigned(self));
  475. // to do: enforce that AValue is a valid IP address range
  476. if Going then
  477. begin
  478. raise EHL7CommunicationError.Create(Name, Format(RSHL7NotWhileWorking, ['IP Restriction'])); {do not localize??}
  479. end;
  480. FIPRestriction := AValue;
  481. end;
  482. procedure TIdHL7.SetPort(const AValue: Word);
  483. begin
  484. assert(assigned(self));
  485. assert(AValue <> 0, 'Attempt to use Port 0 for HL7 Communications');
  486. if Going then
  487. begin
  488. raise EHL7CommunicationError.Create(Name, Format(RSHL7NotWhileWorking, ['Port'])); {do not localize??}
  489. end;
  490. FPort := AValue;
  491. end;
  492. procedure TIdHL7.SetReconnectDelay(const AValue: Cardinal);
  493. begin
  494. assert(assigned(self));
  495. // any value for AValue is accepted, although this may not make sense
  496. if Going then
  497. begin
  498. raise EHL7CommunicationError.Create(Name, Format(RSHL7NotWhileWorking, ['Reconnect Delay'])); {do not localize??}
  499. end;
  500. FReconnectDelay := AValue;
  501. end;
  502. procedure TIdHL7.SetTimeOut(const AValue: Cardinal);
  503. begin
  504. assert(assigned(self));
  505. assert(FTimeout > 0, 'Attempt to configure TIdHL7 with a Timeout of 0');
  506. // we don't fucntion at all if timeout is 0, though there is circumstances where it's not relevent
  507. if Going then
  508. begin
  509. raise EHL7CommunicationError.Create(Name, Format(RSHL7NotWhileWorking, ['Time Out'])); {do not localize??}
  510. end;
  511. FTimeOut := AValue;
  512. end;
  513. procedure TIdHL7.SetCommunicationMode(const AValue: THL7CommunicationMode);
  514. begin
  515. assert(assigned(self));
  516. Assert((AValue >= Low(THL7CommunicationMode)) and (AValue <= High(THL7CommunicationMode)), 'Value for TIdHL7.CommunicationMode not in range');
  517. // only could arise if someone is typecasting?
  518. if Going then
  519. begin
  520. raise EHL7CommunicationError.Create(Name, Format(RSHL7NotWhileWorking, ['Communication Mode'])); {do not localize??}
  521. end;
  522. FCommunicationMode := AValue;
  523. end;
  524. procedure TIdHL7.SetIsListener(const AValue: Boolean);
  525. begin
  526. assert(assigned(self));
  527. // AValue isn't checked
  528. if Going then
  529. begin
  530. raise EHL7CommunicationError.Create(Name, Format(RSHL7NotWhileWorking, ['IsListener'])); {do not localize??}
  531. end;
  532. FIsListener := AValue;
  533. end;
  534. function TIdHL7.GetStatus: TIdHL7Status;
  535. begin
  536. assert(assigned(self));
  537. assert(Assigned(FLock));
  538. FLock.Enter;
  539. try
  540. Result := FStatus;
  541. finally
  542. FLock.Leave;
  543. end;
  544. end;
  545. function TIdHL7.Connected: Boolean;
  546. begin
  547. assert(assigned(self));
  548. assert(Assigned(FLock));
  549. FLock.Enter;
  550. try
  551. Result := FStatus = IsConnected;
  552. finally
  553. FLock.Leave;
  554. end;
  555. end;
  556. function TIdHL7.GetStatusDesc: String;
  557. begin
  558. assert(assigned(self));
  559. assert(Assigned(FLock));
  560. FLock.Enter;
  561. try
  562. Result := FStatusDesc;
  563. finally
  564. FLock.Leave;
  565. end;
  566. end;
  567. procedure TIdHL7.InternalSetStatus(const AStatus: TIdHL7Status; ADesc: String);
  568. begin
  569. assert(assigned(self));
  570. Assert((AStatus >= Low(TIdHL7Status)) and (AStatus <= High(TIdHL7Status)), 'Value for TIdHL7.CommunicationMode not in range');
  571. // ADesc is allowed to be anything at all
  572. assert(Assigned(FLock));
  573. FLock.Enter;
  574. try
  575. FStatus := AStatus;
  576. FStatusDesc := ADesc;
  577. finally
  578. FLock.Leave;
  579. end;
  580. end;
  581. {==========================================================
  582. Application Control
  583. ==========================================================}
  584. procedure TIdHL7.Start;
  585. var
  586. LStatus: TIdHL7Status;
  587. begin
  588. assert(assigned(self));
  589. LStatus := GetStatus;
  590. if LStatus = IsUnusable then
  591. begin
  592. raise EHL7CommunicationError.Create(Name, RSHL7NotFailedToStop);
  593. end;
  594. if LStatus <> IsStopped then
  595. begin
  596. raise EHL7CommunicationError.Create(Name, RSHL7AlreadyStarted);
  597. end;
  598. if FCommunicationMode = cmUnknown then
  599. begin
  600. raise EHL7CommunicationError.Create(Name, RSHL7ModeNotSet);
  601. end;
  602. if FCommunicationMode = cmAsynchronous then
  603. begin
  604. if not assigned(FOnMessageArrive) then
  605. begin
  606. raise EHL7CommunicationError.Create(Name, RSHL7NoAsynEvent);
  607. end;
  608. end;
  609. if (FCommunicationMode = cmSynchronous) and IsListener then
  610. begin
  611. if not assigned(FOnReceiveMessage) then
  612. begin
  613. raise EHL7CommunicationError.Create(Name, RSHL7NoSynEvent);
  614. end;
  615. end;
  616. FIsServer := (FAddress = '');
  617. if FIsServer then
  618. begin
  619. StartServer
  620. end
  621. else
  622. begin
  623. StartClient;
  624. end;
  625. FPreStopped := False;
  626. FWaitingForAnswer := False;
  627. end;
  628. procedure TIdHL7.PreStop;
  629. procedure JoltList(l: TList);
  630. var
  631. i: Integer;
  632. begin
  633. for i := 0 to l.Count - 1 do
  634. begin
  635. TQueuedMessage(l[i]).FEvent.SetEvent;
  636. end;
  637. end;
  638. begin
  639. assert(assigned(self));
  640. if FCommunicationMode = cmSingleThread then
  641. begin
  642. assert(Assigned(FLock));
  643. assert(Assigned(FMsgQueue));
  644. assert(Assigned(FHndMsgQueue));
  645. FLock.Enter;
  646. try
  647. JoltList(FMsgQueue);
  648. JoltList(FHndMsgQueue);
  649. finally
  650. FLock.Leave;
  651. end;
  652. end;
  653. FPreStopped := True;
  654. end;
  655. procedure TIdHL7.Stop;
  656. begin
  657. assert(assigned(self));
  658. if not Going then
  659. begin
  660. raise EHL7CommunicationError.Create(Name, RSHL7AlreadyStopped);
  661. end;
  662. if not FPreStopped then
  663. begin
  664. PreStop;
  665. sleep(10); // give other threads a chance to clean up
  666. end;
  667. if FIsServer then
  668. begin
  669. StopServer
  670. end
  671. else
  672. begin
  673. StopClient;
  674. end;
  675. end;
  676. {==========================================================
  677. Server Connection Maintainance
  678. ==========================================================}
  679. procedure TIdHL7.EnforceWaitReplyTimeout;
  680. begin
  681. Stop;
  682. Start;
  683. end;
  684. function TIdHL7.Going: Boolean;
  685. var
  686. LStatus: TIdHL7Status;
  687. begin
  688. assert(assigned(self));
  689. LStatus := GetStatus;
  690. Result := (LStatus <> IsStopped) and (LStatus <> IsUnusable);
  691. end;
  692. procedure TIdHL7.WaitForConnection(AMaxLength: Integer);
  693. var
  694. LStopWaiting: TDateTime;
  695. begin
  696. LStopWaiting := Now + (AMaxLength * ((1 / (24 * 60)) / (60 * 1000)));
  697. while not Connected and (LStopWaiting > now) do
  698. sleep(50);
  699. end;
  700. procedure TIdHL7.CheckSynchronousSendResult(AResult: TSendResponse; AMsg: String);
  701. begin
  702. case AResult of
  703. srNone:
  704. raise EHL7CommunicationError.Create(Name, 'Internal error in IdHL7.pas: SynchronousSend returned srNone');
  705. srError:
  706. raise EHL7CommunicationError.Create(Name, AMsg);
  707. srNoConnection:
  708. raise EHL7CommunicationError.Create(Name, 'Not connected');
  709. srSent:
  710. raise EHL7CommunicationError.Create(Name, 'Internal error in IdHL7.pas: SynchronousSend returned srSent'); // cause this should only be returned asynchronously
  711. srOK:; // all ok
  712. srTimeout:
  713. raise EHL7CommunicationError.Create(Name, 'No response from remote system');
  714. else
  715. raise EHL7CommunicationError.Create(Name, 'Internal error in IdHL7.pas: SynchronousSend returned an unknown value ' + IntToStr(Ord(AResult)));
  716. end;
  717. end;
  718. { TIdHL7PeerThread }
  719. constructor TIdHL7PeerThread.Create(ACreateSuspended: Boolean);
  720. begin
  721. inherited;
  722. FBuffer := '';
  723. end;
  724. // well, this doesn't do anything. but declared for consistency
  725. destructor TIdHL7PeerThread.Destroy;
  726. begin
  727. assert(assigned(self));
  728. inherited;
  729. end;
  730. procedure TIdHL7.CheckServerParameters;
  731. begin
  732. assert(assigned(self));
  733. if (FCommunicationMode = cmAsynchronous) or not FIsListener then
  734. begin
  735. FConnectionLimit := 1;
  736. end;
  737. if (FPort < 1) then // though we have already ensured that this cannot happen
  738. begin
  739. raise EHL7CommunicationError.Create(Name, Format(RSHL7InvalidPort, [FPort]));
  740. end;
  741. end;
  742. procedure TIdHL7.StartServer;
  743. begin
  744. assert(assigned(self));
  745. CheckServerParameters;
  746. FServer := TIdTCPServer.Create(NIL);
  747. try
  748. FServer.DefaultPort := FPort;
  749. FServer.ThreadClass := TIdHL7PeerThread;
  750. Fserver.OnConnect := ServerConnect;
  751. FServer.OnExecute := ServerExecute;
  752. FServer.OnDisconnect := ServerDisconnect;
  753. FServer.Active := True;
  754. InternalSetStatus(IsNotConnected, RSHL7StatusNotConnected);
  755. except
  756. on e:
  757. Exception do
  758. begin
  759. InternalSetStatus(IsStopped, Format(RSHL7StatusFailedToStart, [e.message]));
  760. FreeAndNil(FServer);
  761. raise;
  762. end;
  763. end;
  764. end;
  765. procedure TIdHL7.StopServer;
  766. begin
  767. assert(assigned(self));
  768. try
  769. FServer.Active := False;
  770. FreeAndNil(FServer);
  771. InternalSetStatus(IsStopped, RSHL7StatusStopped);
  772. except
  773. on e:
  774. Exception do
  775. begin
  776. // somewhat arbitrary decision: if for some reason we fail to shutdown,
  777. // we will stubbornly refuse to work again.
  778. InternalSetStatus(IsUnusable, Format(RSHL7StatusFailedToStop, [e.message]));
  779. FServer := NIL;
  780. raise
  781. end;
  782. end;
  783. end;
  784. procedure TIdHL7.ServerConnect(AThread: TIdPeerThread);
  785. var
  786. LNotify: Boolean;
  787. LConnCount: Integer;
  788. LValid: Boolean;
  789. begin
  790. assert(assigned(self));
  791. assert(assigned(AThread));
  792. assert(assigned(FLock));
  793. FLock.Enter;
  794. try
  795. LNotify := FConnCount = 0;
  796. inc(FConnCount);
  797. LConnCount := FConnCount;
  798. // it would be better to stop getting here in the case of an invalid connection
  799. // cause here we drop it - nasty for the client. To be investigated later
  800. LValid := FConnCount <= FConnectionLimit;
  801. if (FConnCount = 1) and (FCommunicationMode <> cmAsynchronous) and not IsListener then
  802. begin
  803. FServerConn := AThread.Connection;
  804. end;
  805. if LNotify then
  806. begin
  807. InternalSetStatus(IsConnected, RSHL7StatusConnected);
  808. end;
  809. finally
  810. FLock.Leave;
  811. end;
  812. if LValid then
  813. begin
  814. if LNotify and assigned(FOnConnect) then
  815. begin
  816. FOnConnect(self);
  817. end;
  818. if assigned(FOnConnCountChange) and (FConnectionLimit <> 1) then
  819. begin
  820. FOnConnCountChange(Self, LConnCount);
  821. end;
  822. end
  823. else
  824. begin
  825. // Thread exceeds connection limit
  826. AThread.Connection.Disconnect;
  827. end;
  828. end;
  829. procedure TIdHL7.ServerDisconnect(AThread: TIdPeerThread);
  830. var
  831. LNotify: Boolean;
  832. LConnCount: Integer;
  833. begin
  834. assert(assigned(self));
  835. assert(assigned(AThread));
  836. assert(assigned(FLock));
  837. FLock.Enter;
  838. try
  839. dec(FConnCount);
  840. LNotify := FConnCount = 0;
  841. LConnCount := FConnCount;
  842. if AThread.Connection = FServerConn then
  843. begin
  844. FServerConn := NIL;
  845. end;
  846. if LNotify then
  847. begin
  848. InternalSetStatus(IsNotConnected, RSHL7StatusNotConnected);
  849. end;
  850. finally
  851. FLock.Leave;
  852. end;
  853. if assigned(FOnConnCountChange) and (FConnectionLimit <> 1) then
  854. begin
  855. FOnConnCountChange(Self, LConnCount);
  856. end;
  857. if LNotify and assigned(FOnDisconnect) then
  858. begin
  859. FOnDisconnect(self);
  860. end;
  861. end;
  862. procedure TIdHL7.ServerExecute(AThread: TIdPeerThread);
  863. var
  864. LThread: TIdHL7PeerThread;
  865. FSize: Integer;
  866. FStream: TStringStream;
  867. begin
  868. assert(assigned(self));
  869. assert(assigned(AThread));
  870. LThread := AThread as TIdHL7PeerThread;
  871. FStream := TStringStream.Create('');
  872. try
  873. try
  874. // 1. prompt the network for content.
  875. LThread.Connection.ReadFromStack(False, -1, False);
  876. except
  877. try
  878. // well, there was some network error. We aren't sure what it
  879. // was, and it doesn't matter for this layer. we're just going
  880. // to make sure that we start again.
  881. // to review: what happens to the error messages?
  882. LThread.Connection.DisconnectSocket;
  883. except
  884. end;
  885. exit;
  886. end;
  887. FSize := LThread.Connection.InputBuffer.Size;
  888. if FSize > 0 then
  889. begin
  890. FStream.Size := 0;
  891. LThread.Connection.ReadStream(FStream, FSize);
  892. LThread.FBuffer := LThread.FBuffer + FStream.DataString;
  893. HandleIncoming(LThread.FBuffer, LThread.Connection);
  894. end;
  895. finally
  896. FreeAndNil(FStream)
  897. end;
  898. end;
  899. procedure TIdHL7.DropServerConnection;
  900. begin
  901. assert(assigned(self));
  902. assert(assigned(FLock));
  903. FLock.Enter;
  904. try
  905. if assigned(FServerConn) then
  906. FServerConn.Disconnect;
  907. finally
  908. FLock.Leave;
  909. end;
  910. end;
  911. {==========================================================
  912. Client Connection Maintainance
  913. ==========================================================}
  914. procedure TIdHL7.CheckClientParameters;
  915. begin
  916. assert(assigned(self));
  917. if (FPort < 1) then
  918. begin
  919. raise EHL7CommunicationError.Create(Name, Format(RSHL7InvalidPort, [FPort]));
  920. end;
  921. end;
  922. procedure TIdHL7.StartClient;
  923. begin
  924. assert(assigned(self));
  925. CheckClientParameters;
  926. FClientThread := TIdHL7ClientThread.Create(self);
  927. InternalSetStatus(isConnecting, RSHL7StatusConnecting);
  928. end;
  929. procedure TIdHL7.StopClient;
  930. var
  931. LFinished: Boolean;
  932. LStartTime : Cardinal;
  933. begin
  934. assert(assigned(self));
  935. assert(assigned(FLock));
  936. FLock.Enter;
  937. try
  938. FClientThread.Terminate;
  939. FClientThread.FClient.DisconnectSocket;
  940. FClientThread.FCloseEvent.SetEvent;
  941. finally
  942. FLock.Leave;
  943. end;
  944. LStartTime := GetTickCount;
  945. repeat
  946. LFinished := (GetStatus = IsStopped);
  947. if not LFinished then
  948. begin
  949. sleep(10);
  950. end;
  951. until LFinished or (GetTickDiff(LStartTime,GetTickCount) > WAIT_STOP);
  952. if GetStatus <> IsStopped then
  953. begin
  954. // for some reason the client failed to shutdown. We will stubbornly refuse to work again
  955. InternalSetStatus(IsUnusable, Format(RSHL7StatusFailedToStop, [RSHL7ClientThreadNotStopped]));
  956. end;
  957. end;
  958. procedure TIdHL7.DropClientConnection;
  959. begin
  960. assert(assigned(self));
  961. assert(assigned(FLock));
  962. FLock.Enter;
  963. try
  964. if assigned(FClientThread) and assigned(FClientThread.FClient) then
  965. begin
  966. FClientThread.FClient.DisconnectSocket
  967. end
  968. else
  969. begin
  970. // This may happen validly because both ends are trying to drop the connection simultaineously
  971. end;
  972. finally
  973. FLock.Leave;
  974. end;
  975. end;
  976. { TIdHL7ClientThread }
  977. constructor TIdHL7ClientThread.Create(aOwner: TIdHL7);
  978. begin
  979. assert(assigned(AOwner));
  980. FOwner := aOwner;
  981. FCloseEvent := TIdLocalEvent.Create(True, False);
  982. FreeOnTerminate := True;
  983. inherited Create(False);
  984. end;
  985. destructor TIdHL7ClientThread.Destroy;
  986. begin
  987. assert(assigned(self));
  988. assert(assigned(FOwner));
  989. assert(assigned(FOwner.FLock));
  990. FreeAndNil(FCloseEvent);
  991. try
  992. FOwner.FLock.Enter;
  993. try
  994. FOwner.FClientThread := NIL;
  995. FOwner.InternalSetStatus(isStopped, RSHL7StatusStopped);
  996. finally
  997. FOwner.FLock.Leave;
  998. end;
  999. except
  1000. // it's really vaguely possible that the owner
  1001. // may be dead before we are. If that is the case, we blow up here.
  1002. // who cares.
  1003. end;
  1004. inherited;
  1005. end;
  1006. procedure TIdHL7ClientThread.PollStack;
  1007. var
  1008. LBuffer: String;
  1009. FSize: Integer;
  1010. FStream: TStringStream;
  1011. begin
  1012. assert(assigned(self));
  1013. FStream := TStringStream.Create('');
  1014. try
  1015. LBuffer := '';
  1016. repeat
  1017. // we don't send here - we just poll the stack for content
  1018. // if the application wants to terminate us at this point,
  1019. // then it will disconnect the socket and we will get thrown
  1020. // out
  1021. // we really don't care at all whether the disconnect was clean or ugly
  1022. // but we do need to suppress exceptions that come from
  1023. // indy otherwise the client thread will terminate
  1024. try
  1025. // 1. prompt the network for content.
  1026. FClient.ReadFromStack(False, -1, False);
  1027. except
  1028. try
  1029. // well, there was some network error. We aren't sure what it
  1030. // was, and it doesn't matter for this layer. we're just going
  1031. // to make sure that we start again.
  1032. // to review: what happens to the error messages?
  1033. FClient.DisconnectSocket;
  1034. except
  1035. end;
  1036. exit;
  1037. end;
  1038. FSize := FClient.InputBuffer.Size;
  1039. if FSize > 0 then
  1040. begin
  1041. FStream.Size := 0;
  1042. FClient.ReadStream(FStream, FSize);
  1043. LBuffer := LBuffer + FStream.DataString;
  1044. FOwner.HandleIncoming(LBuffer, FClient);
  1045. end;
  1046. until Terminated or not FClient.Connected;
  1047. finally
  1048. FStream.Free;
  1049. end;
  1050. end;
  1051. procedure TIdHL7ClientThread.Execute;
  1052. var
  1053. LRecTime: TDateTime;
  1054. begin
  1055. assert(assigned(self));
  1056. try
  1057. FClient := TIdTCPClient.Create(NIL);
  1058. try
  1059. FClient.Host := FOwner.FAddress;
  1060. FClient.Port := FOwner.FPort;
  1061. repeat
  1062. // try to connect. Try indefinitely but wait Owner.FReconnectDelay
  1063. // between attempts. Problems: how long does Connect take?
  1064. repeat
  1065. FOwner.InternalSetStatus(IsConnecting, rsHL7StatusConnecting);
  1066. try
  1067. FClient.Connect;
  1068. except
  1069. on e:
  1070. Exception do
  1071. begin
  1072. LRecTime := Now + ((FOwner.FReconnectDelay / 1000) * {second length} (1 / (24 * 60 * 60)));
  1073. FOwner.InternalSetStatus(IsWaitReconnect, Format(rsHL7StatusReConnect, [FormatDateTime('hh:nn:ss', LRecTime), e.message])); {do not localize??}
  1074. end;
  1075. end;
  1076. if not Terminated and not FClient.Connected then
  1077. begin
  1078. FCloseEvent.WaitFor(FOwner.FReconnectDelay);
  1079. end;
  1080. until Terminated or FClient.Connected;
  1081. if Terminated then
  1082. begin
  1083. exit;
  1084. end;
  1085. FOwner.FLock.Enter;
  1086. try
  1087. FOwner.FClient := FClient;
  1088. FOwner.InternalSetStatus(IsConnected, rsHL7StatusConnected);
  1089. finally
  1090. FOwner.FLock.Leave;
  1091. end;
  1092. if assigned(FOwner.FOnConnect) then
  1093. begin
  1094. FOwner.FOnConnect(FOwner);
  1095. end;
  1096. try
  1097. PollStack;
  1098. finally
  1099. FOwner.FLock.Enter;
  1100. try
  1101. FOwner.FClient := NIL;
  1102. FOwner.InternalSetStatus(IsNotConnected, RSHL7StatusNotConnected);
  1103. finally
  1104. FOwner.FLock.Leave;
  1105. end;
  1106. if assigned(FOwner.FOnDisconnect) then
  1107. begin
  1108. FOwner.FOnDisconnect(FOwner);
  1109. end;
  1110. end;
  1111. if not Terminated then
  1112. begin
  1113. // we got disconnected. ReconnectDelay applies.
  1114. FCloseEvent.WaitFor(FOwner.FReconnectDelay);
  1115. end;
  1116. until terminated;
  1117. finally
  1118. FreeAndNil(FClient);
  1119. end;
  1120. except
  1121. on e:
  1122. Exception do
  1123. // presumably some comms or indy related exception
  1124. // there's not really anyplace good to put this????
  1125. end;
  1126. end;
  1127. {==========================================================
  1128. Internal process management
  1129. ==========================================================}
  1130. procedure TIdHL7.HandleIncoming(var VBuffer: String; AConnection: TIdTCPConnection);
  1131. var
  1132. LStart, LEnd: Integer;
  1133. LMsg, LReply: String;
  1134. begin
  1135. assert(assigned(self));
  1136. assert(VBuffer <> '', 'Attempt to handle an empty buffer');
  1137. assert(assigned(AConnection));
  1138. try
  1139. // process any messages in the buffer (may get more than one per packet)
  1140. repeat
  1141. { use of Pos instead of Indypos is deliberate }
  1142. LStart := pos(MSG_START, VBuffer);
  1143. LEnd := pos(MSG_END, VBuffer);
  1144. if (LStart > 0) and (LEnd > 0) then
  1145. begin
  1146. LMsg := copy(VBuffer, LStart + length(MSG_START), LEnd - (LStart + length(MSG_START)));
  1147. Delete(VBuffer, 1, (LEnd - 1) + length(MSG_END));
  1148. if HandleMessage(LMsg, AConnection, LReply) then
  1149. begin
  1150. if LReply <> '' then
  1151. begin
  1152. AConnection.Write(MSG_START + LReply + MSG_END);
  1153. end;
  1154. end
  1155. else
  1156. begin
  1157. AConnection.DisconnectSocket;
  1158. end;
  1159. end;
  1160. until (LEnd = 0);
  1161. if length(VBuffer) > BUFFER_SIZE_LIMIT then
  1162. begin
  1163. AConnection.DisconnectSocket;
  1164. end;
  1165. except
  1166. // well, we need to suppress the exception, and force a reconnection
  1167. // we don't know why an exception has been allowed to propagate back
  1168. // to us, it shouldn't be allowed. so what we're going to do, is drop
  1169. // the connection so that we force all the network layers on both
  1170. // ends to reconnect.
  1171. // this is a waste of time of the error came from the application but
  1172. // this is not supposed to happen
  1173. try
  1174. AConnection.DisconnectSocket;
  1175. except
  1176. // nothing - suppress
  1177. end;
  1178. end;
  1179. end;
  1180. function TIdHL7.HandleMessage(const AMsg: String; AConn: TIdTCPConnection; var VReply: String): Boolean;
  1181. var
  1182. LQueMsg: TQueuedMessage;
  1183. LIndex: Integer;
  1184. begin
  1185. assert(assigned(self));
  1186. assert(AMsg <> '', 'Attempt to handle an empty Message');
  1187. assert(assigned(FLock));
  1188. VReply := '';
  1189. Result := True;
  1190. try
  1191. case FCommunicationMode of
  1192. cmUnknown:
  1193. begin
  1194. raise EHL7CommunicationError.Create(Name, RSHL7ImpossibleMessage);
  1195. end;
  1196. cmAsynchronous:
  1197. begin
  1198. FOnMessageArrive(self, AConn, Amsg);
  1199. end;
  1200. cmSynchronous, cmSingleThread:
  1201. begin
  1202. if IsListener then
  1203. begin
  1204. if FCommunicationMode = cmSynchronous then
  1205. begin
  1206. Result := False;
  1207. FOnReceiveMessage(self, AConn, AMsg, Result, VReply)
  1208. end
  1209. else
  1210. begin
  1211. LQueMsg := TQueuedMessage.Create(AMsg, FReceiveTimeout);
  1212. LQueMsg._AddRef;
  1213. try
  1214. FLock.Enter;
  1215. try
  1216. FMsgQueue.Add(LQueMsg);
  1217. finally
  1218. FLock.Leave;
  1219. end;
  1220. LQueMsg.wait;
  1221. // no locking. There is potential problems here. To be reviewed
  1222. VReply := LQueMsg.FReply;
  1223. finally
  1224. FLock.Enter;
  1225. try
  1226. LIndex := FMsgQueue.IndexOf(LQueMsg);
  1227. if LIndex > -1 then
  1228. FMsgQueue.Delete(LIndex);
  1229. finally
  1230. FLock.Leave;
  1231. end;
  1232. LQueMsg._Release;
  1233. end;
  1234. end
  1235. end
  1236. else
  1237. begin
  1238. FLock.Enter;
  1239. try
  1240. if FWaitingForAnswer then
  1241. begin
  1242. FWaitingForAnswer := False;
  1243. FMsgReply := AMsg;
  1244. FReplyResponse := srOK;
  1245. if FCommunicationMode = cmSynchronous then
  1246. begin
  1247. assert(Assigned(FWaitEvent));
  1248. FWaitEvent.SetEvent;
  1249. end;
  1250. end
  1251. else
  1252. begin
  1253. // we could have got here by timing out, but this is quite unlikely,
  1254. // since the connection will be dropped in that case. We will report
  1255. // this as a spurious message
  1256. raise EHL7CommunicationError.Create(Name, RSHL7UnexpectedMessage);
  1257. end;
  1258. finally
  1259. FLock.Leave;
  1260. end;
  1261. end
  1262. end;
  1263. else
  1264. begin
  1265. raise EHL7CommunicationError.Create(Name, RSHL7UnknownMode);
  1266. end;
  1267. end;
  1268. except
  1269. on e:
  1270. Exception do
  1271. if Assigned(FOnReceiveError) then
  1272. begin
  1273. FOnReceiveError(self, AConn, AMsg, e, VReply, Result)
  1274. end
  1275. else
  1276. begin
  1277. Result := False;
  1278. end;
  1279. end;
  1280. end;
  1281. {==========================================================
  1282. Sending
  1283. ==========================================================}
  1284. // this procedure is not technically thread safe.
  1285. // if the connection is disappearing when we are attempting
  1286. // to write, we can get transient access violations. Several
  1287. // strategies are available to prevent this but they significantly
  1288. // increase the scope of the locks, which costs more than it gains
  1289. function TIdHL7.AsynchronousSend(AMsg: String): TSendResponse;
  1290. begin
  1291. assert(Assigned(self));
  1292. assert(AMsg <> '', 'Attempt to send an empty message');
  1293. assert(assigned(FLock));
  1294. Result := srNone; // just to suppress the compiler warning
  1295. FLock.Enter;
  1296. try
  1297. if not Going then
  1298. begin
  1299. raise EHL7CommunicationError.Create(Name, Format(RSHL7NotWorking, [RSHL7SendMessage]))
  1300. end
  1301. else if GetStatus <> isConnected then
  1302. begin
  1303. Result := srNoConnection
  1304. end
  1305. else
  1306. begin
  1307. if FIsServer then
  1308. begin
  1309. if Assigned(FServerConn) then
  1310. begin
  1311. FServerConn.Write(MSG_START + AMsg + MSG_END);
  1312. Result := srSent
  1313. end
  1314. else
  1315. begin
  1316. raise EHL7CommunicationError.Create(Name, RSHL7NoConnectionFound);
  1317. end
  1318. end
  1319. else
  1320. begin
  1321. FClient.Write(MSG_START + AMsg + MSG_END);
  1322. Result := srSent
  1323. end;
  1324. end;
  1325. finally
  1326. FLock.Leave;
  1327. end
  1328. end;
  1329. function TIdHL7.SynchronousSend(AMsg: String; var VReply: String): TSendResponse;
  1330. begin
  1331. assert(Assigned(self));
  1332. assert(AMsg <> '', 'Attempt to send an empty message');
  1333. assert(assigned(FLock));
  1334. Result := srError;
  1335. FLock.Enter;
  1336. try
  1337. FWaitingForAnswer := True;
  1338. FWaitStop := now + (FTimeOut * MILLISECOND_LENGTH);
  1339. FReplyResponse := srTimeout;
  1340. FMsgReply := '';
  1341. finally
  1342. FLock.Leave;
  1343. end;
  1344. try
  1345. Result := AsynchronousSend(AMsg);
  1346. if Result = srSent then
  1347. begin
  1348. assert(Assigned(FWaitEvent));
  1349. FWaitEvent.WaitFor(FTimeOut);
  1350. end;
  1351. finally
  1352. FLock.Enter;
  1353. try
  1354. FWaitingForAnswer := False;
  1355. if Result = srSent then
  1356. begin
  1357. Result := FReplyResponse;
  1358. end;
  1359. if Result = srTimeout then
  1360. begin
  1361. if FIsServer then
  1362. DropServerConnection
  1363. else
  1364. DropClientConnection;
  1365. end;
  1366. VReply := FMsgReply;
  1367. finally
  1368. FLock.Leave;
  1369. end;
  1370. end;
  1371. end;
  1372. procedure TIdHL7.SendMessage(AMsg: String);
  1373. begin
  1374. assert(Assigned(self));
  1375. assert(AMsg <> '', 'Attempt to send an empty message');
  1376. assert(assigned(FLock));
  1377. if FWaitingForAnswer then
  1378. raise EHL7CommunicationError.Create(Name, RSHL7WaitForAnswer);
  1379. FLock.Enter;
  1380. try
  1381. FWaitingForAnswer := True;
  1382. FWaitStop := now + (FTimeOut * MILLISECOND_LENGTH);
  1383. FMsgReply := '';
  1384. FReplyResponse := AsynchronousSend(AMsg);
  1385. finally
  1386. FLock.Leave;
  1387. end;
  1388. end;
  1389. function TIdHL7.GetReply(var VReply: String): TSendResponse;
  1390. begin
  1391. assert(Assigned(self));
  1392. assert(assigned(FLock));
  1393. FLock.Enter;
  1394. try
  1395. if FWaitingForAnswer then
  1396. begin
  1397. if FWaitStop < now then
  1398. begin
  1399. Result := srTimeout;
  1400. VReply := '';
  1401. FWaitingForAnswer := False;
  1402. FReplyResponse := srError;
  1403. end
  1404. else
  1405. begin
  1406. Result := srNone;
  1407. end;
  1408. end
  1409. else
  1410. begin
  1411. Result := FReplyResponse;
  1412. if Result = srSent then
  1413. begin
  1414. Result := srTimeOut;
  1415. end;
  1416. VReply := FMsgReply;
  1417. FWaitingForAnswer := False;
  1418. FReplyResponse := srError;
  1419. end;
  1420. finally
  1421. FLock.Leave;
  1422. end;
  1423. end;
  1424. function TIdHL7.GetMessage(var VMsg: String): pointer;
  1425. begin
  1426. assert(Assigned(self));
  1427. assert(assigned(FLock));
  1428. assert(assigned(FMsgQueue));
  1429. FLock.Enter;
  1430. try
  1431. if FMsgQueue.Count = 0 then
  1432. begin
  1433. Result := NIL
  1434. end
  1435. else
  1436. begin
  1437. Result := FMsgQueue[0];
  1438. TQueuedMessage(Result)._AddRef;
  1439. VMsg := TQueuedMessage(Result).FMsg;
  1440. FMsgQueue.Delete(0);
  1441. FHndMsgQueue.Add(Result);
  1442. end;
  1443. finally
  1444. FLock.Leave;
  1445. end;
  1446. end;
  1447. procedure TIdHL7.SendReply(AMsgHnd: pointer; AReply: String);
  1448. var
  1449. qm: TQueuedMessage;
  1450. begin
  1451. assert(Assigned(self));
  1452. assert(Assigned(AMsgHnd));
  1453. assert(AReply <> '', 'Attempt to send an empty reply');
  1454. assert(assigned(FLock));
  1455. FLock.Enter;
  1456. try
  1457. qm := TObject(AMsgHnd) as TQueuedMessage;
  1458. qm.FReply := AReply;
  1459. qm._Release;
  1460. FHndMsgQueue.Delete(FHndMsgQueue.IndexOf(AMsgHnd));
  1461. finally
  1462. FLock.Leave;
  1463. end;
  1464. qm.FEvent.SetEvent;
  1465. end;
  1466. end.