分享

WSRP 揭秘

 smoking_boy 2005-08-29
BEA WebLogic Portal 8.1 通过采用远程 portlet Web Services 协议( WSRP ),使 门户 可以使用远程 portlet 。一般而言,如果创建和部署 portlet 的初衷是为了供本地门户使用,那么,当你将这些 portlet 传送至远程 WSRP Producer 时,其功能保持不变。 WebLogic Portal 及其 WSRP Consumer 和 WSRP Producer 的功能设计成可使 portlet 不受门户位置的干扰。然而在某些情况下,你会发现某些 portlet 表现异常或出错。产生这种表现最常见的原因,是由于这些 portlet 可能对门户位置作出了某些隐式或明确的假定。在本文中,我将介绍一些最常见的假定,以及为什么这些假定能以某些不良方式干扰 WSRP 协议。我的目标是重点描述能用于制作友好 portlet WSRP 的最佳实践,同时又不必以牺牲其功能性为代价。

引言

  远程 portlet Web services ( WSRP )使你能从门户中去耦自己的 portlet 应用程序。这种去耦可以为管理大型门户的部署提供极大便利。你不必将所有 portlet 与单个应用程序中的门户绑定在一起,而是可以在单独的 portlet 应用程序中部署自己的 portlet ,并让门户通过 WSRP 使用这些 portlet 。对多数大型门户开发项目而言,这种去耦法可以使团队开发、升级和管理变得更加轻松。

  什么是 WSRP ?理解 WSRP 的最好方法便是将它与 HTTP 之类的东西作个比较。最典型的 HTTP 应用程序是通过 Web 浏览器查看远程用户界面(如 Web 应用程序)并与其交互的。浏览器可以利用 HTTP 与远程 HTTP 服务器对话,以得到标记(比如 HTML )和之后的数据(比如,通过提交一个表单)。 WSRP 是存在于两个应用程序之间的类似协议,这两个应用程序中,一个应用程序( Consumer )充当另一个应用程序( Producer )的客户端,以便得到用户界面标记并提交用户动作。 Producer 负责存放用户界面, Consumer 则利用 WSRP 协议收集用户界面并与之交互。

  与浏览器不同,从以下角度看, Consumer 更为复杂:

  • Consumer 可以将几个用户界面组件聚合成单个页面,并提供诸如个性化、定制和安全性之类的特性。
  • Consumer 处理的是标记片段而不是完整的文档。 Consumer 从不同的 Producer 处得到这些标记片段,然后采用 Consumer 特定的页面布局、风格等等,将这些片段结合到单一页面中。

  WSRP 协议定义了 WSRP Producer 实现的一组 Web services 。 WSRP Consumer 能向这些 Web services 发送信息,以查看用户界面并与之交互。 WSRP 协议将典型的浏览器——服务器交互协议转换成某个适用于应用程序( Consumer )的协议,以便充当存放用户界面的应用程序( Producer )客户端。

  要认识的一个重点是, WSRP Web services 是同步的、面向用户界面的。与企业面向逻辑或面向数据的 Web services 不同,面向用户界面的 Web services 提供了一种更为粗粒度的应用程序复用,并且对于变化更富有弹性。

  WebLogic Portal 既包含 Producer 组件,也包含 Consumer 组件。 WebLogic Portal Producer 是一个能存放 portlet 的容器。你的应用程序代码(页面流、支持文件、其他的 portlet 类、控件、 EJB 等等)、用户界面( JSP 和其他资源)以及 portlet 所用的数据都驻留于此。 Producer 的这种设计方式使你能将所有现存的 Web 应用程序转换成某个在配置时变化最小的 WSRP Producer 。一旦某个 Web 应用程序被转换成 Producer ,该程序便开始通过 WSRP 界面提供可在 Web 应用程序上使用的 portlet 。

  在 WebLogic Portal 中, Consumer 被紧密集成到 WebLogic Portal 框架中。为使用远程 portlet ,用户通常一开始便要找到 Producer 的 WSDL 位置,再将 Producer 的元数据添加给 Consumer ,然后创建一个远程 portlet (也称为代理 portlet )。当添加该 portlet 给某个门户或桌面时, WebLogic Portal 框架便利用 WSRP 协议向门户用户表示 portlet 。

  在深入介绍之前,需要熟悉如何创建 portlet 以及如何通过 WebLogic Portal 来使用这些 portlet 。本文的后续内容假定你对已了解这些知识。

示例

  为了阐述 WSRP Consumer 怎样才能与远程 portlet 交互,让我们先看一个简单的页面流 portlet 。

A Search Page Flow
图 1 :一个搜索页面流

  该页面流在一个表单中收集了用户输入,并根据用户输入执行搜索,然后显示搜索结果。 index.jsp 页面所表示的表单如下:

<netui:form action="search" method="post">
 <table>
  <tr valign="top">
   <td>First Name:</td>
   <td><netui:textBox dataSource="{actionForm.firstName}" /></td>
  </tr>
  <tr valign="top">
   <td>Last Name:</td>
   <td><netui:textBox dataSource="{actionForm.lastName}" /></td>
  </tr>
 </table>
 <netui:button value="search" type="submit" />
</netui:form>

  假定你在 Producer Web 应用程序的 /Search/SearchController.jpf 处 创建了该页面流,并为这个使用 WebLogic Workshop 的页面流创建了一个 . portlet 文件。以下是 portlet 示例文件:

