与 Initiate Patient V9.2 的 Outbound Broker Web 服务进行集成

连接患者信息

IBM Initiate® Patient 是一款行业领先的患者主索引 (EMPI) 产品。本文将探讨 Outbound Broker,它是 Initiate Patient 的一项集成功能,该功能允许它将有关集线器内的事件的通知发送到外部系统。文中将演示如何在 Web 服务框架中实现 Outbound Broker。

Jeremy Kohansimeh, 高级顾问,IBM Software Services for WebSphere, Healthcare and Life Sciences Practice, IBM

Jeremy KohansimehJeremy Kohansimeh 在领导团队为药品发现、临床信息系统开发围绕患者分析的解决方案来管理治疗质量和健康信息交换方面的丰富经验。



2011 年 10 月 31 日

概述

IBM 最近收购了 Initiate,这是一家专注于医疗行业主数据管理 (MDM) 的公司。它的旗舰产品 Initiate Patient 提供了许多围绕患者主索引 (EMPI) 的先进的行业领先功能。EMPI 在医疗设置中被用于识别不同的患者记录何时指的是同一个人。此功能对建立患者的综合视图至关重要,例如用于创建从不同设施上的治疗结果汇编而成的时序健康记录。它形成了大部分健康信息交换 (HIE) 实现的基础。

有多种将 Initiate Patient 与医疗企业系统集成的方法。在集成时常常需要的一种功能,即向外部系统异步通知在 Initiate 内发生的事件,使它们能够执行适当的调整操作。Initiate 的 Message Broker Suite 附带了一个 Outbound Broker 组件,可以通过配置它与 Initiate 中心内的特定事件相关联,在这些事件发生时发送消息来通知外部系统。

本文将介绍 Outbound Broker 的一项新功能,该功能允许使用基于 HTTP 的 SOAP 消息,以 Web 服务的形式向外部系统发送通知。在此增强功能可用之前,只能通过 TCP 套接字发送通知,这使得与基于 Web 的系统的集成变得更加复杂。这种套接字通信形式适用于操作 HL7 V2 消息的早期医疗系统,HL7 V2 消息是作为基于 TCP/IP 的 LLP 实现的。但是,随着 HL7 V3 消息标准(通常使用基于 HTTP 的 SOAP 将它实现为 Web 服务)的引入,更现代的医疗机构需要使用基于 Web 的技术进行集成。

本文假设您对 Initiate Patient 及其数据结构有基本的了解。


系统前提条件

要执行本文中的指令和示例,您应该安装以下软件:

  • Initiate Patient V9.2.x
  • Initiate Message Broker Suite V9.2.0.178 或更高版本
  • WebSphere® Application Server V6.1 或类似的 servlet 容器

Outbound Broker 概述

本节将从大体上概述 Outbound Broker。更具体的实现细节已在 Message Broker 文档 中详细介绍过。

安装 Message Broker Suite 之后,该中心就可以将事件广播到外部系统了。当设置 Outbound Broker 实例时,可以将它配置为与 4 种具体的事件相关联。当所选的事件发生在中心内部时,会触发一系列步骤,最终得到一条关于该事件已发送到外部系统的消息。消息的格式以及它的内容可根据每个 Outbound Broker 实例的需要进行配置。消息传输也有多种选项,包括 TCP、SSL 和基于 HTTP 的 SOAP。

可以通过配置 Outbound Broker 实例来监听的 4 种事件是:

  • Add Member:在将一个成员添加到 Initiate 时触发
  • EID Updates:在成员所属的条目更改时触发
  • Has Shadow:在一个成员属性通过 Initiate Inspector 更改并需要来自源系统的确认才能激活时触发
  • PreMerge:在 Inspector 中的潜在重复任务通过合并两个成员记录得以解决,并且需要来自源系统的确认,一个成员才可以废弃它时,才会触发该事件。

为所创建的每个 Outbound Broker 实例安装了两种服务。Outbound Broker 服务在中心内发生触发事件时将消息放入传出的队列中。Message Sender 服务负责从传出的队列获取消息,然后将它们传送给适当的外部系统。Message Sender 会等待回执(来自外部系统的确认或拒绝),以决定将消息移动到成功队列还是拒绝队列中。


