WebSphere Integration Developer V7 中的 XML 映射

转自http://www.ibm.com/developerworks/cn/websphere/library/techarticles/1003_spriet2/1003_spriet2.html

简介

这个共两部分的系列的第 1 部分,使用 Mapping Editor 开发映射,向您展示了如何使用 IBM® WebSphere Integration Developer V7.0.0(此后简称为 Integration Developer)快速创建健壮且结构良好的 XML 映射。另外还描述了用于测试和调试映射的工具,从而及早作出问题诊断。第 2 部分将向您展示如何执行更复杂的 XML 映射。

使用数组

源或目标中的某些字段可重复使用,在这种情况下有一个特定元素数组。一般来说,当您在使用数组时,通常先将输入数组映射到输出数组,这会创建如 图 1 所示的 For-each 转换。


图 1. 数组的处理
数组的处理

我们不建议您直接映射一个数组元素的子数组而不使用父数组上的 For-each,例如 图 2 就不应该完成类似于上面 图 1 中所示的映射。


图 2. 数组的处理
数组的处理

默认情况下,当您将一个输入数组连接到一个 For-each 转换时,它会对输入数组中所有元素进行迭代处理,而每个数组元素将会根据 For-each 映射内定义的嵌套映射得到映射。

类似于一个 Local map,For-each 是一个不生成任何结果的容器映射,除非有嵌套映射定义了如何操作输入数组的每个迭代。因此,您必须具体指定将源中的哪些字段映射到目标中的哪些字段,从而完成 For-each 实现。例如,图 3 显示了一个 For-each 映射内的嵌套映射。


图 3. For-each 嵌套映射
For-each 嵌套映射

处理数组时,这里是一些您可能会遇到的其他场景。

场景 1. 从一个源数组到一个单一目标字段的映射

与处理数组的一般情况一样,首先在源数组元素和目标字段之间创建一个映射。在该场景中,顶级映射将会是一个 Local map 或 Move,因为一个 For-each 仅在源和目标是数组时才适用。在这种具体情况下,您需要决定要使用哪个输入数组元素。您可以在 Properties 页面的 Cardinality 选项卡上为 Local map 或 Move 转换设置适用的数组索引。在 Cardinality 选项卡上指定一个索引时,索引号从 1 而非从 0 开始。

为阐释这种情况,考虑 图 4 中带以下源和目标的以下示例。


图 4. 映射前的单一目标
映射前的单一目标

在本例中,源包含带城市名和国家名的目标对象数组,而目标预期仅有一个城市名和一个国家名。在这种情况下,您需要选取目标数组的第一个条目并将其城市和国家名映射到目标单例。第一步是要在数组和包含想填充的字段的目标项之间创建一个 Local map,如图 5 所示。


图 5. 映射后的单一目标
映射后的单一目标

创建好 Local map 之后,设置基数以表明第一个数组条目将是 Local map 转换的输入。在 Local map 内,目标元素的字段被匹配到 GetWeather 元素的合适字段,如图 6 所示。


图 6. 局部嵌套映射
局部嵌套映射

场景 2. 从一个单一源字段到目标中一个数组的映射

在该场景中,假定目标数组仅包含一个元素,您再次需要在源字段和目标数组之间创建一个 Local map。在 Local map 内,详细填写将源中的哪些字段映射到目标中的哪些字段。

您可以在目标数组中填充多个元素,只需创建到数组的多个 Local map 并在 Properties 页面的 Cardinality 选项卡上指定不同的索引。您还可使用一个 Merge 转换,从而使用源中来自两个或多个不同单例的信息来填充目标中的一个数组元素。在 Cardinality 选项卡上指定输出数组索引时,索引号从 1 而非从 0 开始。

场景 3. 从源中多个数组到目标中单一数组的映射

在某些情况下,您可能在源中有两个或多个数组,可用于创建目标中的单一数组。在这种情况下可能会获得两种结果。第一种结果就是合并输入数组中的条目来为目标数组形成单一条目。另一种希望的结果就是将来自输入数组的条目附加在一起形成更长的输出数组。这两种情况将在下面详细描述。

情况 1 — Merge

在这种情况下,您要将来自源的两个数组合并为目标上的单一数组。例如,假设源包含两个独立的数组:姓名数组和电话号码数组。假定电话号码列表与姓名列表一一对应,且输出数组包含需要一个姓名和电话号码的元素,您可以将来自两个独立数组的信息合并为目标上的一个单一数组。

在该情况下,您可以使用 Merge 转换完成所需的结果。类似于一个 For-each,Merge 转换是一个不生成任何结果的容器映射,除非有嵌套映射定义了如何操作输入数组的每个迭代。因此,您必须具体指定将源中的哪些字段并入目标中的哪些字段,从而完成 Merge 实现。注意,迭代是在一个 Merge 中执行的:整个流程首先选取所有 输入数组的第一个索引并将这些字段并入目标数组中的第一个索引。然后选取所有输入数组的第二个索引来生成目标数组的第二个索引,以此类推。看待合并的一种简单方式是,假如您选取两个大小相同的数组并将它们合并在一起,您会获得与原始数组大小相同的一个目标数组。图 7 就显示了这样一个例子。


图 7. Merge 映射
Merge 映射

如果一个 Merge 转换的输入数组大小不同,您可以指定在 Cardinality 属性页面上迭代哪个输入。默认情况下会将迭代设置为 Merge 转换的第一个输入。不过,在输入数组大小不同时指定要迭代的输入数组会在目标数组上产生不同的结果。图 8图 9 通过示例阐释了在已知输入数组大小不同时产生的不同结果。


图 8. 数组大小不同时的合并
数组大小不同时的合并

图 9. 数组大小不同时的合并
数组大小不同时的合并

在上述示例中,生成的目标数组的大小直接取决于所选择的要进行迭代的输入数组。如果已知输入数组的大小在执行映射的过程中是相同的,那么不管选择进行迭代的输入是哪个,产生的结果都一样。

情况 2 - Append

在这种情况下,您要选取一个源数组和另一个源数组上的所有元素,在目标上创建一个包含两个源数组所有元素的数组。例如,假定您在源上有两列旅行目的地,且您希望在目标上创建一个包含所有目的地的单一列表。

