HL7 Programming using Java and HAPI - Receiving HL7 Messages


Introduction

This is part of my HL7 article series. Before we get started on this tutorial, have a quick look at my earlier article titled “A Very Short Introduction to the HL7 2.x Standard”. One of my earlier tutorials in this series titled "HL7 Programming using Java" gave you a foundational understanding of how to build a simple HL7 message processing client and server using Java alone and hopefully helped you understand the building blocks on which message communications occur using this standard. Although one can in theory build a HL7 client or a server application using such an approach, it won't be sufficient when needing to scale the message processing capabilities and support numerous versions of the HL7 2.x standard and the hundreds of message types and trigger events that are part of each HL7 2.x standard. We looked at a Java library called "HAPI" that was built for such a purpose, and then learnt how to create and transmit messages to a remote HL7 listener in my two articles "HL7 Programming using HAPI - Creating HL7 Messages" and "HL7 Programming using HAPI - Sending HL7 Messages". In this tutorial, we will build on these tutorials and look at how we can build a HL7 listener using the HAPI framework that is able to receive and respond to a variety of HL7 messages and trigger types.

Tools for Tutorial

“Solitude is creativity’s best friend.” ~ Naomi Judd

HL7 Listeners - Some Considerations

In my earlier tutorial "HL7 Programming using Java" you learnt that a socket helps establish a two-way communication link between two programs running on two different (or even same) computers on a network. A socket is always bound to a port number so that the transport layer can help route the data to the correct destination. Socket programming may appear easy especially when using modern languages such as .NET and Java where the frameworks provide a number of high level classes for network-related programming. However, building a mission critical and scalable server application is not trivial and there are many other factors to consider such as message routing, message parsing, error handling, performance and throughput, extensibility, supportability, etc. Just on the area of performance and throughput alone, there are numerous things to consider such as thread and connection pooling and caching, garbage collection of unused objects in memory, etc. These factors and many more have to be considered as part of the overall software design of a well designed HL7 application. Let us look at what HAPI has to offer in this area.

Implementing a Listener using HAPI

In the previous tutorials on HAPI, you saw the use of a special class called HapiContext which came in handy when we wanted to create a HL7 message. Later, we also used this class when we wanted to create a MLLP socket client capable of connecting and communicating with a remote HL7 listener. We used a special utility program called HAPI Test Panel when we needed to simulate a remote listener in order to send messages to. However, we can also use this same class to build a MLLP (over TCP/IP) HL7 listener. HAPI provides numerous other options for building and configuring HL7 listeners as well. It provides an abstract interface called HL7Service based on which a number of pre-built listener classes are provided for you that are capable of listening to multiple connections and multiple message types through specific ports. These listener classes delegate the work of actual processing of any incoming messages to message handlers (known as Applications in HAPI) through the use of special message routing classes (that are aptly named Application Routers). Message routing behaviour for HL7 listeners enabled by these special classes and interfaces can be configured using both code as well as through external configuration files. You can also extend all these classes to implement any custom behaviour in your HL7 system. This loose coupling of behaviour between various aspects of the messaging workflow is one reason why HAPI has been popular and been used under the cover by numerous small as well as large-scale health interface applications over the years. The diagram below that I put together quickly illustrates an example of a messaging architecture that is feasible using the listener classes and message router interfaces provided by the HAPI library.

HAPI HL7 Server Architecture

In this particular tutorial, I want to cover the basics of building HL7 listeners using the HAPI library using a step by step approach similar to one that I had previously used in one of my earlier tutorial called "HL7 Programming using Java". The concepts illustrated in the article should hopefully enable you to explore other advanced options available in this library by yourself. I will mainly use the factory methods available on the HAPIContext class to create a HL7 listener capable of responding to multiple connections through a single port as well through a two-port approach. A two-port listener is a HL7 listener where there is a separate port for inbound traffic and a different port for the outbound message response. HAPI provides many such features through convenient starter classes that you can use or extend, and there are many sensible defaults on most settings for these classes to get started and going easily. However, you can always override the default configurations easily if you wanted. Examples of configurations that you can easily alter include behavior such as timeout settings, exception handling policies, connection pooling, concurrency and thread pool management, etc. Enough talk. Let us dive into some code now.

