Tuesday, February 12, 2013

HL7 Message - "Missing Body Schema"

I've been working with HL7 messages for a few months now.  As anyone who has worked with HL7 knows, each message type can have many different versions - 2.3.1,. 2.4, 2.5.1, and many more.  The question arises as to how to create one process for each message type that can handle all versions.  The correct answer is to create a canonical schema and create a map for each of the versions and put the maps on the receive or send port.

Now lets say you come on to a project that has already been implemented and they are not using canonical schemas.  The next step (after going on and on about why you really should use canonical schemas) is to use the Microsoft.XLANGs.BaseTypes.Any schema.  

Just a little bit of info for those who are new to HL7.  An HL7 messages contain 3 parts.  The MSHSegment or header segment, the BodySegment which obviously contains the body, and a ZSegment which can contain addition information that is not defined in the normal HL7 message but in my experience is usually left blank.  I set the MSH segment to a MSH schema.  The BodySegment is set to the Any schema, and the ZSegment is set to either an XmlDocument or String .NET value.

Using this type of schema will work, however you will run into issues when trying to assemble the HL7 message in the BTAHL72XPipelines.BTAHL72XSendPipeline.  If you get the error message  "Body schema is missing", it does not mean you are missing the the body segment.  It means that the HL7 assembler cannot determine which HL7 schema to use based on the messagetype namespace.  Which, would be set to "http://schemas.microsoft.com/BizTalk/2003/Any". 

The following code can be used if calling the BTAHL72XPipelines from an orchestration.  To call this code from a pipeline component you would only need to call the setContext method.  For reference, XLANGMessage is a message that is used within the orchestrations and does not allow you to set the MessageType context property.  IBaseMessage is used within the pipeline/pipeline components and allows you to set the MessageType context property.  There is no direct way to create one type from another.

HL7SetContext(XLANGMessage hl7Message, string messageType)
        {
            if (hl7Message == null || hl7Message.Count == 0)
                return string.Empty;


            Stream stream = null;


            //Create an IBaseMessage
            IBaseMessageFactory _factory = new MessageFactory();
            IBaseMessage inputMessage = _factory.CreateMessage();
            inputMessage.Context = _factory.CreateMessageContext();

            string partName = string.Empty;

           
           //Get the XLANG Parts and add them to the IBaseMessage
            for (int whichPart = 0; whichPart < hl7Message.Count; whichPart++)
            {
                partName = hl7Message[whichPart].Name;

                stream = null;
                stream = hl7Message[whichPart].RetrieveAs(typeof(Stream)) as Stream;
               
                IBaseMessagePart part = new MessageFactory().CreateMessagePart();
                part.Data = stream;

               inputMessage.AddPart(partName, part, string.Compare(partName, "BodySegments", true) == 0);
            }


 //Very Important Step, do not leave this out
inputMessage = setContext(inputMessage, messageType);

//Create the Pipeline
SendPipelineWrapper sendPipeline = Winterdom.BizTalk.PipelineTesting.PipelineFactory.CreateSendPipeline(typeof(BTAHL72XPipelines.BTAHL72XSendPipeline));

            sendPipeline.AddDocSpec(typeof(Common.Schemas.HL7_25.MSH_25_GLO_DEF));
            sendPipeline.AddDocSpec(typeof(Common.Schemas.HL7_Schema_Type));
            sendPipeline.AddDocSpec(typeof(Common.Schemas.Z_24_GLO_DEF));

            IBaseMessage ret = null;

            try
            {
                ret = sendPipeline.Execute(inputMessage);
            }
            catch (Exception ex)
            {
                throw ex;
            }

   
    //TODO:  Add your code using the messages in ret
 
}




 //The values below are shown as empty strings as examples, but when coding must contain a value

IBaseMessage setContext(IBaseMessage inputMessage, string messageType)
{
inputMessage.Context.Promote("MessageType", "http://schemas.microsoft.com/BizTalk/2003/system-properties", messageType);
//using a custom HL7 party
inputMessage.Context.Promote("MSH3_1", "http://HL7Schemas.HeaderPropertySchemas", "");        inputMessage.Context.Promote("MSH5_1", "http://HL7Schemas.HeaderPropertySchema", "");  inputMessage.Context.Promote("IsStaticAck", "http://HL7Schemas.HeaderPropertySchema", false);

inputMessage.Context.Promote("ZPartPresent", "http://HL7Schemas.HeaderPropertySchema", false);
inputMessage.Context.Promote("SPName", "http://schemas.microsoft.com/BizTalk/2003/system-properties", "XXX");
inputMessage.Context.Promote("MessageEncoding", "http://HL7Schemas.HeaderPropertySchema", 65001);
 inputMessage.Context.Promote("MSH1","http://HL7Schemas.HeaderPropertySchema", "124");
inputMessage.Context.Promote("MSH2","http://HL7Schemas.HeaderPropertySchema", "^~\\&");
inputMessage.Context.Promote("SegmentDelimiter2Char","http://HL7Schemas.HeaderPropertySchema", true;); 



return inputMessage;
}