在该情况下,您可以使用 Append 转换完成所需的结果。看待 Append 的一种简单方式是,如果您选取任何大小的两个数组将其附加在一起,您获得的目标数组就是输入数组大小的总和。图 10 就显示了这样一个例子。


图 10. Append 映射
Append 映射

类似于一个 For-each,Append 转换是一个不生成任何结果的容器映射,除非有嵌套映射定义了如何操作输入数组的每个迭代。因此,您必须具体指定将源中的哪些字段附加到目标中的哪些字段,从而完成 Append 实现。在实现 Append 转换时,通过 For-each 和 Move 映射会自动生成嵌套映射的第一层。在嵌套的 For-each 映射内需要进一步映射来完成实现。

在一个 Append 映射中执行迭代的方法是:首先选取第一个输入数组的第一个索引来生成目标数组中的第一个索引。然后选取第一个输入数组的第二个索引来生成目标数组的第二个索引。该过程一直持续到第一个输入的所有元素执行完毕为止,之后处理第二个输入数组的所有元素(一个接一个),以此类推。附加输入数组的次序基于其进入 Append 转换的次序。必要时,您可以在 Append 转换上的 Order Property 页面中修改该次序。

为数组排序

在将源数组用作转换的一个输入时,您可以选择对输入数组元素排序,使其位于任何其他映射之前。这可以使用 For-each 转换的 Properties 页面完成。您可在要进行排序的数组中指定字段。还可以指定是使用词汇搜索还是数值搜索,是按升序排列还是按降序排列。例如,假定您有一个目的地数组,其中每个目的地都有一个城市名和一个国家名。假定您希望基于城市名按字母升序排列列表。您可以使用 For-each 转换的 Properties 页面来指定如 图 11 所示的排序。


图 11. 为数组排序
为数组排序

筛选数组

有时在处理数组输入时,您可能只想使用数组中的特定元素。如果是这样,您可以在 Properties 视图的 Cardinality 选项卡上指定适用的数组索引或应用一个筛选表达式到转换。

Cardinality

您可使用 Cardinality 来确定哪些数组索引会被用作转换的输入。您可以指定单个索引,索引范围或逗号分隔的索引列表。表 1 显示了公认的特殊字符。


表 1. Cardinality 语义
字符 含义
: 用于指定值域
, 用于分隔索引和/或范围列表的分隔符
* 直到数组末尾的所有索引

表 2 显示了如何使用上述特殊字符指定基数的一些示例。


表 2. Cardinality 语义示例
含义
1 仅为 1 的元素
1:3 从 1 到 3 的元素
2:* 2 及其以上的元素
1,3,5:* 1、3、5 及其以上的元素

数组索引号从 1 而非 0 开始。

使用 XPath 表达式进行筛选

在处理输入数组时,您可以对转换使用一个筛选表达式,以从源数组中筛选需要的元素。在这种情况下,筛选表达式决定要选择源数组中的哪些元素。编写筛选表达式是为了返回 For-each 将操作的节点集。要返回合适的节点集,应将筛选表达式编写为一个谓语,而非真或假条件。例如,假定您有一个旅行目的地数组,其中每个目的地都有一个城市名和国家名。假定您希望删除国家名为 North Pole 的任何目的地。在这种情况下,您可以添加一个筛选表达式到如 图 12 所示的 For-each 转换。


图 12. 使用 XPath 表达式进行筛选
使用 XPath 表达式进行筛选

未设置为 North Pole 的所有目的地将作为 For-each 映射的输入。而设置为 North Pole 的目的地会被忽略且不属于 For-each 映射输入的一部分。

嵌套数组

Group 细分支持将一个数组转化为一个嵌套数组。例如,假定您有一个旅行目的地数组,其中列表中的每个目的地都有一个类别,用于表明旅行目的地的类型。如果您想通过这些目的地创建一个嵌套数组(其中类别构成顶级数组且每个类别中的目的地构成嵌套数组),那么可以使用一个 Group 转换。图 13 显示了 Group 转换,它将目的地按类别分成嵌套数组。注意,在 图 13 中 Group 转换的 Properties 页面中,指定将类别字段作为分组标准。


图 13. Group 映射
Group 映射

类似于一个 Local map,Group 是一个不生成任何结果的容器映射,除非有嵌套映射定义了如何操作输入数组的每个迭代。因此,您必须具体指定将源中的哪些字段映射到目标中的哪些字段,从而完成 Group 实现。

目前没有转换会允许您将嵌套数组分解为单个数组。不过,您可以使用一个定制的 XSLT 转换来将嵌套数组转化为单个数组。欲了解如何实现该转换,请参见本文的 Custom XSLT 部分。

Custom

在有些情况下,您可能会发现,没有现成的细分适用于执行正确填充目标所需的数据操作。在这种情况下,您可以使用一个定制转换。以下几节将解释三种 Custom 转换:Custom XPath、Custom XSLT 和 Custom Java™。

Custom XPath

在某些情况下,可能需要用到不能作为内置功能细分使用的 XPath 表达式或 XPath 表达式组合。在这种情况下,您可以编写一个定制的 XPath 表达式来实现所需的结果。在编写 XPath 表达式时,定制转换的输入可作为变量使用。您可以使用这个语义引用它们:$

例如,如果转换有一个名为 currentTemperature 的输入元素,那么您可使用 $currentTemperature 引用该元素。

要想了解如何在一个表达式中使用一个 XPath 运算符,可以考虑这样一个场景:假如您希望使用 xsd:int 类型的两个输入的总和来生成一个 xsd:int 结果。您可以使用连接到转换的两个 int 输入创建一个 Custom 转换,然后使用类似下面的表达式计算两个输入的总和:$inputx + $inputy

在 XPath 表达式条目字段中(使用 CTRL+Space)调用内容辅助将显示一个可用变量列表和一个 XPath 函数列表,如 图 14 所示,可将这些函数轻易插入表达式中。


图 14. XPath 内容辅助
XPath 内容辅助

Custom XSLT

