内容


为 IBM Lotus Domino V8 设计支持 WS-I 的 Web 服务

Comments

【编辑注:本文所描述的 IBM Lotus Domino V8 特性和更新针对的是 Beta 版软件。本文及其屏幕截图可能并不反映最终推出的产品的功能。】

本文演示了为 Lotus Domino V8 开发 Web 服务,同时保持与 Lotus Domino V7.0.2 的向后兼容性。文中使用的 示例 是一组针对 Discussion 数据库模板的 Web 服务。这个解决方案作为 IBM 都柏林软件实验室时下进行的 Service-Oriented Architecture (SOA) Growth Project 计划的一部分,其目的是通过为 IBM 产品增加一些技术启用程序便于在 SOA 中进行集成,从而使基于 SOA 的解决方案得到更好的采用。这个特殊的解决方案有两个主要目标:

  1. 在 Discussion 数据库模板上设计 Web 服务层以公开关键模板功能供 Web 服务客户机使用。
  2. 确保最终得到的 Web 服务支持 WS-I Basic Profile 1.1,提高服务跨不同 Web 服务引擎和编程语言的互操作性。

Discussion 数据库模板由于使用广泛,因此通常被选作一种代表性工具将它的一些功能公开为 Web 服务。通过 discussion 模板创建的数据库允许多个用户创建、删除和修改主题,响应主题以及对响应作出响应。另外,这些主题和响应可以是公有的,也可以是私有的,并且允许根据数据库中的视图对它们进行过滤。 每个作者都可以创建、修改和删除自己的配置文件,而一些有特权的作者还可以阅读其他人的配置文件。

构建该解决方案的目的是公开以下三种逻辑 Web 服务:

  • ViewManager,用于管理视图。
  • ProfileManager,用于管理作者的配置文件。
  • ThreadManager,用于处理主题和响应。

使用 LotusScript 来实现该解决方案的原因在于:它是一种成熟的技术并且在 Lotus Domino 开发社区中使用广泛。WS-I 的互操作性是使用 Basic Profile 1.1 的 WS-I Interoperability Testing Tool 来验证的。

本文总结了开发这个解决方案的经验并讨论了使用 Lotus Domino V8 开发自己的 Web 服务的最佳实践。本文还强调了实现 Lotus Domino V8 的 WS-I 的依从性的几个陷阱和应对措施,以及与 Lotus Domino V7.0.2 兼容的问题。Lotus Domino V7.0.2 兼容性是一个主要的需求,因为企业可能要花些时间才能迁移到 Lotus Domino V8。

设计

本节将对 Domino Discussion 数据库 Web 服务应用程序中使用的架构进行概述。重点介绍 Lotus Domino 中约束设计选择的一些限制;描述公开的接口并介绍如何实现 Lotus Domino V7.0.2 和 V8 之间的兼容性。

Discussion 数据库 Web 服务是作为无状态 Web 服务进行设计的。在某些情形下,有状态 Web 服务更加适当,例如,提供分页机制以列出数据库中的主题和响应。 有状态的 Web 服务在集群的 Lotus Domino 环境下不能进行故障恢复,因为 Lotus Domino 没有提供在节点间异步复制会话和状态的功能。 出于这个原因和简单性的考虑,我们选择实现无状态的 Web 服务。

Web 服务

Discussion 数据库 Web 服务在功能上可以分解为三种不同的服务。

  • DiscussionProfileManager 允许管理当前用户的配置文件,还允许管理员管理其他用户的配置文件(如列出和删除配置文件)。
  • DiscussionViewManager 用于列出数据库中的视图和文件夹以及为每个视图列出可排序的列。它可以与 DiscussionThreadManager 中处理视图的方法结合使用。
  • DiscussionThreadManager 用于管理主题、响应和类别。它公开了一组 Create、Read、Update、Delete、Exists 和 List (CRUDEL) 操作来处理主题、响应、类别以及一些用于将文档标记为 read 和 unread 的方法。 有些操作在 single 或 bulk 模式下可用,如 updateTopic 和 updateTopics 操作。

Web 服务的 LIST 方法返回主题、响应、作者、视图名和文件夹名的列表。这些方法提供了多个备选方案以允许将返回的列表根据排序标准进行排序。返回的列表可以根据 Notes Formula 语言查询、示例查询或视图进行过滤。一些提供简单分页机制的列表方法是无状态的。要使用这些方法,用户必须提供页号和所需的元素号。每次调用时收集文档;因此根据假设,如果数据库在两个调用之间发生变化,则两个不同的页面中可能返回相同的元素。

数据传输对象

数据传输对象(data transfer objects,DTOs)或值类型如 Lotus Domino Web 服务文档中介绍的那样,是用于在 Web 服务和客户机之间交换数据的自定义类。

对于 Discussion 数据库,我们拥有以下一些 DTOs,如图 1 所示:

  • Author 是一个表示作者配置文件的类。它包含了 author ID、author name、display name、email、phone number、body、goal、role、subject、categories、is-private、modified date、created date。
  • Topic 是一个表示 Discussion 数据库中主题的类。它包含了 document ID、subject、body、categories、modified date、created date 和 mark-as-private 属性。body 不包含任何二进制(如 attachments)。
  • Response 是一个表示 Discussion 数据库中主题的响应或响应的响应的类。它包含了 document ID、parent ID、subject、body、categories、modified date、created date 和 mark-as-private 属性。body 不包含任何二进制内容(如 attachments)。
  • SortItem 是一个包含字段名和排序顺序对的类。SortCriteria 使用该类来确定由列表方法返回的主题、响应和作者的排序顺序。
  • SortCriteria 是一个包含 SortItems 集合和 name 属性的类。列表方法使用该类来指定如何根据 SortItems 中的多个属性和顺序对主题、响应和作者排序。
  • IDList 是一个用于 bulk 更新的类,它包含了一个 ID 列表和一个 type 属性,该属性的合法值为 topic、response 和 any。