Step 1 of 5 - A Basic HL7 Listener (without message handling)

    package com.saravanansubramanian.hapihl7tutorial.listeners;

    import com.saravanansubramanian.hapihl7tutorial.create.AdtMessageFactory;
    import ca.uhn.hl7v2.DefaultHapiContext;
    import ca.uhn.hl7v2.HapiContext;
    import ca.uhn.hl7v2.app.Connection;
    import ca.uhn.hl7v2.app.HL7Service;
    import ca.uhn.hl7v2.app.Initiator;
    import ca.uhn.hl7v2.model.Message;
    import ca.uhn.hl7v2.model.v24.message.ADT_A01;
    import ca.uhn.hl7v2.parser.Parser;

    public class BasicListenerWithoutMessageHandling {

        // change this to whatever your port number is
        private static final int PORT_NUMBER = 61386;

        // In HAPI, almost all things revolve around a context object
        private static HapiContext context = new DefaultHapiContext();

        public static void main(String[] args) throws Exception {

            try {

                boolean useSecureConnection = false; // are you using TLS/SSL?

                Connection ourConnection = context.newLazyClient("localhost", PORT_NUMBER, useSecureConnection);
                Initiator initiator = ourConnection.getInitiator();

                HL7Service ourHl7Server = context.newServer(PORT_NUMBER, useSecureConnection);

                ourHl7Server.startAndWait();

                ADT_A01 adtMessage = (ADT_A01) AdtMessageFactory.createMessage("A01");

                Parser ourPipeParser = context.getPipeParser();
                Message messageResponse = initiator.sendAndReceive(adtMessage);

                String responseString = ourPipeParser.encode(messageResponse);
                System.out.println("Received a message response:\n" + responseString);

                ourConnection.close();

                ourHl7Server.stopAndWait();

            } catch (Exception e) {

                //In real-life, do something about this exception
                e.printStackTrace();
            }

        }

    }

The code illustration above shows the implementation of a minimal MLLP listener communicating through the TCP/IP protocol. When you send a ADT message to this server, you will get an Application Internal Error from it as a message response since you don't have any message handlers configured yet (see console output below). We will look at how to configure a message handler in the next step.


Received response:
MSH|^~\&|Their Remote System|Their Remote Facility|Our System|Our Facility|20180704193502.988-0600||ACK^A01|16002|P|2.4
MSA|AR|123420180704193502|Application internal error
ERR|^^^207&Application internal error&HL70357&&No appropriate destination could be found to which this message could be routed.

Step 2 of 5 - Implementing a Message Handler

Let us now configure a message handler with our MLLP listener. As you see in the code below, the message handler is a class that implements a special interface class called ReceivingApplication. Our message handler (HAPI uses the word Applications to refer to them) simply responds with a positive message acknowledgement regardless of the message version, type or trigger event. I will explain how you can achieve fine grained control over message routing next when we integrate all this together. For now, make note of what a basic message handler implementation looks like below. We will integrate this into our message listener in the next step.

    package com.saravanansubramanian.hapihl7tutorial.listeners.helpers;

    import java.io.IOException;
    import java.util.Map;
    import ca.uhn.hl7v2.DefaultHapiContext;
    import ca.uhn.hl7v2.HL7Exception;
    import ca.uhn.hl7v2.HapiContext;
    import ca.uhn.hl7v2.model.Message;
    import ca.uhn.hl7v2.protocol.ReceivingApplication;
    import ca.uhn.hl7v2.protocol.ReceivingApplicationException;

    public class OurSimpleApplication implements ReceivingApplication {

        private static HapiContext context = new DefaultHapiContext();

        @Override
        public boolean canProcess(Message message) {
            return true;
        }

        @Override
        public Message processMessage(Message receivedMessage, Map<String, Object> metaData)
                throws ReceivingApplicationException, HL7Exception {

            String receivedEncodedMessage = context.getPipeParser().encode(receivedMessage);
            System.out.println("Incoming message:\n" + receivedEncodedMessage + "\n\n");

            try {
                return receivedMessage.generateACK();
            } catch (IOException e) {
                throw new HL7Exception(e);
            }

        }

    }