您可以通过使用 XSLT 实现定制转换。使用 XSLT 时,调用外部文件中一个指定的模板来生成目标输出。将 Custom 转换的输入作为参数发送到定制的 XSLT。通用 XSLT 模板存储在一个库中,由多个映射共享。Custom XSLT 可用于在目标中创建源中没有的元素。您一般可使用一个 Submap 来完成该操作,但也有 Submap 不可行的时候。在使用 Custom XSLT 时要记住:

  • 默认情况下,Custom 映射的目标元素会得到自动创建,且被调用的模板仅填充目标元素的内容。如果您需要使用 Custom XSLT 来创建整个目标元素(例如,您想在目标元素上设置一个属性),就必须选择 Custom XSLT Properties 页面上的 Include target element within the template 复选框。
  • 在一个定制的 XSLT 模板内,如果您需要来自节点而非输入节点的数据来完成模板,可将节点作为一个附加输入连接到 Custom 转换。 另一种方式就是使用一个绝对 XPath 表达式。

要了解如何使用 Custom XSLT,可考虑这样一个场景:在源上有一个嵌套数组。如 图 15 所示的外部数组是一个类别列表,且每个类别包含一个与类别匹配的旅行目的地列表。


表 3. 样例数据
旅行类别:沙滩
Acapulco,Mexico
Veradero,Cuba
Miami,United States
旅行类别:历险
Whitehorse,Canada
Grand Canyon,United States


图 15. 带定制 XSLT 转换的样例映射
带定制 XSLT 转换的样例映射

上述定制转换会调用以下 XSLT 模板,将每个类别传递给模板,如清单 1 所示。


清单 1. 带参数的定制 XSLT
name="CategoriesToAvailableDestinations">
 name="category"/>
 select="$category/travelDestination">
  
   select="destination"/>
    select="../categoryName"/>
    select="popularity"/>
  
 


模板使用一个 xsl:for-each 选取每个类别的目的地,如清单 2 所示。


清单 2. travelDestination 循环

模板还基于嵌套数组的父类别在输出目的地中设置类别值,如清单 3 所示。


清单 3. 父类别

对于样例输入数据,availableDestinations 输出数组如清单 4 所示。


清单 4. 生成的输出 XML


 
  
   Acapulco
   Mexico
  
  Beach
  8
 

 
  
   Veradero
   Cuba
  
  Beach
  7
 

 
  
   Miami
   United States
  
  Beach
  7
 

 
  
   Whitehorse
   Canada
  
  Adventure
  7
 

 
  
   Grand Canyon
   United States
  
  Adventure
  9
 


为继续示例,假定您实际上不需要选取所有类别的目的地并将其展平为单个列表,而是希望使用单一类别的目的地并将其展平为一个列表。让我们假定客户打算让您知道他们对哪些类别的目的地感兴趣。图 16 中的以下映射显示了客户选中旅行类别和传递到定制的 XSLT 转换中的分类目的地列表。


图 16. 带 XSLT 转换的样例映射
带 XSLT 转换的样例映射

上述映射调用以下 XSLT 模板来查找适当类别的目的地,如清单 5 所示。


清单 5. CategoriesToAvailableDestinations 模板
name="CategoriesToAvailableDestinations">
 name="category"/>
 name="customerTravelCategory"/>
 select="$category/travelDestination">
  test="../categoryName=$customerTravelCategory">
   
    select="destination"/>
    select="../categoryName"/>
    select="popularity"/>
   
  
 

在上述示例中,注意以下要点:

  • travelCateogory 元素是作为 Custom 转换上的第二个输入添加的,因此它的值在定制模板中可作为参数使用。
  • 清单 6 中所示的 If 语句确保仅选中适当类别的目的地。

清单 6. customerTravelCategory 条件
test="../categoryName=$customerTravelCategory">

注意:每一次为一个定制的 XSLT 引用一个 XSLT 文件时,都会将对该 XSLT 文件的导入添加到映射文件中。如果要添加和移除 XSLT 文件到特定的映射中,不妨使用 Properties 页面中的 “XSLT Imports” 选项卡来清理未使用的导入。

使用 XSLT 中的内置扩展

当在 WebSphere Process Server 和 WebSphere Enterprise Server Bus 运行时运行的时候,XSL Transformation 原语使用的 XSLT 处理器基于 Apache Xalan-Java XSLTC 处理器,该处理器提供 EXSLT 扩展函数,包括 string 函数、math 函数、set 函数以及 date and time 函数。欲获取 EXSLT 函数列表,请参阅 Apache XML Project

有时 IBM XSLTC 处理器可能不同于 Apache Xalan-Java XSLTC 处理器,因此上面提到的所有函数未必都可用。以下示例显示了正在定制的 XSLT 模板中使用的 ExstlString:split 函数。在示例中,GetWeather 服务返回了一个 XML 字符串,在字符串中存在一个您要提取的温度值。例如:


清单 7. 温度输出 XML
...
...
 55 F (13 C) 
...
...


清单 8. GetWeatherResultToCurrentTemperature XSL 模板
...
xmlns:str="http://exslt.org/strings"
...
name="GetWeatherResultToCurrentTemperature">
  name="GetWeatherResult"/>
  
  select="str:split($GetWeatherResult, '<Temperature>')[2]"/>
  
  select="str:split($temp1, '</Temperature>')[1]"/>
  
  select="normalize-space($temp2)"/>

注意:

  • str 名称空间在样式表上按如下方式定义:xmlns:str="http://exslt.org/strings"
  • 带两个字符串参数的 split 函数按如下方式调用:select="str:split($GetWeatherResult, '<Temperature>')[2]"

使用位于库中的通用 XSL 文件

我们建议您将通用 XSL 模板放在位于库项目中的通用 XSL 文件中。通过使用库,多个中介模块可访问通用 XSL 模板。

在将一个通用 XSL 文件导入到一个位于中介模块中的 XSL 文件中时,使用一个 xsltcl URI。例如,如果中介模块中的 XSL 文件是 MediationModule1/xslt/MyMap-custom.xsl,且您希望导入的 XSL 文件是 /Library1/common/CommonXSLLibrary.xsl,则导入将是:

Library1 的库名不包含在 URI 中。

Custom Java

实现 Custom 转换的第三个方法是使用对静态 Java 方法的调用。您可以使用一个 Java 调用填充任何简单或复杂元素的值。您可以将输入连接到 Custom Java 转换并将这些输入作为方法参数使用。输入可以是简单或复杂类型的输入。在将复杂类型的输入作为方法参数使用时,相应的方法类型会为复杂类型的数组使用 org.w3c.dom.Node 或 org.w3c.dom.NodeList 类型的参数。在处理 Custom Java 转换时,Custom 转换的 Properties 页面允许您选择所需的类和方法,并将 Custom 转换的输入映射到 Java 方法的参数。

