|
AndreasNauerz,软件工程师,IBMBoeblingenLab
JuergenSchaeck,软件工程师,IBMBoeblingenLab
ThomasSchaeck,软件架构师,IBMBoeblingenLab
2005年8月8日
本文通过一个普通的旅行编排业务场景说明了如何向最终用户提供一种有效处理任务的方法。使用业务流程集成(IBM®WebSphere®PortalVersion5.1中的一个新功能)支持业务流程中需要人工参与的任务(例如,完成在线表单)。在本文中使用到了业务流程执行语言(BPEL),基于BPEL的工作流在WebSphereBusinessIntegrationServerFoundationVersion5.1.1中运行。本文是为这样一些人撰写的:为了在其门户中支持业务处理而协作的业务流程设计人员、Portlet应用程序开发人员,以及门户管理员。读者应该熟悉Portlet应用程序开发和业务流程设计。本文主要介绍了将业务流程集成到门户的编程的各个方面,没有详细描述业务流程建模。有关流程建模和工具的详细信息,请参阅参考资料。
引言
在随需应变的业务环境中,客户、员工和供应商之间的高效交互至关重要,各种业务需要允许这三方在业务流程中无缝地参与和协作。开发人员需要提供界面来支持与业务流程的人工交互,例如填写表单或对警告予以响应。需要在合适的时间将这些界面提供给合适的人,并且这些界面应该能够促进快速的操作和高效的处理。还需要向用户提供处理它们所需的信息、应用程序,以及表单。在本文中,将这些涉及人工交互的任务称为人工任务(humantask)。
在WebSpherePortal中,流程中的每一个面对人的活动都与一个任务页定义相关联,任务页定义的内容通常包含任务处理Portlet。这些Portlet检索人员活动的输入消息,将消息显示给用户,获得用户输入,并将其写入系统中。该页上的其他Portlet不能与工作流系统直接进行通信,但可以帮助处理任务,因此称为支持Portlet。有关业务集成功能的概述和更加详细的信息,请参阅WebSpherePortalV5.1InformationCenter的集成业务流程部分。
当用户单击MyTasksPortlet中显示的某个任务时(该Portlet是WebSpherePortal所附带的,并为用户列出了所有任务),系统会在导航区中动态地向用户显示任务页。由关联的任务页定义来定义它的布局。用户可以使用该页上的Portlet,一旦任务完成,此页就会消失。下面的场景说明了这一概念。本文的其他部分通过旅行请求审批(TravelRequestApproval)示例场景向您演示了开发业务流程门户应用程序的步骤。
下载和运行示例应用程序
请下载示例应用程序和源代码。解压缩bp-portal-sample.zip文件并打开readme.html文件,该文件描述了设置说明的内容和链接。
示例场景介绍
虚构的ACME公司的一名员工希望到纽约旅行,参加一个会议。他在预订旅行之前,需要发出一个旅行请求,他的请求必须经过经理的审批。他将旅行偏好作为请求的一部分进行定义,随后行政助理将利用这些偏好为他预订飞机。该旅行应用程序作为ACME公司员工门户的一部分提供。
下面是一个典型的交互流。
员工在浏览器中打开旅行请求页。此页包括一个旅行请求Portlet,员工可以使用它来发出旅行请求,此页还包括一个状态Portlet,它显示所有正在等待审批的旅行请求以及每个请求的当前状态。员工将信息(例如,旅行的原因、目的地机场,以及启程日期)输入到旅行请求Portlet中,然后单击提交按钮发出请求。在状态Portlet中将显示一个新的旅行请求(请参见图1)图1.JavaBeans视图

此员工的经理(目前正在门户中浏览)在顶部的导航区中看到一个警告,通知他有一个新的任务正在等待批准。经理单击警告,门户显示当前的任务列表,该列表现在包含需要审批员工旅行的任务。经理请求这项任务,然后单击它的标题。图2:经理的任务列表

门户显示一个新的页,此页包含用户先前输入的旅行请求详细信息和批准或拒绝请求的按钮。经理可以单击此页名称旁边的小X来关闭当前的任务页,并且在任何时候都可以重新打开。当经理单击某个按钮时,此页关闭,门户返回到MyTasksPortlet。图3:审批Portlet

