在 本系列 的 第 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>yanzhid@cn.ibm.com</email>
</addressbook>
<addressbook>
<name>Liu Xu Jin</name>
<address>Tianjin</address>
<telphone>010-87654321</telphone>
<mobile>13887654321</mobile>
<email>liuxujin@cn.ibm.com</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 的新特性及开发示例已经全部介绍完毕。
附件:本文示例源代码或素材下载