尝试一个实际的模式
独立的模式定义非常适合用于简单演示,但是在应用到企业应用程序中广泛使用的复杂模式定义时,它无法让用户了解工具的工作原理。现在将以一个符合行业标准的 HR-XML 模式定义为例,继续介绍一个更实际的示例。
HR-XML TimeCard 模式
HR-XML Consortium 是为开发针对人力资源的 XML 表示的开放标准而成立的组织。它代表着 110 多家企业成员,并且几乎有 50 家技术公司通过了符合其标准的认证。
本教程中使用的 HR-XML 模式包含 157 个模式,其中混合了顶级文档定义和常用组件。CodeGen 可以轻松地处理这些模式,但是生成的类数目和相互关系的复杂度掩盖了模式处理的更有趣方面。为了关注这些细节,这里使用的 HR-XML 的子集包括 TimeCard 元素的一个顶级文档定义,以及作为 TimeCard 定义的一部分引用的常用组件 — 总计七个模式定义。
您可以在 hrxml/schemas 目录下找到本教程中使用的 HR-XML 模式定义子集。清单 7 显示了经过编辑的 TimeCard 元素定义的主要模式。这将给出一个 HR-XML 模式样式样例,该样例将同时使用嵌套及全局类型的定义,并且比第一个示例包含更广泛的模式结构,包括:
-
<xs:choice> 组合器(compositor)(如 TimeCardType 定义中的某些嵌入式 complexType 所示)
-
<xs:any> 粒子(particle)(查看清单开始部分的 AdditionalDataType 定义)
-
<xs:simpleType> <union>(查看清单末尾的 TimeCardDuration 定义)
- 非枚举型
<xs:simpleType> 限制
清单 7. HR-XML TimeCard 模式
<xs:schema targetNamespace="http://ns.hr-xml.org/2007-04-15" ...
elementFormDefault="qualified" version="2007-04-15">
<xs:import namespace="http://www.w3.org/XML/1998/namespace" ...>
<xs:include schemaLocation="../CPO/EntityIdType.xsd"/>
...
<xs:complexType name="AdditionalDataType" mixed="true">
...
<xs:sequence minOccurs="0" maxOccurs="unbounded">
<xs:any namespace="##any" processContents="strict" minOccurs="0"
maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute name="type" type="xs:string"/>
</xs:complexType>
...
<xs:element name="TimeCard" type="TimeCardType"/>
<xs:complexType name="TimeCardType">
<xs:sequence>
<xs:element name="Id" type="EntityIdType" minOccurs="0"/>
<xs:element name="ReportedResource">
<xs:complexType>
<xs:choice>
<xs:element name="Person" type="TimeCardPersonType"/>
<xs:element name="Resource">
<xs:complexType>
<xs:sequence>
<xs:element name="Id" type="EntityIdType"
minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="ResourceName" type="xs:string" minOccurs="0"/>
<xs:element name="AdditionalData" type="AdditionalDataType" minOccurs="0"
maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute name="type" type="xs:string"/>
</xs:complexType>
</xs:element>
</xs:choice>
</xs:complexType>
</xs:element>
<xs:element name="ReportedTime" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="PeriodStartDate" type="AnyDateTimeType"/>
<xs:element name="PeriodEndDate" type="AnyDateTimeType"/>
<xs:element name="ReportedPersonAssignment" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="Id" type="EntityIdType" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:choice maxOccurs="unbounded">
<xs:element name="TimeInterval">
<xs:complexType>
<xs:sequence>
<xs:element name="Id" type="EntityIdType" minOccurs="0"/>
<xs:element name="StartDateTime" type="AnyDateTimeType"/>
<xs:choice>
<xs:sequence>
<xs:element name="EndDateTime" type="AnyDateTimeType"/>
<xs:element name="Duration" type="TimeCardDuration" minOccurs="0"/>
</xs:sequence>
<xs:element name="Duration" type="TimeCardDuration"/>
</xs:choice>
<xs:element name="PieceWork" minOccurs="0" maxOccurs="unbounded">
...
</xs:element>
<xs:element name="RateOrAmount" minOccurs="0" maxOccurs="unbounded">
...
</xs:element>
<xs:element name="Allowance" minOccurs="0" maxOccurs="unbounded">
...
</xs:element>
...
</xs:sequence>
<xs:attribute name="type" type="xs:string" use="required"/>
...
</xs:complexType>
</xs:element>
<xs:element name="TimeEvent">
...
</xs:element>
<xs:element name="Expense">
...
</xs:element>
<xs:element name="Allowance">
...
</xs:element>
</xs:choice>
...
</xs:sequence>
...
</xs:complexType>
</xs:element>
...
</xs:sequence>
<xs:attribute ref="xml:lang"/>
</xs:complexType>
...
<xs:simpleType name="TimeCardDuration">
<xs:union memberTypes="xs:duration xs:decimal"/>
</xs:simpleType>
</xs:schema> |

 |

