内容


使用 Apache Pluto 构建和测试 JSR 168 兼容的 portlets

您的 portlets 与 Portlet 规范参考实现兼容吗?

Comments

2003 年 10 月,Java Community Process 发布了 Java Specification Request (JSR) 168的最终版本:Portlet Specification(参见 参考资料)。JSR 168 阐述的是第一个 Java portlet 开发的编程标准。以前,为 WebSphere Portal Server 开发的 portlet 不能在另外的 portlet 容器(例如 BEA WebLogic Portal)中运行。Portlet 容器不是 J2EE 应用服务器所必需的组件。然而,这种可移植性的缺少是与标准 J2EE 企业级应用程序相背离的,标准 J2EE 企业级应用程序(当根据规范构建时)可以部署到任何与 J2EE 兼容的应用服务器中。缺少 portlet 可移植性和相关厂商的锁定阻止企业购买门户服务器。通过结束 portlet 开发的混乱状态,JSR 168 平息了那些企业的担忧。

Java 开发人员具有一个可以自由获得的工具,用来测试他们编写的 portlets 是否与 Portlet 规范相一致。Apache Pluto 是 JSR 168 的参考实现,是实现了 Portlet API 的 portlet 容器。像 Pluto 和 IBM WebSphere Portal Server 这样的 portlet 容器充当 portlets 的运行时环境,与 web 应用服务器的 servlet 容器的运行时环境支持 servlet 的情形非常相似。但是,portlet 容器不是独立的,它存在于 servlet 容器的顶部并依赖于它的服务。在本文中,我们将为您演示如何编写简单的 portlet 并使用 Pluto portlet 容器测试它。

安装 Pluto

Apache Pluto 需要 Java SE 5。如果您还没有安装此版本的 JDK,下载并安装它以继续进行本文的练习(参见 参考资料)。

接下来,需要设置和更改 JAVA_HOME 环境变量,Pluto 引用它来查找 JVM(默认情况下,Sun 的安装程序不设置 JAVA_HOME 的值)。如果在系统上没有安装以前版本的 JVM,则执行以下步骤(这些说明假定您运行的是 windows):

  1. 右击我的电脑,并从上下文菜单选择属性
  2. 单击高级选项卡。
  3. 在窗口的底部,单击环境变量按钮。
  4. 系统变量窗格中,单击新建
  5. 为变量名输入 JAVA_HOME。为变量值输入 Sun JVM 的安装目录。

图 1 展示了选择把 Java 5.0 SDK 安装到 C:\Program Files\IBM\Java50 目录下时 JAVA_HOME 的值:

图 1. 更改 JAVA_HOME 环境变量
更改 JAVA_HOME 环境变量
更改 JAVA_HOME 环境变量

现在已经正确地设置了 Java 环境,就来从 Apache 的 Web 站点(参见 参考资料)下载包含 Pluto 的二进制内容的 ZIPet 文件(pluto-current.zip)。

验证下载文件

假定您和我们的安全意识一样强,就会希望保证下载的 ZIP 文件确实来自 Apache Software Foundation。文件使用 PGP 和 MD5 进行了数字签名。使用 GnuPG(参见 参考资料)和 ASCII armor 文件(pluto-current.zip.asc),可以验证此下载文件的真实性。

首先,通过输入清单 1 顶部一行所示的命令,把 Apache Pluto 的公共密钥添加到您的公共密钥环。在 Pluto 下载页面可以获得 KEYS 文件。

清单 1. 导入 KEYS 文件
C:\>gpg --import KEYS
gpg: key E41EDC7E: public key "Carsten Ziegeler <cziegeler@apache.org>" importe
gpg: key 320B4FB4: public key "Nick Lothian (Apache) <nlothian@apache.org>" imp
rted
gpg: key DD4200EA: public key "David H. DeWolf <david@daviddewolf.com>" importe
gpg: Total number processed: 3
gpg:               imported: 3
gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model
gpg: depth: 0  valid:   1  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 1u
gpg: next trustdb check due at 2011-03-08

接下来,输入清单 2 顶部一行所示的命令来验证您从 Apache 接收的已签名的 ZIP 文件:

