【IT168 专稿】
Web入口(portal)程序是一些在单独页面上的独立Web组件的集合。这些可插拔的用户接口组件,被称为portlets。它们被用来处理请求和在portlet容器中产生动态内容。由于portlets可以从不同的数据源获得数据,因此,一个入口程序可以用一个统一的界面(这个界面的数据将来自不同的,互相割裂的数据源)为用户提供交互界面。
对于Java开发人员来说,Java Portlet规范(JPS)将会帮助他们来建立和使用portlets组件。这个规范是基于JSR168和基于Web Services的远程Portlets(WSRP)标准的。事实上,Oracle已经在它的Java开发工具----OracleWebCenter框架中提供了对这个标准的支持,这个开发框架可以开发JSF程序,并且在其中集成了基于ADF-based的Oracle框架。开发人员可以在其中使用内嵌AJAX组件和portlets来建立JSF应用程序。
Oracle最新的Oracle JDeveloper 10 IDE中WebCenter预配置版本OC4J,这个版本将提供一个JSR168标准的portlet容器,可以使用它来开发基于WSRP1.0/WSRP2.0标准的portlets。开发人员可以在JDeveloper10中建立portlet应用程序,并可使用WebCenter框架或任何支持JSR168标准的容器来发布程序。最后,开发人员可以使用应用程序或入口程序来通过注册一个WSRP生产者(producer)来运行WebCenter或其它入口程序中的portlet。
本文将带领读者开发一个基于JSR168和WSRP2.0规范的JSF portlet,并在WebCenter框架中来运行它。这个portlet将使用Oracle数据源和数据表。
一、建立开发环境
首先我们得安装Oracle 10g数据库(包括例子数数库),并选择建立Oracle数据库实例,ORCL,以及使用SQL*Plus或其他的SQL运行工具来执行如下的SQL以建立本例要使用的数据表:
CREATE TABLE OE.Catalog(ID VARCHAR(25) PRIMARY KEY, Journal VARCHAR(25), Publisher VARCHAR(25), Edition VARCHAR(25), Title Varchar(255), Author Varchar(25)); INSERT INTO OE.Catalog VALUES('catalog1', 'Oracle Magazine', 'Oracle Publishing', 'May-June 2006', 'Tuning Your View Objects', 'Steve Muench'); INSERT INTO OE.Catalog VALUES('catalog2', 'Oracle Magazine', 'Oracle Publishing', 'July-August 2006', 'Evolving Grid Management', 'David Baum'); INSERT INTO OE.Catalog VALUES('catalog3', 'Oracle Magazine', 'Oracle Publishing', 'July-August 2005', 'Tuning Undo Tablespace', 'Kimberly Floss');
二、建立一个JSF Portlet
在这部分,我们将使用Oracle数据库作为数据源建立一个JSF应用程序,然后将这个JSF程序转换为portlet。这个JSF应用程序由一个使用SQL查询动态产生的DataTable组成。
在这部分,我们将使用Oracle数据库作为数据源建立一个JSF应用程序,然后将这个JSF程序转换为portlet。这个JSF应用程序由一个使用SQL查询动态产生的DataTable组成。
首先,在JDeveloper中选择File->New建立一个JDeveloper工程,然后使用在New Gallery wizard中的General->Application。指定一个工程名后,选择WebCenter应用程序作为应用程序模板。在建立完WebCenter工程后,所有的数据和资源都会在如图1的Applications Navigator中显示出来。这个应用程序由用于建立portlet的Portlets工程和用于建立JSF视图页的ViewController工程组成,portlets可以在这个工程中被嵌入。