图 1. 数据传输对象
数据传输对象
数据传输对象

错误

Web 服务抛出一些自定义错误,其中的字段指定了以下内容:

  • Error message,关于发生错误的详细信息。
  • Error number,识别错误的代码。
  • Line number,脚本中抛出错误的位置。
  • Database name,使用数据库的名称。
  • Function name,其中发生错误的函数名称。
  • Web service name,发生错误的 Web 服务的名称。

错误可以分类为以下几组:

  • DiscussionError 提示正常使用 Web 服务期间产生的错误。
  • IllegalArgumentError 在每次认为参数无效时抛出,例如,将 null 值作为参数传递给方法。
  • UpdateError 在未成功更新一个或多个文档时由 bulk 更新方法抛出。抛出时它包含了一个未更新的(主题或响应)ID 列表。
  • UpdateConflictError 在尝试使用更早的日期修改当前文档的 modified date 时抛出。

兼容性

Web 服务的设计目的是对 Lotus Domino V7.0.2 和 Lotus Domino V8 使用相同的接口,同时实现使用一个替代方案来完成本文 “实现” 一节所描述的源代码可移植性。

实现

在本节中,我们将描述 Web 服务实现和在跨 Lotus Domino V7.0.2 和 Lotus Domino V8 实现这些服务时所发现的问题。我们将描述如何使用开发环境提供 LotusScript 实现的跨版本兼容性,如何处理传递给 Web 服务的 LotusScript 数组、null 和复杂数据类型,如何处理错误,以及如何复制 Lotus Notes 中的相同行为来保证 Web 服务的安全。 我们使用 Java 1.4Axis 1.4Junit 3.8 来创建测试实现的测试套件。 使用 AntCruisecontrol 可以连续地编译和运行测试套件。

开发环境

Lotus Domino V8 拥有的 Web 服务引擎比 Lotus Domino V7.0.2 的高级,因此不能使用 IBM Lotus Domino Designer V8 来创建 Lotus Domino V7.0.2 上运行的 Web 服务。使用 Web 浏览器访问 Lotus Domino V7.0.2 服务器中运行的 Lotus Domino Designer V8 Web 服务时会显示以下错误。

"This version of the Web Service design is not supported on this server"

Lotus Domino Designer V7.0.2 Web 服务在 Lotus Domino V8 服务器中运行良好。同样道理,使用 Lotus Domino Designer V8 创建或编辑的 Web 服务不能使用 Lotus Domino Designer V7.0.2 或更低的版本来编辑。当您在 Lotus Domino Designer V7.0.2 中打开 Lotus Domino V8 Web 服务时就会出现图 2 所示的警告:

图 2. IBM Lotus Domino Designer 错误对话框
Designer 错误对话框
Designer 错误对话框

数组作为参数和返回值

Discussion 数据库 Web 服务提供的列表方法返回数据传输对象数组。LotusScript 没有提供在函数中返回数组的功能。数组通常是使用 LotusScript 中的 Variant 对象来返回,但是对于 Web 服务,Variant 没有提供足够的信息便于 WSDL 生成。为了帮助正常地生成 WSDL,我们使用一个 holder 类。holder 类是一个派生自 INOUT_HOLDER 类的 LotusScript 类对象(如 lsxsd.lss 中定义的那样)。生成的 WSDL 删除 holder 类。如预期那样,客户端使用 Web 服务时只看见一个返回的数组。developerWorks 文章 “Practical Web services in IBM Lotus Domino 7: Writing and testing simple Web services” 和 “Lotus Notes/Domino 7 Web Services” 对如何在 LotusScript Web 服务方法中使用数组 holder 类返回数组作出了更详细的解释。

注意:lsxsd.lss 库中包含的类允许您传递字符串数组、文件和日期。在本地的 Notes 程序目录下可以找到此文件。

清单 1 和清单 2 展示了一个 holder 类示例及其生成的 WSDL 部分(展示 AuthorDTO 数组)。

清单 1 . AuthorDTOArray_Holder 示例
Class AuthorDTOArray_Holder as INOUT_HOLDER
'The AuthorDTO array holder class
Public value() As AuthorDTO
...
...
End Class
清单 2. 生成的 WSDL
<complexType name="AUTHORDTOArray">
  <sequence>
     <element maxOccurs="unbounded" minOccurs="0" name="item" type="impl:AUTHORDTO"/>
  </sequence>
</complexType>

不幸的是,没有说明如何将数组作为参数传递给 LotusScript Web 服务方法。 在开发 Discussion 数据库 Web 服务期间,我们尝试了两种不同的方法,直接传递数组(清单 3)以及对原语类型和复杂类型使用 holder 类(清单 4)。

清单 3. 直接传递数组的示例
    Sub markListRead(ids() As String)
...
End Sub

Sub markTopicsRead(topics() as TopicDTO)
...
End Sub
清单 4. 使用数组 holder 类的示例
    Sub markListUnread(ids As STRINGARRAY_HOLDER)