然后,员工的行政助理接收到为旅行预订飞机的任务。他看到一个警告,单击警告来了解当前的任务。助理声明这项任务,然后单击标题。门户显示一个页,此页包含一个旅行预订Portlet,它显示员工的旅行偏好,还包含一个航班信息Portlet,它显示匹配的各个航班。助理选择一个航班,WebSpherePortal的Portlet间通信(也称为协作Portlet)机制将所选的航班数据传送到表单,因此该助理无需重新输入数据。图4:行政助理使用自动填写的表单预订旅行

员工返回到旅行页来查看旅行请求的状态,看到现在的状态是已批准和已预订。员工已为旅行做好了准备!
每个用户都可以轻松地处理自己的任务。用户无需担心需要使用的程序或者在何处获取进一步的信息。
您可以轻松地扩展该应用程序。若要添加其他有用的Portlet(例如,经理的项目日历(projectcalendar)或行政助理的即时消息Portlet),只需要将它们添加到适当的任务页定义中。
什么是业务流程门户应用程序?
业务流程门户应用程序包括:
业务流程的定义启动流程的Portlet处理人工任务的Portlet任务页创建业务流程门户应用程序涉及到几个团队成员或角色:
流程设计人员定义业务流程,包括控件和数据流、输入和输出消息的语法,以及流程中包含的各个活动的语义。流程设计人员可以使用WebSphereBusinessIntergrationModeler和WebSphereApplicationDeveloperIntegrationEditionV5.1.1创建设计。Portlet应用程序开发人员使用RationalApplicationDeveloperV6.0.0.1开发Portlet。门户管理员根据其领域的知识组合任务页的内容。门户管理员使用WebSpherePortal完成这项工作。有时,所有这些角色和任务都是由一个开发人员处理。然而,如果不是这种情况(也就是说,多个人员处理这些角色),则流程设计人员必须向Portlet开发人员提供下面的信息和代码。
活动规范
活动规范详细描述了活动的目的、输入和输出消息相互关联的方式、消息中各个条目所接受的值,以及输出消息的期望结果。此规范还包含输入和输出消息的正确语法的定义。
生成的类
有两种情况需要交换生成的类。
某个输入/输出消息是嵌套的或包含复杂的类型。在这种情况下,Portlet必须有权访问这些类。Portlet开发人员应该使用一个已经生成且获得采用的Web服务或EJB接口来启动流程实例,而不是通用EJB接口。如果这不是必需的,则可以使用创建流程实例的通用EJB接口。在这种情况下,指定模板就够了。由于门户公开的每一个人工活动都会分配到任务页定义中,因此每一个人员活动都需要有唯一的客户机UI标识符。为了保证其唯一性,可以重用Java™包结构模式;例如,acme.travelrequests.approvalPage。流程设计人员需要指定该标识符,然后将它传递给门户管理员,门户管理员将其作为唯一名称分配到任务页定义中。
开发业务流程
可以使用IBMWebSphereApplicationDeveloperIntegrationEditionV5.1.1(以下称为IntegrationEdition)创建业务流程的门户连接。
检验旅行请求流程
图5显示了整个示例过程流。
图5:流程模型

当接收到用户的输入时输入活动开始。输入活动启动旅行审批流程。approveRequest表示第一个人员活动。它需要经理根据员工提供的信息决定是否批准请求。流程引擎是路由到第二个人员活动(要求行政助理预订适当的航班的bookFlight),还是直接路由到输出节点(如果经理拒绝员工的请求),这取决于经理的决定。JavaSnippets存储有关哪个人处理了哪项任务的信息。将任务页定义分配到活动
应该在门户中处理的每一个活动都与一个任务页定义相关联。在示例应用程序中,approveRequest活动与包含旅行审批Portlet的审批页相关联。应该使用的客户机UI标识符(唯一名称)是wps.ApprovePage
。bookFlight活动与包含FlightBooking和FlightSelectPortlet的FlightBook页相关联。
要设置人员活动和任务页定义之间的关联,业务流程设计人员需要使用IntegrationEdition执行以下操作。
在BusinessIntegration透视图中打开项目。单击应该为其定义客户机UI标识符的人员活动;本例中为approveRequest。单击Client选项卡,然后选择ClientDefinitions=>Portal。对于UniqueName参数,单击Value字段,然后添加唯一名称wps.ApprovePage
。在Type参数中,单击Value字段,然后从下拉列表中选择PageDefinition。图6:定义客户机UI设置

