From: Tripp, B. <Bry...@uh...> - 2004-06-12 15:56:09
|
Hi Fr=E9d=E9ric,=20 =20 Thanks for pointing this out. No, I wasn't aware of it, and it looks = very powerful. I'd be interested in hearing more about how you integrated = it with HAPI. Is your integration mainly in terms of sending and = receiving messages, or are there other aspects? The message exchange part may be easier with HAPI's new transport design. It's based on this interface (against which the message processing code is written):=20 =20 ca.uhn.hl7v2.protocol.TransportLayer + send(Transportable)=20 + receive() : Transportable + connect() + disconnect() =20 At first glance, it looks like this maps well to the "adaptor" concept = of openadaptor, so I suspect it would be straightforward to write an OpenadaptorTransport, which it seems would be quite useful. =20 =20 Bryan=20 =20 -----Original Message----- From: Fr=E9d=E9ric Dubru [mailto:fre...@sk...] Sent: June 12, 2004 8:32 AM To: hl7...@li... Subject: [HAPI-devel] HAPI-openadaptor integration Tripp, Bryan a =E9crit : Major upcoming items are as follows:=20 1) Improved transport, including JMS and HTTP as well as MLLP, and = easier inclusion of SSL (80% complete) =20 Had you a look at www.openadaptor.org <http://www.openadaptor.org> ? openadaptor is a Java/XML-based EAI framework, which provides = everything needed to exchange data like HL7 messages by JMS, sockets, files, etc. = You can find also some basic mapping functions, allowing to map database = columns to HL7 fields, for instance. In our hospital, we have based all our integration solutions on openadaptor + HAPI + JBossMQ (as JMS server) + Nagios (as monitoring tool). I spent much time to integrate HAPI into openadaptor, and I find the result not so bad: we have now a unified platform to exchange whatever data: HL7, Dicom, financial data, etc. My first HL7 program based on this is running in our production = environment since one year. Bryan, feel free to ask me more details or examples, if you are = interested on this integration. Regards, --=20 Fr=E9d=E9ric Dubru Cliniques universitaires Saint-Luc 10, avenue Hippocrate B-1200 Bruxelles +32 2 764 36 43 http://www.saintluc.be <http://www.saintluc.be>=20 This e-mail may contain confidential and/or privileged information for the sole use of the intended recipient. Any review or distribution by anyone other than the person for whom it was originally intended is strictly prohibited. If you have received this e-mail in error, please contact = the sender and delete all copies. Opinions, conclusions or other information contained = in this e-mail may not be that of the organization. |
From: <fre...@sk...> - 2004-06-21 19:46:29
Attachments:
DemoHAPI.xml
AFEditor.jpg
|
INFO: Calling System.runFinalizersOnExit (true) INFO: Default locale for machine is fr_BE INFO: Default timezone for machine is Europe/Paris INFO: Property DemoHAPI.LocaleISOLanguageCode set to 'fr' INFO: Property DemoHAPI.LocaleISOCountryCode set to 'BE' WARN: Retaining default locale INFO: Property DemoHAPI.TimeZone set to 'Europe/Paris' WARN: Retaining default timezone INFO: Property DemoHAPI.Logging.LoggingTimeInfo set to 'true' INFO: Property DemoHAPI.Logging.LoggingThreadInfo set to 'false' INFO: Property DemoHAPI.Logging.LoggingPackageInfo set to 'false' INFO: Property DemoHAPI.Logging.LogSetting1 set to 'INFO DEFAULT' INFO: Setting loglevel for [DEFAULT] to [INFO] INFO: Property DemoHAPI.Logging.RemoteLogSetting set to 'FATAL' [04/06/21 10:59:32.844] INFO: Checking RemoteLogger [04/06/21 10:59:32.844] INFO: Logger status=OutputLogger status: loggingTimeInfo = true loggingThreadInfo = false loggingPackageInfo = false logLevel for [DEFAULT] is [INFO] [04/06/21 10:59:32.844] INFO: DemoHAPI creating Controller as instance of org.openadaptor.adaptor.SimpleController [04/06/21 10:59:32.876] INFO: DemoHAPI Initializing Controller [04/06/21 10:59:32.876] INFO: DemoHAPI.Controller setting propsPrefix to DemoHAPI.Controller [04/06/21 10:59:32.876] INFO: DemoHAPI.Controller creating instance of org.openadaptor.adaptor.transporter.HTTPRemoteControl [04/06/21 10:59:32.907] INFO: RemoteControl setting password [04/06/21 10:59:32.922] INFO: RemoteControl setting HTTP port to 60000 [04/06/21 10:59:32.954] INFO: DemoHAPI creating PipelineController as instance of org.openadaptor.adaptor.STPipelineController [04/06/21 10:59:32.969] INFO: Setting timeout for component initialization to 0 millis [04/06/21 10:59:34.172] INFO: PollNewAdmissions trying to connect to jdbc:sybase:Tds:orion:5000 [04/06/21 10:59:34.438] INFO: PollNewAdmissions changing default database com.sybase.jdbc2.jdbc.SybConnection@1cbfe9d [04/06/21 10:59:34.485] INFO: PollNewAdmissionsLast primary key value = [0] [04/06/21 10:59:34.563] INFO: SendMessage setting ServerHostname parameter to ugenda-t [04/06/21 10:59:34.563] INFO: SendMessage setting PortNumber parameter to 2576 [04/06/21 10:59:34.594] INFO: SendMessage attempting to connect [04/06/21 10:59:34.610] INFO: SendMessage connected [04/06/21 10:59:34.610] INFO: DemoHAPI.Controller linking MapToHL7 to SendMessage [04/06/21 10:59:34.610] INFO: DemoHAPI.Controller linking PollNewAdmissions to MapToHL7 [04/06/21 10:59:34.610] INFO: DemoHAPI.Controller running [04/06/21 10:59:34.610] INFO: PollNewAdmissions running in polling mode [04/06/21 10:59:34.625] INFO: PollNewAdmissions executing query [set rowcount 1 select visitID, 'Admission' from Admission where processed = 0] [04/06/21 10:59:34.719] INFO: PollNewAdmissions executing query [select visitID, admitDateTime, patientID, firstName, lastName, sex, birthDate, street, zip, city, country, phone, email, deceased from Admission where visitID = 1] [04/06/21 10:59:34.781] INFO: hapi.home is set to D:\app\stluc\info\openadaptor\. [04/06/21 10:59:35.031] INFO: Built HL7 message [MSH|^~\&|DEMO|STLUC|||20040621105935+0100||ADT^A01|4|P|2.3|||NE|AL|BE|UTF-8|FR?EVN||||||20040621101254+0100?PID||M07869D|||DUBRU^FREDERIC||19701004000000+0100|M|||15 RUE DU MANIL^^GEER^^4250^BELGIQUE||019332058^^PH~^^Internet^Fre...@sk...|||||||||||||||||N?PV1|||||||||||||||||||1?] [04/06/21 10:59:36.484] INFO: Received acknowledgment [MSH|^~\&|UltraGendaHL7Engine|UltraGenda|DEMO|STLUC|20040621105936||ACK^A01|632234123767127202|P|2.3|||NE|NE?MSA|AA|4?] [04/06/21 10:59:36.484] INFO: Parsing msg of class ca.uhn.hl7v2.model.v23.message.ACK [04/06/21 10:59:36.516] INFO: PollNewAdmissions executing query [update Admission set processed = 1 where visitID = 1] [04/06/21 10:59:36.531] INFO: PollNewAdmissions executing query [set rowcount 1 select visitID, 'Admission' from Admission where processed = 0] [04/06/21 10:59:36.547] INFO: PollNewAdmissions sleeping for 60000 ... |
From: Tripp, B. <Bry...@uh...> - 2004-06-23 15:29:56
|
Hi Fr=E9d=E9ric,=20 Thank you, that's a very nice introduction to openadaptor. =20 The only part I didn't understand was what underlies the XML element MapToHL7. Did you write that component or is it part of openadaptor? = Where does the Terser-like syntax come from? =20 Thanks again,=20 Bryan=20 -----Original Message----- From: Fr=E9d=E9ric Dubru To: Bry...@uh...; hl7...@li... Sent: 21/06/2004 3:45 PM Subject: Re: [HAPI-devel] HAPI-openadaptor integration Tripp, Bryan a =E9crit : Hi Fr=E9d=E9ric,=20 =20 Thanks for pointing this out. No, I wasn't aware of it, and it looks very powerful. I'd be interested in hearing more about how you integrated it with HAPI. Is your integration mainly in terms of = sending and receiving messages, or are there other aspects? The message exchange part may be easier with HAPI's new transport design. It's based on this interface (against which the message processing code is written):=20 =20 ca.uhn.hl7v2.protocol.TransportLayer + send(Transportable)=20 + receive() : Transportable + connect() + disconnect() =20 At first glance, it looks like this maps well to the "adaptor" concept of openadaptor, so I suspect it would be straightforward to write an OpenadaptorTransport, which it seems would be quite useful. =20 =20 Bryan=20 =20 The openadaptor framework "abstracts the process of sending messages between systems. [...] An adaptor is a one way link between one or more origins and one or more destinations. [...] First there needs to be a connection to the origin (Transport). Once this has been achieved there will be some mechanism whereby data is pushed or pulled (the Protocol). The data is then converted into a message (Message Formatting). The = same steps are performed in reverse at the destination link." Here is how I integrated HAPI and openadaptor: * At the "Transport/Protocol" layer, I simply use the standard components, called Sources and Sinks, provided to connect to different systems, namely JMS queues, TCP/IP sockets and flat files. In terms of HL7 integration, the only thing I added is an implementation of SocketHandshakeProtocol to make socket components able to process ACK messages. This implementation relies on the LowerLayerProtocol class from HAPI. * At the "Message Formatting" layer, openadaptor defines standard APIs, called DOStringReader and DOStringWriter, for converting from strings to DataObjects , the standard openadaptor internal form, and vice versa. Classes which implement conversions to and from fixed-width records, delimited records (e.g. comma separated fields) and a simple XML format are provided. Here, I added two HL7StremReader and HL7StringWriter classes, which of course rely on HAPI, for converting HL7 messages to DataObjects and vice versa. If I understand, your HAPI TransportLayer interface maps the = openadaptor Source/Sink concept, and your Transportable maps the DataObject = concept. But, DataObjects are HL7-unaware and can be used to represent any kind of data, including financial or HR data. HL7 is merely one way to = encode these data, beside Dicom, Swift, HPRIME, etc. Let me illustrate how it works with a small example. The attached XML property file represents an adaptor. You can use AFEditor to compose this file in a graphical way. This file can be run by the adaptor bootstrap. This adaptor polls an SQL database for new patient = admissions and sends corresponding ADT-A01 messages through a socket. It is made = of three components: * The first one, PollNewAdmissions, is in charge of polling the admission table. Every one minute (see property PollPeriod), it checks if a new record has been inserted (NextPrimaryKeySQL) and, if it is the case, selects all the admission details (SelectSQL1). At commit, the record is marked as processed (CommitSQL). In our case, the commit will take place after the ACK message will be received. <PollNewAdmissions> =20 <ClassName>org.openadaptor.adaptor.jdbc.PollingSQLSource</ClassName> ... <NextPrimaryKeySQL>set rowcount 1 select visitID, 'Admission' = from Admission where processed =3D 0</NextPrimaryKeySQL> ... <PollPeriod>60000</PollPeriod> ... <Type1> Admission <CommitSQL>update Admission set processed =3D 1 where visitID = =3D PK</CommitSQL> <SelectSQL1>select visitID, admitDateTime, patientID, = firstName, lastName, sex, birthDate, street, zip, city, country, phone, email, deceased from Admission where visitID =3D PK</SelectSQL1> </Type1> ... </PollNewAdmissions> * At this stage, the openadaptor DataObject is a flat stucture with one attribute per column from the select statement. The second component, MapToHL7, maps this structure into a HL7-like hierarchical structure, using the HL7 message type as DataObject type and attribute names which resemble Terser path names. Note the usage of litteral values ("PH" as telecommunication equipment type) and repetition = indexes (field PID-13 contains two repetitions). <MapToHL7> =20 <ClassName>org.openadaptor.adaptor.standard.AliasingPipeSegment</ClassNa= me> ... <Type1> ADT_A01 <Alias1>patientID PID.PID_2.PID_2_1</Alias1> <Alias2>firstName PID.PID_5.PID_5_2</Alias2> <Alias3>lastName PID.PID_5.PID_5_1</Alias3> <Alias4>sex PID.PID_8</Alias4> <Alias5>birthDate PID.PID_7</Alias5> <Alias6>street PID.PID_11.PID_11_1</Alias6> <Alias7>zip PID.PID_11.PID_11_5</Alias7> <Alias8>city PID.PID_11.PID_11_3</Alias8> <Alias9>country PID.PID_11.PID_11_6</Alias9> <Alias10>{PH} PID.PID_13[0].PID_13_3</Alias10> <Alias11>phone PID.PID_13[0].PID_13_1</Alias11> <Alias12>{Internet} PID.PID_13[1].PID_13_3</Alias12> <Alias13>email PID.PID_13[1].PID_13_4</Alias13> <Alias14>deceased PID.PID_30</Alias14> <Alias15>visitID PV1.PV1_19.PV1_19_1</Alias15> <Alias16>admitDateTime EVN.EVN_6</Alias16> ... <Type>Admission</Type> </Type1> </MapToHL7> * The last component, SendMessage, encodes the data into a HL7 message and sends it through a socket. The HL7 encoding is controlled = by the HL7StringWriter (DOStringWriter), according the MinLowerLayerProtocol (LLPDelegate). The HL7SocketHandshakeProtocol (HandshakeDelegate) is in charge of checking the acknowledgement message. Note that the MSH fields can be set explicitly in the DataObject (like the other fields, see above) or set automatically by the HL7StringWriter (see properties GenerateMessageIDs, HL7Version, ProcessingID, etc.). Note that the HL7StringWriter will encode = correctly the date/time attribute birthDate as a TS field and the boolean attribute deceased as a yes/no indicator. <SendMessage> = <ClassName>org.openadaptor.adaptor.standard.SocketSink</ClassName> <!-- Adaptor DemoHAPI - Component SendMessage SocketSink --> <Port>2576</Port> =20 <DOStringWriter>org.openadaptor.adaptor.hl7.HL7StringWriter</DOStringWri= ter> =20 <HandshakeDelegate>org.openadaptor.adaptor.hl7.HL7SocketHandshakeProtoco= l</HandshakeDelegate> <HostName>ugenda-t</HostName> ... <!-- Adaptor DemoHAPI - Component SendMessage HL7StringWriter --> <AcceptAcknowledgementType>NE</AcceptAcknowledgementType> =20 <ApplicationAcknowledgementType>AL</ApplicationAcknowledgementType> <CharacterSet>UTF-8</CharacterSet> <EnforceHL7FieldStructure>true</EnforceHL7FieldStructure> <GenerateMessageIDs>true</GenerateMessageIDs> <HL7Version>2.3</HL7Version> <LLPDelegate>ca.uhn.hl7v2.llp.MinLowerLayerProtocol</LLPDelegate> <NullAsQuotes>false</NullAsQuotes> <ProcessingID>P</ProcessingID> <SendingApplication>DEMO</SendingApplication> <SendingFacility>STLUC</SendingFacility> </SendMessage> Please find attached a screenshot of the AFEditor and a log of the execution output. Note that it does work without writing one line of code! Of course, real-life adaptors often need to add custom business logic but it is quite easy to subclass standard openadaptor Java classes. Other openadaptor features are really useful in the context of HL7 messaging: value aliasing (from specific codes used by the hospital to standard HL7 codes, for instance), filtering, auditing, etc. But = this mail is long enough ;-). Hope this is clear and can help. --=20 Fr=E9d=E9ric Dubru Cliniques universitaires Saint-Luc 10, avenue Hippocrate B-1200 Bruxelles +32 2 764 36 43 http://www.saintluc.be <http://www.saintluc.be>=20 <<DemoHAPI.xml>> <<DemoHAPI.log>> <<AFEditor.jpg>>=20 This e-mail may contain confidential and/or privileged information for the sole use of the intended recipient. Any review or distribution by anyone other than the person for whom it was originally intended is strictly prohibited. If you have received this e-mail in error, please contact = the sender and delete all copies. Opinions, conclusions or other information contained = in this e-mail may not be that of the organization. |
From: <fre...@sk...> - 2004-06-24 14:52:32
|
Tripp, Bryan a =E9crit : >Hi Fr=E9d=E9ric,=20 > >Thank you, that's a very nice introduction to openadaptor. =20 > >The only part I didn't understand was what underlies the XML element >MapToHL7. Did you write that component or is it part of openadaptor? W= here >does the Terser-like syntax come from?=20 > MapToHL7 is an AliasingPipeSegment component, which is described at=20 http://openadaptor.openadaptor.org/pg/transforming_components.htm#Aliasin= g.=20 In fact, I don't use the standard class provided by *openadaptor* but a=20 slightly modified version. But it is pure openadaptor code, not related=20 to HL7 at all. The objective is to transform a dataobject from one=20 structure to another. In my previous example, MapToHL7 transforms each dataobject built from=20 the SQL result set, for instance: <Admission> <patientID>M07869D</patientID> <firstName>FREDERIC</firstName> <lastName>DUBRU</lastName> <sex>M</sex> <birthDate>1970-10-04T00:00:00 @Europe/Paris</birthDate> ... </Admission> into another dataobject with a HL7-like structure: <ADT_A01> <PID> <PID_2>M07869D</PID_2> <PID_5> <PID_5_1>DUBRU</PID_5_1> <PID_5_2>FREDERIC</PID_5_2> </PID_5> <PID_7>1970-10-04T00:00:00 @Europe/Paris</PID_7> <PID_8>M</PID_8> ... </PID> ... </ADT_A01> This XML piece is only the XML representation of the transformed=20 dataobject; it's not HAPI XML. Then, the HL7StringWriter encodes this dataobject into an HL7 message,=20 with the following pseudo-code, using the HAPI classes: Instantiate an Message after the name of the dataobject type (ADT_A01) For each attribute (nested dataobject) of this dataobject (EVN, PID, PV1). Create a Segment after the name of this attribute For each attribute of this nested dataobject (PID_2, PID_5, etc.) Create a Type after the index in the name of this attribute If it is a simple attribute Set the value of this attribute into this primitive type Else if it is a composite attribute (nested dataobject) For each attribute of this nested dataobject (PID_5_1, PID_5_2, etc.) ... Create or complete the MSH segment Encode the message Surely it would have been easier to use a Terser, rather than build all=20 the HAPI structures by hand, but the Terser didn't exist yet at the time=20 I wrote the code. Fr=E9d=E9ric |