级别: 初级 Shunguo Yan (shunguoy@us.ibm.com), 助理软件工程师, IBM Software Group
2004 年 5 月 01 日 本文描述了 WebSphere Portlet 过滤器的基本知识,它与 Servlet 过滤器的不同之处,以及如何开发和使用 Portlet 过滤器来修改和自定义现有的 Portlet。
WebSphere Portlet 过滤器技术提供了一种解决方案来快速地修改和自定义现有的 Portlet,以便它们可以用于新的或不同的门户应用程序。Portlet 过滤器是一个 Java 模块,它能够动态地插入到一个或多个现有的 Portlet 中 ,以便修改 Portlet 请求和响应,它还可以将新的功能添加到现有的 Portlet 中,而无需对它们进行更改。本文描述了 WebSphere Portlet 过滤器的基本知识,它与 Servlet 过滤器的不同之处,以及如何开发和使用 Portlet 过滤器来修改和自定义现有的 Portlet。
引言
已经开发和交付了为数众多的 Portlet,这些 Portlet 可以安装在 WebSphere Portal 中。除了包含在 WebSphere Portal 中的内部 Portlet 以外,企业应用程序供应商为了提供对他们的系统的访问也开发了许多的 Portlet,单个开发人员为了执行特定的门户功能或者为了提供针对特定问题的解决方案也纷纷推出了许多的 Portlet。单单通过WebSphere 门户目录就已经可以找到成百上千的 Portlet 可供使用,这些 Portlet 涵盖了各种各样的应用程序和行业。通常,门户将使用一个或多个现有的 Portlet 来提供特定的功能并加速开发过程。在某些情况下,由于安全性控制或不公开的接口的缘故,从门户访问特定于厂商的企业应用程序的惟一途径就是使用现有的 Portlet。
虽然 Portlet 一般会通过配置提供一些自定义功能,但通常这是很有限的。在将现有的 Portlet 用于门户之前,有时还需要其他的自定义功能,包括简单的自定义(如提供与门户的其他部分更一致的外观)和复杂的自定义(如添加新的功能)。我们来考虑一个现有的文件上传 Portlet。我们可能需要加入一种常规算法来检查待上传的文件内容;如果文件内容不合适,那么在它抵达后台系统之前会被拒绝。对于一个提供实时世界新闻的现有新闻频道 Portlet,它可能只关注特定地区或特定主题的新闻,而不应该显示其他的新闻。在很多情况下,门户范围的性能调优可能需要在门户的每一个 Portlet 中添加其他的逻辑,以便测量执行时间。
有多种方法可以修改和自定义现有的 Portlet。最常用的方法就是面向对象的继承,它使你能够从现有的 Portlet 派生新的 Portlet 以便添加新的功能,因为 Portlet 的核心是一个或多个 Java 类。这种方法的主要缺点是:
- 首先,新的 Portlet 要想能够自定义,就必须从现有的 Portlet 创建。
- 其次,因为 Portlet 应用程序是作为 web 应用程序档案文件(WAR)交付的,所以新的 Portlet 不能直接引用现有的 Portlet 的 WAR 中的类;WAR 中 的所有类及其依赖性都必须先解包,然后再重新打包到新的 Portlet 的 WAR 中。
- 另外,调用 Portlet 方法(如 service 和 actionPerformed)时还必须谨慎以防它将只被容器调用。
与基于继承的自定义相比,WebSphere Portlet 过滤器技术提供了一种更容易且更好的方法来重用和自定义现有的 Portlet:
- Portlet 过滤器是一个(或多个)可重用的 Java 类,可以通过一种标准的方式来声明性地添加或删除,而无需对它所修改的现有 Portlet 或 Portlet 组进行更改。
- 过滤器并不是一个 Portlet;过滤器不创建对请求的响应,但是它截取和修改请求(在请求传送到目标 Portlet 以进行处理之前)及响应(在响应聚集到门户页面之前)。
- 过滤器并没有绑定到任何特定的 Portlet,因此它的生命周期不依赖于任何单个 Portlet。这就意味着,一旦过滤器初始化完毕,它就能够动态地应用到所有的可应用 Portlet,而无需进一步的初始化。
- 另外,由于过滤器独立于任何 Portlet,所以现有的 Portlet 或 Portlet 的 WAR 几乎不需要创建过滤器。
WebSphere 代码转换和机器翻译是基于 Portlet 过滤器技术。关于 WebSphere 代码转换和翻译的详细描述可以在版本 5.0 的 WebSphere portal 信息中心中找到,而相关的文章列表请见
参考资料部分。
本文介绍了 WebSphere Portlet 过滤器的基础知识、Portlet 过滤器和 Servlet 过滤器的不同之处、以及如何开发和使用 Portlet 过滤器来自定义 Portlet。另外,还将解决各种开发问题以帮助简化其使用。一种称为文件内容过滤器(File Content Filter)的过滤器为现有的文件上传 Portlet 添加了细粒度的文件控制机制,本文就是以它为例来说明 Portlet 过滤器的使用的。
您可以找到一些适用于 WebSphere Portal 的文件上传 Portlet。在 WebSphere Portal 中 Portal Document Manager(PDM)Portlet 和 Portlet Installation Portlet 都是缺省安装的。更有许多是在 WebSphere Portal Catalog 中列出的。本文以 PDM Portlet(PDM)为例来说明文件内容过滤器的使用。在 WebSphere Portal V5 中,PDM Portlet 在缺省状态下自动安装在 My Portal 中的 Document 页面内。它给门户的用户提供了简单实时的文档查看和解决方案。PDM 让用户具有编辑者(Editor)的权限,使得他们几乎可以上传任何类型的文件。然而,对于许多组织来说,这种功能的需求可能远远比 PDM Portlet 提供的标准行为复杂得多。例如,文件控制机制可以用于控制能够上传的文件的类型、记录上传的文件和它们的属性、报告上传成功或失败的状态等等。本文中的文件内容过滤器将修改 PDM Portlet 的行为,这样就只有某些类型的文件被允许上传。
Portlet 过滤器与 Servlet 过滤器
如果您熟悉 Servlet 过滤器(请参阅
Java Servlet 规范以获得详细信息),从已经阅读的资料中,您可能已经注意到了 Portlet 过滤器和 Servlet 过滤器的相似性。实际上,它们之所以是相似的,是因为二者都可以声明性地嵌入,从而截获并修改请求和响应。但是理解它们之间存在着很大的不同是非常重要的。在一定程度上,它们之间的差异是与 Servlet 和Portlet 之间的差异相联系的:Servlet 过滤器是一个门户级过滤器,它可以修改由一些小的部分(来自页面上所有 Portlet 的响应)集合而成的整个门户页面;而 Portlet 过滤器只能用于那些小的部分。Servlet 过滤器(如果已经安装的话)是接收和修改客户端请求的第一个组件,同时也是修改对客户端的响应的最后一个组件(请参见图 1)。
图 1. 带有 Servlet 过滤器和 Portlet 过滤器的客户端请求事件序列
如图 1 所示, Servlet 请求(步骤 1)在分派给一个或多个 Portlet 请求(步骤 3)之前首先通过一个 Servlet 过滤器链进行处理(步骤 2)。在结果集聚到一起(步骤 6 和 7)之前,Portlet 请求进一步转发到 Portlet 过滤器链进行处理(步骤4)。接着,将集聚的结果发送回 Servlet 过滤器进行处理,之后,将集聚的结果最终显示给用户(步骤 9)。
一个过滤器链包含一个或多个过滤器。在一个过滤器完成处理之后,新的请求和响应将传送到链上的下一个过滤器;链上的最后一个过滤器调用目标资源(Servlet 或 Portlet)。
与 Servlet 过滤器一样,Portlet 过滤器也是 WebSphere portal 体系结构中的一个可选的组件。因此在解决实际的问题时,可以使用一种类型的过滤器,也可以同时使用两种类型的过滤器,还可以不使用过滤器。例如,Servlet 过滤器可以用于压缩和加密整个门户页面,而 Portlet 过滤器可能更适合只压缩和加密门户页面的一部分。在 WebSphere Portal Transcoding Technology(请参见
参考资料)中,Portlet 级代码转换使用 Portlet 过滤器来进行内容转换、标记转换和注解等等,而门户级代码转换使用 Servlet 过滤器(或门户过滤器,在 WebSphere portal 中是这样称呼的)来提供 Deck 片段。
Portlet 过滤器的支持和开发
Portlet 过滤器的支持类定义在 WebSphere portal 的
com.ibm.wps.pe.pc.legacy.cmpf 包中。PortletFilter 接口提供了三个方法来管理 Portlet 的生命周期:
-
void init(PortletFilterConfig config)
容器调用一次这个方法来准备用于服务的过滤器。对象 PortletFilterConfig(config)使得过滤器能够访问配置参数以及对门户上下文的引用。
-
void destroy()
这个方法是在将过滤器从服务移除之后调用的。这个方法使得过滤器能够清除任何存放的资源。
-
void doFilter(PortletFilter.Method method, PortletRequest request, PortletResponseresponse, PortletFilterChain filterChain)
这个方法执行实际的过滤工作。这个方法使得过滤器能够检查和修改请求和响应,或者完全跳过请求的处理。第一个参数(method)是过滤器能够调用的请求方法的类型(如 SERVICE 或 ACTIONEVENT)。最后 的参数(filterChain)包含一个已经注册且正在运行的过滤器的列表。
包中的 PortletFilterAdapter 类为 PortletFilter 接口提供了一个缺省的实现。根据作为 doFilter方法中的第一个参数传送的请求方法的类型,可以将 doFilter 方法的缺省实现委托给一组 do 方法(doService、doTitle、doActionEvent、doMessageEvent、doLogin、doBeginPage和 doEndPage)。do 方法的缺省实现做的惟一一件事情就是将最初的请求和响应发送到链上的下一个过滤器或目标 Portlet。过滤器的每个 do方法都是在目标 Portlet 的对应方法调用之前调用的。表 1 列出了每个过滤器方法和目标 Portlet 方法之间的映射。
表
1.Portlet 和 Portlet 过滤器方法
|
Portlet 过滤器方法
|
Portlet 方法
| | doService | 服务或它的助手方法(doEdit、doView、 doConfigure 或 doHelp) | | doActionEvent | actionPerformed | | doMessageEvent | messageReceived | | doLogin | login | | doTitle | doTitle | | doBeginPage | beginPage | | doEndPage | endPage |
自定义的 Portlet 过滤器应该扩展 PortletFilterAdapter 类,并且覆盖表 1中所列的一个或多个方法。举例来说,如果自定义过滤器仅支持 Portlet VIEW 并且修改输出,那么就只需要覆盖 doService方法。然而,如果过滤器支持 Portlet ActionEvent 并且修改输出,那么 doService 和 doActionEvent都应该被覆盖(请参见下一部分以获得详细信息)。WebSphere Portal 提供了三个便利的包装类:
- PortletRequestWrapper(用于 PortletRequest)
- PortletResponseWrapper(用于 PortletResponse)
- ClientWrapper(用于客户端接口)
包装类应该用于创建自定义请求、响应和客户端类,这样,就可以容易地添加新的功能而不必实现整个接口了。
图 2. 带有 Portlet 过滤器的服务请求的请求事件序列
典型的过滤器开发包括如下步骤(同样显示在图 2 中,图 2 展示了带有 Portlet 过滤器的服务请求的事件流以及 Portlet 过滤器中的处理步骤):
-
初始化过滤器和检索配置数据
-
检查和修改 Portlet 请求
-
缓冲响应流
-
调用或阻塞过滤器链
-
修改响应流
-
使用原始编写器输出已修改的响应流
-
过滤器中的事后处理
当然,这些步骤将是可选的,也并非应用于所有的过滤器。举例来说,如果过滤器没有修改请求,那么与请求处理有关的步骤就可以省略。
下一部分将以文件内容过滤器为例详细地描述这些步骤。
初始化过滤器和检索配置数据
Portlet 配置数据是 Portlet 配置文件
PortletFilterService.properties 中的可选参数设置,Portlet 配置文件
PortletFilterService.properties 在 WebSphere Portal 的
PORTAL_HOME\shared\app\config\services 目录中,其中
PORTAL_HOME 是服务器安装目录。文件内容过滤器的配置(名为 FileContentFilter)作为一个示例显示在清单 1 中。从清单 1 显示中可以看出,文件内容过滤器将覆盖 doActionEvent 和 doService 方法,并且具有名为
Trace 的配置参数,这个参数指示是打开还是关闭过滤器的跟踪。(为了避免文本过长,我们从本文的所有代码样本中删除了跟踪语句。您可以在
下载文件中找到完整的源代码。)
清单 1. 文件内容过滤器配置
# ------ File Content Filter ------ #
# filter name, required
filtername14 = FileContentFilter
# filter implementation class, required
FileContentFilter.classname = myportlet.filters.FileContentFilter
# supported methods, at least one is required
FileContentFilter.method.1 = ActionEvent
FileContentFilter.method.2 = service
# configuration parameter, optional
FileContentFilter.Trace = true
|
该 Portlet 过滤器通过调用 PortletFilterConfig 对象(它作为参数由容器传送给 init 方法)中的 getInitParameter 或 getInitParameterNames 方法在其 init 方法中检索配置参数。对过滤器运行时环境(门户上下文)的引用也可以通过调用 getPortalContext(清单 2)实现。门户上下文是一种 ServletContext,它可以进一步用于访问系统日志,从容器加载资源,或检索 Portal 配置,等等。如前所述,过滤器独立于任何特定的 Portlet,因而 Portlet 运行时环境(PorletContext)对于该过滤器来说是不可用的,并且门户上下文引用(清单 2 中的上下文)不能向下强制类型转换到 PortletContext 引用。因此,过滤器既不可以用于检索目标 Portlet 的配置数据,也不可以用于调用 Portlet 服务,这两者对于 PortletContext 对象是可用的,但是对于 ServletContext 对象却不可用。
清单 2. 检索 Portlet 过滤器运行时环境和配置参数
ServletContext context;
boolean trace;
public void init(PortletFilterConfig config) throws PortletException {
//initialize the filter
super.init(config);
// get portal context
context = config.getPortalContext();
//get configuration parameter "Trace"
String param = config.getInitParameter("Trace");
if ((param!=null)&&(param.equalsIgnoreCase("true"))) {
trace = true;
}
context.log("Trace = " + trace);
}
|
检查和修改 Portlet 请求
在这样一些情况下,过滤器需要检查和修改 Portlet 请求:
- 记录来自请求的数据。
- 获取发出请求的用户并验证他们。
- 检索和修改客户端(WML、HTML 等等)。
- 基于请求的内容或请求中的某些参数拒绝或接受请求。
在检索和修改 Portlet 请求时需要解决以下问题:
- 通过调用对象 PortletRequest 中的 setCharacterEncoding 和 set/removeAttribute 方法,可以直接修改请求中的字符编码和属性。例如,对 setCharacterEncoding(ISO-8859-1)的调用将把请求流的字符编码从用户概要中的缺省编码(通常为 UTF-8)更改为 ISO-8859-1。
- 要修改请求中的大多数其他的字段,您应该创建您自己的自定义 PortletRequest 类,并且覆盖一个或多个标准方法来修改它的标准行为。举例来说,如果需要修改请求中的客户端设备,就应该覆盖 getClient 方法以返回所需的客户端设备类型。如果需要修改请求中的本地字段,就应该覆盖 getLocal 方法以返回所需的本地设备类型。
- 要实际检索请求流,您通常需要创建您自己的 PortletRequest 类。其原因在于,如果在这个请求中已经调用了 PortletRequest 对象中的 getInputStream 方法,它就会返回空值。换言之,对 PortletRequest 对象中的 getInputStream 方法的调用将首先检索数据流,然后在所有对这个方法进行的并发调用中返回空值。当已经为过滤器中的请求调用了 getInputStream 方法时,如果它试图检索后面来自相同请求的输入流,就会由目标 Portlet 抛出运行时 NullPointerException。过滤器可能首先检索和缓存来自原始请求的数据流,然后传送缓存的数据来为目标 Portlet 创建新的 PortletRequest 对象。您的 PortletRequest 类中的 getInputStream 方法应该返回缓存的数据流。
- 只能在动作事件处理方法(doActionEvent 方法)中调用 PortletRequest 对象的 getInputStream 方法。如果过滤器试图访问超出了动作事件处理的这种功能,就会抛出 IllegalStateException。
文件内容过滤器创建了 FileContentPortletRequest 类(清单 3),FileContentPortletRequest 类将数据流看作是它的构造器中的参数之一,并且覆盖 getInputStream 方法来返回数据流。因而就可以使用数据流和原始请求来实例化新的 FileContentPortletRequest 实例。
清单 3. 从 PortletRequestWrapper 类派生的自定义 PortletRequest 类
public class FileContentPortletRequest extends PortletRequestWrapper {
byte[] buffer;
ServletInputStream input;
public FileContentPortletRequest(PortletRequest req, byte[] buffer) {
super(req);
this.buffer = buffer;
}
/** Enforce the behavior of getInputStream method in PortletRequest class:
* return data stream for the first call, and null for all the subsequent calls
*/
public ServletInputStream getInputStream() throws IOException {
if (input == null) {
input = new ServletInputStreamAdapter(new ByteArrayInputStream(buffer));
} else {
input = null;
}
return input;
}
}
|
清单 4 展示了文件内容过滤器中的 doActionEvent 方法。这个方法首先通过调用原始 Portlet 请求中的 getInputStream 方法来对数据流进行本地缓存以检索该流,然后将缓存的数据传送到 FileContentPortletRequest 的构造器以实例化新的实例。两个实例都被实例化:
- 一个被传送到文件内容检验模块(validateContent method,清单 5)。
- 另一个通过过滤器链进行传送(清单 4)。
对于文件内容过滤器来说,缓存原始的数据流是必要的,因为 Apache FileUpload 库通过调用请求中的 getInputStream 方法(它作为参数进行传送)来检索数据流。如果我们将原始的请求首先传送到文件内容检验模块,然后再将其传送到目标 Portlet,我们就不能通过调用 getInputStream 方法来获得数据流,因而会引起异常。如果该请求包含无效的文件内容,那么 doActionEvent 方法就会将无效的项(InvalidItems)保存在 Portlet 会话中,这样该过滤器的 doService 方法(在下一部分中,清单 7)就可以检索这些项,并且将错误消息插入响应中。如下一部分所述,doActionEvent 方法本身不能将错误消息插入响应中。
清单 4. doActionEvent 方法
public void doActionEvent(PortletRequest portletRequest, PortletResponse portletResponse,
PortletFilterChain filterChain) throws PortletException, IOException {
byte[] buffer = getStreamFromInput(portletRequest.getInputStream());
Vector invalidItems = null;
try {
invalidItems = validateContent(new FileContentPortletRequest(portletRequest,buffer));
} catch (Exception exp) {
throw new IOException("FileContentFilter: "+ exp.getMessage());
}
PortletSession session = portletRequest.getPortletSession();
//add invalid items to the session
session.setAttribute("InvalidItems", invalidItems);
if (invalidItems.size() > 0) {
//Block filter chain for invalid file content
filterChain.doFilter(null, null);
} else {
PortletRequest newPortletRequest = new FileContentPortletRequest(portletRequest, buffer);
//Invoke filter chain
filterChain.doFilter(newPortletRequest, portletResponse);
}
//Post processing
context.log("FileContentFilter: current time: " + df.format(new Date(System.currentTimeMillis())));
}
|
清单 5 展示了文件内容过滤器的文件内容检验模块。它使用 Apache Multipart FileUpload 库来检索文件并检查文件内容。它首先检查请求是否包含用于文件下载的 Multipart 内容。如果它是 Multipart 内容,该模块就检查文件内容的有效性。为了演示起见,该文件内容过滤器的文件有效性检查规则是非常简单的:它将
*.jar 、
*.jpg 、
*.gif 和
*.jpeg 看作是有效地文件类型,所有其他的类型都是无效的(清单 5)。
清单 5. 文件内容有效性检查模块
/**Use Apache Commons FileUpload for multipart content processing
* See http://jakarta.apache.org/commons/license.html
for Apache
* Software License terms
*/
private Vector validateContent(PortletRequest portletRequest)
throws FileUploadException, IOException {
Vector invalidItems = new Vector();
// check whether the request encoding type is multipart/form-data).
if (DiskFileUpload.isMultipartContent(portletRequest)) {
DiskFileUpload upload = new DiskFileUpload();
upload.setSizeMax(2000000); //max. file size 2M
List items = upload.parseRequest(portletRequest);
Iterator iter = items.iterator();
while (iter.hasNext()) {
FileItem item = (FileItem)iter.next();
if (!item.isFormField()) {
String fileName = item.getName();
if (!isFileContentValid(fileName)) {
invalidItems.addElement(fileName);
}
}
}
}
return invalidItems;
}
private boolean isFileContentValid(String fileName) {
/**Add the logic to validate the file.
*For this demo, we only accept jar, jpg, gif and jpeg
*return true if file is valid, false otherwise. See attached
*source code for implementation
*/
}
|
缓冲响应流
用于修改 Portlet 响应的过滤器通常必须在请求返回到客户端之前将其捕获,以便过滤器可以操纵它。这样做的一个典型的方法是将一个指向输出流的引用传送到 Portlet。在 Portlet 创建了发送到输出流的响应之后,过滤器就可以使用该引用来修改流。为了将输出流引用传送到 Portlet,您需要创建自定义的 PortletResponse 类来扩展 PortletResponseWrapper 类并覆盖 getWriter(或 getOutputStream)和 toString 方法。getWriter 或 getOutputStream 方法返回编写者或输出流,而 toString 方法返回输出流的字符串表示。文件内容过滤器通过这种方式来创建 FileContentPortletResponse 类(清单 6)。
清单 6. 从 PortletResponseWrapper 类派生的自定义 PortletReponse 类
public class FileContentPortletResponse extends PortletResponseWrapper {
protected CharArrayWriter charWriter;
private ServletOutputStream stream;
protected PrintWriter writer;
public FileContentPortletResponse(PortletResponse resp) {
super(resp);
}
//Return an output stream that writes to a buffer
public ServletOutputStream getOutputStream() throws IOException {
//Can't call getOutputStream if getWriter has already been called
if (writer != null) {
throw new IllegalStateException("getWriter already called");
}
stream = super.getOutputStream();
return stream;
}
//Return a PrintWriter that writes to a buffer
public PrintWriter getWriter() throws IOException {
if (writer != null) {
return writer;
}
//Can't call getWriter if getOutputStream has already been called
if (stream != null) {
throw new IllegalStateException("getOutputStream already called");
}
charWriter = new CharArrayWriter();
writer = new PrintWriter(charWriter);
return writer;
}
public String toString() {
String str = null;
/**Only return a string if the writer was used.
*If the ServletOutputStream was used, it may not be encoded correctly
*/
if (writer != null) {
str = charWriter.toString();
}
return str;
}
}
|
在执行事件处理的同时必须注意捕获和修改响应;您不能在事件处理方法(doActionEvent 或 doMessageEvent 方法)中捕获和修改响应,因为该响应总是空值。请回顾 Portlet 编程,您或者在动作事件的 actionPerformed 方法中处理事件,或者在消息事件的 messageReceived 方法中处理事件;一旦事件处理结束,容器就将调用相同 Portlet 中的服务方法来呈现内容。因此,您应该在过滤器中捕获来自 doService 方法的响应,即使您用于请求的过滤逻辑是 doActionEvent 或 doMessageEvent 方法中的也一样。
调用或阻塞过滤器链
对 PortletFilterChain 对象中的 doFilter 方法的调用会激活过滤器链或目标 Portlet 中的下一个过滤器。如果需要修改请求和响应的话,传送到 doFilter 方法的参数就是 PortletRequest 和 PortletResponse 的自定义实现。目标 Portlet 将把自定义 PortletRequest 和 PortletResponse 对象而不是原始对象用于处理。如果需要阻塞请求(对于无效的请求来说的),就应该不调用 doFilter 方法,或者将具有空值的参数传送到 doFilter 方法,语法如下:
filterChain.doFilter(null, null)
如果请求被阻塞,过滤器就负责填充响应。如果这种情况发生在事件处理方法(doActionEvent 或 doMessageEvent)中,就应该用过滤器的 doService 方法填充响应。
清单 7 展示了如何用文件内容过滤器的 doService 捕获和修改响应。它基于请求是否包含无效的文件内容(这作为会话变量(InvalidItems,清单 4)设置在前面的 doActionEvent 方法中)来决定是应该转发还是应该阻塞请求。如果请求包含无效的文件内容,它就首先通过将空值作为参数传送到 doFilter 方法来阻塞请求,然后通过插入具有无效的文件名的错误消息来修改响应。如果请求是有效的,它就简单地将原始的请求传过整个过滤器链。
清单 7. doService 方法
public void doService(PortletRequest portletRequest, PortletResponse portletResponse,
PortletFilterChain filterChain) throws PortletException, IOException {
PortletSession session = portletRequest.getPortletSession();
//retrieve session variable to see if the request contains invalid files
Vector invalidItems = (Vector)session.getAttribute("InvalidItems");
//if the file content is valid, forward to request to next filter in the chain
if ((invalidItems==null)||(invalidItems.size()==0)) {
//Invoke filter chain using original request and response
filterChain.doFilter(portletRequest, portletResponse);
} else { //if the file content is not valid, print an error message with invalid file names
String errMsg = "<font color='red'>Error: invalid file content: ";
for (int i=0; i < invalidItems.size(); i++) {
errMsg += (String)invalidItems.elementAt(i)+"<p>";
}
errMsg += "</font>";
session.removeAttribute("InvalidItems");
//Buffer response stream
PortletResponse newPortletResponse = new FileContentPortletResponse(portletResponse);
//Invoke filter chain using custom response object
filterChain.doFilter(portletRequest, newPortletResponse);
//Modify the response
String response = newPortletResponse.toString();
String newResponse = errMsg + response;
//Output modified response using original writer
PrintWriter writer = portletResponse.getWriter();
writer.println(newResponse);
}
//Post processing
context.log("FileContentFilter: current time: " + df.format(new Date(System.currentTimeMillis())));
}
|
修改响应流
在被捕获之后,Portlet 响应可以通过它的字符串表示进行修改。您可以通过调用自定义 PortletResponse 类(您已经创建了它并且将其放在 doFilter 方法中传送到过滤器链或目标 Portlet)中的 toString 方法来将响应作为一个字符串加以捕获。toString 方法使用对前面的步骤中缓存的输出流的引用来返回 Portle 响应的输出流中的字符串表示(清单 6)。您照常可以操纵响应字符串,例如,插入、删除或转换字符串。
使用原始的编写器输出已修改的响应流
应该使用原始响应中的编写器而不是您创建的响应中的编写器来生成输出,语句如下:
PrintWriter writer = portletResponse.getWriter();
writer.println("modified response string");
过滤器中的事后处理
如果您愿意的话,可以编写一些过滤器的代码来做其他的工作,例如:
- 记录 Portlet 请求和响应。
- 记录 doFilter 方法启动和完成的时间戳。
- 通知管理员请求已被批准或拒绝、响应已被修改等等。
在 WebSphere Portal 中配置和安装 Portlet 过滤器
要将 Portlet 过滤器插入门户服务器中,请首先将相应的类文件复制到
PORTAL_HOME\shared\app ,向服务器注册该过滤器,然后安装每个目标 Portlet 的过滤器。对于本例中将要使用的FileContentFilter 您还需要从
Apache Jakarta Commons下载 FileUpload 组件,并且将 JAR 文件(在我们的示例中使用的是
commons-fileupload-1.0.jar )放在您的类路径中。重启门户服务器以重新下载类和特性文件。请查阅
WebSphere Portal Information Center for Version 5.0以获得过滤器配置的详细信息。
我们通过将文件内容过滤器的特性(清单 1)添加到 WebSphere Portal V5 的
PORTAL_HOME\shared\app\config\services 目录中的
PortletFilterService.properties 文件来添加该过滤器。您可以更改过滤器名,但请确保过滤器名和值是惟一的一对。
要将文件内容过滤器安装到 PDM Portlet,可以遵循下列步骤:
- 在 WebSphere Portal 中,选择
Administration => Portlets => Manage Portlets => Select Document Manager => Modify Parameters。
- 添加
FilterChain 和
FileContentFilter 作为名称/值对。
- 选择
Add,然后选择
Save。
要使用该文件内容过滤器查看 PDM Portlet 的结果,可以遵循下列步骤:
- 选择
My Portal => Documents。
- 选择
Update Document选项卡。
- 选择
Upload a new document,然后单击
Next。
- 定位到本地系统中所选的文件,然后单击
OK提交。
根据演示代码中我们的有效性检查规则,如果文件是有效的(
*.jar 、
*.jpg 、
*.gif 或
*.jpeg 文件类型),那么它就将被上传并且以 PDM Portlet 工作正常的形式列出(图 3)。如果该文件是无效的,那么它就会显示错误消息,并且不上传文件。
图 3. 使用带有文件内容过滤器的 PDM Portlet 上传有效的文件(EmailReader.jar)
图 4. 使用带有文件内容过滤器的 PDM Portlet 上传无效的文件(share.exe)
结束语
我们已经分析了 WebSphere Portal 中的一个最强大的 Portlet 特征。过滤器使门户开发人员和解决方案集成人员能够提供快速的解决方案来修改和自定义现有的Portlet,以便满足他们的门户的需要,他们采取的方式是修改 Portlet 请求和响应以及将新的功能添加到现有的 Portlet。我们开发一个文件内容过滤器来给现有的文件上传 Portlet 添加细粒度的控制机制。通过使用这些指导原则和作为示例的可下载源代码,开发人员应该能够应用这里提供的信息来创建他们自己的过滤器。
致谢
作者的经理 Edward Rozmiarek 给予了他极大的鼓励、支持,并且帮助审阅了本文,作者对此表示由衷的感谢。
下载 | 名字 | 大小 | 下载方法 |
|---|
| &origin=wsdd | 10 KB | HTTP |
参考资料
关于作者  | |  |
Shunguo Yan是一名助理软件工程师,在位于奥斯汀的 IBM 软件部的应用程序和集成中间件(Application and Integration Middleware,AIM)分部工作。他主要负责针对使用 J2EE、XML、Web 服务和 IBM 软件产品的各个行业设计和开发集成解决方案。
|
对本文的评价
|