图1 . WebCenter工程效果图
在这里我们将使用WebCenter框架中的JSF-Portlet桥来编写JSF应用程序,这就意味着JSF应用程序将是基于JSR168 portlets的。我们可以选择Tools->Project项来加入JSF-Portlet桥所需的库。我们还需要加入ADF Faces运行时库和Oracle JDBC库。
接下来,我们将使用如下的步骤在WebCenter框架中实现和发布JSF应用程序:
1. 在" Applications Navigator"中选择Portlets工程,并选择File->New
2. 在新Gallery模板中,选择Categories 中的Web Tier->JSF,然后选择JSF JSP项,单击OK。
3. 当JSF JSP模板启动后,单击Next,选择J2EE1.4作为Web应用程序的版本,然后再次单击Next。
4. 指定input.jsp作为文件名后,单击Next。
5. 在一个新的管制Bean中选择Automatically Expose UI Components,并指定PortletBean作为类名,然后单击Next。
6. 选择默认的tag库,JSF Core1.0和JSF HTML 1.0,然后单击Next。
7. 选择默认的HTML选择,然后单击Next。
8. 单击Finish按钮后,在Portlets工程中建立一个JSF页(也就是input.jsp)和一个faces-config.xml发布文件。
下面我们来建立一个portlet。一个portlet需要一个portlet.xml文件,因此,我们按如下步骤在Portlets工程中加入一个portlet.xml文件。
8. 单击Finish按钮后,在Portlets工程中建立一个JSF页(也就是input.jsp)和一个faces-config.xml发布文件。
下面我们来建立一个portlet。一个portlet需要一个portlet.xml文件,因此,我们按如下步骤在Portlets工程中加入一个portlet.xml文件。
1. 选择File->New
2. 在New Gallery wizard中,从Catagories菜单中选择Filter By->All Technologies and select General->Deployment Descriptors
3. 选择portlet.xml,并单击OK
图2显示了我们的JSF portlet应用程序的目录结构。

现在,我们准备在应用程序中加入JSF组件。在Component Palette中,选择JSF HTML,并将以下内容加入到input.jsp页中:
图2显示了我们的JSF portlet应用程序的目录结构。