清单 2. 验证 pluto-1.0.1.zip.asc
C:\>gpg --verify pluto-1.0.1.zip.asc
gpg: Signature made 10/19/05 09:11:31 using DSA key ID DD4200EA
gpg: Good signature from "David H. DeWolf <david@daviddewolf.com>"
gpg:               aka "David H. DeWolf <ddewolf@apache.org>"
gpg:               aka "David H. DeWolf <ddewolf@rocketmail.com>"
gpg:               aka "David H. DeWolf <ddewolf@gmail.com>"
gpg:               aka "David H. DeWolf <david.dewolf@digitalfocus.com>"
gpg: WARNING: This key is not certified with a trusted signature!
gpg:          There is no indication that the signature belongs to the owner.
Primary key fingerprint: 6AA5 5850 9A7B 275C E0BC  2851 B217 63FA DD42 00EA

如果 gpg --verify 命令生成的输出与清单 2 显示的相似,就可以确信 Pluto 归档文件确实来自 Apache。可以忽略表明签名不可信的警告。保证公共密钥签名来自所有者的惟一方法是,密钥的所有者亲自把磁盘上的密钥交给您。但是,采取这些步骤以后,可以在某种程度上确定从 Apache 下载的 ZIP 文件是不用怀疑的。(有关验证 Apache 下载文件的更多信息,请参见 参考资料 。)

解压 Pluto 下载文件

现在准备把 ZIP 文件解压到您的机器上。我们假定您把 Pluto 解压到 C:\ 盘的根目录上:

C:\>unzip pluto-current.zip

这就创建了一个包含 bin 子目录的 C:\pluto-1.0.1 目录。导航到 bin 子目录并在命令提示符下输入 startup.bat 以启动 Pluto 服务器。

如图 2 所示,您可以从 Pluto 主页管理和查看 portlets。启动 Web 浏览器并导航到 http://localhost:8080/pluto/portal。

图 2. Pluto 主页
Pluto 主页
Pluto 主页

注意:如果 Pluto 主页没有出现,请确保防火墙没有阻止 Pluto 接受连接。

现在已经启动和运行 Pluto,您将创建一个简单的 portlet,然后使用 Pluto 测试它的 JSR 168 兼容性。

开发 JSR 168 portlet

为了查看如何使用 Pluto 作为 portlet 的 JSR 168 兼容性测试平台,需要一个 portlet 来进行测试。在此练习中,您将创建一个简单的 portlet,它根据用户的输入,把文本框中的内容转换为全是大写字符或全是小写字符(见图 3):

图 3. Change Case portlet
Change Case portlet
Change Case portlet

我们从查看清单 3 所示的 portlet.xml 文件开始。portlet.xml 文件是包含关于 WAR 文件中捆绑的 portlet 的配置细节的描述符文件。

清单 3. portlet.xml 文件
<?xml version="1.0" encoding="UTF-8"?>
<portlet-app xmlns="http://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd" 
   version="1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
   xsi:schemaLocation="http://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd 
   http://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd" 
   id="com.ibm.changecase.ChangeCasePortlet">
   <portlet>
      <portlet-name>ChangeCase</portlet-name>
      <display-name>Change Case Portlet</display-name>
      <portlet-class>com.ibm.changecase.ChangeCasePortlet</portlet-class>
      <supports>
         <mime-type>text/html</mime-type>
         <portlet-mode>view</portlet-mode>
      </supports>
      <portlet-info>
         <title>ChangeCasePortlet</title>
      </portlet-info>
   </portlet>
</portlet-app>

清单 3 中的 <portlet-app> 标记定义了 XML 模式定义和 portlet 应用程序 的 ID。一个 portlet 应用程序可以包含零个或多个 portlet。使用 <portlet> 标记来定义 portlet 应用程序中的单个 portlet:

  • <portlet-name> —— 提供一个名称,在内部或由程序使用该名称来引用 portlet。
  • <display-name> —— portlet 的缩写名,用来在 GUI 工具中显示 portlet 名称,它随 portlet 容器的不同而不同。
  • <portlet-class> —— 充当 portlet 控制器的类。
  • <supports> —— 这些标记定义 portlet 支持的 portlet 模式和 mime 类型。
  • <title> —— 可以在 portlet.xml 中定义 portlet 的首选标题。但是,如何使用该标题取决于 portlet 容器。

