2008年12月23日星期二

WebService中的跨系统图片查询及显示服务实现

系统A提供图片和相关文字信息查询服务,系统B需要调用系统A的服务,得到相关文字信息和二进制图片信息,并需要将图片在系统B进行显示,下面介绍具体的实现方式和思路:
1.服务端代码
服务端代码从Oracle中读出Blob数据,使用BASE64算法编码,BASE64编码将数据按6位分组,转化为2^6共64个字符(A..Z,0..9和其他字符等),关于编码的具体细节,可以查阅相关资料。这里需要注意的是当数据库查不到图片的时候,处于数据冗余考虑,我们希望用一张存放在服务器上的默认图片来代替,而不采用在每条记录插入默认BLOB数据;这张默认图片同样需要采用BASE64算法编码,并传送给服务调用端。要注意的问题是关于图片的相对路径如何定位。

/* -----------------------*
* 处理照片数据
* -----------------------*/

BASE64Encoder encoder = new BASE64Encoder();
InputStream is = null;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
String picData = "";
Blob pic = ent.getZp0000();//

try{
//图片文件为空的时候,使用默认图片代替,在普通的Java类中访问服务器的图片,
//其相对路径要使用类装载器来定位
if(null==pic || pic.length()<2L){
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
if (classLoader == null) {
classLoader = WsLocationConfig.class.getClassLoader();
}
is = classLoader.getResourceAsStream("no_pic.jpg");
int c = 0;
while((c=is.read())!=-1){
baos.write(c);
}
byte[] buff = baos.toByteArray();
picData = encoder.encode(buff);
System.out.println(picData);
}else{
byte[] buff = pic.getBytes(1L, new Long(pic.length()).intValue());
picData = encoder.encode(buff);
System.out.println(picData);
}
}catch(SQLException e){
System.out.println(e.getMessage());
e.printStackTrace();
}catch(FileNotFoundException e){
System.out.println(e.getMessage());
e.printStackTrace();
}catch(IOException e){
System.out.println(e.getMessage());
e.printStackTrace();
}

reXML.append("<?xml version=\"1.0\" encoding=\"GBK\"?>");
reXML.append("<person>");
reXML.append("<picture>").append(picData).append("</picture>");
reXML.append("</person>");

return reXML.toString();


这段代码将Blob数据转化为byte[]数组,并使用BASE64Encoder 编码,将编码后的字符串组合到XML中以便传送。
2.调用端代码
调用端代码其实和普通的服务调用相同,只是将服务器的图像编码解码,得到byte[],并将这些数据存储到上下文,供图片解析Servlet来处理。为什么要将图片存储到上下文?因为图片不是从本地数据库读取,它只是以字节数据的形式临时存储到本地服务器内存中,可能的做法是给它生成一个文件存储到硬盘,但这样要频繁操作IO,而且相对路径难以处理,存储到上下文也许是比较好的选择,当然,你可能需要控制图片的数据量,对于正常的业务,这种做法已经可以承受大部分的需求了。


/* ----------------------------*
* 图片处理
* ----------------------------*/
String picData = foo.elementText("picture");
String key = foo.elementText("idcard");
BASE64Decoder decoder=new BASE64Decoder();
byte[] data=decoder.decodeBuffer(picData);

PictureMap pcm = new PictureMap(key,data);
String html = htmlBuffer.toString();
xml.setHtml(html);
xml.setPicutre(pcm); //PictureMap是存储了图片的key值和byte[]图片数据的HashMap名-值对
//将图片放到上下文
request.getSession().setAttribute("picture", xml.getPicutre().getPictureMap());
============================


3.图片显示
通过Servlet从上下文取出图片数据,并输出到前台

import java.io.IOException;

import java.util.HashMap;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class ReadXmlPicture extends HttpServlet{
private static final long serialVersionUID = 1L;

public void init(){

}

public void doGet( HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
HashMap map = null;
byte[] pic = null;
ServletOutputStream picDataToFront = null; //要输出的图片流

String key = req.getParameter("cid");//根据身份证查询
map = (HashMap)req.getSession().getAttribute("picture");

if(null==map){
return;
}

try{
pic = (byte[]) map.get(key); //取得照片
res.setContentType("image/jpeg");
picDataToFront=res.getOutputStream(); //获取输出流
for (int i = 0; i < pic.length; i++) {
picDataToFront.write(pic[i]); //输出到页面
}
picDataToFront.close(); //关闭输出流
}catch(IOException ioe){
System.out.println(ioe.getMessage());
ioe.printStackTrace();
}
}

public void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException{
doGet(req,res);
}

public void destroy(){

}

}


这样,就可以实现Web Services远程调用的图片传输了。

实现应用动态配置的方法

XML在配置方面的应用越来越普遍,刚好有个地方需要用到动态配置以减少系统硬编码的应用,顺便做了一下XML形式的配置文件读取,在这里记录一下相关代码:(待续)

使用Servlet显示存储在Oracle的Blob类型图片数据

要将图片数据保存到数据库中,一般的做法是上传图片到服务器的某个目录,而在数据库中存储图片的路径。当然这种做法简单、容易实现,且在数据库处于远程的情况下可以节省带宽,只要到网上一搜,可能是大部分人的首选做法。然而这种方法有几个弊端:1.图片不好索引,2.路径迁移会带来麻烦,3.数据完整性难以保证。

因此,必要的情况下,还需要将图片数据直接存储到数据库中,比如某些需要个人身份和照片确认的场合,这样做方便检索和变更。在ORACLE中有Blob格式可以存储任何大的二进制对象(图片就是这种格式)。下面我们按照图片上传保存、检索显示的步骤来介绍具体实现:
1.图片上传和保存(Struts+Hibernate)

[待续]

2.图片检索和显示(Hiberante+Servlet)

[待续]

2008年12月4日星期四

软件设计师考试大纲 (2008年)

中级:软件设计师

   上下午各2个半小时。上午试卷有15道必做题,内容包括:数据结构(如树和图的定义、存储和操作),程序语言及编译知识,操作系统理论知识(如进程、资源管理方法),软件工程基础知识(含面向对象方法,软件开发工具等),数据库基础知识(含SQL的使用),多媒体基础知识,网络基础知识,计算机体系结构,存储器系统,可靠性与系统评价,计算机专业英语等。这些试题涉及的内容比较广,有一定深度,也比较注重基础理论知识,考前应注意补充有关的知识以弥补自己的缺陷。
   下午试卷中共有6题。前3题为有关的分析设计图(数据流图、程序流程图、系统流程图、ER图),可选做2题,要求考生按照题意填补图中的空缺,并回答若干问题。试卷中还有1道CASL汇编语言程序题和2道C语言程序题,都是必做题。程序题中给出了算法以及不完整的程序,要求填补空缺的语句。CASL汇编语言是从许多具体机型的汇编语言中抽象出来的一种简化的、专用于考试的汇编语言。在清华大学出版社出版的考试大纲书中以及在试卷中都含有CASL汇编语言文本。如果考生并没有学过、用过任何汇编语言,则需要花费一定的时间来学习和练习。

考试内容要求
  知识点的掌握程度从深到浅可分五个层次,分别要求
为熟练掌握、掌握、理解、熟悉和了解。其中包括2个熟练
掌握(常用数据结构和常用算法、软件设计的方法和技术
)、8个掌握、2个熟悉,1个正确理解,具体体现在以下12
个方面:
  (1) 掌握数据表示、算术和逻辑运算;
   (2) 掌握相关的应用数学、离散数学的基础知识;
   (3) 掌握计算机体系结构以及各主要部件的性能和
基本工作原理;
   (4) 掌握操作系统、程序设计语言的基础知识,了
解编译程序的基本知识;
   (5) 熟练掌握常用数据结构和常用算法;
   (6) 熟悉数据库、网络和多媒体的基础知识;
   (7) 掌握C程序设计语言,以及C++、Java、Visual
Basic、Visual C++中的一种程序设计语言;
   (8) 熟悉软件工程、软件过程改进和软件开发项目
管理的基础知识;
   (9) 熟练掌握软件设计的方法和技术;
   (10) 掌握常用信息技术标准、安全性,以及有关法
律、法规的基本知识;
   (11) 了解信息化、计算机应用的基础知识;
   (12) 正确阅读和理解计算机领域的英文资料。
  分析:相对于2001年考试大纲,新大纲对知识面的要
求更宽,更注重软件设计开发的实践能力,这一点充分体
现在下午考试中。考试内容除了技术要求外,还增加了对
软件工程实践能力、安全性、标准化、法律法规等方面知
识的要求,毕竟软件设计师是软件产业的骨干,因此考试
要求相对较高。
  二、考试范围
  考试科目1:计算机与软件工程知识
  本部分包含以下内容:
  l 计算机科学基础
  l 计算机系统知识
  l 系统开发和运行知识
  l 安全性知识
  l 标准化知识
  l 信息化基础知识
  l 计算机专业英语
  1.计算机科学基础
  1.1 数制及其转换
  • 二进制、十进制和十六进制等常用制数制及其相互
转换
  1.2 数据的表示
  •(原码、反码、补码、移码表示,整数和实数的机
内表示,精度和溢出)
  • 非数值表示(字符和汉字表示、声音表示、图像表
示)
  • 校验方法和校验码(奇偶校验码、海明校验码、循
环冗余校验码)
  1.3 算术运算和逻辑运算
  • 计算机中的二进制数运算方法
  • 逻辑代数的基本运算和逻辑表达式的化简
  【分析1.1 / 1.2 / 1.3】
  本部分要求与程序员考试大纲基本一致
  重点:数的表示、校验码
  难点:数的四种编码:原码、反码、补码、移码及相
互之间的变换方法。浮点数的表示以及其规格化。
  考试题型一般分布在:数制表示、数据表示、校验码
的长度、逻辑表达式(公式、等效变换),明确“与”、
“非”、“异或”的运算规则。
  考试出现频率较高的内容:浮点数规格化、进制转换
、求反补码。
  1.4 数学基础知识
  • 命题逻辑、谓词逻辑、形式逻辑的基础知识
  • 常用数值计算(误差、矩阵和行列式、近似求解方
程、插值、数值积分)
  • 排列组合、概率论应用、应用统计(数据的统计分
析)
  • 运算基本方法(预测与决策、线性规划、网络图、
模拟)
  【分析1.4】
  考纲要求掌握相关的应用数学、离散数学的基础知识
  本部分内容作为背景知识,虽不专门考察,但在其他
题目中尤其是程序设计中出现。
  1.5 常用数据结构
  • 数组(静态数组、动态数组)、线性表、链表(单
向链表、双向链表、循环链表)、队列、栈、树(二叉树
、查找树、平衡树、线索树、线索树、堆)、图等的定义
、存储和操作
  • Hash(存储地址计算,冲突处理)
  1.6 常用算法
  • 排序算法、查找算法、数值计算方法、字符串处理
方法、数据压缩算法、递归算法、图的相关算法
  • 算法与数据结构的关系、算法效率、算法设计、算
法描述(流程图、伪代码、决策表)、算法的复杂性
  【分析1.5/1.6】
  因为数据结构是程序设计的基础,算法与数据结构密
切相关,因此本部分为考试重点内容,要求熟练掌握。
  重点:顺序存储结构和链式存储结构的特点、链表中
的头结点、栈的运算与溢出、循环队列的基本操作
  数组元素的存储位置、特殊矩阵的压缩存储、广义表
的表头和表尾求法
  二叉树存储方法、由遍历序列确定二叉树、二叉树的
计数问题
  图的遍历算法、最小生成树算法、拓朴排序和关键路
径、最短路径算法
  平均查找长度、折半查找判定树、平衡二叉树的最少
结点数、插入运算及平衡化处理、哈希表的构造和查找
  排序的稳定问题、快速排序的分析及改进、内部排序
的时间下界、算法时间复杂度分析
  难点:二叉树节点与深度的关系、图的存储(矩阵、
邻接表)与操作、遍历、
  考试出现频率较高的内容:树的遍历、数据元素存储
方式、查找树、平衡树
  考试题型一般分布在:数据元素存储方式、队和栈的
性质及其对比、链表操作、二叉树的定义及其性质、二叉
树的遍历/节点计算、树的遍历、查找树、平衡树等,图的
概念及其性质。
  常见的排序算法、查找算法、递归算法、算法的基本
特点、常用算法思想、算法效率的比较(时间、空间复杂
度)、算法描述、算法复杂性比较
  2.计算机系统知识
  2.1 硬件知识
  2.1.1 计算机系统的组成、体系结构分类及特性
   • CPU和存储器的组成、性能和基本工作原理
   • 常用I/O设备、通信设备的性能,以及基本工作
原理
   • I/O接口的功能、类型和特性
   •(中断系统、DMA、I/O处理机方式)
   • CISC/RISC,流水线操作,多处理机,并行处理
  2.1.2 存储系统
   • 主存-Cache存储系统的工作原理
   • 虚拟存储器基本工作原理,多级存储体系的性能
价格
  • RAID类型和特性
  【分析2.1.1/2.1.2】
  分析:磁盘存储器的容量计算、地址映射、流水线、
系统结构分类
  重点:Cache替换算法、内存与接口的编址方式、流水
线操作
  难点:I/O控制方式、地址计算、并行处理的概念和层

  考试出现频率较高的内容:指令的寻址方式以及指令
的执行过程;存储容量的计算、
  流水线处理机的主要指标、CISC/RISC比较等
  2.1.3 安全性、可靠性与系统性能评测基础知识
   • 诊断与容错
   • 系统可靠性分析评价
  • 计算机系统性能评测方式
  【分析2.1.3】本部分内容多为记忆型内容
  重点:数据加密/解密知识、计算机安全等级、认证技
术、数字签名等
  平均无故障时间、平均修复时间定义、计算机可靠性
模型、性能评价
  难点:数据加密/解密知识、密钥体制
  考试出现频率较高的内容:可靠性能评价、数据安全
知识、计算机可靠性及性能评价
  2.2 软件知识
  2.2.1 操作系统知识
  • 操作系统的内核(中断控制)、进程、线程概念
  • 处理机管理(状态转换、共享与互斥、分时轮转、
抢占、死锁)
  • 存储管理(主存保护、动态连接分配、分段、分页
、虚存)
  • 设备管理(I/O控制、假脱机)
   • 文件管理(文件目录、文件组织、存取方法、存
取控制、恢复处理)
   • 作业管理(作业调度、作业控制语言(JCL)、
多道程序设计)
   • 汉字处理,多媒体处理,人机界面
   • 网络操作系统和嵌入式操作系统基础知识
  • 操作系统的配置
  【分析2.2.1】
  重点:操作系统的五大管理功能,其中如处理机管理
、存储管理、作业调度、进程调度、页面调度等尤为重要
、状态转换、共享与互斥、分时轮转、抢占、死锁
  难点:作业调度、进程调度、页面调度算法、PV操作
  考试题型一般分布在:操作系统的概述、处理机管理
、存储、文件管理、作业、进程调度、进程状态变化
  考试出现频率较高的内容:PV操作、进程死锁/同步、
内存分配、并发执行程序、进程间状态转换、PV实现进程
间的同步与互斥、死锁及其避免、地址变换、页面置换、
工作集
  2.2.2 程序设计语言和语言处理程序的知识
   • 汇编、编译、解释系统的基础知识和基本工作原

   • 程序设计语言的基本成分:数据、运算、控制和
传输,过程(函数)调用
  • 各类程序设计语言主要特点和适用情况
  【分析2.2.2】
  分析:文法考查、自动机、正则式
  重点:编译程序的基本组成、正则表达式和有限自动