使用MemberManager人员插件提供程序
为了将面对人的任务分配给用户,WebSphereProcessChoregrapher使用在运行时分析并定义任务的潜在所有者列表的StaffVerb。例如,GroupMembersStaffVerb返回作为某个特定组的一部分的用户列表。执行分析的组件称为StaffPlugin,它访问用户资料库,例如,LDAP或自定义用户注册表(CustomUserRegistry),来执行此任务。
有关人员分析(StaffResolution)机制的详细描述,请参阅WebSphereApplicationServerEnterpriseProcessChoreographer:StaffResolutionArchitecture。还可以参阅WebSpherePortalV5.1InfoCenter的MemberManager人员插件提供程序部分。
为了从WebSpherePortal和WebSphereProcessChoreographer提供用户资料库的统一视图,WebSpherePortal5.1附带了一个特殊的人员插件,该插件使用的WebSphereMemberManager用户资料库还可以由WebSpherePortal使用。
业务流程设计人员使用IntegrationEdition指定用于流程中人员分析的MemberManager人员插件提供程序:
在Services视图中,右键单击服务项目。选择Properties=>Staff。定义人员插件配置的JNDI名称;例如,bpe/staff/wpswmmconfiguration。图7:指定人员插件提供程序

人员分析与其他人员插件的角色和动词的定义相同。在示例中,审批者组中的所有用户都可以处理approveRequest类型的任务。
审批请求:
在编辑器中,单击应该为其定义客户机UI标识符的人员活动;例如,approveRequest。单击Staff选项卡。从可用角色列表中,单击PotentialOwner。选择GroupMembers作为动词。输入要使用的组的名称(本例中为approvers),然后为IncludeSubgroups属性从下拉列表中选择false。图8:定义人员分析

生成部署代码
创建或更新流程后,业务流程设计人员可以生成要部署到服务器的代码。在IntegrationEdition中:
在服务项目中,右键单击.bpel文件。选择EnterpriseServices=>Generatedeploycode。要导出已经生成的EJB,右键单击EJB,选择Export=>ExportasEAR,然后选择一个导出目的地。现在门户管理员可以使用WebSphereApplicationServer管理控制台部署EAR文件(或一个更新文件)。有关详细信息,请参考下载中包含的安装说明。
导出任务处理Portlet需要的类
在开发业务流程的过程中,必须将一些已经生成的类提供给任务处理Portlet开发人员,以将它们包含在Portlet应用程序中。
IntegrationEdition为<processname>.wsdl
文件中定义的所有消息和添加到架构中的所有复杂类型生成类。消息的包名附带有_msg
后缀。例如,当将WSDL文件中的目标名称空间设置为x.y.z.TravelRequest
时,类的包名为x.y.z.TravelRequest_msg
。复杂类型的包名也以类似的方式进行构建,然而,这些包名没有_msg
后缀。这些类是任务处理Portlet所必需的,因此将它们以JAR文件的形式放在相应的Portlet应用程序的/WEB-INF/lib
目录中。
要导出包含示例中所需的这些类的JAR文件,请执行以下步骤:
右键单击要导出的包:com.ibm.wps.wfi.samplecom.ibm.wps.wfi.TravelRequestProcess_msgcom.ibm.wps.wfi.TravelRequestProcess_msg_beans
|
从上下文菜单中选择Export。选择Jar文件。为JAR文件输入目的位置。单击Finish。开发Portlet
接下来,您可以演练Portlet开发人员创建流程启动和任务处理Portlet所采用的步骤。使用RationalApplicationDeveloperVersion6.0(以下称为ApplicationDeveloper)创建这些Portlet。
导入需要的类库
需要将某些类添加到Portlet的类路径中。将打包到JAR文件中的已经生成的流程消息类添加到Portlet的/Web-Content/WEB-INF/lib
目录中。
在ApplicationDeveloper中,将一个JAR文件添加到类路径:
打开显示Portlet项目的视图。打开Portlet项目的/Web-Content/WEB-INF/lib
文件夹。右键单击/lib
文件夹。从上下文菜单中选择Import。打开导入向导。选择SelectFromFile,然后单击Next。单击Browse选择JAR所在的文件夹,然后单击OK。选择JAR并单击Finish。JAR已导入到Portlet项目中。
还需要指定位于was_root/lib下的这些JAR作为外部JAR进行引用:
wsif.jarwsif-j2c.jarwsatlib.jar
|
使用RationalApplicationDeveloper:打开显示Portlet项目的视图。右键单击项目,然后从上下文菜单中选择Properties。选择JavaBuildPath。选择Libraries选项卡。单击AddExternalJARs。浏览上面列出的三个JAR文件。单击Finish。开发流程启动Portlet
要开发流程启动Portlet,请使用BusinessProcessEngineofWebSphereProcessChoreographer(BPEAPI)。请参阅参考资料以找到BPEAPI的Javadoc。
首先,需要编写查找BPEEJB的代码。最好不要硬编码BPEEJB的位置,而是在web.xml中指定EJB引用:清单1.构建和安装流程
<ejb-ref><ejb-ref-name>ejb/BusinessProcessHome</ejb-ref-name><ejb-ref-type>Session</ejb-ref-type><home>com.ibm.bpe.api.BusinessProcessHome</home><remote>com.ibm.bpe.api.BusinessProcess</remote></ejb-ref>
|
要添加一个引用,可以在ApplicationDeveloper中执行以下步骤:
双击WebDeploymentDescriptor。选择References选项卡。使用Add按钮创建一个新的引用。输入图9所示的数据。图9:通过RAD添加一个EJB引用