清单 4 显示的是 portlet.xml 中引用的 com.ibm.changecase.ChangeCasePortlet portlet 类。此类必须实现 javax.portlet.Portlet 接口,但幸运的是,您不必直接实现 Portlet 接口。JSR 168 为 javax.portlet.Portlet 接口定义了一个称为 javax.portlet.GenericPortlet 类的默认实现。com.ibm.changecase.ChangeCasePortlet 类继承 GenericPortlet 类。

清单 4. ChangeCasePortlet 类
package com.ibm.changecase;
import java.io.*;
import javax.portlet.*;
/**
 *
 * A sample portlet based on GenericPortlet
 * 
 */
public class ChangeCasePortlet extends GenericPortlet {
   
   private static String VIEW_JSP = "/view.jsp";
   protected void doView(RenderRequest request, RenderResponse response) 
      throws PortletException, IOException {
      response.setContentType(request.getResponseContentType());
       PortletContext context = getPortletConfig().getPortletContext();
       context.getRequestDispatcher(VIEW_JSP).include(request, response);
   }
public void processAction(ActionRequest request, ActionResponse response) 
   throws PortletException, java.io.IOException {
//Do Action Handling here.
}
}

注意重写 doView()processAction() 方法的选择。每当出现 portlet 操作时都会调用 processAction() 方法。当用户处于 portlet 的视图模式时调用 doView() 方法。JSR 168 支持其他的模式,例如帮助模式和编辑模式。但是,如果回过头看一下 清单 3,在 <supports> 部分可以看到 portlet 只支持视图模式。

现在仔细地看一下清单 4 中的 doView() 方法。与 Java servlet 编码中一样,portlet 编码中也经常使用模型-视图-控制器 (MVC) 设计模式。因此,在 portlet 中,把表示的职责转交给了 view.jsp。或者,也可以使用 prinltn 逻辑在 doView() 方法中实现视图。

response.getWriter().println("<p>Hello World</p>()");

这种方法的问题是,图形用户界面的设计者需要具有 portlet 技术的知识来编写 doView() 方法。JSP 开发人员从复杂的 Java 开发中解放出来,能够集中精力开发前端界面。清单 5 显示的是 view.jsp:

清单 5. view.jsp
<%@ taglib uri="http://java.sun.com/portlet" prefix="portlet"%>
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
	pageEncoding="ISO-8859-1" session="false"%>
<portlet:defineObjects />
<%String textBox = renderRequest.getParameter("textBox");
		if (textBox == null)
			textBox = "";
		String caseString = renderRequest.getParameter("case");
		boolean isUpperCase = true;
		if ((caseString != null) && (caseString.equals("false"))) {
			isUpperCase = false;
		}
		String errorMessage = renderRequest.getParameter("errorMessage");%>
<%if (errorMessage != null) {%>
<p><%=errorMessage%></p>
<%}%>
<FORM name="<portlet:namespace/>caseform" action="<portlet:actionURL/>">
<INPUT type="text" name="textBox" size="20" value="<%=textBox%>">
<p><INPUT type="radio" name="case" value="lowercase"
	<%if (!isUpperCase) {%> checked="checked" <%}%>>To Lowercase</p>
<p><INPUT type="radio" name="case" value="uppercase"
	<%if (isUpperCase) {%> checked="checked" <%}%>>To Uppercase</p>
<INPUT type="submit" name="<portlet:namespace/>submitCase"
	value="Change Case"></FORM>

首先,注意在 view.jsp 中定义 portlet 标记库。这是 JSP 解析器识别 portlet 标记所必需的。您使用的第一个 portlet 标记是 <portlet:defineObjects/>。此标记允许访问 renderRequestrenderResponseportletConfig 对象。使用 renderRequest 对象使您具有访问 requestParameters 的权利。portlet 类通过 request 参数向 view JSP 传递值。

接下来,在 view.jsp 中创建一个向 portlet 类发送表单数据的表单。为了发送表单数据,必须创建一个 actionURL,它使 ChangeCasePortlet portlet 类的 processAction() 方法被调用。使用 <portlet:actionURL/> 标记创建 actionURL。 注意,在 view.jsp 中将文本框和单选按钮的值设置为服务器传回 JSP 的值。因此,view.jsp 负责处理请求输入和显示 portlet 的响应。

