The previous couple of posts were related to using Apache Camel in a healthcare integration setting.  The HL7 support in Camel is built on the HAPI library which provides Java classes for most of the HL7 v2 variants, plus MLLP support.  So in integration terms, HL7 can be considered a combination of an endpoint type (the MLLP/TCP protocol) and a message format (the HL7 ER7 or XML representation).

The Camel architecture nicely isolates endpoint-types and message-structure types in Components and DataFormats, respectively.  A Component is something which knows about a specific communication mechanism or protocol: in the case of HL7, the Camel HL7 Component knows how to exploit the underlying Apache Mina library to exchange delimited text messages using the HL7 protocol.  The Component also acts as a factory for Camel Endpoint objects: it hands out individual Endpoint objects, each of which represents an instance of the endpoint-type implemented by the Component. Camel DataFormat objects marshal / unmarshal byte-streams or strings to / from Java objects of some type.  This makes it much easier to build content-based routes in Camel.

HL7 ACK / NAK Messages

In the previous post, one of the issues I had to deal with in the example route was returning an ACK or a NAK to the message originator. The HL7 protocol uses a positive-acknowledgement scheme: receivers will ACKnowledge (ACK) messages they understand and can process, and will Negatively AcKnowledge (NAK) anything else.  The HAPI library provides support for this in the makeACK method of the DefaultApplication class.  Unfortunately, this is less helpful when we need to return a NAK.

Recall that the first part of my route was checking to see whether the right kind of message has been received. In the sample, I was testing just the message type (MSH-9.1) and the trigger event (MSH-9.2) fields.  In reality, you would probably also want to check that the sender was using the same version of HL7 as you are expecting: you can do this by examining the value of field MSH-12. It will become clear why this is important, below.

Suppose the inbound message fails the MSH-9 test. The otherwise() clause in my little route will send the HL7 data (unmarshalled from the stream) to the badMessage() method on my handler bean.  This method expects a single argument of type ca.uhn.hl7v2.model.Message, i.e. a generic HAPI HL7 message object.  The argument can’t be of a more specific type than this because the Camel route builder cannot know in advance what will be received and unmarshalled from the endpoint; it can only assume it will be a HAPI Message.

Constructing an ACK

In the badMessage method, I need to construct a HL7 NAK message to be returned to the caller. HL7 ACKs and NAKs share the same structure: we just need to change the value of one field (and optionally add a diagnostic message) to indicate NAK. The obvious solution is to call DefaultApplication.makeACK, mentioned earlier, and modify the returned structure as necessary.  MakeACK’s single argument is a single Segment, the intention being that the caller passes-in the MSH segment of the original inbound message:
Message makeACK(Segment inboundHeader)

The reason for this is that the makeACK method needs some values from the inbound MSH segment to populate fields in the Message Acknowledgement segment (MSA) of the ACK correctly. An HL7 ACK (or NAK) must be associated with the original message (i.e. the inbound message being ACK’d or NAK’d) so that the originator can distinguish which of its outbound messages is being acknowledged or rejected. This is done using the Message Control ID, which is field 10 of the MSH segment.

But remember where we are in the Camel route: we have an invalid message in our hands. We don’t know the exact class-type of the inbound message object so we can’t cast the Message to a specific type. And the Message interface itself doesn’t provide a way to extract the MSH segment. So how are we going to call makeACK(Segment)?

We cannot simply new-up an instance of some specific message type (e.g. a version 2.3 ORM^O01) and use its MSH segment as the makeACK argument:

    // Assume ca.uhn.hl7v2.model.v23.*
   
    ORM_O01 orm = new ORM_O01();

    ACK ackMsg = (ACK)DefaultApplication.makeACK(orm.getMSH());        // <– Bang!

    // Set MSA.1 to Application Reject (AR)
    ackMsg.getMSA().getAcknowledgementCode().setValue(“AR”);

    // etc.