实际示例

我们将通过一个常见场景来演示设置和集成 Outbound Broker 实例与基于 Web 的系统的步骤。假设一家医疗企业构建了一个基于 Java™ EE 的应用程序,在将患者记录添加到 Initiate 中心时,需要通知该应用程序,以便可以使用中心记录的链接填充患者记录的内部表示形式。请注意,该示例仅演示了将 Initiate 与外部系统集成的一种方法。还有一些可供替代的架构,应该让具备所有相关可用知识的专家对它们进行评估。

对于本示例,有两部分内容需要集中在一起。第一部分是一个 Outbound Broker 实例,它将在将新成员添加到中心时使用基于 HTTP 的 SOAP 发送 Web 服务消息。第二个是基于 Java-EE 的应用程序的一个端点,它能够接收和处理实例的 Message Sender 所发送的消息。如果选择了基于 TCP 套接字的方法作为示例的消息传输方法,则需要构建一个独立的适配器组件,将原始的套接字消息转换为 HTTP 传输消息,后者可供基于 Web 的应用程序使用。

以下概述了集成两个系统的关键步骤:

  1. 安装 Message Broker Suite(如果还未安装)。请参阅 Message Broker 文档了解详细的说明。
  2. 创建一个数据源。请参阅 Message Broker 文档了解详细的说明。
  3. 创建一个会在将某个成员添加到中心时通知外部系统的传出实例。请参阅 Message Broker 文档,了解有关的详细说明。请留意文档中的以下选项:
    • 发送主机名:外部系统的主机名
    • 发送端口号:将与外部系统进行 HTTP 通信的端口
    • 在询问 Message Sender 是否将调用 Web 服务时输入 y
      • 对于 To URL 选项,请键入将从 Message Sender 接收消息的 servlet 的完整 URL 端点
    • 对于 message type 选项,请键入 xml
    • 在要求在将成员添加到中心时创建消息时输入 y
  4. 创建传出的实例后,需要配置它,使发送的消息中包含正确的数据。这通过编辑传出实例主目录的 config_<instance name> 目录中的 OutboundEIDAddXML.ini 配置文件来完成。此外,此文件被指定包含在从 Message Sender 发送到外部系统的消息中。清单 1 中提供了一个简单示例的一部分,其中包含基本的人口信息。清单 2 中给出了一个相应的示例消息,演示了该配置如何指导消息的创建。请注意,可能需要对示例配置进行编辑,以便能够与中心内的数据名称相匹配。
    清单 1. 配置文件摘录
    [Global-Data]
    
    0||MSGHEADER||||||
    	evtType||ADD|/EmpiMsg/MsgHeader/evtType
    	evtId|||/EmpiMsg/MsgHeader/evtId
    	evtInitiator|||/EmpiMsg/MsgHeader/evtInitiator
    	evtCtime|||/EmpiMsg/MsgHeader/evtCtime
    
    1||MEMHEAD||||||
    	srcCode|||/EmpiMsg/Member/MemHead/srcCode
    	memIdnum|||/EmpiMsg/Member/MemHead/memIdnum
    	entRecno|||/EmpiMsg/Member/MemHead/entRecno
    
    2||MEMNAME|PATNAME|||||
    	recStat|||/EmpiMsg/Member/MemName[attrCode="PATNAME"][@deleteInd]
    	onmFirst|||/EmpiMsg/Member/MemName[attrCode="PATNAME"]/onmFirst
    	onmMiddle|||/EmpiMsg/Member/MemName[attrCode="PATNAME"]/onmMiddle
    	onmLast|||/EmpiMsg/Member/MemName[attrCode="PATNAME"]/onmLast
    	onmPrefix|||/EmpiMsg/Member/MemName[attrCode="PATNAME"]/onmPrefix
    	onmSuffix|||/EmpiMsg/Member/MemName[attrCode="PATNAME"]/onmSuffix
    	onmDegree|||/EmpiMsg/Member/MemName[attrCode="PATNAME"]/onmDegree
    	onmTitle|||/EmpiMsg/Member/MemName[attrCode="PATNAME"]/onmTitle
    清单 2. 示例传出代理消息
    <?xml version="1.0" encoding="ISO-8859-1" standalone="no" ?>
    <!DOCTYPE EmpiMsg SYSTEM "initiate.dtd">
    <EmpiMsg>
    	<MsgHeader>
    		<evtType>ADD</evtType>
    		<evtId>103-466</evtId>
    		<evtInitiator></evtInitiator>
    		<evtCtime>20110222133649</evtCtime>
    	</MsgHeader>
    	<Member>
    		<MemHead>
    			<srcCode>OUTP</srcCode>
    			<memIdnum>1782^^^&2.16.840.1.113883.3.18.104&ISO</memIdnum>
    			<entRecno>205</entRecno>
    		</MemHead>
    		<MemName>
    			<attrCode deleteInd="A">PATNAME</attrCode>
    			<onmFirst>Blake</onmFirst>
    			<onmMiddle></onmMiddle>
    			<onmLast>Griffin</onmLast>
    			<onmPrefix></onmPrefix>
    			<onmSuffix></onmSuffix>
    			<onmDegree></onmDegree>
    			<onmTitle></onmTitle>
    		</MemName>
    	</Member>
    </EmpiMsg>
  5. 在 Java EE 中为基于 HTTP 的 SOAP 设置 Web 服务端点的最简单方式是:硬编码一个分析 SOAP 负载,并相应地处理其 servlet。一种更复杂的设置方法可能是:使用您最喜爱的 Java EE IDE 中的可用 Web 服务工具来自动生成将消息解组为 Java 对象的代码。
  6. 全面实现该 servlet 端点需要一个示例 SOAP 消息。获得此消息的一种方法是通过编码创建一个对向它发送的 SOAP 消息进行回复的端点,以便开始实现您的 servlet。从这里,您可以构建代码来解析和进一步处理消息。
  7. 为了将消息发送到端点,需要启动与实例对应的 Outbound Broker 服务和 Message Sender 服务。请注意,第一次启动它们时,需要为中心内存在的每个成员发送一条消息。在这之后,只在添加新成员时才发送消息。
  8. 使用 XPath 从传入的消息中解析出数据的示例 servlet 实现如清单 3 中所示。它包含一个 XML 响应的示例,该响应通知 Message Sender 成功创建了一条消息,并导致 Message Sender 将该消息移动到成功队列中。
    清单 3. Servlet 实现
    package com.ibm.initiate.broker.sample;
    
    import java.io.IOException;
    import java.util.Enumeration;
    import java.util.logging.Logger;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.xml.soap.MessageFactory;
    import javax.xml.soap.MimeHeaders;
    import javax.xml.soap.SOAPBody;
    import javax.xml.soap.SOAPConstants;
    import javax.xml.soap.SOAPEnvelope;
    import javax.xml.soap.SOAPException;
    import javax.xml.soap.SOAPFault;
    import javax.xml.soap.SOAPHeader;
    import javax.xml.soap.SOAPMessage;
    import javax.xml.soap.SOAPPart;
    import javax.xml.xpath.XPath;
    import javax.xml.xpath.XPathConstants;
    import javax.xml.xpath.XPathExpressionException;
    import javax.xml.xpath.XPathFactory;
    
    public class MemberAddedEndpoint extends HttpServlet 
    {
      private static final long serialVersionUID = 1L;
      
      private static final String CLASS_NAME = MemberAddedEndpoint.class.getName();
      private static Logger logger = Logger.getLogger(CLASS_NAME);
      
      private static final String CONTENT_TYPE_XML = "text/xml";
      private static final String CHAR_ENCODING_UTF_8 = "utf-8";
      
      // success Ack message for Outbound Broker Sender
      private static final String OUTBOUND_ACK_MSG =
    "<EmpiResp><MsgHeader><code>ok</code></MsgHeader></EmpiResp>";
      
      private static final String XPATH_PREFIX_MEMBER = "//EmpiMsg/Member";
      
      private static final String XPATH_PREFIX_MEMHEAD = 
        XPATH_PREFIX_MEMBER + "/MemHead";
      private static final String XPATH_SRCCODE = XPATH_PREFIX_MEMHEAD + "/srcCode";
      private static final String XPATH_MEMIDNUM = XPATH_PREFIX_MEMHEAD + "/memIdnum";
      private static final String XPATH_ENTRECNO = XPATH_PREFIX_MEMHEAD + "/entRecno";
      
      private static final String XPATH_PREFIX_MEMNAME = 
        XPATH_PREFIX_MEMBER + "/MemName";
      private static final String XPATH_FIRSTNAME = XPATH_PREFIX_MEMNAME + "/onmFirst";
      private static final String XPATH_MIDDLENAME = XPATH_PREFIX_MEMNAME + "/onmMiddle";
      private static final String XPATH_LASTNAME = XPATH_PREFIX_MEMNAME + "/onmLast";
      private static final String XPATH_NAMESUFFIX = XPATH_PREFIX_MEMNAME + "/onmSuffix";
      
      public MemberAddedEndpoint() {
        super();
      }
        
      protected void doGet(HttpServletRequest request, HttpServletResponse response) 
      throws ServletException, IOException 
      {
        // TODO Auto-generated method stub
      }
      
      protected void doPost(HttpServletRequest request, HttpServletResponse response) 
      throws ServletException, IOException 
      { 
        MessageFactory messageFactory = null;
        SOAPMessage responseMsg = null;
        
        try {
          messageFactory = MessageFactory.newInstance(SOAPConstants.SOAP_1_2_PROTOCOL);
          SOAPMessage soapMsg = 
            messageFactory.createMessage( getMimeHeaders( request), 
                                          request.getInputStream());
          
          SOAPBody body = soapMsg.getSOAPBody();
          logger.fine(
            "Received SOAP Message from Initiate Outbound Broker for Member Add: " 
            + body);
          
          // just parses message data and emits to log file
          // this is where business logic for the endpoint could be implemented
          displayParsedMessageData( body);
          responseMsg = messageFactory.createMessage();
            
          response.setContentType(CONTENT_TYPE_XML);
          response.setCharacterEncoding(CHAR_ENCODING_UTF_8);
    
          // send HTTP OK & custom XML message Ack back to Outbound Broker sender
          response.setStatus( HttpServletResponse.SC_OK);
          response.getWriter().write( OUTBOUND_ACK_MSG);
        } catch ( Exception e) {
          try {
            // send HTTP error and SOAPFault back to Outbound Broker sender
            addSOAPFault(responseMsg);
            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, 
                               responseMsg.toString());
          } catch (SOAPException soape) {
            // couldn't generate SOAPFault, so just send HTTP error back to sender
            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
          }
          
          logger.severe( "An unexpected error occurred: " +  e.getLocalizedMessage());
          e.printStackTrace();
        } 
      }
      
      private void displayParsedMessageData( SOAPBody body)
      throws XPathExpressionException
      {
        StringBuffer buff = new StringBuffer();
        
        buff.append( "Message received with contents: [");
        buff.append( "Member ID Number=").append( getDataValue(body,XPATH_MEMIDNUM));
        buff.append( ", Entity Record Number=").append( 
                                                  getDataValue(body,XPATH_ENTRECNO));
        buff.append( ", Source Code=").append( getDataValue(body,XPATH_SRCCODE));
        buff.append( ", First Name=").append( getDataValue(body,XPATH_FIRSTNAME));
        buff.append( ", Last Name=").append( getDataValue(body,XPATH_LASTNAME));
        buff.append( "]");
        
        logger.info(buff.toString());
      }
      
      private String getDataValue( SOAPBody body, String xpathExpr) 
      throws XPathExpressionException
      {
          XPathFactory factory = XPathFactory.newInstance();
          XPath xpath = factory.newXPath();
          
          String value = null;
          
          Object result = xpath.evaluate( xpathExpr, body, XPathConstants.STRING);
          if ( result != null) {
            value = result.toString();
          }
          
          return value;
      }
      
      private MimeHeaders getMimeHeaders( HttpServletRequest request)
      {
        MimeHeaders mimeHeaders = new MimeHeaders();
        Enumeration mimeHeaderNames = request.getHeaderNames();
        
        String headerName = "";
        String headerVal = "";
        
        while (mimeHeaderNames.hasMoreElements()) {
          headerName = (String)mimeHeaderNames.nextElement();
          headerVal = request.getHeader(headerName);
          mimeHeaders.addHeader(headerName, headerVal);
        }
        
        return mimeHeaders;
      }
      
      private void addSOAPFault( SOAPMessage msg)
      throws SOAPException
      {
          SOAPPart part = msg.getSOAPPart();
          SOAPEnvelope env = part.getEnvelope();
          SOAPHeader head = msg.getSOAPHeader();
          SOAPBody body = msg.getSOAPBody();
          
          head.detachNode();
          SOAPFault soapFault = body.addFault();
          soapFault.addNamespaceDeclaration(env.getPrefix(), env.getNamespaceURI());
          soapFault.setFaultCode(env.createName("Receiver", 
                                                env.getPrefix(),env.getNamespaceURI()));
          
          String faultMsg = 
               "System is unable to process the Initiate Outbound Broker Message";
          soapFault.setFaultString(faultMsg);
          soapFault.setFaultActor("Add Member Outbound Broker Endpoint");
      }
    }

