• BPM 应用系统开发案例实战


    概述

    IBM BPM 的前身是 Lombardi,是由 IBM 于 2009 年收购的产品,之后产品更名为 IBM WebSphere Lombardi Edition,目前最新版本称为 IBM BPM V7.5。IBM BPM 提供了强大的业务流程管理(BPM)功能,而且通过 IBM BPM 所支持的 Portal 和可定制的 Coach,使得客户可以直接通过 Web 的方式与 BPM 的业务流程进行交互。但是由于用户需求的的多样性以及 Coach 所提供的功能的局限性,加上 JSP 与 JS 结合的灵活性,使得通过广泛的 Web 技术以充分利用 IBM BPM 的业务流程管理功能成为一种需要。 本文针对目前 BPM 市场主流的一款重磅产品 IBM Business Process Management, 首先从 IBM BPM 的一些背景、基础框架和产品使用基础知识进行了介绍,然后结合一个具体的业务实例来分析采用 IBM BPM 实现 Business Process 应用的一些优势和技术 / 产品选型策略,同时给出如何使用 IBM BPM 来开发一个具体的业务流程的案例实践,能够帮助 IBM BPM 的初学者快速的入门,并快速构建一个功能简单但是相对完整的 BPM 项目。

    IBM Business Process Management 介绍

    商业流程,或者称为业务流程,其高可用性已经成为市场的一个共同的目标。越来越多的公司将通过业务流程有效的增加客户价值定为自己的商业目 标。大部分公司已经意识到传统的“以代码功能为核心”的软件开发工具和开发方式已经不能满足“以人为中心”的商业目标,而日渐重要的流程管理,如工作流、 任务管理和流程模拟等,使得流程带来了越来越多的商业价值。而目前市场上并没有一个完整的包含了流程开发、测试、分析、模拟、统计及部署应用的一体化工 具。

    IBM BPM 正是为了解决这一问题而产生,它使用基于 Java,J2EE 和 XML 等工业标准技术,开发出各种各样的用户应用程序,包括 Process Modeler、Service Modeler 、Process Inspector 、Process Optimizer、Process Portal、Process Coaches、Console 等,它们通过共享同一个模型来满足不同背景的人参与到流程的生命周期中。

    IBM BPM 的总体架构包含了服务器和客户端两部分,其中客户端包括了以 Eclipse 为基础的 Process Designer ( 旧版本中称为 Lombardi Authoring Environment),以及基于 Web 的 Process Center Console,Performance Admin Console、 Process Admin Console 和 Process Portal,而服务端包括 Process Center、Process Server 及 Performance Data Warehouse。它们在 IBM Business Process Manager 中的作用如表 1 所示:


    表 1. IBM BPM 组件介绍

    组件功能
    Process Center 为 Process Designer 和 Process Center 控制台提供一个集成式的开发环境和共享存储库,它包括了 Process Center Server 和 Performance Data Warehouse 两个组件。
    Process Server 用于执行流程和服务,它内置于 Process Designer 中,存储在 Process Center Repository 中。
    Performance Data Warehouse 收集和分类流程执行的数据。
    Process Center Console 管理和维护 IBM BPM 的存储库,包括管理流程应用、工作空间和快照以及将流程应用安装到运行时环境。
    Process Designer 包括了一系列的图形用户界面使用户可以方便的建模、实现、模拟以及查看商业流程。
    Integration Designer 集成式的设计、开发环境。
    Process Portal 提供了一个集成的界面使得流程的参与者可以方便的执行被分配的任务、查看历史任务以及团队的工作效率等。通过 Process Portal,用户可以方便的连上 Process Center Server 或者 Process Server
    Process Admin Console 及 Performance Admin Console 配置及维护 Process Center 和 Performance Data Warehouse

    各种客户端通过服务端与各种实际环境相关联,他们也可以直接与实际的环境联系起来。关系如图 1 所示:


    图 1. 客户端、服务端及运行环境关系
    图 1. 客户端、服务端及运行环境关系

    实例背景

    本文实例来自于 RAPID Process 解决方案。The Remote Access and Processing of Image Documents (RAPID) 是一个用来接收、处理、审批和存储用户电子文档的工作流平台, 主要是用来提高电子文档的审批和归档效率。本文采用了其中一个最典型的实例(发票审核流程)来作为本文流程建模和应用开发的实例。流程的用户场景如图 2 所示:


    图 2. 发票审核流程用户场景
    图 2. 发票审核流程用户场景

    如上图所示,发票录入员将扫描成像的发票录入系统,根据发票的属性(如金额、用途等)决定审批的级数和审批者。审批者最多有 5 个,但不限定为 5 个。在审批过程中,如果其中某一级拒绝(Reject),则审批流程提前结束,否则,直到所有人审批(Approve)完成后结束。录入员在填写发票基本 信息时,会根据情况确定是否需要进行发票重复性审查,如果不需要,则直接进入后面的发票审批流程,否则先进入重复性检查,如果出现重复,则以异常的方式结 束该流程。根据上述用户场景,我们将构建如图 3 所示的流程模型:


    图 3. 发票审核流程
    图 3. 发票审核流程

    如上图所示,“COCE_GIW_ALL_Index”节点对应了录入员录入发票基本信息的动 作,“COCE_GIW_ROBF_DuplicateCheck”节点对应了重复性检查的动作,“Approve”节点对应了各级发票审核的动 作,“SendMail”节点用于给各级审核者发送邮件,审核者通过邮件中的链接登录系统并审核对应的发票。整个流程类似于图 2 中所示用户场景。

    构建一个业务流程定义

    本文使用 Process Designer(文中简称 PD)作为流程开发环境,其过程中所涉及的操作流程与旧版本的 Authoring Environment 相同,如果本机环境中没有,可从 http://[your-ibm-bpm-server]:[port number]/ProcessCenter/ 上进行下载。

    双击 PD 图标链接在对应的用户名、密码位置输入合法用户名 / 密码,点击“登录”按钮即可登录开发工具。默认的页面为 Process Center 页面的 Process App 标签页,如果没有选中该标签页,点击即可选中。页面中会显示已经存在的 Process App。确保选中“Process App”选项卡,点击右边的“新建 Process App”按钮在弹出的窗口中填入 Process App 的名称,它的缩写名以及 Process App 的描述,“首字母缩写”中信息为“RAPSIM2”,该信息将用于在 Web 系统中确定需要交互的 Process App。填写完成后点击“创建”按钮,完成 Process App 的创建并回到 Process Center 页面,此时,将会看见你所创建的 Process App,点击对应的流程应用右边的“在 Designer 中打开”,将会打开 Designer 页面,AE 会默认切换到“设计器”页面。点击“流程”右边的“+”号,会弹出“新建”对话框,选择其中的“业务流程定义”,会弹出“新建业务流程定义”对话框,填写业务流程名称 SimpleRapidBPD,并点击“完成”按钮。完成后,AE 会回到设计器页面,并默认打开刚创建的流程定义的“概述”选项卡,如图 4 所示:


    图 4. 查看 BPD 概述
    图 4. 查看 BPD 概述

    如上图所示,在“概述”选项卡下可以看见“系统标识”,此为该业务流程定义的系统标识。该系统标识将用于在 Web 系统中用于确定需要交互的业务流程定义(Business Process Definition,简称 BPD)。选择旁边的“图”选项卡,查看所生成的流程图,我们会看到在不做任何流程定义的情况下,工具已经定义好了两个泳道 ------“参与者”泳道和“系统”泳道,以及“开始”和“结束”节点。此时不必做任何修改。分别点击“参与者”泳道和“系统”泳道,可以看出“系统” 泳道的属性标签页下的“是系统泳道”复选框是默认勾选的,而“参与者”泳道没有勾选。

    在设计器的右边选择“Activity”图标,然后点击“开始”和“结束”节点的空白区域,选择下方的“属性”标签,在“名称”字段中填入“COCE_GIW_ALL_Index”。按照以上的步骤,完成流程中所有节点的定义,节点信息如表 2:


    表 2. BPD 节点信息

    节点类型节点名称所在泳道
    Activity COCE_GIW_ALL_Index 参与者
    Activity COCE_GIW_ROBF_DuplicateCheck 系统
    DecisionGateway LoopDecision 参与者
    EndExceptionEvent EndException 系统
    Activity SendMail 系统
    Activity Approve 参与者

    完成后如图 5 所示:


    图 5. 完成 BPD 节点定义

    XML error: The image is not displayed because the width is greater than the maximum of 580 pixels. Please decrease the image width.

    点击右边“序列流”图标,将“开始”节点和“COCE_GIW_ALL_Index”活动连接起来,按照上述步骤将其他节点连接起来(最终效果可查看图 3)。

    此时,已完成 BPD 基本的流定义,可选择右上方的“Snapshot”按钮,对 BPD 进行一次快照备份,在弹出的对话框中输入名称之后点击“确定”按钮,完成。在“设计器”页面中点击“变量”选项卡,点击“添加专有”按钮,以添加专有变 量,在右边的名称字段中填入“maxNumberApprovers”,添加对应的文档信息。点击“变量类型”右边的“选择”按钮,在弹出的对话框中选择 “Integer”类型。勾选“具有缺省值”选项,并将默认值改为 5。点击“添加专有”按钮,在右边的名称字段中填入“approverNames”,添加对应的文档信息。保持默认的变量类型“String”。勾选“具 有缺省值”选项及“是列表”选项。点击“添加专有”按钮,在右边的名称字段中填入“invoiceData”,添加对应的文档信息。点击“变量类型”右边 的“新建”按钮,在弹出的对话框中输入新建类型的名称“InvoiceDataModel”,点击“完成”按钮,此时会显示“变量类型”创建页面,在“行 为”区域中会显示该类型是“复合结构类型”,保持该选择不变,在“参数”区域点击“添加”按钮,并在右边的“参数属性”区域中的名称字段中,将名称改为 “Source”,保持变量类型为 String 不变。按照以上步骤并根据表 3 完成 InvoiceDataModel 类型的定义。


    表 3. InvoiceDataModel 数据结构定义

    参数名称参数变量类型
    Source String
    UserID String
    Timestamp String
    D_DocStatus String
    ORIGIN_ITEM_ID String
    OLD_CREATES String

    完成后如图 6 所示:


    图 6. 完成 InvoiceDataModel 类型字段定义
    图 6. 完成 InvoiceDataModel 类型字段定义

    选择下方“高级属性”区域,将“名称空间”和“元素名称空间”改为非默认选项,如图 7 所示:


    图 7. 更改高级属性
    图 7. 更改高级属性

    更改完成后点击下方的“View XML Schema”按钮,在弹出的浏览器页面中可查看该复合结构类型的 XML Schema,以便于程序中反序列化变量使用,如图 8 所示:


    图 8. InvoiceDataModel XML Schema
    图 8. InvoiceDataModel XML Schema

    点击“InvoiceDataModel”左边的左箭头按钮,直到视图回到“SimpleRapidBPD”。 按照前述步骤及表 4,完成整个 BPD 变量的定义。


    表 4. BPD 变量定义

    添加类型名称变量类型默认值是否是列表
    添加输入 attachedFilePath String "" false
    添加专有 maxNumberApprovers Integer 5 false
    添加专有 approverNames String 默认 true
    添加专有 duplicateCheck Boolean false false
    添加专有 mailContents String 默认 true
    添加专有 invoiceData InvoiceDataModel 默认 false
    添加专有 currentApprovalStatus Boolean true false
    添加专有 currentApproverNumber Integer 1 false
    添加专有 duplicateStatus Boolean false false

    变量定义完成后结果如图 9 所示:


    图 9. 完成 BPD 变量定义
    图 9. 完成 BPD 变量定义

    选择“图”选项卡,右键点击“COCE_GIW_ALL_Index”活动,在弹出的上下文菜单中选择“活动向导”, 在弹出的对话框中,服务类型区域中选择“系统服务”,保持其他选项不变,点击“下一步”按钮,在新的对话框中修改“输入 / 输出参数”,如图 10 所示,点击“完成”按钮。


    图 10. 完成活动的参数映射
    图 10. 完成活动的参数映射

    按照“COCE_GIW_ALL_Index”活动所述步骤完成“Approve”活动的步骤。

    右键点击“SendMail”,在弹出的菜单中选择“活动向导”,在弹出的对话框中,“服务类型”区域选择“系统服务”,在“服务选择”区域 中选择“连接现有服务”,并点击“选择”按钮,在弹出的对话框中选择“Send E-mail via SMTP”,在“属性”标签页下的数据映射选项卡中填写邮件发送的相关信息,其中"to"字段填写如下 JS 代码:tw.local.approverNames[tw.local.currentApproverNumber - 1]

    "messageText"字段填写如下 JS 代码:tw.local.mailContents[tw.local.currentApproverNumber - 1]

    contentType 字段填写“text/html”,"smtpHost"字段填写合法的 SMTP 主机 IP 或者域名,"from"字段填写对应的合法邮件账户,其他字段填写空字符串。右键点击“COCE_GIW_ROBF_DuplicateCheck”,在 弹出的界面中分别选择“系统服务”和“新建服务”,保持名称不变点击“下一步”,在“活动向导 - 参数”页面为新建的服务修改如图 11 所示的参数输入输出映射,并点击“完成”按钮:


    图 11. COCE_GIW_ROBF_DuplicateCheck 参数映射
    图 11. COCE_GIW_ROBF_DuplicateCheck 参数映射

    双击业务流程定义中“COCE_GIW_ROBF_DuplicateCheck”的节点,详细编辑节点的子步骤。选择 Common 区域中的“Nested Service”,在其“Properties”选项卡下的“Implementation”中的“AttachedService”区域中,点击右边的 “Select”按钮,在弹出的页面中选择“Read From HTTP”服务,最后用“序列流”将整个子步骤连接起来,完成后如图 12 所示:


    图 12. 更改 COCE_GIW_ROBF_DuplicateCheck 的实现
    图 12. 更改 COCE_GIW_ROBF_DuplicateCheck 的实现

    返回到“SimpleRapidBPD”主流程定义页面,点击图中“LoopDecision”决策网关,选择“属性”选项卡下的“实施”子属性,在右边的“决策”区域中,“to SendMail”对应的编辑框中填入相应代码。

    完成后如图 13 所示:


    图 13. 决策网关定义
    图 13. 决策网关定义

    如上图所示,“to 结束”右边默认填入“缺省行”,如果“to SendMail”处于下面,并且右边区域默认填入“缺省行”,可通过右边的上下箭头进行交换。此时流程定义结束。点击设计器右上方的 arrow 按钮,Authoring Environment 将自动切换到“检查器”视图,左上方区域中是流程实例相关数据,右上方是 Activity 相关运行数据,左下方是当前运行实例的模拟视图,右下方是调试数据。当前正在运行“COCE_GIW_ALL_Index”活动,由橘黄色游标指示。点击 右上角的 refresh 按钮,会刷新出最新产生的活动,选择其中一个活动,点击其右边的 arrow 按钮,会执行该活动。通过不断重复上述步骤,直到流程正确结束。

    注意:在模拟运行流程之前,须将 SendMail 活动的数据映射中的“to”和“messageText”字段改成任意符合条件的非空字段,否则模拟运行时会抛出异常。

    自此,基于 IBM BPM 的发票审核流程定义完成。

    构建基于 IBM Business Process Manager 流程定义的应用程序

    本文所描述的 JavaEE 系统主要使用 IBM BPM 所提供的 WebAPI 作为外部系统和 BPM 系统交互的通道。

    打开 Eclipse 启动程序,点击菜单栏“File”--->“New”---->“Dynamic Web Project”,在弹出的对话框中“Project name”段中填入“LombardiSimulation”,选择合适的“Target Runtime”和“Web Module Version”点击“Finish”,完成工程的创建。根据情况选择合适的“Target Runtime”和“Dynamic web module version”,项目中分别为 Apache Tomcat V6.0 和 v2.5。完成后在工作台中的 JavaEE Perspective 中的 Project 视图中可以看见工程结构。

    创建 Web 组件之前,先了解一下工程中所使用的 Web 组件。表 5 中列出了 Web 工程中主要的后台功能组件,表 6 列出了工程中的前台页面组件。


    表 5. Dynamic Web Project 功能组件

    名称类型访问 URL描述
    FileLoadDaemon Thread 轮训后台服务端,当出现新的文档 Image 的时候启动 StartPIThead 的实例
    StartPIThread Thread 根据配置文件,连接 IBM BPM 的服务器,并启动一个 Process 的实例
    ApproverServlet Servlet ApproverServlet 处理审核者的审核请求
    DisposeIndexServlet Servlet DisposeIndexServlet 处理发票录入员录入发票数据及其他信息的请求
    DuplicateCheckServlet Servlet DuplicateCheckServlet 响应由 IBM BPM 服务端发送的进行重复性检查的 HTTP 请求
    InitServlet Servlet InitServlet 加载并初始化整个系统所使用的参数,并根据配置文件创建 FileLoadDaemon 实例
    LoadApproveData Servlet LoadApproveData 为发票审核页面加载后台需审核的数据
    LoadProcessListData Servlet LoadProcessListData 为初始页面加载活动的 Process 实例列表数据
    WebAPIUtil Common 产生连接 IBM BPM 服务端的 WebAPI 实例的工具类

    列表中为 Web 系统所使用的主要的类,包括各种 Thread 类,Servlet 类以及普通的 Java 类,图 14 描述了各个类之间的关系:


    图 14. 类关系
    图 14. 类关系

    表 6. 使用 JSP 实现的系统界面

    名称访问路径描述
    approver.jsp /approver/approver.jsp 页面启动时向后台 LoadApproveData 请求并展示需要审核的发票数据,并向 ApproverServlet 发送审核请求
    basic_index.jsp /indexer/basic_index.jsp 发票录入员所示用的发票数据录入页面,向 DisposeIndexServlet 发送请求数据,其包含两个标签页,区分待审核数据和基本的流程数据
    index_list.jsp /indexer/index_list.jsp 系统起始页面,用于展示当前所有活动的流程实例的列表,通过向 LoadProcessListData 发送请求获取数据

    调用 WebAPI 必须包含的两个主要的 Jar 包,其中 teamworks-client-sample.jar 是使用 WebAPI 的辅助类,可以在 IBM BPM 的安装目录下找到,其中的 lib 目录下包含了使用该 Jar 包所依赖的其他的 Jar 包。webapi.jar 是连接 IBM BPM 的功能包,可以在服务端安装目录下直接找到。将所需包导入到工程的 Class Path 下。

    新建一个普通 Java 类,命名为 WebAPIUtil,该类利用 teamworks-client-sample.jar 所提供的功能,产生 WebAPI 的实例,详细代码可查看示例程序程序中的 WebAPIUtil.java,下面是关键代码:


    清单 1. 创建 WebAPI 实例代码片段

    				 
     
     WebAPIFactory factory = WebAPIFactory.newInstance(properties); 
     // Create a new WebAPI client stub 
     factory.setProperty(Stub.USERNAME_PROPERTY, username); 
     factory.setProperty(Stub.PASSWORD_PROPERTY, password); 
     webAPI = factory.newWebAPI(); 
        

    上面这段代码展示了通过 WebAPIFactory 产生 WebAPI 连接实例的过程,其中 username、password 用于连接 IBM BPM 的服务端,必须是合法的用户名 / 密码对,properties 对象通过 properties 配置文件产生,可参考示例程序中的 WebAPIFactory.properties 文件,内容如下:


    清单 2. WebAPIFactory.properties 配置文件内容片段

    				 
     
     Wjavax.xml.rpc.service.endpoint.address= 
     http://[ibm-bpm-host]:[port]/webapi/services/WebAPIService 
     javax.xml.rpc.session.maintain=true 
     com.lombardisoftware.includeNullArrayElements=true 
        

    其中第一条配置信息包含了要访问的 WSDL 文件的地址,[ibm-bpm-host]:[port] 分别是 IBM BPM 的服务器地址和端口号。

    新建一个 Servlet,取名为 LoadProcessListData,该程序创建一个查询(Search),查询出当前服务器所有活动的流程实例,详细代码可查看示例程序中的 LoadProcessListData.java,关键代码如下:


    清单 3. LoadProcessListData.java 代码片段

    				 
     
     WwebAPI = WebAPIUtil.getWebAPIConnection(username, password,webAPIProperty); 
     // execute query to get the process instance list 
     SearchResultRow[] rrs = getActiveInstanceList(); 
     // generate XML data 
     String xmloutput = getXMLDataFromSearch(rrs); 
        

    其中第一行代码展示了通过先前创建的辅助工具类获取 WebAPI 的实例,第三个参数为配置文件所在的路径。第二行代码通过调用一个方法获取查询的结果了数组,最后通过该数组生成需要返回的 XML 字符串,其中获取查询结果的代码如下:


    清单 4. 获取查询结果的代码片段

    				 
     
     Search se = new Search(); 
     se.setOrganizedByType("ProcessInstance"); 
     SearchResults sr = null; 
    
     // set search column for the search and search result 
     SearchColumn searchColumn1 = new SearchColumn(); 
     searchColumn1.setType("ProcessInstance"); 
     searchColumn1.setName(SearchableProcessInstanceColumn._Id); 
     SearchColumn searchColumn2 = new SearchColumn(); 
     searchColumn2.setType("ProcessInstance"); 
     searchColumn2.setName(SearchableProcessInstanceColumn._Name); 
    
     // set the condition 
     SearchColumn searchColumn3 = new SearchColumn(); 
     searchColumn3.setType("ProcessInstance"); 
     searchColumn3.setName(SearchableProcessInstanceColumn._Status); 
     SearchCondition searchCondition1 = new SearchCondition(); 
     searchCondition1.setColumn(searchColumn3); 
     searchCondition1.setOperator("EQUALS"); 
     searchCondition1.setValue("Active"); 
    
     // create data 
     SearchColumn[] scs = new SearchColumn[2]; 
     scs[0] = searchColumn1; 
     scs[1] = searchColumn2; 
     SearchCondition[] sCond = new SearchCondition[1]; 
     sCond[0] = searchCondition1; 
    
     // do the search 
     se.setColumns(scs); 
     se.setConditions(sCond); 
     try { 
        sr = webAPI.executeSearch(se, null, null); 
     } catch (RemoteException e) { 
        e.printStackTrace(); 
     } 
     return sr.getRows(); 
        

    此段代码先创建一个 Search 对象,然后将结果的组织类型设置为根据“ProcessInstance”的方式,及以 Process 实例为基本单位获取结果,5 至 10 行创建需要展示的结果的列,12 至 18 行创建需要查询的条件,20 至 24 行设置查询数据,最后调用 WebAPI 的方法执行查询并返回结果。

    当然,调用 Search 的方式除了上面介绍的通过代码硬编码的方式以外,还可以通过 IBM BPM 的 Portal 创建 Saved Search,然后通过 WebAPI 直接调用,类似于调用数据库中的存储过程。

    此时通过界面即可查看当前的活动的实例列表,界面详细代码可查看示例程序中 index_list.jsp 及 process_instance_list.js 文件。

    新建一个 Servlet,命名为 DisposeIndexServlet,该 Servlet 会接受页面参数,将参数通过“COCE_GIW_ALL_Index”活动传入到流程中,并将流程从当前活动推动到下一个指定的活动节点上,详细代码可查 看示例程序中的 DisposeIndexServlet.java,下面是关键代码:


    清单 5. DisposeIndexServlet.java 代码片段

    				 
     
     int variableLength = 5; // variable length need to 
     // initialize the varaibles 
     Variable[] vars = new Variable[variableLength]; 
     for(int i=0; i<vars.length; i++){ 
        vars[i] = new Variable(); 
     } 
     vars[0].setName("maxNumberApprovers"); 
     vars[0].setValue(numApprs); 
     try { 
        vars[1] = makeApproverName(approvers); 
     } catch (ParserConfigurationException e) { 
        e.printStackTrace(); 
     } catch (SAXException e) { 
        e.printStackTrace(); 
     } 
     vars[2].setName("duplicateCheck"); 
     vars[2].setValue(duplicateCheck); 
     ...... 
     vars[3] = makeContentList(contents); 
    
     // dispose the complex value 
     ComplexValue cv = (ComplexValue)vars[3].getValue(); 
     MessageElement arrayOfStr = cv.get_any()[0].getRealElement(); 
     if(null != arrayOfString){ 
        List<MessageElement> elementList = arrayOfStr.getChildren(); 
        for(MessageElement strEle : stringElementList){ 
            Text e = (Text) strEle.getChildren().get(0); 
            e.setValue(mailContent); 
        } 
     } 
     vars[4] = makeInvoiceData(data); 
        

    如上所示,程序首先创建 Variable 类型的数组,程序通过该数组给 Process 实例传递参数,对于简单参数,只需要调用 Variable 对象的 setName 和 setValue 为设置名称和对应的值,名称必须和活动中的参数名称相同。代码中 vars[1]、vars[3] 和 vars[4] 比较特殊,因为他们都是复杂类型的数据,类似于 C/C++ 中的结构体,需要通过反序列化的方式,其中 vars[1] 对应了流程定义中的参数 approverNames,它是一个 String 的列表类型,但是不同于 Java 的 String[] 或是 List<String> 及其子类型 , 后面会有详细的介绍,vars[3] 对应了 BPD 中的 mailContents 参数,也是 String 列表类型,vars[4] 对应了 BPD 中的 InvoiceDataModel 类型,同样是复杂类型的数据结构,下面以 InvoiceDataModel 为例,介绍一下 makeInvoiceData 方法中的序列化代码,如下:


    清单 6. 序列化代码片段

    				 
     
     Variable v = null; 
     StringBuffer outputXml = 
     new StringBuffer("<InvoiceDataModel xmlns="http://www.w3.org/2001/XMLSchema">"); 
     outputXml.append("<Source>" + data.source + "</Source>>"); 
     outputXml.append("<UserID>" + data.userid + "</UserID>"); 
     outputXml.append("<Timestamp>" + data.Timestamp + "</Timestamp>"); 
     outputXml.append("<D_DocStatus>" + data.d_DocStatus + "</D_DocStatus>"); 
     outputXml.append("<ORIGIN_ITEM_ID>" + data.origin_item_id + "</ORIGIN_ITEM_ID>"); 
     outputXml.append("<OLD_CREATETS>" + data.old_creates + "</OLD_CREATETS>"); 
     outputXml.append("</InvoiceDataModel>"); 
     // create variable 
     v = new Variable("invoiceData", ClientUtilities.toComplexValue(outputXml.toString())); 
     return v; 
        

    在构建流程定义的过程中,我们创建了复杂的数据类型 InvoiceDataModel,图 26 展示了该数据类型的 Schema 定义,上面的代码即根据该 Schame 的定义构建复杂数据类型的结构,最后通过 ClientUtilities 将 XML 转换成一个 ComplexValue 类型,并通过 Variable 的构造函数创建一个 Variable 对象,其第一个参数为 BPD 中定义的变量的名称。

    由前述代码可以看到,在构建 vars[3] 也就是 mailContents 的时候,做了一部分额外的处理,这里涉及到 ComplexValue 的结构。ComplexValue 是一个复杂的数据类型,它根据被反序列化的 XML 内容的结构,其层次可以是一个嵌套的很深的 N 叉树。当复杂结构的内容中出现了本身就是 XML 的情况时,也就是说数据本身是 XML 数据,比如出现 HTML 标签,则需要像上面一样做额外处理,否则标签数据会被“吞掉”,代码如下:


    清单 7. HTML 标签处理代码片段

    				 
     
     vars[3] = makeContentList(contents); 
     // dispose the complex value 
     ComplexValue cv = (ComplexValue)vars[3].getValue(); 
     MessageElement arrayOfStr = cv.get_any()[0].getRealElement(); 
     if(null != arrayOfString){ 
        List<MessageElement> elementList = arrayOfStr.getChildren(); 
        for(MessageElement strEle : stringElementList){ 
            Text e = (Text) strEle.getChildren().get(0); 
            e.setValue(mailContent); 
        } 
     } 
        

    如上面所示,代码第三行先获取 Variable 对象中的 ComplexValue 对象,接下来的代码是通过调试确定你当前的 ComplexValue 对象实际存储值的位置,该位置与被反序列化的 XML 的结构内容有关,也就是说上面的代码不是通用的。最后找到真正存储值的位置将需要的值设置进去,即最后 setValue 的调用。

    所有的参数设置完毕后,调用 WebAPI 将参数转入对应的流程实例,如下代码所示:


    清单 8. WebAPI 将参数转入流程实例代码片段

    				 
     
     // get process instance by its ID 
     ProcessInstance proIn = webAPI.getProcessInstance(instanceID); 
     logger.info("Process Instance : " + proIn.getName()+ " start to be disposed"); 
     if (null != proIn) { 
        Task[] tasks = proIn.getTasks(); 
        if (null != tasks && tasks.length > 0) { 
        // get the index task 
            currentTask = tasks[0]; 
            if (!checkTaskAttribute(currentTask)) { 
                // just the the return result 
                retResult = false; 
             } else { 
                // forward the task 
                retResult = webAPI.completeTask(currentTask.getId(), vars); 
              } 
        } 
     } 
        

    上面代码第二行,通过流程实例的 ID 获取 ProcessInstance 的对象,通过 getTasks 方法拿到该流程当前可查询的所有任务,上面的代码获取了第一个 Task,即“COCE_GIW_ALL_Index”中的任务,最后调用 completeTask 方法将参数传入对应的 Task 并完成任务,将流程推到下一个活动中。

    该 Servlet 对应的页面代码可查看 basic_index.jsp 及 basic_index_form.js。此时发票录入的 Web 模块创建完成。

    审核模块与前述的发票录入模块类似,详细代码可查看 ApproverServlet.java、approver.jsp 及 approver_form.js。

    所有 Web 功能模块定义完成后,需要定义后台程序以查询新出现的文档并启动相应的流程实例。InitServlet 和 FileLoadDaemon 即完成这样的工作,详细代码可查看 InitServlet.java 及 FileLoadDaemon.java。InitServlet 的关键代码如下:


    清单 9. InitServlet.java 代码片段

    				 
     
     // start the daemon thread 
     daemon=new FileLoadDaemon(properties,doctype_url_mappings,application); 
     Thread tDeamon = new Thread(daemon); 
     if (null != tDeamon) { 
     /* daemonT.setDaemon(true); */ 
        tDeamon.start(); 
     } 
        

    上述代码创建了一个 FileLoadDaemon,该实例是一个线程,当 Servlet 的 Init 方法退出时,该线程会一直循环下去,直到服务器退出。FileLoadDaemon 关键代码如下所示:


    清单 10. FileLoadDaemon.java 代码片段

    				 
     
     while (alive) { 
        if (null == dirPath || "".equals(dirPath)) { 
            if (null != properties) { 
                String contextpath = properties.get("contextPath"); 
                String dir = properties.get("base_dir"); 
                // make dirPath from context path and base dir 
                dirPath = contextpath + dir; 
                } // if inner 
        } // if outer 
        File fileDir = new File(dirPath); 
        if (!dirValidation(fileDir)) { 
            break; 
        } 
        File[] files = fileDir.listFiles(); 
        if (null != files && files.length > 0 && isFileModified(fileDir)) { 
            for (File f : files) { 
                if (f.isDirectory()) { 
                    continue; 
                } 
                startProcessInstance(f.getPath()); 
            } // for loop 
        } // if outer 
     } // while loop 
        

    该程序首先获取需要检查的服务器目录,如果目录有新文档加入,其调用 startProcessInstance 方法,该方法创建一个 StartPIThread 的实例,该实例也是一个线程,该线程将会启动一个流程实例,并将启动实例时所需的参数传入,本例中为文档的存储路径,详细代码可查看 StartPIThread.java。关键代码如下:


    清单 11. StartPIThread.java 代码片段

    				 
     
     // get Process instance 
     Process pro; 
     try { 
     pro = webAPI.getProcessBySystemId(appAcronym, systemID); 
     // launch the ProcessInstance instance 
     proInst = webAPI.startProcess(pro, vars); 
        logger.info(proInst.getName() + "has been launched"); 
     } catch (RemoteException e) { 
        e.printStackTrace(); 
     } 
        

    首先通过 WebAPI 的 getProcessBySystemId 方法获取对应的流程定义,该方法需要两个参数,第一个为 Process App 的简短名称,即前述创建 Process App 时填写的首字母缩写,此处为“RAPSIM2”。第二个参数为该 Process 的系统 ID。获取 Process 对象之后,通过 startProcess 即可启动一个流程实例,其中第一个参数为 Process 对象,第二个参数为需要传递的参数,是一个 Variable 数组类型的数据对象。

    所有模块构建完毕之后,即可运行该 BPM 应用程序。首先启动所使用的 Web 服务器。如果 IBM BPM 的服务器没有启动,需要先等待 IBM BPM 服务器启动,并通过 Authoring Environment 查看流程运行情况。此时,在控制台视图中,可以查看到如图 15 所示的信息:


    图 15. 流程实例启动信息
    图 15. 流程实例启动信息

    上面的信息显示有 6 个实例被启动,ID 分别从 301 到 306. 实例启动的个数与文档的个数相同。打开 Authoring Environment 的检查器,可以看见启动的 6 个实例,如图 16 所示:


    图 16. 查看流程实例
    图 16. 查看流程实例

    图 17. 查看流程实例当前状态
    图 17. 查看流程实例当前状态

    打开浏览器,在地址栏输入 http://localhost:8080/LombardiSimulation/indexer/index_list.jsp 可看到当前所有的活动实例的列表,如图 18 所示:


    图 18. 活动流程列表
    图 18. 活动流程列表

    任意点击一个 execute, 例如点击 305 左边的三角形按钮,页面会跳转到发票录入页面,点击“Open Document”按钮,可以查看要录入的发票的 Image 图像,填完信息后,点击“Submit”可提交表单。提交后,打开 Authoring Environment,在“查看器”视图中,点击右上方的 refresh 按钮,会接收到新的 Task,流程图中当前的活动游标指向“SendMail”活动,如图 19 所示:


    图 19. 流程进入 SendMail 活动
    图 19. 流程进入 SendMail 活动

    再次点击 refresh 按钮,由于 SendMail 是系统活动,不需要人工干预,此时流程会自动完成 SendMail 的活动,并将流程推动到下一个活动 Approve 的节点,如图 20 所示:


    图 20. 流程进入 Approve 活动节点
    图 20. 流程进入 Approve 活动节点

    此时,打开你的测试邮箱,会发现点击进入 Approve 页面的超链接,点击时,页面自动跳转到进行发票审核的页面。当点击“Approve”按钮时,在“查看器”视图中点击 refresh 按钮,流程会自动进入到一级审核流程中,流程图最自动更新到新一次的“SendMail”活动节点上,如图 21 所示:


    图 21. 流程进入“SendMail”节点
    图 21. 流程进入“SendMail”节点

    再点一次 refresh 按钮,流程会再一次回到“Approve”活动节点上,重复上面的工作,直到所有的人完成审核或者某一次提交审核表单是“Reject”,此时流程将正常结束,如图 22 所示:


    图 22. 正常结束表单
    图 22. 正常结束表单

    此时不再有新的活动产生,流程实例状态更新为“已完成”,即流程结束。此时点击“执行状态”中的“SimpleRapidBPD”,选择“变 量”选项卡,点击“currentApprovalStatus”变量,可查看到其值为 false,表明当前由于某一级审核被“拒绝”使得流程提前结束,否则其值为 true。自此整个 BPM 应用程序构建完成,流程运行结束。

    结束语

    本文中使用了 IBM BPM 的 Web API 作为 J2EE 系统和 BPM 系统交互的方式,在最新版的 IBM BPM 系统中提供了基于 REST 的 API,在使用方式和技术上有了很大的提升,感兴趣的读者可以根据本文章做适当的修改。文章着重于 IBM BPM 系统和 J2EE 系统的集成,在设计方面完全使用纯 J2EE 技术,即 JSP 加上 Servlet 的传统方式。如果考虑使用 IBM BPM 自带的 Coach 作为展示层,可查看相关资料。由于时间所限,本文中 BPM 对外部系统的调用使用的是最简单的 HTTP 的方式,有兴趣的读者可以使用 BPM 中的内置服务调用外部系统所暴露出的 Web Service 来完成交互。文中没有使用复杂的 JS 作为服务端脚本完成复杂的后台逻辑,在本文后续版本中可能会有相关实现。

    致谢

    感谢 IBM CDL 上海的周镇焕(zhouzhenhuan@cn.ibm.com)在各方面的大力支持。

    参考资料

    学习

    获得产品和技术

    讨论

  • 相关阅读:
    [LeetCode82]Remove Duplicates from Sorted List II
    IOS开发常见BUG和一些小技巧(PS:耐心看完,很实用)
    IOS-一步一步教你自定义评分星级条RatingBar
    iOS手机淘宝加入购物车动画分析
    iOS mac终端下的SQL语句
    iOS SQLite 数据库迁移
    iOS 判断两个日期之间的间隔
    iOS应用架构谈 本地持久化方案及动态部署
    用 SQLite 和 FMDB 替代 Core Data
    ios 消除 字符串 首尾空格
  • 原文地址:https://www.cnblogs.com/adrianlamo/p/3350719.html
Copyright © 2020-2023  润新知