程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> JSR 286 Portlet的新特性,第3部分

JSR 286 Portlet的新特性,第3部分

編輯:關於JAVA

Portlet 過濾器和 Portlet 窗口

在 本系列 的 第 1 部分 簡要回顧了JSR 168 Portlet,並對 JSR 286 Portlet 的新增特性做了詳細的介紹,第 2 部分 和第 3 部分 將通過在 Apache Pluto 2.0 平台上開發和部署 Portlet 應用程序, 向讀者介紹 JSR 286 Portlet 新特性的使用方法。本文將介紹 JSR 286 Portlet 的 Portlet 過濾器和 Portlet 窗口應用程序開發。

關於本系列

本系列 專門針對具有 JSR 168 Portlet 開發基礎,並且想了解 JSR 286 Portlet 新特性和開發流程的開發人員。在學完本系列後, 您將了解到相對於 JSR 168 Portlet,JSR 286 Portlet 究竟提供了哪些增強功能, 以及這些新增特性在實際開發中的應用。

本系列的 第 1 部分 簡單回顧了 JSR 168 Portlet, 並列出了 JSR 286 Portlet 的新增內容。第 2 部分 和第 3 部分將通過在 Apache Pluto 2.0 平台上開發和部署 Portlet 應用程序,向讀者介紹 JSR 286 Portlet 新特性的使用方法。

關於本文

本文承接 第 2 部分,繼續介紹 JSR 286 Portlet 的 Portlet 過濾器和 Portlet 窗口應用程序開發。閱讀本文之前,您應當對 JSR 168 Portlet 有所了解,並閱讀了本系列的 第 1 部分 和 第 2 部分。

Portlet 過濾器

通過 第 1 部分 的介紹,我們知道 Portlet 過濾器分為:

Action 過濾器

Render 過濾器

Resource 過濾器

Event 過濾器

我們將首先對這四種 Portlet 過濾器的開發使用流程分別單獨進行介紹,然後將這四種 Portlet 過濾器綜合起來進行更進一步的開發 ,最後通過和 Servlet 過濾器的結合使用,使讀者明白 Portlet 過濾器和 Servlet 過濾器的關系和區別。

Action 過濾器

新建 Java 類 TestActionFilter:

清單 1. TestActionFilter.java 文件

package com.ibm.samples.jsr286.filters;

import ...

public class TestActionFilter implements ActionFilter {

   private static Log log = LogFactory.getLog(TestActionFilter.class);

   private FilterConfig filterConfig;

   public void init(FilterConfig filterConfig) throws PortletException {
     log.info("action filter [" + filterConfig.getFilterName()
         + "] is initialized.");
     this.filterConfig = filterConfig;
   }

   public void destroy() {
     log.info("action filter [" + filterConfig.getFilterName()
         + "] is destroyed.");
   }

   public void doFilter(ActionRequest actionRequest,
       ActionResponse actionResponse, FilterChain filterChain)
       throws IOException, PortletException {
     log.info("action filter [" + filterConfig.getFilterName()
         + "] is called.");
     filterChain.doFilter(actionRequest, actionResponse);
   }
}

這個程序的主要作用就是在 Action Filter 初始化、過濾器調用,銷毀的時候分別打印相應的信息。清單 1 中 filterChain.doFilter(actionRequest, actionResponse) 需要讀者特別注意,這行代碼保證了過濾器鏈的傳遞,刪去這行代碼,則過濾 器鏈將在該過濾器執行結束後終結。

編輯 portlet.xml 文件,加入如下片斷:

清單 2. Action 過濾器定義

<portlet-app ...>
   ...
   <filter>
     <display-name>TestActionFilter</display-name>
     <filter-name>TestActionFilter</filter-name>
     <filter-class>com.ibm.samples.jsr286.filters.TestActionFilter</filter-class>
     <lifecycle>ACTION_PHASE</lifecycle>
   </filter>
   ...
</portlet-app>

定義 Action 過濾器映射,可以影射到具體某個 Portlet, 或者根據模式匹配到一組 Portlet:

清單 3. Action 過濾器映射

...
<filter-mapping>
   <filter-name>TestActionFilter</filter-name>
   <portlet-name>*</portlet-name>
</filter-mapping>
...

在清單 3 的定義中,我們聲明 TestActionFilter 對所有 Portlet 的 processAction 調用進行攔截。

重啟 Web 應用程序並將 TestPortlet 部署到 "Test JSR 286 Portlet Page" 頁面, 並將該頁面其它所有 Portlet 移除,輸入數據, 點擊 Submit 按鈕觸發 processAction 調用,Eclipse Console 出現如下輸出:

清單 4. Action 過濾器調用結果