为阐述一个 Custom Java 调用的基本使用方法,以清单 9 为例。在中介流期间,对一个 Web 服务进行调用来检查特定目的地的天气状况。天气服务返回一个 XML 结果,目的地的温度就包含在该结果中。一个静态 Java 方法可用于解析 XML 数据并将温度作为一个 int 值提取出来。


清单 9. Web 服务返回的 XML 输出

  Toronto Pearson Int'L. Ont., Canada (CYYZ) 
   43-40N 079-38W 173M
  
  from the ESE (110 degrees) at 9 MPH (8 KT):0
  15 mile(s):0
  overcast
  50 F (10 C)
  41 F (5 C)
  71%
  30.14 in. Hg (1020 hPa)
  Success

假定您关注于提取摄氏温度,在上述示例中即为 21。以上述数据为基础,清单 10 中所示的 Java 方法将通过在 output. 中查找 标记来提取摄氏温度。


清单 10. getCelsiusTemperature Java 方法
  public static int getCelsiusTemperature(String originalWeatherReport) {

    int temperature = -99; // Error - Can't find temperature
    if (originalWeatherReport != null)
    {
      int temperatureStartIndex = originalWeatherReport
          .indexOf(TEMPERATURE_START_TAG);
      int temperatureEndIndex = originalWeatherReport
          .indexOf(TEMPERATURE_END_TAG);
      if ((temperatureStartIndex >= 0)
          && (temperatureEndIndex > temperatureStartIndex))
      {
        temperatureStartIndex = temperatureStartIndex
            + TEMPERATURE_START_TAG.length();
        String temperatureString = originalWeatherReport.substring(
            temperatureStartIndex, temperatureEndIndex);
        int startBracketIndex = temperatureString.indexOf('(');
        int endBracketIndex = temperatureString.indexOf(')');
        if (startBracketIndex >= 0 && endBracketIndex >= 0
            && (startBracketIndex < endBracketIndex))
        {
          String celsiusString = temperatureString.substring(
              startBracketIndex + 1, endBracketIndex);
          if (celsiusString.endsWith("C"))
          {
            celsiusString = celsiusString.substring(0,
                celsiusString.length() - 1);
            celsiusString = celsiusString.trim();
            try {
              double doubleValue = Double.parseDouble(celsiusString);
              temperature = (int) Math.round(doubleValue);
            } catch (NumberFormatException e) {
            }
          }
        }
      }
    }

    return temperature;

  }

要在一个 Custom 转换中使用上述方法,请执行以下操作:

  • 确保包含映射文件的库或模块对包含 Java 文件(内含方法)的项目有一定的依赖性。
  • 在适当的映射文件中创建 Custom Java 转换,如 图 17 所示。

    图 17. Custom Java 映射
    Custom Java 映射

  • 在 Custom Java 转换的 General 属性页面上,使用 Class 字段旁边的 Browse 按钮来浏览包含方法的类。在 Select Type 对话框中输入类名时,可用选项列表会更新为包含与所键入前缀相匹配的类。
  • 在 Method 下拉框中,选择所需的方法。
  • 在参数部分,选择合适的输入字段以映射到每个方法参数。例如,图 18 中的以下属性将 GetWeatherResult 值作为一个变量引用。

    图 18. Custom Java 映射属性页面
    Custom Java 映射属性页面

在上一个例子中,Custom Java 转换的输入和输出都是简单类型。下面考虑另一个例子,假设您希望使用复杂类型输入的数组来填充目标上复杂类型的数组。Java 方法会将 NodeList 作为一个输入参数,并将 NodeList 作为一个结果返回。

在该例中,假定您在源中有一个目的地数组,且希望在填充目标上的目的地列表之前使用一个 Java 方法添加额外的目的地到列表中。第一步是要创建一个实现以下功能的定制 Java 类:

  1. 生成额外目的地列表。
  2. 合并现有目的地与额外目的地。
  3. 返回 NodeList 中合并后的列表。

在该例中,将会创建一个 DestinationCreationUtility 类。该类将包含清单 11 中所示的以下方法。


清单 11. appendCreatedDestinations Java 方法
public static NodeList appendCreatedDestinations(NodeList 
 arrayOfTravelDestinations)

该方法会接受一个 TravelDestination 节点数组,并解析这些节点来确定现有目的地列表。该方法会将现有目的地转换为 TravelDestination 对象,从而简化它们与额外目的地列表的合并,如清单 12 所示。


清单 12. getAdditionalDestinations Java 方法
public static ArrayList getAdditionalDestinations()

这是一个生成额外目的地列表的方法。您可以将该列表与现有目的地列表合并来创建 TravelDestination 对象的一个特有列表,如清单 13 所示。


清单 13. convertToNodeList Java 方法
private static NodeList convertToNodeList(ArrayList allDestinations)

该方法会选取 TravelDestination 对象列表并构建一个 NodeList。convertToNodeList 方法按清单 14 所示实现。


清单 14. DestinationCreationUtility Java 类
import java.util.ArrayList;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.xpath.NodeSet;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;

public class DestinationCreationUtility {	
  ...
  private static NodeList convertToNodeList(
    ArrayList allDestinations) {
		
    NodeSet resultSet = new NodeSet();

    try {

      Document doc = DocumentBuilderFactory.newInstance()
        .newDocumentBuilder().newDocument();

      for (int i = 0; i < allDestinations.size(); i++) {
        TravelDestination next = allDestinations.get(i);
        Element nextAvailableDestination = doc.createElement("availableDestination");
        Element destinationElement = doc.createElement("destination");
        Element cityName = doc.createElement("cityName");
        Text cityNameText = doc.createTextNode(next.cityName);
        cityName.appendChild(cityNameText);
        Element countryName = doc.createElement("countryName");
        Text countryNameText = doc.createTextNode(next.countryName);
        countryName.appendChild(countryNameText);
        Element categoryElement = doc.createElement("category");
        Text categoryText = doc.createTextNode(next.category);
        categoryElement.appendChild(categoryText);
        Element popularityElement = doc.createElement("popularity");
        Text popularityText = doc.createTextNode(next.popularity + "");
        popularityElement.appendChild(popularityText);
        destinationElement.appendChild(cityName);
        destinationElement.appendChild(countryName);
        nextAvailableDestination.appendChild(destinationElement);
        nextAvailableDestination.appendChild(categoryElement);
        nextAvailableDestination.appendChild(popularityElement);
        resultSet.addElement(nextAvailableDestination);
      }
    } catch (DOMException e) {
        throw new org.apache.xml.utils.WrappedRuntimeException(e);
    } catch (ParserConfigurationException e) {
        throw new org.apache.xml.utils.WrappedRuntimeException(e);
    }
    return resultSet;
  }
  ...
}