Step 3 of 5 - Listener with Integrated Message Handling and Routing

HL7 listeners are often required to respond to many different HL7 message versions, message types and trigger events. Large health interface engines for example have to communicate and respond to hundreds of messages originating from a variety of hospital systems on the network. To be able to process these messages differently from one another, we usually set up many message handlers each capable of processing a specific trigger event such as ADT^A01 or sometimes a family of messages types. To enable routing behavior so that different message types are routed to various message handlers, HAPI provides classes called Application Routers. Although there are numerous ways to configure routing logic using HAPI, I want to show just a few ways here to get you started easily. You can explore more advanced options on your own once you understand these basics.

    package com.saravanansubramanian.hapihl7tutorial.listeners;

    import com.saravanansubramanian.hapihl7tutorial.create.AdtMessageFactory;
    import com.saravanansubramanian.hapihl7tutorial.listeners.helpers.OurSimpleApplication;
    import ca.uhn.hl7v2.DefaultHapiContext;
    import ca.uhn.hl7v2.HapiContext;
    import ca.uhn.hl7v2.app.Connection;
    import ca.uhn.hl7v2.app.HL7Service;
    import ca.uhn.hl7v2.app.Initiator;
    import ca.uhn.hl7v2.model.Message;
    import ca.uhn.hl7v2.model.v24.message.ADT_A01;
    import ca.uhn.hl7v2.parser.Parser;
    import ca.uhn.hl7v2.protocol.ApplicationRouter.AppRoutingData;

    public class BasicListenerWithEnhancedMessageHandlingAndRouting {

        // change this to whatever your port number is
        private static final int PORT_NUMBER = 61386;

        // In HAPI, almost all things revolve around a context object
        private static HapiContext context = new DefaultHapiContext();

        public static void main(String[] args) throws Exception {

            try {
                boolean useSecureConnection = false; // are you using TLS/SSL?

                // Set up a connection and a initiator purely for testing the server that we are
                // configuring
                Connection ourConnection = context.newLazyClient("localhost", PORT_NUMBER, useSecureConnection);
                Initiator initiator = ourConnection.getInitiator();

                HL7Service ourHl7Server = context.newServer(PORT_NUMBER, useSecureConnection);

                // You can set up routing rules for your HL7 listener by extending the
                // AppRoutingData class like this
                ourHl7Server.registerApplication(new RegistrationEventRoutingData(), new OurSimpleApplication());

                // You can also set up the same routing logic like below
                // Try several applications all processing different message versions, types and
                // trigger events on your own
                // AppRoutingDataImpl ourRouter = new AppRoutingDataImpl("ADT", "A0.", "P",
                // "2.4");
                // ourHl7Server.registerApplication(ourRouter, new OurSimpleApplication());

                ourHl7Server.startAndWait();

                // assemnble a test message to send to our listener
                ADT_A01 adtMessage = (ADT_A01) AdtMessageFactory.createMessage("A01");

                // send this test message through our test client's initiator and get a response
                Message messageResponse = initiator.sendAndReceive(adtMessage);

                // parse the message reponse
                Parser ourPipeParser = context.getPipeParser();
                String responseString = ourPipeParser.encode(messageResponse);
                System.out.println("Received a message response:\n" + responseString);

                // close our test connection
                ourConnection.close();

                // stop our HL7 listener
                ourHl7Server.stopAndWait();
            } catch (Exception e) {
                //In real-life, do something about this exception
                e.printStackTrace();
            }

        }

    }

    class RegistrationEventRoutingData implements AppRoutingData {

        // Note, all conditions must cumulatively be true
        // for a message to be processed

        @Override
        public String getVersion() {
            // process HL7 2.4 version messages only
            return "2.4";
        }

        @Override
        public String getTriggerEvent() {
            // you can use regular expression-based matching for your routing
            // only trigger events that start with 'A0' will be processed
            return "A0.";
        }

        @Override
        public String getProcessingId() {
            // process all messages regardless of processing id
            return "*";
        }

        @Override
        public String getMessageType() {
            // process only ADT message types
            return "ADT";
        }
    }