机、文法及语言的形式描述、中间代码、自动机原理、文
法和语言的定义
  考试出现频率较高的内容:正则表达式和正则集、有
限自动机和正则表达式的转换
  2.3 计算机网络知识
  • 网络体系结构(网络拓扑、OSI/RM、基本的网络协
议)
  • 传输介质、传输技术、传输方法、传输控制
  • 常用网络设备和各类通信设备
  • Client/Server结构、Browser/Server结构
  • LAN拓扑,存取控制,LAN的组网,LAN间连接,
LAN-WAN连接
  • 因特网基础知识以及应用
  • 网络软件
  • 网络管理
  • 网络性能分析
  【分析2.3】
  计算机网络中对OSI/RM七层的划分,及各种协议的功
能。传输理论、互联设备、Internet知识,除参考模型理
论外,其他的比较比较琐碎,需要知识积累。
  重点:网络分类、拓扑结构、OSI参考模型层次划分及
对应的协议、带宽计算、子网划分、防火墙技术
  难点:IP子网划分;传输控制、各层协议的功能
  考试题型一般分布在:这部分知识除了上面的难点部
分外,基本属于了解记忆性的知识点,知识点比较散乱,
但考试中考点均落在大纲中列出的知识点中。
  考试出现频率较高的内容:TCP/IP协议、参考模型7层
协议、IP地址的分类、各种网络设备的比较;
  2.4 数据库知识
  • 数据库管理系统的功能和特征
  • 数据库模型(概念模式、外模式、内模式)
  • 数据模型,ER图,第一范式、第二范式、第三范式
  • 数据操作(集合运算和关系运算)
  • 数据库语言(SQL)
  • 数据库的控制功能(并发控制、恢复、安全性、完
整性)
  • 数据仓库和分布式数据库基础知识
  【分析2.4】
  无论对上午题目还是下午题目,本部分都很重要,这
是学习和复习的一个重点。
  重点:用E-R模型表示现实问题,扩充的E-R模型、ER
图向关系模式的转换、数据库语言(SQL)、对SELECT查询
语句的应用、关系代数表达式、关系模型的完整性约束、
函数依赖
  难点:五种基本的关系运算方法、组合的关系运算方
法、规则化理论()、函数依赖、分解的保持函数依赖、
无损连接又保持函数依赖的分解、判断一个分解的无损连
接性和保持函数
  考试出现频率较高的内容:数据模型、关系运算与SQL
语句的转换、函数依赖
  2.5 多媒体知识
  • 多媒体系统基础知识,多媒体设备的性能特性,常
用多媒体文件格式
  • 简单图形的绘制,图像文件的处理方法
  • 音频和视频信息的应用
  • 多媒体应用开发过程
  【分析2.5】
  基本属于了解记忆性的知识点。
  重点:多媒体的基本概念、声音数字化的过程、图像
压缩编码、视频文件的国际标准、流媒体概念、多媒体计
算机系统、虚拟现实技术
  难点:多媒体信息的容量计算、数据压缩、常用多媒
体文件格式、音频和视频的物理特征等
  考试出现频率较高的内容:基本知识、信息的容量计

  2.6 系统性能知识
  • 性能指标(响应时间、吞吐量、周转时间)和性能
设计
  • 性能测试和性能评估
  • 可靠性指标及计算、可靠性设计
  • 可靠性测试和可靠性评估
  【分析2.6】
  重点:可靠性评估
  考试出现频率较高的内容:系统可靠性的计算、设备
周转时间
  2.7 计算机应用基础知识
  • 信息管理、数据处理、辅助设计、自动控制、科学
计算、人工智能等基础知识
  • 远程通信服务基础知识
  • 常用应用系统
  【分析2.7】
  本部分内容不是考试重点,做一般了解即可。
  3.系统开发和运行知识
  本部分内容涉及上午考试和下午考试,是软件设计师
工作能力的考查重点。
  3.1 软件工程、软件过程改进和软件开发项目管理知

  • 软件工程知识
  • 软件开发生命周期各阶段的目标和任务
  • 软件开发项目管理基础知识(时间管理、成本管理
、质量管理、人力资源管理、风险管理等)及其常用管理
工具
  • 主要的软件开发方法(生命周期法、原型法、面向
对象法、CASE)
  • 软件开发工具与环境知识
  • 软件过程改进知识
  • 软件质量管理知识
  • 软件开发过程评估、软件能力成熟评估基础知识
  【分析3.1】
  重点:软件开发方法、CMM、成本估算、风险分析、进