...
End Sub

Sub markTopicsUnread(topics As TopicDTOArray_Holder)
...
End Sub

这两种方法在 Lotus Domino V8 中都有效。这些工作在 Lotus Domino V7.0.2 中使用 RPC Encoded WSDL 来完成。我们不使用 RPC 编码因为它不受 WS-I Basic Profile 1.1 支持。在默认情况下,这些方法在使用其他编码的 Lotus Domino V7.0.2 中无效。 当数组或数组 holders 用于原语类型时,Web 服务方法从基于 Axis 的测试客户机接收空数组。当数组或数组 holders 用于复杂类型时,Web 服务错误被返回到包含清单 5 所示错误消息的客户机。

清单 5. 错误消息
“Web service (DiscussionThreadManager) object does not have specified member
TOPICS in method MARKTOPICSREAD: value "" at element  quot;urn:DefaultNamespace:
MARKTOPICSREAD/TOPICS"”

可以通过手工编辑 WSDL(从清单 6 到清单 7)使数组生效。

清单 6. 生成的 WSDL
<complexType name="ArrayOf_xsd_string">
    <complexContent>
        <restriction base="soapenc:Array">
            <attribute ref="soapenc:arrayType" wsdl:arrayType="xsd:string[]"/>
        </restriction>
    </complexContent>
</complexType>
清单 7. 手工编辑的 WSDL
<complexType name="ArrayOf_xsd_string">
    <sequence>
      <element maxOccurs="unbounded" minOccurs="0" name="item" type="xsd:string"/>
    </sequence>
</complexType>

这个问题的应对方法是使用一个用作数组容器但是没有扩展 INOUT_HOLDER 的自定义类。在 Lotus Domino V8 中,任何包含数组字段的自定义类都被自动视为数组 holder。为了避免这种行为并创建一致的接口 —— 即不管 LotusScript 部署在 Lotus Domino V7.0.2 或 Lotus Domino V8 Beta 上都可以通用接口,可以使用包含多个字段的自定义类,如清单 8 所示。

清单 8. IDList 示例
Class IDList

    Public ids() As String

    Public type As String

End Class

Null 值和复杂类型

LotusScript 原生数据类型(如 String、Integer 或 Boolean)不允许使用 null 值。要使用 null 值,比如,要为主题 DTO 中的 body 字段传递一个 null 值,我们使用 lsxsd.lss 库中的数据类型(如 XSD_String 或 XSD_Boolean),这些类型允许使用 null 值。清单 9 展示了带有 XSD_String 类型字段的 TopicDTO 类,清单 10 中生成的 WSDL 的相应部分显示,body 属性可以为空。

清单 9. TopicDTO 示例
Class TopicDTO
    Public body As XSD_STRING
    ...
End Class
清单 10. TopicDTO WSDL 片段
    <complexType name="TOPICDTO">
   <sequence>
      <element name="BODY" nillable="true" type="xsd:string"/>
...
...
   </sequence>
</complexType>

当带有复杂类型参数的 Web 服务方法收到 null 或带 null 值属性的实例时,Lotus Domino V7.0.2 和 Lotus Domino V8 的行为不尽相同。Lotus Domino V8 将 null 视为 LotusScript Nothing(即其他编程语言中的 null),而 Lotus Domino V7.0.2 则使用默认值对它们进行实例化。数值字段被赋以 0 值,而字符串字段则赋以空字符串值。类的实例化是给它们所有的原语类型属性赋予默认值。这个行为在 Lotus Domino V7.0.2 和 Lotus Domino V8 中需要使用不同的方法来进行 null 检查。

清单 11 中的代码首先检查传递的响应是否为 null,如果不是,则检查所需字段是否为 null。

清单 11. Lotus Domino V8 的 createResponse 示例
    Function createResponse(response As ResponseDTO) As ResponseDTO
    If response Is Nothing Then
        Error 1, "Response is null."
    End If

    If response.parentID Is Nothing Then
        Error 1, "The Responses parent ID is null."
    End If
    ...
End Function

这段代码在 Lotus Domino V8 中运行正常,但在 Lotus Domino V7.0.2 中响应实例决不是 Nothing,因为它已被实例化为默认值。如果已传递响应,则 parentID 决不是 Nothing,原因同上。

清单 12. Lotus Domino V7 的 createResponse 示例
        Function createResponse(response As ResponseDTO) As ResponseDTO
    If response.ID Is Nothing Then
        Error 1, "Response is null."
    End If

    If response.parentID.GetValueAsString = "" Then
        Error 1, "The Responses parent ID is null."
    End If
    ...
End Function

在 Lotus Domino V7.0.2 中,清单 12 中的代码可以正常运行,原因在于收到的输入是 null 参数时将动态地进行对象实例化。在 Lotus Domino V8 中,null 参数在 LotusScript 中会变成 Nothing,因此如果 response 是 Nothing 则 response.ID 将导致错误,而如果 parentID 字段是 Nothing 则 response.parentID.GetValueAsString 将导致错误。