图2 JSF Portlet应用程序的目录结构
现在,我们准备在应用程序中加入JSF组件。在Component Palette中,选择JSF HTML,并将以下内容加入到input.jsp页中:
1. 一个输出标签
2. 一个输入文本字段
3. 一个命令按钮
4.一个数据表
在JSF页中的文本字段指定一个SQL查询。当一个用户单击建立数据表命令时,PortletBean.java类中的commandButton_action方法被调用,然后一个数据表被动态地创建。PortletBean.java中的部分代码如下:package portlet.backing; ... ... public class PortletBean { .... .... public String commandButton_action() { ResultSet rs = null; try { InitialContext initialContext = new InitialContext(); javax.sql.DataSource ds = (javax.sql.DataSource)initialContext.lookup("java:comp/env/jdbc/OracleDBConnectionDS"); java.sql.Connection connection = ds.getConnection(); Statement stmt = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); rs = stmt.executeQuery((String)inputText1.getValue()); dataTable1.setBorder(5); dataTable1.setCellpadding("1"); dataTable1.setVar("catalog"); HtmlOutputText headerComponent1 = new HtmlOutputText(); headerComponent1.setId("headerComponent1"); headerComponent1.setValue("CatalogId"); column1.setHeader(headerComponent1); HtmlOutputText headerComponent2 = new HtmlOutputText(); headerComponent2.setId("headerComponent2"); headerComponent2.setValue("Journal"); column2.setHeader(headerComponent2); HtmlOutputText headerComponent3 = new HtmlOutputText(); headerComponent3.setId("headerComponent3"); headerComponent3.setValue("Publisher"); column3.setHeader(headerComponent3); HtmlOutputText headerComponent4 = new HtmlOutputText(); headerComponent4.setId("headerComponent4"); headerComponent4.setValue("Edition"); column4.setHeader(headerComponent4); HtmlOutputText headerComponent5 = new HtmlOutputText(); headerComponent5.setId("headerComponent5"); headerComponent5.setValue("Title"); column5.setHeader(headerComponent5); HtmlOutputText headerComponent6 = new HtmlOutputText(); headerComponent6.setId("headerComponent6"); headerComponent6.setValue("Author"); column6.setHeader(headerComponent6); HtmlOutputText column1Text = new HtmlOutputText(); column1Text.setId("htmlOutputText1"); ValueBinding vb = FacesContext.getCurrentInstance().getApplication().createValueBinding("#{catalog.ID}"); column1Text.setValueBinding("value", vb); column1.getChildren().add(column1Text); HtmlOutputText column2Text = new HtmlOutputText(); column2Text.setId("htmlOutputText2"); vb = FacesContext.getCurrentInstance().getApplication().createValueBinding("#{catalog.JOURNAL}"); column2Text.setValueBinding("value", vb); column2.getChildren().add(column2Text); HtmlOutputText column3Text = new HtmlOutputText(); column3Text.setId("htmlOutputText3"); vb = FacesContext.getCurrentInstance().getApplication().createValueBinding("#{catalog.PUBLISHER}"); column3Text.setValueBinding("value", vb); column3.getChildren().add(column3Text); HtmlOutputText column4Text = new HtmlOutputText(); column4Text.setId("htmlOutputText4"); vb = FacesContext.getCurrentInstance().getApplication().createValueBinding("#{catalog.EDITION}"); column4Text.setValueBinding("value", vb); column4.getChildren().add(column4Text); HtmlOutputText column5Text = new HtmlOutputText(); column5Text.setId("htmlOutputText5"); vb = FacesContext.getCurrentInstance().getApplication().createValueBinding("#{catalog.TITLE}"); column5Text.setValueBinding("value", vb); column5.getChildren().add(column5Text); HtmlOutputText column6Text = new HtmlOutputText(); column6Text.setId("htmlOutputText6"); vb = FacesContext.getCurrentInstance().getApplication().createValueBinding("#{catalog.AUTHOR}"); column6Text.setValueBinding("value", vb); column6.getChildren().add(column6Text); ResultSetDataModel dataModel = new ResultSetDataModel(); dataModel.setWrappedData(rs); dataTable1.setValue(dataModel); } catch (SQLException e) { System.out.println(e.getMessage()); } catch (javax.naming.NamingException e) { System.out.println(e.getMessage()); } return null; } }
在输入文本框中被指定的SQL查询在commandButton_action方法中运行,并为数据表产生一个结果集。下面将显示input.jsp的全部代码:
<%@ page contentType="text/html;charset=windows-1252"%> <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%> <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%> <f:view> <h:form id="form1"> <h:outputLabel value="SQL" binding="#{backing_input.outputLabel1}" id="outputLabel1"/> <h:inputText id="inputText1" binding="#{backing_input.inputText1}"/> <h:commandButton id="commandButton1" value="Create Data Table" binding="#{backing_input.commandButton1}" action="#{backing_input.commandButton_action}"/> <h:dataTable id="dataTable1" rows="5" binding="#{backing_input.dataTable1}"> <h:column id="column1" binding="#{backing_input.column1}"/> <h:column id="column2" binding="#{backing_input.column2}"/> <h:column id="column3" binding="#{backing_input.column3}"/> <h:column id="column4" binding="#{backing_input.column4}"/> <h:column id="column5" binding="#{backing_input.column5}"/> <h:column id="column6" binding="#{backing_input.column6}"/> </h:dataTable> </h:form> </f:view>
接下来我们应为每一个动态JSP组件指定一个ID。如果我们不这么做,JSF框架将在portlet标记中产生重复的ID,当portlet运行时会产生以下错误:
编辑web.xml和portlet.xml文件,在web.xml中加入以下context-param结点:
按着如下步骤编辑portlet.xml文件:
Duplicate component ID … found in view.
An internal error has occurred in method getMarkup()编辑web.xml和portlet.xml文件,在web.xml中加入以下context-param结点:
<context-param> <param-name>javax.faces.application.CONFIG_FILES</param-name> <param-value>/WEB-INF/faces-config.xml,/WEB-INF/portlet.xml</param-value> </context-param> <context-param> <param-name>javax.faces.STATE_SAVING_METHOD</param-name> <param-value>server</param-value> </context-param>
在web.xml中加入以下ADF Faces filter过滤器:
<filter>
<filter-name>adfFaces</filter-name>
<filter-class>oracle.adf.view.faces.webapp.AdfFacesFilter</filter-class>
</filter>
按着如下步骤编辑portlet.xml文件:
1. 使用<portlet></portlet>结点指定一个portlet。
2. 指定一个portlet类oracle.portlet.server.bridges.jsf.FacesPortlet
3. 为默认页指定一个结点<init-param/>,当portlet运行时这个页被显示。代码如下:
<init-param>
<name>DefaultPage.view</name>
<value>/input.jsp</value>
</init-param>
4. 为BridgeLifecycleListeners listener类指定一个<init-param/>结点,代码如下:
<init-param>
<name>BridgeLifecycleListeners</name>
<value>
oracle.portlet.server.bridges.jsf.adf.ADFFacesBridgeLifecycleListener
</value>
</init-param>
下面将显示portlet.xml文件的完整内容:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <portlet-app version="1.0" 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" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd"> <portlet> <portlet-name>DataTablePortlet</portlet-name> <display-name>DataTable Portlet</display-name> <portlet-class>oracle.portlet.server.bridges.jsf.FacesPortlet</portlet-class> <init-param> <name>DefaultPage.view</name> <value>/input.jsp</value> </init-param> <init-param> <name>BridgeLifecycleListeners</name> <value> oracle.portlet.server.bridges.jsf.adf.ADFFacesBridgeLifecycleListener </value> </init-param> <supports> <mime-type>text/html</mime-type> <portlet-mode>VIEW</portlet-mode> </supports> <supported-locale>en</supported-locale> <portlet-info> <title>DataTable Portlet</title> <short-title>datatable</short-title> </portlet-info> </portlet> </portlet-app>
三、 为Oracle数据库建立一个JDBC连接
为了使用Oracle数据库配置JDBC连接,我们可以选择Connections视图,右击Database结点,然后选择新建数据库连接。指定OracleDBConnection作为连接名,并选择Oracle JDBC作为连接类型。最后单击Next。指定OE作为用户名和密码,然后单击Next。选择默认的连接URL,再次单击Next。最后测试连接是否成功后,击击Finish按钮完成配置JDBC连接。
下面我们在web.xml加一个<resource-ref/>结点来配置JDBC数据源,代码如下:
<resource-ref> <res-ref-name>jdbc/OracleDBConnectionDS</res-ref-name> <res-type>javax.sql.DataSource</res-type> <res-auth>Container</res-auth> </resource-ref>
四、发布JSF Portlet
现在我们首先将JSF portlet发布到在WebCenter框架中预配置的OC4J中,并按着以下步骤为JSF portlet建立一个发布文件:
1. 在Applications Navigator中选择Portlets工程,然后选择File->New。
2. 在New Gallery wizard中,选择General->Deployment Profiles->WAR File,然后单击OK。
3. 指定"webapp1"作为发布文件名,然后单击OK。
4. 指定"portletsApp"作为WAR文件中的J2EE Web Context Root,然后单击OK。5. 选择Tools->Start WebCenter预配置OC4J,然后在WebCenter OC4J对话框中单击Yes安装OC4J。如图3所示:

图3 开始WebCenter预配置OC4J
Oracle Containers for J2EE 10g (10.1.3.1.1) initialized
接下来,我们要使用WebCenter Preconfigured OC4J配置一个连接。在Connections view中。右击Application Server,然后选择New Application Server Connection。在这个模板中单击Next,指定LocalOC4J作为连接名,然后的选择Standalone OC4J 10g 10.1.3作为连接类型。单击Next,将默认的名子"oc4jadmin"作为用户名,并指定"welcome"作为密码。单击Next。将默认的"localhost"作为服务器名,并指定22667作为RMI端口。然后单击Next。
接下来单击Test Connection,然后单击Finish测试这个连接。到现在为止,我们已经使
用WebCenter Preconfigured OC4J建立了一个连接。如图4所示:
图4 用WebCenter建立的一个 Connection:
接下来,我们来发布在JSP portlet应用程序中建立的WAR包。右击webapp1.deploy,选择Deploy to->LocalOC4J。然后在配置应用程序窗口单击OK来为WSRP应用程序建立WSDL。并将JSP portlet应用程序发布到WebCenter Preconfigured OC4J中。
五、将Portlet注册为WSRP生成者(Producer)
WSRP定义了一套接口,这些接口使用portlet组件和用户应用程序之间的调用显得更加标准。portlet组件产生用户程序所使用的标记。在WebCenter应用程序页使用portlet之前,我们必须先得到一个WSDL,然后在应用程序中为portlet注册一个WSRP生产者。我们可以从http://localhost:6688/portletsApp/info得到这个WSDL URL,如图5所示。

图5 WSRP Producer测试页
在WebCenter框架中提供了一个WSRP Producer注册模板,如图6所示,这个模板可以让我们使用带有JSF portlet的WebCenter应用程序注册一个WSRP Producer。选择ViewController工程,并选择File->New。在New Gallery wizard中,选择Web Tier->Portlets,然后选择WSRP Producer注册。单击OK。下面我们将要看到WSRP Portlet Producer注册模板。单击Next。为WSRP Producer指定一个名子,再次单击Next。

图6 WSRP Portlet Producer
六、在JSF页中加入Portlet
现在使用WSRP Portlet Producer注册的Portlets已经可以在组件面版上看到了。在这一部分,我们要将JSF portlet加入到JSF页中。选择ViewController工程,然后选择File->New。选择Web Tier->JSF->JSF JSP,然后点击OK。为了使JSF页可定制,我们将建立一个.jspx文档来取代jsp文档。单击Next。在组件绑定窗口中,在Managed Bean中选择Do Not Automatically Expose UI Components,然后单击Next。在Tag Libraries窗口,选择ADF Portlet组件库,这个库被要求加入到JSF页中。如果我们想定制JSF页,可以选择可定制的组件库。如PanelCustomizable和ShowDetailFrame,这些可以向程序加入更丰富的特性,如layout、scrollbar,以及向JSF页中增加最大化,最小化的功能。
现在使用WSRP Portlet Producer注册的Portlets已经可以在组件面版上看到了。在这一部分,我们要将JSF portlet加入到JSF页中。选择ViewController工程,然后选择File->New。选择Web Tier->JSF->JSF JSP,然后点击OK。为了使JSF页可定制,我们将建立一个.jspx文档来取代jsp文档。单击Next。在组件绑定窗口中,在Managed Bean中选择Do Not Automatically Expose UI Components,然后单击Next。在Tag Libraries窗口,选择ADF Portlet组件库,这个库被要求加入到JSF页中。如果我们想定制JSF页,可以选择可定制的组件库。如PanelCustomizable和ShowDetailFrame,这些可以向程序加入更丰富的特性,如layout、scrollbar,以及向JSF页中增加最大化,最小化的功能。
接下来,将我们以前产生的JSF portlet加到JSF页上。将鼠标放到PortletsPage.jspx页上,在组件面版上选择WsrpPortletProducer1,然后选择DataTable Portlet。这个被加到JSF页上的DataTable Portlet和其他的JSF组件类似。
在JSF页中的portlet使用adfp:portlet标签描述,我们可以为其指定其他的属性,如height、width、icon、isMaximizable、isMinimizable、isMovable等。通过吉击PortletsPage.jspx,选择Run来运行WebCenter应用程序的JSF页,运行结果如图7所示:

接下来,在SQL输入字段中指定一个SQL表达式,然后单击Create Data Table按钮。一个以Oracle数据库作为数据源的数据表将显示在当前页上,如图8所示。

图8 数据表Portlet
在JSF页中的portlet使用adfp:portlet标签描述,我们可以为其指定其他的属性,如height、width、icon、isMaximizable、isMinimizable、isMovable等。通过吉击PortletsPage.jspx,选择Run来运行WebCenter应用程序的JSF页,运行结果如图7所示:

图7 JSF Portlet

图8 数据表Portlet