随着系统变得愈加松散耦合,开发者开始碰 到一些相互矛盾的问题。有些系统可以从事件驱动编程(比如 Web 服务应用)中获益,并不需要采用这样一种可以简化问题的方式进行构建。WS-Notification 规范试图解决这个问题,方法是通过为 Web 服务提供一种标准的方法,从而对希望接收有关特定主题的需求方发送特定的需求信号。本文将介绍 NotificationConsumer、NotificationProducer、订阅和通知的一个简单实现。本文还将简要介绍规范所涉及的其他领 域。 注意:本文使用 Java? servlets 来展示实现一个 WS-Notification 解决方案的必要步骤,但是其中的概念对于任何编程环境来说都是相同的。要采用这些代码,您需要熟悉 Java 编程的知识。如果具有 Java servlet 的编程经验,将会非常有帮助。如果具有 XML 方面的经验,也会非常有帮助,但这并不是必需的。 什么是 WS-Notification?WS- Notification(WSN)是一系列规范,它制定了在 Web 服务环境中创建事件驱动系统的标准流程。它们是与 Web 服务资源框架(Web Services Resource Framework,WSRF)一起开发的(后者提供了一种在无状态环境中使用状态的方法),但是它们也可以在普通的 Web 服务中使用。 WSN 包括 3 个规范:
本文将着重介绍 WS-BaseNotification 规范的一些基本信息。 什么是 WS-Resource?我们应该花一点时间来讨论一个主题:它超出了 WSRF 规范的范围,但对于 WS-Notification 来说却是至关重要的,这就是 WS-Resources。 Web 服务本质上是一个无状态 媒介。请求中惟一可用的信息就是这个请求中所包含的信息。这使得创建一些必须在多次交互之间保留信息的应用程序很困难。例如,我们很快就要使用一个 Web 服务来创建对某个特定主题 订阅 。如果该订阅只是在创建该请求时存在,那么它就不太有用了。 反之,我们需要订阅是有状态的。有状态的资源可以在交互过程之间存在,顾名思义,它是具有状态 的。状态是通过一组属性进行定义的。修改一个属性,状态就会发生变化,反之亦然。 WS-Resource 是一组有状态的资源和与之进行交互的 Web 服务的组合。在本文中,我们将创建几个 WS-Resource。 我们要实现什么?为 了展示 WS-Notification 的用法,我们将创建一个简单的系统,它会跟踪搜索引擎(例如 google)中的第一个位置。 NotificationConsumer 发起对某个关键字的订阅,当该关键字的第一个位置发生变化时,NotificationProducer 就会发送一个通知来详细介绍这种变化。(实际对搜索引擎进行检查的过程已经超出了本文的范围。相反,我们会着重介绍整个通知的过程。不过如果您非常关注这 个问题,请参阅 参考资料,获得有关使用 Google 的 Web 服务 API 的教程)。 我 们首先使用一个简单的 HTML 表单,用户可以在这个表单中输入自己的 e-mail 地址和希望查看的关键字。这个表单会提交到消费者 Web 服务(consumer Web service),它会将每个用户保存为一个 WS-Resource。消费者服务调用生产者 Web 服务(producer Web service),后者创建一个订阅 WS-Resource。 当生产者看到一个针对关键字的变化时,就为每个监视这个关键字的订阅收集信息,并发送一个通知给消费者 Web 服务,而它又将信息传递给适当的 WS-Resource。 准备开始为 了让一切都正常工作,我们需要为消费者和生产者各创建一个 Web 服务。为了简化这个过程,我们会将这两个 Web 服务各自创建为一个 servlet,它负责接收 POST 请求。在这个例子中,我们使用 Apache Jakarta 项目中的 Tomcat V5 的 servlet 容器,但是任何可以支持 servlet 的服务器都可以使用。这种设置需要执行以下操作:
NotificationConsumer在真正开始编码之前,让我们首先来了解一下消费者的结构。 NotificationConsumer 可以是一个普通的 Web 服务或是一个 WS-Resource。在这两种情况中,我们都是使用 Web 服务描述语言(Web Services Description Language,WSDL)来描述它们。在 ConsumerServiceServlet 的情况中,我们具有以下的 WSDL 文件: 清单 1. NotificationConsumer WSDL 文件<?xml version="1.0" encoding="UTF-8"?> <definitions name="Search" targetNamespace="http:///search" xmlns="http://schemas./wsdl/" xmlns:tns="http:///search" xmlns:wsa= "http://schemas./ws/2004/03/addressing" xmlns:wsdl="http://schemas./wsdl/" xmlns:wsrp="http://docs./wsrf/2004/06/wsr f-WS-ResourceProperties-1.2-draft-01.xsd" xmlns:wsrpwsdl="http://docs./wsrf/2004/0 6/wsrf-WS-ResourceProperties-1.2-draft-01.wsdl" xmlns:wsnt="http://docs./wsn/2004/06/wsn-W S-BaseNotification-1.2-draft-01.xsd" xmlns:wsntw="http://docs./wsn/2004/06/ws n-WS-BaseNotification-1.2-draft-01.wsdl" xmlns:soap="http://schemas./wsdl/soap/"> <wsdl:import namespace="http://docs./ws rf/2004/06/wsrf-WS-ResourceProperties-1.2-draft-01.wsdl" location="WS-ResourceProperties.wsdl" /> <wsdl:import namespace="http://docs./ws n/2004/06/wsn-WS-BaseNotification-1.2-draft-01.wsdl" location="WS-BaseN.wsdl" /> <types> <xsd:schema targetNamespace="http:///search" xmlns:xsd="http://www./2001/XMLSchema"> <xsd:element name="email" type="xsd:string" /> <xsd:element name="keyword" type="xsd:string" /> <xsd:element name="SearchSubscriberProperties"> <xsd:complexType> <xsd:sequence> <xsd:element ref="email" minOccurs="1" maxOccurs="1"/> <xsd:element ref="keyword" minOccurs="1" maxOccurs="1"/> <xsd:any/> </xsd:sequence> </xsd:complexType> </xsd:element> </xsd:schema> </types> <portType name="ConsumerPortType" wsrp:ResourceProperties="tns:SearchSubscriberProperties"> <operation name="GetResourceProperty"> <input message= "wsrpwsdl:GetResourcePropertyRequest" wsa:Action="http://docs./wsr f/2004/06/WS-ResourceProperties/GetResourceProperty"/> <output message= "wsrpwsdl:GetResourcePropertyResponse" wsa:Action="http://docs./wsrf/200 4/06/WS-ResourceProperties/GetResourcePropertyResponse"/> </operation> <wsdl:operation name="Notify"> <wsdl:input message="wsntw:Notify" wsa:Action="http://docs./wsn/2 004/06/wsn-WS-BaseNotification/Notify"/> </wsdl:operation> </portType> <binding name="ConsumerSoapBinding" type="tns:/ConsumerPortType"> <soap:binding style="document" transport= "http://schemas./soap/http"/> <operation name="GetResourceProperty"> <input> <soap:body use="literal"/> </input> <output> <soap:body use="literal"/> </output> </operation> <operation name="Notify"> <input> <soap:body use="literal"/> </input> </operation> </binding> <service name="ConsumerService"> <port name="ConsumerPort" binding="tns:ConsumerSoapBinding"> <soap:address location="http:///search"/> </port> </service> </definitions> 下面我们逐步来讲解。首先,导入 WS-ResourceProperties 和 WS-BaseNotification WSDL 文件。这些文件定义了我们所需要的类型和消息,因此包括这些文件使我们可以简单地引用它们。 接下来,我们为每个使用 SearchSubscriberProperties 类型的订阅者定义资源属性文档。在本文中,我们并不想使用 Web 服务来操作这些属性,但是这对于理解总体的结构非常重要。 然后,我们创建 portType,它会作为这个服务的一种接口。注意另外一个属性:wsrp:ResourceProperties。该值指向资源属性文档类型,并指明所包含的操作将应用到的资源。 在 这种情况中,我们有两个操作。首先是 GetResourceProperty,它是所有的 WS-Resources 都需要的。顾名思义,它使客户机可以获取一个 WS-Resource 的属性的值。其次是 Notify 操作,NotificationProducer 会使用它来真正发送通知。这两个操作使用各自所包含的 WSDL 文件中定义的消息。 从现在开始,就是为每个操作指定一个绑定并将这个绑定应用到一个服务上的标准问题了。 现在,我们将着重介绍 Notify 操作,因为不管您是使用一个普通的 Web 服务还是 WS-Resource,所有的 NotificationConsumers 都需要执行这个操作。 基本的消费者 servlet这个过程的第一个步骤是为每个订阅者搜集 e-mail 地址和关键字。要实现这种功能,可以创建一个简单的 HTML 表单,如清单 2 所示。 清单 2. 订阅表单<html> <head> <title>Create a subscription</title> </head> <body> <form method="GET" action="http://localhost:8080/servlets-examples/servle t/ConsumerServiceServlet"> <h2>Create a subscription</h2> Email <input type="text" name="email" /> <br /> Topic <input type="text" name="topic" /> <br /> <input type="submit"> </form> </body> </html> 注意,我的消费者 servlet 位于 确保将 action 参数修改为匹配您自己的系统。这个页面会创建一个表单,我们可以在这个表单中输入订阅者的基本信息。 现在让我们来看一下基本的 servlet。对这些值的检索就是一个访问请求参数的简单问题。 清单 3. 基本的 servletimport javax.servlet.*; import javax.servlet.http.*; import java.io.*; public class ConsumerServiceServlet extends HttpServlet implements Servlet { public ConsumerServiceServlet() { super(); } protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String email = req.getParameter("email"); resp.getWriter().println("Email: "+email); String topic = req.getParameter("topic"); resp.getWriter().println("Topic: "+topic); } protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { } } 这个 servlet 可以从客户机(在本例中是浏览器)接收 GET 或 POST 消息。通常,我们会作为一个 POST 消息提交这个表单,但是以后我们将使用 编译这个 servlet,并将其部署到服务器上。(为了让这些变化生效,可能需要重新启动服务器。)输入一个 e-mail 地址和关键字,然后提交表单。现在这些信息应该在浏览器中重现了。 创建一个订阅者消费者服务的目的是利用一些信息,并使用这些信息为这个用户创建一个订阅。当这个服务接收到该用户的一个通知时,就将其传递给这个用户。要使这一切正常操作,我们需要该服务使用一种方法来对每个用户进行跟踪。 在一个实际的应用程序中,我们将使用一些持久性的方法,例如将信息保存到一个文件或数据库中。在本例中,为了简单起见,创建了 SubscriptionConsumer 类。 清单 4. 基本的消费者类public class SubscriptionConsumer { private String email; private String topic; public SubscriptionConsumer(String newEmail, String newTopic){ setEmail(newEmail); setTopic(newTopic); } public void setEmail (String newEmail){ email = newEmail; } public void setTopic (String newTopic){ topic = newTopic; } public String getEmail(){ return email; } public String getTopic(){ return topic; } } 对于每个新用户,我们都会创建一个 SubscriptionConsumer 对象,并将其添加到这个 servlet 中的一个静态数组中,如清单 5 所示。(是的,这意味着在重新启动服务器时,我们将失去所有的信息。这是简单性的代价。) 清单 5. 保存订阅者的信息import javax.servlet.*; import javax.servlet.http.*; import java.io.*; public class ConsumerServiceServlet extends HttpServlet implements Servlet { static SubscriptionConsumer[] consumers = new SubscriptionConsumer[1000]; static int nextId; public ConsumerServiceServlet() { super(); nextId = 1; } protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String email = req.getParameter("email"); resp.getWriter().println("Email: "+email); String topic = req.getParameter("topic"); resp.getWriter().println("Topic: "+topic); SubscriptionConsumer thisConsumer = new SubscriptionConsumer(email, topic); int thisConsumerId = nextId++; consumers[thisConsumerId] = thisConsumer; resp.getWriter().println("Added consumer number "+ thisConsumerId); } protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { } } 当这个 servlet 第一次运行时,它会对这个数组和 每 次用户提交表单时,我们都创建一个新的 SubscriptionConsumer 对象,并将其添加到这个数组中。数组带来的简便性是,使我们可以简单地检索一个特定的订阅者。只要我们知道 ID,检索实际对象就非常简单。当服务接收到一个特定消费者的通知并需要传递该通知时,这种功能就非常方便。 订阅一个主题尽 管由于我们正在对一个 Web 服务进行处理,还没有真正创建 NotificationProducer,但是我们确切地知道需要发送的数据。根据 WS-BaseNotification 规范,我们需要发送一个 Simple Object Access Protocol(SOAP)消息,如清单 6 所示。 清单 6. Subscribe 消息<soapenv:Envelope xmlns:soapenv="http://schemas./soap/envelope/" xmlns:sat="http:///searchSystem" xmlns:wsa="http://www./2005/02/addressing" xmlns:wsnt="http://docs./wsn/2004/06/ws n-WS-BaseNotification-1.2-draft-01.xsd"> <soapenv:Header> <wsa:Action> http://docs./wsn/2004/06/WS-BaseNoti fication/Subscribe </wsa:Action> <wsa:To soapenv:mustUnderstand="1"> http://localhost:8080/servlets-examples/servlet/ProducerServ iceServlet </wsa:To> </soapenv:Header> <soapenv:Body> <wsnt:Subscribe> <wsnt:ConsumerReference> <wsa:Address> http://localhost:8080/servlets-examples/servlet/ConsumerServ iceServlet </wsa:Address> <wsa:ReferenceProperties> <search:subId xmlns:search="http://www./searcher"> 23 </search:subId> </ReferenceProperties> </wsnt:ConsumerReference> <wsnt:TopicExpression Dialect="http://docs.oasi s-open.org/wsn/2004/06/TopicExpression/Simple"> cheese </wsnt:TopicExpression> </wsnt:Subscribe> </soapenv:Body> </soapenv:Envelope> SOAP 头中包含两部分信息:我们在干什么( 实际的 Subscribe 消息包含两部分重要信息。首先是 现在让我们构建并准备传递这个消息。 清单 7. 构建 Subscribe 消息import javax.servlet.*; import javax.servlet.http.*; import java.io.*; import javax.xml.soap.*; public class ConsumerServiceServlet extends HttpServlet implements Servlet { static SubscriptionConsumer[] consumers = new SubscriptionConsumer[1000]; static int nextId; SOAPFactory soapFactory; public ConsumerServiceServlet() { super(); nextId = 1; try { soapFactory = SOAPFactory.newInstance(); } catch (Exception e){ e.printStackTrace(); } } protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String email = req.getParameter("email"); String topic = req.getParameter("topic"); SubscriptionConsumer thisConsumer = new SubscriptionConsumer(email, topic); int thisConsumerId = nextId++; consumers[thisConsumerId] = thisConsumer; try { MessageFactory messageFactory = MessageFactory.newInstance(); SOAPMessage message = messageFactory.createMessage(); SOAPHeader header = message.getSOAPHeader(); SOAPHeaderElement actionElement = header.addHeaderElement( soapFactory.createName("Action", "wsa", "http://www./2005/02/addressing")); actionElement.addTextNode( "http://docs./wsn/2004/06/WS-BaseNotificatio n/Subscribe"); SOAPHeaderElement toElement = header.addHeaderElement( soapFactory.createName("To", "wsa", "http://www./2005/02/addressing")); toElement.addTextNode( "http://localhost:8080/servlets-examples/servlet/ProducerSer viceServlet"); toElement.setMustUnderstand(true); SOAPBody body = message.getSOAPBody(); SOAPElement bodyElement = body.addChildElement(soapFactory.createName( "Subscribe", "wsnt", "http://docs./wsn/2004/06/wsn-WS-BaseNotificat ion-1.2-draft-01.xsd")); SOAPElement subEndpoint=soapFactory.createElement( "ConsumerReference", "wsnt", "http://docs./wsn/2004/06/wsn-WS-BaseNotificat ion-1.2-draft-01.xsd"); SOAPElement address = soapFactory.createElement( "Address", "wsa", "http://www./2005/02/addressing"); address.addTextNode( "http://localhost:8080/servlets-examples/servlet/ConsumerSer viceServlet"); subEndpoint.appendChild(address); SOAPElement referenceProps = soapFactory.createElement("ReferenceProperties", "wsa", "http://www./2005/02/addressing"); SOAPElement subscriberId = soapFactory.createElement("subID", "search", "http://www./searcher"); subscriberId.addTextNode( String.valueOf(thisConsumerId)); referenceProps.appendChild(subscriberId); subEndpoint.appendChild(referenceProps); bodyElement.appendChild(subEndpoint); SOAPElement topicExpression = bodyElement.addChildElement("TopicExpression"); topicExpression.addAttribute( soapFactory.createName("Dialect", "", "http://docs./wsn/2004/06/wsn-WS-BaseNotificat ion-1.2-draft-01.xsd"), "http://docs./wsn/2004/06/TopicExpression/Simp le"); topicExpression.addTextNode(topic); bodyElement.appendChild(topicExpression); message.saveChanges(); message.writeTo(resp.getOutputStream()); } catch (Exception e){ e.printStackTrace(resp.getWriter()); } } protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { } } 现在我们来逐步讲解。首先,我们正在使用 SOAP with Attachments API for Java(SAAJ)中的类来构建 SOAP 消息,因此我们要做的第一件事情对于我们要使用的东西来说非常重要。在大部分情况下,我们都是使用 SOAPFactory 来创建这个对象。由于我们最终是使用各种方法来使用这个消息,因此将创建一个全局的实例,并在创建这个 servlet 时对其进行初始化。 接下来,我们将使用 MessageFactory 创建实际的消息。从现在开始,我们会获得一个对这个 SOAP 消息头的引用,并向其中添加适当的元素,包括它们的名称空间。注意, 在检索对 最后,我们将保存这个消息,并将其输出到浏览器中。再次提交这个表单,我们会看到一个如清单 8 所示的响应。 清单 8. 所生成的消息<soapenv:Envelope xmlns:soapenv="http://schemas./soap/envelope/" xmlns:xsd="http://www./2001/XMLSchema" xmlns:xsi="http://www./2001/XMLSchema-instance"> <soapenv:Header> <wsa:Action soapenv:actor= "http://schemas./soap/actor/next" soapenv:mustUnderstand="0" xmlns:wsa="http://www./2005/02/addressing"> http://docs./wsn/2004/06/WS-BaseNotification/S ubscribe </wsa:Action> <wsa:To soapenv:actor= "http://schemas./soap/actor/next" soapenv:mustUnderstand="1" xmlns:wsa="http://www./2005/02/addressing"> http://localhost:8080/servlets-examples/servlet/ProducerServ iceServlet </wsa:To> </soapenv:Header> <soapenv:Body> <wsnt:Subscribe xmlns:wsnt= "http://docs./wsn/2004/06/wsn-WS-BaseNotificat ion-1.2-draft-01.xsd"> <wsnt:ConsumerReference> <wsa:Address xmlns:wsa= "http://www./2005/02/addressing"> http://localhost:8080/servlets-examples/servlet/ConsumerServ iceServlet </wsa:Address> <wsa:ReferenceProperties xmlns:wsa= "http://www./2005/02/addressing"> <search:subID xmlns:search= "http://www./searcher"> 7 </search:subID> </wsa:ReferenceProperties> </wsnt:ConsumerReference> <wsnt:TopicExpression Dialect= "http://docs./wsn/2004/06/TopicExpression/Simp le"> cheese </wsnt:TopicExpression> </wsnt:Subscribe> </soapenv:Body> </soapenv:Envelope> 现在我们已经准备好创建 NotificationProducer 服务了,它将接收这个消息。 创建 NotificationProducerNotificationProducer 也是一个 WS-Resource,这意味着我们可以使用一个 WSDL 文件来描述它。为了节省空间,我们就不再在本文中详细列出这个文件了;您可以从 参考资料 中下载这个文件。但是从形式上来说,它与 NotificationConsumer 使用的 WSDL 文件非常类似。 至于需求来说,NotificationProducer 必须能够满足以下要求:
当 NotificationProducer 创建一个订阅时,SubscriptionManager 必须也可以执行 在本文中,我们将着重介绍 NotificationProducer servlet现在我们已经准备好创建基本的 ProducerServiceServlet 了。此时,我们只是做总体介绍并接收实际的消息: 清单 9. 接收消息import java.io.*; import javax.servlet.*; import javax.servlet.http.*; import javax.xml.soap.*; public class ProducerServiceServlet extends HttpServlet implements Servlet { static SOAPFactory soapFactory; static Subscriber[] subscribers = new Subscriber[1000]; static int nextId; public ProducerServiceServlet() { super(); nextId = 1; try { soapFactory = SOAPFactory.newInstance(); } catch (Exception e){ e.printStackTrace(); } } protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { } protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { try { InputStream toSOAP = req.getInputStream(); MessageFactory messageFactory = MessageFactory.newInstance(); SOAPMessage message = messageFactory.createMessage(null, toSOAP); SOAPMessage reply = message; reply.writeTo(resp.getOutputStream()); } catch (Exception e){ e.printStackTrace(resp.getWriter()); } } } 首先从最上面开始,这个服务与消费者服务一样,最终都要保存资源,因此我们已经添加了一个 Subscriber 数组(作为一个静态变量),并初始化了计数器。稍后我们将编写实际的类。也与消费者服务类似,我们将创建很多 SOAP 对象,因此我们已经提前创建了 SOAPFactory。 在本例中,我们正在接收一个 SOAP 消息,因此需要使用 最终,我们将对这个消息进行响应,因此我们已经创建了第二个 SOAP 消息,将其作为响应消息使用,然后我们将其作为对原始请求的响应写入到 发送请求SAAJ 使得发送 SOAP 消息并接收响应变得相当简单。 清单 10. 发送 SOAP 消息... message.saveChanges(); message.writeTo(resp.getOutputStream()); String destination = "http://localhost:8080/servlets-examples/servlet/ProducerSer viceServlet"; SOAPConnectionFactory soapConnFactory = SOAPConnectionFactory.newInstance(); SOAPConnection connection = soapConnFactory.createConnection(); SOAPMessage reply = connection.call(message, destination); reply.writeTo(resp.getOutputStream()); } catch (Exception e){ e.printStackTrace(resp.getWriter()); } } ... 目标非常简单,就是这个生产者 servlet 的 URL。使用这个信息,我们可以创建一个新的 SOAPConnection 对象,并使用它来发送我们创建的 SOAPMessage。结果是一个 SOAPMessage 响应,之后我们可以将其输出到浏览器上。 要保证它可以工作,只要提交原始的 HTML 表单即可。消费者服务接收信息,使用所接收的信息来创建一个 SOAP 消息,并将其发送给生产者服务,生产者服务现在只是返回该消息。消费者服务然后会输出所产生的响应。 因此,现在您应该会在浏览器中看到原始 SOAP 消息的两个拷贝。 分析请求我们已经得到了请求,现在需要对其进行分析并确定要做什么。首先,由于这个服务可以用于多种请求类型,我们需要确定要采取什么操作。 清单 11. 确定操作import java.io.*; import javax.servlet.*; import javax.servlet.http.*; import javax.xml.soap.*; public class ProducerServiceServlet extends HttpServlet implements Servlet { static SOAPFactory soapFactory; static Subscriber[] subscribers = new Subscriber[1000]; static int nextId; public ProducerServiceServlet() { super(); nextId = 1; try { soapFactory = SOAPFactory.newInstance(); } catch (Exception e){ e.printStackTrace(); } } protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { } private String getAction(SOAPMessage message){ String action; try { SOAPElement request = (SOAPElement)message .getSOAPBody().getChildElements().next(); action = request.getLocalName(); } catch (Exception e){ action = e.getMessage(); } return action; } private SOAPMessage Subscribe(SOAPMessage message){ SOAPMessage reply = null; try { MessageFactory messageFactory = MessageFactory.newInstance(); reply = messageFactory.createMessage(); reply.getSOAPBody().addBodyElement( soapFactory.createName("SubscribeResponse")); } catch (Exception e){ e.printStackTrace(); } return reply; } ... protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { try { InputStream toSOAP = req.getInputStream(); MessageFactory messageFactory = MessageFactory.newInstance(); SOAPMessage message = messageFactory.createMessage(null, toSOAP); SOAPMessage reply; String action = getAction(message); if (action.equals("Subscribe")){ reply = Subscribe(message); } else if (action.equals("GetCurrentMessage")) { reply = GetCurrentMessage(message); } else if (action.equals("GetResourceProperty")){ reply = GetResourceProperty(message); } else { reply = UnknownActionFault(message); } reply.writeTo(resp.getOutputStream()); } catch (Exception e){ ...
在每一种情况中,我们将返回响应消息。在本例中,我们创建一个简单的响应,它将显示已经执行了哪些方法;为了节省空间,其他操作的类似方法都已经忽略了。现在我们可以创建真正的订阅了。 保存订阅下一个步骤是创建真正的订阅。正如 ConsumerSubscribers 中的情况一样,我们将创建一个简单的类。 清单 12. Subscriber 类import javax.xml.soap.SOAPElement; public class Subscriber { private SOAPElement consumerEndpoint; private String topicExpression; private String useNotify; public Subscriber(SOAPElement consumer, String topic, String notify){ setConsumerEndpoint(consumer); setTopicExpression(topic); setUseNotify(notify); } public void setConsumerEndpoint(SOAPElement consumer){ consumerEndpoint = consumer; } public void setTopicExpression (String topic){ topicExpression = topic; } public void setUseNotify (String notify){ useNotify = notify; } public SOAPElement getConsumerEndpoint(){ return consumerEndpoint; } public String getTopicExpression(){ return topicExpression; } public String getUseNotify(){ return useNotify; } } Subscriber 类为每个订阅者保存了三部分信息:地址/端点,希望对哪些主题接收通知,以及格式选项。WS-Notification 允许用户指定是希望接收完整的 Notify-format 消息(正如我们此处看到的一样),还是只接收一个简化的版本。由于我们没有在 Subscribe 请求中包含这个信息,所以我们假设所有的订阅者都希望使用 Notify 格式,但是从技术上来说,NotificationProducer 必须可以按照订阅者到订阅者的原则满足这两个选项。 下一个步骤是分析 Subscribe 消息,从中提取出订阅消息。 清单 13. 分析 Subscribe 消息... private SOAPMessage Subscribe(SOAPMessage message){ SOAPMessage reply = null; try { Name subscribe = soapFactory.createName("Subscribe", "wsnt", "http://docs./wsn/2004/06/wsn-WS-BaseNotificat ion-1.2-draft-01.xsd"); Name consumerReference = soapFactory.createName( "ConsumerReference", "wsnt", "http://docs./wsn/2004/06/wsn-WS-BaseNotificat ion-1.2-draft-01.xsd"); Name topicExpression = soapFactory.createName( "TopicExpression", "wsnt", "http://docs./wsn/2004/06/wsn-WS-BaseNotificat ion-1.2-draft-01.xsd"); SOAPElement bodyElement = (SOAPElement)message.getSOAPBody() .getChildElements(subscribe).next(); SOAPElement consumerElement = (SOAPElement)bodyElement .getChildElements(consumerReference).next(); SOAPElement topicElement = (SOAPElement)bodyElement .getChildElements(topicExpression).next(); String topicString = topicElement.getValue().toString(); Subscriber newSubscriber = new Subscriber(consumerElement, topicString, "true"); int thisId = nextId++; subscribers[thisId] = newSubscriber; MessageFactory messageFactory = MessageFactory.newInstance(); reply = messageFactory.createMessage(); SOAPElement sub = reply.getSOAPBody(). addBodyElement( soapFactory.createName("SubscribeResponse")); sub.addChildElement(consumerElement); sub.addChildElement(topicElement); } catch (Exception e){ e.printStackTrace(); } return reply; } ... 我们不会遍历 SOAPMessage 来查找单个元素,而是创建 Name 对象(它直接指向要检索的元素),然后使用 在本例中,我们将消费者的端点直接保存在对象中 —— 稍后我们需要使用这个初始的信息 —— 不过我们要提取 最后,通过将所提出的元素临时添加到响应消息中,我们可以确保收集到想要的数据。 发送 SubscribeResponse在创建订阅之后,我们需要为其创建一个端点引用,并发送回消费者。采用这种方法,消费者可以使用这个端点对订阅进行修改,例如暂停、恢复或取消订阅。 我们从创建端点并将其添加到消息开始。 清单 14. 添加端点... Subscriber newSubscriber = new Subscriber(consumerElement, topicString, "true"); int thisId = nextId++; subscribers[thisId] = newSubscriber; MessageFactory messageFactory = MessageFactory.newInstance(); reply = messageFactory.createMessage(); SOAPElement sub = reply.getSOAPBody() .addBodyElement( soapFactory.createName("SubscribeResponse", "wsnt", "http://docs./wsn/2004/06/wsn-WS-BaseNotificat ion-1.2-draft-01.xsd")); SOAPElement subEndpoint = soapFactory.createElement("SubscriptionReference", "wsnt", "http://docs./wsn/2004/06/wsn-WS-BaseNotificat ion-1.2-draft-01.xsd"); SOAPElement address = soapFactory.createElement("Address", "wsa", "http://www./2005/02/addressing"); address.addTextNode( "http://localhost:8080/servlets-examples/servlet/ProducerSer viceServlet"); subEndpoint.appendChild(address); SOAPElement referenceProps = soapFactory.createElement("ReferenceProperties", "wsa", "http://www./2005/02/addressing"); SOAPElement subId = soapFactory.createElement("subscriptionID", "sub", "http://www./searchService"); subId.addTextNode(String.valueOf(thisId)); referenceProps.appendChild(subId); subEndpoint.appendChild(referenceProps); sub.appendChild(subEndpoint); reply = populateHeader(reply, newSubscriber.getConsumerEndpoint(), "http://docs./wsn/2004/06/WS-BaseNotificatio n/SubscribeResponse"); } catch (Exception e){ e.printStackTrace(); } return reply; } ... 这个过程与消费者中的过程是相同的:创建各个元素,并将其构建在一个层次结构中,然后将它们添加到消息中。 然而,在本例中,我们还需要另外一个步骤。当我们在订阅时,已经知道了 NotificationProducer 的地址,因此可以直接创建 SOAP 头。然而,在本例中,这个头是基于消费者的端点引用中的信息的,因此我们需要根据这些信息来构建这个头。 清单 15. 构建 SOAP 头private SOAPMessage populateHeader(SOAPMessage message, SOAPElement endpoint, String action){ try { SOAPHeader header = message.getSOAPHeader(); SOAPHeaderElement actionElement = header.addHeaderElement( soapFactory.createName("Action", "wsa", "http://www./2005/02/addressing")); actionElement.addTextNode(action); Name address = soapFactory.createName("Address", "wsa", "http://www./2005/02/addressing"); SOAPElement addressElement = (SOAPElement)endpoint .getChildElements(address).next(); String toAddr = addressElement.getFirstChild() .getNodeValue(); SOAPHeaderElement toElement = header.addHeaderElement( soapFactory.createName("To", "wsa", "http://www./2005/02/addressing")); toElement.addTextNode(toAddr); Name referenceProperties = soapFactory.createName("ReferenceProperties", "wsa", "http://www./2005/02/addressing"); SOAPElement refPropsElement =(SOAPElement)endpoint .getChildElements(referenceProperties).next(); java.util.Iterator refProps = refPropsElement.getChildElements(); while (refProps.hasNext()){ Object thisPropertyObj = refProps.next(); SOAPElement thisProperty = (SOAPElement)thisPropertyObj; header.addHeaderElement(soapFactory .createName(thisProperty.getLocalName(), thisProperty.getPrefix(), thisProperty.getNamespaceURI())) .addTextNode(thisProperty .getFirstChild().getNodeValue()); } } catch (Exception e){ try { SOAPBody replySoapBody = message.getSOAPBody(); replySoapBody.addBodyElement( soapFactory.createName("Response")); .addTextNode(e.toString()); } catch (Exception e2){} } return message; } ... 有些信息,例如 然而, 结果是 清单 16. |
描述 | 名字 | 大小 |
---|---|---|
Source code | producer.wsdl | 6.17 KB |
WSN 和 WSRF 文档 的主要位置位于 The Globus Alliance,但是最新的 WS-Notification 规范可以在 Oasis 处找到。文档包括:
|
来自: LibraryPKU > 《技术动态》