一种创建在 Lotus Domino V7.0.2 和 Lotus Domino V8 中都能运行的代码的应变方案需要使用一条 If 语句来根据 LotusScript 版本对代码进行分支。当开发从 Versioon 8 向后兼容到 Version V7.0.2 的 Web 服务时,我们需要一种方法来确定在哪个版本上运行 Web 服务。使用清单 13 所示的 Getthreadinfo() 函数可以找到这条信息。通过使用一条 If 语句,Getthreadinfo(LSI_THREAD_VERSION) 函数可以返回 Lotus Domino V7.0.2 的 LotusScript 版本,即 5.0.0.07B。Lotus Domino V7.0.2 执行 If 分支中的代码,而更高版本则执行 Else 分支中的代码。

清单 13. LotusScript 版本检查示例
    Function createResponse(response As ResponseDTO) As ResponseDTO
    If CStr(GetThreadInfo(LSI_THREAD_VERSION)) <= "5.0.0.07B" Then
        ' Domino 7.0.2 or earlier
        If response.ID Is Nothing Then
            Error 1, "Response is null."
        End If

        If response.parentID.GetValueAsString = "" Then
            Error 1, "The Responses parent ID is null."
        End If
    Else
        ' Later than Domino 7.0.2
        If response Is Nothing Then
            Error 1, "Response is null."
        End If

        If response.parentID Is Nothing Then
            Error 1, "The Responses parent ID is null."
        End If

    End IF
    ...
End Function

错误

我们将在此介绍 LotusScript 如何处理错误以及如何利用它使 Web 服务错误能够给客户机提供一些有用的信息。LotusScript 提供了三个函数来考察当前错误的状态并显示错误信息。这些函数分别是:

  • Error(),返回相关的错误消息。
  • Err(),返回相关的错误号。
  • Erl(),返回发生错误的行号。

在 LotusScript 中通过使用一个特殊的出口参数来抛出 Web 服务错误,该参数是 WS_Fault 类(可从 lsxsd.lss 库中获得)的实例,如 developerWorks 文章 “在 IBM Lotus Domino 7 中使用 Web 服务,第 3 部分: 编写复杂的 Web 服务” 所示。 见清单 14。

清单 14. 错误示例
Function foo(fault As WS_FAULT) As ...

    Fault.SetFault True
    Fault.setFaultString "error message"

End Function

Lotus Domino V8 处理的自定义错误是 WS_Fault 类的扩展。它使 Axis 1.4 能够将错误生成为 AxisFault 的子类,当发生错误时将捕获到这些子类。清单 15 中的代码使用 OnError 语句来捕获运行时错误并使用自定义的 throwFault 函数来实例化 DiscussionError。DiscussionError 是一个(默认)自定义类,其中包含了错误消息、生成消息的函数名和包含 Web 服务的数据库名。函数名通过 GetThreadIfo(LSI_THREAD_PROC) 函数来收集。出于适用性考虑,错误在生成时被使用 nLog.logError 函数记录到日志数据库。

清单 15. createTopic 示例
    Function createTopic(topic As TopicDTO, fault As DiscussionError) As TopicDTO
 On Error Goto FAULT
 ...
 Exit Function
FAULT:
 Set fault = throwFault(fault, GetThreadInfo(LSI_THREAD_PROC), database.FileName)
 nLog.LogError Err, "Error in " & GetThreadInfo(LSI_THREAD_PROC) & " : " & Error & "
            Line : " & Erl
 Exit Function
End Function

Lotus Domino V7.0.2 没有返回 SOAP 错误子类型成员数据。结果,不管在 Lotus Domino V7.0.2 中使用的自定义类是什么,所有的错误在运行时都被客户机视为 AxisFault 异常而不是自定义错误。例如,Axis 1.4 生成了一个自定义异常类,但是 Web 服务在运行时决不会抛出该异常。我们尚未找到此问题的解决方法。

安全性

Web 服务提供的用户访问权限与 Lotus Notes 客户机施加的权限一致。在 Lotus Domino 中,每个文档都有一个 Authors 字段和一个 Readers 字段;请参阅 Lotus Domino Designer 文档。Authors 字段决定了谁能够编辑或更新文档。Readers 字段控制谁能够阅读文档。为达到 Lotus Notes 客户机和 Web 服务之间的一致性,当创建一个主题或响应的时候,用户被添加到 Authors 字段。如果用户选择将主题或响应设为私有,则用户被添加到 Readers 字段。主题和响应有一个 Boolean 字段可将它们标记为私有或公有。

部署 DiscussionProfileManager

在本节中,我们将描述如何配置 Lotus Domino 来公开安全 Web 服务并部署这些服务。 在本例中,我们将展示如何部署为 Discussion 数据库开发的 DiscussionProfileManager Web 服务。 您可以按照同样的步骤部署针对此解决方案开发的另外两个 Web 服务:DiscussionThreadManager 和 DiscussionViewManager。

配置安全性

为使用户能够以一种安全的方式访问 Web 服务,Lotus Domino 应使用带有 HTTPS 协议的嵌入式 Web 服务器。

要配置服务器,应执行以下操作:

  1. 启动 IBM Lotus Domino Administrator 客户机。
  2. 选择 Configuration 附签。
  3. 打开 Server 文档。
  4. 选择 Ports 附签。
  5. 选择 Internet Ports 附签。
  6. 单击 Edit Server 按钮。
  7. 作出更改启用 HTTPS 并禁用 HTTP。

要验证用户,必须启用 Internet 口令。 此口令可在 Domino Directory 中进行设置,方法是使用 Notes 客户机编辑个人文档。

手工部署 DiscussionProfileManager