Running the code above should result in a different message response than we got previously when we did not have a message handler. The HL7 listener parsed the incoming message, and based on the message type that was recognized, it then routed this message to the OurSimpleApplication application class that we created for message processing and message acknowledgment. Program console output as a result of running the code above is shown below.


Incoming message:
MSH|^~\&|Our System|Our Facility|Their Remote System|Their Remote Facility|20180706225549||ADT^A01^ADT_A01|123420180706225549|P|2.4
EVN|A01|20180706225549
PID|||378785433211||Mouse^Mickey||||||123 Main Street^^Lake Buena Vista^FL^^USA
PV1||O|Some Point of Care^^^Some Treatment Facility Name|ALERT||||99999999^Smith^Jack^^^^^^^^^^456789||||||||||||||||||||||||||||||||||||20180706225549


Received a message response:
MSH|^~\&|Their Remote System|Their Remote Facility|Our System|Our Facility|20180706225549.647-0600||ACK^A01|23001|P|2.4
MSA|AA|123420180706225549

“Together we can change the world, just one random act of kindness at a time.” ~ Ron Hall

Step 4 of 5 - Exception Handling and Connection Listeners

Although we would ideally want our systems to all work seamlessly with each other all the time, issues arise with both the sending and the receiving systems from time to time, and these issues need to be addressed quickly. This is especially important in healthcare environments when information exchange needs to happen in an expedient manner. To enable these systems and personnel operating these system in these environments to diagnose and rectify these issues quickly a HL7 listener should be fitted with good exception handling capabilities as well as with solid error logging and alerting capabilities. HAPI framework provides plenty of support in these aspects by providing several configuration options and "hooks" to manage exception-related processing during the message flow using primarily three different ways:

  • By using a special HL7 exception class
  • By using application exception policies
  • By using exception handlers that can be configured for connections