<portal:root xmlns:netuix="http://www./servers/netuix/xsd/controls/netuix/1.0.0"
  xmlns:portal="http://www./servers/netuix/xsd/portal/support/1.0.0"
  xmlns:xsi="http://www./2001/XMLSchema-instance" 
  xsi:schemaLocation=
  "http://www./servers/netuix/xsd/portal/support/1.0.0 portal-support-1_0_0.xsd">
  <netuix:portlet definitionLabel="search" title="Search">
    <netuix:titlebar>
      <netuix:maximize/>
      <netuix:minimize/>
    </netuix:titlebar>
    <netuix:content>
      <netuix:pageflowContent contentUri="/Search/SearchController.jpf"/>
    </netuix:content>
  </netuix:portlet>
</portal:root> 

  对于本文余下的部分,我假定你已创建了一个 Consumer 门户 Web 应用程序、为上面的搜索 portlet 创建了一个远程 portlet ,并且已将其添加到门户或桌面。

  注:在 WSRP 协议中, Consumer 使用所谓的 portletHandle 指代 Producer 上的某个 portlet 。 Producer 负责为它赋值。当 WebLogic Portal Producer 向 Consumer 显示任意 portlet 时,它会用 portlet 的 definitionLabel 作为 portletHandle 。

  创建远程 portlet 之后,你便可以更改其属性,如高速缓冲、 definitionLabel 、标题、错误页等等。你还可以为 Consumer 端的远程 portlet 添加一个支持文件。

显示远程 portlet

  门户利用 WSRP 从 Producer 那里获得标记(在本例中,为 portlet 生成 HTML )。当 WebLogic Portal 框架发现页面中有远程 portlet 时,该框架会:

  • 给 Producer 发送一条 getMarkup 消息,并接收 Producer 的响应。
  • 从 Producer 的响应中收集标记。
  • 将标记聚合到门户页面中。

  如果远程 portlet 有一个支持文件,那么, WebLogic Portal 会按适当顺序调用其方法。以下是 Consumer 发送给 Producer 的一个典型 getMarkup 请求。