要部署此 Web 服务,请按照以下步骤执行:

  1. 在 Lotus Domino Designer 中打开目标数据库。
  2. 选择 Shared Code,然后在 Navigator 中选择 Web Services。
  3. 单击 New Web Service。
  4. 在 Web Service 属性框中输入 Web 服务名,比如 DisscussionProfileManager。
  5. 输入 DiscussionProfileManager 作为 Port Type Class,如图 3 所示。
图 3. Web Service 属性框
Web Service 属性框
  1. 选择 Web Service 属性框的 Security 附签,如图 4 所示。
  2. 选择 “Run as web user” 选项。这使得用户可以使用自己的用户名和 Internet 口令来调用 Web 服务。
图 4. Web Service 属性框的 Security 附签
Web Service 属性框的 Security 附签
  1. 选择 Web Service 属性框的 Advanced 附签。
  2. 为 Port 类型名、Service 元素名和 Service 端口名输入适当的名称(如果这些字段留空则 Lotus Domino 将自动填充这些字段)。图 5 给出了一个 DiscussionProfileManager 的例子。
  3. 选择所需的编码方法。
图 5. Web Service 属性框的 Advanced 附签
Web Service 属性框的 Advanced 附签
  1. 关闭 Web Service 属性框。
  2. 从选项区删除文本 Option Base。
  3. 在文本编辑器(如 Microsoft Notepad)中打开 DiscussionProfileManager.lss。
  4. 复制 DiscussionProfileManager.lss 中的所有文本。
  5. 在 Lotus Domino Designer 中选择新 Web 服务的 Declarations 段,如图 6 所示。
图 6. Web 服务的 Declarations 段
Web 服务的 Declarations 段
  1. 在 Web 服务中粘贴。
  2. 保存并关闭 Web 服务。

现在可用以下地址获得 Web 服务:

http://<DominoServer>/<Database Path>/DiscussionProfileManager

注意:根据默认,Web 服务引擎被配置为单线程引擎。要更改此默认配置,需要使用 Lotus Domino Administrator 客户机连接到服务器,选择左侧窗格上的 Server 图标,打开列表,选择 Current Server Document,转到 Internet Protocols 附签,单击 Domino Web Engine 附签,然后将 Web Agents 设置 “Run Web agents concurrently?” 条目从 Disabled 改为 Enabled,如图 7 所示。然后执行 HTTP 任务重新启动或重新启动服务器。

关于咨询 IBM Redpaper 的更多详细信息,请参阅 “IBM Lotus Domino for iSeries, performance, and tuning”。

图 7. Web Agents 并发设置
Web Agents 并发设置

WS-I 依从性

WS-I 依从性的目标是保证 Discussion 数据库 Web 服务之间的互操作性,允许客户机通过其他支持 Web 服务的技术来使用这些服务。例如,J2ME 客户机要求所有的 Web Service Description Language (WSDL) 定义都满足 WS-I 依从性。

Web Services Interoperability Organisation 开发了一个测试工具,用来评估 Web 服务对 Basic Profile 1.1 的依从性。此工具使用一个非侵入式的黑盒方法来测试 Web 服务实现。该工具的测试重点在于 Web 服务和用户应用程序之间的交互。 要执行这些测试,我们可以使用 Interoperability Testing Tool 1.1

该测试工具包含一个 Monitor、一个 Analyzer(见图 8)和各种支持文件。Monitor 用作消息捕获和日志记录的工具。它将截取消息而记录器对这些消息重新定义格式并将它们存储到一个消息日志中以便以后进行分析。Monitor 使用中间方法中的 man 截取和记录消息来实现。Analyzer 是一个分析工具,用来验证 Web 服务对 WS-I Profiles 的依从性。它将分析发送到 Web 服务的消息和从 Web 服务发出的消息,前提是 Monitor 已经将这些消息记录到消息日志中。

有关更多的详细信息,请参阅 WS-I Organization 中的参考资料,使 Web 服务开发人员能够创建互操作的 Web 服务并验证其结果是否符合 WS-I 指南。

图 8. 互操作性测试工具
互操作性测试工具
互操作性测试工具

工具中附带了关于 Interoperability Test Tool 的使用方法的说明。

针对 Discussion 数据库 Web 服务执行的测试

Lotus Domino 允许使用以下编码发布 Web 服务:

  • Document Wrapped
  • Document Literal
  • RPC Literal
  • RPC Encoded

RPC-encoded Web 服务不受 WS-I Basic Profile 1.1 支持,因此,本文不再对它进行讨论。在 Web 服务开发期间,对于其他三种编码 Document Wrapped、Document Literal 和 RPC Literal,我们针对 Lotus Domino V7.0.2 和 Lotus Domino V8 都执行了静态和动态 WS-I 依从性测试。

静态测试指使用 Analyzer 对每个 Web 服务 WSDL 执行静态分析。 动态测试指使用 Monitor 记录 Web 服务和客户机之间的 SOAP 消息;在本例中,客户机一个基于 Axis 1.4 的 JUnit Test Suite。Monitor 将 SOAP 消息记录到一个日志文件中,Analyzer 工具稍后将分析这个日志文件。生成的依从性报告文件指明 Web 服务是否满足 WS-I 依从性。

Lotus Domino V7.0.2 和 V8 WS-I 失败

Lotus Domino V7.0.2 生成的 WSDL 在以下情形下会使 WS-I Interoperability Tests 失败:

  • 使用 Document Literal 和 Document Wrapped 编码时
  • 在 Web 服务中使用数组时
  • RPC Literal 编码的 Web 服务包含 SOAP 错误时
  • Document Literal 编码的 Web 服务中的方法使用多个参数时