可以使用WebSphereApplicationServer管理控制台设置引用的特定位置(您可以在不更改源代码的情况下对其进行更改):
选择EnterpriseApplications下的流程应用程序。选择MapEJBreferencestobeans。图10:设置EJB引用的JNDI名称

获得指向BPEEJB的引用的代码使用EJB引用名称(在示例中为ejb/BusinessProcessHome)。
publicBusinessProcessgetBusinessProcessEJB()throwsException{[...]//ObtainthedefaultinitialJNDIcontextContextinitialContext=newInitialContext();//LookuptheremotehomeinterfaceoftheBPEEJBObjectresult=initialContext.lookup("java:comp/env/ejb/BusinessProcessHome");//ConvertthelookupresulttothepropertypeBusinessProcessHomeprocessHome=BusinessProcessHome}javax.rmi.PortableRemoteObject.narrow(result,BusinessProcessHome.class);//CreateBusinessProcessprocess=processHome.create();returnprocess;...
|
启动流程实例的方法需要输入ClientObjectWrapper,它包装包含真正的输入数据的WSIFMessage。要检索流程输入消息的空实例,请使用下面的代码。
publicClientObjectWrappergetProcessInputMessage(BusinessProcessbusinessProcess,StringtemplateName)throwsException{ProcessTemplateDatatemplateData=businessProcess.getProcessTemplate(PROCESS_NAME);StringinputTypeName=templateData.getInputMessageTypeName();ClientObjectWrapperwrapper=businessProcess.createMessage(templateData.getID(),inputTypeName);returnwrapper;[...]
|
流程启动Portlet显示允许最终用户输入数据的表单和用于启动流程实例的Submit按钮。当用户单击Submit按钮时,门户将触发执行以下代码的Portlet操作:
//GetareferencetotheBPEEJBBusinessProcessbusinessProcess=workflowService.getBusinessProcessEJB();//GettheprocessinputmessageClientObjectWrapperinputData=workflowService.getProcessInputMessage(businessProcess,PROCESS_NAME);WSIFMessageinputMessage=(WSIFMessage)inputData.getObject();//FillthevaluesoftheinputmessageusingthegeneratedJavaBeanTravelRequestInputMessagetravelInputMessage=newTravelRequestInputMessage(inputMessage);travelInputMessage.setDestination(destination)[...]//TriggertheprocessbusinessProcess.initiate(PROCESS_NAME,inputData);
|
提示:PROCESS_NAME是流程模板(processtemplate)的名称。开发任务处理Portlet
要开发任务处理Portlet,请使用下面的API:
TaskAPIWebSphereProcessChoreographer的人工任务管理器TaskUIManagerAPI用于任务用户界面的Portlet服务PropertybrokerAPI与其他Portlet交换属性TaskAPI的Javadocs位于was_root/web/apidocs.task/,其中was_root是安装WebSphereApplicationServer的目录。
TaskUIManager和PropertyBrokerAPI的Javadocs位于wp_root/doc/Javadoc/WP/API,其中wp_root是安装WebSpherePortal的目录。
任务处理Portlet必须包含由WebSphereStudioApplicationDeveloperIntegrationEdition生成的消息类以及用于属性代理支持的特殊类。
任务处理Portlet的内部控制流可以分成三个阶段。
从MyTasksPortlet检索属性。处理任务。关闭任务页。从MyTasksPortlet检索属性
由于MyTasksPortlet发送的属性只能传送一次,因此只要属性是必需的,任务处理Portlet就必须存储它们。
表1.MyTasksPortlet发送到任务处理Portlet的属性
名称 | 值 | 描述 |
TaskID | String | 任务的唯一标识符 |
ReturnPageID | com.ibm.portal.ObjectID | 当任务完成时,用户应该导航到的页的对象ID。 |
TaskUIHandle | com.ibm.portal.taskui.TaskUiHandle | 识别当前任务页的句柄。用于关闭任务页。 |
任务处理Portlet必须指示它可以使用portlet.xml中的以下设置检索任务属性:
<preference><name>com.ibm.portal.pagecontext.enable</name><value>true</value></preference>
|
只有审批Portlet和航班预订Portlet是从MyTasksPortlet检索属性的任务处理Portlet。通过添加该首选值,启用每一个Portlet来检索上下文:
双击PortletDeploymentDescriptor。切换到Portlets选项卡。在PersistentPreferenceStore部分中,单击Add按钮添加一个新条目。输入com.ibm.portal.pagecontext.enable
作为名称,并输入true
作为值。图11:启用Portlet以检索上下文

遵从JSR168规范的Portlet使用com.ibm.portal.action.namerequest属性检索processAction()方法的页面上下文参数。该属性的值是存储上下文条目的一个映射。可以按名称获得每一个属性的值。
因此,在审批Portlet和航班预订Portlet的processAction()方法内,以下代码检索表1中的三个属性:
publicvoidprocessAction(ActionRequestrequest,ActionResponseresponse)throwsPortletException,java.io.IOException{[...]//PerformpagecontextprocessingStringspecialAction=request.getParameter(PARAMETER_SPECIALACTION);if(specialAction!=null&&specialAction.equals(ACTION_PAGECONTEXT)){//Thisindicatescontextwaspassedtothelaunchedpagejava.util.MapcontextMap=(java.util.Map)request.getAttribute(PARAMETER_PAGECONTEXT);//ReadtheinformationweneedStringtaskID=(String)contextMap.get(SESSION_TASKID);TaskUIHandletaskUIHandle=(TaskUIHandle)contextMap.get(SESSION_TASKUIHANDLE);ObjectIDreturnPageID=(ObjectID)contextMap.get(SESSION_RETURNPAGEID);//StorethepropertiesreadinthesessionportletSession.setAttribute(SESSION_TASKID,taskID);portletSession.setAttribute(SESSION_TASKUIHANDLE,taskUIHandle);portletSession.setAttribute(SESSION_RETURNPAGEID,returnPageID);}[...]
|
处理任务
要处理任务,请执行以下步骤:
使用TaskManagerDelegateFactoryService获得TaskManagerDelegate。TaskManagerDelegate是HumanTaskManager(HTM)EJB的业务委托(businessdelegate),用于与流程引擎进行交互。因此,审批Portlet和航班预订Portlet包含以下代码:publicvoidinit(PortletConfigportletConfig)throwsPortletException,UnavailableException{super.init(portletConfig);[...]Contextctx=newInitialContext();//ObtainareferencetotheTaskUIManagerservice,whichisusedtoclosetaskpagesandtosentaredirectafterclosurePortletServiceHomeserviceHome=(PortletServiceHome)ctx.lookup(SERVICE_TASKUIMANAGER);taskUIManager=(TaskUIManager)serviceHome.getPortletService(TaskUIManager.class);//ObtainareferencetothetaskmanagerbusinessdelegatetointeractwiththeprocessPortletServiceHometaskManagerDelegateFactoryServiceHome=(PortletServiceHome)ctx.lookup(SERVICE_TASKMANAGERDELEGATEFACTORY);TaskManagerDelegateFactoryServicetaskManagerDelegateFactoryService=(TaskManagerDelegateFactoryService)taskManagerDelegateFactoryServiceHome.getPortletService(TaskManagerDelegateFactoryService.class);taskManager=taskManagerDelegateFactoryService.getTaskManagerDelegate();}
|
从HTM中检索输入消息并显示此消息。任务处理Portlet使用TaskManagerDelegate的getInputMessage()方法从HTM获得输入消息。所用的TaskID是先前从MyTasksPortlet中检索的TaskID属性。为审批Portlet和航班预订Portlet配备以下代码:publicvoidgetTravelRequest(PortletRequestrequest){[...]//ObtaintheTaskIDofthecurrenttaskStringtaskID=(String)session.getAttribute(SESSION_TASKID);//ReadtheinputmessageWSIFMessagemessage=(WSIFMessage)(((com.ibm.task.api.ClientObjectWrapper)taskManager.getInputMessage(taskID)).getObject());[...]
|
重要:当任务的输入或输出消息包含复杂类型时,请确保将WebSphereStudio流程工具为这些消息创建的Java类添加到Portlet的类路径中。该路径为Portlet的/WEB-INF/classes目录,如果打包到一个JAR文件,则在/WEB-INF/lib目录中。
显示输入消息并提供一个用户界面来从用户获得所需的输出数据。作为此步骤的一部分,Portlet可以通过属性代理与页上的其他支持Portlet交换属性。将输出消息发送到HTM。用户将信息输入到Portlet并提交更改以后,Portlet使用TaskManagerService接口的setOutputMessage()将信息发送到HTM。对于审批Portlet,使用输出消息指示经理已批准或已拒绝旅行请求:
//ReadtheTaskIDofthecurrenttaskStringtaskID=(String)portletSession.getAttribute(SESSION_TASKID);//ObtaintheactualtaskTasktask=taskManager.getTask(taskID);//Createandsettheoutputmessagecom.ibm.task.api.ClientObjectWrappercow=(com.ibm.task.api.ClientObjectWrapper)taskManager.createMessage(taskID,task.getOutputMessageTypeName());WSIFMessagemessage=(WSIFMessage)cow.getObject();ApprovalStatusMessageapprovalStatus=newApprovalStatusMessage(message);approvalStatus.setManagerApproved(true);approvalStatus.setWorker(userName.toString());taskManager.setOutputMessage(taskID,cow);
|
如果经理拒绝了旅行请求,则将输出消息更改为approvalStatus.setManagerApproved(false)。
在航班预订Portlet中执行类似的操作。
重要:当任务的输入或输出消息包含复杂类型时,请确保将WebSphereStudio流程工具为这些消息创建的Java类添加到Portlet的类路径中。该路径为Portlet的/WEB-INF/classes目录,如果打包到一个JAR文件,则在/WEB-INF/lib目录中。
完成任务。Portlet使用TaskManagerDelegate的complete()方法。因此,设置输出消息以后,审批Portlet和航班预订Portlet都使用以下命令完成任务://CompletethetasktaskManager.complete(taskID);
|
关闭任务页
Portlet使用TaskUIManagerPortlet服务关闭任务页,并使用户返回到任务列表页。
审批Portlet和航班预订Portlet都使用以下命令关闭任务页:
//FinallyclosethepageandperformtheredirecttaskUIManager.closeDynamicUI(request,response,taskUIHandle);
|
重要:参数taskUIHandle包含MyTasksPortlet先前发送的属性的值。
关闭此页并设置新的页
最后,setPage()方法指示只有任务页关闭以后才能打开新的页(ReturnPageID)。审批Portlet和航班预订Portlet都使用以下命令重定向到承载MyTasksPortlet的页:
taskUIManager.setPage(request,response,returnPageID);
|
参数returnPageID包含MyTasksPortlet先前发送的属性的值。
结束语
在本文中,您已经了解如何创建利用WebSpherePortal中的业务流程集成功能的应用程序,以将人员、流程和信息连接到一起。您可以使用自己的Portlet扩展示例应用程序,或者用自己的版本替换现有的Portlet。还可以启动公司现有的业务流程。
来源:互连网