提示和技巧

能够从中心发送消息而不向其中实际添加新成员(此活动有时可能很耗时),这常常很有用。有一种简单方法可以完成此任务,即从您的中心数据库运行以下 SQL 语句:

update mpi_eidtrigger_<entity name> 
	set complete = 0 
	where memrecno = <existing member’s MemRecNo>

要更好地调试传出实例中的问题,可以针对 Outbound Broker 和 Message Sender 单独调整日志记录级别。可以通过编辑 services.ini 文件中对应于您希望提高其日志级别的服务部分来是实现此目的。设置 MAD_TRACE=1 将导致启用最详细的日志级别,然后是 MAD_DEBUG。提高日志级别会导致性能降级,因为需要协调额外的 I/O 操作,以便将它们写入日志文件。出于此原因,在生产环境中应该谨慎使用跟踪和调试日志级别。services.ini 文件的位置是通过 MAD_CONFNAME 环境变量来配置的。每个服务的日志文件单独位于传出实例主目录的日志目录中。

有 3 个队列用于处理发送到外部系统的消息。input、success 和 failure 队列都在文件系统上,通过一系列相互关联的文件来维护。它们位于实例主目录的 data/interface/outbound_<instance name> 目录中。为了清除队列,应该停止 Outbound Broker 和 Message Sender 服务,然后便可以安全地删除目录中的所有文件。有关队列工作原理的更多细节已在 Message Broker 文档中详细介绍。