单击表单的 Submit 按钮会调用 portlet 的 processAction() 方法,如清单 6 所示。processAction() 从 view.jsp 接收一个 ActionRequest 对象作为输入。

清单 6. processAction 方法
 public void processAction(ActionRequest request, ActionResponse response) 
    throws PortletException, java.io.IOException {
    String newCase = request.getParameter("case");
    String textBox = request.getParameter("textBox");
    String errorMessage = null;
      
    boolean isUpperCase = true;
    if ((newCase!=null) && (newCase.equals(ChangeCaseConstants.LOWER_CASE)))
       isUpperCase = false;
    else if ((newCase == null) || (newCase==ChangeCaseConstants.UPPER_CASE))
       errorMessage = "Error no case selected!  Select a case.";
    if (textBox !=null) {
       if (isUpperCase)
          textBox = textBox.toUpperCase();
       else 
          textBox = textBox.toLowerCase();
       response.setRenderParameter("textBox", textBox);
    } else 
       errorMessage = "Error, text in the text box is invalid";
    response.setRenderParameter("case", Boolean.toString(isUpperCase));
    if (errorMessage != null) {
       response.setRenderParameter("errorMessage",errorMessage);
    }
   
 }

ActionRequest 对象包含输入到表单中的数据。为了检索表单数据,可使用 getParameter() 方法。在 processAction() 方法中,也要执行业务逻辑,确定用户是想要大写形式还是小写形式的输出。根据该逻辑,把输入的文本转换成想要的大小写形式并发送给用户。使用 setRenderParameter() 方法把数据发送给视图。

编译并打包 JSR 168 portlet

现在已经开发了 portlet,需要把它转换成已编译的形式,并为了部署到 Pluto 将它打包。首先,确保 portlet-api-1.0.jar 在 CLASSPATH 中。然后使用 javac 编译器编译 ChangeCaseConstants.java 和 ChangeCasePortlet.java:

javac ChangeCaseConstants.java
javac ChangeCasePortlet.java

接着需要为 WAR 文件创建所需要的文件夹结构,WAR 文件是归档文件,通过它把 portlet 部署到 portlet 容器。把刚才编译的两个类放在 classes\com\ibm\changecase 目录中。

为了构建 WAR 文件,需要以下的目录结构,如清单 7 所示:

清单 7. 用于部署的目录结构
changeCaseWAR\
   META-INF
      MANIFEST.MF
   WEB-INF
      classes
         com
            ibm
               changecase
                  ChangeCaseConstants.class
                  ChangeCasePortlet.class
      lib
      tld
         portlet.tld
      portlet.xml
      web.xml
   index.html
   view.jsp

注意,清单 7 引入了 4 个我们还没有讨论过的文件:

  • portlet.tld:这是 portlet 标记库。如果回想一下,您曾经用 <portlet:defineObjects /> 之类的引用在整个 JSP 中都用到过 portlet 标记库。portlet 标记库在 Apache-SVN 代码库中可以得到(参见 参考资料)。
  • MANIFEST.MF:因为这里不存在外部依赖,所以这个清单文件除了 Manifest-Version: 1.0 之外什么也不包含。
  • index.html:如果由于某些原因上下文根被直接访问,则会显示 index.html。在 index.html 中可以有任何正确格式的 HTML。
  • web.xml(如清单 8 所示):它定义包含单个 portlet 的 Web 应用程序。
清单 8. 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 id="WebApp_ID">
   <display-name>CasePortlet</display-name>
   <welcome-file-list>
      <welcome-file>index.html</welcome-file>
   </welcome-file-list>
   <taglib>
      <taglib-uri>http://java.sun.com/portlet</taglib-uri>
      <taglib-location>tld/portlet.tld</taglib-location>
   </taglib>
   <taglib id="PortletTLD">
      <taglib-uri>http://java.sun.com/portlet</taglib-uri>
      <taglib-location>/WEB-INF/tld/std-portlet.tld</taglib-location>
   </taglib>
</web-app>

一旦生成了 MANIFEST.MF、portlet.tld、web.xml 和 index.html,就可以使用 JAR 实用工具将清单 7 所示的结构归档到一个 WAR 文件中:

C:\temp>jar -cvf changeCase.war

结果是一个叫做 changeCase.war 文件,用于部署到 JSR 168 兼容的门户。

确保 portlet 是 JSR 168 兼容的