Lotus Domino V8 生成的 WSDL 在以下情形会使 WS-I Interoperability Tests 失败:

  • RPC Literal 编码的 Web 服务包含 SOAP 错误时
  • Document Literal 编码的 Web 服务中的方法使用多个参数时
  • Document Literal 编码的 Web 服务的参数不惟一时

这里通过一些错误的 WSDL 示例对这些 WS-I 失败作出了详细解释,并说明了使其满足 WSDL 依从性所需进行的更改。如 Lotus Domino Designer 文档 中所述,有些 WSDL 或 XML 模式结构对到 LotusScript 或 Java 的映射只提供了有限的支持或者不支持。当它们尝试导入 WSDL 时得不到 Lotus Domino Designer 的支持。

数组表示

Lotus Domino V7.0.2 生成的用于 Web 服务的 WSDL 所包含的数组不满足 WS-I 依从性。这将对以下的编码产生影响:

  • Domino V7 Document Literal
  • Domino V7 Document Wrapped
  • Domino V7 Document RPC Literal

WS-I 测试失败源于以下错误:

"An Array declaration uses - restricts or extends - the soapenc:Array type,
or the wsdl:arrayType attribute is used in the type declaration."

对于三种不同的编码,WSDL 中使用清单 16 所示的代码来表示数组。

清单 16. 生成的 WSDL
<complexType name="ArrayOf_xsd_string">
    <complexContent>
        <restriction base="soapenc:Array">
            <attribute ref="soapenc:arrayType" wsdl:arrayType="xsd:string[]"/>
        </restriction>
    </complexContent>
</complexType>

解决此问题的办法是手工编辑 WSDL 并将它们导入 Lotus Domino Designer 中(有关将 WSDL 导入 Lotus Domino Designer 的更多信息,请参阅 “导入 WSDL” 一节)。 要使它满足 WS-I 依从性,可删除 <complexContent> 标记并将其替换为 <sequence> 标记,如清单 17 所示。

清单 17. 满足依从性的 WSDL
<complexType name="ArrayOf_xsd_string">
    <sequence>
      <element maxOccurs="unbounded" minOccurs="0" name="item" type="xsd:string"/>
    </sequence>
</complexType>

Namespace 属性

出于 WS-I 依从性考虑,namespace 属性不能包含在任何 soapbind:body、soapbind:header、soapbind:headerfault、soapbind:fault 元素中。此问题会影响以下的 WSDL 编码:

  • Domino V7.0.2 Document Literal
  • Domino V7.0.2 Document Wrapped

运行 WS-I 测试工具后将得到以下错误消息:

"A document-literal binding in a DESCRIPTION MUST NOT have
the namespace attribute specified on contained soapbind:body,soapbind:header, soapbind:headerfault
and soapbind:fault elements."

清单 18 展示了生成的 WSDL 的一部分,它对于 Document Wrapped 和 Document Literal 都不满足 WS-I 依从性,二者包含了 namespace 属性。

清单 18. 生成的 WSDL
<wsdl:binding name="DiscussionProfileManagerSoapBinding"
        type="impl:DiscussionProfileManagerPort">
        <wsdlsoap:binding style="document"
            transport="http://schemas.xmlsoap.org/soap/http" />
        <wsdl:operation name="GETVERSION">
            <wsdlsoap:operation soapAction="" />
            <wsdl:input name="GETVERSIONRequest">
                <wsdlsoap:body namespace="urn:DefaultNamespace"
                    use="literal" />
            </wsdl:input>
            <wsdl:output name="GETVERSIONResponse">
                <wsdlsoap:body namespace="urn:DefaultNamespace"
                    use="literal" />
            </wsdl:output>
        </wsdl:operation>

要解决这个问题,可以手工编辑 WSDL 并从 <wsdlsoap:body> 标记删除 namespace 属性,如清单 19 中的 WSDL 示例所示。然后将 WSDL 导入到 Lotus Domino Designer 中。

清单 19. 满足依从性的 WSDL
<wsdl:binding name="DiscussionProfileManagerSoapBinding"
        type="impl:DiscussionProfileManagerPort">
        <wsdlsoap:binding style="document"
            transport="http://schemas.xmlsoap.org/soap/http" />
        <wsdl:operation name="GETVERSION">
            <wsdlsoap:operation soapAction="" />
            <wsdl:input name="GETVERSIONRequest">
                <wsdlsoap:body use="literal" />
            </wsdl:input>
            <wsdl:output name="GETVERSIONResponse">
                <wsdlsoap:body use="literal" />
            </wsdl:output>
</wsdl:operation>

WSDL 元素属性

当 <wsdl:message> 标记中的 <wsdl:part> 标记使用题头或错误元素的类型属性来定义时就会出现这个错误。此错误将影响以下编码:

  • Domino V7.0.2 RPC Literal
  • Domino V8 RPC Literal

针对此错误给出的消息是:

"A wsdl:binding in a DESCRIPTION MUST refer, in each of itssoapbind:header,
soapbind:headerfault and soapbind:fault elements, only to wsdl:part element(s) that have been defined
using the element attribute."