I will illustrate some of these exception handling capabilities through the use of some simple examples below. First we will look at what HAPI provides by default. In the example below, I simulate an exception condition by using a dummy application that simply throws an error. The HAPI framework can trap errors like these and send a message acknowledgement with the error message contained in the exception. This behaviour "comes out of the box" for you. In addition to error handling, sometimes it is also useful to capture information regarding connection establishment required for any diagnostic troubleshooting process. You can inject this behaviour into your HAPI-enabled HL7 applications using connection listeners. In the example below, I am also registering a connection listener with our listener so we can listen for connections that are opened and closed from time to time.

    package com.saravanansubramanian.hapihl7tutorial.listeners;

    import java.util.Map;
    import com.saravanansubramanian.hapihl7tutorial.create.AdtMessageFactory;
    import ca.uhn.hl7v2.DefaultHapiContext;
    import ca.uhn.hl7v2.HL7Exception;
    import ca.uhn.hl7v2.HapiContext;
    import ca.uhn.hl7v2.app.Connection;
    import ca.uhn.hl7v2.app.ConnectionListener;
    import ca.uhn.hl7v2.app.HL7Service;
    import ca.uhn.hl7v2.app.Initiator;
    import ca.uhn.hl7v2.model.Message;
    import ca.uhn.hl7v2.model.v24.message.ADT_A01;
    import ca.uhn.hl7v2.parser.Parser;
    import ca.uhn.hl7v2.protocol.ReceivingApplication;
    import ca.uhn.hl7v2.protocol.ReceivingApplicationException;
    import ca.uhn.hl7v2.protocol.impl.AppRoutingDataImpl;

    public class BasicListenerShowingDefaultErrorHandling {

        // change this to whatever your port number is
        private static final int PORT_NUMBER = 56420;

        // In HAPI, almost all things revolve around a context object
        private static HapiContext context = new DefaultHapiContext();

        public static void main(String[] args) throws Exception {

            try {
                boolean useSecureConnection = false; // are you using TLS/SSL?

                Connection ourConnection = context.newLazyClient("localhost", PORT_NUMBER, useSecureConnection);
                Initiator initiator = ourConnection.getInitiator();

                HL7Service ourHl7Server = context.newServer(PORT_NUMBER, useSecureConnection);

                AppRoutingDataImpl ourRouter = new AppRoutingDataImpl("ADT", "A0.", "P", "2.4");

                //call a dummy application that simulates an exception scenario
                ourHl7Server.registerApplication(ourRouter, new AnErrorThrowingApplication());

                //register a connection listener to the listener as well
                //this lets us listen for connection-related events
                //such as when a new connection is being opened
                //or when a connection is being closed and discarded
                ourHl7Server.registerConnectionListener(new OurConnectionListener());

                ourHl7Server.startAndWait();

                ADT_A01 adtMessage = (ADT_A01) AdtMessageFactory.createMessage("A01");

                Parser ourPipeParser = context.getPipeParser();
                Message messageResponse = initiator.sendAndReceive(adtMessage);

                String responseString = ourPipeParser.encode(messageResponse);
                System.out.println("Received a message response:\n" + responseString);

                ourConnection.close();

                ourHl7Server.stopAndWait();
            } catch (Exception e) {
                //In real-life, do something about this exception
                e.printStackTrace();
            }

        }

    }

    class AnErrorThrowingApplication implements ReceivingApplication {

        @Override
        public boolean canProcess(Message arg0) {
            return true;
        }

        @Override
        public Message processMessage(Message incomingMessage, Map<String, Object> messageMetaData)
                throws ReceivingApplicationException, HL7Exception {
            System.out.println("Received incoming message:\n" + incomingMessage);

            //intentionally raise an exception here to see what the default message acknowledgement looks like
            throw new RuntimeException("Some Error Thrown Here. This will be returned in the ERR segment of the message response");
        }

    }

    class OurConnectionListener implements ConnectionListener {

        @Override
        public void connectionDiscarded(Connection connectionBeingDiscarded) {
            System.out.println("Connection discarded event fired " + connectionBeingDiscarded.getRemoteAddress());
            System.out.println("For Remote Address: " + connectionBeingDiscarded.getRemoteAddress());
            System.out.println("For Remote Port: " + connectionBeingDiscarded.getRemotePort());
        }

        @Override
        public void connectionReceived(Connection connectionBeingOpened) {
            System.out.println("Connection opened event fired " + connectionBeingOpened.getRemoteAddress());
            System.out.println("From Remote Address: " + connectionBeingOpened.getRemoteAddress());
            System.out.println("From Remote Port: " + connectionBeingOpened.getRemotePort());
        }

    }

Connection opened event fired /127.0.0.1
From Remote Address: /127.0.0.1
From Remote Port: 56870

Received incoming message:
MSH|^~\&|Our System|Our Facility|Their Remote System|Their Remote Facility|20180707125654||ADT^A01^ADT_A01|123420180707125654|P|2.4
EVN|A01|20180707125654
PID|||378785433211||Mouse^Mickey||||||123 Main Street^^Lake Buena Vista^FL^^USA
PV1||O|Some Point of Care^^^Some Treatment Facility Name|ALERT||||99999999^Smith^Jack^^^^^^^^^^456789||||||||||||||||||||||||||||||||||||20180707125654

Received a message response:
MSH|^~\&|Their Remote System|Their Remote Facility|Our System|Our Facility|20180707125654.381-0600||ACK^A01|30301|P|2.4
MSA|AE|123420180707125654|Application internal error
ERR|^^^207&Application internal error&HL70357&&Some Error Thrown Here. This will be returned in the ERR segment of the message response

