级别: 初级 Michael Wanderski (mwanders@us.ibm.com), IBM Pervasive Computing Development, Raleigh,North Carolina
2003 年 12 月 01 日 本文介绍了自定义 JSP 标记库开发,JSP 标记库作为一种使用 WebSphere Portal V4.1 创建可视控件的工具,可以用于 portlet 开发。
©IBM版权所有,2003年。保留所有权利。
引言
通过内容聚合和个性化,IBM®WebSphere®Portal V4允许您通过固定且易于定位的接口来公开 Web内容和应用程序。用户可以根据各种标准(比如个人兴趣、公司应用程序的访问权限、部门职位等等)有选择地把应用程序添加到他们基于Portal 的桌面。
一般来说,可以用来查看 Portal的浏览器非常丰富,比如 Microsoft®Internet Explorer(IE)或 Netscape Navigator®。这些客户机允许用户同时查看由许多基于Web 的技术组成的应用程序,比如 HTML 内容、Java™applet 和 Macromedia Flash™。这些应用程序通过称为Portlet 的 Portal 应用程序公开,Portal应用程序允许通过程序化的接口和基于 Portal的服务将这些基于 Web 的技术集成到整个 Portal容器框架中。一个样本 IBM WebSphere Portal 屏幕示于图1中。
图1. 样本 WebSphere Portal 服务器
在本文中,我们将介绍自定义 JSP标记库开发,JSP标记库作为一种创建可视控件的工具,可以用于 portlet开发。
本文假定您至少有一点 IBM WebSphere Portal编程经验,并且具备基本的 JSP和标记库编程知识。我们将通过一个示例来简要地回顾Portlet 编程,然后基于示例展示集成标记库技术的好处。
Portlet 开发回顾
让我们首先从回顾一个普通的 Portlet例子开始,这个例子展示了 Portlet 开发的基本概念。清单1中的Portlet 简单地展示了消息“hello world”,但是在后面的部分中,我们将对其进行扩展以加入自定义 JSP标记。如果您不熟悉 Portlet API 或 Portlet安装和开发,可以查阅 WebSphere Portal InfoCenter中的文档(请参见
参考资料)来获得完整的关于 API的信息。
清单1. GenericPortletHtmlController.java
package com.ibm.wps.portlets.GenericPortlet;
import java.io.*;
import com.ibm.wps.portlets.*;
import org.apache.jetspeed.portlet.*;
public class GenericPortletHtmlController extends AbstractMVCController {
protected static final String JSP_Directory = "/WEB-INF/GenericPortlet/";
protected static final String JSP_View = "/GenericPortletView.jsp";
protected PortletContext context = null;
public void init(PortletConfig config) throws UnavailableException {
super.init(config);
context = config.getContext();
}
public void doView(PortletRequest request, PortletResponse response)
throws PortletException, IOException {
context.include(
JSP_Directory + "html" + JSP_View, request, response);
}
}
|
我们首先创建一个简单的 Portlet控制器类,它扩展 AbstractMVCController类。在这个简单的例子中,控制器需要完成的惟一工具就是调用 JSP来提交 Portlet 视图,Portlet 视图驻留在
\WEB-INF\GenericPortlet\html 目录下的 portlet WAR 中。
清单2. GenericPortletView.jsp
<%@ page language="java" contentType="text/html;charset=utf-8" %>
<%@ taglib uri="/WEB-INF/tld/portlet.tld" prefix="portlet" %>
<portlet:init/>
Hello World
|
然后,我们使用清单2中的 JSP创建一个简单的 Portlet 视图来显示消息“Hello World”。在随后的部分中,我们将使用这个 portlet来集成自定义标记库控件。
使用自定义标记库创建可视的 Portlet 服务
为了创建可视的 Portlet服务,让我们把幻灯片(您可以定位的一系列图片)的概念设想成一个控件,这对于许多Portlet开发人员都是非常有用的。保持这个例子尽可能的简单,让我们假定幻灯片控件是直截了当的,并且可以看作是输入一组指向图像的URL。该控件将有两个按钮,Next 和 Previous,用于移动幻灯片中的图像。图2展示了当幻灯片控件指向IBM徽标时的外观。对于剩余的讨论,我们将假定您熟悉自定义标记库编程,所以我们正好可以跳到标记开发。如果您想了解关于自定义标记库编程更进一步的信息,请参阅
参考资料部分。
图2. 普通的 Portlet JSP 视图
- 首先需要做的事情是创建标记库描述符(tag library descriptor,TLD)文件,它将定义用来构造Portlet JSP 中幻灯片控件的实例的接口(清单3)。
清单3. 标记库描述符文件(mytags.tld)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN" "http://java.sun.com/j2ee/dtds/web-jsptaglibrary_1_1.dtd">
<taglib>
<tlibversion>1.0</tlibversion>
<jspversion>1.1</jspversion>
<shortname>MyTags</shortname>
<uri></uri>
<info>Custom tags used for the Portal</info>
<tag>
<name>addControl</name>
<tagclass>com.ibm.wps.tags.AddControl</tagclass>
<bodycontent>JSP</bodycontent>
<info>Create an instance of the Control</info>
<attribute>
<name>name</name>
<required>yes</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>jsp</name>
<required>yes</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>jspContext</name>
<required>no</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
<tag>
<name>addParam</name>
<tagclass>com.ibm.wps.tags.AddParam</tagclass>
<bodycontent>JSP</bodycontent>
<info>Add a parameter to the Control</info>
<attribute>
<name>param</name>
<required>yes</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
</taglib>
|
- 在此文件(称为
mytags.tld )中,我们定义了两个协作的标记。第一个标记
<addControl> 用于构造幻灯片控件的实例并且可看作是输入三个如下表所述的属性:
|
属性名称
|
描述
| | Name | 用于为控件提供全局惟一的名称。这是特别重要的,因为可以把控件的多个实例插入相同的页面,它是典型的Portal 服务器环境。 | | jsp | 正如后面将要展示的,实际上是通过JSP来提交控件。这使得所有的标记库代码可重用于您可能开发的各种类型的控件。控件可视的方面的提交由动态分配的 JSP决定。 | | jspContext | JSP上下文允许以参数的形式传送执行提交的 JSP的位置。这允许控件在许多环境中运行(例如Portal 服务器、独立 EAR 等等)。 |
- 第二个标记称为
<addParam> ,它允许传送可变数目的参数到控件中。我们将利用这种能力来传送幻灯片控件的图像URL 列表。这两个标记可以一起使用,如清单4中的例子所示:
清单4. 标记用法示例
<mytags:addControl name="MyControl" jsp="control.jsp" jspContext="/">
<mytags:addParam param="http://www.yourco.com/image1.gif/>
<mytags:addParam param="http://www.yourco.com/image2.gif/>
<mytags:addParam param="http://www.yourco.com/image3.gif/>
</mytags:addControl>
|
- 我们现在定义
<addControl> 标记的实现:
清单5. AddControl.java
package com.ibm.wps.tags;
import java.util.*;
import javax.servlet.*;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
public class AddControl extends BodyTagSupport {
private String name = null;
private String jsp = null;
private String jspContext = "/";
private Control aControl = null;
public void setName(String name) { this.name = name; }
public String getName() { return name; }
public void setJsp(String jsp) { this.jsp = jsp; }
public String getJsp() { return jsp; }
public void setJspContext(String jspContext) { this.jspContext = jspContext; }
public String getJspContext() { return jspContext; }
Control getControl() {
return aControl;
}
public int doStartTag() throws JspException {
pageContext.setAttribute("com_ibm_wps_control", name,
PageContext.REQUEST_SCOPE);
aControl = new Control(name);
pageContext.setAttribute("com_ibm_wps_control_" + name,
aControl, PageContext.REQUEST_SCOPE);
try { pageContext.getOut().flush(); }
catch (Exception e) { throw new JspException(); }
return EVAL_BODY_TAG;
}
public int doEndTag() {
ServletContext servletContext = pageContext.getServletContext();
servletContext = servletContext.getContext(jspContext);
RequestDispatcher dispatcher =
servletContext.getRequestDispatcher("/" + jsp);
if (dispatcher != null) {
try {
dispatcher.include(pageContext.getRequest(),
pageContext.getResponse());
}
catch (Throwable t) {
}}
return EVAL_BODY_INCLUDE;
}
}
|
AddControl 扩展了 BodyTagSupport类,因为它将与该标记的 Body内容进行交互,更具体地说,就是获取任何使用
<addParam> 标记指定了的参数。然后,该类为这三个属性(即name、jsp、jspContext)中的每一个定义 getter 和 setter方法。另外,注意到有一个控件(Control)类的声明。这个类将用于存放关于控件(例如图像URL)的所有信息,并且被传送到 JSP 以进行提交。
在 doStartTag方法中,创建了新的控件(Control)实例,并且将其与请求范围内页面上下文中的属性相关联。这样就使得可以把控件(Control)作为请求属性传送到 JSP。然后返回
EVAL_BODY_TAG 常量,指示应该评估标记的 Body 内容。
在评估了 Body 内容之后,doEndTag方法就准备把控件传送到在标记的属性中指定的 JSP。为此,可以通过 jspContext属性将 Servlet上下文转换成定位资源的上下文。然后,使用指定的 JSP属性创建请求分配器(dispatcher),并且请求分配器包括 JSP来提交控件。
- 我们现在定义下面的
<addParam> 标记的实现:
清单6. AddParam.java
package com.ibm.wps.tags;
import java.util.*;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
public class AddParam extends TagSupport {
private String param = null;
public String getParam() {
return param;
}
public void setParam(String param) {
this.param = param;
}
public int doStartTag() throws JspException {
AddControl ancestorTag =
(AddControl)findAncestorWithClass(this, AddControl.class);
Vector params = (Vector)ancestorTag.getControl().getParams();
if (params != null) params.add(param);
return EVAL_BODY_INCLUDE;
}
public int doEndTag() {
return EVAL_PAGE;
}
}
|
AddParam 类库扩展了 TagSupport类,并且为 param 属性定义了 getter 和 setter 方法。doStartTag方法只有一个目的,它必须记录在父标记中指定的参数。为此,它请求祖先标记addControl 提供 Control对象的实例,然后将参数添加到实例中。
- 我们现在定义控件(Control)对象,它用来包含所有与控件有关的信息,将把控件传送到提交控件的 JSP中。
清单7. Control.java
package com.ibm.wps.tags;
import java.util.*;
public class Control {
private String name = null;
private Hashtable attributes = new Hashtable();
private Vector params = new Vector();
public Control(String name) {
this.name = name;
}
public String getName() {
return name;
}
public Vector getParams() {
return params;
}
public void setAttribute(String key, Object val) {
attributes.put(key, val);
}
public Object getAttribute(String key) {
return attributes.get(key);
}
}
|
控件(Control)是通过全局惟一的名称构造的,它包含一个名为
params 的向量,这个向量可以用于存放使用
<addParam> 标记的任何参数集。此外,我们还定义了一个属性哈希表来存放要传送到提交JSP的资源。(在接下去的一部分中,此哈希表的优点将变得很明显。)
现在请注意,到此为止,我们留心了使所有的实现细节非常的普通,这样标记代码就可重用于任何您可能开发的控件。通过创建新的JSP来提交控件,然后传送此控件和任何相关的参数,您就可以非常轻松地开发您自己的控件。
- 现在,在清单8中,我们将定义用于提交幻灯片控件的 JSP。
清单8. control.jsp
<%@ page buffer="none" autoFlush="true" %>
<%@ page import="java.util.*" %>
<%@ page import="com.ibm.wps.tags.*" %>
<jsp:useBean id="com_ibm_wps_control" class="String" scope="request"/>
<% Control aControl =
(Control)request.getAttribute("com_ibm_wps_control_" + com_ibm_wps_control); %>
<% String aName = aControl.getName(); %>
<script language="javascript">
<% String main = ""; %>
<%= aName %>_images = new Array;
<% int i = 1; %>
<% for (Enumeration en = aControl.getParams().elements();
en.hasMoreElements(); i++) { %>
<% String url = (String)en.nextElement(); %>
<% if (i == 1) main = url; %>
<%= aName %>_images[<%= i %>] = new Image;
<%= aName %>_images[<%= i %>].src = "<%= url %>";
<% } %>
<%= aName %>_current = 1;
<%= aName %>_max = <%= i - 1 %>
function <%= aName %>_prev() {
if (<%= aName %>_current > 1) {
<%= aName %>_current = <%= aName %>_current - 1;
}
document.getElementById("<%= aName %>_picture").src =
<%= aName %>_images[<%= aName %>_current].src;
}
function <%= aName %>_next() {
if (<%= aName %>_current < <%= aName %>_max) {
<%= aName %>_current = <%= aName %>_current + 1;
}
document.getElementById("<%= aName %>_picture").src =
<%= aName %>_images[<%= aName %>_current].src;
}
</script>
<table border="0" cellspacing="2" cellpadding="2">
<tr>
<td style="text-align:center">
<a href="javascript:<%= aName %>_prev()">Prev</a>
</td>
<td style="text-align:center">
<a href="javascript:<%= aName %>_next()">Next</a>
</td>
</tr>
<tr>
<td colspan="2">
<img id="<%= aName %>_picture" src="<%= main %>">
</td>
</tr>
</table>
|
该 JSP 首先获得控件(Control)类的实例,然后使用getName 方法获取控件的名称。我们使用此 JSP变量来定义任何 JavaScript函数、变量等等是非常重要的,因为这个特殊控件可能只是页面上许多幻灯片控件之一。
该 JSP然后定义与幻灯片控件相关联的 JavaScript,它简单地定义了在选择Next 和 Previous按钮时替换图像所必需的脚本。通过遍历由调用控件(Control)对象上的 getParams方法提供的所有 URL来执行此函数。从效率的角度考虑,可以将此 JavaScript定义在单独的可缓存的
script.js 文件中,并且把函数修改为用编辑器实例的名称进行传送。为了简单起见,我们将所有的都包括在这一个JSP 中。
最后,该 JSP 通过
<table> 标记与 Next 和 Previous 按钮的
<table> 标记以及提交图片的
<img> 标记的组合来提交控件。
由于使用了
<%=aName%> Scriptlet 来有效地限定控件的范围,所以该 JSP是难以解释的。因此,下面使用控件名 MyControl来展示提交的 JSP 内容的样本。
清单9. control.jsp 的样本输出
<script language="javascript">
MyControl_images = new Array;
MyControl_images[1] = new Image; MyControl_images[1].src = ?mage1.gif_
MyControl_images[2] = new Image; MyControl_images[2].src = ?mage2.gif";
MyControl_images[3] = new Image; MyControl_images[3].src = ?mage3.gif";
MyControl_current = 1;
MyControl_max = 3
function MyControl_prev() {
if (MyControl_current > 1) {
MyControl_current = MyControl_current - 1;
}
document.getElementById("MyControl_picture").src =
MyControl_images[MyControl_current].src;
}
function MyControl_next() {
if (MyControl_current < MyControl_max) {
MyControl_current = MyControl_current + 1;
}
document.getElementById("MyControl_picture").src =
MyControl_images[MyControl_current].src;
}
</script>
<table border="0" cellspacing="2" cellpadding="2">
<tr>
<td style="text-align:center">
<a href="javascript:MyControl_prev()">Prev</a>
</td>
<td style="text-align:center">
<a href="javascript:MyControl_next()">Next</a>
</td>
</tr>
<tr>
<td colspan="2">
<img id="MyControl_picture" src="image1.gif">
</td>
</tr>
</table>
|
为了使幻灯片控件 JSP标记可用于运行在 Portal 服务器中的所有 Portlet,需要在Portal服务器应用程序上安装标记库组件。可以把所有的类文件放在 JAR文件中,然后置于
<AppServer>\lib\app 目录之下。可以把
mytags.tld 文件放在
<AppServer>\lib\app\WEB-INF\tld 目录中,并且可以把
control.jsp 文件放在目录
<PortalServer>\app\wps.ear\wps.war 下的 Portal服务器的上下文根中。在更改生效之前必须重新启动Portal 服务器。
- 返回到我们在前面创建的简单 GenericPortlet,我们可以修改
GenericPortletView.jsp 来利用带有下列更改的标记库:
清单10. GenericPortletView.jsp
<%@ page language="java" contentType="text/html;charset=utf-8" %>
<%@ taglib uri="/WEB-INF/tld/portlet.tld" prefix="portlet" %>
<%@ taglib uri="/WEB-INF/tld/engine.tld" prefix="portal" %>
<%@ taglib uri="/WEB-INF/tld/mytags.tld" prefix="mytags" %>
<portlet:init/>
<portal:constants/>
<mytags:addControl
name="MyControl"
jsp="control.jsp"
jspContext="<%= wpsBaseURL %>"
>
<mytags:addParam param="http://www.ibm.com/i/v11/m/en/lanim.gif"/>
<mytags:addParam param="http://www.lotus.com/art/wtlotswlogo104.gif"/>
<mytags:addParam param="http://www.google.com/images/logo.gif"/>
</mytags:addControl>
|
注意到我们使用了 Portal服务器提供的
engine.tld 标记库来获得Portal 服务器的上下文根 URL(即 wpsBaseURL 变量),
control.jsp 驻留在其中。该 Portlet视图现在看起来像下面的样子:
图3. 使用幻灯片控件的 Portlet
注意到该幻灯片控件并没有使用 Portal 服务器的样式。这把我们带入到了下一个主题。
样式和主题支持
Portal 服务器应用主题的概念来确保跨所有 Portlet可视的一致性。这是使用 Portlet必须在它们的内容中提供的一组通用的 CSS 类属性实现的。如前所述,我们已经创建的控件可以用在 Portlet服务器中,也可以用在独立的应用程序中。您可能想要在当前的版本4 Portlet服务器以及即将发行的版本上使用此控件。这些环境中的每一个都将有它们自己的样式单,因而定义了它们自己的类属性。因此,我们需要一个这样的解决方案,它不把类属性名直接硬编码成提交控件的
control.jsp 。
为了达此目的,我们将提供一个间接层;而不是直接将 CSS类属性插入
control.jsp 标记中,我们将使用特性文件来查找更普通的类属性定义的值,然后将在特性文件中指定的名称插入
control.jsp 。因此,每种环境都可以提供它自己的特性文件,这将进行从普通的类属性定义到应用程序的样式单所期望的特定类属性定义的映射。
WebSphere Portal V4 的样本特性文件如下所示:
清单11. com.ibm.wps.tags.PortletStyles.properties
link=wpsToolBarLink
background=wpsToolBar
|
这里,我们定义了两个抽象的 CSS 定义,
link 用于 Next 和 Previous 按钮的样式,而
background 用于控件的背景颜色。对于 Portal服务器环境,我们把这些属性映射到定义在该 Portal的样式单中的
wpsToolBarLink 和
wpsToolBar 属性。
为了使样式特性文件成为动态的,我们需要进行下列操作:
- 我们将把新的属性添加到
<addControl> 标记中,如下所示:
清单12. mytags.tld -- AddControl
<attribute>
<name>style</name>
<required>yes</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
|
- 然后,我们把 getter 和 setter 添加到 AddControl 类中,如清单13中所示:
清单13. AddControl.java
public class AddControl extends BodyTagSupport {
_BR> private String style = null;
_BR>
public void setStyle(String style) { this.style = style; }
public String getStyle() { return style; }
_BR>
public int doStartTag() throws JspException {
_
aControl.setAttribute("styles", new StyleHelper(style));
_BR> }
}
|
控件(Control)类并不需要更改,因为我们刚使用属性的普通概念来把样式类附到控件(Control)上,这样它就可以传送到 JSP 以进行提交。
- 为了帮助载入样式属性名,我们创建了一个新的名为 StyleHelper 的类,其定义如下:
清单14. StyleHelper.java
package com.ibm.wps.tags;
import java.util.*;
public class StyleHelper {
private ResourceBundle map = null;
public StyleHelper(String prop) {
super();
try {
map = ResourceBundle.getBundle(prop);
}
catch (Exception e) {
map = null;
}
}
public String getClass(String name) {
StringBuffer sb = new StringBuffer();
sb.append("class=\"").append(getString(name)).append("\"");
return sb.toString();
}
private String getString(String resource) {
String className = null;
try {
if (map != null) {
className = map.getString(resource);
}}
catch (MissingResourceException e) {
className = null;
}
return className == null ? "" : className;
}
}
|
这个新的类简单地载入了特定的特性文件,然后使用 getClass 方法返回要插入 JSP 标记的类字符串。例如,使用前面指定的特性文件调用以参数的形式传送
link 的方法,会返回:
class='wpsToolBarLink' 。
- 现在,将
control.jsp 修改为使用 StyleHelper 类,如下所示:
清单15. control.jsp
_BR> <% StyleHelper styles = (StyleHelper)aControl.getAttribute("styles"); %>
_BR>
<table
<%= styles.getClass("background") %>border="0" cellspacing="2" cellpadding="2">
<tr>
<td style="text-align:center">
<a
<%= styles.getClass("link") %>
href="javascript:<%= aName %>_prev()">Prev</a>
</td>
<td style="text-align:center">
<a
<%= styles.getClass("link") %>
href="javascript:<%= aName %>_next()">Next</a>
</td>
</tr>
<tr>
<td colspan="2">
<img id="<%= aName %>_picture" src="<%= main %>">
</td>
</tr>
</table>
|
- 最后,我们更新 Portlet JSP 来使用新的标记属性。
清单16. GenericPortletView.jsp
_BR> <mytags:addControl
name="MyControl"
jsp="control.jsp"
jspContext="<%= wpsBaseURL %>"
style="com.ibm.wps.tags.PortletStyles"
>
_BR>
|
幻灯片控件现在继承了 Portal服务器中的主题。
图4. 使用幻灯片控件的 Portlet
结束语
本文论及您可以使用自定义标记库创建的丰富的控件的类型,您可以使其可用于所有的 Portlet开发人员。我们已经从头到尾完成了示例,并且使用您可以自定义的模板创建了您自己的控件。您将发现可以把其他的特征(比如自然语言支持)添加到示例中,所采用的方式与添加样式支持的方式类似。希望这些基本概念将有助于您开始创建通用的控件,您可以很容易且成功地把它们加入您自己的开发项目。
参考资料
关于作者  | |  |
Michael Wanderski
是一名咨询软件工程师,工作在 IBM 的 Pervasive Computing Division
的 Research Triangle Park Lab。他目前正在从事 WebSphere Portal
Server 和相关产品的研究。您可以通过
mwanders@us.ibm.com
与 Mike 联系。
|
对本文的评价
|