This will compile, but won’t work. The manufactured ORM_O01, despite being an instance of a specific version of HL7, is not properly constructed, in several ways. First, MSH-12.1 will not contain “2.3″, which is surprising, given that HAPI should be able to populate MSH-12.1 for a new message instance. If we pass the ORM_O01 MSH to makeACK, it tries to get the value of MSH-12.1 but finds a null. This causes makeACK to fall-back to creating and returning an ACK for version 2.4 of HL7 (this is baked-in to the HAPI code – I’m not sure if this is an arbitrary HAPI design decision, or to do with the HL7 spec). And of course we get a ClassCastException at run-time as a result, because we are expecting a v2.3 object.

But even if this did work, isn’t it ugly? We’d be baking-in a version of HL7 because we must choose a specific ORM_O01. So how do we create the ACK structure, and how can we make our code work for any version of HL7 supported by HAPI?

Terser to the rescue

Fortunately, HAPI provides a handy utility class for parsing and unparsing HL7 messages of arbitrary versions – the Terser. This will let us get hold of the MSH segment from the original message, so we could at least call makeACK with the original MSH:

    // This still depends on the ackMsg decl. being for the same specific version
    // as the original message.
    Terser t = new Terser(originalMsg);
    Segment msh = t.getSegment(“MSH”);
    ackMsg = (ACK)DefaultApplication.makeACK(msh);
    ackMsg.getMSA().getAcknowledgementCode().setValue(“AR”);

Now when we call makeACK we are passing-in a valid MSH so it will extract the HL7 version number and dynamically create the correct ACK class, using Class.forName.  But as the comment says, this stilll only works if the compiled-in ACK declaration matches the returned ACK message version. We need to lose the compiled-in ACK declaration, but still find a way to populate the ACK message’s MSA segment with the right values.

Fortunately, we can use the Terser to set the field values in a version-independent way. This is exactly what DefaultApplication’s static methods makeACK and fillResponseHeader do.  So I used a combination of the Terser and the guts of DefaultApplication.makeACK to create a couple of helper methods which do the right thing.  I’ve put the source code on this page in my Google site, for anyone who wants to use it.

Thanks to Claus Ibsen for such a helpful response to my previous post.  As Claus points out, the answer is to use a Camel helper class, PredicateBuilder, which contains a bunch of useful static combinator methods.  Claus shows in his post how to implement my trivial conjunction easily using the and() method.  Note the generic signatures of all these helper methods, e.g.

<E extends Exchange> Predicate<E> and(final Predicate<E> left, final Predicate<E> right)

Because they accept Exchange (or any derived class), any custom Predicate you might need to write has access to the complete message exchange structure (in, out and fault), and all predicates could be applied to a custom extension of Exchange.

Having got over those two ‘humps’ (sorry – couldn’t resist that), I’m feeling very good about Camel.  It’s a mighty impressive piece of work and looks like it has a lot of potential for the area I’m working in right now.

I’ve been spending some time with Apache Camel and ActiveMQ, with a view to using Camel in a healthcare integration project.  Camel has support for HL7 both at the message structure level (wrapping access to the HAPI libraries) and at the protocol level (via the Apache Mina extensions for HL7 MLLP).

As anyone who’s looked at Camel (or IONA’s FUSE Mediation Router) will know, Camel implements a rather nice Java DSL for building routes.  This lets you write things like:
from(“hl7listener”)
            .unmarshal(hl7Format)
            .choice()
              .when( header(“hl7.msh.messageType”).isEqualTo(“ORM”) )
                    .beanRef(“hl7handler”, “handleORM”)
              .otherwise()
                    .beanRef(“hl7handler”, “badMessage”)
              // end choice block- marshal the ACK/NAK back to the TCP endpoint
            .end()
            .marshal(hl7Format);