Connection discarded event fired /127.0.0.1
For Remote Address: /127.0.0.1
For Remote Port: 56870

As you saw in the above example, HAPI provides a default error handling mechanism even when you don't handle any exceptions explicitly. This however will not be sufficient in many implementations as you will want to perform additional error logging, alerting and customization of the message responses for the listener. This is where exception handlers prove extremely useful. When configured on a listener (see setExceptionHandler method on the HL7 listener) you can gain greater visibility and control over the exceptions that are occuring across the various applications/message handlers registered on the listener, and you can perform addtional processing as necessary. These exception handlers receive information such as the incoming message that caused the exception to happen, the meta data of this incoming message to assist with any additional routing without having to parse the raw message explicitly, the default outgoing NAK (NAK stands for "negative acknowledgment message") as well as the exception itself. When using exception handlers ensure that something is always returned from the processException method, or else the client will not receive any response to the original message it transmitted to the listener. Not responding to a message is not considered a good practice as most HL7 systems will not continue to transmit any additional messages until then delaying potentially hundreds of messages that need to be transmitted sequentially. The code below shows an exception handler class that can be injected into this listener.

    package com.saravanansubramanian.hapihl7tutorial.listeners.helpers;

    import java.util.Map;
    import ca.uhn.hl7v2.HL7Exception;
    import ca.uhn.hl7v2.protocol.ReceivingApplicationExceptionHandler;

    public class OurExceptionHandler implements ReceivingApplicationExceptionHandler {

        @Override
        public String processException(String theIncomingMessage, Map<String, Object> theIncomingMetadata, String theOutgoingNegativeAcknowledgementMessage, Exception theException)
                throws HL7Exception {

            System.out.println("The error message was:" + theException.getMessage() + "\n");

            //do any additional error processing such as error logging, alerting, etc here
            //based on the exception type or the exception message
            //use or extend the HL7Exception class in your message processing logic where possible
            //so that logic here is short and simple

            //you can return the outgoing message as is, or throw a customized error message
            //you have to return something from this method no matter what

            return theOutgoingNegativeAcknowledgementMessage;
        }

    }

Step 5 of 5 - Building a Two-Port Listening Service

In this last step of this tutorial, I want to cover another feature that HAPI provides. This feature enables application programmers to develop two-port message interfaces relatively easily although you still have deal with sockets directly. These two-port listeners are needed sometimes when the inbound and outbound messages need to be communicated through separate ports. A simple two-port listener is shown below along with the console output when running such a program.

    package com.saravanansubramanian.hapihl7tutorial.listeners;

    import java.net.Socket;
    import com.saravanansubramanian.hapihl7tutorial.create.AdtMessageFactory;
    import com.saravanansubramanian.hapihl7tutorial.listeners.helpers.OurSimpleApplication;
    import ca.uhn.hl7v2.app.ActiveConnection;
    import ca.uhn.hl7v2.app.Connection;
    import ca.uhn.hl7v2.app.TwoPortService;
    import ca.uhn.hl7v2.concurrent.DefaultExecutorService;
    import ca.uhn.hl7v2.llp.MinLowerLayerProtocol;
    import ca.uhn.hl7v2.model.Message;
    import ca.uhn.hl7v2.model.v24.message.ADT_A01;
    import ca.uhn.hl7v2.parser.Parser;
    import ca.uhn.hl7v2.parser.PipeParser;

    public class BasicListenerWithTwoPortBehavior {

        public static void main(String[] args) throws Exception {

            try {

                TwoPortService ourTwoPortListeningService;
                int outboundPort = 4567;
                int inboundPort = 5678;

                //instantiate a two port service that listens on these two ports
                ourTwoPortListeningService = new TwoPortService(outboundPort, inboundPort);

                //start the service and listen for ADT A01 messages only
                ourTwoPortListeningService.registerApplication("ADT", "A01", new OurSimpleApplication());
                ourTwoPortListeningService.start();

                Parser ourPipeParser = new PipeParser();
                MinLowerLayerProtocol protocol = new MinLowerLayerProtocol();

                Socket outboundSocket = new Socket("localhost", outboundPort);
                Socket inboundSocket = new Socket("localhost", inboundPort);

                //configure a connection that is capable of sending and receiving messages through the two ports
                Connection conn = new ActiveConnection(ourPipeParser, protocol, inboundSocket, outboundSocket);
                conn.activate();

                ADT_A01 adtMessage = (ADT_A01) AdtMessageFactory.createMessage("A01");

                System.out.println("Sending message to listener through port:" + outboundPort + "\n");
                Message messageResponse = conn.getInitiator().sendAndReceive(adtMessage);
                conn.close();

                String responseString = ourPipeParser.encode(messageResponse);
                System.out.println("Received a message response through port:" + inboundPort + "\n");
                System.out.println("Message response was:\n" + responseString);

                //stop the service and the thread executor associated with it
                ourTwoPortListeningService.stopAndWait();
                DefaultExecutorService.getDefaultService().shutdown();

            } catch (Exception e) {

                //In real-life, do something about this exception
                e.printStackTrace();
            }

        }

    }