<soapenv:Envelope xmlns:soapenv="http://schemas./soap/envelope/">
   <soapenv:Body>
      <urn:getMarkup xmlns:urn="urn:oasis:names:tc:wsrp:v1:types">
         <urn:registrationContext xsi:nil="true" 
		 xmlns:xsi="http://www./2001/XMLSchema-instance"/>
         <urn:portletContext>
            <urn:portletHandle>search</urn:portletHandle>
         </urn:portletContext>
         <urn:runtimeContext>
            <urn:userAuthentication>wsrp:none</urn:userAuthentication>
            <urn:portletInstanceKey>search_1</urn:portletInstanceKey>
            <urn:namespacePrefix>search_1</urn:namespacePrefix>
            <urn:templates>
               <!-- Snip -->
            </urn:templates>
         <urn:userContext xsi:nil="true" 
		 xmlns:xsi="http://www./2001/XMLSchema-instance"/>
         <urn:markupParams>
            <urn:secureClientCommunication>false</urn:secureClientCommunication>
            <urn:locales>en-US</urn:locales>
            <urn:mimeTypes>text/html</urn:mimeTypes>
            <urn:mimeTypes>*/*</urn:mimeTypes>
            <urn:mode>wsrp:view</urn:mode>
            <urn:windowState>wsrp:normal</urn:windowState>
            <urn:clientData>
               <urn:userAgent>
               Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; 
                  .NET CLR 1.1.4322; FDM)
               </urn:userAgent>
            </urn:clientData>
            <urn:markupCharacterSets>UTF-8</urn:markupCharacterSets>
         </urn:markupParams>
      </urn:getMarkup>
   </soapenv:Body>
</soapenv:Envelope>

  让我们看看以上请求中标为黑体的几行:

  • 如上所述, Consumer 提供由 Producer 赋值的 portletHandle 。

  • portletInstanceKey 和 namespacePrefix 的值要依据远程 portlet 的 instanceLabel 值而定。这个值由创建远程 portlet 的用户指定。
  • locales 元素的值根据浏览器请求的语言而定,并与从用户浏览器接收到的 Accept-Language HTTP 请求报头相一致。如果浏览器为该报头发送多个值, WebLogic Portal Consumer 会按相同顺序把所有这些值发送给 Producer 。
  • mimeTypes 元素的值要根据门户响应上设定的内容类型来确定,并放在从用户浏览器接收到的 Accept HTTP 请求报头值之前。
  • mode 和 windowState 元素要与远程 portlet 的窗口模式和窗口状态相一致。

  注:在 WSRP 协议中, Consumer 会跟踪 portlet 的模式和窗口状态。

  现在,让我们看看 WebLogic Portal Producer 从 Consumer 那里收到一条 getMarkup 请求时会怎样做。

  • roducer 根据 portletHandle 来识别相应的 portlet 。
  • Producer 根据 mode 元素 值来确定该请求应该调用在 portlet 文件中由 pageFlowContent 元素所指定的页面流的 begin 动作。
  • Producer 调用页面流的 begin 动作,并包含起始页(在本例中即 index.jsp )。
  • Producer 随后收集响应,并创建 SOAP 响应消息。

  以下是 Producer 的响应示例。

<soapenv:Envelope xmlns:soapenv="http://schemas./soap/envelope/">
   <soapenv:Body>
      <urn:getMarkupResponse xmlns:urn="urn:oasis:names:tc:wsrp:v1:types">
         <urn:markupContext>
            <urn:mimeType>text/html; charset=UTF-8</urn:mimeType>
            <urn:markupString><![CDATA[
 <form name="searchForm" action="http://localhost:7001/consumer/test.portal?_nfpb=true
 &_windowLabel=search_1_1&_pageLabel=test_page_2&wsrp-urlType=blockingAction&wsrp-url=
 &wsrp-requiresRewrite=&wsrp-navigationalState=&wsrp-interactionState=
 _action%3D%252FSearch%252Fsearch&wsrp-mode=&wsrp-windowState=" method="post">
    <table>
        <tr valign="top">
            <td>First Name:</td>
            <td><input type="text" name="search_1{actionForm.firstName}" value=""></td>
        </tr>
        <tr valign="top">
            <td>Last Name:</td>
            <td><input type="text" name="search_1{actionForm.lastName}" value=""></td>
        </tr>
    </table>
    <br/>
    <input type="submit" value="search">
</form>]]></urn:markupString>
            <urn:locale>en-US</urn:locale>
            <urn:requiresUrlRewriting>false</urn:requiresUrlRewriting>
         </urn:markupContext>
         <urn:sessionContext>
            <urn:sessionID>
            B9Ml78JJyZNrMbzKnPxfyXZj511LL420BfKZGmLssNG02DbSJm3y!-1979539005
            </urn:sessionID>
            <urn:expires>3600</urn:expires>

         </urn:sessionContext>
      </urn:getMarkupResponse>
   </soapenv:Body>
</soapenv:Envelope>

  让我们再来看看该响应中的黑体行。

  提示:默认情况下, Producer 会通过如上所示的 markupString 元素返回 portlet 标记。 WebLogic Portal Producer 还能在 markupBinary 元素中将标记以 BASE64 编码字节的形式返回,或以在 SOAP Envelope 之外以 MIME 附件的方式返回。

  如果 portlet 的标记大于 8k ,便可通过设定 Producer 以 MIME 附件的方式返回标记来改善性能。欲了解配置详情,请参考 WebLogic Portal WSRP documentation

  • mimeType 元素表明 Producer 提交了 portlet ,以生成带有 UTF-8 字符编码的 text/html 标记。 Portlet 可以利用 Content-Type 的 JSP 页面指令或 HttpServletResponse 上的 setContentType() 方法影响该值。
  • markupString 元素包含 portlet 产生的响应。在我们的示例中,该响应便是搜索页面流 index.jsp 页面产生的 HTML 。要调用 portlet , Producer 使用定制请求 / 响应包装器。尽管输入的请求是一个 SOAP 请求而非 HTTP 请求,但这些包装器仍然模拟了一个 Web 容器环境。
  • 表单的动作元素指 Consumer 的门户。采用 Consumer 提供的 URL 模板可以实现这一点。欲深入了解 URL 模板,请参考 URLs in WebLogic Portal 上的文章。
  • Producer 利用 Consumer 提供的 namespacePrefix 元素重写表单的输入控件名称。这样做是为了确保名称不与门户页面中表示的其他 HTML 元素的名称属性以及 HTML 表单动作目标中显示的名称属性发生冲突。
  • sessionContext 元素包括 Producer 或 portlet 创建的 HTTP 会话 ID 。在当前的示例中,页面流运行时会在执行页面流 begin 动作的同时创建一个 HTTP 会话。我将在本文后面详细介绍会话管理。

  一旦接收了 getMarkup 请求的响应, WebLogic Portal Consumer 便会提取 markupString 并将其写给门户的 HTTP 响应,使最终用户看见 portlet 。

  知道访问的请求参数怎样影响可移植性很重要。当你将 portlet 作为本地 portlet 部署时,该 portlet 便能访问来自门户请求的请求参数,以及同一页面上其他 portlet 所设置的请求属性。如果你根据这些请求参数和属性实现一个 portlet ,那么,该 portlet 也许不能在 WSRP 环境中正确运行。请注意,在 WSRP 环境中, portlet 是 远程的 ,而 portlet 接收的 HTTP 请求(在 Producer 上)与门户接收的 HTTP 请求(在 Consumer 上)并不相同。

  如果你想在应用 portlet 时使用这些参数或属性,可以利用 Custom Data Transport (定制数据传输) API 明确地将该数据从 Consumer 发送 Producer 。

提交表单

  现在,让我们看看用户填好并提交搜索 portlet 中的表单时会发生什么。由于 portlet 是远程的,因此, Consumer 要想将用户请求传送给 Producer ,必须做到:

  • Consumer 从输入的 HTTP 请求中提取所有参数。这些参数包括所有查询参数和表单参数
  • Consumer 向 Producer 发送一条 performBlockingInteraction 请求。

以下是一个 performBlockingInteraction 请求示例。

<soapenv:Envelope xmlns:soapenv="http://schemas./soap/envelope/">
   <soapenv:Body>
      <urn:performBlockingInteraction xmlns:urn="urn:oasis:names:tc:wsrp:v1:types">
         <urn:registrationContext xsi:nil="true" 
		 xmlns:xsi="http://www./2001/XMLSchema-instance"/>
         <urn:portletContext>
            <urn:portletHandle>search</urn:portletHandle>
         </urn:portletContext>
         <urn:runtimeContext>
            <urn:userAuthentication>wsrp:none</urn:userAuthentication>
            <urn:portletInstanceKey>search_1</urn:portletInstanceKey>
            <urn:namespacePrefix>search_1</urn:namespacePrefix>
            <urn:sessionID>
            B9ZVFSbYrxv74C8pnsyspSp2hW1ZnnXkPvzXHxrX3kgTbTQ0Jp4C!-1979539005
            </urn:sessionID>
         </urn:runtimeContext>
         <urn:userContext xsi:nil="true" 
		 xmlns:xsi="http://www./2001/XMLSchema-instance"/>
         <urn:markupParams>
            <urn:secureClientCommunication>false</urn:secureClientCommunication>
            <urn:locales>en-US</urn:locales>
            <urn:mimeTypes>text/html</urn:mimeTypes>
            <urn:mode>wsrp:view</urn:mode>
            <urn:windowState>wsrp:normal</urn:windowState>
            <urn:clientData>
               <urn:userAgent>
               Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; 
                  .NET CLR 1.1.4322; FDM)
               </urn:userAgent>
            </urn:clientData>
            <urn:navigationalState/>            
            <urn:markupCharacterSets>UTF-8</urn:markupCharacterSets>
         </urn:markupParams>
         <urn:interactionParams>
            <urn:portletStateChange>readOnly</urn:portletStateChange>
            <urn:interactionState>_action=%2FSearch%2Fsearch</urn:interactionState>
            <urn:formParameters name="search_1{actionForm.lastName}">
               <urn:value>Allamaraju</urn:value>
            </urn:formParameters>
            <urn:formParameters name="search_1{actionForm.firstName}">
               <urn:value>Subbu</urn:value>
            </urn:formParameters>
         </urn:interactionParams>
      </urn:performBlockingInteraction>
   </soapenv:Body>
</soapenv:Envelope>

  在这个请求中,最需要注意的一点是 interactionParams 元素。该元素为 portlet 封装了用户的请求,并包含:

  • portletStateChange 元素表示允许 portlet 还是允许 Producer 给 portlet 的持久性状态施加变化。在 WebLogic Portal Producer 的情况中,该值表示 portlet 是否获准改变 portlet 的首选项。我将在本文后面更详尽地讨论通过远程 portlet 来使用 portlet 首选项。
  • interactionState 元素包含用户交互的状态。对页面流 portlet 而言, WebLogic Portal Producer 利用该元素来封装必须执行的页面流动作。如果你回头搜索 getMarkupResponse 消息,便会注意到 WebLogic Portal Producer 已经将该数据插入到表单的动作 URL 中。
  • formParameters 元素包含用户提交的表单参数。

  当 WebLogic Portal Producer 接收该请求时, Producer 会采取以下措施:

  • Producer 根据 portletHandle 来识别相应的 portlet 。
  • Producer 为 interactionState 元素解码,以便找出所要执行的页面流动作。在本例中,该动作便是 search 动作。
  • Producer 再次创建请求 / 响应包装器,以模拟 Web 容器环境,并调用页面流的动作。页面流现在可以访问来自该 HTTP 请求的表单参数了。
  • 一旦调用了页面流的动作, Producer 便可获得下一个所要显示的 JSP 值。在本例中,该值即 results.jsp 。

  此时,按照 WSRP 协议, Producer 有两种选择:

  • 创建 performBlockingInteractionResponse 并返回。
  • 调用 portlet 的 results.jsp ,并在 performBlockingResponse 中包含标记。

  在第一种情况下, Producer 会返回以下响应。

<soapenv:Envelope xmlns:soapenv="http://schemas./soap/envelope/">
   <soapenv:Body>
      <urn:performBlockingInteractionResponse 
       xmlns:urn="urn:oasis:names:tc:wsrp:v1:types">
         <urn:updateResponse>
            <urn:navigationalState>_action=%2FSearch%2Fsearch&_next=/Search/results.jsp
               &_action=/Search/search&search_1{actionForm.lastName}=Allamaraju
               &search_1{actionForm.firstName}=Subbu</urn:navigationalState>
         </urn:updateResponse>         
      </urn:performBlockingInteractionResponse>
   </soapenv:Body>
</soapenv:Envelope>

  在后一种情况下, Producer 包含一个带有 portlet 标记的 MarkupContext 元素。

  理解这两种选择很重要,因为部署在本地门户中的 portlet 未必知道二者的区别。为了理解 WSRP 为什么要指定关于 getMarkup 和 performBlockingInteraction 的两种请求,以及这些请求对 portlet 意味着什么,让我们分析一下上面的 performBlockingInteractionResponse 。

  该响应有一个 navigationalState 元素,其值包含了处理用户输入的 performBlockingInteraction 请求后 portlet 所处的状态。该元素的实际值根据 Producer 的不同而有所不同。对页面流 portlet 而言, WebLogic Portal Producer 利用该元素包含已经执行的动作、用于该动作的表单参数,以及必须向用户显示的 JSP 。

  通过利用这个 navigationalState , Producer 在未来任何时间都可以呈现 portlet 。比如,我们不妨假定门户页面还有另一个 portlet 。查看搜索结果之后,用户也许会使用用第二个 portlet 开始工作。用户在用第二个 portlet 工作的同时,只要第二个 portlet 没有最大化,用户便可以期望在门户在搜索 portlet 中显示搜索结果。为显示搜索结果, Consumer 必须做到要求 Producer 呈现具有相同内容的 portlet 。也就是说, Producer 必须能够呈现一个没有任何次数、任何时候都没有交互的 portlet 。这便是“无交互呈现”, portlet 必须为无交互呈现做好准备。

  为允许无交互呈现, WSRP 定义了以下协议:

  • Consumer 采用 performBlockingInteraction 请求向 Producer 发送用户交互。 Producer 包含 portlet 的当前状态,这一状态在响应的 navigationalState 值中体现。
  • Consumer 利用 getMarkup 请求,要求 Producer 返回一个 portlet 标记。如果 Producer 先前已返回 navigationalState , Consumer 便可以和 getMarkup 请求一起提供该标记,这样, Producer 便可以呈现带有该状态的 portlet 。

  WSRP 1.0 规范称该模型为“两步协议”。在当前示例中,这种“两步协议”可作如下解释:

  • Consumer 向 Producer 发送一个不带有 navigationalState 的 getMarkup 请求。 Producer 随之呈现搜索 portlet 的 index.jsp .
  • 当用户提交一个表单时, Consumer 向 Producer 发送一个 performBlockingInteraction 请求。作为对该请求的响应, Producer 返回下一页,显示为 navigationalState 。
  • 无论何时想显示 portlet , Consumer 都会发送一个带有当前 navigationalState 的 getMarkup 请求。 Producer 利用该状态呈现页面流 portlet 的 results.jsp 。

  以上步骤中,第三步可出现任意次数而不带有任何用户交互。以下是一个带有当前 navigationalState 的 getMarkup 请求。

<soapenv:Envelope xmlns:soapenv="http://schemas./soap/envelope/">
   <soapenv:Body>
      <urn:getMarkup xmlns:urn="urn:oasis:names:tc:wsrp:v1:types">
         <urn:registrationContext xsi:nil="true" 
		 xmlns:xsi="http://www./2001/XMLSchema-instance"/>
         <urn:portletContext>
            <urn:portletHandle>search</urn:portletHandle>
         </urn:portletContext>
         <urn:runtimeContext>
            <!-- Snip -->
         <urn:userContext xsi:nil="true" 
		 xmlns:xsi="http://www./2001/XMLSchema-instance"/>
         <urn:markupParams>
            <urn:secureClientCommunication>false</urn:secureClientCommunication>
            <urn:locales>en-US</urn:locales>
            <urn:mimeTypes>text/html</urn:mimeTypes>
            <urn:mode>wsrp:view</urn:mode>
            <urn:windowState>wsrp:normal</urn:windowState>
            <urn:clientData>
               <urn:userAgent>
               Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; 
                  .NET CLR 1.1.4322; FDM)
               </urn:userAgent>

            </urn:clientData>
            <urn:navigationalState>_action=%2FSearch%2Fsearch&_next=/Search/results.jsp
               &_action=/Search/search&search_1{actionForm.lastName}=Allamaraju
               &search_1{actionForm.firstName}=Subbu</urn:navigationalState>
            <urn:markupCharacterSets>UTF-8</urn:markupCharacterSets>
         </urn:markupParams>
      </urn:getMarkup>
   </soapenv:Body>
</soapenv:Envelope>

  下面的序列图概括了这一呈现模型。

Interaction-free rendering
图 2 :无交互呈现

  该图显示了从 Consumer 到 Producer 的请求 / 响应消息,并且显示了 WebLogic Portal Producer 是如何调用搜索 portlet 的。你也许心生疑问: Consumer 为什么应该向 Producer 发送另一个 getMarkup 请求以得到 portlet 的标记呢?这是因为作为一种优化, WSRP 允许 Producer 在 performBlockingInteractionResponse 消息内返回标记,以使 Consumer 避免额外的往返。至于决定使用返回的标记还是新发送一条新的 getMarkup 请求,那是 Consumer 的事。 WebLogic Portal 的 Consumer 和 Producer 都支持这种优化。

  这种无交互呈现并不仅仅为远程 portlet 专用。 WebLogic Portal 也将同样的方法运用于本地 portlet 。如果对 Java Portlet API 比较了解,你便会注意到, javax.portlet.Portlet 接口也体现了这一模型。 Java Portlet API 中的 render 参数与 navigationalState 一致。

  这种无交互呈现对 portlet 意味着什么呢?

  • Consumer 可能随时调用 portlet ,而 portlet 也必须在被 Consumer 调用时做好呈现其内容的准备。
  • Consumer 可能不在执行某个动作后立即呈现 portlet 。因此, portlet 无法猜测同一请求内是否会发生动作处理和后续呈现。据此,如果在动作处理期间添加请求属性,那么, portlet 也许不会在呈现期间获得这些请求属性。

  页面流运行时对请求属性来说算是例外。如果你在一个动作期间设置某个请求属性,页面流运行时便会在用户的 HTTP 会话中存贮该属性,使页面流在包括下一个页面流 JSP 时可使用这一属性。对 Struts portlet 而言,这一规律同样适用,但对其他类型的 portlet 和在这些 portlet 上使用的支持文件来说则并非如此。

  提示:如果你希望所有状态都能从动作处理带至动作呈现,便可以在 Producer 端的 HTTP 会话中放置数据。换言之,你可以实现一次定制数据传输。

  注意,在上述示例中,我假定在远程 portlet 上 Consumer 未启用高速缓冲。如果你在远程 portlet 上启用高速缓冲, WebLogic Portal Consumer 将不发送请求以获得 portlet 的标记,除非用户直接与搜索 portlet 交互。

在 getMarkup 请求期间, WSRP 协议对可以做什么制定了某些限制。

  • 在 getMarkup 请求期间,不允许 Producer 改变 portlet 的持久性状态。对 WebLogic Portal Producer 而言,这意味着 portlet 不能改变其首选项。如果考虑到每个门户页面刷新都能引起 getMarkup 请求这一事实,你将立即注意到:每次呈现一个 portlet 时,改变 portlet 首选项都会引起的不需要的数据库改变。正如本文稍后将要介绍的那样,在特定情形下, WSRP 允许 Producer 改变 portlet 的持久性状态(即 portlet 首选项)。
  • 在 getMarkup 请求期间,不允许 Producer 改变 portlet 的模式或窗口状态。这意味着 portlet 不能使用任何 WebLogic Portal API 去改变某个 portlet 的模式或窗口状态。 WSRP 允许 Producer 在 performBlockingInteraction 期间进行这些更改。

  这些限制并非专门针对 WSRP 。 Java Portlet API 也有类似限制,而 WebLogic Portal 又将这些限制强加给所有的 portlet ,不管是本地还是远程的 portlet 。

  说到这里,我并没有讲到从 WebLogic Portal Consumer 到 WebLogic Portal Producer 用于支持 portlet 间通讯的请求 / 响应。为支持 portlet 间通讯, WebLogic Portal 扩展了 WSRP 1.0 协议,并使用 handleEvents 请求来传输从 Consumer 到 Producer 的事件。如果考虑到请求 / 响应流, handleEvents 请求与 performBlockingInteraction 请求相似。比如说,事件处理期间添加给请求的请求属性,在呈现期间也许不能被 portlet 所用。欲进一步了解 portlet 间通讯模型,请参考 WebLogic Portal 文档。

会话处理

  会话是 Web 应用程序开发的一个重要组成部分。 WSRP 协议允许 Producer 为 Consumer 管理会话。正如浏览器参与 Web 服务器的会话方式一样, WSRP Consumer 也能参与 WSRP Producer 的会话。

  在上面的示例中,请再看一下第一条 getMarkupResponse 消息。这一响应包括一个 sessionContext 元素,如下面黑体部分所示:

<soapenv:Envelope xmlns:soapenv="http://schemas./soap/envelope/">
   <soapenv:Body>
      <urn:getMarkupResponse xmlns:urn="urn:oasis:names:tc:wsrp:v1:types">
         <urn:markupContext>
            <!-- Snip --->         
         </urn:markupContext>
         <urn:sessionContext>
            <urn:sessionID>
            B9Ml78JJyZNrMbzKnPxfyXZj511LL420BfKZGmLssNG02DbSJm3y!-1979539005
            </urn:sessionID>
            <urn:expires>3600</urn:expires>
         </urn:sessionContext>
      </urn:getMarkupResponse>
   </soapenv:Body>
</soapenv:Envelope> 

  该元素表明 Producer 为 portlet 创建了一个会话,该会话在 3600 秒后结束。当 Producer 返回一个 session ID 时, Consumer 便负责跟踪此 session ID ,并将其和进一步的请求一起提供给 Producer 。以下 performBlockingInteraction 请求显示了该 session ID 。

<soapenv:Envelope xmlns:soapenv="http://schemas./soap/envelope/">
   <soapenv:Body>
      <urn:performBlockingInteraction xmlns:urn="urn:oasis:names:tc:wsrp:v1:types">
         <!-- Snip -->
         <urn:runtimeContext>
            <urn:userAuthentication>wsrp:none</urn:userAuthentication>
            <urn:portletInstanceKey>search_1</urn:portletInstanceKey>
            <urn:namespacePrefix>search_1</urn:namespacePrefix>
            <urn:sessionID>
               B9ZVFSbYrxv74C8pnsyspSp2hW1ZnnXkPvzXHxrX3kgTbTQ0Jp4C!-1979539005
               </urn:sessionID>
         </urn:runtimeContext>
         <!-- Snip -->
     </urn:performBlockingInteraction>
   </soapenv:Body>
</soapenv:Envelope>

  WebLogic Portal Consumer 将该 sessionID 存放在其用户的 HTTP 会话中,并将其和未来的 getMarkup 、 performBlockingInteraction 以及 handleEvents 请求放在一起提交。在 WebLogic Portal 中,某个给定 Producer Web 应用程序中的所有 portlet 都共享该会话。因此,对每个与 Consumer 交互的用户而言, Consumer 都为每个 Producer 维护了一个会话。只要 Consumer 能够提供 session ID ,这个 Producer 会话便始终处于可访问状态。如果用户 Consumer 上的会话在 Producer 上相应会话超时之前便已超时,那么, Producer 的会话便会丢失。

  提示: Consumer 的会话超时间隔应该至少与 Producer 的会话超时间隔一样长,以使用户在不丢失远程 portlet 会话的情况下能够继续工作。

集群化 Producer

  WebLogic Portal Producer 和 Consumer 实现设计成支持集群。由于 Consumer 只是个 Web 应用程序,因此,你也可以设置 Consumer 集群。你还可以在 Consumer 集群内启用复制,以便该集群利用 WebLogic Server 的故障转移。 WebLogic Portal Producer 也支持集群和故障转移。然而,这需要 Consumer 支持负载均衡、复制和复制转移。为了提供这种支持, WebLogic Portal Producer 使用一条由 WSRP 1.0 协议指定的操作—— itCookie 。

  initCookie 操作允许 Producer 初始化 cookie ,并通过作为 SOAP 响应基础的 HTTP 响应来返回这些 cookie 。要查看这是如何进行的,可以打开某个 WebLogic Portal Producer Web 应用程序上的 SOAP 消息监控器。该监控器的 URL 是 http://< 你的域名 >:< 你的端口 >/<producer-webapp-context-root>/monitor 。当用户第一次查看包含某个远程 portlet 的页面时,你会注意到 Consumer 会发送一条 initCookie 请求。以下是请求示例。

<soapenv:Envelope xmlns:soapenv="http://schemas./soap/envelope/">
   <soapenv:Body>
      <urn:initCookie xmlns:urn="urn:oasis:names:tc:wsrp:v1:types">
         <urn:registrationContext xsi:nil="true" 
		 xmlns:xsi="http://www./2001/XMLSchema-instance"/>
      </urn:initCookie>
   </soapenv:Body>
</soapenv:Envelope>
  在响应中, Producer 会创建一个 cookie ,将它放到响应上,并返回以下响应。
<soapenv:Envelope xmlns:soapenv="http://schemas./soap/envelope/">
<soapenv:Body>
<urn:initCookieResponse xmlns:urn="urn:oasis:names:tc:wsrp:v1:types"/>
</soapenv:Body>
</soapenv:Envelope>

  该响应看似有些异常,因为响应体中几乎什么也没有。不过,如果你查看一下底层的 HTTP 响应,便会发现一个 Set-Cookie 响应报头。 Consumer 应该将该 cookie 和所有未来的请求一起提供给 Producer 。

要启用 Producer 集群,只要用户与 Consumer 一一对应, WebLogic Portal Producer 始终都要求 Consumer 发送一条 initCookie 请求。 Consumer 应该对所有返回的 cookie 进行跟踪,并为后续请求提供这些 cookie 。 WebLogic Portal Consumer 则将这些 Cookie 存放在其用户的 HTTP 会话中。

使用支持文件

  WebLogic Portal Producer 支持在 portlet 上使用支持文件。然而, WSRP 的本质和 portlet 是远程的这一事实,又给该功能施加了某些限制。

  让我们先看看支持文件。支持文件的目的是:在 portlet 的调用周期中、在某些点上实现业务逻辑。让我们将以下支持文件添加给搜索 portlet 。

public class SearchBacking implements JspBacking
{
    public void init(HttpServletRequest httpRequest, HttpServletResponse httpResponse)
    {
    }

    public boolean handlePostbackData(HttpServletRequest httpRequest, 
	HttpServletResponse httpResponse)
    {
        // Some logic
        return false;
    }

    public boolean preRender(HttpServletRequest httpRequest, 
	HttpServletResponse httpResponse)
    {
        // Some logic
        return true;
    }

    public void dispose() 
    {
    }
}

  当你给某个本地 portlet 附加一个支持文件时, WebLogic Portal 框架便会按以下顺序使用相同的 HTTP 请求和响应对象来调用该类上的所有方法。

  • init()
  • handlePostbackData()
  • preRender()
  • dispose()
  • init()
  • handlePostbackData()
  • preRender()
  • dispose()

  不过,如果该 portlet 被部署在 Producer 上,并且通过 WSRP 被调用,那么, WebLogic Portal Producer 便会保存生命周期顺序,但是可能会执行使用不同 HttpServletRequest 和 HttpServletResponse 对象对的生命周期方法。 Producer 所执行的方法取决于 Consumer 的请求,如下所示:

  • getMarkup : init() 、 preRender() 和 dispose()
  • performBlockingAction : init() 、 handlePostbackData() 和 dispose()
  • handleEvents : init() 、 所有事件处理方法和 dispose()

  所以,当某个 Consumer 向某个 Producer 发送一条 performBlockingRequest 、接着又发送一条 getMarkup 请求以获得一个带有支持文件的 portlet 时, Producer 会按以下顺序调用支持文件方法:

  • performBlockingInteraction : init() 、 handlePostbackData() 、 preRender() 和 dispose() 。 Producer 只在决定将 portlet 的标记作为 performBlockingInteractionResponse 的一部分返回时,才会调用 preRender() 。
  • getMarkup : init() 、 preRender() 和 dispose() 。

  如果从最终用户的观点来监控支持文件生命周期方法的执行情况,你便会发现某些方法被调用了两次。出现这一问题是由于 portlet 和 WSRP 协议在本质上是远程的。

  提示: 如果你正在 Producer 上使用支持文件,不要以为用于调用所有支持文件 生命周期方法 的都是同一个 HttpServletRequest 和 H ttpServletResponse 。如果你的 portlet 需要将状态从一个 生命周期方法带至另一个生命周期方法,不妨将此类数据存储在 Producer 的 HTTP 会话中。

注意,在 Consumer 端远程 portlet 上使用的支持文件不受这些限制的约束。

访问门户框架 API

  部署在 WebLogic Portal Producer 上的 Portlet 可以在运行时访问某些门户框架 API 和 JSP 标签。下面是一些常用的 API portlet 。

  • PortletPresentationContext : 无论 Producer 何时呈现某个 portlet , portlet 都能访问该内容。如果呈现的是搜索 portlet ,页面流便可以于 begin 动作期间在页面流 JSP 中访问 PortletPresentationContext 。
  • PortletBackingContext : portlet 可以在动作、支持文件和 JSP 中访问这一部分内容。不过, portlet 只能在动作处理期间改变窗口状态、模式和首选项。需要注意的是,页面流的 begin 动作是在呈现期间执行的,因此,页面流并不能更改窗口状态、模式或 portlet 的首选项。

  只能在动作处理期间改变窗口状态、模式和首选项。需要注意的是,页面流的 begin 动作是在呈现期间执行的,因此,页面流并不能更改窗口状态、模式或 portlet 的首选项。

  这些类并不支持任何 Consumer 专用的方法,并在被调用时会抛出 UnsupportedOperationException 异常 。例如,以下代码片断在 Producer 上将无法执行。

PortletPresentationContext ctx = 
    PortletPresentationContext.getPortletPresentationContext(httpRequest);
PagePresentationContext pageCtx = 
    ctx.getPagePresentationContext(); // Throws UnsupportedOperationException

  在这个代码片断中, portlet 试图访问 Consumer 上有关页面的数据。请注意, WebLogic Portal Producer 并不能访问 Consumer 所呈现的任何门户工件( artifact )。如果你的 portlet 需要访问此类信息,可以在 Consumer 端部署支持文件以获得远程 portlet ,支持文件能够在那里访问此类门户工件。

  提示 :构建独立于任何门户工件之外(包括其他 portlet )的 portlet 不失为一种好的实践。通过引进某种这样的依赖性,你的 portlet 能够始终与门户紧密地耦合在一起,而且不会利用 WSRP 所提供的去耦装置。避免依赖门户工件的另一个理由是互操作性。并非所有的 Consumer 实现都有页面的概念。

Portlet 的首选项和注册

  portlet 首选项的目的在于让 portlet 开发人员和管理人员便于结合和管理 portlet 的属性。你可以将首选项和任何使用 WebLogic Workshop 或 WebLogic Portal 管理工具的 portlet 结合起来。我只在本节讨论远程 portlet 如何利用 portlet 首选项。要了解首选项的内容及其使用方法的更多信息,请参考 Portlet Preferences 一文。

  在 WebLogic Portal 的 WSRP 实现中, Producer 存储和管理 portlet 首选项,并允许 Consumer 利用 WSRP 的 Portlet Management 界面来管理这些首选项。例如,如果你利用 WebLogic Portal Admin Tools 查看 / 修改某个远程 portlet 的 portlet 首选项, Consumer 便利用 Portlet Management 界面从 Producer 那里获取 / 修改首选项。在大多数情况下, portlet 开发人员不必关心直接使用这一界面。欲深入了解这一界面,请参考 WSRP 1.0 Primer (见下面的参考资料)。

  查看 / 修改 portlet 首选项还有另一种方法,便是通过使用 javax.portlet.PortletPreferences 对象。 Portlet 可以利用 Java Portlet API ( 适用于符合 JSR 168 规范的 portlet) ,或利用 PortletPresentationContext 和 PortletBackingContext (适用于其他类型的 portlet )来获得该对象的某个实例。该对象提供了访问并修改 portlet 首选项的方法。当 portlet 第一次修改 portlet 首选项时, WebLogic Portal 会复制 portlet (从而创建一个新的 portlet 实例),并将修改后的首选项和复制的实例关联起来。然而,只有当 Consumer 允许这种复制动作时, WSRP Producer 才允许 portlet 改变首选项。

  如果你正在使用远程 portlet 上的 portlet 首选项,要注意如下几点:
  • Portlet 不能在呈现期间改变持久性状态。如果某个 portlet 在其呈现阶段调用 store() 方法,那么, javax.portlet.PortletPreferences 上的 store() 方法便会抛出一个 IllegalStateException 异常 。
  • 如果你正在通过基于文件(即通过某个 .portal 文件)的门户使用远程 portlet ,或者如果正在访问 portlet 的用户是匿名的,那么, WebLogic Portal Consumer 便不允许任何类型的定制,包括更改 portlet 首选项。为了表明 Producer 不应允许 portlet 修改首选项, Consumer 会在 performBlockingInteraction 请求中为 portletStateChange 元素发送一个 readOnly 值。
  • 如果远程 portlet 实例在几位用户间共享, WebLogic Portal Consumer 会为 portletStateChange 元素发送一个 cloneBeforeWrite 值。该值向 Producer 表明它必须在改变首选项之前复制 portlet 。如果确实需要修改 portlet 首选项, Producer 便会给 Consumer 返回一个新的 portletHandle 。该新的 portletHandle 会取代初始的 portletHandle 。
  • 在随后的请求中, Consumer 会发送一个 readWrite 值,表明 Producer 允许 portlet 修改首选项。
  • 从该列表中可以看出: Producer 能在 portlet 修改 portlet 首选项时创建新的 portlet 实例。随着越来越多来自不同 Consumer 的用户开始通过修改 portlet 首选项来定制 portlet , Producer 也许会停止创建几个 portlet 实例。如果 Consumer 突然决定不再使用 Producer 会怎么样呢?这些实例将会永久保存在 Producer 上。为了避免该情况发生,你可以使用注册界面。

  WSRP 1.0 指定了一个注册界面,以便允许 Consumer 向 Producer 注册。尽管 WSRP 1.0 规范中并未考虑注册的可能使用情况,它也没有制定某种规则,规定当某个 Producer 支持注册时 Consumer 和 Producer 应该怎么做。注册界面包括三个操作:

  • register() ,允许 Consumer 向 Producer 注册。
  • modifyRegistration() ,允许 Consumer 修改某个已有的注册。
  • deregister() ,允许 Consumer 终止注册。

  提示: 如果 portlet 使用 portlet 首选项,注意保持 Portlet Management 和 Registration 界面始终处于启用状态。对于复杂的 Producer 而言,这两个界面默认是启用的。如果 portlet 不使用 portlet 首选项,你可以禁用这两个界面。参阅 WebLogic Portal 文档以了解配置详情。

  当 Producer 支持注册时, Consumer 必须在访问任何 portlet 之前向 Producer 注册。一旦注册, Producer 便会向 Consumer 返回一个 registrationHandle 。之后, Consumer 无论发送何种请求,都必须应要求提供这一句柄( handle ),起到取消注册为止。

  当设置 WebLogic Portal Producer 要求注册时,它会贮存已复制 portlet 的 INSTANCE_ID 以应对 Consumer 的 registrationHandle 。因此, Producer 会始终跟踪是哪个 Consumer 导致创建了哪个 portlet 。当 Consumer 发送一条 deregister() 请求时, Producer 会删除为该 Consumer 所创建的所有实例。

  注意,其他 WSRP Producer 实现也许会(也许不会)支持 portlet 首选项的概念。

结束语

  我写这篇文章的主要目的,在于为大家提供 WebLogic Portal 中某些关键概念和 WSRP 支持实现细节的内幕。你在构建 portlet 时,要考虑本文列出的指导原则。这些原则将确保你的 portlet 与 WSRP 和睦相处。

致谢

  感谢 Adam Bruce 和 Nathan Lipke 提供的观点和评论。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多