Very readable, but as you might expect, it hides quite a bit of complexity.  This is no bad thing, but you do need to understand what’s going on behind the DSL. 
Camel exploits Spring, and in the above fragment “hl7listener” and “hl7handler” are bean names. The unmarshal and marshal methods allow DataFormat objects to convert between byte-streams and more convenient objects, such as HAPI messages.  The choice/when/otherwise operators allow predicates to control routing.

It’s taking a while for me to grok Camel; here is a couple of things I’m not yet clear about:

  • Knowing how to get an intermediate stage in a route to return an ACK or NAK to the originator, without subsequent (or parallel) stages in the route from doing so. 
  • Creating compound boolean expressions in predicates. For instance, if I want a conjunction inside a when(), I can’t have it. There don’t appear to be combinators for expressions.  
In the fragment above for example, how can I express a conjunction/disjunction in the when( … ) expression?  It only seems to be able to handle a single condition: I haven’t found a way to combine predicates e.g. I can’t do this:
when ( header(“hl7.msh.messageType”).isEqualTo(“ORM”) &&
             header(“hl7.msh.triggerEvent”).isEqualTo(“O01″) ).    // etc.
I have tried alternatives, e.g. nesting the when clauses:
from(“hl7listener”)
            .unmarshal(hl7Format)
            .choice()
              .when( header(“hl7.msh.messageType”).isEqualTo(“ORM”) )
                  .choice()
                    .when( header(“hl7.msh.triggerEvent”).isEqualTo(“O01″) )
                        .beanRef(“hl7handler”, “handleORM”)
                    .otherwise()
                        .beanRef(“hl7handler”, “badMessage”)
              .otherwise()
                  .beanRef(“hl7handler”, “badMessage”)
            // end choice block- marshal the ACK/NAK back to the TCP endpoint
            .end()
            .marshal(hl7Format);
But this doesn’t work either.  I’m just starting out with Camel, so I expect I simply haven’t read the right bit of the documentation (which even the creators admit is one of Camel’s weak points).
Another problem is controlling which component in the route returns a response to the originating endpoint (the MLLP sender, in the HL7 case).  I may have solved this one, but it’s worth setting out the problem and my approach, in case it helps someone else.
My requirements are for the pipeline to log the inbound message (i.e. whatever is actually received on the HL7 socket), unmarshal the message to HL7 (HAPI), then validate the HL7 message type: at this stage in the pipeline I want to return a NAK to the sender (on the HL7 MLLP connection) if the message is not of the expected type, otherwise I want to write the message to a JMS queue for further processing and return an ACK.
The following route appears to work (note that I have omitted the message-logger stage):
        // We need the specific HL7 DataFormat object for unmarshalling HL7 ER7 from the
        // inbound MLLP link.
        DataFormat hl7Format = new HL7DataFormat();
        // Inbound HL7 comes from MLLP endpoint. 
        // Timestamp message, log receipt, unmarshal and perform initial
        // handling, which just determines if its the right kind of HL7
        // message.
        // If so, return ACK to originator and push message to JMS queue.
        // If not, return NAK to originator and end.
        //
        from(“hl7listener”)
            .unmarshal(hl7Format)
            .choice()
              .when( header(“hl7.msh.messageType”).isEqualTo(“ORM”) )
                    .marshal().hl7().to(“jms:queue:orderMessage.queue”)
              .otherwise()
                    .beanRef(“hl7handler”, “badMessage”)
            .end()
            .marshal(hl7Format);
        
        // The order message processor – pulls message from JMS queue and sends to
        // the processor bean, which creates and returns an ACK to the hl7 channel
        // if the message is of the expected type.
        //
        from(“jms:queue:orderMessage.queue”)
            .unmarshal().hl7().beanRef(“hl7handler”, “handleORM”); 
All of this routing can be expressed in XML instead of using the Java DSL. Once I’m comfortable with Camel I may try writing a NetBeans plugin or RCP application to create visuals tools for Camel routes.