为完成任务,一旦 DestinationCreationUtility 类被创建,使用 Custom Java 转换将源目的地映射到目标目的地的映射也会被创建,如 图 19 所示。


图 19. 带属性集的 Custom Java 映射
带属性集的 Custom Java 映射

调试定制 Java 调用

当您有一个使用定制 Java 调用的映射时,可以在映射运行于服务器中时调试正被调用的 Java。使用 Test Map 视图或使用 Integration Test Client 本地测试一个映射文件时,在 Java 代码中设置断点不会产生任何影响。然而,如果您使用 Integration Test Client 执行一个组件测试,且服务器在 Debug 模式下运行,在被调用的 Java 方法中设置的断点会促使执行中止,从而可以单步调试 Java 方法。

通配符的使用

通配符是一种机制,它使一个 XML Schema 定义变得很灵活。由于通配符的结构在 XML Schema 中未作定义,XML Mapping Editor 不能自动显示通配符的结构。自 V7.0.0 以来,XML Mapping Editor 支持将通配符投射(cast)为一个具体类型,允许显示并轻松映射结构。在 Mediation Flow 中工作时,投射的另一种方法是在使用 XSL Transform. 原语之前使用一个 Set Message Type 原语。Set Message Type 原语允许您指定用于通配符的具体类型。

识别通配符

基于 XML Schema 定义,通配符有以下三种形式:

通配符

在这种情况下,任何名称和类型的元素都适用。在 XML Mapping Editor 中,可通过使用 Any                     icon 图标查找字段来识别 通配符,其名称为 any 且输入栏中未指定类型,如 图 20 所示。


图 20. XML Schema any 元素
XML Schema any 元素

在上述示例中, 通配符也可重复使用,此时目标可包含任意名称和类型的元素数组。像任何其他字段一样,基数栏将表明一个 通配符是否可重复使用。

通配符

在这种情况下,带任何名称和值的零个或更多属性都可接受。在 XML Mapping Editor 中,可通过使用 anyAttribute icon 图标寻找字段来识别 通配符,其名称为 anyAttribute 且在输入栏中未指定类型,如 图 21 所示。


图 21. XML Schema any attribute
XML Schema any attribute

anyType 类型

这种情况适合带指定名称的元素,但元素可以是任何类型。下面是一个名为 correlation 的 anyType 类型元素示例,如 图 22 中的 XML Mapping Editor 中所示。


图 22. XML Schema any anyType
XML Schema any anyType

当预期目标是 anyType 类型时,我们建议使用 xsi:type 属性设置最终目标元素中的类型。在处理过程中运行时使用 xsi:type 属性。如果不为 anyType 目标设置 xsi:type 属性,可能会出现错误。

从源中的一个通配符进行映射

当您的源是一个通配符或有 anyType 类型时,您可以使用以下方法中的一种将该输入的内容映射到目标:

Cast

New icon 当您知道源总是包含相同的类型或全局元素/属性时,Cast 是首选方法。Cast 是通过右键单击通配符并选择 Cast 菜单来完成的。

通配符:当您可以将潜在的 通配符输入列表缩小为全局元素集时,就可将 通配符输入投射到每个潜在的全局元素。一经投射,通过 cast 全局元素创建的转换仅在相应的全局元素在运行时真正属于输入文档的一部分时才运行。

通配符:当您可以将潜在的 通配符输入列表缩小为全局属性集时,就可将 通配符输入投射到每个潜在的全局属性。一经投射,通过 cast 全局属性创建的转换仅在相应的全局元素在运行时真正属于输入文档的一部分时才运行。

anyType type:当您可以将潜在的输入列表缩小为具体类型的集合时,就可将 anyType 输入元素投射到每个潜在的具体类型。一经投射,通过 cast 类型创建的转换仅在相应类型在运行时真正属于输入文档的一部分时才运行。

Move

当不需要对输入进行任何操作时,您可以使用 Move 转换将确切的输入复制到目标文档。

通配符:如果您希望原封不动地复制源元素,可以使用 Move 转换将 通配符表示的源元素移动到目标。要使源元素的名称和类型在运行时有效,就需要将其与目标的要求相匹配。

通配符:您不能使用 Move 从一个 通配符源进行映射。

anyType 类型:如果源元素和目标元素都有一个 anyType 类型,您只能对源元素使用 Move 转换。从带 anyType 类型的源元素到带具体类型的目标元素的 Move 转换不受支持。

Submap

当决定所需投射的逻辑复杂且可重用时,考虑使用 Submap。

通配符:如果源中有一个 通配符包含填充目标元素所需的数据,但与目标元素所需的元素结构不精确匹配时,可使用 Submap 映射这两个元素或这些元素的两个类型。创建 Submap 时,指定源的预期元素或类型为 Submap 输入。设置好 Submap 输入之后,输入的完整结构在 Submap 中显示,且源字段与匹配的目标字段一一映射。

通配符 通配符源不支持 Submap。

Custom

当 Cast、Move 或 Submap 不能实现所需的效果时,可以使用带通配符输入的 Custom XPath、Custom XSLT 或 Custom Java 转换。一个 Custom 转换允许您以任意形式细分一个源通配符。它还允许您将两个独立的通配符源映射到一个单一目标。

在使用 Custom 转换从一个 通配符执行映射时,建议您将 通配符的父级作为转换输入。这可以使您根据名称或位置访问属性。例如,如果您有一个 Custom XPath 转换使用一个名为 luggage 的元素的输入,且您希望访问一个名为 passengerName 的属性以将其赋给输出属性时,您可以使用一个 Custom XPath 转换,其 XPath 表达式类似于 $luggage/@passengerName

映射到目标中的一个通配符

如果您的目标是一个通配符或含有 anyType 类型,您可以使用以下方法中的一种映射到通配符目标。

Cast