Message Broker Suite V9.2.0.178 中已修复了一个错误,在 Message Sender 等待从外部系统收到回执时,该消息可能导致问题。所有执行 Web 服务消息传递的 Message Sender 都只等待 1 秒钟的时间,如果它们没有在该时间内收到成功回执,则会重新尝试修复错误消息并最终将它移动到失败队列中。此问题已修复,现在可通过调整 services.ini 文件中相应部分中的 MAD_SOTIMEOUT 设置,配置 Message Sender 等待来自外部系统的回执的时间长度。


结束语

较新的 Initiate Outbound Broker 版本中包含使用基于 HTTP 的 SOAP 发送 Web 服务消息的功能。与以前通过 TCP 套接字通信提供的支持相比,此新功能支持更加无缝地集成基于 Web 的系统:

  • 请参阅 清单 1 了解摘录自示例 Outbound Broker 配置文件的内容。
  • 请参阅 清单 2 了解从 Outbound Broker 发送的示例 XML 消息。
  • 请参阅 清单 3 了解接收 Outbound Broker 消息的示例 Java Servlet 端点。

参考资料

学习

获得产品和技术

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Information Management, SOA and web services
ArticleID=768625
ArticleTitle=与 Initiate Patient V9.2 的 Outbound Broker Web 服务进行集成
publish-date=10312011