Make HL7 v2.3.1 Delimited Messages from Custom Delimited Record
Written by Michael.Czapski updated by Mriganka Banerjee
July, 2013
Dealing with Pipe-delimited Data
HL7 v2.3.1 Register a Patient Message (ADT A04)
Mapping PatientCustom to ADT A04
Introduction
In this document I will walk through the process of generating HL7 v2.3.1 delimited messages from pipe-delimited records containing patient information, sending and receiving HL7 v2.3.1 delimited messages, parsing HL7 v2.3.1 delimited messages and writing HL7 v2 delimited messages to a file. This gives me an opportunity to use the File Binding Component (File BC), the HL7 BC, the HL7 Encoder and the BPEL Service Engine (BPEL SE). This also gives me an opportunity to demonstrate a HL7 v2.3.1 delimited message sender solution and to demonstrate a HL7 v2.3.1 delimited message receiver solution. At the end of the process we will have a file containing HL7 v2 delimited ADT A04 messages, which we will use in related blogs.
We will first create and deploy a project, HL7Feeder, which will read the pipe-delimited file, convert each record into a HL7 v2.3.1 ADT A04 message and send that message out using a HL7 BC. We will then create and deploy a project, HL7Reciver, which will receive the HL7 ADT A04 message and write them out to a file as HL7 Delimited messages. This will allow us to practice conversion of non-XML data to XML (using the custom encoder), conversion of HL7 v2 XML to HL7 v2 Delimited data and sending HL7 v2 Delimited data using the HL7 Binding Component. This will also allow us to practice receiving HL7 v2 Delimited data and writing HL7 v2 Delimited data out. As a side-effect we will get a file containing over 1400 HL7 v2 delimited ADT A04 (Register Patient) message which we will use in a different blog to populate a database.
Pre-requisites
In NetBeans switch to the Services Tab, expand Servers, GlassFish v2, JBI, Binding Components and locate the sun-hl7-binding entry.
Expand Shared Libraries, right-click sun-encoder-library and choose properties.
Click on the Ellipsis button at the far right of the Description property and inspect the property value to confirm that the hl7encoder-1.0 is listed.
Drop down the Tools menu in NetBeans, choose Plugins, switch to Installed tab, scroll down sow that entries starting with the letter ‘E’ are at the top of the list and locate Encoder-related and HL7-related entries.
This confirms that the HL7 support is now available in the GlassFish ESB v2.1 environment, both design-time and runtime.
Dealing with Pipe-delimited Data
As mentioned, we will be reading a bumch of delimited record from a file and transforming them inot an equivalent number of HL7 v2 delimited messages.
Data in the pipe-delimited file looks like this:
The data file is provided as a downloadable archive.
The first line is a line of column headers, separated by the pipe character, corresponding to the data columns starting on line 3. Before using the file we will remove the first two lines.
The columns are:
U_ID Enterprise-wide Unique ID
FACILITY Facility by which the Local ID wa sissued
LOCAL_ID Local ID in the Facility
LAST_NAME Patient’s Last Name
FIRST_NAME Patient’s First/Given Name
MIDDLE_INITIAL Patient’s Middle Initial, if any
SUFFIX Suffix – Junior, the 3rd, etc
TITLE Title – Dr., Prof., etc
ADDR1 Street address
CITY City/Subpurb/Town
STATE State/Region
POST_CODE Postal Code/ZIP Code
COUNTRY Country
DOB Date of Birth as yyyymmdd
DATE_OF_BIRTH Date of Birth as dd-mm-yyyy
SEX Gender Code – M, F, etc.
PATIENT_GENDER Gender as Male, Female, etc.
MSTATUS Marital Status Code – M, D, W, etc.
MARITAL_STATUS Marital Status as Married, Divorced, Widowed, etc.
SSN Social Security Number/Medicare Number
HOMEPHONE Phone Number
RACE Race Code – 01, 02, etc
PATIENT_RACE Race as Caucasian, African American, etc.
ETHNIC Ethnic Origin Code – 01. 02, etc.
PATIENT_ETHNICITY Ethnic Origin as English, Welsh, Irish, etc.
RELIGION Religion Code – R/C, ANG, etc.
PATIENT_RELIGION Religion as Roman Catholic, Anglican, etc.
LANGUAGE Language Code – ENG, VIE, MAL, etc.
PATIENT_LANGUAGE Language as English, Vietnamese, Matese, etc.
STATUS Record Status Code – A (Active)
VIP_FLAG VIP Flag – E, V
PERSON_STATUS Person Status (VIP) as Employee, Very Important Person
CHURCH_CODE always null
MY_ROWNUM a row serial number
We will need to transform records of this structure into HL7 v2.3.1 delimited records. We will use BPEL 2.0 to perform this transformation. BPEL 2.0 is an XML-based language and it can only deal with XML data. GlassFish ESB uses Encoders to transform data between XML and non-XML formats. A prerequisite to this transformation is the availability of a XML Schema that represents the XML version of the data to be encoded / decoded (transformed from/to XML). Looking at the columns list we see that we have a flat structure. We will use the GlassFish ESB XML Schema Editor to create the XML Schema, PatientCustom.xsd.
Let’s create a project group, PatientSvcPrjGrp, in which to create all projects we will develop in this walk through.
[ picture ]
Let’s create the project in which we will decode the pipe-delimited record, transform them into HL7 messages and send each out through a HL7 Adapter (Binding Component). Create New Project -> SOA -> BPEL Module, and name it HL7Feeder.
Close the BPLE process skeleton – we will come back to it later.
Create New … -> XML Schema. Name it PatientCustom.
Add element Patient. Under Patient add subelements for each column, making each a string built-in type.
Mark the following as optional by setting the MinOccurs attribute to 0.
MIDDLE_INITIAL
SUFFIX
TITLE
ADDR1
CITY
STATE
POST_CODE
MSTATUS
MARITAL_STATUS
SSN
HOMEPHONE
RACE
PATIENT_RACE
ETHNIC
PATIENT_ETHNICITY
RELIGION
PATIENT_RELIGION
LANGUAGE
PATIENT_LANGUAGE
VIP_FLAG
PERSON_STATUS
CHURCH_CODE
Save, check and validate XML.
Now that we have the XML structure to which to convert the pipe-delimited records we need to configure delimiter levels and delimiters. Lets right-click the PatientCustom.xsd and choose Encoding -> Apply Custom Encoding.
Click Process.
An annotation node was added under the Patient element. Expand the structure Patient->annotation->encoding, right-click encoding and choose Properties.
Check the checkbox for the Top property and click the ellipsis button on the far right of the Delimiter List property.
Click the Add Level button to create two levels. Click the Add Delimiter button to create two delimiters, one under each level. Set the Bytes field to on the top level delimiter to \r\n and the second level to | then click OK and Close.
We defined delimiters. Let’s now prepare a test file to test delimiters and encoding and perform a test.
Let’s copy the file called ui_pid_segment_with_no_(null).csv as a new file ui_pid_segment_with_no_(null)_one_record.csv, edit the file and remove all except the 3rd line. I am using Ultra Edit set to wrap long lines. The actual text should be all on a single line with no terminator like carriage return.
Let’s right-click on the PatientCustom.xsd, choose Encoding -> Test …
Click the Browse button corresponding to the Input -> Decode -> Data File field, navigate to the ui_pid_segment_with_no_(null)_one_record.csv and select it.
Click the Process button, noting where the output file will be written and what its name will be.
The output file will be opened in a window. Right-click anywhere in the XML text and choose Format.
Inspect the formatted XML text to see if it looks as expected. For me it does.
We know, now, that the delimiters are set correctly so “decoding” is expected to work at runtime. “Decoding”, when using encoders, is the process of converting non-XML data to XML. Encoding, then, is the process of converting XML data to non-XML format – a very XML-centric world view.
HL7 v2.3.1 Register a Patient Message (ADT A04)
In a healthcare enterprise HL7 (Health Level Seven – http://www.hl7.org) is a common messaging standard, in use in the industry for over 20 years. This standard pre-dates XML by a good deal of time. The original HL7 version 2.x standard defined messages as consisting of “printable” text with “printable” delimiter characters separating “segments”, “fields”, subfields” and repetitions of components. Each “segment” would carry specific kind of information. For example the Patient Identification Segment, PID, would carry information about the patient such as names, address, gender and so on. A complete HL7 message would consist of a number of mandatory segments and possibly a number of optional segments. There are a great number of different kinds of messages defined in the standard.. They are grouped into functional groups. One of the groups of messages is the Patient Administration group (ADT). Messages in that group related to registering (A04), admitting (A01), discharging (A03), and transferring patients (A03), amongst others. In this document we will concern ourselves with just one message type, ADT A04 – Register Patient.
According to the HL7 v2.3.1 standard, Chapter 3, Patient Administration, (1998 edition) the ADT A04 message has the following structure:
Segment IDs are the 3-uppercase character in the first column. Segment IDs enclosed in [ ] are optional. Segment IDs enclosed in { } are repeating. Ignoring all the optional segments we see that a minimal ADT A04 message must consist of:
MSH Message Header Segment
EVN Event Type Segment
PID Patient Identification Segment
PV1 Patient Visit Segment
All other segments are optional.
Here is an example HL7 v2.3.1 delimited ADT A04 message:
To construct a valid ADT A04 message we must assemble these 4 segments.
In each segment there are a number of segment-specific fields and subfields, some of which are mandatory and some of which are optional.
The following tables provide details for the 4 segments, with required components highlighted.
Optionality (OPT) column says R == Required, O == Optional and B == Depends on the circumstances.
There is a great deal more to the HL7 and the HL7 Standard documents spell it all out. Unfortunately, the HL7 organization always charged for its standards, which means they are not freely available. It is a pity because charging for standards obstructs standardization. There are a number of freely available documents, on the Internet, which discuss HL7 and its various aspects. Feel free to search for “HL7 ADT A04 2.3.1” and see what you get.
Since the BPEL 2.0 is a XML-based language, and is not capable of dealing with non-XML data, we will have to convert the HL7 v2.3.1 delimited messages to their XML equivalents to hand them over to BPEL, or to convert them from XML to the delimited format if that is the format that needs to be output. The HL7 Encoder library will be the means to effect this conversion. To convert HL7 delimited data to HL7 XML data, or the other way, we need a set of XML Schema documents which represent the HL7 messages. Recall from the previous section that to convert between XML and non-XML formats the encoder library needs a XML Schema, which to use to determine document structure and in which to configure delimiter levels and delimiter characters.
The HL7 v2.x XML Schema Documents can be downloaded from the HL7 Encoder page at the OpenESB site: http://wiki.open-esb.java.net/attach/HL7/hl7v2xsd.zip. Download the XML Schemas and unzip the archive to a convenient directory.
The HL7 v2.3.1 folder, extracted from the hl7v2xsd.zip, does not have an ADT A04 message definition. We need to create one, using one of the other messages as a template. Recall from earlier that our message must have the following segments: MSH, EVN, PID and PV1. Any existing message that includes these segments will be a good starting point for us. Let’s use the ADT_A01.xsd by making a copy of it with the name of ADT_A04_custom.xsd.
Let’s use the NetBeans IDE to modify this definition.
Because the HL7 XSD uses included XSDs let’s create a subfolder, under the Process Files folder in the HL7Feeder project. And call this subfolder HL7v231_ADT_A04.
Let’s now add the necessary XSD document using New -> Other -> XML -> External XML Schema Document(s)
Click the “From Local File System” radio button, navigate to the folder where you extracted the content of the hl7v2xsd archive and choose the following files, then click Finish:
ADT_A04_custom.xsd
datatypes.xsd
fields.xsd
messages.xsd
segments.xsd
Annoyingly, even though I specified just 5 files to import, I got the lot imported. There must be an issue in the IDE. Once the import is over delete all the files except the 5 named above.
Open the ADT_A04_custom.xsd and modify as follows:
Delete the ADT_A01.INSURANE and ADT_A01.PROCEDURE elements and ADT_A01.INSURANCE.CONTENT and ADT_A01.PROCEDURE.CONTENT complex types.
Rename ADT_A01.CONTENT complex type to ADT_A04.CONTENT.
Rename ADT_A01 element to ADT_A04.
Expand the ADT_A01.CONTENT complex type and delete all elements except MSH, EVN, PID and PV1, noticing that all the elements we are deleting are optional and all the elements we are keeping are required.
Validate XML and confirm that the structures look like in the picture below.
The custom ADT A04 message is now ready to use.
HL7 messages do not, naturally, spring out of the thin air. Many Healthcare applications have the capability to produce HL7 v2.x delimited messages and consume HL7 v2.x delimited messages. In the following two sections we will develop a HL7 message producer and a HL7 message consumer.
Mapping PatientCustom to ADT A04
As showing in the BPEL Editor, the HL7 XML structures are fairly unfriendly. The field names are of the form varName->part1->PID.3->CX.2. This is pretty useless unless one is very, very familiar with what the 3rd field of the PID segment corresponds to. As it happens, it corresponds to the Patient Identifier List, is a required field and can repeat.
This information, by itself, is not enough. We need to consult the HL7 v2.3.1 Standard document, Chapter 3 Patient Administration, section 3.3.2 PID – patient identification segment, subsection 3.3.2.3 Patient identifier list, which says:
So in this case, subfield CX.2 would correspond to the “check digit” subfield. Structure and meaning of this, and other fields, are defined elsewhere in the standard.
Rather then bore you with the details I will provide the mapping table for mapping fields of the PatientCustom XML structure to the HL7 v2.3.1 structure, below, including the literals that must be mapped to the HL7 structure and that are not present in the input data. The literals, shown in “”, should be entered in the BPEL mapper without the quotation marks.
Patient Custom Field or “Literal” | Filed/Subfield name | HL7 v2.3.1 target field/subfield |
“|” | Filed Separator | MSH->MSH.1 |
“^~\&” | Encoding Characters | MSH->MSH.2 |
“HL7Sender” | Sending Application | MSH->MSH.3->HD.1 |
“SunMicrosystems” | Sending Facility | MSH->MSH.4->HD.1 |
“HL7Receiver” | Receiving Application | MSH->MSG.5->HD.1 |
“SunMicrosystems” | Receiving Facility | MSH->MSH.6->HD.1 |
“ADT” | Message Type | MSH->MSH.9->MSG.1 |
“A04” | Trigger Event | MSH->MSH.9->MSG.2 |
“ADT_A01” | Message Structure ID | MSH->MSH.9->MSG.3 |
Generated unique id | Message Control ID | MSH->MSH.10 |
“T” | Processing ID | MSH->MSH.11->PT.1 |
“2.3.1” | Version ID | MSH->MSH.12->VID.1 |
“A04” | Event Type Code | EVN->EVN.1 |
current date | Recorded Date/Time | EVN->EVN.2->TS.1 |
LOCAL_ID | Patient Identifier List -> ID | PID->PID.3->CX.1 |
FACILITY | Patient Identifier List -> Assigning Authority | PID->PID.3->CX.4->HD.1 |
FACILITY | Patient Identifier List -> Assigning Facility | PID->PID.->CX.6->HD.1 |
LAST_NAME | Family Name ID | PID->PID.5->XPN.1->FN.1 |
FIRST_NAME | Given Name | PID->PID.5->XPN.2 |
MIDDLE_INITIAL | Middle Initial | PID->PID.5->XPN.3 |
SUFFIX | Suffix | PID->PID.5->XPN.4 |
TITLE | Prefix | PID->PID.5->XPN.5 |
DOB | Date/Time of Birth | PID->PID.7->TS.1 |
SEX | Sex | PID->PID.8 |
RACE | Race Identifier | PID->PID.10->CE.1 |
PATIENT_RACE | Race Text | PID->PID.10->CE.2 |
ADDR1 | Street Address | PID->PID.11->XAD.1 |
CITY | City | PID->PID.11->XAD.3 |
STATE | State or Province | PID->PID.11->XAD.4 |
POST_CODE | Zip or postal code | PID->PID.11->XAD.5 |
COUNTRY | Country | PID->PID.11->XAD.6 |
HOMEPHONE | Phone Number – Home | PID->PID.13->XTN.1 |
LANGUAGE | Primary Language ID | PID->PID.15->CE.1 |
PATIENT_LANGUAGE | Primary Language Text | PID->PID.15->CE.2 |
MSTATUS | Marital Status ID | PID->PID.16->CE.1 |
MARITAL_STATUS | Marital Status Text | PID->PID.16->CE2 |
RELIGION | Religion ID | PID->PID.17->CE.1 |
PATIENT_RELIGION | Religion Text | PID->PID.17->CE.2 |
SSN | SSN Number – Patient | PID->PID.19 |
ETHNIC | Ethnic Group ID | PID->PID.22->CE.1 |
PATIENT_ETHNICITY | Ethnic Group Text | PID->PID.22->CE.2 |
“I” | Patient Class | PV1->PV1.2 |
Figure 1 PatientCustom to HL7 Mapping rules
Note that we omitted a lot of optional fields, just as we did not include optional segments. What will get produced is the minimal HL7 ADT A04 message containing most of the data we have available. Certain data items we have in the PatientCustom structure, for example Status or My_RowNum, are not applicable to HL7 and are not useful to us, so we ignore them.
When dealing with HL7 XSDs in BPEL mapper and the task of mapping from HL7 to non-HL7 structure, in either direction, it pays to spend the time working out the mapping table beforehand and having it validated by the business and HL7 people to ensure the right data gets mapped to the right fields.
HL7 Feeder
We have a file containing 1400, or so, patient records. Records in the file are separated from one another by carriage return+linefeed characters. Within each record fields are pipe-delimited (|). The structure of each record was discussed in section “Dealing with Pipe-delimited Data” and a XML Schema document required to “Decode” this data to XML was developed.
We need to create a HL7 v2.3.1 Delimited message for each of the pipe-delimited records. We obtained the HL7 v2.3.1 XML Schema documents and modified one to represent a custom ADT A04 message structure, in section “HL7 v2.3.1 Register a Patient Message (ADT A04)”.
The mapping table, which helps us implement mapping between fields in the PatientCustom structure and the HL7 ADT A04 structure was discussed in section “Mapping PatientCustom to ADT A04”.
We are now in a position to develop a solution in which the file gets read, each of its records gets transformed into a HL7 message and each message gets sent to a HL7 receiver, which will be developed later.
Having developed the PatientCustom XML Schema and having developed the HL7 ADT A04 XML Schema we have a BPEL Module project, HL7Feeder, and a skeleton BPEL 2.0 process, HL7Feeder.bpel. Our process will read a file so we need a WSDL Document that will provide the structure which to use to configure the File BC. We also need a WSDL Document that will provide the structure which to use to configure the HL7 BC that will send HL7 Delimited messages out.
Let’s create a new WSDL Document, HL7Feeder_FileIn, for the File BC.
This will be a Concrete WSDL with FILE binding of type poll.
Enter File Name (pattern) as ui_pid_segment_*.csv, Polling Directory as c:/temp/PatientSvc/ (or a directory of your own choosing), modify Polling Interval to 10000, check the Multiple Record checkbox, specify \r\n as Delimited By, change the Message Type to encoded data, click the ellipsis to the far` right of the XSD Element/Type field and choose the Patient element in the PatientCustom XSD.
Type customencoder-1.0 into the Encoded type: data entry field, check the Remove Trailing EOL check box and click Finish.
The outcome of this wizard is a WSDL document with File BC configuration providing file location and polling interval but also specifying that the content of the file is to be broken up at delimiters (\r\n) and each record is to be “decoded”, that is converted from delimited format to XML format according to the PatientCustom XML schema.
To add a bit of a twist let’s modify this WSDL manually to change the name of the file to a Regular Expression and to indicate that this is a regular expression.
The two sample file we will be dealing with have names like ui_pid_segment_with_no_(null)_one_record.csv and ui_pid_segment_with_no_(null).csv
A regular expression that covers these and other names of this form might look like:
ui_pid_segment[_()a-z]*.csv
Lets open the WSDL, if it is not already opened, expand the Bindings node all the way to file:message, click the file message and modify the filename and fileNameIsRegex properties to specify the regular expression and set “true”, respectively.
This will cause the File BC to use Regular Expressions, now in v2.1, to poll for a file rather then a literal file name or a simple and limited file name pattern available in previous releases.
Let’s now create a WSDL Document, HL7Feeder_HL7Out, for the HL7 BC. The WSDL will be a Concrete WSDL, using HL7 binding of type HL7 Version 2 – Outbound Request. Click Next.
Click Browse, choose ADT_A04_custom.xsd’s ADT_A04 element, click OK and click Next.
Modify the location for the Endpoint if the HL7 Listener, to receive messages we will be sending, runs/will run on a different host and a different port. By default this is hl7://localhost:4040. I use hl7://localhost:24040 to keep it within the port range I chose for this GlassFish ESB installation. Accept all other defaults unless you know what the different properties mean and have a reason to change them. Click Next.
Review property settings in the HL7 v2 Settings dialogue box. Modify property values if you know what they mean and you have a need to modify them. Otherwise accept defaults and click Next.
Leave properties in the Communication Controls dialogue box at default and click Finish. If you have a need to change these properties get familiar with the HL7 BC documentation so you know what they meand and what they do.
Now we are ready to flesh out the skeleton BPEL process, HL7Fiider.bpel, create a while ago when we create a New Project -> SOA -> BPEL Module.
Open the BPEL process, HL7Feeder.bpel.
Drag the HL7Feeder_FileIn WSDL onto the target marker in the left-hand swim line.
Rename the Partner Link to FileIn.
Drag the HL7Feeder_HL7Out WSDL onto the target market in the right-hand swim line.
Rename the Partner Link it to HL7Out.
Drag Receive, Assign and Invoke activities onto the target marker inside the HL7Feeder process scope.
Connect Receive to the FileIn partner link and Invoke to the HL7Out partner link.
Edit the Receive activity, create an input variable and name it vRecIn.
Edit the Invoke activity, create an inout variable and name it vHL7Out.
Double-click the Assign1 activity to switch to the mapper window, or click the Assign1 activity and click the Mapper tab.
Expand the vRecIn variable structure in the left hand variable tree.
Expand the vHL7Out variable structure to the level of MSH, ENV, PID and PV1 nodes in the right-hand variable structure. Note that depending on the clock speed of your machine this may take a noticeable while and use noticeable amount of resources. Be patient.
Use the mapping table, “1 PatientCustom to HL7 Mapping rules”, to work out how to map data to the vHL7Out variable structure.
Literal strings can be assigned by selecting the target node, clicking the String functiods drop down and selecting the String Literal functioid.
I will not provide a step-by-step pictorial guide to every mapping rule – there are too many. Just a couple of representative pictures should be enough.
If you experience undue unresponsiveness, when trying to map to the HL7 structure, right-click on the project name, choose Properties and uncheck the two properties related to validation. The BPEL editor by default performs rolling process validation. It has been observed, with large and complex structures, and slow processors / cores, that this continuous background validation may consume so much resources as to make it very difficult to work with the BPEL editor.
This example shows a literal mapping of Message Type, “ADT”, to vHL7Out->part1->MSH->MSH.9->MSG.1
This example shows “generation” a “unique id” for MSH.10, Message Control ID. Normally one would do something a bit more sophisticated here.
Here are more mappings.
With mapping completed switch to Source mode and click the Validate File button.
This process can take a while, particularly if you experienced the slowness in mapping and turned off background validation. This validation will be getting done now.
Let’s build the project.
To deploy the project and test it we need a Composite Application. Let’s create New Project -> SOA -> Composite Application. Name it HL7Feeder_CA.
Drag the project, HL7Feeder, onto the CASA Editor canvas.
Build.
Deploy.
If you see message similar to the ones below you have succeeded in deploying the composite application.
Create a folder hierarchy C:\Temp\PatientSvc\sources, using your own root folder hierarchy if you do not use “C:\Temp\PatientSvc” as I do. Just make sure you have a sources folder / directory under the folder / directory where the File BC is configured to poll for a file.
Copy the file ui_pid_segment_with_no_(null)_one_record.csv, which we used to test decoding in “Dealing with Pipe-delimited Data”, to the sources directory.
Now copy the file ui_pid_segment_with_no_(null)_one_record.csv from the sources directory to the directory one level up (for me this will be C:\Temp\PatientSvc). Within 10 seconds the file should disappear. Observe server.log.
We will get an exception, unless it so happens that there is a HL7 listener listening on port 28080 (this is the port I use). I did not have a HL7 listener so I got an exception.
[#|2009-07-02T11:12:55.796+1000|SEVERE|sun-appserver2.1|com.sun.jbi.hl7bc.OutboundMessageProcessor|_ThreadID=60;_ThreadName=HL7BC-OutboundReceiver-2;_RequestID=4ddb583e-c578-4de5-ba70-a22f64f0ca9c;|HL7BC-E0209: Failed to process the message exchange with ID 203614745659895-22018-134657899742810109 and exchange pattern http://www.w3.org/2004/08/wsdl/in-only due to error: HL7BC-W0121: Unable to process message exchange 203614745659895-22018-134657899742810109, end point com.sun.jbi.hl7bc.EndpointImpl@150cdbd and operation {http://j2ee.netbeans.org/wsdl/HL7Feeder/HL7Feeder_HL7Out}hl7wsdlOperation.
java.lang.Exception: HL7BC-W0121: Unable to process message exchange 203614745659895-22018-134657899742810109, end point com.sun.jbi.hl7bc.EndpointImpl@150cdbd and operation {http://j2ee.netbeans.org/wsdl/HL7Feeder/HL7Feeder_HL7Out}hl7wsdlOperation.
at com.sun.jbi.hl7bc.OutboundMessageProcessor.processOneWayOutbound(OutboundMessageProcessor.java:851)
at com.sun.jbi.hl7bc.OutboundMessageProcessor.processMessage(OutboundMessageProcessor.java:301)
at com.sun.jbi.hl7bc.OutboundAction.run(OutboundAction.java:66)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
at java.lang.Thread.run(Thread.java:619)
Caused by: java.lang.NullPointerException
at com.sun.jbi.hl7bc.OutboundMessageProcessor.establishConnectionToExtSys(OutboundMessageProcessor.java:1801)
at com.sun.jbi.hl7bc.OutboundMessageProcessor.start2WayChannelWithExtSystem(OutboundMessageProcessor.java:1713)
at com.sun.jbi.hl7bc.OutboundMessageProcessor.processOneWayOutbound(OutboundMessageProcessor.java:825)
… 5 more
|#]
[#|2009-07-02T11:12:55.812+1000|INFO|sun-appserver2.1|com.sun.jbi.hl7bc.OutboundMessageProcessor|_ThreadID=60;_ThreadName=HL7BC-OutboundReceiver-2;|HL7BC-I0139: Set message exchange status to ERROR for message exchange ID 203614745659895-22018-134657899742810109 and exchange pattern http://www.w3.org/2004/08/wsdl/in-only|#]
[#|2009-07-02T11:12:55.859+1000|WARNING|sun-appserver2.1|com.sun.jbi.engine.bpel.core.bpel.model.runtime.impl.BPELProcessInstanceImpl|_ThreadID=61;_ThreadName=sun-bpel-engine-thread-8;Process Instance Id=129.150.152.74:-3d4f6dad:12238e68000:-7e0e;Service Assembly Name=HL7Feeder_CA;BPEL Process Name=HL7Feeder;_RequestID=3ded8520-1aa1-46c8-bec9-920aaf40a094;|BPCOR-6151: The process instance has been terminated because a fault was not handled; Fault Name is {http://www.sun.com/wsbpel/2.0/process/executable/SUNExtension/ErrorHandling}systemFault; Fault Data is <?xml version=”1.0″ encoding=”UTF-8″?><jbi:message xmlns:sxeh=”http://www.sun.com/wsbpel/2.0/process/executable/SUNExtension/ErrorHandling” type=”sxeh:faultMessage” version=”1.0″ xmlns:jbi=”http://java.sun.com/xml/ns/jbi/wsdl-11-wrapper”><jbi:part>HL7BC-W0121: Unable to process message exchange 203614745659895-22018-134657899742810109, end point com.sun.jbi.hl7bc.EndpointImpl@150cdbd and operation {http://j2ee.netbeans.org/wsdl/HL7Feeder/HL7Feeder_HL7Out}hl7wsdlOperation.</jbi:part></jbi:message>
com.sun.jbi.engine.bpel.core.bpel.exception.SystemException: BPCOR-6131: An Error status was received while doing an invoke (partnerLink=HL7Out, portType={http://j2ee.netbeans.org/wsdl/HL7Feeder/HL7Feeder_HL7Out}hl7wsdlPortType, operation=hl7wsdlOperation)
BPCOR-6129: Line Number is 174
BPCOR-6130: Activity Name is Invoke1
The exception and fault messages are not particularly illuminating. Nothing, at the default logging level, tells me that the fault was thrown because there was no listener to which to connect. I would have expected a message to contain text like “Connection Refused” or something of a sort.
I happen to have a 7Scan HL7 tool, which can, amongst other things, act as a server. I started it, configured it as a listener on port 24040 and then submitted the file again. My server.log, amongst other things, says:
[#|2009-07-02T12:57:13.140+1000|INFO|sun-appserver2.1|com.sun.jbi.hl7bc.extservice.client.TCPIpHL7Connector|_ThreadID=46;_ThreadName=AnonymousIoService-6;|HL7BC-I0155: Sent HL7 request to HL7 External System :MSH|^~\&|HL7Sender|SunMicrosystems|HL7Receiver|SunMicrosystems|||ADT^A04^ADT_A01|2009-07-02T12:57:12.87+10:00ARMC0534005|T|2.3.1
EVN|A04|2009-07-02T12:57:12.87+10:00
PID|||0534005^^^ARMC^^ARMC||STYZINSKI^JOSEPHINE^^^||19570126|F||01|1/97 FARRINGDON VILLAGE^^Dianella^NSW^2747^AU||||||ANG||2222222222|||01
PV1||I
|#]
…
[#|2009-07-02T12:57:13.250+1000|FINE|sun-appserver2.1|com.sun.jbi.hl7bc.OutboundMessageProcessor|_ThreadID=51;_ThreadName=HL7BC-OutboundReceiver-4;ClassName=com.sun.jbi.hl7bc.OutboundMessageProcessor;MethodName=sendAndReceiveHL7Message;MSH|^~\&|HL7Sender|SunMicrosystems|HL7Receiver|SunMicrosystems|||ADT^A04^ADT_A01|2009-07-02T12:57:12.87+10:00ARMC0534005|T|2.3.1
MSA|AA
;_RequestID=20008e0b-08b9-4bb4-a358-92f06a94af65;|HL7BC-: Received HL7 Message|#]
7Scan shows a received HL7 v2 delimited message and the log shows bith the message that was sent and the acknowledgment that was received, when the sun-hl7-adpater is configured to log at more verbose level then the default INFO level.
We see a HL7 message, which 7Scan thinks is valid. We are now in a position to develop the HL7Receiver of our own, to receive HL7 v2 delimited messages and write these messages into a file of HL7 v2 delimited messages.
This is great for a single message but we have a file containing over 1400 records. In a healthcare environment message order is, most of the time, critical. We must ensure that the File BC -> BPEL SE -> HL7 BC solution sends messages out in the order in which they were read from the file.
If you are using an external HL7 receiver make sure it is configured to send HL7 acknowledgments. If it is not then the solution will appear to hang after sending one message as it waits for the acknowledgement which will never come.
Let’s open the CASA map in the CASA Editor, if it is not already opened, right-click on the word QoS and choose Properties.
Let’s set Max Concurrency Limit to 1 and click Close.
Note how the appearance of the word QoS changed. This tells the developer that at least one QoS property differs from default.
Let’s repeat the process for the HL7 BC and set the Max Concurrency Limit to 1.
This serializes processing of messages, assuring sequence preservation.
Build and deploy the composite application.
If you just choose Deploy, clicking the button or choosing a menu options, NetBeans will build what components it needs to build anyway, and then deploy the project.
HL7Receiver
Create a New Project -> SOA -> BPEL Module, named HL7Receiver.
Close the skeleton BPEL process which was generated. We will come back to it later.
Recall what this solution is to accomplish: Receive HL7 v2 Delimited messages and write them as HL7 v2 Delimited messages to as file. To accomplish this we need a configure HL7 BC and a configured File BC. Strictly speaking we don’t need a logic component, since we are building a pass-through solution. Nevertheless we will use the BPEL 2.0 SE to create a pass-through logic for passing data between the HL7 BC and the File BC.
Much as we have done in the HL7Feeder, we need to make the necessary HL7 v2 XML Schemas available to the HL7 BC. Let’s create a subfolder under the Process Files folder and name if HL7v231_ANY.
Right-click on the folder HL7v231_ANY and choose New -> External XML Schema Document(s).
Click the From Local File System radio button, click Browse, Locate the directory containing HL7 v2.3.1 XML Schemas and choose the following:
ADT_A01.xsd
datatypes.xsd
fields.xsd
messages.xsd
segments.xsd
Click Finish. Notice that NetBeans loaded the entire content of the directory – not what we expected. Select and delete all objects except the five objects named above.
We need to convert the ADT_A01 schema into a schema w=that will accept any HL7 message. We could have used the ADT A04 Schema, which we created earlier because we will only be dealing with ADT A04 messages, but creating a generic HL7 schema will be more educations.
Let’s rename the ADT_A01 schema to ANY.
Let’s open the ABY.xsd and delete insurance-related and procedure-related nodes from the structure.
Let’s rename the ADT_A01.CONTENT to ANY_MSG.CONTENT and ADT_A01 to ANY_MSG.
Expand the ComplexType- > ANY_MSG.CONTENT node to the next level of elements and delete all elements except the MSH element. Recall that every HL7 message will start with the MSH Segment and that the MSH segment has information which a solution might use for routing and basic validation. This is why we are leaving this segment as is.
Once all the segments, except MSH, are gone, right-click the sequence node just above the MSH segment element, choose Add and choose Any.
Modify properties of the new Element of type Any to MinOccurs 0, MacOccurs unbounded, Process Contents Lax and Namespace Any.
This structure will work for any HL7 v2.3.1 message and will allow manipulation of components in the MSH Segment but not in any other segment.
Let’s create a New -> WSDL Document to represent the configuration of the inbound HL7 BC. WSDL Name will be HL7Receiver_HL7In, it will be a Concrete WSDL, with HL7 binding or type HL7 Version 2 – Inbound Request. Click Next.
Click the Browse button alongside the Request Message data entry field, locate the ANY_MSH element in the ANY.xsd and click OK.
Click Next.
Modify the Location Endpoint Property to use the correct address, hl7://localhost:24040 (recall that I modified from the default of hl7://localhost:4040 to hl7://locahost:24040 in the HL7Feeder). What we are configuring here if the HL7 listener which will receive messages from the HL7Feeder which we created earlier. The host and port in both HL7 BC configurations must agree, otherwise communication will not take place. Also, modify the Encoding Details property group, clicking Use: literal rather then leaving the default of Use: Encoded. This change will prevent the HL7 Delimited messages from being “decoded” to their XML equivalents. In the solution which we are creating such conversion would have been a waste of resources because the File BC would have to convert the HL7 XML messages back to HL7 Delimited records on the way out without us manipulating anything in the message anyway. By clicking Use: literal we are avoiding this double conversion.
Accept defaults in the HL7 Version 2 Settings panel, just as we have done in the outbound HL7 BC, and click Next.
Accept defaults in the Communication Controls panel and click Finish.
We could have modified all manner of properties relating to HL7 processing and communication processing. We did not because we are keeping the project simple at this time. If these properties are of interest to you please consult the HL7 BC documentation and set them as might be required for your real environment.
Once the Wizard completes, the HL7 BC configuration WSDL will be ready.
Let’s now create a New -> WSDL Document to contain configuration of the File BC. The WSDL will be named HL7Rceiver_FileOut, will be a Concrete WSDL, FILE binding and type write.
Specify hl7_v231_adt_a04_multi.hl7 as File Name. Specify C:\Temp\PatientSvc as Path (or provide a directory / folder location of your own choosing). Click the Append to Existing File radio button and specify \t\t\t as the value of the Delimited By property. We are using three consecutive tab characters as delimiters because HL7 uses \r\n as segment delimiters, so we can not use them. Leave the default of text for the value of Message Type property, check the Add trailing EOL checkbox and click Finish.
If we were dealing with HL7 XML messages in the BPEL process, which in this case we are not in this case, ans we wanted HL7 v2 Delimited message in the file, we would have to change the Message Type to delimited, provide the XSD element type to use and specify the encoder. As mentioned before, by accepting HL7 delimited messages as literally in the HL7 BC, passing them through the BPEL Process unchanged (which we will do next) and writing them as text to the file we are avoiding double conversion – first form delimited to XML then back from XML to delimited.
Let’s now re-open the original BPLE process, HL7Receiver.bpel.
As before, if you experience slow performance when mapping HL7 structures in the BPEL mapper, uncheck the two project properties controlling automatic process validation. This has been mentioned in the previous section.
Let’s drag the HL7Receiver_HL7In WSDL to the target marker in the left-hand swim line and change partner link name to HL7In.
Let’s now drag the HL7Rceiver_FileOut WSDL onto the target marker in the right-hand swim line and rename the partner link to FileOut.
Add Receive, Assign and Invoke activities to the process scope by dragging them onto the target markers inside the process scope.
Connect the Receive1 to the HL7 partner and the Invoke1 to the FileOut partner.
Edit the Receive1 and add an Input Variable vHL7In.
Edit the Invoke1 activity and add an Input Variable vHL7Out.
Double-click the Assign1 activity to switch to the Mapper panel. Map vHL7In->part1 to vHL7Out->part1.
Switch to Design view and Build the process by right-clicking the name of the project and choosing Build.
The process project is ready. To deploy it we must first create a composite application, much as we have done when creating the HL7Feeder solution.
Create New Project -> SOA -> Composite Application and name if HL7Reciver_CA.
Drag the HL7Reciver project onto the CASA canvas.
Build.
Deploy.
Copy the one record test file, ui_pid_segment_with_no_(null)_one_record.csv, to the directory in which the HL7Feeder_FileIn File BC is expecting it. Observe server.log.
References
[1] “GlassFish ESB v 2.1 ‑ Creating a Healthcare Facility Web Service Provider”, http://blogs.sun.com/javacapsfieldtech/entry/glassfish_esb_v_2_1
[2] GlassFish ESB v2.1 download and installation, https://open‑esb.dev.java.net/Downloads.html
[3] “Adding Sun WebSpace Server 10 Portal Server functionality to the GlassFish ESB v2.1 Installation”, http://blogs.sun.com/javacapsfieldtech/entry/adding_sun_webspace_server_10.
[4] “Making Web Space Server And Web Services Play Nicely In A Single Instance Of The Glassfish Application Server”, http://blogs.sun.com/javacapsfieldtech/entry/making_web_space_server_and.