因为 fault 和 header 不包含参数,所以对于每个 WSDL 1.1,soapbind:fault、soapbind:header 和 soapbind:headerfault 假定 style 属性的值为 document。所有 style 属性值为 document 并且被绑定到 soapbind:body 上的 wsdl:part 元素都使用 element 属性来定义。对 soapbind:fault、soapbind:header 和 soapbind:headerfault 元素也有同样的要求。

注意:RPC Literal Web 服务的 header 元素和 fault 元素都应按 Document Literal 中相同的样式来定义,按照断言需求 R2205 编码。

清单 20 中给出了一个不满足 WS-I 依从性的 WSDL。请注意 <wsdl:part> 标记中 type 属性的使用。

清单 20. 生成的 WSDL
<complexType name="DISCUSSIONERROR">
    <sequence>
        ...
        ...
    </sequence>
</complexType>
...
...
<wsdl:message name="DISCUSSIONERROR">
   <wsdl:part name="DISCUSSIONERROR" type="impl:DISCUSSIONERROR" />
</wsdl:message>

要更改这个 WSDL,使它满足 WS-I 依从性,可以手工地将 <element> 标记添加到 <complexType> 标记两侧并将 <wsdl:part> 标记中的 type 属性更改为 element 属性。清单 21 给出了一个满足依从性 WSDL 的示例。编辑 WSDL 后,将编辑后的 WSDL 导入到 Lotus Domino Designer 中。

清单 21. 满足依从性的 WSDL
<element name="DISCUSSIONERROR">
    <complexType>
        <sequence>
            ...
            ...
        </sequence>
    </complexType>
</element>
...
...
<wsdl:message name="DISCUSSIONERROR">
   <wsdl:part name="DISCUSSIONERROR" element="impl:DISCUSSIONERROR" />
</wsdl:message>

Message 元素拥有多个 part 元素

如果对于以下编码,在 <wsdl:message> 标记中有多个 <wsdl:part> 元素就会出现这个错误:

  • Domino V7.0.2 Document Literal
  • Domino V8 Document Literal

这些编码使 WS-I 测试失败后得到以下错误信息:

"A document-literal binding which does not specify the parts attribute, has more than one wsdl:part in the associated wsdl:message element."

清单 22 给出了生成的 WSDL 中出问题的部分。

清单 22. 生成的 WSDL
    <wsdl:message name="LISTAUTHORSBYEXAMPLEANDSORTCRITERIARequest">
        <wsdl:part element="impl:EXAMPLE" name="EXAMPLE" />
        <wsdl:part element="impl:SORT" name="SORT" />
</wsdl:message>

当定义 document/literal 服务时,在输入消息和输出消息中都最多只能有一个 body 部分。因此,Web 服务传递给方法的参数不能多于一个。如果要使用带有多个参数的方法,则必须使 Document Wrapped 编码满足依从性。

绑定拥有不惟一的操作

出现此错误是因为 message 元素包含了不惟一的部分。这个错误会影响以下编码:

  • Domino V7.0.2 Document Literal
  • Domino V8 Document Literal

运行 WS-I 测试后将出现以下错误消息:

"A binding has operations that are not unique."

清单 23 给出了不满足依从性的 WSDL 的一段。注意 message 元素中的一些 part 元素不惟一,比如 ID 元素就不惟一。

清单 23. 生成的 WSDL
...
...
<wsdl:types>
        <schema targetNamespace="urn:DefaultNamespace"
            xmlns="http://www.w3.org/2001/XMLSchema">
<element name="ID" type="xsd:string" />
<element name="AUTHOR" type="impl:AUTHORDTO" />
...
...
</schema>
    </wsdl:types>

<wsdl:message name="GETVERSIONRequest">
    Part element missing
    </wsdl:message>

<wsdl:message name="DELETEAUTHORPROFILERequest">
        <wsdl:part element="intf:ID" name="ID" />
    </wsdl:message>

<wsdl:message name="READAUTHORPROFILERequest">
        <wsdl:part element="intf:ID" name="ID" />
    </wsdl:message>

<wsdl:message name="EXISTSAUTHORPROFILERequest">
        <wsdl:part element="intf:ID" name="ID" />
    </wsdl:message>

<wsdl:message name="UPDATEAUTHORPROFILERequest">
        <wsdl:part element="intf:AUTHOR" name="AUTHOR" />
    </wsdl:message>

<wsdl:message name="CREATEAUTHORPROFILERequest">
    <wsdl:part element="intf:AUTHOR" name="AUTHOR" />
</wsdl:message>
...
...

要满足 WS-I Basic Profile 1.1,所有的 message 元素都必须包含惟一的 part。清单 24 给出了相应的满足依从性的 WSDL 部分。注意 <wsdl:part> 标记中的元素声明和元素名现在都是惟一的。如果 Web 服务中有很多方法,那么这种解决方案可能就很麻烦。我们建议您使用 Document Wrapped 编码。阅读 Anne Thomas Manes 撰写的文章 “"The wrapped document/literal convention”。

清单 24. 满足依从性的 WSDL
<wsdl:types>
    <schema targetNamespace="urn:DefaultNamespace"
        xmlns="http://www.w3.org/2001/XMLSchema">
        <element name="GETVERSIONReturn" type="xsd:string" />
<element name="ID_1" type="xsd:string" />
<element name="ID_2" type="xsd:string" />
<element name="ID_3" type="xsd:string" />
...
<element name="AUTHOR_1" type="impl:AUTHORDTO" />
<element name="AUTHOR_2" type="impl:AUTHORDTO" />
...
...