...
2008-3-16 22:35:20 com.ibm.samples.jsr286.filters.TestActionFilter init
信息: action filter [TestActionFilter] is initialized.
2008-3-16 22:35:24 com.ibm.samples.jsr286.filters.TestActionFilter doFilter
信息: action filter [TestActionFilter] is called.
2008-3-16 22:35:24 com.ibm.samples.jsr286.filters.TestActionFilter destroy
信息: action filter [TestActionFilter] is destroyed.
...

從上面的信息可以看出,對於 Portlet 的每次 processAction 調用,Action Filter 都要經歷一個初始化、過濾方法 doFilter 調用 、銷毀的全過程。讀者可以多次實驗證實這一點。

Render 過濾器

新建 Java 類 TestRenderFilter:

清單 5. TestRenderFilter.java 文件

package com.ibm.samples.jsr286.filters;

import ...

public class TestRenderFilter implements RenderFilter {

   private static Log log = LogFactory.getLog(TestRenderFilter.class);

   private FilterConfig filterConfig;

   public void init(FilterConfig filterConfig) throws PortletException {
     log.info("render filter [" + filterConfig.getFilterName()
         + "] is initialized.");
     this.filterConfig = filterConfig;
   }

   public void destroy() {
     log.info("render filter [" + filterConfig.getFilterName()
         + "] is destroyed.");
   }

   public void doFilter(RenderRequest renderRequest,
       RenderResponse renderResponse, FilterChain filterChain)
       throws IOException, PortletException {
     log.info("render filter [" + filterConfig.getFilterName()
         + "] is called.");
     filterChain.doFilter(renderRequest, renderResponse);
   }
}

這個程序的主要作用就是在 Render Filter 初始化、過濾器調用,銷毀的時候分別打印相應的信息。和 Action Filter 一樣,讀者同 樣需要注意過濾鏈傳遞的問題。

編輯 portlet.xml 文件,加入如下片斷:

清單 6. Render 過濾器定義

<portlet-app ...>
   ...
   <filter>
     <display-name>TestRenderFilter</display-name>
     <filter-name>TestRenderFilter</filter-name>
     <filter-class>com.ibm.samples.jsr286.filters.TestRenderFilter</filter-class>
     <lifecycle>RENDER_PHASE</lifecycle>
   </filter>
   ...
</portlet-app>

定義 Render 過濾器映射,可以影射到具體某個 Portlet, 或者根據模式匹配到一組 Portlet:

清單 7. Render 過濾器映射

...
<filter-mapping>
   <filter-name>TestRenderFilter</filter-name>
   <portlet-name>*</portlet-name>
</filter-mapping>
...

在清單 7 的定義中,我們聲明 TestRenderFilter 對所有 Portlet 的 render 調用進行攔截。

重啟 Web 應用程序並將多個 Portlet 部署到 "Test JSR 286 Portlet Page"頁面, 訪問該頁面,Eclipse Console 出現多個如下輸出 :

清單 8. Render 過濾器調用結果

...
2008-3-16 22:53:11 com.ibm.samples.jsr286.filters.TestRenderFilter init
信息: render filter [TestRenderFilter] is initialized.
2008-3-16 22:53:11 com.ibm.samples.jsr286.filters.TestRenderFilter doFilter
信息: render filter [TestRenderFilter] is called.
2008-3-16 22:53:11 com.ibm.samples.jsr286.filters.TestRenderFilter destroy
信息: render filter [TestRenderFilter] is destroyed.
...

從上面的信息可以看出,對於 Portlet 的每次 render 調用,Render Filter 都要經歷一個初始化、過濾方法 doFilter 調用、銷毀 的全過程。以上信息出現的次數與部署到頁面上的 Portlet 個數相同,意味著 Portlet 過濾器的攔截是分別針對每個 Portlet 進行的。

Resource 過濾器

新建 Java 類 TestResourceFilter

清單 9. TestResourceFilter.java 文件

package com.ibm.samples.jsr286.filters;

import ...

public class TestResourceFilter implements ResourceFilter {

   private static Log log = LogFactory.getLog(TestResourceFilter.class);

   private FilterConfig filterConfig;

   public void init(FilterConfig filterConfig) throws PortletException {
     log.info("resource filter [" + filterConfig.getFilterName()
         + "] is initialized.");
     this.filterConfig = filterConfig;
   }

   public void destroy() {
     log.info("resource filter [" + filterConfig.getFilterName()
         + "] is destroyed.");
   }

   public void doFilter(ResourceRequest resourceRequest,
       ResourceResponse resourceResponse, FilterChain filterChain)
       throws IOException, PortletException {
     log.info("resource filter [" + filterConfig.getFilterName()
         + "] is called.");
     filterChain.doFilter(resourceRequest, resourceResponse);
   }
}

