Introduction to the Open eHealth Integration Platform
HL7 message processing
HL7 (Health Level 7) is an ANSI-accredited standards developing organization (SDO) in the healthcare sector. Among other standards it focuses on developing messaging standards for the clinical and administrative domain. This standard is often referred to as the HL7 messaging standard. There are several versions of this standard. HL7 version 2.x is widely used, the most recent update was version 2.6 in 2007. It differs significantly from the XML-based HL7 version 3. The following subsections focus on IPF's support for HL7 version 2.x. Support for HL7 version 3 is currently under development. IPF uses the open source HAPI library as its basis for HL7 version 2 message processing.
HL7 version 2 at a glance HL7 version 2 messages have a hierarchical structure and are organized into groups, segments, fields and datatypes which can also repeat. Message elements are referenced by their path within the hierarchy (see navigation syntax in the following figure).
|
HL7 DSL
The core of IPF's HL7 message processing capabilities is the HL7 DSL. This is a domain-specific language for accessing and manipulating HL7 version 2 messages. The IPF library that implements this DSL (modules-hl7dsl) can also be used standalone (i.e. independent from other IPF components) in any Groovy application. Seamless integration into the IPF DSL is provided as well. The following examples presume a certain degree of familiarity with HL7 version 2 message structures and terms.
Constructing messagesThe entry point to the HL7 DSL is IPF's MessageAdapter. To create a MessageAdapter instance from an HL7 file we use the load method from the MessageAdapters utility class.
import org.openehealth.ipf.modules.hl7dsl.MessageAdapter
import org.openehealth.ipf.modules.hl7dsl.MessageAdapters
MessageAdapter message = MessageAdapters.load('ADT-A01.hl7')
This loads the HL7 file ADT-A01.hl7 from the classpath.
Accessing message contentThe following example obtains the PID segment contained in the PATIENT group which itself is contained in the first repetition of the repeatable PATIENT_RESULT group.
// repeating elements are indexed from 0 to n
def segment = message.PATIENT_RESULT(0).PATIENT.PID
The function call operator () is used to refer to an element in a repetition. The element index is passed as argument. Obtaining fields is similar to obtaining groups and segments except that fields are often referred to by index rather than by name. To obtain the MSH-3 field from a message we can write:
// fields are indexed from 1 to n
def composite = message.MSH[3]
If the field is a composite we access the second component with:
// components of a composite field are indexed from 1 to n
def primitive = message.MSH[3][2]
or, equivalently,
def primitive = message.MSH.sendingApplication.universalIDType
As shown above, navigation is also possible using field names instead of indices. Care must be taken, because along with the change of internal message structures, individual field names change between HL7 versions, even when they refer to the same position of the field in a segment. If the version of the HL7 message is not known in advance, it is better to use the more concise index notation.
Fields may also repeat. To obtain a given element of a repeating field the function call operator () is used, just like with groups and segments. In the next example we obtain the first element of the repeating NK1-5 field. Because NK1 is a repeating segment, the function call operator is used on segment-level, too.
def field = message.NK1(0)[5](0)
def fieldList = message.NK1(0)[5]()
Omitting the repetition index returns a list of repeating elements. Omitting the function call operator, the first repetition of a group, segment or field is assumed. This is called smart navigation:
assert message.NK1(0)[5](0)[1].value == message.NK1[5](0)[1].value
assert message.NK1(0)[5](0)[1].value == message.NK1[5][1].value
assert message.PATIENT_RESULT(0).PATIENT.PID[5][1] ==
message.PATIENT_RESULT.PATIENT.PID[5][1]
If a component is omitted, the first component or subcomponent of a composite is assumed
assert message.NK1(0)[5](0)[1].value == message.NK1[5].value
assert message.NK1(0)[5](1)[1].value == message.NK1[5](1).value
assert message.NK1(0)[2][1][1].value == message.NK1[2].value
Using smart navigation, the expressions are usually shorter and less error-prone. Moreover, in many cases the same expression can be used for different HL7 versions, making the DSL more portable.
Modifying message contentA segment of one message can be assigned the segment value of another message using the assigment operator (=). The following example copies the EVN segment from message2 to message1.
message1.EVN = message2.EVN
To change a field value we navigate to that field (either by name or index, as shown above) and assign it a string or another field value.
def msh = message.MSH
def nk1 = message.NK1(0)
msh[5] = nk1[4][4]
msh[5] = 'abc'
Composite fields may also be changed by assigning other composite fields. In the following example we copy the composite NK1(0)[4] field from message2 to message1.
message1.NK1(0)[4] = message2.NK1(0)[4]Externalizing messages
The left-shift operator (<<) appends the string-represenation of a MessageAdapter object to a writer. In the following example, we write an HL7 message to System.out.
MessageAdapter message = ...
System.out << message
To obtain the string-representation directly, we can use the MessageAdapter.toString() method.
MessageAdapter message = ...Usage in route definitions
def rendered = message.toString()
This section shows a few examples how to combine the HL7 DSL with IPF's route definition DSL. The unmarshal().ghl7() extension should be used to create a MessageAdapter object from an external HL7 message representation. The created MessageAdapter object can then be used in subsequent processors.
from('file:input')
.unmarshal().ghl7()
.process {exchange ->
MessageAdapter message = exchange.in.body
...
}
...
The reverse operation marshal().ghl7() marshals a MessageAdapter object into a stream. This is often needed for transmitting HL7 messages over a variety of transports such as JMS, for example.
from('file:input')
.unmarshal().ghl7()
...
.marshal().ghl7()
.to(jms:queue:validated)Another usage example is content-based routing. In the following example a message is routed to different destinations depending on the content of the MSH[4] field. Here we use closures in combination with Camel's when DSL element for implementing routing rules. The it variable inside closures represents a message exchange.
from(...)
.unmarshal().ghl7()
...
.choice()
.when { it.in.body.MSH[4].value == 'ABC' }
.to(...)
.when { it.in.body.MSH[4].value == 'DEF' }
.to(...)
.otherwise()
.to(...)
- Login or register to post comments
- 6287 reads
- Printer-friendly version
(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)