度管理、人员管理、软件开发环境
  3.2 系统分析基础知识
  • 系统分析的目的和任务
  • 结构化分析方法(数据流图(DFD)、数据字典
(DD)、实体关系图(ERD)、描述加工处理的结构化语言

  • 统一建模语言(UML)
  • 系统规格说明书
  【分析3.2】
  高度重视UML在系统分析中的应用
  重点:数据流图(DFD)、数据字典(DD)、实体关系
图(ERD)
  考点:UML的各类图
  3.3 系统设计知识
  • 系统设计的目的和任务
  • 结构化设计方法和工具(系统流程图、HIPO图、控
制流程图)
  • 系统总体结构设计(总体布局、设计原则、模块结
构设计、数据存储设计、系统配置方案)
  • 系统详细设计(代码设计、数据库设计、用户界面
设计、处理过程设计)
  • 系统设计说明书
  【分析3.3】
  重点:系统流程图、HIPO图、控制流程图
  3.4 系统实施知识
  • 系统实施的主要任务
  • 结构化程序设计、面向对象程序设计、可视化程序
设计
  • 程序设计风格
  • 程序设计语言的选择
  • 系统测试的目的、类型,系统测试方法(黑盒测试
、白盒测试、灰盒测试)
  • 测试设计和管理(错误曲线、错误排除、收敛、注
入故障、测试用例设计、系统测试报告)
  • 系统转换基础知识
  3.5 系统运行和维护知识
  • 系统运行管理基础知识
  • 系统维护基础知识
    • 系统评价基础知识
  【分析3.4/3.5】
  重点:结构化设计中信息流、变换分析、系统结构设
计原则、系统划分、模块设计、数据存储设计、面向对象
程序设计、测试方法、系统维护的分类
  难点:系统测试方法、测试分类、系统可维护性评价
指标
  3.6 面向对象开发方法
  • 面向对象开发概念(类、对象、属性、封装性、继
承性、多态性、对象之间的引用)
  • 面向对象开发方法的优越性以及有效领域
  • 面向对象设计方法(体系结构、类的设计、用户接
口设计)
  • 面向对象实现方法(选择程序设计语言、类的实现
、方法的实现、用户接口的实现、准备测试数据)
  • 面向对象程序设计语言(如C++、Java、Visual、
Bsasic、Visual C++)的基本机制
  • 面向对象数据库、分布式对象的概念
  【分析3.6】
  重点:面向对象开发:类、对象、属性、封装性、继
承性、多态性、OMT方法
  难点:建议在数据流图、结构化分析方法上多加掌握

  【分析3】
  考试题型一般分布在:DFD、软件的生存周期;数据流
图;模块间的关系;软件测试的分类、软件质量管理(标
准)软件的特性、主要的软件开发方法、系统测试、软件
能力成熟评估
  考试出现频率较高的内容:数据流图、黑盒/白盒测试
、面向对象技术的概念
  4.安全性知识
  • 安全性基本概念
  • 防治计算机病毒、防范计算机犯罪
  • 存取控制、防闯入、安全管理措施
  • 加密与解密机制
  • 风险分析、风险类型、抗风险措施和内部控制
  【分析4】
  系统安全问题是目前社会关注的问题,也是应用价值
较高的知识,可结合现实中的相关问题来加深理解。
  考试出现频率较高的内容:加密与解密算法、
  5.标准化知识
  • 标准化意识、标准化的发展、标准制订过程
  • 国际标准、国家标准、行业标准、企业标准基本知

  • 代码标准、文件格式标准、安全标准、软件开发规
范和文档标准知识
  • 标准化机构
  6.信息化基础知识
  • 信息化意识
  • 全球信息化趋势、国家信息化战略、企业信息化战
略和策略
  • 有关的法律、法规
  • 远程教育、电子商务、电子政务等基础知识
  • 企业信息资源管理基础知识
   【分析5/6】
  信息化、标准化知识是新增考点。标准化方面有标准
标识,标准修订等是对基本素质的考查,也要重视。
  考试出现频率较高的内容
  7.计算机专业英语
  • 掌握计算机技术的基本词汇
  • 能正确阅读和理解计算机领域的英文资料
  【分析7】
  专业英语,是对专业知识和英语水平的考查,考前需
有意识阅读点英文专业资料。
  考试题型一般分布在:软件行业标准,计算机安全基
础知识,信息化基础知识。
  考试出现频率较高的内容:行业标准的类别;计算机
安全,CMM分类,计算机软件著作权问题。
  考试科目2:软件设计
  本部分具体内容如下:
  l 外部设计
  l 内部设计
  l 程序设计
  l 系统实施
  l 软件工程
  本部分所涉及内容为软件设计的日常工作,这些内容
同样出现在上午考试试题中。
  1.外部设计
  1.1 理解系统需求说明
  1.2 系统开发的准备
  • 选择开发方法、准备开发环境、制订开发计划
  1.3 设计系统功能
  • 选择系统结构,设计各子系统的功能和接口,设计
安全性策略、需求和实现方法,制订详细的工作流和数据

  1.4 设计数据模型
  • 设计ER模型、数据模型
  1.5 编写外部设计文档
  • 系统配置图、各子系统关系图、系统流程图、系统
功能说明书、输入输出规格说明、数据规格说明、用户手
册框架
  • 设计系统测试要求
  1.6 设计评审
  应能由考试说明内容,来阅读
  2.内部设计
  2.1 设计软件结构
  • 按构件分解,确定构件功能规格以及构件之间的接

  • 采用中间件和工具
  2.2 设计输入输出
  • 屏幕界面设计、设计输入输出检查方法和检查信息
  2.3 设计物理数据
  • 分析数据特性,确定逻辑数据组织方式、存储介质
,设计记录格式和处理方式
  • 将逻辑数据结构换成物理数据结构,计算容量,进
行优化
  2.4 构件的创建和重用
  • 创建、重用构件的概念
  • 使用子程序库或类库
  2.5 编写内部设计文档
  • 构件划分图、构件间的接口、构件处理说明、屏幕
设计文档、报表设计文档、文件设计文档、数据库设计文

  2.6 设计评审
  3.程序设计
  3.1 模块划分(原则、方法、标准)
  3.2 编写程序设计文档
  • 模块规格说明书(功能和接口说明、程序处理逻辑
的描述、输入输出数据格式的描述)
  • 测试要求说明书(测试类型和目标、测试用例、测
试方法)
  3.3 程序设计评审
  4.系统实施
  4.1 配置计算机系统及其环境
  4.2 选择合适的程序设计语言
  4.3 掌握C程序设计语言,以及C++、Java、Visual
Basic、Visual C++中任一种程序设计语言,以便能指导程
序员进行编程和测试,并进行必要的优化
  4.4 系统测试
  • 指导程序员进行模块测试,并进行验收
  • 准备系统集成测试环境和测试工具
  • 准备测试数据
  • 写出测试报告
  5.软件工程
  • 软件生存期模型(瀑布模型、螺旋模型、喷泉模型
)和软件成本模型
  • 定义软件需求(系统化的目标、配置、功能、性能
和约束)
  • 描述软件需求的方法(功能层次模型、数据流模型
、控制流模型、面向数据的模型、面向对象的模型等)
  • 定义软件需求的方法(结构化分析方法、面向对象
分析方法)
  • 软件设计(分析与集成、逐步求精、抽象、信息隐
蔽)
  • 软件设计方法(结构化设计方法、Jackson方法、
Warnier方法、面向对象设计方法)
  • 程序设计(结构化程序设计、面向对象程序设计)
  • 软件测试的原则与方法
  • 软件质量(软件质量特性、软件质量控制)
  • 软件过程评估基本方法、软件能力成熟度评估基本
方法
  • 软件开发环境和开发工具(分析工具、设计工具、
编程工具、测试工具、维护工具、CASE)
  • 软件工程发展趋势(面向构件,统一建模语言
(UML))
  • 软件过程改进模型和方法
  本部分综合分析:
  软件设计师,关键是设计软件的能力。考纲要求:要
熟悉软件工程、软件过程改进和软件开发项目管理的基础
知识;熟练掌握软件设计的方法和技术;掌握C程序设计语
言及指定的四种面向对象语言中的一种。这部分专业能力
严重依赖工作实践,要求有一定经验的积累,是具有工程
师的实际工作能力和业务水平的体现。如无实践经验,要
学会借鉴,以取人之长,补已之短。
  这部分主要体现在下午考试中,现就如何应对下午考
试进行分析:
  近几次考试中下午试题分五个题目,一个数据库,一
个程序填空题、一个面向对象的语言题,另两个题目分别
为数据流图、UML、或流程图等。
  数据库题目,要求补全SQL语言,这要求考生熟悉SQL
的语言,无论对上午题目还是下午题目都很重要。这是学
习和复习的一个重点。
  数据流图,DFD是一种分析系统数据流程的图形,意在
让用户理解系统的功能、输入、输出和数据存储等。请认
真弄清其应用,在画出数据流图的情况下,系统的功能也
就确定了,再经过细化,逐步向物理结构迈进。考核时,
试题多从父图和子图的平衡来分析。这部分内容,一个解
题的关键是高度重视题目说明,务必正确、深入理解其内
容,必要时要读几遍,同时对于给出的图表,也要务必看
懂。这样答题就轻松了,答案实际就蕴含在说明中。
  流程图类题目,是大家再熟悉不过的了,它就一个具
体问题的解题思路进行描述,是面向过程的。但所求问题
是千差万别的,因此应理解思路,细心作答。
  答题形式最简单也是难度最大的是程序填空。为便于
阅卷,这类题目以程序填空形式出现,这不仅要求理解问
题本质,同时也要弄清作者解题思路,这一点比自己独立
完成程序设计要难得的多。针对问题,首先设计自己的思
路,如何解决问题,先后顺序怎样;然后试读程序,如何
思路大体一致,很好,这题容易解决了。如思路不一致,
设法弄清每一段代码的功能,其逻辑结构怎样,进而弄清
命题人的解题思路,再顺势解决问题。人们常讲,答案就
在题目中,这是对的。在分析问题过程中,找到所求答案
。不过前提条件是考生要熟悉这种语言,又要明白解题思
路,这样才能正确作答。这个题目比较难,要么不得分,
要么得全分。
  近年对于统一建模语言UML考查较多,已引起了考生的
注意。它代表了软件工程的发展趋势,目前是可视化建模
的事实上的工业标准。人们对于图的理解相对其他形式更
容易一些,图能更清晰地描述和说明问题的本质,因此,
UML体现了这一特点。这类题目难度与数据流图相似,自然
解题思想也相同。从形式上看,数据流图更朴实一些,UML
类的题目则透出一种新颖、现代的气息。
  最后的题目面向对象语言是一个选做题,给考生以自
由,可以发挥个人的优势。命题已注意到不同语言的考查
难度一致性,要求考生就同一问题回答,实现了形式上的
公平,自然是一个进步。

2008年11月5日星期三

jBPM用户指南-CH07

第七章 持久化 Persistence

在大多数场合,jBPM用来在一段很长的时间内维持流程的执行。在这个上下文中,“一段很长的时间”意思是跨越很多个事务。持久化的主要意图是在等待状态下存储流程执行。因此我们把流程执行想像成一个状态机。在一个事务中,我们要把流程执行状态机从一个状态移动到下一个状态。
一个流程定义可以被表述成3种形式:作为XML,作为一个java对象和作为jBPM数据库中的一条记录。可执行(运行时)的信息和日志信息可以被表述成两种形式:作为一个java对象和作为jBPM数据库的一条记录。



图 7.1. The transformations and different forms

更多关于流程定义和流程归档的XML表示形式,请看第16章jBPM Process Definition Language (JPDL)。
更多关于如何部署一个流程归档到一个数据库的信息,请看16.1.1节,“部署一个流程归档”。

7.1. 持久化 API
7.1.1. 配置框架的相互关系
持久化API是和配置框架暴露在JbpmContext之外的一些简单易用的方法的一个集成。因此持久化API操作能够在一个jBPM上下文块内被调用,如下:
JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
try {

// Invoke persistence operations here

} finally {
jbpmContext.close();
}

在下文中,我们假设配置包含一个和这个例子(配置文件src/config.files/jbpm.cfg.xml)相似的持久化服务。
<jbpm-configuration>

<jbpm-context>
<service name='persistence' factory='org.jbpm.persistence.db.DbPersistenceServiceFactory' />
...
</jbpm-context>
...
</jbpm-configuration>

7.1.2. JbpmContext的易用的方法
3中最为常见的持久化操作是:
部署一个流程
启动一个流程的执行
继续一个执行

首先部署一个流程定义。典型地,这一步将在一个流程图形设计器或用部署流程的ant任务直接完成。但这里你可以看到如何用编程的方式完成:

JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
try {
ProcessDefinition processDefinition = ...;
jbpmContext.deployProcessDefinition(processDefinition);
} finally {
jbpmContext.close();
}

为了创建一个流程执行,我们需要指定这个被实例化的执行是属于哪个流程定义。指定这个最为通用的方法是引用流程的名字且让jBPM在数据库中找到流程的最新版本:
JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
try {
String processName = ...;
ProcessInstance processInstance =
jbpmContext.newProcessInstance(processName);
} finally {
jbpmContext.close();
}

为了继续一个流程执行,我们需要从数据库获取流程实例、令牌或任务实例,在POJO jBPM对象调用一些方法,然后再次保存对流程实例所作的更新到数据库中。

JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
try {
long processInstanceId = ...;
ProcessInstance processInstance =
jbpmContext.loadProcessInstance(processInstanceId);
processInstance.signal();
jbpmContext.save(processInstance);
} finally {
jbpmContext.close();
}

注意,如果你在JbpmContext使用xxxForUpdate方法,明确的jbpmContext.save方式将不再需要,因为在关闭jbpmContext期间这一步将自动发生。例如:假设我们想要告知jBPM我们已经完成一个任务实例。需要注意的是,任务实例完成将触发执行继续,因此关联到任务实例的流程实例必须保存。要完成这种需求的最为方便的做法是使用loadTaskInstanceForUpdate方法:
JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
try {
long taskInstanceId = ...;
TaskInstance taskInstance =
jbpmContext.loadTaskInstanceForUpdate(taskInstanceId);
taskInstance.end();
} finally {
jbpmContext.close();
}

下面一部分是关于jBPM如何管理持久化和使用Hibernate的一个说明,只作为一个背景知识。
JbpmConfiguration维护了一个ServiceFactorys的集合。服务工厂在前面说说的jbpm.cfg.xml配置,并以lazy的方式实例化。

DbPersistenceServiceFactory只有在第一次需要的时候实例化。实例化以后,服务工厂在JbpmConfiguration维护。DbPersistenceServiceFactory管理一个hibernate SessionFactory。但同样的,hibernate session factory当在第一次请求的时候以lazy的方式创建。



Figure 7.2. The persistence related classes

在调用jbpmConfiguration.createJbpmContext()的过程中,只有JbpmContext被创建。那时没有更多持久化相关的初始化操作。JbpmContext管理一个DbPersistenceService,DbPersistenceService是在第一次请求时实例化的。DbPersistenceService管理一个hibernate会话。同样的,一个DbPersistenceService之内的hibernate会话以lazy方式创建。因而,一个hibernate会话将只有在第一个请求持久化的操作被调用的时候打开。

7.1.3. 高级 API 用法
DbPersistenceService维护一个lazy初始化的hibernate会话。所有的数据库访问通过这个hibernate会话来完成。所有由jBPM完成的查询和更新是通过XxxSession形式的类来表现,比如GraphSession,SchedulerSession,LoggingSession,...这些会话类与hibernate查询有关,且底层全部使用相同的hibernate会话。
XxxSession类也是通过JbpmContext才可以访问的。

7.2. 配置持久化服务
7.2.1. Hibernate 会话工厂
默认情况下,DbPersistenceServiceFactory将使用ClassPath根目录下的hibernate.cfg.xml资源文件来创建hibernate会话工厂。需要注意的是hibernate配置文件资源和'jbom.hibernate.cfg.xml'属性相映射,且可以在jbpm.cfg.xml中被定制。以下是默认的配置:

<jbpm-configuration>
...
<!-- configuration resource files pointing to default configuration files in jbpm-{version}.jar -->
<string name='resource.hibernate.cfg.xml'
value='hibernate.cfg.xml' />
<!-- <string name='resource.hibernate.properties'
value='hibernate.properties' /> -->
...
</jbpm-configuration>

当resouce.hibernate.properties属性被指定,这些在那个资源文件中的properties将覆盖在hibernate.cfg.xml文件中的所有properties。hibernate.properties可以被用来处理jbpm方便升级:(hibernate.cfg.xml可以被拷贝而不必重新应用改变),而不是使用修改hibernate.cfg.xml的方式来指向你的数据库。

7.2.2. DbPersistenceServiceFactory
DbPersistenceServiceFactory本身有另外三个配置properties:isTransactionEnabled,sessionFactoryJndiName,dataSourceJndiName。想要在jbpm.cfg.xml中指定这些properties,你需要在factory元素中将服务工厂指定为一个bean。就如:
<jbpm-context>
<service name="persistence">
<factory>
<bean factory="org.jbpm.persistence.db.DbPersistenceServiceFactory">
<field name="isTransactionEnabled"><false /></field>
<field name="sessionFactoryJndiName">
<string value="java:/myHibSessFactJndiName" />
</field>
<field name="dataSourceJndiName">
<string value="java:/myDataSourceJndiName" />
</field>
</bean>
</factory>
</service>
...
</jbpm-context>

isTransactionEnabled: 默认情况下,jBPM将会开始和结束hibernate事务。为了禁用事务和阻止jPBM用hibernate管理事务,可以在上述的例子中将isTransactionEnabled配置为false。更多关于事务的信息,请看7.3节,“Hibernate事务”。
sessionFactoryJndiName: 默认情况下,这个值为null,意为会话工厂不是从JNDI获取的。如果这个值被设置,且需要会话工厂来创建一个hibernate会话,那么会话工厂将会使用提供的JNDI名称来被获取。
dataSourceJndiName: 默认情况下,这个值为null,且创建JDBC连接的工作将委托给hibernate。通过制定一个数据源,jBPM将从数据源获取一个JDBC连接,并会在打开一个新的会话时将连接提供给hibernate。要了解用户提供的JDBC连接,请看7.5节,“User provided stuff”。

7.3. Hibernate事务
默认情况下,jBPM将事务委托给hiberante,并在每个事务模式使用会话。当一个hibernate会话打开的时候,jPBM将开始一个hibernate事务。这将在JbpmContext之上的一个持久化操作初次调用的时候发生。事务将在hibernate会话关闭之前正确提交。提交操作在jbpmContext.close()方法内部完成。

使用jbpmContext.setRollbackOnly()来标记一个要回滚的事务。在那种情况下,事务将在jbpmContext.close()方法内执行会话关闭之前被正确回滚。

想要禁止jBPM调用hibernate API之上的任何事务方法,可以如7.2.2节“The DbPersistenceServiceFactory”所讲的将isTransactionEnabled属性设置为false。

7.4. 管理事务
管理事务最为常见的场景是当你在一个诸如JBoss的JEE应用服务器使用jBPM的时候。最为常见的场景有如下几种:
在你的应用服务器配置数据源
配置hibernate来使用数据源的连接
使用容器管理的事务
在jBPM中禁用事务

7.5. User provided stuff
你可以用编程的方式提供JDBC连接,Hiberante会话和Hiberante会话工厂给JBPM.
当这样的一个资源提供给jBPM,它将使用提供的资源而不是使用原先配置的那一个。
JbpmContext类包含了一些便利的方法来以编程的方式注入资源。比如,提供一个JDBC连接给jBPM可以使用如下代码:
JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
try {
Connection connection = ...;
jbpmContext.setConnection(connection);

// invoke one or more persistence operations

} finally {
jbpmContext.close();
}

JbpmContext 类有以下几个便利的方法来以编程的方式提供资源:

JbpmContext.setConnection(Connection);
JbpmContext.setSession(Session);
JbpmContext.setSessionFactory(SessionFactory);

7.6.定制查询
所有jBPM使用的HQL查询都集中在一个配置文件中。那个资源文件在hibernate.cfg.xml配置文件被引用:
<hibernate-configuration>
...
<!-- hql queries and type defs -->
<mapping resource="org/jbpm/db/hibernate.queries.hbm.xml" />
...
</hibernate-configuration>
为了定义一个或多个那样的查询,可以拷贝一份原始文件并将你的新版本防止到CLASSPATH的某个地方,然后在hibernate.cfg.xml更新引用'org/jbpm/db/hibernate.queries.hbm.xml'来指向你的定制版本。
7.7. 数据库兼容性
jBPM运行于任何被hibernate支持的数据库之上。
jBPM的实例配置文件(src/config.files)指定了hypersonic内存数据库。那个数据库在开发期间和测试都相当理想。hypersonic内存数据库将它的所有数据库保存在内存,不放到磁盘上。

7.7.1. 改变jBPM数据库
下面的指示列表是在当你想改变jBPM以使用另一个数据库时要做的相关事情:
把jdbc驱动程序jar包放到CLASSPATH中
更新jBPM使用的hibernate配置
在新的数据库创建数据库表

7.7.2. jBPM 数据库方案表(Schema)
jbpm.db子项目,包含了一些驱动、指示和脚本来帮助你对你选择的数据库有个入门。请参考jbpm.db项目根目录下的readme.html来了解更多信息。

尽管jBPM能够为所有的数据库产生DDL脚本,但这些表并不总是经过优化的。所以你可能需要你的数据库管理员检查生成的DDL,以优化字段类型和使用索引。
在开发中你可能插入如下的hibernate配置:如果你把hibernate配置属性‘hibernate.hbm2ddl.auto’设置为‘create-drop’(比如在hibernate.cfg.xml),那么这些数据库表将在第一次在应用中使用到的时候自动创建到数据库中。当应用关闭,数据库表将被删除。

Schema的产生也可以通过jbpmConfiguration.createSchema()和jbpmConfiguration.dropSchema()方法用编程的方式调用。


7.8. 结合你的Hibernate类
在你的项目中,你可能会用Hibernate来做持久化。将你的持久化类和jBPM持久化类组合是可选的。将你的Hibernate持久化类和jBPM的Hibernate持久化结合有两个主要的好处:

首先,会话、连接和事务管理变得更加容易。通过结合jBPM和你的持久化到一个Hibernate会话工厂,将有【一个】hibernate会话,【一个】JDBC连接来处理你的和jBPM的持久化。所以自动地,当jBPM更新操作和在你自己的域模型的更新操作是在同一个事务当中。这可以排除使用一个事务管理的需求。

其次,这将允许你删除你的持久化对象到流程变量中而不用更多的处理。

集成你的持久化类和jBPM持久化类最简单的方式是通过创建一个中心的hibernate.cfg.xml.你可以把jBPM src/config.flies/hibernate.cfg.xml作为一个出发点并在那里添加引用到你自己的hibernate映射文件。

7.9. 定制jBPM hibernate映射文件
要定制任何jBPM的hibernate映射文件,你可以做如下处理:
从源代码src/java.jbpm/...或从jbpm.jar文件内部拷贝你想要拷贝的jBPM hibernate映射文件,并将这些拷贝放在CLASSPATH中你想要的任何地方。在配置文件hibernate.cfg.xml中更新引用到定制的映射文件。

7.10. 第二层缓存
jBPM使用hibernate的第二层缓存以便一旦流程定义装载后在内存中保存流程定义。流程定义类和集合在 jBPM Hibernate映射文件配置,cache元素的配置如:

<cache usage="nonstrict-read-write"/>

尽管流程定义(可能)不会变动,保存在第二层缓存也是可以的。详见第16章:“Changing deployed process definitions”
第二层缓存是JBoss jBPM实现的一个很重要的方面。如果没有缓存,JBoss jBPM和其他技术实现的BPM引擎相比可能有一系列的不足。

缓存策略被设置为:nonstrict-read-write。在运行时,缓存策略可能被设置为只读。但在那种情况下,你可能需要一组独立的hibernate映射文件来部署一个流程。这也是为什么我们选择nonstrict-read-write的原因。

2008年10月28日星期二

jBPM用户指南-CH06


第六章 配置

配置jBPM的最简单的方式是将jbpm.cfg.xml配置文件放到classpath的根目录中。如果那个文件不能作为一个资源被找到,包含在 jbpm库中的默认的最小化配置将被使用。记住最小化的配置没有任何持久化的配置信息。
jBPM配置通过org.jbpm.JbpmConfiguration类来描述。最简单的获取JbpmConfiguration的方式是使用单态实例方法JbpmContext.getInstance();
如果你想从另一个来源装载配置信息,你可以使用JbpmConfiguration。parseXxxx方法。

static JbpmConfinguration jbpmConfiguration = JbpmConfinguration.getInstance();
JbpmConfiguration是线程安全的,因此可以是一个静态成员。所有的线程都可以用JbpmConfiguration作为JbpmContext对象的一个工厂。一个JbpmContext典型地代表了一个事务。JbpmContext使得在一个context块中的服务可用。一个context块看起来就像这样:
try {
// This is what we call a context block.
// Here you can perform workflow operations

} finally {
jbpmContext.close();
}
JbpmContext使得一系列的服务和配置对于JBPM可用。这些服务在jbpm.cfg.xml配置文件中配置,并使得jBPM可以运行在任何Java环境且在那个环境中无论使用哪个服务都可用。
这是一个JbpmContext典型的配置,你可以在src/config.files/jbpm.cfg.xml中找到它:
<jbpm-configuration>

<jbpm-context>
<service name='persistence' factory='org.jbpm.persistence.db.DbPersistenceServiceFactory' />
<service name='message' factory='org.jbpm.msg.db.DbMessageServiceFactory' />
<service name='scheduler' factory='org.jbpm.scheduler.db.DbSchedulerServiceFactory' />
<service name='logging' factory='org.jbpm.logging.db.DbLoggingServiceFactory' />
<service name='authentication' factory='org.jbpm.security.authentication.DefaultAuthenticationServiceFactory' />
</jbpm-context>

<!-- configuration resource files pointing to default configuration files in jbpm-{version}.jar -->
<string name='resource.hibernate.cfg.xml' value='hibernate.cfg.xml' />
<!-- <string name='resource.hibernate.properties' value='hibernate.properties' /> -->
<string name='resource.business.calendar' value='org/jbpm/calendar/jbpm.business.calendar.properties' />
<string name='resource.default.modules' value='org/jbpm/graph/def/jbpm.default.modules.properties' />
<string name='resource.converter' value='org/jbpm/db/hibernate/jbpm.converter.properties' />
<string name='resource.action.types' value='org/jbpm/graph/action/action.types.xml' />
<string name='resource.node.types' value='org/jbpm/graph/node/node.types.xml' />
<string name='resource.parsers' value='org/jbpm/jpdl/par/jbpm.parsers.xml' />
<string name='resource.varmapping' value='org/jbpm/context/exe/jbpm.varmapping.xml' />

<int name='jbpm.byte.block.size' value="1024" singleton="true" />
<bean name='jbpm.task.instance.factory' class='org.jbpm.taskmgmt.impl.DefaultTaskInstanceFactoryImpl' singleton='true' />
<bean name='jbpm.variable.resolver' class='org.jbpm.jpdl.el.impl.JbpmVariableResolver' singleton='true' />
<long name='jbpm.msg.wait.timout' value='5000' singleton='true' />

</jbpm-configuration>
在文件中你可以看到3个部分:
第一个片段用一些列服务的实现配置了jbpm上下文,可能的配置选项在涵盖特定服务实现的章节中有介绍。
第二部分是对配置资源的所有的引用映射。如果你想定制其中一个配置文件,那么资源引用可以被更新。典型的,你制作默认配置的一个拷贝,这个默认配置是在jbpm-3.x.jar中,切放置在classpath的某个地方。那么你在这个文件更新了引用,那么jbpm将使用你定制的那个配置文件的版本。
第三部分是在jbpm使用的一些别称(miscallanious)。这些配置选项在涵盖了指定主题的章节有描述。

服务的默认配置集合的目标定位是最小的web应用环境和最小的依赖。持久化服务将获取一个jdbc连接,且所有其他的服务将使用相同的连接来完成他们的服务。所以你的所有工作流操作将被集中到1个JDBC连接事务内,而不再需要一个事务管理器。

JbpmContext包含了大多数流程操作的方便的方法:
public void deployProcessDefinition(ProcessDefinition processDefinition) {...}//流程部署
public List getTaskList() {...}//获取任务列表
public List getTaskList(String actorId) {...}//通过actorId获取任务列表
public List getGroupTaskList(List actorIds) {...}//通过一个actorId列表来获取一组工作列表
public TaskInstance loadTaskInstance(long taskInstanceId) {...}//装载任务实例
public TaskInstance loadTaskInstanceForUpdate(long taskInstanceId) {...}//装载任务实例以更新
public Token loadToken(long tokenId) {...}//装载令牌
public Token loadTokenForUpdate(long tokenId) {...}//装载令牌以更新
public ProcessInstance loadProcessInstance(long processInstanceId) {...}//装载流程实例
public ProcessInstance loadProcessInstanceForUpdate(long processInstanceId) {...}//装载流程实例以更新
public ProcessInstance newProcessInstance(String processDefinitionName) {...}//产生新的流程实例
public void save(ProcessInstance processInstance) {...}//保存流程实例
public void save(Token token) {...}//保存令牌
public void save(TaskInstance taskInstance) {...}//保存任务实例
public void setRollbackOnly() {...}//设置回滚

注意XxxForUpdate方法将注册装载的对象以自动保存,所以你不必再明确地调用一个save方法。

可以指定多个jbpm-context,但另一方面你必须确保每一个jbpm-context必须拥有一个唯一的name属性。命名的上下文可以用JbpmConfiguration.createContext(String name)来获取。
一个service项指定了一个服务的名字和服务的service factory。这个服务将只在用JbpmContext.getServices().getService(String name)方法要求创建时被创建。
工厂也可以被指定为一个element元素来替代attribute属性,那可能需要在factory对象注入一些配置信息。负责解析XML,创建和写入对象的组件成为对象工厂(the object factory)。

6.1. Configuration properties

jbpm.byte.block.size: 文件附件和二进制变量存储在数据库中。不是作为二进制大对象(blob)存储,而是作为一些固定大小的二进制对象列表。这是为了达到在不同的数据库中提高移植性和提高jBPM的总体嵌入性。这个参数控制了这个固定长度块的大小。

jbpm.task.instance.factory: 为了定制任务实例的创建方式,在这个属性中指定了完全限定类名。当你想要定制TaskInstance bean且要添加一个新的properties到TaskIntance Bean时可能需要这么做。(请看Section 11.10,“定制任务实例”),指定的类需要实现org.jbpm.taskmgmt.TaskIntanceFactory.

jbpm.variable.resolver: 为了定制jBPM将要查找的在JSF形式的表达式第一项的方法。

jbpm.msg.wait.timout: To customize the time inbetween polls for messages.

6.2. Configuration 文件
这是在jBPM可定制的所有配置文件的简短描述。

6.2.1. Hibernate cfg xml 文件
这个文件包含hibernate配置和hibernate映射资源文件的引用。
位置:hibernate.cfg.xml除非特别指定,否则在jbpm.properties文件的jbpm.hibernate.cfg.xml属性指定。在jbpm工程,默认的hibernate.cfg.xml文件处在src/config.files/hibernate.cfg.xml目录下。


6.2.2. Hibernate 查询配置文件
该文件包含hibernate查询,这些查询在jBPM 会话 org.jbpm.db.*Session中使用。
位置:org/jbpm/db/hibernate.queries.hbm.xml

6.2.3. 节点类型配置文件
该文件XML节点元素到节点实现类的映射。
位置:org/jbpm/graph/node/node.types.xml


6.2.4. Action 类型配置文件
该文件包含了XML action元素到Action实现类的映射
位置:org/jbpm/graph/action/action.types.xml


6.2.5. 业务日历配置文件
包含了业务时间和自由时间的配置
位置:org/jbpm/calendar/jbpm.business.calendar.properties
Location: org/jbpm/calendar/jbpm.business.calendar.properties

6.2.6. 变量映射配置文件
指定流程变量(java对象)的值如何被转化为实例变量来存储到jbpm数据库中。
位置:org/jbpm/context/exe/jbpm.varmapping.xml


6.2.7. Converter 配置文件
指定id-to-classname映射。id存储在数据库中。org.jbpm.db.hibernate.ConverterEnumType用来映射id到单独的对象。
位置:org/jbpm/db/hibernate/jbpm.converter.properties


6.2.8. 默认 modules 配置文件
指定那个modules被默认添加到一个新的流程定义中。
位置: org/jbpm/graph/def/jbpm.default.modules.properties

6.2.9. 流程归档解析配置文件
指定流程归档解析安排。
位置: org/jbpm/jpdl/par/jbpm.parsers.xml

6.3.对象工厂(Object factory)
对象工厂能通过beans-like XML配置文件来创建对象。配置文件指定对象应该如何被创建、配置和连接在一起来形成一个完整的对象图。对象工厂可以注入配置和其他bean到一个bean中。
在最简单的形式下,对象工厂能通过如下的配置来创建基本类型和java beans。

<beans>
<bean name="task" class="org.jbpm.taskmgmt.exe.TaskInstance"/>
<string name="greeting">hello world</string>
<int name="answer">42</int>
<boolean name="javaisold">true</boolean>
<float name="percentage">10.2</float>
<double name="salary">100000000.32</double>
<char name="java">j</char>
<null name="dusttodust" />
</beans>

---------------------------------------------------------

ObjectFactory of = ObjectFactory.parseXmlFromAbove();
assertEquals(TaskInstance.class, of.getNewObject("task").getClass());
assertEquals("hello world", of.getNewObject("greeting"));
assertEquals(new Integer(42), of.getNewObject("answer"));
assertEquals(Boolean.TRUE, of.getNewObject("javaisold"));
assertEquals(new Float(10.2), of.getNewObject("percentage"));
assertEquals(new Double(100000000.32), of.getNewObject("salary"));
assertEquals(new Character('j'), of.getNewObject("java"));
assertNull(of.getNewObject("dusttodust"));
Also you can configure lists:

<beans>
<list name="numbers">
<string>one</string>
<string>two</string>
<string>three</string>
</list>
</beans>
和 maps

<beans>
<map name="numbers">
<entry><key><int>1</int></key><value><string>one</string></value></entry>
<entry><key><int>2</int></key><value><string>two</string></value></entry>
<entry><key><int>3</int></key><value><string>three</string></value></entry>
</map>
</beans>
Beans可以用直接的域注入和通过property的setter被配置。

<beans>
<bean name="task" class="org.jbpm.taskmgmt.exe.TaskInstance" >
<field name="name"><string>do dishes</string></field>
<property name="actorId"><string>theotherguy</string></property>
</bean>
</beans>
Beans可以被引用。引用它的对象不必是一个bean,它也可以是一个字符串,整数或任何其它的对象。

<beans>
<bean name="a" class="org.jbpm.A" />
<ref name="b" bean="a" />
</beans>
Beans可以用任何构造器进行构造。

<beans>
<bean name="task" class="org.jbpm.taskmgmt.exe.TaskInstance" >
<constructor>
<parameter class="java.lang.String">
<string>do dishes</string>
</parameter>
<parameter class="java.lang.String">
<string>theotherguy</string>
</parameter>
</constructor>
</bean>
</beans>
...或通过bean上的一个对象方法

<beans>
<bean name="taskFactory"
class="org.jbpm.UnexistingTaskInstanceFactory"
singleton="true"/>

<bean name="task" class="org.jbpm.taskmgmt.exe.TaskInstance" >
<constructor factory="taskFactory" method="createTask" >
<parameter class="java.lang.String">
<string>do dishes</string>
</parameter>
<parameter class="java.lang.String">
<string>theotherguy</string>
</parameter>
</constructor>
</bean>
</beans>
...或用一个类上的静态工厂方法...

<beans>
<bean name="task" class="org.jbpm.taskmgmt.exe.TaskInstance" >
<constructor factory-class="org.jbpm.UnexistingTaskInstanceFactory" method="createTask" >
<parameter class="java.lang.String">
<string>do dishes</string>
</parameter>
<parameter class="java.lang.String">
<string>theotherguy</string>
</parameter>
</constructor>
</bean>
</beans>
每一个命名的对象可以用属性siglenton="true"来标记为单态。那意味着一个给定的对象工厂对于每一个请求将总是返回同一个对象。注意,单台在两个不同的对象工厂中不被共享。

单态的特征带来了getObject和getNewObject方法的区别。典型的对象工厂用户将使用getNewObject.这意味着第首先对象工厂的对象缓存在一个新的对象图构造之前被清空,存储在对象工厂缓存的非单态对象允许被一个对象共享引用。单态对象缓存不同于普通的对象缓存。单台缓存从不被清空,而普通对象缓存在每一个getNewObject方法启动时被清空。


jBPM用户指南-CH05


第五章 部署


jBPM是一个可嵌入的BPM引擎,意思是你可以把jPBM嵌入到你自己的java工程,而不是安装一个独立的产品并与它集成。最关键的一方面是它使最小化依赖变为可能。本章讨论jbpm库和他们的依赖关系。
5.1.Java运行时环境
jPBM3要求J2SE1.4.2及以上版本。

5.2.jBPM库
jbpm-[version].jar是带有核心jbpm功能的库。

jbpm-identity-[version].jar是包含身份验证组件可选库,在11.11"身份验证组件"描述。

5.3.第三方库
在一个最小的部署中,你可以只把commons-logging和dom4j库放到你的classpath中就可以用jBPM创建并运行流程。但持久化流程到数据库中是不被支持的。如果你不使用流程xml解析,dom4j库是可以移除的,但你必须用编程的方式来构建你的对象图。


















LibraryUsageDescriptionDirectory
commons-logging.jarlogging in jbpm and hibernateThe jBPM code logs to commons logging. The commons logging library can be configured to dispatch the logs to e.g. java 1.4 logging, log4j, ... See the apache commons user guide for more information on how to configure commons logging. if you're used to log4j, the easiest way is to put the log4j lib and a log4j.properties in the classpath. commons logging will automatically detect this and use that configuration. lib/jboss (from jboss 4.0.3)
dom4j-1.6.1.jarprocess definitions and hibernate persistencexml parsinglib/jboss (from jboss 4.0.3)


一个jBPM的典型部署将包含流程定义和流程执行的持久化存储。在那种情况下,jBPM在hibernate之外不必依赖任何依赖库。
当然,hibernate要求的苦取决于你所用的环境和特性。更多的细节请参考hibernate文档。下一个表给出了普通的独立POJO开发环境的指示。
jBPM最终和hibernate3.1一起发布。但它可以和3.0.x.一起工作。在那种情况下,你必须在hibernate.queries.hbm.xml配置文件中更新一点hibernate查询。关于定制查询的更多信息,请看7.6节“定制查询”。
表 5.2.
































































LibraryUsageDescriptionDirectory
hibernate3.jarhibernate persistencethe best O/R mapperlib/hibernate (hibernate 3.1 final)
antlr-2.7.5H3.jarused in query parsing by hibernate persistenceparser librarylib/jboss (from jboss 4.0.3)
cglib-2.1_2jboss.jarhibernate persistencereflection library used for hibernate proxieslib/jboss (from jboss 4.0.3)
commons-collections.jarhibernate persistencelib/jboss (from jboss 4.0.3)
ehcache-1.1.jar hibernate persistence (in the default configuration)second level cache implementation. When configuring a different cache provider for hibernate, this library is not required.lib/hibernate
jaxen-1.1-beta-4.jarprocess definitions and hibernate persistenceXPath library (used by dom4j)lib/hibernate
jdbc2_0-stdext.jar hibernate persistencelib/hibernate
asm.jarhibernate persistenceasm byte code librarylib/hibernate
asm-attrs.jarhibernate persistenceasm byte code librarylib/hibernate



beanshell库是可选的。如果你不包含它,你将不能集成beanshell到你的jbpm流程语言中,且你将会得到一条消息说jbpm不能装载Script类,同时,script元素将不可用。

Table 5.3.













LibraryUsageDescriptionDirectory
bsh-1.3.0.jarbeanshell script interpreterOnly used in the script's and decision's. When you don't use these process elements, the beanshell lib can be removed, but then you have to comment out the Script.hbm.xml mapping line in the hibernate.cfg.xmllib/jboss

jBPM用户指南-CH04



第四章 面向图形的编程

4.1. 介绍
本章可以看作是JBoss jBPM的表现。它给出了当前策略背后的思想和蓝图以及JBoss jBPM工程未来方向的完整概览。这个蓝图和传统的途径有很大的不同。
首先,我们相信很多流程语言。有很多不同的环境和不同的目的要求有他们自己指定的流程语言。
其次,面向图形编程是一种新的实现技术为所有的机遇图形的流程语言提供服务。
我们进步的最有利的地方在于他定义了所有类型的流程语言的一种基本技术。
当前软件开发依赖于各领域指定的语言。一个典型的Java开发者将使用相当一部分领域制定的语言。各种框架里面的工程中的XML文件可以被认为是特定领域语言。




图4.1. 基于图形语言的位置

用于工作流、BPM、流程编制和页面流的特定领域语言是基于有向图的执行。其他的诸如Hibernate映射文件,ioc-configuration就不是。 面向图形编程是所有基于执行图的特定领域语言的基础。
面向图形编程是一种非常简单的技术,它描述了在平面的面向对象编程语言之上图形如何被定义以和执行。
在4.5节,“应用领域”,我们见爱你个覆盖大部分常用的能被使用工作流、BPM、流程编制和页面流实现的流程语言。

4.1.1. 特定领域语言
每种流程语言都可以被认为是特定领域语言(DSL)。DSL透视图在流程语言如何与普通的OOP关联上给了开发者很好的洞察力。
这一节我们将单纯地集中注意力在编程环境上以有个印象。面向图形编程包含了连续整套的BPM产品,从API库到完整丰富的BPM套件产品。BPM套件产品是以业务流程为中心的完整的软件开发环境。在这种类型的产品中,用编程语言编写代码能够被尽量地避免。
特定领域语言的一个重要的方面是每种语言有一个特定的文法。该文法能够被表达为一个域模型。在java中,这些可以是类,方法,字段域,构造器...在jPDL中,可以是节点,转换,动作(Action)...在Rules中,可以是条件,concequence...
DSL的主要想法是,开发者用一种指定的语言创造东西。IDE是围绕一种语言的语法构建的。因此,可以有很多不同的编辑器来进行创作。比如jPDL了流程有一个图形的编辑器和一个XML源代码视图编辑器。同样的,也有不同的方法来存储相同的作品:比如,对于jPDL,可以是一个流程XML文件或是一个节点和转换对象图的序列化对象。其他的(理论上的)例子是java:你可以在系统中使用java类文件格式。当用户打开编辑器,源代码就产生好了。当用保存时,编译的类也同时被保存。
10年前,最大一部分的开发人员用在编写代码。现在随着特定领域语言的学习和使用,这一切改变了。这一趋势将持续下去,其结果是开发人员在同一个宿主平台上将在框架和编写软件之间有更大的选择余地。JBoss SEAM在这个方向上迈出了一大步。
这些语言中有些是基于图形执行的。比如:用于java工作流的jPDL,用于流程编制服务的BPEL,SEAM页面流…等等。面向图形编程是所有这些特定领域语言类型的公共基础。
以后,对于每种语言,开发人员将可以选择最适合他/她的编辑器。比如:核心程序员可能因为工作起来更快而喜欢在源文件格式下编辑Java代码。但缺乏经验的java程序员可能选择一个点击的编辑器来组合一个最终形成一个java类的功能。java源代码编辑可能显得更加复杂。

从其他方式来看这些特定领域语言可以从架构软件的角度。面向对象编程通过组合方法和数据来增加结构。面向关联编程 (AOP)增加了提取交错关联的方法。依赖注入(DI) 和反向控制 (IoC) 框架增加了图形对象的连线的简便性。 同样的基于图形的执行语言(这里介绍的)能通过围绕图行的执行架构你的部分软件项目的方式来有效的解决复杂性。
关于特定领域元的最初解释可以在Martin Fowler的bliki找到。但其背后更细致的阐述请看Martin关于Language Workbenches的论文。

4.1.2. 基于图形语言的特性
有很多的机遇图形的流程语言。在环境和侧重点上有很大的区别。例如,BPEL用来作为建立在企业服务总线(ESB)之上的基于XML的服务编制组件。而页面流流程语言则定也了在一个web应用中页面是如何导航的。这些是两种完全不同的环境。
尽管所有的这些都有区别,但你将发现在几乎每个流程语言中都有两个特性:支持等待状态和图形化表示。这一点没有一致性性,因为它的两个特性在普通的OOP编程语言(如Java)中不能得到充分支持。
面向图形编程是用面向对象编程语言实现这两个特性的一种技术。面向图形编程在面向对象编程的依赖性意味着所有具体的实现于面向图形编程之上的流程语言,必须用OOP开发。但这并不意味着流程语言本身暴露了底层OO的实现。比如:BPEL和OO编程没有任何关联,并且它能够在面向图形编程上实现。

4.1.2.1. 支持等待状态
像Java这样的强制性语言用来表达有一个系统执行的一系列指令。没有等待指令。强制性语言可以完美地描述诸如在服务器中的一个请求响应周期。系统连续地执行指令序列直到请求被处理并且响应结束。
但一个这样的请求往往是一个比想象中更大的一部分。比如:客户端提交了一个订单,该订单要被订单管理员验证。正式批准后,消息必须进入ERP系统。很多发到服务器的请求也是想象中一样大的一部分。
所以流程语言是用来描述更大场景的语言。我们这里必须区分的一个重要的区别是一个系统内部的执行和多个系统的协议。面向图表语言的实现技术仅仅用于在一台机器上执行的程序语言。
所以一个编制流程按照一个系统描述所有的场景。比如:当客户端提交订单时,一个流程开始。流程的下一步是订单管理员的批准。所以系统必须在订单管理员的任务列表中增加一个入口,并等待直到订单管理员提供请求输入。当输入被接受,流程继续执行。现在消息被发送到ERP系统,系统将再次等待直到响应发回。
所以,为了为一个系统描述全部的场景,我们需要一种机制来处理等待状态。
在大多数的应用领域,在等待状态期间执行必须被持久化。这就是为什么阻塞线程不够充分。聪明的Java程序员会想到Object.wait()和Object.notify()方法。这些将用来模拟等待状态但问题是线程不是持久化的。、

Continuations是一种使得线程(及上下文变量)可持久化的技术。这足够可以用来解决等待状态问题。但和我们在下节要讨论的一样,图形化表示在很多应用领域也一样重要。Countinuation是一种基于命令编程的技术,所以它不适合图形化表示。

所以支持等待状态的一个重要方面是执行需要被持续化。不同的应用领域可能有持久化这么一个执行的不同需求。对于大部分工作流、BPM和编排应用,执行需要被持久化到一个关系数据库中。典型的,一个流程执行中的状态转换将和数据库的一个转换相一致。

4.1.2.2. 图形化表示
软件开发的一些方面可以从基于图形的进步受益。业务流程管理是基于图形语言最明显的一个应用。在那个例子中,业务分析和开发人员的沟通把基于图形的业务流程图表作为通用语言进行改良。请看4.5.1节“业务流程管理(BPM)”
另一个可以从图形化表示受益的方面是页面流。在这种情况下,页面、导航和动作命令被展示并用图形化表示链接到一起。
在面向图表编程中,我们的目标是使用图表来代表一些格式的业务程序的执行。那与UML类图实例有明显的区别,UML类图用来表示OO数据结构的静态模型。

同时图形化表示可以被看作是OO编程的一个缺失的特性。没有一种好的方法能图形化地表示OO程序的执行。因此在OO程序和图形化视图之间没有直接的联系。

在面向图形的编程中,图形的描述是中心,而且它是像描述流程图的XML文件的一个实际的软件作品。由于图形化视图实质上是软件的一部分,因此它往往是同步的。从图形化需求到软件设计并不需要手册翻译。软件是围绕着图形构造的。


4.2. 面向图形编程
我们这里介绍的是基于图形的执行语言的一种实现技术。该技术是基于图形的runtime解释。其他的图形执行技术是基于消息队列和代码生成的。
本节将解释关于流程执行如何在OO编程语言上实现的策略。对于那些熟悉设计模式的人来说,这是命令模式和职责链模式的一种结合。

我们将尽量从最简单的模型开始,并一点点地扩展。

4.2.1. 图形化结构
首先,图形的结构由Node和Transition类来描绘。transition有一个方向,故节点(nodes)有离开和到达转换的行为。



Figure 4.2. Node and Transition classes

节点是一个命令,并有一个执行方法。Node的子类支持覆盖execute方法来实现那个节点类型的特定行为。

4.2.2. 执行
我们定义在图形结构上的执行模型看起来与有限状态机或UML状态图类似。实际上面向图形编程可以被用来实现那些类型的行为,但它还可以做得更多。
执行(亦即token)由叫做Executeion的类来描绘。执行有一个指向当前节点的引用。




Figure 4.3. The Execution class

转换可以通过take方法把执行从一个源节点传递到目标节点。

Figure 4.4. The Transition take method

当执行到达一个节点,那个节点就被执行。Node的execute方法同样负责传递执行。传递执行意味着一个节点可以传递抵达某个节点的执行越过一个即将离开的转换到下一个节点。


Figure 4.5. The Node execute method

当一个节点的execute方法不传递执行,它就成了等待状态。同样的,当一个新的执行被创建,它将被初始化在某个起始状态并等待一个事件。
事件是用来发给执行的,并触发执行开始移动。当事件发给一个当前节点要离开的转换相关的执行,该执行就会带起转换。然后执行将继续传递直到它进入另一个处于等待状态的节点。


Figure 4.6. The Execution event method


4.2.3. 流程语言
现在我们已经可以看到两种被支持的特性:等待状态和图形化表示。在等待状态期间,Execution只是指向图形中的一个节点。流程图和Execution两者都能被持久化:比如使用如Hibernate的O/R映射保存到数据库或序列化图形对象到一个文件中。同样的你可以看到图形中的节点和转换在图形化表示中有直接的关联。你也能看到一个图表/“业务程序定义”的节点和转向。因此,这也是一个与可视化表示的直接结合。 Also you can see that the nodes and transitions form a graph and hence there is a direct coupling with a graphical representation.
流程语言只不过是节点-实现的一个集合。 每个节点-实现与一个流程结构相一致。流程结构的准确的行为是通过覆盖execute方法来实现的。
这里我们展示一个具有4个流程结构的流程语言的例子:一个起始状态,一个决定,一个任务和结束状态。这个例子与jPDL流程语言无关。



Figure 4.7. An example process language

具体的节点对象现在可以用来在我们流程语言的示例中创建流程图了。



Figure 4.8. An example process


当为该流程创建了一个新的执行,我们把执行定位到起始状态开始。如果执行没有收到任何事件,那么执行将保持定位在起始状态。



Figure 4.9. A new execution

现在我们看看当一个事件被启动时会发生什么。在这个初始的情形,我们启动将与默认转换保持一致的默认事件。
这可以通过调用executeion对象的event方法完成。event方法将开始查找默认的离开转换并通过调用转换之上的take方法吧执行传递到转换,并把自身作为参数传递。
转换将传递执行到decision节点并调用execute方法。我们假设decision的execute实现履行计算功能并且决定通过发送‘yes’事件给执行来传递执行。那将导致执行结束‘yes’转换而继续并且执行将到达‘doubleCheck’任务。
什么假设doubleCheck(二次核对)的任务节点的执行实现增加了一个条目到核对者的任务列表并在以后不传递执行来等待核对者的输入。
现在,执行将仍旧定位在doubleCheck任务节点。所有的嵌套呼叫将在直到原始的event方法返回时开始返回。



Figure 4.10. An execution in the 'doubleCheck' wait state

4.2.4. Actions

在某些应用领域中,必须有一种方法来包含程序逻辑的执行而不用为它引进一个节点。比如在业务流程管理中这就是非常重要的一方面。业务分析管理图形化表示而开发人员负责使之执行。如果开发人员必须改变图表来包含业务分析员不感兴趣的技术细节,那将是难以接受的。
Action还是一个带有execute方法的命令。Action可以和事件绑定。
在执行的运行过程中有2个基本的事件由Node类启动:node-leave和node-enter。随着导致转换被带起的事件,这已经给了往图形的执行注入程序逻辑很大的自由。


Figure 4.11. Actions that are normally hidden from the graphical view

每个时间可以与一个actions列表相结合。随着事件的触发,所有的action都将被执行。

4.2.5. 代码示例
为了让人们学习到面向图形编程的规则,我们用少于130行的代码开发了这4个类。你可以阅读代码来获得一个认识或你可以进行改造并实现你自己的节点类型。

这是实例代码:

* Execution.java
* Node.java
* Transition.java
* Action.java

你也可以下载全部的(297KB)源代码项目并自己改造。它包含一个eclipse工程,你只要导入已有工程便可以使用。也有一系列的展示基本流程执行的测试,高级的图表执行概念在下一节介绍。

4.3. 高级面向图形编程扩展
前一节用最简单的形式介绍了普通的面向图形编程模型。本周将讨论基于图形语言的各种概念以及面向图形编程如何被使用或扩展以符合这些需求。
4.3.1. Process variables
4.3.1. 流程变量

流程变量保存一个流程执行的上下文数据。在一个保险索赔流程,“索赔总额”、“核准总额”和“拨付标志”就是很好的流程变量的例子。在很多情况下,它们与类的成员(域)相似。

面向图形编程能通过联合一个 与流程结合的键-值对集合,随着流程变量支持 简单地扩展,并发的执行路径和流程的组合将使事情变得有点复杂。万一在并发路径或子流程的情况下,范围规则将会定义流程变量的可见性。

工作流数据模式是一个关于各种范围的广泛的研究报告,这些范围可以应用于在子流程上下文和并发执行的流程变量。

4.3.2. 并发执行

假设你用基于图形的流程处理语言在为工作流程开发一个“sale”流程。在客户端提交订单之后,将有一系列的活动在为客户端付现金,同时也有一系列活动在为客户端运送商品项。正如你能想象的,付款活动和运送活动能够并行完成。

在这种情况下,一条执行路线将不足以跟踪整个流程状态。让我们一步步扩展面向图的编程模型并增加对并发执行的支持。

首先,我们对执行重新命名为一个执行路径,然后我们可以引进一个新的概念叫做流程执行(a process execution).流程执行描绘了一个流程的完整执行,并包含了很多执行路线。

执行路径可以被分级的排序。这意味着当一个流程执行被实例化的时候一个跟执行被创建。当这个根执行路径被分解到多个并发执行路径时,根就成了父亲,而新创建的执行路径则是根的所有的孩子。这样,一个join执行就变得相当易懂:一个join执行只要检查是否所以的兄弟执行路线已经全部指向了join节点。如果是这种情况,那么复制性路径可以回复执行,离开join节点。

尽管分层次的执行路径和join执行基于兄弟执行路径覆盖了该用例的一大部分,其他的并发行为应该在具体的环境中进行描述。比如当多条合并与一个分叉关联。在这种情形下,其他运行时数据和混合执行的组合是必须的。动作通常情况下都从图形视图中隐藏。


图 4.12. Actions that are normally hidden from the graphical view


执行的多条并发路径经常和多线程编程混淆在一起,尤其在工作流和BPM上下文中,他们有很大的不同。一个流程指定一个状态机。考虑一下,一个状态机通常总是处于稳定状态的,而状态转换是瞬时的。你可以通过查看状态转换引起的事件来解释执行的并发路径。并发执行意味着可以被处理的事件与两个并发的执行路径没有关联。现在,我们假设流程执行中的状态转换与一个数据库转换相关联,(as explained in Section 4.3.6, “Persistence and Transactions”),那么你可以看到多线程编程实际上没有被要求来支持执行的并发路径。

4.3.3. 流程的制作

流程创造可以包含一个子流程作为一个超流程(上级流程、父流程)的一部分。这个高级特性使得增加流程建模的抽取成为可能。对于业务分析来说,这个特性对于将模型分解为子模块的解决办法是很重要的。
主要的是路是超流程在图中有一个节点描绘了一个子流程的完整的执行。当一个执行进入超流程的子流程节点,有很多事情要考虑:
*首先,一个子流程的执行被创建。
*一些存储在超流程的流程变量的信息可以可选地从超流程的执行中注入到子流程执行。最为简单的方式是子流程节点被配置为:将带有从超流程的流程变量拷贝过来的一个变量集合复制到子流程变量中。
*子流程的开始节点必须只能有一个离开的转换。支持多个离开转换的流程语言必须有一种机制来从那些基于超流程变量的转换中选择一个转换。
*子流程的执行通过发送一个相当于它的开始状态的默认离开转换的事件来被启动。

在子流程进入一个等待状态后,超流程执行将被指向子流程节点而子流程执行将被指向某个等待状态。
当子流程执行完成,超流程执行可以继续。那是一下几个方面必须被考虑:
*流程变量信息必须从子流程执行被拷贝回来,放到超流程执行中。
*超流程执行将继续。典型的,流程语言允许在子流程节点只有一个离开转换。这样,超流程执行会在默认单独的离开转换上被传播。
*当一个子流程节点被允许多于一个离开转换时,必须引入一种机制开选择一个离开转换。这个选择可以给予子流程执行的变量,或是子流程的结束状态(一个典型的状态机可以有多个结束状态)。

未完成:bpel有一个未明确的子处理概念,没有明确指出...

4.3.4. 同步执行
4.3.5. 异步执行
4.3.6. 持久化与事务
4.3.7. 服务和环境


2008年10月13日星期一

jBPM用户指南-CH03


第三章 指南

本指南将向你展示用JPDL构造的基本流程和管理运行时执行的API的用法。
指南的格式是解释一组的例子。这些例子将集中讲解一个详细的主题并包含一些扩展的内容。这些例子也可以在jBPM下载包中的src/java.examples目录找到。
最好的学习方式是创建一个工程,并在给出的例子的基础上改造,进行试验。

Eclipse用户入门:下载jbpm-3.0-[version].zip包并解压到你的系统中。然后按照Eclipse菜单的"File" --> "Import..." --> "Existing Project into Workspace"顺序进行操作。点击“下一步”(Next),然后浏览jBPM根目录并点击“完成”(Finish)按钮。现在在你的工作区就有了jbpm.3工程了。现在你可以在src/java.examples/...目录下发现本指南的示例。当你打开这些示例,你可以通过菜单"Run" --> "Run As..." --> "JUnit Test"来运行它们。
jBPM包括一个图形设计工具来制作例子中所示的XML文件。你可以在2.1节“Downloadables Overview”(下载概览)中找到图形设计器的下载指示。
学习本指南,你不需要具备图形设计工具。


3.1. Hello World 的例子
流程定义是由节点和转换构成的有向图。本Hello world流程由有3个节点。为了看看这些小单位如何组合在一起,我们将不利用设计器工具从一个简单的流程开始。下图展示了hello world流程的图形化表示。


图3.1. hello world 流程图

public void testHelloWorldProcess() {
// 该方法展示了一个流程定义和一个流程定义执行。
// 流程定义有3个节点:一个未命名的开始状态,一个‘s’状态,一个命名为‘end’的结束状态。
// 下一行将一个xml文本片段解析为一个ProcessDefinition(流程定义)。
// ProcessDefinition是一个被表现为java对象的流程的正式描述。

ProcessDefinition processDefinition = ProcessDefinition.parseXmlString(
"<process-definition>" +
" <start-state>" +
" <transition to='s' />" +
" </start-state>" +
" <state name='s'>" +
" <transition to='end' />" +
" </state>" +
" <end-state name='end' />" +
"</process-definition>"
);

//下一行创建了一个流程定义的执行,构造之后,流程执行就有一个定位到开始状态的执行主路径(=root token)
ProcessInstance processInstance = new ProcessInstance(processDefinition);

//构造之后,流程执行就有一个定位到开始状态的执行主路径(=root token)
Token token = processInstance.getRootToken();//获取执行主路径

// 同样的,在构造完之后,执行主路径定位到流程定义的开始状态。
assertSame(processDefinition.getStartState(), token.getNode());
// 现在开始流程的执行,离开开始状态,结束默认的转换。
// 现在开始流程的执行,留下开始状态作为默认的转换。
// Let's start the process execution, leaving the start-state
// over its default transition.
token.signal();//发信号
// signal方法将阻塞直到流程执行进入一个等待(wait)状态。

// 流程执行将进入第一个等待状态‘s’。所以执行的主路径现在定位到状态‘s’。
assertSame(processDefinition.getNode("s"), token.getNode());

// 我们再送另外一个信号signal. 这将继续执行离开状态's' 结束默认的转换(transition)
// 现在我们发另一个信号。这将通过留下状态‘s’作为默认转换而挂起执行。
// Let's send another signal. This will resume execution by
// leaving the state 's' over its default transition.
token.signal();//发另一个信号

// 现在signal方法返回,因为流程实例已经到达结束状态。
assertSame(processDefinition.getNode("end"), token.getNode());
}


3.2. 数据库的例子
jBPM的一个基本特性是它能够在当流程处于等待状态时,将流程执行持久化存储到数据库中。下一个例子将向你展示如何在jBPM数据库中存储一个流程实例。例子给出一个将会发生的上下文.分开的方法因不同的用户代码块而创建。比如:一个web应用中的用户代码段开始了一个流程并将其执行持久化存储到数据库中。然后,一个消息驱动bean从数据库中装载了这个流程实例并挂起该流程的执行。
更多的jBPM持久化内容将在 第七章:持久化 找得到。

public class HelloWorldDbTest extends TestCase {

static JbpmConfiguration jbpmConfiguration = null;

static {
// 和这个例子相同的配置文件可以在‘src/config.files’目录找到。典型的,配置信息保存在jbpm.cfg.xml资源文件中,
// 但在这里我们将配置信息当成一个XML字符串来传递。
// 首先,我们静态地创建一个JbpmConfiguration。JbpmConfiguration能被用在系统的所有线程中,
// 这就是为什么我们能够安全地使之成为静态的。

jbpmConfiguration = JbpmConfiguration.parseXmlString(
"<jbpm-configuration>" +
// jbpm-context机制将jbpm核心引擎从环境中与jbpm使用的服务分开。
" <jbpm-context>" +
" <service name='persistence' " +
" factory='org.jbpm.persistence.db.DbPersistenceServiceFactory' />" +
" </jbpm-context>" +
//同样的,所有jbpm使用的资源文件都从jbpm.cfg.xml引用。


" <string name='resource.hibernate.cfg.xml' " +
" value='hibernate.cfg.xml' />" +
" <string name='resource.business.calendar' " +
" value='org/jbpm/calendar/jbpm.business.calendar.properties' />" +
" <string name='resource.default.modules' " +
" value='org/jbpm/graph/def/jbpm.default.modules.properties' />" +
" <string name='resource.converter' " +
" value='org/jbpm/db/hibernate/jbpm.converter.properties' />" +
" <string name='resource.action.types' " +
" value='org/jbpm/graph/action/action.types.xml' />" +
" <string name='resource.node.types' " +
" value='org/jbpm/graph/node/node.types.xml' />" +
" <string name='resource.varmapping' " +
" value='org/jbpm/context/exe/jbpm.varmapping.xml' />" +
"</jbpm-configuration>"
);
}

//JUnit的setUp()方法
public void setUp() {
jbpmConfiguration.createSchema();
}
//JUnit的tearDown()方法
public void tearDown() {
jbpmConfiguration.dropSchema();
}

public void testSimplePersistence() {
// 在下面调用的3个方法之间,所有的数据都通过数据库进行传输。在这个单元测试中,
// 因为我们想测试一个完整的想定的流程,所以这3个方法依次执行。但在实际情况中,
// 这些方法的展示的是对一个服务的不同请求。
// 由于我们以一个干净的,空的内存数据库开始,我们需要首先部署流程。在实际情况中,
// 这一步将由流程开发者来完成。
deployProcessDefinition();//部署流程定义ProcessDefinition

// 假设当用户在web应用中提交了一个表单时我们想启动一个流程实例(=process execution) ...
processInstanceIsCreatedWhenUserSubmitsWebappForm();
// 然后,等下异步消息到来时,执行必须继续。
theProcessInstanceContinuesWhenAnAsyncMessageIsReceived();//当收到异步消息时流程实例继续
}

//部署流程定义ProcessDefinition
public void deployProcessDefinition() {
// 该测试展示了流程定义以及一个流程定义的执行。流程定义有3个节点:一个未命名的起始状态,一个状态‘s’
// 以及一个名为‘end’的结束状态。
ProcessDefinition processDefinition = ProcessDefinition.parseXmlString(
"<process-definition name='hello world'>" +
" <start-state name='start'>" +
" <transition to='s' />" +
" </start-state>" +
" <state name='s'>" +
" <transition to='end' />" +
" </state>" +
" <end-state name='end' />" +
"</process-definition>"
);
// 查找上面配置的pojo持久化上下文构造器
// Lookup the pojo persistence context-builder that is configured above
JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
try {
// 在数据库中部署流程定义
jbpmContext.deployProcessDefinition(processDefinition);

} finally {
// 销毁pojo持久化上下文。这包括刷新插入流程定义到数据库的SQL语句。(flush后事务将提交)
jbpmContext.close();
}
}

// 当用户提交web应用Form时流程实例被创建
public void processInstanceIsCreatedWhenUserSubmitsWebappForm() {
// 本方法中的代码应该放在struts的action中或是JSF的managed bean中。
// 查找上面配置的pojo持久化上下文构造器(context-builder)
JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
try {

GraphSession graphSession = jbpmContext.getGraphSession();

ProcessDefinition processDefinition =
graphSession.findLatestProcessDefinition("hello world");
// 当我们从数据库中获取了processDefinition后,我们可以像在hello world的例子(没有持久化存储)
// 一样创建这个流程定义的一个执行。
ProcessInstance processInstance = new ProcessInstance(processDefinition);

Token token = processInstance.getRootToken();
assertEquals("start", token.getNode().getName());
// 让我们来启动流程的执行
token.signal(); //发信号
// 现在流程处于状态‘s'。
assertEquals("s", token.getNode().getName());
// 现在流程实例已经被保存到数据库中。所以该流程的执行的当前状态已经被存储到数据库中
jbpmContext.save(processInstance);

// 下面的方法将从数据库取回流程并通过提供另外的信号来挂起执行。
// The method below will get the process instance back out
// of the database and resume execution by providing another
// external signal.

} finally {
// 拆卸pojo持久化上下文。
jbpmContext.close();
}
}

//当异步消息收到时流程实例继续
public void theProcessInstanceContinuesWhenAnAsyncMessageIsReceived() {
// 该方法中的代码可以是消息驱动BEAN的内容
// 查找上面配置的pojo持久化上下文构造器
// Lookup the pojo persistence context-builder that is configured above
JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
try {

GraphSession graphSession = jbpmContext.getGraphSession();
// 首先,我们需要从数据库取回流程实例
// 这里有很多选项可以知道我们正在处理的是哪个流程。
// 这个简单测试用例最简单的地方是只要去查找完整的流程实例列表。
// 那将给我们唯一的一个结果。所以我们来查找流程定义。
ProcessDefinition processDefinition = graphSession.findLatestProcessDefinition("hello world");
// 现在我们搜索这个流程定义的所有流程实例
List processInstances = graphSession.findProcessInstances(processDefinition.getId());
// 因为我们知道该单元测试的上下文中,只有一个执行。(所以这里使用get(0))
// 在实际情况下,processInstanceId能够从到达的消息或用户作出的一个选择中抽取出来

ProcessInstance processInstance =
(ProcessInstance) processInstances.get(0);
// 现在我们可以继续执行。注意:流程实例processInstance委派信号到执行的主路径(= root token)。
processInstance.signal();//发信号
// 在发信号之后,我们知道流程的执行已到达结束状态
assertTrue(processInstance.hasEnded());
// 现在我们可以更新执行的状态到数据库中了。
jbpmContext.save(processInstance);

} finally {
// 拆卸pojo流程实例持久化上下文。
jbpmContext.close();
}
}
}




3.3. 上下文的例子:流程变量
当流程执行时,流程变量包含了上下文信息。流程变量与java.util.Map把变量名映射到值类似,都是java对象。流程变量作为流程实例的一部分持久保存。为了使事情更加简单,在这个例子中,我们将只展示操作变量的API,而不持久化保存。

更多关于变量的信息请看 第10章 Context

// 这个例子同样从hello world流程开始。这次甚至不用修改。
ProcessDefinition processDefinition = ProcessDefinition.parseXmlString(
"<process-definition>" +
" <start-state>" +
" <transition to='s' />" +
" </start-state>" +
" <state name='s'>" +
" <transition to='end' />" +
" </state>" +
" <end-state name='end' />" +
"</process-definition>"
);

ProcessInstance processInstance =
new ProcessInstance(processDefinition);
// 从流程实例中获取上下文实例来与流程变量共事

ContextInstance contextInstance =
processInstance.getContextInstance();
// 在流程离开开始状态之前,我们将在流程实例的上下文中设置一些流程变量。
contextInstance.setVariable("amount", new Integer(500));
contextInstance.setVariable("reason", "i met my deadline");
// 从现在开始,这些变量就与流程实例关联在一起了。
// 现在流程变量可以由这里展示的API通过用户代码来访问了,但同样在这个动作和节点执行当中。
// 流程变量同样作为流程实例的一部分存储到数据库中。
processInstance.signal();//发信号

// 变量可以通过上下文实例访问。
assertEquals(new Integer(500),
contextInstance.getVariable("amount"));
assertEquals("i met my deadline",
contextInstance.getVariable("reason"));


3.4. 任务分配的例子
下一个例子中,我们将展示你如何才能够分配一个任务给一个用户。由于jBPM工作流引擎和组织模型的分离,用来计算参与者的表达式语言将一直很受限制。因此,你必须指定AssignmentHandler的一个实现来包含任务参与者的计算。

//测试任务分配
public void testTaskAssignment() {
// 下面展示的流程基于hello world流程。状态节点被task-node(状态节点)替换了。任务节点是JPDL的一个节点,
// 它描述了在流程能够继续执行之前的一个等待状态和产生要被完成的(一个或多个)任务

ProcessDefinition processDefinition = ProcessDefinition.parseXmlString(
"<process-definition name='the baby process'>" +
" <start-state>" +
" <transition name='baby cries' to='t' />" +
" </start-state>" +
" <task-node name='t'>" +
" <task name='change nappy'>" +
" <assignment class='org.jbpm.tutorial.taskmgmt.NappyAssignmentHandler' />" +
" </task>" +
" <transition to='end' />" +
" </task-node>" +
" <end-state name='end' />" +
"</process-definition>"
);
// 创建流程定义的一个执行。
ProcessInstance processInstance = new ProcessInstance(processDefinition);
Token token = processInstance.getRootToken();
// 现在开始流程的执行,离开起始状态,结束它的默认转换。
token.signal();//发信号
// signal()方法将阻塞直到流程执行进入一个等待状态。在这种情况下,指的是task-node。
assertSame(processDefinition.getNode("t"), token.getNode());

// 当执行到达task-node,任务“change nappy”被创建,同时NappyAssignmentHandler被调用,来决定任务该分配给谁。
// NappyAssignmentHandler返回“papa”

// 在实际的环境中,任务将用org.jbpm.db.TaskMgmtSession从数据库获取。
// 由于在这个例子中我们不想包含复杂的持久化过程,我们只是获取该流程实例的第一个任务实例。
//(我们知道在这个想定的测试中任务只有一个)

TaskInstance taskInstance = (TaskInstance)processInstance
.getTaskMgmtInstance()
.getTaskInstances()
.iterator().next();
// 现在,我们检查一下任务实例是否真的赋给了“papa”
assertEquals("papa", taskInstance.getActorId() );
// 现在,我们假设“papa”已经完成了他的职责并标记任务为完成。
taskInstance.end();
// 由于这是要做的最后(唯一)一个任务,该任务的完成触发了流程实例执行的继续。
assertSame(processDefinition.getNode("end"), token.getNode());
}

3.5. 定制 action 的例子
Actions 是绑定你的自定义java代码到jBPM流程的一种机制。Actions能够联合组建的节点(如果他们在流程的图形化表示中相关的话)。或者actions也可以被放在诸如带走一个转换、离开一个节点或进入一个节点之类的事件中。在这种情况下,actions不是图形化表示的一部分,但当在一个运行时流程执行触发了事件的时候actions会被执行。
在我们要开始使用我们的例子前先来看看action的实现:MyActionHandler。这个action处理器实现没有真正做很多事情,它只是将boolean类型的变量isExecuted设置为true。变量isExecuted是静态的,所以它不但不只在action handler可以访问,而且从要检查它的值的其他action也可以访问。
更多关于 actions 的内容可以从 9.5节 “Actions”找到。

// MyActionHandler 描绘了一个类,该类能在jBPM流程执行期间执行一些用户代码。
public class MyActionHandler implements ActionHandler {
// 在每个测试之前(在setUp方法中),isExecuted成员变量将被设置为false。
public static boolean isExecuted = false;

// action将设置isExecuted为true,故单元测试可以显示action在何时执行。
public void execute(ExecutionContext executionContext) {
isExecuted = true;
}
}

如前所述,在每个测试之前,我们设置静态域MyActionHandler.isExecuted为false;

// 每个测试将以设置MyActionHandler的静态成员isExecuted为false开始;
public void setUp() {
MyActionHandler.isExecuted = false;
}

我们将在一个转换中开始一个action

// 测试转换Action
public void testTransitionAction() {
// 下一个流程是hello world流程的一个变量。
// 我们在从起始状态到结束状态的转换之上加了一个action。本测试的目的是展示集成java代码到jBPM流程中是多么的简单。

ProcessDefinition processDefinition = ProcessDefinition.parseXmlString(
"<process-definition>" +
" <start-state>" +
" <transition to='s' />" +
" </start-state>" +
" <state name='s'>" +
" <transition to='end'>" +
" <action class='org.jbpm.tutorial.action.MyActionHandler' />" +
" </transition>" +
" </state>" +
" <end-state name='end' />" +
"</process-definition>"
);

// 我们为流程定义启动一个新的执行
ProcessInstance processInstance = new ProcessInstance(processDefinition);

// 下一个信号将导致执行离开起始状态而进入状态‘s’
processInstance.signal();

// 这里表明MyActionHandler还没有被执行。
assertFalse(MyActionHandler.isExecuted);

// 故执行的主路径位于状态‘s’
assertSame(processDefinition.getNode("s"),
processInstance.getRootToken().getNode());

// 下一个信号将触发root token的执行。该token将带走转换和action,并且该action在signal方法调用期间被执行。
processInstance.signal();

// 这里我们可以看到在signal方法调用期间MyActionHandler被执行了。
assertTrue(MyActionHandler.isExecuted);
}

下一个例子展示同样的action,但现在这个action分别放于enter-node和leave-node事件中。注意:节点和转换相比有多于一个的事件类型,转换只有一个事件。因此放置于节点的action应该被放在一个事件元素中。

ProcessDefinition processDefinition = ProcessDefinition.parseXmlString(
"<process-definition>" +
" <start-state>" +
" <transition to='s' />" +
" </start-state>" +
" <state name='s'>" +
" <event type='node-enter'>" +
" <action class='org.jbpm.tutorial.action.MyActionHandler' />" +
" </event>" +
" <event type='node-leave'>" +
" <action class='org.jbpm.tutorial.action.MyActionHandler' />" +
" </event>" +
" <transition to='end'/>" +
" </state>" +
" <end-state name='end' />" +
"</process-definition>"
);

ProcessInstance processInstance = new ProcessInstance(processDefinition);

assertFalse(MyActionHandler.isExecuted);
// 下一个信号将导致执行离开起始状态并进入状态‘s’。所以状态‘s’被进入因而action被执行。
processInstance.signal();
assertTrue(MyActionHandler.isExecuted);

// 现在重置MyActionHandler.isExecuted
MyActionHandler.isExecuted = false;

// 下一个信号将触发执行离开状态‘s’。所以action将再次被执行一次。
processInstance.signal();
// 看,结果就是这样.
assertTrue(MyActionHandler.isExecuted);

2008年10月10日星期五

jBPM用户指南-CH02


第二章 入门

本章将带你从第一步获取JBoss jBPM到提供立即启动运行JBoss jBPM的初始指示。

2.1.下载包概览
下面列出的现在可用的不同jBPM包。每个包包含了一个或多个可下载的文件。这些文件都伴有一份内容说明和相关的安装指令提示(如果文件可安装的话)。
下面描述的所有下载内容均可在sourceforge jbpm下载页面找到。

2.1.1. JBPM3
在sourceforge.net下载JBoss jBPM 3。这是包含了核心引擎和一些运行JBPM所需要的附加模块。

开发新手包: (jbpm-starters-kit-.zip): 如果你想对JBoss jBPM快速入门,这就是你所需要的文件。它在一个单独的下载中包含了这个包的所有其他模块并加上一个图形设计器。解开压缩包到指定的目录并阅读readme.html文件以获取更多信息和更多的安装指令。有了哥哥新手包你就可以立即从第三章开始。【指南】
核心引擎和身份组件(jbpm-.zip):该下载包含了jBPM核心引擎和用于参与者与组管理的身份组件。要开始使用它,解开压缩包到指定的目录,你可以发现指向用户指南的提示和在jbpm-文件夹的readme.html文件中描述的其他重要信息资源。
数据库扩展(jbpm-db-.zip):数据库扩展包包含了jBPM核心引擎和用于参与者与组管理的身份组件。要开始使用它,解开压缩包到指定的目录,你可以发现指向用户指南的提示和在jbpm-文件夹的readme.html文件中描述的其他重要信息资源。


2.1.2. jBPM 流程设计器
在sourceforge.net下载jBPM流程设计器。该设计器是一个Eclipse插件并允许你制作自己的流程定义并简单地进行部署。该插件可以作为一个压缩的Eclipse feature或压缩的Eclipse update site提供下载。内容上基本一致,唯一不同的地方是你的安装的方式。

Eclipse Update Site(jbpm-gpd-site-<version>.zip):如果你想确信设计器的安装能够顺利进行,我们推荐使用update site机制与一个新的Eclipse安装相结合。当然,Eclipse版本要跟下载的update site文档相匹配。要入手设计器插件,请按照文档根目录的readme.html文件的指示以成功安装GPD(图形设计器)。
Eclipse Feature (jbpm-gpd-feature-<version>.zip): 如果你厌烦了每次去刷新Eclipse安装,那么你将应付一些可能的问题,你可以尝试feature下载包。这种安装相当简单,只需要解压文档到你的Eclipse安装路径并覆盖可能先前存在的同名文件夹即可(确保plugins和features文件夹在你的Eclipse安装的同一个位置结束)。这种安装方式非常简单,但由于你已经安装了其他的features,覆盖先前存在的插件可能会让你陷入相互冲突的问题。尽管文件有相同的名字,但还可能配到这些冲突的插件版本不同,因此存在冲突的可能性。readme.html文件的安装指示中已经多次强调。
2.1.3. jBPM BPEL 扩展
在sourceforge.net下载JBoss jBPM BPEL扩展。它只包含一个文件:jbpm-bpel-.zip。要使用BPEL扩展,请参看顶层文件夹doc子目录下的用户指南。


2.2. JBoss jBPM 项目目录

专业支持: JBoss 是为该项目提供专业支持、培训和资讯服务的公司。
用户指南:你正在阅读的提供进入本项目主要切入点服务的文档。
论坛:与社区联系,提问和讨论jBPM。
wiki:额外信息,大部分由社区提供。
问题跟踪:提交BUG和特性要求。
下载:sourceforge提供的jBPM下载页。
邮件列表:用于公告的邮件列表。
javadocs:部分的下载在doc/javadoc目录。

2.3. CVS 访问
2.3.1. 匿名 CVS 访问

作为选择,根据下面的信息你可以从CVS得到JBoss jBPM。

* 连接类型: pserver
* 用户: anonymous
* 主机: anoncvs.forge.jboss.com
* 端口: 2401 (which is the default)
* 知识库路径: /cvsroot/jbpm
* 标签: :pserver:anonymous@anoncvs.forge.jboss.com:/cvsroot/jbpm

2.3.2. 开发人员 CVS 访问
为了能够进行开发者访问,你必须签署贡献者协议并且需要一个SSH KEY。更多两者的信息可以在JBoss cvs 知识库 wiki页找到。
* 连接类型: ext over ssh (extssh in eclipse)
* 用户: sf.net username or jboss username
* 主机: cvs.forge.jboss.com
* 端口: 2401 (which is the default)
* 知识库路径: /cvsroot/jbpm
* 标签: :pserver:anonymous@cvs.forge.jboss.com:/cvsroot/jbpm


jBPM用户指南-CH01


第一章:介绍

JBoss jBPM是一个灵活、可扩展的工作流管理系统。JBoss jBPM有直观的流程设计语言,可以根据任务、异步通信的等待状态、定时器、自动操作等来图形化地表示业务流程。为了将这些操作绑定在一起,JBoss jBPM拥有一套最强大的、可扩展的流程控制机制。
JBoss jBPM有着最小的依赖性,使用起来和使用普通的java库一样简单。当然也可以把它通过部署到J2EE 集群应用服务器,以用在吞吐量极为关键的环境中.
JBoss jBPM 可以同任何数据库配置,并且可以部署在任何应用服务器上.

1.1综述
核心的工作流和BPM(业务流程管理)功能被打包为一个简单的java库。这个库包含了一个用来从jBPM数据库中存储、更新和获取流程信息的服务。


图1.1 :JBPM组件概览图


1.2. The JBoss jBPM starter kit

本工具包包含了绑定到一个简单下载包的所有的jBPM组件。内容包括:
jbpm-server: 预先配置的jboss 应用服务器.
jbpm-designer: 用来图形化地创建jBPM流程的eclipse插件 .
jbpm-db: jBPM 数据库兼容包 (参看下面).
jbpm: 核心jbpm组件包括库和本手册.
jbpm-bpel: JBoss jBPM BPEL 扩展的参考实现.

预配置的JBoss应用服务器安装了以下的组件。
核心jBPM组件,打包为一个服务档案文件
集成了jBPM表的数据库:包含jBPM表和流程的默认hypersonic数据库
能由流程参与者与jBPM管理员使用的web控制台应用程序。
定时器执行的jBPM调度计划。调度程序在新手包中被配置为一个Servlet。这个servlet将产生一个线程来监控和执行定时器。
异步执行命理的jBPM命令执行器。该执行器同样配置为一个Servlet。该Servlet将产生一个线程来监视和执行命令。
一个示例流程以及部署到jBPM数据库中。

2008年10月9日星期四

jBPM用户指南-CH10

第十章 上下文


上下文与流程变量有关。流程变量是保存与流程实例相关信息的键-值对。由于上下文必须能被存储到数据库,将会稍会约束使用。

10.1.访问变量
org.jbpm.context.exe.ContextInstance作为为流程变量提供服务的中心接口。你可以像这样从流程实例获取上下文实例:
ProcessInstance processInstance = ...;
ContextInstance contextInstance = (ContextInstance) processInstance.getInstance(ContextInstance.class);
最基本的操作是:
void ContextInstance.setVariable(String variableName, Object value);
void ContextInstance.setVariable(String variableName, Object value, Token token);
Object ContextInstance.getVariable(String variableName);
Object ContextInstance.getVariable(String variableName, Token token);
变量名称为java.lang.String类型。默认情况下,jBPM支持下面几种值类型:
java.lang.String
java.lang.Boolean
java.lang.Character
java.lang.Float
java.lang.Double
java.lang.Long
java.lang.Byte
java.lang.Short
java.lang.Integer
java.util.Date
byte[]
java.io.Serializable
classes that are persistable with hibernate
空类型null值同样可以被持久化保存。
所有其他类型能正常地存储到流程变量中。但当你试图保存流程实例时将导致一个异常。
如何配置jBPM以能够在变量中存储HIBERNATE持久化对象,请参看Storing hibernate persistent objects。

10.2.变量的生命周期
变量不必再流程定义文件中(流程归档中)声明。在运行时,你可以随意放置任何对象到变量中。如果变量不存在,它将会被建立。这和普通的java.util.Map类似。
变量可以使用如下语句删除:
ContextInstance.deleteVariable(String variableName);
ContextInstance.deleteVariable(String variableName, Token token);
自动改变类型以及被支持。这意味着允许使用一个不同类型的值覆盖一个变量。当然,你应该尽力限制类型改变的数量,因为更新一个普通的列将带来更多的数据库消耗。

10.3.变量持久化
变量是流程实例的一部分。保存流程实例到数据库中,保持流程实例和数据库同步。变量由于流程实例在数据库中的保存(更新)操作而在数据在被创建、更新、和删除。更多信息请看Chapter 7, Persistence.

10.4.变量范围
每条执行路径(阅读:Token)都有它所属的流程变量集合。请求一个变量往往在一个Token(令牌)中完成。流程实例有一个令牌树(请看graph oriented programming)。当请求一个变量而没有指定一个令牌时,默认的令牌为根令牌。

变量查询递归到给定令牌的父母令牌.这种行为与编程语言的变量范围类似。

当不存在的变量被设置到令牌中,变量将被创建在根令牌。这意味着每个变量默认就是整个流程范围。为使得变量基于本令牌(局部变量),你可以用如下方法明确创建:
ContextInstance.createVariable(String name, Object value, Token token);

10.4.1. 变量重载
变量重载一为之每个执行路径能够用相同的名称拥有自己的一份变量的拷贝。他们能被独立的对待,因此可以是不同的类型。如果你在相同的转换上启动了多条并发的执行路径,变量重载将十分有趣。唯一的事情是区分哪些执行路径是它们各自的变量集合。

10.4.2.变量的覆盖
变量覆盖意味着执行的嵌套路径中的变量覆盖了执行中更为全局的变量。一般的,执行的嵌套路径与并发性有关:一般的,嵌套执行路线涉及到并发问题: 在form和join之间的执行路线是到达fork的执行路线的. 比如,如果你有变量 'contact'在流程实例范围, 你可以在嵌套的执行路线'shipping'和'billing'重写这个变量.

10.4.3.任务实例变量范围
更多关于任务实例变量的信息,请看 Section 11.4, “Task instance variables”.

10.5 瞬时变量
当流程实例被持久化到数据库中,普通变量可以同时作为流程的一部分被持久化。在某些情况下你可能需要在ige代理类中使用一个变量,但你不想把它存储到数据中。比如一个你想从jBPM外部传递数据库连接到一个代理类。这可以通过瞬时变量来完成。
瞬时变量的生命周期和流程实例的java对象相同。
由于他们的特性,瞬时变量不与令牌相关。所以对一个流程实例只有一个瞬时变量的Map。瞬时变量可以在上下文实例中通过它们自己的方法集合来访问,你不必在流程定义文件processdefinition.xml中声明。
Object ContextInstance.getTransientVariable(String name);
void ContextInstance.setTransientVariable(String name, Object value);

10.6.定制变量持久化
变量通过2个步骤被存储在数据库中:
user-java-object <---> converter <---> variable instance
变量存储在变量实例VariableInstances。VariableInstances的数量与Hibernate在数据库中的域映射。在jBPM默认的配置中,有6种VariableInstances被使用:
DateInstance (with one java.lang.Date field that is mapped to a Types.TIMESTAMP in the database)
DoubleInstance (with one java.lang.Double field that is mapped to a Types.DOUBLE in the database)
StringInstance (with one java.lang.String field that is mapped to a Types.VARCHAR in the database)
LongInstance (with one java.lang.Long field that is mapped to a Types.BIGINT in the database)
HibernateLongInstance (this is used for hibernatable types with a long id field. One java.lang.Object field is mapped as a reference to a hibernate entity in the database)
HibernateStringInstance (this is used for hibernatable types with a string id field. One java.lang.Object field is mapped as a reference to a hibernate entity in the database)

Converters在java-user-objects和能被VariableInstances存储的java对象之间进行转换。因此,当一个流程变量用:ContextInstance.setVariable(String variableName, Object value)进行设置,那么值将随意地通过Converter被转换。然后转换过的对象将被存储到VariableInstances。Converters实现了下面的接口:
public interface Converter extends Serializable {
boolean supports(Object value);
Object convert(Object o);
Object revert(Object o);
}

Converters是非强制的,Converters必须在jBPM class loader可用。
user-java-objects被转换并存储到变量实例的方式是在org/jbpm/context/exe/jbpm.varmapping.properties文件配置的。要定制这些属性文件,需要放一个修改版本在classpath的根路径中,如Section 6.2, “Configuration files” 中所述,属性文件的每行指定2或3个类名,通过空格隔开:user-java-object的类名,可选的转变器的类名和变量实例的名字。当你引用你定制的转换器,确保他们在jBPM class path中。当你引用定制的变量实例,他们必须在jBPM class path中,并且hibernate映射文件中的org/jbpm/context/exe/VariableInstance.hbm.xml必须被更新以包含VariableInstance定制的子类。
比如,看从org/jbpm/context/exe/jbpm.varmapping.xml文件截取的片段:
<jbpm-type>
<matcher>
<bean class="org.jbpm.context.exe.matcher.ClassNameMatcher">
<field name="className"><string value="java.lang.Boolean"></field>
</bean>
</matcher>
<converter class="org.jbpm.context.exe.converter.BooleanToStringConverter">
<variable-instance class="org.jbpm.context.exe.variableinstance.StringInstance">
</JBPM-TYPE>
该片段指定了所有java.lang.Boolean类型的对象必须使用BooleanToStringConverter转换器来转换,并且结果对象(是一个String值)将被存储到一个类型为StringInstance的变量实例对象。
如果没有指定Converter

<jbpm-type>
<matcher>
<bean class="org.jbpm.context.exe.matcher.ClassNameMatcher">
<field name="className"><string value="java.lang.Long"></field>
</bean>
</matcher>
<variable-instance class="org.jbpm.context.exe.variableinstance.LongInstance">
</JBPM-TYPE>

则意味着放在变量中的Long对象只能被存储为一个类型为LongInstance变量实例而没有被转化。


2008年9月22日星期一

[转]ORACLE各种数据类型详细介绍


ORACLE数据类型大约分为:character, number, date, LOB, 和RAW等类型。虽然
ORACLE8i也允许你自定义数据类型,但是它们是最基本的数据类型。  

在下面的文章中你将了解到他们在oracle 中的用法、限制以及允许值。
  
  Character 数据类型

  Character 数据类型用来存储字母数字型数据。当你在oracle 中定义一个character 数据时通常需要制定字段的长度,它是该字段的最大长度。ORACLE提供以下几种character 数据类型:

CHAR() CHAR数据类型是一种有固定长度和最大长度的字符串。存储在数据类型为CHAR字段中的数据将以空格的形式补到最大长度。长度定义在1——2000字节之间。当你创建一个CHAR型字段,数据库将保证在这个字段中的所有数据是定义长度,如果某个数据比定义长度短,那么将用空格在数据的右边补到定义长度。如果长度大于定义长度将会触发错误信息。
  

VARCHAR() varchar型数据是varchar2型数据的快照[目前varchar是varchar2的同义词。工业标准的varchar类型可以存储空字符串,但是oracle不这样做,尽管它保留以后这样做的权利。Oracle自己开发了一个数据类型varchar2,这个类型不是一个标准的varchar,它将在数据库中varchar列可以存储空字符串的特性改为存储null值。如果你想有向后兼容的能力,Oracle建议使用varchar2而不是varchar]

VARCHAR2() varchar2数据类型是一种可变长度的、有最大长度的字母数字型数据。Varchar2类型的字段长度可以达到4000字节,Varchar2类型的变量长度可以达到32676字节。
一个空的varchar2(2000)字段和一个空的varchar2(2)字段所占用的空间是一样的。

NCHAR()NVARCHAR2() NCHAR() 和 NVARCHAR2()数据类型分别与CHAR() 和 VARCHAR2()类型是相同的,只不过它们用来存储NLS(National Language Support)[本地语言支持]数据。
  
  LONG LONG 数据类型是一个遗留下来的而且在将来不会被支持的数据类型。它将被LOB(Large Object) [大型对象]数据类型所代替。

  

  比较规则 Varchar2和char数据类型根据尾部的空格有不同的比较规则。对Char型数据,尾部的空格将被忽略掉,对于Varchar2型数据尾部带空格的数据排序比没有空格的要大些。比如:
  
  Char 型数据:        'YO' = 'YO ' 

  Varchar2型数据:      'YO' < 'YO '   


  Numberic 数据类型


  Numberic 数据类型用来存储负的和正的整数、分数和浮点型数据,范围在-1*10-103 和9.999…99*10125之间,有38位的精确度。标识一个数据超出这个范围时就会出错。

  Number(p,s) Number数据类型存储一个有p位精确度的s位等级的数据。

  
  DATE 数据类型

  DATE 数据类型用来存储日期和时间格式的数据。这种格式可以转换为其他格式的数据去浏览,而且它有专门的函数和属性用来控制和计算。以下的几种信息都包含在DATE数据类型中:

  Century 
  Year 
  Month 
  Day  
  Hour  
  Minute  
  Second

  

  LOB 数据类型

  LOB(Large Object) 数据类型存储非结构化数据,比如二进制文件,图形文件,或其他外部文件。LOB 可以存储到4G字节大小。数据可以存储到数据库中也可以存储到外部数据文件中。LOB数据的控制通过DBMS_LOB 包实现。BLOB, NCLOB, 和CLOB 数据可以存储到不同的表空间中,BFILE存储在服务器上的外部文件中。LOB数据类型有以下几种:

  BLOB: 二进制数据 

  CLOB: 字符型数据

  BFILE: 二进制文件



  其他数据类型
 
  ROWID ROWID 数据类型是ORACLE数据表中的一个伪列,它是数据表中每行数据内在的唯一的标识。







2008年9月18日星期四

工作流环境jBPM搭建初探

使用jBPM开发基于WEB的工作流,首先需要搭建好开发环境,经过一段时间的摸索,先将环境搭建的步骤做个简要说明:
1.前提:
Eclipse 3.2
MyEclipse 5.0GA
jBPM3.1.2(jbpm-starters-kit-3.1.2)
Oracle 10g
Struts1.2
Hibernate3

2.至于数据库的脚本生成就不说了,有些参考资料讲得比较清楚,也有备份。
3.安装完JBPM的相关Eclipse插件以后,就可以使用Eclipse创建(new...)一个Process Project以及相关的ProcessDefinition,但这不是我们想要的。我们想要的从步骤4开始。

4.使用Eclipse创建一个Web Project,加入必要的框架包,如Struts,JSTL等。

5.在构建路径中配置jBPM库,这也是环境搭建的重点。由于使用默认的Process Project可以帮我们创建好jBPM库,但没有帮我们创建WEB环境,因此,我们首先建立WEB工程,再来搭建JBPM库。
6.在工程的“Build Path”中选择配置构建路径(Config Build Path...)。
7.在打开的窗口中选择右边的Libraries标签库,通过Add Library选项,在打开的窗口中添加一个jBPM库。
8.可以看到没有所谓的jBPM库选项,因此要自己动手,选择User Library选项,创建一个自定义的用户库,取名为jBPM Library [jBPM Runtime]。
9.添加jar文件到我们自定义的User Library中,需要添加的内容是:
jbpm-starters-kit-3.1.2\jbpm.3\lib目录下的所有jar包;
jbpm-starters-kit-3.1.2\jbpm.3\build目录下的所有jar包。

OK,开发环境初步搭建完成,我们同样可以在新建的工程中自由的创建Process Definition文件。

2008年9月17日星期三

使用Oracle的闪回技术恢复被删除的表

使用Oracle 10g版本的数据库时,查询数据库表,偶尔会出现一些以BIN$开头命名的表,这些表是ORACLE数据库中被DROP掉的表,保留在一个回收站中。如果万一错删了一个重要的表,则可以使用“闪回”的方法来恢复。
现在做一个测试:

SQL> create table aa(
2  col001 varchar2(20) primary key,
3 col002 varchar2(20),
4 col003 varchar2(100)
5 );

SQL> insert into aa values('001','China','Asia');

1 rows inserted

SQL> select * from aa;

COL001 COL002 COL003
-------------------- -------------------- -------------------
001 China Asia

SQL> drop table aa;

Table dropped

SQL> select * from user_recyclebin;

OBJECT_NAME ORIGINAL_NAME OPERATION
BIN$szLzmW8iSNe1cOL+pL3nDA==$0 AA DROP
BIN$uvwnj9g/TMOOZPxdVC9azg==$0 SYS_C0030540 DROP

如果我们要恢复表,可以在SQL*PLUS中使用如下命令:

SQL> flash back table TABLE_NAME to before drop

执行完毕后,查询用户表,可以发现表已经成功恢复,且数据完整。

由于被删除的表存在于回收站,并没有真正从硬盘删除,因此它还占用原有的存储空间。如果我们不想看到在查询表的时候出现回收站表的名称,是想释放存储空间,则可以清空回收表:

SQL> purge user_recyclebin;

这样做将删除回收站的所有表,有时候为了保险起见,我们只删除回收站中特定的表,可以使用带表名(也可以是回收站的特殊表名)的purge命令,如:

SQL> purge table TABLE_NAME;

SQL> purge table BIN$szLzmW8iSNe1cOL+pL3nDA==$0;

也可以在删除表的时候加上purge参数,直接删除而不保留在回收站,这样表就永久删除且不可恢复了。如:

SQL> drop table TABLE_NAME purge;

使用闪回技术还可以使表恢复到某个时间点,可以使用命令:
flashback table TABLE_NAME TO TIMESTAMP to_timestamp(string1, [ format_mask ],[ 'nlsparam' ] );

当表空间不足的时候,回收站也会由ORACLE自动管理,按照先进先出的原则删除回收站中的对象。

2008年9月12日星期五

PageRank算法代码实现


package org.jazz.pagerank;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
/**
* PageRank代码实现
* 支持从文件中读取邻接矩阵
* @author zhengtanyun
* @version 0.1
*/
public class PageRank{

public static void main(String[] args) throws IOException{

int nodeNum = 4; //节点数
int TIMES = 10; //迭代次数
int[]outLink = new int[nodeNum]; //存放节点的外链数A>>>>Z

float rank = 0f; //中间变量,保存rank值得中间值
float value = 0f; //计算后的pagerank值
float[] copyPR = new float[nodeNum]; //迭代完成后由新数组拷贝过来的副本

boolean flag = true;


/* --------------------------------------------------------*
* 相关参数获取
* 将邻接矩阵放在文本文件中,规则如下:
* 第一行:节点数;
* 第二行:迭代的初始PR值,个数与节点数一致,以逗号分隔;
* 第三行开始:每一行对应邻接矩阵的一行,共N行N列,N同节点数目,其中:
* 每个节点以逗号分隔,9代表节点自身,1代表有链接关系,0代表无链接关系。
* --------------------------------------------------------*/
FileReader reader = new FileReader("E:\\a.txt"); //邻接矩阵文件存放位置
BufferedReader br = new BufferedReader(reader);

String paramLine = br.readLine();
String initPR = br.readLine();

/* -----------------------------*
* 获取节点数
* -----------------------------*/
nodeNum = new Integer(paramLine).intValue(); //获取节点数
int[][] link = new int [nodeNum][nodeNum]; //邻接矩阵定义

/* -----------------------------*
* 获取迭代初值
* -----------------------------*/
float[] tempPR = new float[nodeNum];
String[] strInit = initPR.split(",");
for(int i=0;i<strInit.length;i++){
tempPR[i] = new Float(strInit[i]).floatValue();
System.out.print(tempPR[i]+"f, ");
}
System.out.println();

/* -----------------------------*
* 构造邻接矩阵
* -----------------------------*/
int tmp=0;
while(flag){
String lineText = br.readLine();
if(null==lineText){
flag=false;
continue;
}
String[] s = lineText.split(",");

for(int j=0;j<nodeNum;j++){
link[tmp][j] = new Integer(s[j]).intValue();
}
tmp++;
}

br.close(); //关闭流
reader.close(); //关闭文件

/* ---------------------------------*
* 邻接矩阵打印模块
* ---------------------------------*/
for(int i=0;i<nodeNum;i++){
for(int j=0;j<nodeNum;j++){
System.out.print(link[i][j]+",");
}
System.out.println();
}


/* ---------------------------------*
* 统计节点的外链接数
* ---------------------------------*/
for(int i=0;i<nodeNum;i++){
int links = 0;
for(int j=0;j<nodeNum;j++){
if(link[i][j]!=9){
links += link[i][j];
}
}
outLink[i] = links; //节点0到NODENUM的外链数 假设对应为A B C D
}
System.out.println("");

//计算PR值
/* ----------------------------------------------------------------*
* 循环对每个节点的"入链"进行计算,得到该节点的PR值。 所谓入链接点,就是指邻接矩阵
* 的纵向节点,其计算的方向为纵向;
*
* 对于横向的每个节点,有一个对应的纵向节点的数组。
* 为0表示没有链接,不用计算,
* 为9表示节点自身,不用计算,
* 为1表示有链接,我们在前一个步骤中已经得到了该入链节点的外链接数。
* ----------------------------------------------------------------*/
for(int m=0;m<TIMES;m++){ //迭代
/* ------------------------------------------------------------*
* 每次迭代完成后,要把各个节点的PR值放到一个数组中,用于下一次迭代
* 假设放到tempPR[]中;
* 同时,还要保留一个计算用PR值的副本,假设为copyPR[]
* ------------------------------------------------------------*/
//数组拷贝
for(int x=0;x<tempPR.length;x++){
copyPR[x] = tempPR[x];
}
float sum = 0f; //PageRank临时求和变量
//float[] fac = {2f,3f,4f,5f};
for(int node=0;node<nodeNum;node++){
/* --------------------------------------------------------*
* 这个循环是为了得到本轮各个节点计算后的PR值,将得到一个新的PR值数组
* --------------------------------------------------------*/
//使用copyPR[]进行计算
for(int j=0;j<nodeNum;j++){

int var = link[j][node];
/* --------------------------------------------------------*
* 比如要计算第0个节点,那么对应的第0列:
* link[0][0],link[1][0],link[2][0],link[3][0]
* 为其对应的纵向节点,
* 其中值为9代表节点本身,为0不参与计算,为1代表与节点有链接
* -------------------------------------------------------*/
if(var==0var==9){
continue; //没有链接就跳过
}else{
rank = rank+copyPR[j]/(outLink[j]); //计算
}
}
value = rank*0.85f + 0.15f; //这个得到该节点在本轮迭代的最后值
sum += value;

System.out.println("PR["+node+"]"+value); //信息打印
tempPR[node] = value;
rank = 0f; //本节点计算完成后,中间变量清零
}
System.out.println("AVG OF PR = "+sum/nodeNum);
System.out.println("=====================================");
}
}
}