New icon 当您知道目标总是包含相同的类型或全局元素/属性时,Cast 是首选方法。Cast 是通过右键单击通配符并选择 Cast 菜单来完成的。

通配符:通过 Cast 方法,您可以将一个 通配符投射到一个或多个特定全局元素。如果您确定会一直使用相同的全局元素或元素来填充目标 通配符,可以将 通配符投射到期望的全局元素。当目标仅限于单一元素且存在多个 cast 全局元素时,我们建议您在 cast 全局元素的顶级创建映射。例如,假如有一个单例 通配符被投射到两个不同的全局元素,即 GlobalElementA 和 GlobalElementB。该例的目的在于,根据某些输入的存在状况,在运行时 GlobalElementA 和 GlobalElementB 中仅有一个在目标中得到创建。为确保在运行时仅创建其中一个全局元素,一定要做到:

  • 只有其中一个全局元素的输入在运行时存在于输入 XML 文档中。
  • 目标全局元素在最顶级被映射。

图 23 阐释了到最顶级的映射,以下映射是在 cast 全局元素的最顶级创建的,且它们是正确的。


图 23. 有效的 Cast 映射
有效的 Cast 映射

以下映射不是 在最顶级创建的,且导致在运行时创建空元素,不管输入元素是否存在。图 24 中所示的映射是不正确的。


图 24. 无效的 Cast 映射
无效的 Cast 映射

通配符:通过 Cast 方法,您可以将一个 通配符投射到一个或多个特定全局属性。如果您确定会一直使用相同的全局属性填充目标 通配符,可以将 通配符投射到预期的全局属性。

anyType 类型:如果目标 anyType 的类型是已知的,那么使用 anyType 的首选方法是将其投射到一个具体类型。

Move

通配符:如果有一个源元素恰好就是您希望用来填充目标的元素,Move 会将元素从源复制到目标 通配符。

当映射到目标中的一个 通配符时,您要确保不违反名称空间和处理约束。当您选择目标栏中的一个 通配符时,Properties 视图会显示名称空间和处理约束,不过没有相关的验证检查来确保不违反约束。

通配符:如果要包含在目标中的属性与源中所需的名称和值一同出现,您可以使用一个或多个 Move 转换将属性从源复制到目标。当属性不存在于源中或需要对属性名作更改时,就需要一个 Custom 转换。图 25 显示了如何将多个源属性映射到目标上的一个 通配符。


图 25. 将多个源属性映射到一个目标
将多个源属性映射到一个目标 </FONT> <BR>
<P><STRONG>anyType 类型</STRONG>:如果有一个源元素恰好是您想用来填充目标的类型,Move 会将元素内容从源复制到目标。Move 转换自动将推荐的 xsi:type 属性添加到目标元素,这样就可在运行时准确识别类型。</P>
<P><STRONG>Submap</STRONG> </P>
<P>如果您有复杂的逻辑可以确定使用的类型是什么,且您希望该逻辑可重用,这时就可使用一个 Submap。</P>
<P><STRONG><any> 通配符</STRONG>:使用 Submap 映射到目标中的一个 <any> 通配符时,您要确保不违反名称空间和处理约束。在目标栏中选择 <any> 通配符时,Properties 视图会显示名称空间和处理约束,但没有相关的验证检查来确保不违反约束。</P>
<P><STRONG><anyAttribute> 通配符</STRONG>:您不能使用一个 Submap 来填充目标中的 <anyAttribute> 通配符。</P>
<P><STRONG>anyType 类型</STRONG>:在使用 anyType 类型的目标时,Submap 转换为目标元素使用正确的元素名,另外添加推荐的 xsi:type 属性来确保在运行时正确识别目标元素。</P>
<P><STRONG>Custom</STRONG> </P>
<P>当 Cast、Move 转换或 Submap 转换不适用时,您可以使用 Custom XPath、Custom XSLT 或 Custom Java 转换来为目标构建合适的元素或属性。如果使用一个 Custom 转换映射到拥有 anyType 类型的一个元素,记住一定要为该元素设置 xsi:type 属性。</P>
<P><A name=Casting><SPAN class=atitle><STRONG><FONT size=5>使用通过扩展和限制定义的派生类型</FONT></STRONG></SPAN></A></P>
<P><STRONG><FONT size=5><IMG height=11 alt= 当对基本类型进行了扩展或限制之后,您可以使用 cast 转换显示源或目标中的派生结构。当输入包含派生类型的基本类型时,您可以将该基本类型投射到其任何派生类型,如 图 26 所示。


图 26. 投射派生类型
投射派生类型

在上述示例中,Address 是基本类型,且是在模式中被定义为 field2 类型的类型。在本例中,field2 被同时投射到 USAddress 和 UKAddress,它们都是 Address 类型的扩展类型。

为防止在运行时在目标中创建不需要的副本或空元素,我们建议您遵循以下约定:

  • 在将源或目标元素投射到派生类型之后,我们建议您仅创建与派生类型之间的来回映射,而不要创建到基本类型的映射。
  • 要在目标上一直创建到可选元素最顶级的映射。例如,假定在目标中有类型 Address 的一个单一元素,该元素分别投射到两个派生类型 USAddress 和 UKAddress。该例的目的在于,基于某些输入的存在状况,仅有一个派生类型将存在于目标文档中。为确保一个目标只有在被映射的输入存在于输入文档时得到创建,在映射任何子级之前要映射到顶级。图 27 解释了顶级映射,且该映射是正确的。

    图 27. 有效的派生类型投射
    有效的派生类型投射

    图 28 显示了直接到 cast 元素子级的映射,且该映射是不正确的。

    图 28. 无效的派生类型投射
    无效的派生类型投射

投射可通过右键单击源或目标中的字段然后选择 Cast 按钮完成。在调用投射动作时只显示有效的派生类型。如果选中字段没有有效的派生类型,会显示一个对话框表示无类型可用。如果找不到所需类型,确保所需的派生类型包含在引用的项目中。

使用 choice 元素

默认情况下,xsd:sequence 和 xsd:choice 等 XSD 模型组不在 XML Mapping Editor 中显示。更改显示组的视图首选项就会显示 XSD 模型组的信息,从而允许您识别 choice 组元素的位置。单击本地工具栏上的 Open 首选项按钮(Open preferences button)可更改视图首选项。