這個程序的主要作用就是在 Resource Filter 初始化、過濾器調用,銷毀的時候分別打印相應的信息,讀者同樣需要注意過濾鏈傳遞 的問題。

編輯 portlet.xml 文件,加入如下片斷:

清單 10. Resource 過濾器定義

<portlet-app ...>
   ...
   <filter>
     <display-name>TestResourceFilter</display-name>
     <filter-name>TestResourceFilter</filter-name>
     <filter-class>com.ibm.samples.jsr286.filters.TestResourceFilter</filter-class>
     <lifecycle>RESOURCE_PHASE</lifecycle>
   </filter>
   ...
</portlet-app>

定義 Resource 過濾器映射,可以影射到具體某個 Portlet, 或者根據模式匹配到一組 Portlet:

清單 11. Resource 過濾器映射

...
<filter-mapping>
   <filter-name>TestResourceFilter</filter-name>
   <portlet-name>*</portlet-name>
</filter-mapping>
...

在清單 11 的定義中,我們聲明 TestResourceFilter 對所有 Portlet 的 serveResource 調用進行攔截。

重啟 Web 應用程序並將 TestPortlet 部署到 "Test JSR 286 Portlet Page"頁面, 訪問該頁面, 點擊超鏈接“Click me to request Resource URL”請求資源,Eclipse Console 出現如下輸出:

清單 12. Resource 過濾器調用結果

...
2008-3-17 13:21:03 com.ibm.samples.jsr286.filters.TestResourceFilter init
信息: resource filter [TestResourceFilter] is initialized.
2008-3-17 13:21:03 com.ibm.samples.jsr286.filters.TestResourceFilter doFilter
信息: resource filter [TestResourceFilter] is called.
2008-3-17 13:21:05 com.ibm.samples.jsr286.filters.TestResourceFilter destroy
信息: resource filter [TestResourceFilter] is destroyed.
...

從上面的信息可以看出,對於 Portlet 的每次 serveResource 調用,Resource Filter 都要經歷一個初始化、過濾方法 doFilter 調 用、銷毀的全過程。

Event 過濾器

新建 Java 類 TestEventFilter

清單 13. TestEventFilter.java 文件

package com.ibm.samples.jsr286.filters;

import ...

public class TestEventFilter implements EventFilter {

   private static Log log = LogFactory.getLog(TestEventFilter.class);

   private FilterConfig filterConfig;

   public void init(FilterConfig filterConfig) throws PortletException {
     log.info("event filter [" + filterConfig.getFilterName()
         + "] is initialized.");
     this.filterConfig = filterConfig;
   }

   public void destroy() {
     log.info("event filter [" + filterConfig.getFilterName()
         + "] is destroyed.");
   }

   public void doFilter(EventRequest eventRequest,
       EventResponse eventResponse, FilterChain filterChain)
       throws IOException, PortletException {
     log.info("event filter [" + filterConfig.getFilterName()
         + "] is called.");
     Event event = eventRequest.getEvent();
     log.info("event name: " + event.getName());
     log.info("event qname: " + event.getQName());
     log.info("event value: " + event.getValue().toString());
     filterChain.doFilter(eventRequest, eventResponse);
   }

}

這個程序的主要作用就是在 Event Filter 初始化、銷毀的時候分別打印相應的信息,在 doFilter 方法中,截獲事件的名稱、QName 和 事件值,讀者同樣需要注意過濾鏈傳遞的問題。

編輯 portlet.xml 文件,加入如下片斷:

清單 14. Event 過濾器定義

<portlet-app ...>
   ...
   <filter>
     <display-name>TestEventFilter</display-name>
     <filter-name>TestEventFilter</filter-name>
     <filter-class>com.ibm.samples.jsr286.filters.TestEventFilter</filter-class>
     <lifecycle>EVENT_PHASE</lifecycle>
   </filter>
   ...
</portlet-app>

定義 Event 過濾器映射,可以影射到具體某個 Portlet, 或者根據模式匹配到一組 Portlet:

清單 15. Event 過濾器映射

...
<filter-mapping>
   <filter-name>TestEventFilter</filter-name>
   <portlet-name>*</portlet-name>
</filter-mapping>
...

在清單 15 的定義中,我們聲明 TestEventFilter 對所有 Portlet 的 processEvent 調用進行攔截。

重啟 Web 應用程序並將 TestSimpleEventSenderPortlet、TestSimpleEventReceiverPortlet、 TestComplexEventSenderPortlet、 TestComplexEventReceiverPortlet 部署到 "Test JSR 286 Portlet Page" 頁面, 訪問該頁面,點擊 TestSimpleEventSenderPortlet 按 鈕,Eclipse Console 出現如下輸出:

