转自 http://www.ibm.com/developerworks/cn/lotus/domino-wsc-wps/
IBM Lotus Domino 是 IBM Lotus Notes 系列企业级协作软件的服务器端产品。在 Lotus Domino 中,业务数据和业务流程存储在 NSF 格式的数据库文件中。一个 NSF 数据库文件也称为一个应用。在 Lotus Domino 中,所有的数据,例如电子邮件,讨论文章或者是结构化的业务数据,都以文档的方式保存,同一类文档都按同一个表单展现。通过 Lotus Domino Designer,可以设计 Lotus Domino 应用的业务数据格式,即设计表单 Form;也可以使用 Lotus Script. 或 Java 语言开发业务流程程序。结合表单和业务流程程序,就可以实现多种多样复杂的应用。本文在后面的章节将结合 Web Service Consumer 的使用介绍如何利用 Lotus Script. 或 Java 语言开发业务流程程序,本文中使用的 Lotus Domino 示例应用包括 Lotus Script. 和 Java 代码在 TestWebS.nsf 中。
IBM WebSphere Process Server(WPS)是 IBM 的一款企业级业务流集成服务器。WPS 是基于 WebSphere Application Server (WAS) 构建的面向服务架构 (SOA) 平台。WPS 的开发工具是 WebSphere Integration Developer (WID),通过 WID,用户可以很方便的将基于各种不同标准的服务部署到 WPS 上。WPS 上提供对 Business Process Execution Language (BPEL),状态机和 Java 等多种业务流实现方式的支持。在本文中,将利用 WID 在 WPS 上部署一个示例 Web Service,以模拟在 WPS 上实现的某部分业务流,并通过示例介绍如何在 Lotus Domino 业务流中调用它,本文中使用的 WPS 示例应用在 MyWebServiceSample.zip 中。
Web Service 是一种面向服务架构(Service Oriented Architecture)的实现技术,用以实现网络间不同应用服务之间的互操作。Web Service 系统是一个跨平台,跨语言的软件系统,通过 Web Service,系统间共享服务的数据格式和调用方式,而服务的具体实现隐藏在后端。这样,一个运行在 Windows 上的软件可以调用运行在另一台 Unix 机器上的服务,一段 Java 程序可以调用另一个实际由 C++ 编写的服务。
正因为 Web Service 的这种跨平台,跨语言的特性,它经常被用来集成不同软件系统。本文将通过代码示例介绍如何利用 Web Service 集成两种业务流程服务器 Lotus Domino Server 和 WebSphere Process Server。在进入本文的关键技术之前,将在本小节简单介绍的关键技术,特别是 WSDL (Web Service Description Language).
XML
XML 是可扩展标记语言(Extensible Markup Language)的简称,因为其可扩展性,被广泛使用作为各种结构化数据描述语言的基础。WSDL 就是基于 XML 的。
WSDL
对于一个 Web Service 来说,服务的请求者并不关心服务的提供者如何实现该服务,但是服务的数据格式和调用方式必须是双方共享的,只有这样,服务的输入数据和输出数据才能被正确地传递和解析。Web Service 描述语言(WSDL)就是用 XML 来描述 Web Service 的数据格式和调用方式的语言。WSDL 定义了以下元素:
- 数据类型 types:采用通用的 XML 数据模型定义了 Web Service 中需要用到的数据类型。
- 消息 message:定义一个用以传递服务数据一个单向消息,可以是请求消息,也可以是答复消息,可以是正常消息,也可以是故障消息。一个消息可能包含多个数据元素。
- 操作 operation:定义了一个单向或双向的交互操作。一个操作可能包含输入消息,输出消息和故障消息。
- 端口类型 portType 或接口 interface:定义了一个服务接口,接口是一个服务调用方式的完整描述。一个接口中可能包含多个操作。
- 绑定 binding:绑定定义了服务接口的传输方式。一个绑定中指定了所绑定的服务接口和对应的传输方式。目前最常用的绑定类型是 SOAP。
- 端口 port 或 endpoint:端口指定了提供某个绑定的服务的计算机端口位置。对于绑定而言,就是提供的 HTTP 地址的 URL。
- 服务 service:定义了一个 Web Service 服务,一个服务可能包含多个端口。
SOAP
简单对象访问协议 (Simple Object Access Protocol)是利用 HTTP 或 SMTP 传输 XML 数据的协议。由于 HTTP 的易用性,大多数 Web Service 采用作为数据传输协议。
Web Service Consumer 是由 Lotus Domino 提供的在业务流中调用其他 Web Service 服务的机制。在 Lotus Domino 中,业务流是通过 Lotus Script. 或 Java 语言编程实现的。Web Service Consumer 的功能就是将一个描述 Web Service 服务的 WSDL 转化成 Lotus Script. 或 Java 的库函数,使它可以在 Lotus Script. 或 Java 程序中调用。后文将通过例子程序分别介绍如何创建和使用基于 Lotus Script. 或 Java 语言的 Web Service Consumer。
本节将使用 WID (WebSphere Integration Developer) 作为开发 Web Service 的工具,WPS 作为部署平台。利用 WID (WebSphere Integration Developer) 可以方便的创建 Web Service 并部署到 WPS 上。本节将用 WID 创建一个简单的服务 changValue,将一个输入对象 Value 属性改变并返回结果对象。
在 WID 的 Business Integration透视窗下,选择 File – New Module,并填写 MyWebServiceSample 作为模块名。
右键选择 MyWebServiceSample 的 Data Types目录,选择 New – Business Object,填写 MyBO 作为业务对象名,添加两个 string 类型的属性 Name 和 Value 作为该业务对象的域。创建完成的业务对象如图 1 所示。
右键选择 MyWebServiceSample 的 Interfaces目录,选择 New – Interface,填写 MyInterface 作为接口名。打开接口编辑器后,在右键菜单中选择 Add Request Response Operation添加一个操作 changeValue;操作的输入和输出类型都选择 MyBO 为业务对象,如图 2 所示。
双击 MyWebServiceSample 的 Assembly Diagram打开编辑器,在编辑器中添加一个名为 MyExport 的 Export。选择 MyExport,在右键菜单中选择 Add Interface将接口 MyInterface 添加到编辑器中。在编辑器中添加一个名为 MyJavaImpl 的 Java模块。将 MyExport 同 MyJavaImpl 相连接,WID 将提示添加该接口的实现代码。WID 将自动为类 MyJavaImpImpl 填写一些空函数,我们只需要实现其中的 changeValue 方法。在 changeValue 方法中添加清单 1 中的代码。MyWebServiceSample 的如图 3 所示。
图 3. 业务应用 MyWebServiceSample 的组装图
清单 1. changeValue 方法代码
public DataObject changeValue(DataObject input1) {
String name = input1.getString("Name");
String value = input1.getString("Value");
System.out.println("Request: Name=" + name + ", Value=" + value);
// Create output data object using BOFactory service
BOFactory boFactory = (BOFactory)ServiceManager.INSTANCE
.locateService("com/ibm/websphere/bo/BOFactory");
DataObject output1 = boFactory.create("http://MyWebServiceSample", "MyBO");
output1.setString("Name", name);
if (null != value) {
output1.setString("Value", value.toUpperCase());
}
System.out.println("Response: Name=" + output1.getString("Name") +
", Value=" + output1.getString("Value"));
return output1;
}
|
将 Export 绑定到 Web Service 服务并部署到 WPS 上
选择 MyExport,在右键菜单中选择 Generate Binding – Web Service Binding – SOAP1.1/HTTP绑定,WID 会在 Web Service Ports目录中自动生成一个名为 MyExport_MyInterfaceHttpPort 的服务。这个自动生成的服务的端口默认为 9080,我们需要将其改成运行中 WPS 的 HTTP 服务端口,笔者的端口为 9086。在 WID 中检查整个 MyExport 可以看到图中的业务工件如图 4 所示。在确认应用创建无错误后,便可将该业务集成应用部署到 WPS 上。
图 4. 业务应用 MyWebServiceSample 的业务工件
收集调用 Web Service 所需的 WSDL 和 XSD 文件
部署成功后,changeValue 的 Web Service 提供者部分的工作就完成了。我们需要在的项目目录中收集该服务的 WSDL 文件以及该服务 WSDL 所引用的所有其他 WSDL 和 XSD 文件。所有这些表 1 中文件要在请求者一方可以访问,服务请求者才能成功调用这个服务。
表 1. 项目 MyWebServiceSample 的 Web Service 描述文件列表
| 文件名 | 作用 |
|---|---|
| MyWebServiceSample_MyExport.wsdl | 描述了 Web Service 服务 |
| MyInterface.wsdl | 定义了接口 MyInterface |
| MyBO.xsd | 定义了对象 MyBO |
利用 Web Service Consumer 调用 Web Service
本节将用到 Lotus Domino Designer 作为开发工具,Lotus Domino 服务器作为工作平台。我们利用 Lotus Domino Designer 在 Lotus Domino 服务器上创建一个新的名为 Test Web Service 的应用,其文件名为 TestWebS.nsf。本文所有的示例都将在 Test Web Service 中展示。
创建基于 Lotus Script. 的 Web Service Consumer
在 Lotus Domino Designer 中打开 Test Web Service 应用,选择 Code – Web Service Consumer,在右键菜单中选择 New Web Service Consumer,在创建对话框中填写 MyLotusScriptConsumer 作为名称,类型选择为 LotusScript. 并通过文件浏览对话框选择 Web Service 服务的描述文件 MyWebServiceSample_MyExport.wsdl,如图 5 所示,点击 OK,Lotus Domino Designer 会为我们自动生成调用对应 Web Service 所需的所有 LotusScript. 代码,如代码清单 2 所示。不过这里有一点要注意,就是要将表一中所有相关的描述文件都放在同一个 Lotus Domino Designer 可以访问到的目录。
图 5. 创建 Lotus Script. Web Service Consumer
清单 2. 生成的 MyLotusScriptConsumer 代码
%INCLUDE "lsxsd.lss"
Class MyBO_n16 As XSD_ANYTYPE
Public Name As XSD_STRING
Public Value As XSD_STRING
Sub NEW
End Sub
End Class
Const n17 = "http://MyWebServiceSample/MyInterface"
Const n16 = "http://MyWebServiceSample"
Class MyInterface_n17 As PortTypeBase
Sub NEW
Call Service.Initialize (
"HttpMyWebServiceSampleMyInterfaceBindingMyExport_MyInterfaceHttpService", _
"MyExport_MyInterfaceHttpService.MyExport_MyInterfaceHttpPort", _
"http://localhost:9086/MyWebServiceSampleWeb/sca/MyExport", _
"MyInterface_n17")
End Sub
Function changeValue(input1 As MyBO_n16) As MyBO_n16
Set changeValue = Service.Invoke("changeValue", input1)
End Function
End Class
|
代码总结
浏览 MyLotusScriptConsumer 代码,我们可以看到以下特点:
- 所有 WSDL 中定义的对象类型都是 XSD_ANYTYPE 的子类
- 业务对象类是 MyBO_n16
- 调用的服务类是 MyInterface_n17
- 调用的方法是 MyInterface_n17 中的 changeValue 方法
创建基于 Java 的 Web Service Consumer
基于 Java 的 Web Service Consumer 与基于 Lotus Script. 的基本相同,只是类型选择为 Java,名称命名为 MyJavaConsumer 如图 6 所示。Lotus Domino Designer 会为 WSDL 中定义的每一个对象生成一个 Java 类。生成的调用接口类 MyInterface 如清单 3 所示,其他生成的类请参照本文的示例应用 TestWebS.nsf。
图 6. 创建 Java Web Service Consumer
清单 3. 生成的 MyInterface.java 代码
package MyWebServiceSample.MyInterface;
public interface MyInterface extends lotus.domino.types.PortTypeBase, java.rmi.Remote {
public MyWebServiceSample.MyBO changeValue(MyWebServiceSample.MyBO input1)
throws java.rmi.RemoteException;
}
|
代码总结
浏览 MyJavaConsumer 代码,可以发现以下特点
- 所有 WSDL 都会生成一个相应的 Java 类。
- 业务对象类是 MyBO。
- 调用的服务类是 MyExport_MyInterfaceHttpServiceLocator,通过 MyExport_MyInterfaceHttpServiceLocator 的 getMyExport_MyInterfaceHttpPort 方法可以得到调用接口。
- 调用的接口类是 MyInterface。
- 调用的方法是 MyInterface 中的 changeValue 方法。
在 Lotus Domino 业务流中使用 Web Service
在本节中我们将通过示例,利用一个简单的表单 TestWebServiceForm. 来展示如何在业务流中通过 Web Service Consumer 来调用外部的 Web Service。一方面,调用的方式按实现的脚本语言类型可以分为 Lotus Script. 和 Java;另一方面,按照脚本运行的环境可以分为 Notes 客户端和 Domino 服务器端。按照通常的情况,运行在客户端的脚本的操作对象是界面显示中的文档;而运行在服务器端的脚本的操作对象是存储到数据库文件的文档。客户端脚本通过嵌在表单中的一个名为 Invoke MyLotusScriptConsumer 的 Action触发;而服务器端的脚本则在后台自动针对所有刚刚创建或更改过的文档运行。
在本例的表单 TestWebServiceForm. 中,我们有一个域 input 作为用户添加 MyBO 中 Value 的输入值,一个域 output 用于显示通过客户端调用 changeValue 后,MyBO 中 Value 的返回值,一个域 outputOnServer 用于显示通过服务器端调用 changeValue 后,MyBO 中 Value 的返回值。同时在表单中添加一个名为 Invoke MyLotusScriptConsumer 的 Action。一个典型按表单 TestWebServiceForm. 创建的文档如图 7 所示。
图 7. 符合 TestWebServiceForm. 表单的文档
为了在客户端手动触发对 MyLotusScriptConsumer 的调用,我在表单中添加了一个同时在表单中添加一个名为 Invoke MyLotusScriptConsumer 的 Action,这个 Action调用一个运行在客户端的名为 LotusScriptClientAgent 的 Agent,再通过 LotusScriptClientAgent 调用 MyLotusScriptConsumer 代码。Action调用 Agent有多种方式,最简单的是利用 Fomular,如清单 4 所示,Invoke MyLotusScriptConsumer 通过 Fomular脚本调用了 LotusScriptClientAgent。本节将通过 LotusScriptClientAgent 来介绍如何编写客户端脚本。
在 Lotus Domino Designer 中打开 Test Web Service 应用,选择 Code – Agents,在右键菜单中选择 New Agent,填写 LotusScriptClientAgent 作为名称,LotusScript作为类型,属性按照图 8 填写,代码参照代码清单 5。
图 8. LotusScriptClientAgent 属性
清单 4. Invoke MyLotusScriptConsumer 的 Fomular 代码
@Command([ToolsRunMarco];”(LotusScriptCientAgent)”) |
清单 5. LotusScriptClientAgent 代码
Option Public
Use "MyLotusScriptConsumer"
Option Declare
Sub Initialize()
Dim ws As New NotesUIWorkspace
Dim docui As NotesUIDocument
Dim inputName As New Xsd_string
Dim inputValue As New Xsd_string
Dim service As New MyInterface_n17
Dim inputBO As New MyBO_n16
Dim outputBO As MyBO_n16
'Update Notes client
Set docui = ws.CurrentDocument
Set inputBO.Name = New Xsd_string
Call inputBO.Name.Setvaluefromstring("Input from Notes")
Set inputBO.Value = New Xsd_string
Call inputBO.Value.Setvaluefromstring(docui.Getselectedtext("input"))
Set utputBO = service.Changevalue(inputBO)
Call docui.Fieldsettext("output", outputBO.Value.Getvalueasstring())
Call docui.Refresh
Call ws.Viewrefresh()
End Sub
|
代码总结
以本节的脚本为例,我们可以得到以下编写 Lotus Script. 的经验:
- 如果要 Agent只作为一个被调用的库函数,而不让它自动地或者定期地运行,那么该 Agent的 Trigger应设置为 On event – Agent list selection,Target应设置为 None。这种不自动运行的 Agent在保存后,Lotus Domino Designer 会自动为其名字加括号。
- 用 Use关键字导入其他脚本或 Web Service Consumer 作为脚本库。
- 对于操作客户端界面的脚本,主要的工具类有 NotesUIWorkspace 和 NotesUIDocument 等,这些类读写的是 GUI 界面显示的属性值,而与数据库中的实际存储值无关。
- 运行时的上下文入口为创建一个新的 NotesUIWorkspace 对象。
- 在更改完成后,调用 NotesUIDocument 的 Refresh 方法刷新文档,或调用 NotesUIWorkspace 的 Viewrefresh 方法刷新视图。
测试本小节示例
要测试本节示例中的脚本,只需以编辑方式打开任何一个 TestWebServiceForm. 文档,如图所示,在 Input属性中填写任意字符,然后点击 Invoke MyLotusScriptConsumer,则调用后的返回值会在 Output属性中显示。需要注意这里改变的只是界面的显示值,只有保存文档后,这些更新后的值才会存储到数据库中。
在服务器端调用 Lotus Script. Web Service Consumer
在服务器端运行的 Agent,主要分为定期触发和根据事件触发两类,本小节通过编写一个根据事件触发的 Agent介绍如何用 Lotus Script. 编写服务器端脚本处理那些最新被创建或更改的文档。因为服务器端的脚本都是在后台运行,所以一般来说脚本的操作对象是文档的数据结构,这一点跟客户端脚本是不同的。
在 Lotus Domino Designer 添加一个名称为 LotusScriptServerAgent 作为名称,LotusScript作为类型,属性按照图 9 填写,代码参照代码清单 6。
图 9. LotusScriptServerAgent 属性
清单 6. LotusScriptServerAgent 代码
Option Public
Use "MyLotusScriptConsumer"
Option Declare
Sub Initialize()
Dim session As New NotesSession
Dim database As NotesDatabase
Dim notesCollection As NotesDocumentCollection
Dim note As NotesDocument
Dim inputArray As Variant
Dim inputName As New Xsd_string
Dim inputValue As New Xsd_string
Dim service As New MyInterface_n17
Dim inputBO As New MyBO_n16
Dim outputBO As MyBO_n16
'Update Notes Server
Print "Agent started to run"
Set notesCollection = session.Currentdatabase.Unprocesseddocuments
Set note = notesCollection.Getfirstdocument()
While Not (note Is Nothing)
Set inputBO.Name = New Xsd_string
Call inputBO.Name.Setvaluefromstring("Input from Notes")
Set inputBO.Value = New Xsd_string
Print "Input string:" & note.Getfirstitem("input").Text
Call inputBO.Value.Setvaluefromstring(note.Getfirstitem("input").Text)
Set utputBO = service.Changevalue(inputBO)
Print "Input string:" & outputBO.Value.Getvalueasstring()
Call note.Replaceitemvalue("outputOnServer", outputBO.Value.Getvalueasstring())
Call note.Save(True, True, True)
Call session.Updateprocesseddoc(note)
Set note = notesCollection.Getnextdocument(note)
Wend
End Sub
|
代码总结
以本节的脚本为例,我们可以得到以下编写 Lotus Script. 的经验:
- 对于操作服务器端文档的脚本,主要的工具类有 NotesSession,NotesDatabase 和 NotesDocument 等,这些类读写的是内存中文档所对应的各个属性。
- 运行时的上下文入口为创建一个新的 NotesSession 对象。
- 在更改完成后,调用 NotesDocument 的 Save 方法保存对文档的修改。
- 对于新创建或更改的文档,Lotus Domino 会将它们标记为未处理的文档,通过 NotesDatabase 的 Unprocesseddocuments 方法可以得到所有未被处理的文档。在处理过文档后,要调用 NotesSession 的 Updateprocesseddoc 方法将文档标注为已处理。不过当同一个文档再次被更改的时候,它又会自动被标记为未处理。
测试本小节示例
服务器端的 Agent一旦保存成功就开始在后台自动运行了。本小节的 LotusScriptServerAgent 脚本一旦保存成功,会在后台定期的检查是否有未被处理的新更新文档,如果有新更新的文档,脚本会根据保存的 input 值调用,将返回值放到 outputOnServer 中。
要测试本节示例中的脚本,只需 TestWebServiceForm. 文档中的 input 值,保存该文档,等待一段时间后再打开该文档,会发现 outputOnServer 的值也发生了相应的改变。
后台运行的 Agent对文档的处理总会相对与文档的更改时刻有一些延迟。如果要减小这种延迟,可以更改 notes.ini 中与 Agent相关的 AMgr_DocUpdateAgentMinInterval,AMgr_DocUpdateEventDelay 和 AMgr_SchedulingInterval 等几个属性。需要注意的是更改配置后要重新启动 Lotus Domino 才能发挥作用,此外,减小延迟势必增加服务器的运行开销,对服务器性能有影响,在实际运行的系统中要综合考虑才能更改相应设置。
在服务器端调用 Java Web Service Consumer
在服务器端运行的 Agent调用 JavaWeb Service Consumer 的方法同调用 Lotus Script. Web Service Consumer 的方法基本一致,只是 Agent的脚本语言选择为 Java即可。本小节示例的程序如清单 7 所示。
import MyWebServiceSample.MyBO;
import MyWebServiceSample.MyInterface.Binding.MyExport_MyInterfaceHttpServiceLocator;
import lotus.domino.*;
public class JavaAgent extends AgentBase {
public void NotesMain() {
try {
Session session = getSession();
AgentContext agentContext = session.getAgentContext();
// (Your code goes here)
System.out.println("Start running agent: JavaServerAgent");
MyExport_MyInterfaceHttpServiceLocator service =
new MyExport_MyInterfaceHttpServiceLocator();
DocumentCollection collection = agentContext.getUnprocessedDocuments();
for (Document document = collection.getFirstDocument(); document != null;
document = collection.getNextDocument()) {
MyBO input1 = new MyBO();
input1.setName("Input from Notes");
input1.setValue(document.getItemValueString("input"));
System.out.println("document.getItemValueString:"
+ document.getItemValueString("input"));
MyBO output1 = service.getMyExport_MyInterfaceHttpPort()
.changeValue(input1);
System.out.println("input:" + input1.getValue());
document.replaceItemValue("outputOnServer", output1.getValue());
System.out.println("outputOnServer:" + output1.getValue());
document.save();
agentContext.updateProcessedDoc(document);
}
} catch(Exception e) {
e.printStackTrace();
}
}
}
|
代码总结
以本节的 Java 程序为例,我们可以得到以下用 Java 编写 Agent 的经验:
- 对于操作服务器端文档的 Java 程序,主要的工具类有 NotesSession,NotesDatabase,AgentContext 和 NotesDocument 等,这些类读写的是内存中文档所对应的各个属性。
- 实现的方法 Agent 是实现基类 lotus.domino.AgentBase 的一个子类。而运行时的上下文入口就是当前对象 this。通过调用 lotus.domino.AgentBase 的各种方法,可以得到编程所需要的运行期参数。
- 在更改完成后,调用 NotesDocument 的 Save 方法保存对文档的修改。
- 对于新创建或更改的文档,Lotus Domino 会将它们标记为未处理的文档,通过 NotesDatabase 的 Unprocesseddocuments 方法可以得到所有未被处理的文档。在处理过文档后,要调用 NotesSession 的 Updateprocesseddoc 方法将文档标注为已处理。不过当同一个文档再次被更改的时候,它又会自动被标记为未处理。
测试本小节示例
本小节中示例 Agent 的作用和上一小节基本相同,可以按照测试服务器端 Lotus Script. Web Service Consumer 一样测试。但需要注意的是,两个 Agent 同时运行的时候只有一个能发生作用,因此在测试一个 Agent 的时候要停用另一个。停用一个 Agent的方法是将该 Agent的 Trigger 应设置为 On event – Agent list selection。
在本节中,我们通过实现调用 Web Service Consumer 的 Agent, 介绍了如何利用 Lotus Script. 或 Java 编写业务流代码。 在 Lotus 业务流系统中,与人工操作相关的处理逻辑,例如界面操作等通常是在客户端运行的,这一部分逻辑用 Lotus Script. 来实现比较合适。在 Lotus Domino 8.5.1 中虽然也引入了进行界面操作的 Java 编程接口,包括 com.ibm.notes.java.ui.* 下的 Java 类,但是还没有清晰的代码示例展示如何使用,有兴趣的话可以继续关注。对于一些自动处理的业务流逻辑,代码通常在服务器端运行,这一类 Agent 既可以用 Lotus Script. 实现也可以用 Java 实现,不过如果业务流逻辑较复杂的话,用 Java 实现更容易实现较好的封装特性。