<wsdl:message name="GETVERSIONRequest">
    There must be one part element here
    </wsdl:message>

<wsdl:message name="DELETEAUTHORPROFILERequest">
        <wsdl:part element="intf:ID_1" name="ID" />
    </wsdl:message>

<wsdl:message name="EXISTSAUTHORPROFILERequest">
        <wsdl:part element="intf:ID_2" name="ID" />
    </wsdl:message>
<wsdl:message name="READAUTHORPROFILERequest">
        <wsdl:part element="intf:ID_3" name="ID" />
    </wsdl:message>
<wsdl:message name="UPDATEAUTHORPROFILERequest">
        <wsdl:part element="intf:AUTHOR_1" name="AUTHOR" />
    </wsdl:message>

<wsdl:message name="CREATEAUTHORPROFILERequest">
    <wsdl:part element="intf:AUTHOR_2" name="AUTHOR" />
</wsdl:message>
...
...

导入 WSDL

在 Lotus Domino Designer 中,创建 Web 服务可以通过两种方法:输入代码并让 Lotus Domino Designer 生成 WSDL,或导入 WSDL 并让 Lotus Domino Designer 为 Web 服务接口生成代码签名。本文中描述的 Web 服务通过 Lotus Domino Designer 来生成 WSDL。有时使用现有的 WSDL 更好,比如,对 Lotus Domino Designer 生成的 WSDL 进行更改,使它们满足 WS-I 规范。

要通过导入 WSDL 创建 Web 服务,请执行以下步骤:

  1. 在 Lotus Domino Designer 中打开目标数据库。
  2. 选择 Shared Code,然后在 Navigator 窗格中选择 Web Services。
  3. 单击 New Web Service。
  4. 关闭 Web Service Properties 框。
  5. 选择 Import WSDL 并在警告对话框中单击 OK。
  6. 选择所需的 WSDL 文件并单击 Open。

这就为 Web 服务生成了 LotusScript 函数签名。如果生成的接口被修改并保存了 Web 服务,则 Lotus Domino Designer 将使用自己自动生成的 WSDL 重写导入的 WSDL。 为防止发生这种情况并保持导入的 WSDL,在继续进行 Web 服务实现之前执行以下步骤非常重要:

  1. 通过右键单击代码窗格打开图 9 所示的 Web Service 属性框。
  2. 输入 Web 服务名。
  3. 选择 “Warn if the WSDL interface is modified” 选项。
图 9. Web Service 属性框
Web Service 属性框
  1. 关闭 Web Service 属性框。
  2. 保存 Web 服务。

WS-I 静态依从性测试可以轻松地集成到基于 Ant 的构建环境中,并通过构建自动化工具(如 CruiseControl)自动执行。我们建议您在构建解决方案时连续地检查公开的 Web 服务是否兼容。

对于 Domino Discussion 数据库 Web 服务,Lotus Domino V7.0.2 没有生成满足 WS-I 规范的 WSDL 文档,但是可以手工编辑 WSDL 文档来实现 WS-I 依从性然后将其导入 Lotus Domino Designer 中。对于 Lotus Domino V8,惟一不会使生成满足 WS-I 规范的 WSDL 文档失败的编码是 Document Wrapped 编码。所有其他的编码都必须通过手工编辑来满足 WS-I 规范。

大致说来,Lotus Domino V7.0.2 只有在使用 RPC Literal 编码并且没有使用数组和错误时才能生成满足依从性的 WSDL。Lotus Domino V8 生成满足依从性的 WSDL 的条件包括:若使用 RPC literal 编码则要求没有使用错误;若使用 Document Literal 编码则要求所有的方法都只有一个参数并且所有方法的参数都具有惟一的名称;使用 Document Wrapped 编码。

结束语

在本文中,我们介绍了如何为 Lotus Domino V8 设计 Web 服务。我们以 Discussion 数据库为例,描述了 Web 服务架构,如何分解它们的功能,如何在 Web 服务和客户机之间传输数据,而且我们还定义了可能出现的错误。我们简要地介绍了如何实现 Web 服务,如何实现与 Lotus Domino V7.0.2 兼容,可能遇到的一些问题及其解决方法。我们提出了如何配置 Lotus Domino 以便通过 HTTPS 实现安全连接,以及用户如何使用与 Notes 客户机相同的用户权限进行身份验证以便访问 Web 服务。然后我们描述了如何使用 Lotus Domino Designer 在 Domino 数据库中部署 Web 服务。

我们执行了 WS-I 互操作性测试并重点介绍了使用 Discussion 数据库的 Web 服务应使用 Document Wrapped 编码来公开。这是当 Lotus Domino 生成 WSDL 时通过 Lotus Domino V8 依从性测试的惟一编码。我们利用 Lotus Domino V7 发布的 Web 服务不能通过互操作性测试,不管生成的 WSDL 的编码是什么。满足 WS-I 规范的 WSDL 文档可以通过手工编辑 WSDL,然后将其导入 Lotus Domino Designer 中来进行创建。

用作本文示例的 Discussion 数据库 Web 服务可在 in the Lotus Sandbox 找到。

致谢

我在此要特别感谢 Gordon Hegfield、Steve Nikopoulos 和 John Grosjean 为本文所做的技术评论。


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Lotus, SOA and web services
ArticleID=229582
ArticleTitle=为 IBM Lotus Domino V8 设计支持 WS-I 的 Web 服务
publish-date=06112007