您不能直接映射 choice 元素,但可以映射一个 choice 组内包含的元素。一个实例文档任何时候都只能包含一个不可重复的 choice 模型组内的其中一个元素,不能包含多个元素。因此,通过一个不可重复的 choice 组的成员创建转换时,只有与存在于运行时实例文档的这些成员元素连接的转换才得到执行。与连接到其他元素的任何转换都将被忽略。

在创建以 choice 组元素为目标的转换时,要确保映射逻辑只产生一个 choice 组元素。如果一个 choice 组中预备有多个元素,就会产生意料之外的结果,因为所生成的 XSL 会试图创建一个有效的 XML 输出文档。使用 If、Else If 和 Else 转换,或使用 XPath、Custom XSLT 或 Custom Java 转换来确保一个 choice 组中只有一个元素得到创建,从而确保映射执行过程中获得预期结果。

使用 substitution 组

默认情况下,substitution 组不在 XML Mapping Editor 中显式显示。不过,您可以通过一个元素上的以下图标识别包含在一个 substitution 组内的头元素:Substitution 组图标。更改显示组的视图首选项可显式地显示 substitution 组。单击本地工具栏上的 Open 首选项按钮(Open preferences button)可更改显示组首选项。

如同所显示的实际 xsd:sequence 和 xsd:choice 元素,您不能直接映射 sbstitution 组节点,但是可以映射一个 substitution 组内包含的元素。同样地,像 choice 元素一样,关键要记住,一个示例文档一次只能包含可替代元素的一个成员,而不能包含多个成员。

Business Object Mapper 将其映射基于泛类,与此不同,XSLT Mapper 在执行期间仅执行与实例文档中的元素精确匹配的转换。因此,在一个 substitution 组中的头元素上创建一个 “常规” 转换来处理实例文档中可能存在的任何派生元素不能处理所有情况。相反地,如果打算转换每个元素,在特定转换中必须包含涉及的元素,以处理特有情况。如果必要,该方法允许筛选包含在 substitution 组中的某些元素(即不将数据移动到目标)。

在创建以 substitution 组成员为目标的转换时,确保映射逻辑仅产生其中一个成员。如果一个 substitution 组中预备有多个元素,就会产生意料之外的结果,因为所生成的 XSL 会试图产生一个有效的 XML 输出文档。

SOAP 头

下面内容将解释使用 SOAP 头时发生的一些常见用例。SOAP 头元素中的值元素是一个名为 value 的 anyType 类型元素,可使用 映射到目标中的一个通配符 部分解释的方法进行设置。使用值元素的最简单的方法是将其投射到所需输出类型的一个元素。

另一种情况发生在当您基于 SOAP 头名执行一个条件映射时。在该情况下,您可以创建 if、Else If 或 Else 映射。欲了解如何执行 if-else 逻辑,请参见本系列的第 1 部分 条件映射

SOAP 编码的数组

当您的源或目标包含一个经 SOAP 编码的数组时,您可能会希望将数组元素从一个 SOAP 编码的数组移动到一个标准数组,反之亦然。XML Mapping Editor 不识别一个 SOAP 编码数组内的元素类型,且将元素作为 通配符元素的一个数组显示。与 SOAP 编码数组的相互转化将在本节介绍。

情况 1. 从一个 SOAP 编码的源数组复制到一个常规目标数组

在该情况下,您可以使用 For-each 和 Submap 映射。For-each 用于迭代源数组中的每个 通配符元素,而 Submap 用于将这些元素投射到所需的类型。例如,在源上有 Destination 元素的一个 SOAP 编码数组,您希望使用它在目标上填充 Destination 元素的常规数组,如 图 29 所示。


图 29. SOAP 编码数组的 Foreach 映射
SOAP 编码数组的 Foreach 映射

在 For-each 映射内,Submap 映射用于将 通配符元素(它实际上是本例中的 Destination 元素)映射到 Destination 元素,如 图 30 所示。


图 30. SOAP 编码数组的 Submap 映射
SOAP 编码数组的 Submap 映射

上面的 Submap 转换调用将 Destination 同时作为源和目标类型的一个 Submap。

情况 2. 从一个常规源数组复制到一个 SOAP 编码的目标数组

这种情况需要编写定制的 XSLT 来为 SOAP 数组创建数组元素。定制的 XSLT 必须执行以下操作来创建一个一致的 SOAP 数组:

  • 为每个数组元素使用名称项。因而可以在 Integration Test Client 中工作的同时查看 SOAP 数组内容。例如:<item xsi:type="in:Destination">
  • 在 SOAP 数组内的每个项目上使用 xsi:type 属性。否则会发生运行时错误,因为数组元素的类型无法确定:xsi:type="in:Destination">

为展示将常规数组转化为 SOAP 编码数组的场景,假定在源中有一个 Destination 元素列表,您希望使用这些元素填充目标上 Destination 的 SOAP 编码列表。如 图 31 所示,在映射中创建了一个 Custom 转换。


图 31. SOAP 编码数组的 Custom 映射
SOAP 编码数组的 Custom 映射

Custom XSLT 转换的目标是可重复使用的 通配符。在 Custom XSLT Properties 页面上,模板复选框内的 Include 目标元素处于选中状态,从而允许您创建完全在 Custom XSLT 内的 通配符数组元素。目标元素的外部 shell 总是在 Custom XSLT 转换上被默认创建,除非选择模板复选框内的 Include 目标元素。清单 15 显示了定制的 XSLT。


清单 15. DestinationToSOAPDestination XSL 模板
name="DestinationToSOAPDestination">
 name="destination" />
  select="$destination">
   xsi:type="in:Destination">
    
     select="cityName" />
    
    
     select="countryName" />
    
   
  

高级提示和技巧

创建新目标节点

新节点可通过各种方式引入目标。其中一种方式是应用一个 Submap 或 Custom 转换到一个可替换的目标节点(例如,一个通配符、一个派生类型或一个 substitution 组成员)。然后您可以用特定 Submap 或 Custom 节点中的一个合适节点或节点集替换该目标节点。此外,您可以直接将新节点引入映射编辑器,只需用一个 substitution 组成员替换另一个或将一个基本类型投射到其一个或多个派生类型。欲了解更多信息,请参见 Custom使用通过扩展和限制定义的派生类型 部分。

确保一个完整的目标 XML 文档