Sending message to listener through port:4567

Incoming message:
MSH|^~\&|Our System|Our Facility|Their Remote System|Their Remote Facility|20180707140618||ADT^A01^ADT_A01|123420180707140618|P|2.4
EVN|A01|20180707140618
PID|||378785433211||Mouse^Mickey||||||123 Main Street^^Lake Buena Vista^FL^^USA
PV1||O|Some Point of Care^^^Some Treatment Facility Name|ALERT||||99999999^Smith^Jack^^^^^^^^^^456789||||||||||||||||||||||||||||||||||||20180707140618

Received a message response through port:5678

Message response was:
MSH|^~\&|Their Remote System|Their Remote Facility|Our System|Our Facility|20180707140618.923-0600||ACK^A01|32701|P|2.4
MSA|AA|123420180707140618

For those of you wondering what a DefaultExecutorService (shown in the last line of code illustration above), it is a concurrency feature offered by the Java language to enable you to run tasks asynchronously without having to manage thread management ourselves (it manages the thread pooling and caching internally for you). HAPI provides a default executor service as part of the Default HAPI Context we used in our examples when managing connection listeners, and this implementation should be suffient for most small to mid-size messaging requirements. However, HAPI also allows you to inject your own custom executor service if you wanted full control over the thread pooling strategy. Normally an instance of an Executor Service (default or otherwise) will not be automatically destroyed when all tasks assigned to it have been completed, and it will still linger around and wait for any new tasks to be assigned. However, if you have no use for it, and you want to clean up properly after your completion of your application, you will want to explicitly shut down the ExecutorService using either the shutdown and shutdownNow methods. I will let you read the official Java documentation and also consult the official HAPI documentation if you are keen to explore this area.

Conclusion

That brings us to the end of this tutorial on building HL7 listeners using the HAPI framework. It was a bit lengthy but hopefully it provided a good overview of all the essentials when starting with HAPI and HL7 listeners. A topic that I could not delve deeply in this tutorial as much as I would have to liked to is on message acknowledgments. This is an extremely important topic in the overall scheme of things especially when you are building HL7 listeners. This also requires a lengthy explanation of the many ways of responding to HL7 messages. However, I will at least mention here that there are several message acknowledgment modes specified in the HL7 standard. These modes are a way of specifying whether you want a response saying a message was simply received by the listener, or whether you want to know whether a message was semantically understood and fully processed by the listener in a deeper sense. There are also non-HL7 static string responses that are sometimes used when a message response in a HL7 format is not possible for some reason by the receiving application. Like I said previously, this is a very large topic and you should consult the official documentation for more information. I will hopefully at least cover some of this information in a future tutorial. However, in the next tutorial in my HL7 article series, I will cover message parsing functionality available within the HAPI framework which will enable you to extract content from the HL7 messages easily. See you then!