|
为 TimeCard 生成的代码
hrxml 目录中的 Ant build.xml 文件将定义尝试为 TimeCard 模式生成基本代码的 Ant 目标,包括默认生成和几个自定义示例(稍后讨论)。样例目录还包含一个测试程序 org.jibx.hrxml.Test。它将使用生成的数据模型类将样例文档解组,然后将文档重新编组并将结果与原始文档相比较。并且样例目录中有一组来自 HR-XML 发行版的测试文档。codegen 目标将使用默认值运行 CodeGen,compile 将编译生成的代码和测试代码,bind 将编译 JiBX 绑定,而 roundtrip 将对样例文档运行测试程序。您还可以使用 full 任务按顺序运行所有这些步骤。
大多数通过模式生成代码的方式都将为每个 complexType 定义及枚举 simpleType 生成一个单独的类。通过在可能的位置检查引用和内联定义,并且忽略包括和导入的模式定义中未使用的定义,CodeGen 通常能够减少生成的类的数量。在 TimeCard 模式中,有总计 10 个全局(命名的)complexType 和附加的 23 个本地(匿名)complexType,以及 8 个枚举 simpleType。生成的默认数据模型将包含 15 个顶级类和 23 个内部类,要比根据模式组件计算的少一些。您稍后将看到,如果不需要用到全部模式组件,如何使用自定义进一步简化数据模型。
<xs:choice> 处理
清单 8 显示了 CodeGen 如何处理 TimeCardType complexType 定义中的两个元素之间的选择。默认情况下,CodeGen 将使用一个选择变量来跟踪目前处于活动状态的选择。选择中包括的值的 set 方法将允许您写入目前选择的新值,但是不能直接更改选择(如果您尝试这样做,则抛出 IllegalStateException)。要在设定后更改目前的选择,首先需要调用一个清除方法(此处为 clearReportedResourceSelect()),该方法将重置选择状态。
清单 8. HR-XML TimeCard 生成的代码样例
/**
* Schema fragment(s) for this class:
* <pre>
* <xs:complexType xmlns:ns="http://ns.hr-xml.org/2007-04-15"
* xmlns:ns1="http://www.w3.org/XML/1998/namespace"
* xmlns:xs="http://www.w3.org/2001/XMLSchema" name="TimeCardType">
* <xs:sequence>
* <xs:element type="ns:EntityIdType" name="Id" minOccurs="0"/>
* <xs:element name="ReportedResource">
* <xs:complexType>
* <xs:choice>
* <xs:element type="ns:TimeCardPersonType" name="Person"/>
* <xs:element name="Resource">
* <!-- Reference to inner class Resource -->
* </xs:element>
* </xs:choice>
* </xs:complexType>
* </xs:element>
* ...
*/
public class TimeCardType
{
private EntityIdType id;
private int reportedResourceSelect = -1;
private final int REPORTED_RESOURCE_PERSON_CHOICE = 0;
private final int RESOURCE_CHOICE = 1;
private TimeCardPersonType reportedResourcePerson;
private Resource resource;
...
private void setReportedResourceSelect(int choice) {
if (reportedResourceSelect == -1) {
reportedResourceSelect = choice;
} else if (reportedResourceSelect != choice) {
throw new IllegalStateException(
"Need to call clearReportedResourceSelect() before changing existing choice");
}
}
/**
* Clear the choice selection.
*/
public void clearReportedResourceSelect() {
reportedResourceSelect = -1;
}
/**
* Check if ReportedResourcePerson is current selection for choice.
*
* @return <code>true</code> if selection, <code>false</code> if not
*/
public boolean ifReportedResourcePerson() {
return reportedResourceSelect == REPORTED_RESOURCE_PERSON_CHOICE;
}
/**
* Get the 'Person' element value.
*
* @return value
*/
public TimeCardPersonType getReportedResourcePerson() {
return reportedResourcePerson;
}
/**
* Set the 'Person' element value.
*
* @param reportedResourcePerson
*/
public void setReportedResourcePerson(
TimeCardPersonType reportedResourcePerson) {
setReportedResourceSelect(REPORTED_RESOURCE_PERSON_CHOICE);
this.reportedResourcePerson = reportedResourcePerson;
}
/**
* Check if Resource is current selection for choice.
*
* @return <code>true</code> if selection, <code>false</code> if not
*/
public boolean ifResource() {
return reportedResourceSelect == RESOURCE_CHOICE;
}
/**
* Get the 'Resource' element value.
*
* @return value
*/
public Resource getResource() {
return resource;
}
/**
* Set the 'Resource' element value.
*
* @param resource
*/
public void setResource(Resource resource) {
setReportedResourceSelect(RESOURCE_CHOICE);
this.resource = resource;
} |
对于大多数应用程序来说,这类选择处理工作得非常好,可以防止用户尝试在一个选择中设置多个备选值。不过,可以使用自定义修改默认选择处理,因此如果您不喜欢这种选择处理形式,您可以轻松地更改它。choice-check 属性将控制如何在生成的代码中检查 <xsd:choice> 的选择状态。choice-check="disable" 值将禁用所有检查并且不跟踪选择状态,让用户设置一个选择状态并且每个选择只有一个值。choice-check="checkset" 与 清单 8 中所示的默认处理相符,其中只有 set 方法将检查目前的设置并抛出异常。choice-check="checkboth" 还将在调用 get 方法时检查选择状态,如果 get 方法与目前的选择状态不符,则抛出异常。最后,choice-check="override" 把默认处理修改为在设置选择的任何值时始终更改目前的状态,而不是在之前设置其他状态时抛出异常。
choice-exposed 自定义属性将与 choice-check 设置结合使用,这些设置将跟踪目前的选择状态。choice-exposed="false" 值将选择状态常量、状态变量值和状态更改方法全部设置为私有,匹配 清单 8 中所示的默认代码生成。choice-exposed="true" 将为状态变量添加 get 方法,使得所有这些内容可以公开访问。这将允许您轻松地使用 Java switch 语句以根据目前的状态执行不同的代码,而不再需要使用多条 if 语句。
这两个属性可以在任意级别的自定义中使用,允许您为最外层自定义中所有生成的代码轻松地设置行为,同时仍然保留根据具体情况执行其他操作的能力。
<xs:any> 和 mixed="true" 处理
和许多企业模式一样,HR-XML 模式将使用 <xs:any> 模式组件为数据创建扩展点,这些扩展点可以独立于原始模式,由用户定义。默认情况下,CodeGen 将使用 org.w3c.dom.Element 对象(如果 <xs:any> 中的 maxOccurs 值大于 1,则使用 Element 列表)处理 <xs:any> 模式组件。Element 对象可用于表示任意一个 XML 元素(包括所有属性、名称空间声明和内容),因此它将提供处理任何匹配模式定义的文档所需的所有灵活性。
清单 9 显示了匹配 清单 7 模式样例的 <xs:any> 组件的生成代码。由于 <xs:any> 使用 maxOccurs="unbounded",因此生成的代码将使用一个 Element 列表。
清单 9. <xs:any> 生成的代码样例
/**
* ...
* Schema fragment(s) for this class:
* <pre>
* <xs:complexType xmlns:xs="http://www.w3.org/2001/XMLSchema" mixed="true"
* name="AdditionalDataType">
* <xs:sequence>
* <xs:any minOccurs="0" maxOccurs="unbounded" processContents="strict"
* namespace="##any"/>
* </xs:sequence>
* <xs:attribute type="xs:string" name="type"/>
* </xs:complexType>
* </pre>
*/
public class AdditionalDataType
{
private List<Element> anyList = new ArrayList<Element>();
private String type;
/**
* Get the list of sequence items.
*
* @return list
*/
public List<Element> getAny() {
return anyList;
}
/**
* Set the list of sequence items.
*
* @param list
*/
public void setAny(List<Element> list) {
anyList = list;
}
...
} |
清单 9 中模式定义的某些方面是被忽略的,或者只是由 CodeGen 处理了一部分。首先,封装的 <xs:complexType> 定义指定了 mixed="true",这意味着允许将字符数据与 <xs:any> 粒子所表示的元素相混合。CodeGen 所生成的数据模型没有空间来保存这类字符-数据内容,因此在文档被解组时,这些内容将被丢弃。其次,<xs:any> 将使用 processContents="strict",意味着实例文档中存在的所有元素都需要拥有自己的模式定义。CodeGen 将忽略此属性,尽管可能使用其他形式的 <xs:any> 处理(下面将讨论)得到类似的效果。CodeGen 还将忽略 <xs:any> 名称空间限制。清单 9 使用 namespace="##any",表示匹配 <xs:any> 的元素都不受名称空间的限制,但是举例来说,如果该值是 namespace="##other",则结果应当相同。
您可以在任意级别的自定义中使用 any-handling 自定义属性,选择处理 <xs:any> 的其他方式。值 any-handling="discard" 只忽略生成的数据模型中的 <xs:any>,并在出现解组时丢弃与 <xs:any> 对应的所有元素。any-handling="dom" 将匹配默认处理,使用 org.w3c.dom.Element 表示匹配 <xs:any> 的元素。最后,any-handling="mapped" 将生成代码,要求每个匹配 <xs:any> 的元素都有一个全局模式定义(大致对应于 processContents="strict" 模式条件)。在这最后一种情况中,数据模型将使用 java.lang.Object 表示元素,并且对象的实际运行时类型匹配全局模式定义。
<xs:simpleType> 处理
和大多数通过模式生成代码的方式一样,CodeGen 将忽略或者只部分处理 <xs:simpleType> 定义的许多方面。<xs:simpleType> 限制就是这类有限支持的一个例子。在模式所定义的各种 simpleType 限制(包括长度限制、值范围,甚至正则表达式模式)中,只有 <xs:enumeration> 限制目前是在生成的数据模型中强制执行的。
CodeGen 目前也忽略 <xs:simpleType> <union>。清单 10 显示了匹配 <xs:union> 引用的生成的代码,以及匹配代码的初始模式片段(位于清单底部)。您可以在清单 10 中看到,每个指向联合(union)类型(包括清单中所示的 TimeCardDuration 类型和 AnyDateTimeType)的引用在生成的代码中是用简单的 String 值表示的。
清单 10. <xs:union> 生成的代码样例及原始模式
/**
* Schema fragment(s) for this class:
* <pre>
* <xs:element xmlns:ns="http://ns.hr-xml.org/2007-04-15"
* xmlns:xs="http://www.w3.org/2001/XMLSchema" name="TimeInterval">
* <xs:complexType>
* <xs:sequence>
* <xs:element type="ns:EntityIdType" name="Id" minOccurs="0"/>
* <xs:element type="xs:string" name="StartDateTime"/>
* <xs:choice>
* <xs:sequence>
* <xs:element type="xs:string" name="EndDateTime"/>
* <xs:element type="xs:string" name="Duration" minOccurs="0"/>
* </xs:sequence>
* <xs:element type="xs:string" name="Duration"/>
* </xs:choice>
* ...
* </pre>
*/
public static class TimeInterval
{
private EntityIdType id;
private String startDateTime;
private int choiceSelect = -1;
private final int END_DATE_TIME_CHOICE = 0;
private final int DURATION_CHOICE = 1;
private String endDateTime;
private String duration;
private String duration1;
...
...
<xsd:element name="TimeInterval">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="Id" type="EntityIdType" minOccurs="0"/>
<xsd:element name="StartDateTime" type="AnyDateTimeType"/>
<xsd:choice>
<xsd:sequence>
<xsd:element name="EndDateTime" type="AnyDateTimeType"/>
<xsd:element name="Duration" type="TimeCardDuration" minOccurs="0"/>
</xsd:sequence>
<xsd:element name="Duration" type="TimeCardDuration"/>
</xsd:choice>
...
<xsd:simpleType name="TimeCardDuration">
<xsd:union memberTypes="xsd:duration xsd:decimal"/>
</xsd:simpleType>
|

 |

|
模式修改
如果将 清单 10 顶部的 Javadoc 中嵌入的模式片段与清单底部的实际模式片段相比较,您将看到初始模式中的 union simpleType 引用在 Javadoc 版本中已经替换为 xs:string 引用。这是故意的,并且它在 CodeGen 所执行的若干种模式结构转换中具有代表性。诸如删除 <union> simpleType 及 simpleType 限制(而非 <xs:enumeration>)之类的转换都是被硬编码到 CodeGen 操作中的。其他转换都受自定义控制。不管使用哪种方法,Javadocs 中包括的模式片段总是显示转换后的模式,因为实际上用于生成代码的就是转换后的模式。
您将在教程的后几节中看到受自定义控制的更多类型的转换。
|