现在使用 Apache Pluto 来查明 portlet 是否能通过 JSR168 兼容性的最终测试。

确保 Apache Pluto 启动并运行,然后导航到 Pluto 主页(http://localhost:8080/pluto/portal)。单击侧栏上的 Admin。应该看到 Deploy War portlet 显示在管理页面上,如图 4 所示。单击 Browse 并导航到在前一节中放置在一起的 changeCase.war 文件的位置,然后单击 Submit

图 4. Pluto 的 Deploy War portlet
Pluto 的 Deploy War portlet
Pluto 的 Deploy War portlet

现在必须为 portlet 应用程序输入布局信息 —— 一个非常不重要的任务,因为在 portlet 应用程序中只有一个 portlet。正如在图 5 中可以看到的,您告知 Pluto portlet 有一行和一列,然后单击 Submit 向服务器提交此选择:

图 5.为 portlet 应用程序输入页面布局信息
为 portlet 应用程序输入页面布局信息
为 portlet 应用程序输入页面布局信息

接下来,必须定义 portlet 出现在 portlet 应用程序页面布局的何处。通过把 portlet 应用程序中的所有 portlet 映射到刚才定义的行和列来完成此任务。因为只有一个 portlet 需要部署并且您先前选择了一行和一列的页面布局,所以在该位置输入 ChangeCase portlet 并单击 Submit,如图 6 所示:

图 6. 映射 portlet 的位置
映射 portlet 的位置
映射 portlet 的位置

为了部署 portlet,您可以选择重新启动 Pluto 或者热部署 包含 portlet 的 portlet 应用程序,如图 7 所示:

图 7. 热部署 portlet
热部署 portlet
热部署 portlet

一个具有 portlet 应用程序名称(Change Case)的链接出现在侧栏。单击该超链接,现在将看到 portlet 应用程序,其中包含 portlet,如图 8 所示。这时应该与 Change Case portlet 交互并确保它的功能与预期的一样。如果与预期的一样,您就可以确定此 portlet 与 JSR 168 兼容。

图 8. 部署到 Pluto 的 Change Case portlet
部署到 Pluto 的 Change Case portlet
部署到 Pluto 的 Change Case portlet

Change Case Portlet 将能够运行在任何支持 JSR 168 portlet 标准的 Portlet 容器中。

结束语

Apache Pluto 使 portlet 开发人员能够确定他们的 portlet 能够运行在任何 JSR 168 兼容的 portlet 容器中。大多数 portlet 容器(包括 WebSphere Portal Server)都包括 Portlet 规范未提及的扩展。例如,IBM WebSphere Portal Server 提供非标准的一点即动(click-to-action)扩展(即协作 portlet)。是否使用扩展由使用 portal 技术的企业来决定。作为开发人员,您应该牢记的是支持这种技术会损害 portlet 的可移植性 —— 但是有时这么做能满足业务需要。Apache Pluto 使那些使用 portlet 的组织能够知道他们偏离了 Portlet 规范有多远,并决定是否采取一些措施来调整那些 portlet 符合标准。

展望未来:JSR 286

JSR 168 在走向使 portal 领域有序的过程经过了很长的路。但是,portlet 的标准化工作并没有停止。当写作本文的时候,Java Portlet API version 2.0 规范 (JSR 286) 仍旧在开发中,打算把对 J2EE 1.4 的支持引入到 portlet 规范中(参见 参考资料)。许多曾由供应商利用其自有实现通过非标准方式处理的 Portlet 技术(提一下其中的两个,portlet 过滤器和形式化的 portlet 间通信)现将采纳 2.0 规范。Apache Pluto 的将来版本将充当 JSR 286 规范的参考实现。当 JSR 286 成为事实上的 portlet 标准时,您将仍旧能够使用 Pluto 测试兼容性。

在我们写作本文时,还不能得到 Pluto 1.1 的 alpha 版本。Pluto 1.1 将具有一个新的容器架构,并包含一些使 portlet 开发更容易的更改。但是,Pluto 1.0.1 仍然是一个很好的用于验证 portlet 是否 JSR 168 兼容的工具。


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Java technology, WebSphere
ArticleID=112368
ArticleTitle=使用 Apache Pluto 构建和测试 JSR 168 兼容的 portlets
publish-date=05182006