使用 XML Mapping Editor 创建映射时,没有自动验证机制来确保所创建的映射会产生一个完整的目标 XML 文档。验证机制试图确保每个目标字段都填充有有效数据,但它不能检测出必需的目标字段根本未得到填充的情况。如果一个必需的目标字段未得到映射,最终的目标元素根据其模式是无效的。您可以通过查看目标的基数栏来识别必需的目标字段。如果基数显示为 [1..1] 或 [1…n] 或 [1…*],字段就是必需的。如果一个字段是必需的,且该字段的父级存在于输出中,则该必需字段也会存在于输出中。要确定一个字段是否必须存在于输出中很棘手,因为它取决于所有字段的上级。例如,如果所有字段上级都是必需的,则字段就是必需的。再例如,如果字段是必需的但字段父级是可选的且未被映射,字段就不需存在于输出文档中。

在确定字段是必需的且需要存在于输出 XML 文档中之后,确保以下内容:

  • 有一个到必需字段的映射。
  • 如果从一个以可选字段作为输入的映射开始映射必需字段,那么一定要使用第二个条件映射来弥补源字段不存在于输入 XML 中的情况。通常在使用一个可选源填充必需目标时会出现这种情况。在这种情况下,您需要为源不存在的情况制定一个应急计划。为创建一个应急映射,考虑使用 If 和 Else 映射,以确保不管是否有可选输入,总是至少有一个到必需目标的映射。欲了解有关条件映射的更多信息,请参见本系列第 1 部分中的 条件映射

映射到目标头元素的相关注意事项

有时,您会发现执行到一个目标头元素(即映射或嵌套映射内的顶级目标元素)的映射很有必要,或至少很有益处。 图 32图 33 分别是映射和嵌套映射内的头元素示例。


图 32. 根头元素
根头元素

图 33. 嵌套头元素
嵌套头元素

到头元素的映射并非在任何情况下都受支持。下面内容是映射到头元素目标应遵循的一般规则:

  • 一般而言,容器映射(If、Else if、Else、Local、For-each、Merge 和 Append)对于头元素目标来说是不允许的。惟一的例外是,在 If、Else if 或 Else 映射内可使用 Local、Foreach、Merge 和 Append。
  • Move、Convert、Submap、Custom、Substring、Normalize 和 Concat 映射在适当情况下可用于头元素目标。
  • 在使用 Append 时,到头元素目标的映射是自动被创建的。这些生成的映射是 Append 内允许的惟一头元素映射。
  • Submap 和 Custom 是两个常用于映射到头元素目标的转换,因为它们允许重用。使用 Submap 执行到头元素目标的映射时,Submap 必须是一个类型映射。

确保在 Custom XSLT 中使用的名称空间前缀在输出 XML 中能被识别

有一个已知问题:

  • 一个 XSL 文件(比如为一个 .map 文件生成的那个文件)调用另一个 XSL 模板(比如一个 Custom XSLT 映射)。
  • 调用的 XSL 文件使用 exclude-result-prefixes 属性排除某些前缀。例如:
    xmlns:in="http://TravelDestinationsLibrary"
    ...
    exclude-result-prefixes="in xalan"			
    

  • 被调用的 XSL 文件重用被排除的前缀,但不声明将其排除。例如:
    xmlns:in="http://TravelDestinationsLibrary"
    ...
    exclude-result-prefixes="xalan"			
    

  • 输出 XML 文件不会包含被调用的 XSL 文件所需的名称空间声明。对该名称空间的使用将导致运行时错误,因为它未被识别。

如果服务器在调试模式下运行,该问题在 local map 测试或运行时期间不会发生。该问题仅在服务器在非调试模式下运行时发生。会发生这种情况的一个常见场景是在 Custom XSLT 中使用 xsi:type 属性时。有一个变通方案可确保名称空间包含其中。该方案使用的示例与用于解释从常规数组到 SOAP 编码数组的映射的场景相同。场景中使用了 Custom XSLT 且 Custom XSLT 使用 xsi:type 属性,如清单 16 所示。


清单 16. DestinationToSOAPEncodedDestinationArray XSL 模板
name="DestinationToSOAPEncodedDestinationArray">
 name="destination" />
  select="$destination">
   xsi:type="in:Destination">
    
     select="cityName" />
    
    
     select="countryName" />
    
   
  

item 元素由使用 in 名称空间的一个 xsi:type 属性声明,如清单 17 所示。


清单 17. item xsi:type 属性

上述 XSLT 在进行局部测试的同时会生成正确的结果,但如果服务器不在调试模式下运行则会在运行时出错,因为 in 名称空间不会被识别出来。为解决该问题,使用 将名称空间声明添加到元素标记,如清单 18 所示。


清单 18. DestinationToSOAPEncodedDestinationArray XSL 模板

    
    

    
          
    
          
              
          
          
              
          
    
          
    

    

item 元素的初始和结束标记由 nxsi:text 包围,目的是强制包含 in 名称空间声明。

组织导入

New icon在向一个映射添加 Custom XSLT、Custom Java 或 Lookup 转换时,同时会将对已引用 XSLT 或 Java 文件的相应导入添加到映射。如果稍后删除导致添加导入的转换,导入不会被自动删除。为保持导入的组织性,您可以查看所有导入并使用映射文件属性页面手动删除未使用的导入。为访问一个映射文件的属性,在编辑器中打开文件时选择映射背景画布,然后打开 Properties 视图。选择映射背景可确保无特定元素或转换被选中,且显示的属性将应用于映射文件,而非映射中的特定元素。在属性视图中,可以使用 XSLT Imports 和 Java Imports 页面处理映射文件导入。

还有一个名为 Namespaces 的属性页面,它允许为给定名称空间定义定制前缀。当在运行时特定名称空间不带前缀或如果有必要在整个应用程序内为一个给定名称空间使用统一前缀时,可以使用这个属性页面。

结束语

在本文中,我们学习了如何处理复杂数组、通过 Java、XSLT 模板和 XPath 实现的定制转换,以及用于处理 XML Schema any、anyType 和派生类型的最佳实践。文中还讨论了如何在 WebSphere Enterprise Service Bus 内处理 SOAP 头和 SOAP 编码数组。这些关键映射技能是在 WebSphere Integration Developer 内创建更多复杂 XML 映射的一个重要部分。

请使用浏览器的分享功能分享到微信等