清單 16. Event 過濾器調用結果

...
2008-3-17 19:51:35 com.ibm.samples.jsr286.filters.TestEventFilter init
信息: event filter [TestEventFilter] is initialized.
2008-3-17 19:51:35 com.ibm.samples.jsr286.filters.TestEventFilter doFilter
信息: event filter [TestEventFilter] is called.
2008-3-17 19:51:35 com.ibm.samples.jsr286.filters.TestEventFilter doFilter
信息: event name: simple-event
2008-3-17 19:51:35 com.ibm.samples.jsr286.filters.TestEventFilter doFilter
信息: event qname: {http://cn.ibm.com/}simple-event
2008-3-17 19:51:35 com.ibm.samples.jsr286.filters.TestEventFilter doFilter
信息: event value: simple-event is sent by TestSimpleEventSenderPortlet
2008-3-17 19:51:35 com.ibm.samples.jsr286.filters.TestEventFilter destroy
信息: event filter [TestEventFilter] is destroyed.
...

從上面的信息可以看出,對於 Portlet 的每次發送事件行為,Event Filter 都要經歷一個初始化、過濾方法 doFilter 調用、銷毀的 全過程。從清單 16 也可以看到過濾器捕獲到的事件信息。

發送復雜事件的過濾器結果捕獲讀者可以自行測試。

綜合使用 Portlet 過濾器

Portlet 的四種過濾器可以集成到一個類中去實現,只要該類實現了上述四個接口即可。以下為類 TestAllPhaseFilter 的類圖:

圖 1. TestAllPhaseFilter 的繼承關系

TestAllPhaseFilter 實現代碼如清單 17 所示:

清單 17. TestAllPhaseFilter.java 文件

package com.ibm.samples.jsr286.filters;

import ...

public class TestAllPhaseFilter implements ActionFilter, RenderFilter,
     ResourceFilter, EventFilter {

   private static Log log = LogFactory.getLog(TestAllPhaseFilter.class);

   private FilterConfig filterConfig;

   public void init(FilterConfig filterConfig) throws PortletException {
     log.info("filter [" + filterConfig.getFilterName()
         + "] is initialized.");
     this.filterConfig = filterConfig;
   }

   public void destroy() {
     log.info("filter [" + filterConfig.getFilterName() + "] is destroyed.");
   }

   public void doFilter(ActionRequest actionRequest,
       ActionResponse actionResponse, FilterChain filterChain)
       throws IOException, PortletException {
     log.info("filter [" + filterConfig.getFilterName()
         + "] for action is called.");
     filterChain.doFilter(actionRequest, actionResponse);
   }

   public void doFilter(RenderRequest renderRequest,
       RenderResponse renderResponse, FilterChain filterChain)
       throws IOException, PortletException {
     log.info("filter [" + filterConfig.getFilterName()
         + "] for render is called.");
     PrintWriter out = renderResponse.getWriter();
     out.println(
     "<font color=red>Portlet filter test for writing to output stream.</font>");
     filterChain.doFilter(renderRequest, renderResponse);
   }

   public void doFilter(ResourceRequest resourceRequest,
       ResourceResponse resourceResponse, FilterChain filterChain)
       throws IOException, PortletException {
     log.info("filter [" + filterConfig.getFilterName()
         + "] for resouce is called.");
     log.info("Resource ID:" + resourceRequest.getResourceID());
     PrintWriter out = resourceResponse.getWriter();
     out.println(
     "<font color=red>Portlet filter test for writing to output stream.</font>");
     filterChain.doFilter(resourceRequest, resourceResponse);
   }

   public void doFilter(EventRequest eventRequest,
       EventResponse eventResponse, FilterChain filterChain)
       throws IOException, PortletException {
     log.info("filter [" + filterConfig.getFilterName()
         + "] for event is called.");
     Event event = eventRequest.getEvent();
     log.info("event name: " + event.getName());
     log.info("event qname: " + event.getQName());
     log.info("event value: " + event.getValue().toString());
     filterChain.doFilter(eventRequest, eventResponse);
   }
}

編輯 portlet.xml 文件,加入如下片斷:

清單 18. TestAllPhaseFilter的配置

...
   <filter>
     <display-name>TestAllPhaseFilter</display-name>
     <filter-name>TestAllPhaseFilter</filter-name>
     <filter-class>com.ibm.samples.jsr286.filters.TestAllPhaseFilter</filter-class>
     <lifecycle>ACTION_PHASE</lifecycle>
     <lifecycle>RENDER_PHASE</lifecycle>
     <lifecycle>RESOURCE_PHASE</lifecycle>
     <lifecycle>EVENT_PHASE</lifecycle>
   </filter>
...
   <filter-mapping>
     <filter-name>TestAllPhaseFilter</filter-name>
     <portlet-name>*</portlet-name>
   </filter-mapping>
...

這部分聲明代表該過濾器支持 portlet 四個階段 action、render、resource, event 的過濾攔截,且對所有 portlet 均起作用。具 體執行結果請讀者自行驗證。讀者可以從中體會到 portlet 過濾器的攔截和輸出流中寫入數據的功能。

同 Servlet 過濾器一樣,Portlet 過濾器同樣存在過濾器鏈,但這個過濾器鏈是特定於 Portlet 的某個階段的,如圖 2 所示:

圖 2. Portlet 過濾器鏈

其中圖 2 中的兩個 Portlet 過濾器組成了過濾器鏈( Portlet Filter Chain),Portlet 請求先流經 Portlet 過濾器鏈,最後到達 Portlet, Portlet 響應同樣也流經 Portlet 過濾器鏈到達 Portal 頁面聚集內容顯示。

TestAllPhaseFilter 支持 Portlet 四個階段的響應,在 Portlet 特定的階段,可以分別和 1 - 4 節中的 Portlet 過濾器形成過濾 器鏈,讀者可以從日志輸出的 Porltet 過濾器調用次序證明這一點。

和 Servlet 過濾器的結合

從本系列 第 1 部分 的圖 3 所示,我們已經得知,Servlet 過濾器是一個門戶級過濾器,它是接收和修改客戶端請求的第一個組件, 同時也是修改對客戶端的響應的最後一個組件。Servlet 過濾器比 Portlet 過濾器的優先級別要高,容器將首先進行 Servlet 過濾,其 次是 Portlet 過濾。

為了在 Portal 容器中測試 Servlet 過濾器,我們最好能自己將 Portal 容器構建起來。幸運的是,Apache Pluto 使用 Servlet 實 現了輕量級的 Portal 容器,我們很容易就可以做到這一點。

使用 Pluto 構建自己的門戶

將 ${TOMCAT_HOME}\PlutoDomain\ 下的 pluto-portal-2.0.0-SNAPSHOT.war 解壓縮。目錄結構如圖 3 所示:

圖 3. pluto-portal-2.0.0-SNAPSHOT 目錄結構

在 Eclipse 新建動態 web 項目 myportal, 運行時選擇 Tomcat 6,Servlet 版本選擇 2.3。

將 jsr286portles 項目中 WebContent\META-INF\ 目錄下的 context.xml 文件復制到 myportal 項目的 WebContent\META-INF\ 目錄 下。

將 pluto-portal-2.0.0-SNAPSHOT\ 目錄下的以下目錄和文件拷貝到 myportal 項目的 WebContent\ 目錄下。

images 目錄

pluto.css 文件

pluto.js 文件

portlet-spec-1.0.css 文件

將 pluto-portal-2.0.0-SNAPSHOT\WEB-INF\ 目錄下的以下目錄和文件拷貝到 myportal 項目的 WebContent\WEB-INF 目錄下。

lib 目錄

tld 目錄

themes 目錄

pluto-portal-driver-services-config.xml 文件

將 pluto-portal-2.0.0-SNAPSHOT\WEB-INF\classes\ToolTips.properties 文件復制到 myportal 項目的 src 目錄下。

編輯 myportal 項目的 web.xml 文件,內容如清單 19 所示:

清單 19. web.xml 文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
   "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
   <display-name>myportal</display-name>

   <context-param>
     <param-name>contextConfigLocation</param-name>
     <param-value>
       /WEB-INF/pluto-portal-driver-services-config.xml 
     </param-value>
   </context-param>

   <listener>
     <listener-class>
       org.springframework.web.context.ContextLoaderListener
     </listener-class>
   </listener>

   <listener>
     <listener-class>
       org.apache.pluto.driver.PortalStartupListener
     </listener-class>
   </listener>

   <servlet>
     <servlet-name>plutoPortalDriver</servlet-name>
     <servlet-class>
       org.apache.pluto.driver.PortalDriverServlet
     </servlet-class>
   </servlet>

   <servlet-mapping>
     <servlet-name>plutoPortalDriver</servlet-name>
     <url-pattern>/portal/*</url-pattern>
   </servlet-mapping>

   <taglib>
     <taglib-uri>http://portals.apache.org/pluto</taglib-uri>
     <taglib-location>/WEB-INF/tld/pluto.tld</taglib-location>
   </taglib>
</web-app>

在 myportal 項目的 WEB-INF 目錄下新建 pluto-portal-driver-config.xml 文件,該文件為 pluto portal 容器的門戶配置文件。 內容如清單 20 所示:

清單 20. pluto-portal-driver-config.xml 文件

<?xml version="1.0" encoding="UTF-8"?>
<pluto-portal-driver>

   <portal-name>pluto-portal-driver</portal-name>
   <portal-version>2.0.0-SNAPSHOT</portal-version>
   <container-name>Pluto Portal Driver</container-name>

   <supports>
     <portlet-mode>view</portlet-mode>
     <portlet-mode>edit</portlet-mode>
     <portlet-mode>help</portlet-mode>
     <portlet-mode>config</portlet-mode>

     <window-state>normal</window-state>
     <window-state>maximized</window-state>
     <window-state>minimized</window-state>
   </supports>

   <render-config default="JSR 286 test page">
     <page name="JSR 286 test page"
       uri="/WEB-INF/themes/pluto-default-theme.jsp">
       <portlet context="/jsr286portlets"
         name="TestPortlet" />
       <portlet context="/jsr286portlets"
         name="TestSimpleEventSenderPortlet"/>
       <portlet context="/jsr286portlets"
         name="TestSimpleEventReceiverPortlet"/>
       <portlet context="/jsr286portlets"
         name="TestComplexEventSenderPortlet"/>
       <portlet context="/jsr286portlets"
         name="TestComplexEventReceiverPortlet"/>
       <portlet context="/jsr286portlets"
         name="TestPublicRenderParameterPortlet"/>
     </page>
   </render-config>

</pluto-portal-driver>

在 Eclipse 的 Servers 視圖中將 myportal 項目添加到 tomcat 服務器中,啟動 tomcat。使用浏覽器訪問 http://localhost:8080/myportal/portal/,呈現一個包含我們之前生成的所有 portlet 的一個 portal 頁面,如圖 4 所示:

圖 4. 自行構建的 portal 容器 myportal

創建 Servlet 過濾器

在 myportal 項目中新建 Java 類 TestServletFilter, 如清單 21 所示:

清單 21. pluto-portal-driver-config.xml 文件

package com.ibm.samples.servlet.filters;

import ...

public class TestServletFilter implements Filter {

   private static Log log = LogFactory.getLog(TestServletFilter.class);

   private FilterConfig filterConfig;

   public void init(FilterConfig filterConfig) throws ServletException {
     log.info("servlet filter [" + filterConfig.getFilterName()
         + "] is initialized.");
     this.filterConfig = filterConfig;
   }

   public void destroy() {
     log.info("servlet filter [" + filterConfig.getFilterName()
         + "] is destroyed.");
   }

   public void doFilter(ServletRequest servletRequest,
       ServletResponse servletResponse, FilterChain filterChain)
       throws IOException, ServletException {
     log.info("serlvet filter [" + filterConfig.getFilterName()
         + "] is called.");
     filterChain.doFilter(servletRequest, servletResponse);
   }

}

配置 Servlet 過濾器

編輯 myportal 項目的 web.xml 文件,增加如清單 22 的配置內容:

清單 22. Servlet 過濾器配置片斷

...
   <filter>
     <filter-name>TestServletFilter1</filter-name>
     <filter-class>
       com.ibm.samples.servlet.filters.TestServletFilter
     </filter-class>
   </filter>

   <filter>
     <filter-name>TestServletFilter2</filter-name>
     <filter-class>
       com.ibm.samples.servlet.filters.TestServletFilter
     </filter-class>
   </filter>

   <filter-mapping>
     <filter-name>TestServletFilter1</filter-name>
     <url-pattern>/portal/*</url-pattern>
   </filter-mapping>

   <filter-mapping>
     <filter-name>TestServletFilter2</filter-name>
     <url-pattern>/portal/*</url-pattern>
   </filter-mapping>
   ...

可以看到,清單 22 中使用java 類 TestServletFilter 共配置了兩個 Servlet 過濾器,均對 portal 進行過濾。

Servlet 和 Porlet 過濾器鏈

通過日志分析,我們可以得到如圖 5 所示的過濾器鏈,該圖體現了一個 HTTP 請求所流經的 Servlet 過濾器鏈和 Portlet 過濾器鏈 。請注意圖中用不同顏色的線條表示 Portlet 過濾器針對的 Portlet 的四個不同的階段,這四個階段的過濾器操作是分別獨立發生的, 而不是並行發生的。

圖 5. Servlet 過濾器鏈和 Portlet 過濾器鏈

讀者可以從日志輸出注意到 Servlet 過濾器和 Portlet 過濾器的以下區別和聯系:

Servlet 過濾器在 Web 應用啟動時初始化,在 Web 應用關閉時銷毀;Portlet 過濾器在訪問相應 Portlet 的時候初始化,在 Portlet 訪問結束的時候銷毀。

Servlet 過濾器在訪問所有 portlet 的聚合體 - portal 頁面的時候調用,並在所有的 portlet 過濾器調用之前調用。

Servlet 過濾器過濾的對象是整個 Portal 頁面或者一個完整的資源, Portlet 過濾器過濾的對象是 Portal 頁面上的組成元素 Portlet 小部件,當然 Portlet 過濾器也可以通過 Resource Filter 過濾一個單獨的資源。

Servlet 過濾器只可以對 Servlet 唯一的生命階段 Servlet 響應階段進行過濾,Portlet 過濾器可以分別對 Portlet 的操作、呈現 、事件、資源這四個不同的生命階段進行過濾。

Portlet 窗口

PortletRequest 新增了一個方法 getWindowID(),可以獲得 Portlet 的窗口 ID,這個 ID 是由容器生成的。在 Portal 容器中布局 同一個 Portlet 多次的情況下,windowID 可以用來區分同一個 Portlet 的不同窗口,從而可以使這些 Portlet 窗口緩存並呈現不同的 數據。

在本節中,將提供一個示例,在同一個 portlet 的不同窗口中,根據 Portlet 窗口 ID 的不同,呈現不同的數據。

准備數據

本例中我們使用 xml 文件提供兩個聯系人數據,在工程 jsr286portlets 的 src 目錄下, 新建 address.xml 文件,內容如清單 23 所示:

清單 23. address.xml 文件

<?xml version="1.0" encoding="UTF-8"?>
<addressbooks>
   <addressbook>
     <name>Yan Zhi Dong</name>
     <address>Beijing</address>
     <telphone>010-12345678</telphone>
     <mobile>13512345678</mobile>
     <email>[email protected]</email>
   </addressbook>
   <addressbook>
     <name>Liu Xu Jin</name>
     <address>Tianjin</address>
     <telphone>010-87654321</telphone>
     <mobile>13887654321</mobile>
     <email>[email protected]</email>
   </addressbook>
</addressbooks>

將 XML 數據映射為 Java Bean

本例使用 Apache Commons 庫的 Digester 包解析 XML 內容並且封裝到 Java Bean 中。

從 http://commons.apache.org/ 查找並下載 commons-digester、commons-collections、commons-beanutils 包,並將 jar 文件復 制到 WEB-INF\lib 目錄下。

按照 Digester 包的要求,在 src 目錄下新建一個規則文件 address-rule.xml, 定義 XML 文件到 Java Bean 的映射規則。如清單 24 所示:

清單 24. address-rule.xml 文件

<?xml version="1.0" encoding="UTF-8"?>
<digester-rules>
   <pattern value="addressbooks/addressbook">
     <object-create-rule
       classname="com.ibm.samples.bean.AddressBook" />
     <set-next-rule methodname="add" paramtype="java.lang.Object" />
     <set-properties-rule />
     <bean-property-setter-rule pattern="name" />
     <bean-property-setter-rule pattern="address" />
     <bean-property-setter-rule pattern="telphone" />
     <bean-property-setter-rule pattern="mobile" />
     <bean-property-setter-rule pattern="email" />
   </pattern>
</digester-rules>

新建 Java 類 AddressBook, 該類為所映射的 Java Bean, 如清單 25 所示:

清單 25. AddressBook.java 文件

package com.ibm.samples.bean;

import java.io.Serializable;

public class AddressBook implements Serializable {

   private static final long serialVersionUID = 5490046055431517141L;

   private String name;
   private String address;
   private String telphone;
   private String mobile;
   private String email;

   ...//setters and getters 
}

應用 portlet 窗口

新建 Portlet 類 TestWindowPortlet, 該類繼承了 GenericPortlet,實現了 VIEW 和 EDIT 模式。如清單 26 所示:

清單 26. TestWindowPortlet.java 文件

package com.ibm.samples.jsr286.portlets;

import ...

public class TestWindowPortlet extends GenericPortlet {
   private static Log log = LogFactory.getLog(TestWindowPortlet.class);

   @Override
   protected void doEdit(RenderRequest request, RenderResponse response)
       throws PortletException, IOException {
     PortletRequestDispatcher rd = getPortletContext().getRequestDispatcher(
         "/WEB-INF/jsp/TestWindowPortletEdit.jsp");
     rd.include(request, response);
   }

   @Override
   protected void doView(RenderRequest request, RenderResponse response)
       throws PortletException, IOException {
     String windowID = request.getWindowID();
     request.setAttribute("windowID", windowID);
     AddressBook addressbook = (AddressBook) getPortletContext()
         .getAttribute(request.getWindowID());
     request.setAttribute("addressbook", addressbook);
     PortletRequestDispatcher rd = getPortletContext().getRequestDispatcher(
         "/WEB-INF/jsp/TestWindowPortletView.jsp");
     rd.include(request, response);
   }

   @Override
   public void processAction(ActionRequest request, ActionResponse response)
       throws PortletException, IOException {
     List<AddressBook> addressbooks = (List<AddressBook>) getPortletContext()
         .getAttribute("addressbooks");
     if (addressbooks == null) {
       addressbooks = new ArrayList<AddressBook>();
       URL rule = getClass().getResource("/address-rule.xml");
       Digester digester = DigesterLoader.createDigester(rule);
       digester.push(addressbooks);
       InputStream inputStream = getClass().getResourceAsStream(
           "/address.xml");
       try {
         digester.parse(inputStream);
       } catch (SAXException e) {
         log.error(this, e);
       }
       getPortletContext().setAttribute("addressbooks", addressbooks);
     }
     String strIndex = request.getParameter("index");
     int index = Integer.parseInt(strIndex);
     AddressBook addressbook = addressbooks.get(index);
     getPortletContext().setAttribute(request.getWindowID(), addressbook);
     response.setPortletMode(PortletMode.VIEW);
   }
}

注意 processAction 方法,根據 Portlet 的窗口 ID, 在方法的末尾將特定的 AddressBook 對象和特定的 portlet 窗口關聯起來, 並將這種關聯放在 Portlet 上下文中(也可以視作緩存)。

相應的 JSP 文件,如清單 27 和 清單 28:

清單 27. TestWindowPortletEdit.jsp 文件

<%@ page language="java" contentType="text/html; charset=UTF-8"
   pageEncoding="UTF-8"%>
<%@taglib prefix="portlet" uri="http://java.sun.com/portlet_2_0"%>
<portlet:defineObjects />
<form action="<portlet:actionURL/>">
<table>
   <tr>
     <td>Please select one item of the following:</td>
   </tr>
   <tr>
     <td><input type="radio" name="index" value="0" />Yan Zhi Dong</td>
   </tr>
   <tr>
     <td><input type="radio" name="index" value="1" />Liu Xu Jin</td>
   </tr>
   <tr>
     <td><input type="submit" value="Submit" /></td>
   </tr>
</table>
</form>

清單 28. TestWindowPortletView.jsp 文件

<%@ page language="java" contentType="text/html; charset=UTF-8"
   pageEncoding="UTF-8"%>
<%@taglib prefix="portlet" uri="http://java.sun.com/portlet_2_0"%>
<portlet:defineObjects />
<table>
   <tr>
     <td>WindowID:</td>
     <td>${windowID}</td>
   </tr>
   <tr>
     <td>Name:</td>
     <td>${addressbook.name}</td>
   </tr>
   <tr>
     <td>Address:</td>
     <td>${addressbook.address}</td>
   </tr>
   <tr>
     <td>Telphone:</td>
     <td>${addressbook.telphone}</td>
   </tr>
   <tr>
     <td>Mobile:</td>
     <td>${addressbook.mobile}</td>
   </tr>
   <tr>
     <td>Email:</td>
     <td>${addressbook.email}</td>
   </tr>
</table>

在 portlet.xml 和 web.xml 文件中增加相應的 Portlet 定義 TestWindowPortlet ,並設定該 portlet 支持 VIEW 和 EDIT 模式。

新建 test 頁面,並將 TestWindowPortlet 添加到 test 頁面兩次,如圖 6 所示:

圖 6. TestWindowPortlet 的兩個窗口

在 EDIT 模式下,分別選擇不同的選項,然後提交,如圖 7:

圖 7. EDIT 模式下選擇不同的選項

最後兩個窗口可以顯示不同的數據,如圖 8 所示:

圖 8. TestWindowPortlet 的兩個窗口分別顯示不同的數據

關於本示例的引申

在該示例中,我們使用 XML 文件存儲數據。在實際的生產環境中,數據來源可能是一個關系數據庫,甚至是一個遠程調用(比如 Web Services)。在這種情況下,如果數據的更新周期比較長,就可以通過數據的緩存提高性能。在同一個 portal 頁面中,如果存在同一個 portlet 的不同窗口副本,就可以根據 Portlet 的窗口 ID,把從數據庫調用或者遠程調用獲得的數據緩存到 Portal Server 本地,並且 和 Portlet 的特定窗口關聯起來,以減少數據庫訪問或者遠程調用的開銷,提高系統的性能。本例中使用 Portlet 上下文緩存數據,實 際生產過程中可以使用某些緩存產品,如 Ehcache 等。

小結

本部分通過示例代碼介紹了 JSR 286 Portlet 過濾器和 Portlet 窗口的實際開發使用步驟。至此,JSR 286 Portlet 的新特性及開發 示例已經全部介紹完畢。

本文配套源碼

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved