1 背景概述
Portlet是AEAI Portal组件API,是基于Java的Web组件,由Portlet容器管理,并由容器处理请求,生产动态内容。AEAI Portal中已经预置了许多Portlet组件,可以直接配置使用。由于不同业务需求也可以将Portlet进行定制开发。本文是本人在综合集成项目中由于业务需要动态显示风险统计信息,即对某一风险进行评估时引用不同的风险点对其的影响(可能性与影响程度的乘积)进行分析,并在页面以个数的形式显示不同区间所包含风险点的影响。故而对Portlet的定制开发的过程以及方法进行总结。
2 预期读者
-
AEAI Portlet开发人员
-
数通畅联人员
-
其他相关人员
3 技能要求
本文档假定目标用户具备以下技能:
-
了解Eclipse基本用法;
-
了解基本数据库知识安装以及SQL基本技能;
-
了解AEAI Portal基本知识;
-
了解AEAI ESB基本知识。
4 名词解释
5 实现思路
Portlet工程的定制开发过程大致为:初始化数据库portal、portal_test,portal_test中初始化业务数据表(risk_level_setting、risk_analyze_entry)以便提供业务需要的数据(参见6.3数据表初始化),在ESB工程(TestPortlets)中创建数据源portal_test(连接数据库portal_test)以及创建消息流程(测试Portlets)来提供页面显示的数据信息(参见7.3Json处理),本地搭建Portal环境,开发Portlet工程,配置开发的Portlet实现业务需求。6 实现步骤
6.1 创建工程
1. 在中点击创建工程,选择集成Web项目,填写应用名称(可自定义)、主目录(可自定义),点击测试连接,连接成功后点击Next按钮。配置如下图:
2. 填写连接的数据库名称,对应的用户名以及密码点击测试连接弹出连接成功后点击Finish按钮。
按钮若创建的PortLet数据库中没有内容,则点击初始化数据,将自动生成数据表。若数据库已经存在且有内容,不可点击初始化数据,否则将覆盖已有的数据库内容。
3. 由于新版的PortalServer的加密机制升级与当前的的加密机制不同,故需修改对应配置文件中的密码(参见附件中密码生成文档)
4. 替换新的密码位置(两处)如下:
1) 工程中的配置文件
2) (你的设计器的路径)\miscdp-studio-win32_x86\workspace\test_portlets下的
5. 点开新创建的项目,在业务管理上点击右键,选择创建功能。
6. 填写功能名称(可自定义),功能类型选择Portlet功能模型,点击Finish按钮。
7. 如下图所示,填写目录编码(可自定义)保存。
8. 点击按钮生成代码,点击按钮或者点击切换到Java透视图可以查看代码。如下图:
9. 开发Portlet的Java类(相当于Handler)
1) 定义四个方法(具体参见7.1四个方法)
1. @RenderMode(name ="view")—指向View页面构造页面需要的属性
publicvoidview(RenderRequest request, RenderResponse response)
2. @RenderMode(name = "edit")—指向Edit页面构造页面需要的属性
publicvoidedit(RenderRequest request, RenderResponse response)
3. @ProcessAction(name ="saveConfig")
publicvoidsaveConfig(ActionRequest request, ActionResponse response)
4. @Resource(id ="getAjaxData")支持异步传递传递回调函数需要的Json等信息
publicvoidgetAjaxData(ResourceRequest request, ResourceResponse response)
2) 创建两个页面(具体参见7.2两个页面)
1. View—在js中添加回调函数(回调函数处理Json等显示的信息)
2. Edit—传递属性信息
10. 扩展方法以及页面完成后点击加载应用,重启应用
6.2 数据表初始化
1. 初始化数据表(Json中用到的业务数据)
1) 在数据库中创建数据表risk_level_setting 步骤如下:
2) 在下点击下的将下面的SQL复制在其中
DROP TABLE IF EXISTS `risk_level_setting`;
CREATE TABLE `risk_level_setting` (
`RL_ID` char(36) NOT NULL,
`RLS_MIN` float DEFAULT NULL,
`RLS_MAX` float DEFAULT NULL,
`RLS_CORLOR` varchar(255) DEFAULT NULL,
PRIMARY KEY (`RL_ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of risk_level_setting
-- ----------------------------
INSERT INTO `risk_level_setting` VALUES ('29FE4961-6F01-4913-99E3-24551E3BCF1F', '0', '5', 'GREEN');
INSERT INTO `risk_level_setting` VALUES ('874494A8-CD87-4EC2-9E44-4AAF9A493976', '5', '12', 'YELLOW');
INSERT INTO `risk_level_setting` VALUES ('EE214872-FE74-4F7E-A80F-FDC127570725', '12', '25', 'RED');
|
3) 点击执行
结果如下
4) 在数据库中创建数据表risk_analyze_entry 步骤同上,SQL如下:
DROP TABLE IF EXISTS `risk_analyze_entry`;
CREATE TABLE `risk_analyze_entry` (
`RAE_ID` char(36) NOT NULL,
`RAE_HAPP_POSS` decimal(10,0) DEFAULT NULL,
`RAE_INF_DEGREE` decimal(10,0) DEFAULT NULL,
`RAE_RISK_LEVEL` decimal(10,0) DEFAULT NULL,
`RAE_SORT` int(11) DEFAULT NULL,
PRIMARY KEY (`RAE_ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of risk_analyze_entry
-- ----------------------------
INSERT INTO `risk_analyze_entry` VALUES ('0EC07E27-5130-4EA8-A64B-6B3DE5DFD22E', '2', '2', '4', null);
INSERT INTO `risk_analyze_entry` VALUES ('470840C1-086C-43C4-977A-7F16D14F7D33', '5', '3', '15', null);
INSERT INTO `risk_analyze_entry` VALUES ('67815A3F-4B52-490B-A520-E787AE8585BE', '3', '3', '9', null);
INSERT INTO `risk_analyze_entry` VALUES ('B76D5B92-FDE5-46E6-8E02-BD06CD6D5E6D', '1', '1', '1', null);
|
6.3 配置说明
1. Portal配置说明
1) 浏览器输入http://localhost:8080/portal/login.jsp访问portal工程,界面如下:
2) 以admin身份登录密码为admin,点击门户管理。
3) 点击组件管理Portlet应用
4) 点击按钮填写编码标识(为自定义开发的portlets工程名)、编码值(可自定义)、选择编码类型、编码排序、选择是否有效,后点击保存按钮。
5) 点击组件管理中的Portlet设置
6) 点击按钮填写标题(可自定义)、选择所属应用、Portlet名称、分组,后点击保存按钮。
7) 点击下的点击综合门户如下图:
8) 选中点击右侧的弹出界面填写编码、名称(可自定义)点击保存按钮。配置如下:
9) 点击刚创建好的切换到页面布局点击按钮添加容器
10) 弹出的界面选择对应的Portlet
点击保存按钮
11) 点击下的切换回主界面,点击决策分析菜单中的显示如下图:
12) 点击按钮切换到界面,将创建的ESB流程中的HttpRequest节点(参见7.3Json处理)的访问地址填写到数据URL中,设置高度,点击保存按钮。配置如下:
13) 结果如下:
6.4 参考技巧
由于本人Portlet的开发并不是十分了解,故在开发过程中找到如下小技巧为开发者提供参考。对根据需求开发不同功能的Portlet,定义所需要的属性,可利用反编译软件打开已封装完现有的Portlet组件功能,作为实现的参考。达到Portlet的定制开发的目的。参考路径为:…..你的服务器的地址)\portal_server_x64_Vx.x.x.YYYYMMDD\webapps\portal_portlets\WEB-INF
\classes\com\agileai\portal\portlets
7 核心说明
7.1 四个方法
1. 自定义开发的Portlets中扩展开发下面的四个方法,本例位于
2. View方法中指向View页面构造页面需要的属性
@RenderMode(name = "view")
publicvoid view(RenderRequest request, RenderResponse response)
throws PortletException, IOException {
PortletPreferences preferences = PreferencesHelper.getPublicPreference(request);
String chartHeight = preferences.getValue("chartHeight", "50");
String namespace = preferences.getValue("namespace", null);
String defaultVariableValues = preferences.getValue("defaultVariableValues", null);
String dataURL = preferences.getValue("dataURL", null);
String defaultValueString = "namespace="+namespace+"&"+defaultVariableValues;
PortletVariableHelper variableHelper = new PortletVariableHelper(request,defaultValueString);
dataURL = variableHelper.getRealDataURL(dataURL);
String portletId = request.getWindowID();
Integer height = Integer.parseInt(chartHeight);
request.setAttribute("height", height);
request.setAttribute("portletId", portletId);
request.setAttribute("namespace", namespace);
request.setAttribute("dataURL", dataURL);
super.doView(request, response);
}
|
3. Edit方法中指向Edit页面构造页面需要的属性
@RenderMode(name = "edit")
publicvoid edit(RenderRequest request, RenderResponse response)
throws PortletException, IOException {
PortletPreferences preferences = PreferencesHelper.getPublicPreference(request);
String dataURL = preferences.getValue("dataURL", null);
String chartHeight = preferences.getValue("chartHeight", null);
String namespace = preferences.getValue("namespace", null);
String defaultVariableValues = preferences.getValue("defaultVariableValues", null);
request.setAttribute("dataURL", dataURL);
request.setAttribute("chartHeight", chartHeight);
request.setAttribute("namespace", namespace);
request.setAttribute("defaultVariableValues", defaultVariableValues);
super.doEdit(request, response);
}
|
4. saveConfig方法保存配置信息
@ProcessAction(name = "saveConfig")
publicvoid saveConfig(ActionRequest request, ActionResponse response)
throws ReadOnlyException, PortletModeException, ValidatorException,
IOException, PreferenceException {
String dataURL = request.getParameter("dataURL");
String chartHeight = request.getParameter("chartHeight");
String namespace = request.getParameter("namespace");
String defaultVariableValues = request.getParameter("defaultVariableValues");
PreferencesWrapper preferWapper = new PreferencesWrapper();
preferWapper.setValue("dataURL", dataURL);
preferWapper.setValue("chartHeight", chartHeight);
preferWapper.setValue("namespace", namespace);
preferWapper.setValue("defaultVariableValues", defaultVariableValues);
PreferencesHelper.savePublicPreferences(request,preferWapper.getPreferences());
response.setPortletMode(PortletMode.VIEW);
}
|
5. getAjaxData支持异步传递传递回调函数需要的Json等信息
@Resource(id = "getAjaxData")
publicvoid getAjaxData(ResourceRequest request, ResourceResponse response)
throws PortletException, IOException {
String ajaxData = "";
try {
String dataURL = getDataURL(request);
StringWriter buffer = new StringWriter();
URL url = new URL(dataURL);
InputStream inputStream = url.openStream();
IOUtils.copy(inputStream, buffer, "UTF-8");
IOUtils.closeQuietly(inputStream);
ajaxData = buffer.toString();
} catch (Exception e) {
this.logger.error("获取取数据失败getAjaxData", e);
}
PrintWriter writer = response.getWriter();
writer.print(ajaxData);
writer.close();
}
|
7.2 两个页面
1. 自定义开发的Portlets中扩展开发下面的两个页面,本例位于WebRoot下:
2. view页面为界面所显示的信息,其中responseText为getAjaxData方法中返回的参数Json
获得Java类中定义的height变量,将css的高度变量化
代码如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/portlet_2_0" prefix="portlet"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@page import="com.agileai.hotweb.domain.PageBean"%>
<portlet:defineObjects/>
<%
Integer height = (Integer)request.getAttribute("height");
Integer tab2height = (Integer)request.getAttribute("height") - height/10;
%>
<style>
.graphTable<portlet:namespace/>{
font-family: verdana,arial,sans-serif;
border-width:1px;
border-color: #000000;
border-collapse: collapse;
}
.graphTable<portlet:namespace/>tr{
height:<%=height%>px;
border-width: 1px;
border-style: solid;
border-color: #000000;
}
.graphTable<portlet:namespace/> tr td{
height:<%=height%>px;
width:<%=height%>px;
text-align:center;
border-width: 1px;
border-style: solid;
border-color: #000000;
}
.table2<portlet:namespace/> tr td{
width:<%=height%>px;
text-align:right;
}
.table1<portlet:namespace/> tr td{
height:<%=tab2height%>px;
}
</style>
<portlet:resourceURL id="getAjaxData" var="getAjaxDataURL">
</portlet:resourceURL>
<body>
<span style="float:left">
<table border="0" style="float:left" class = "table1<portlet:namespace/>" id="table1">
<tr>
<td>影</td>
<td> </td>
<td>5.0</td>
</tr>
<tr>
<td>响</td>
<td> </td>
<td>4.0</td>
</tr>
<tr>
<td>程</td>
<td> </td>
<td>3.0</td>
</tr>
<tr>
<td>度</td>
<td> </td>
<td>2.0</td>
</tr>
<tr>
<td> </td>
<td> </td>
<td>1.0</td>
</tr>
<tr>
<td> </td>
<td> </td>
<td>0.0</td>
</tr>
</table>
<table border="1" class="graphTable<portlet:namespace/>" id="graphTable1">
<tr>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
</tr>
<tr>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
</tr>
<tr>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
</tr>
<tr>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
</tr>
<tr>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
</tr>
</table>
<table border="0" class="table2<portlet:namespace/>" id="table2">
<tr>
<td>1.0</td>
<td>2.0</td>
<td>3.0</td>
<td>4.0</td>
<td>5.0</td>
</tr>
<tr>
<td> </td>
<td>可</td>
<td>能</td>
<td>性</td>
<td> </td>
</tr>
</table>
</span>
</body>
<%
String portletId = (String)request.getAttribute("portletId");
%>
<div>${someProperty}</div>
<script type="text/javascript">
$(function(){
var __renderPortlet<portlet:namespace/> = function(){
sendAction('${getAjaxDataURL}',{dataType:'text',onComplete:function(ajaxData){
var allJson = $.parseJSON(ajaxData);
var rowCountIndex = 0;
for (var i=5;i > 0;i--){
rowCountIndex++;
for (var j=0;j < 5;j++){
var rowIndex = i-1;
var colIndex = j;
var colCountIndex = j+1;
var total = rowCountIndex * colCountIndex;
if (total >= allJson.dataLine.GREEN.min && total <= allJson.dataLine.GREEN.max){
$("#graphTable1 tr:eq("+rowIndex+") td:eq("+colIndex+")").css("background-color","green");
}
elseif (total >= allJson.dataLine.YELLOW.min && total <= allJson.dataLine.YELLOW.max){
$("#graphTable1 tr:eq("+rowIndex+") td:eq("+colIndex+")").css("background-color","yellow");
}
else{
$("#graphTable1 tr:eq("+rowIndex+") td:eq("+colIndex+")").css("background-color","red");
}
}
}
for (var i=0 ;i < 25 ;i++){
if(allJson.cellNumbers[i].count == 0){
$("#graphTable1 tr:eq("+allJson.cellNumbers[i].rowIndex+") td:eq("+allJson.cellNumbers[i].colIndex+")").text("");
}else{
$("#graphTable1 tr:eq("+allJson.cellNumbers[i].rowIndex+") td:eq("+allJson.cellNumbers[i].colIndex+")").text(allJson.cellNumbers[i].count);
}
}
}});
};
__renderPortlets.put("<%=portletId%>",__renderPortlet<portlet:namespace/>);
__renderPortlet<portlet:namespace/>();
});
</script>
|
3. Edit页面为配置Portal中编辑页面所显示的属性信息如下图:
编辑页面所显示的属性
代码如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/portlet_2_0" prefix="portlet"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<portlet:defineObjects/>
<portlet:actionURL name="saveConfig" var="saveConfigURL"></portlet:actionURL>
<portlet:renderURL portletMode="edit" var="editURL"></portlet:renderURL>
<%
String isCache = (String)request.getAttribute("isCache");
%>
<form id="<portlet:namespace/>Form">
<table width="90%" border="1">
<tr>
<td width="120">数据URL <span style="color: red;">*</span></td>
<td><input type="text" size="50" name="dataURL" id="dataURL" value="${dataURL}" /></td>
</tr>
<tr>
<td width="120">默认值</td>
<td>
<input type="text" name="defaultVariableValues" id="defaultVariableValues" size="50" value="${defaultVariableValues}" />
</td>
</tr>
<tr>
<td width="120">高度 <span style="color: red;">*</span></td>
<td><input type="text" size="50" name="chartHeight" id="chartHeight" value="${chartHeight}" /></td>
</tr>
<tr>
<td colspan="2" align="center">
<input type="button" name="button" id="button" value="保存" />
<input type="button" name="button2" id="button2" value="取消" onclick="fireAction('${editURL}')"/></td>
</tr>
</table>
</form>
<script language="javascript">
</script>
|
7.3 Json处理
1. ESB创建工程TestPortlets、创建数据源portal_test为ESB的基础知识,在这里不做过多的赘述。(可参见AEAI ESB集成平台技术手册)
2. ESB创建消息流程
1) 在消息流程中添加数据查询节点以及数据转换节点
2) 在JdbcQueryer节点中创建DataSet接收查询的结果集
3) 在JavaConverter中创建XXX用于接收转换后的结果集,在扩展代码中修改对应代码
4) 在HttpResponse中打印转换后的结果:@{XXX}
3. 流程图如下:
查询风险评价节点选择数据源创建结果类型变量riskGraphMaxMinDataSet,点击选择表选择数据表(risk_level_setting)生成SQL。配置如下:
点击刷新按钮后,点击Finish按钮。
查询风险点节点(参见查询风险评价节点)结果变量为(riskGraphDataSet)数据表为(risk_analyze_entry)配置如下:
下面我们重点讲解JOSN格式转换节点。
双击改节点选择转换,DataSet装换为数据表格,点击Next按钮。
点击选择按钮选择来源变量,创建目标变量,选中扩展代码,点击Finish按钮。
扩展代码如下:
创建Json存储、最外层的Json名称为dataLine
将rowIndex与colIndex放入到子Json中。
此方法是由于业务需要设置默认值均为0
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONObject;
import com.agileai.domain.DataRow;
import com.agileai.domain.DataSet;
import com.agileai.esb.component.Variable;
import com.agileai.esb.component.transformer.JavaTransformer;
import com.agileai.esb.core.AdapteException;
publicclass JavaConverter1 extends JavaTransformer{
private List<DataRow> dataModel = new ArrayList<DataRow>();
publicvoid handleRequest() throws AdapteException{
try {
this.initModel();
Variable riskGraphVariable = this.getVariable("riskGraphDataSet");
Variable riskGraphMaxMinVariable = this.getVariable("riskGraphMaxMinDataSet");
DataSet riskGraphDataSet = (DataSet)riskGraphVariable.getValue();
for (int i=0;i < riskGraphDataSet.size();i++){
DataRow row = riskGraphDataSet.getDataRow(i);
BigDecimal degree = (BigDecimal)row.get("RAE_INF_DEGREE");
for (int m =0 ;m < 5;m++){
if (degree.compareTo(new BigDecimal(m)) > 0 && degree.compareTo(new BigDecimal(m+1)) <=0){
DataRow modelRow = this.dataModel.get(m);
BigDecimal poss = (BigDecimal)row.get("RAE_HAPP_POSS");
for (int n = 0;n < 5;n++){
if (poss.compareTo(new BigDecimal(n)) > 0 && poss.compareTo(new BigDecimal(n+1)) <=0){
Integer currentCount = (Integer)modelRow.get(String.valueOf(n));
modelRow.put(String.valueOf(n),++currentCount);
}
}
}
}
}
DataSet riskGraphMaxMinDataSet = (DataSet)riskGraphMaxMinVariable.getValue();
JSONArray riskGraphjsonArray = new JSONArray();
JSONObject totalJsonObj = new JSONObject();
JSONObject riskGraphMaxMinJsonObj = new JSONObject();
for (int i=0;i < riskGraphMaxMinDataSet.size();i++){
DataRow dataLineRow = riskGraphMaxMinDataSet.getDataRow(i);
JSONObject dataLinejsonObject = new JSONObject();
riskGraphMaxMinJsonObj.put(dataLineRow.stringValue("RLS_CORLOR"),dataLinejsonObject);
dataLinejsonObject.put("min", dataLineRow.stringValue("RLS_MIN"));
dataLinejsonObject.put("max", dataLineRow.stringValue("RLS_MAX"));
}
totalJsonObj.put("dataLine", riskGraphMaxMinJsonObj);
for (int m=0;m < 5;m++){
DataRow row = this.dataModel.get(m);
for (int n=0;n < 5;n++){
JSONObject cellNumsJsonObject = new JSONObject();
int rowIndex = 4-m;
int colIndex = n;
cellNumsJsonObject.put("rowIndex", rowIndex);
cellNumsJsonObject.put("colIndex", colIndex);
int count = row.getInt(String.valueOf(n));
cellNumsJsonObject.put("count", count);
riskGraphjsonArray.put(cellNumsJsonObject);
}
}
totalJsonObj.put("cellNumbers", riskGraphjsonArray);
Variable targetVariable = this.getVariable("riskGraphJson");
targetVariable.setValue(totalJsonObj);
} catch (Exception e) {
logger.error(e.getLocalizedMessage(), e);
thrownew AdapteException(e.getLocalizedMessage(),e);
}
}
privatevoid initModel(){
for (int i=0;i < 5;i++){
DataRow row = new DataRow();
this.dataModel.add(row);
for (int n=0;n < 5;n++){
row.put(String.valueOf(n),0);
}
}
}
}
|
HttpResponse节点打印Json配置如下:
4. 在TestPortlets应用节点右键,如下:
5. 点击部署ESB应用,弹出窗口如下:
6. 点击“Finish”,由于部署ESB应用可能较慢(5-10秒),可能在设计器上没有及时响应,图表处于变灰状态,点击按钮来刷新,左边的菜单节点才能高亮显示,如下:
7. 访问HttpRequest节点的URL http://localhost:9090/TestPortlets/http/TestPortlets得到Json如下:
8 附件说明
ESB文件夹/ TestPortlets.zip:消息流程--测试Portlets提供业务数据
Portal文件夹/ portal.zip:本地搭建portal环境
Portal文件夹/ portal_portlets.zip:封装好的portlets
Portal文件夹/ test_portlets.zip:自定义开发的portlets
sqls文件夹/ portal.sql本地搭建portal环境需要的数据库文件
sqls文件夹/ portal_test.sql提供自定义开发的portlets需要的数据库信息,包含需要初始化的业务数据表
密码生成:生成密文密码的方法
AEAI Portlet开发心得相关附件及文档 下载
相关推荐
一个关于portlet开发的使用手册,非常实用。。。。
portlet 开发指南 开发指南 中文
portlet开发以及相应的工具包,如何与tomcat整合相应资料
基于jetspeed的portlet开发探讨,供大家学习参考!!!!
portlet资料 portlet开发指南 portlet开发手册
liferay portlet 开发的例子介绍
Liferay Portlet 开发文档, 比较全的开发介绍,了解PORTLET 的应用和基本开发, 熟悉Lifery IDE 开发。
portlet 开发 详细开发pdf 文档 十分详细的开发文档
portlet开发手册,使你成为高手中的高手
JSR-168 Portlet 开发 JSR-168 Portlet 开发
文章专门针对具有 JSR 168 Portlet 开发基础,并且想了解 JSR 286 Portlet 新特性和开发流程的开发人员。在学习完本系列后,您将了解相对于 JSR 168 Portlet,JSR 286 Portlet 究竟提供了哪些增强功能, 以及这些...
liferay portlet开发参考手册——作者:温兵
struts-portlet开发 比较细 有需要的可以下载 关于struts2的
portlet,开发详解 portlet,开发详解portlet,开发详解
开发portlet过程:包括:1.1 类名规范;1.2 RAD7开发JSR168 portlet规范;1.3编写代码 .......
详细介绍了Portlet的开发过程,转载。
Portlet开发技术解决方案.pdf,很有用的学习Portlet的资料。
内含有jetspeed 的portlet 二次开发,高级开发,和开发探讨等内容
liferay portlet开发介绍文档。