16

我有这个 API 使用 JAXB 方便地使用对象模型,由 XJC(XML 到 Java)编译器通过命名引用从 XML 模式生成。它通过各种背景魔法和反射抽象了 JAXB 上下文的创建和查找 ObjectFactory 方法。它的基本要点是您总是定义一个通用模式,然后任何数量(也可能是 0)的模式“扩展”该通用模式,每个模式都会产生自己的数据模型。通用模式带有可重用的定义,扩展它的那些使用这些定义来组成自己的模型。

我现在遇到了一种情况,我想为多个项目重用通用模式。通用类型定义在项目之间应该保持相同,并且将针对从这些生成的抽象类构建一些代码。所以我需要首先为一些通用模式生成类,然后生成那些扩展和单独使用它们的类。我在构建过程中使用 Maven。

我遇到的问题是从扩展模式中的通用模式解析类型定义。

假设我的通用架构名为“general.xsd”,如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.foobar.com/general"
xmlns:gen="http://www.foobar.com/general"
elementFormDefault="qualified" attributeFormDefault="qualified">

    <!-- Element (will usually be root) -->
    <xs:element name="transmission" type="gen:Transmission" />

    <!-- Definition -->
    <xs:complexType name="Transmission" abstract="true">
        <xs:sequence>
            <!-- Generic parts of a transmission would be in here... -->
        </xs:sequence>
    </xs:complexType>

</xs:schema>

旁边有一个绑定文件来进行一些命名自定义并设置输出的包名称:

<?xml version="1.0" encoding="UTF-8"?>
<bindings xmlns="http://java.sun.com/xml/ns/jaxb" 
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/jaxb http://java.sun.com/xml/ns/jaxb/bindingschema_2_0.xsd"
    version="2.1">

    <!-- Bindings for the general schema -->
    <bindings schemaLocation="general.xsd" node="/xs:schema">

        <schemaBindings>
            <package name="com.foobar.models.general"/>
        </schemaBindings>

        <bindings node="//xs:complexType[@name='Transmission']">
            <!-- Some customization of property names here... -->
        </bindings>

</bindings>

然后,我将在该项目的 POM 中使用下一点来生成 Java 类:

<plugin>
    <groupId>org.jvnet.jaxb2.maven2</groupId>
    <artifactId>maven-jaxb21-plugin</artifactId>
    <version>0.8.0</version>
    <executions>
        <execution>
            <id>xjc-generate</id>
            <goals>
                <goal>generate</goal>
            </goals>
            <configuration>
                <schemaDirectory>${basedir}/src/main/resources/com/foobar/schemas</schemaDirectory>
                <schemaLanguage>XMLSCHEMA</schemaLanguage>
                <addCompileSourceRoot>true</addCompileSourceRoot>
                <episode>true</episode>
                <removeOldOutput>true</removeOldOutput>
            </configuration>
        </execution>
    </executions>
</plugin>

如您所见,我使用的是 JAXB2.1 Maven 插件。我已将选项设置为生成用于逐步编译的剧集文件。删除先前输出的选项是为了解决错误;它所做的只是确保首先清理所有内容,以便强制重新编译。

到目前为止,一切都很好。该项目编译顺利。需要注意的是,除了生成的 Java 类之外,我还将模式打包到生成的 jar 文件中。所以这些在类路径上可用!该sun-jaxb.episode文件应该在 META-INF 中。

然后我开始使用将扩展上述模式的项目,首先导入它。其中一种“子类型”可能如下所示(我将其称为 sub.xsd):

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.foobar.com/sub"
xmlns:sub="http://www.foobar.com/sub"
xmlns:gen="http://www.foobar.com/general"
elementFormDefault="qualified" attributeFormDefault="qualified">

    <xs:import namespace="http://www.foobar.com/general" />

    <!-- Definition -->
    <xs:complexType name="SubTransmission">
        <xs:complexContent>
            <xs:extension base="gen:Transmission">
                <xs:sequence>
                    <!-- Additional elements placed here... -->
                </xs:sequence>
            </xs:extension>
        </xs:complexContent>
    </xs:complexType>

</xs:schema>

同样,有一个绑定文件:

<?xml version="1.0" encoding="UTF-8"?>
<bindings xmlns="http://java.sun.com/xml/ns/jaxb" 
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/jaxb http://java.sun.com/xml/ns/jaxb/bindingschema_2_0.xsd"
    version="2.1">

    <!-- Bindings for sub type -->
    <bindings schemaLocation="sub.xsd" node="/xs:schema">

        <schemaBindings>
            <package name="com.foobar.models.sub"/>
        </schemaBindings>

    </bindings>

</bindings>

以下是该项目的 POM 中处理 XJC 生成的部分内容:

<plugin>
    <groupId>org.jvnet.jaxb2.maven2</groupId>
    <artifactId>maven-jaxb21-plugin</artifactId>
    <version>0.8.0</version>
    <executions>
        <execution>
            <id>xjc-generate</id>
            <goals>
                <goal>generate</goal>
            </goals>
            <configuration>
                <schemaDirectory>${basedir}/src/main/resources/com/foobar/schemas</schemaDirectory>
                <schemaLanguage>XMLSCHEMA</schemaLanguage>
                <addCompileSourceRoot>true</addCompileSourceRoot>
                <episode>false</episode>
                <catalog>${basedir}/src/main/resources/com/foobar/schemas/catalog.cat</catalog>
                <episodes>
                    <episode>
                        <groupId>com.foobar</groupId>
                        <artifactId>foobar-general-models</artifactId>
                        <version>1.0.0-SNAPSHOT</version>
                        <scope>compile</scope>
                    </episode>
                </episodes>
                <removeOldOutput>true</removeOldOutput>
            </configuration>
        </execution>
    </executions>
</plugin>

最初,所有模式都在一个文件夹中,我schemaLocation将导入中的属性设置为general.xsd,效果很好。但是现在事情是跨项目分开的,我遇到了问题。第一个问题是找不到其他模式。我已通过从元素中取出属性schemaLocation<xs:import />仅保留namespace属性并添加目录文件 ( catalog.cat它的内容是:

PUBLIC "http://www.foobar.com/general" "classpath:/com/foobar/schemas/general.xsd"

这似乎有效,因为我不再收到指出无法找到架构的错误。但由于某种原因,从导入的模式解析实际类型定义继续失败。这是一个例外:

Error while parsing schema(s).Location [ file:/C:/NetBeans_groups/Test/SubModelBundle/src/main/resources/com/foobar/schemas/sub.xsd{...,...}].
org.xml.sax.SAXParseException: src-resolve: Cannot resolve the name 'gen:Transmission' to a(n) 'type definition' component.

这是我到目前为止所尝试的:

  • 使用目录文件。部分成功,因为现在可以找到导入的模式。
  • 让通用模式的编译生成一个剧集文件,并将其用于子模式的编译。似乎没有什么区别,虽然这应该只在类型被解析后才起作用,所以我认为这还不重要。
  • 使用不同的 JAXP(注意:不是JAXB,JAXP)实现。它确实使用了不同的,因为我可以在异常的堆栈跟踪中看到它,但最终结果是相同的。
  • 使用maven-jaxb22-plugin21 代替。没有区别。

在网上环顾四周,似乎人们至少从 2006 年开始就遇到了这个问题,这可能与一些 Xerces 解析器问题有关。我希望这不是一个已经潜伏了 6 年而没有人愿意修复它的错误。其他人有什么建议吗?也许有人遇到了同样的问题并找到了解决方案?我能想到的唯一解决方法是使用 'svn:externals' 将通用模式拖到子项目中并在那里重新生成类,但它很脏,只有当你可以连接到我们的 svn 存储库时才会起作用。

非常感谢您阅读这篇长文。请记住,我已经从现有项目中获取了以上所有内容,并替换了一些命名空间和其他匿名内容,因此可能会出现一些拼写错误。

4

2 回答 2

4

此答案已编辑。之前,我有一个使用自定义目录解析器的解决方案。但是,我现在发现了实际问题。解释如下。对于提供解决方案的 TL;DR 版本,请滚动到此答案的底部。


问题出在目录文件上。注意它是如何有这条线的:

PUBLIC "http://www.foobar.com/general" "classpath:/com/foobar/schemas/general.xsd"

那说明什么?它表示如果http://www.foobar.com/general遇到公共 ID,则架构的系统 ID 为classpath:/com/foobar/schemas/general.xsd. 到目前为止,一切都很好。如果我们从元素中取出schemaLocation属性<xs:import />,唯一剩下的就是公共 ID(命名空间 URN),并且目录文件告诉我们在哪里可以找到它的模式。

当该架构随后使用<xs:include />元素时,就会出现问题。它们包括具有相同目标命名空间的模式文件。它们指定系统 ID(相对位置)。因此,您希望将其用于分辨率。但是,将调用记录到目录解析器会显示请求是使用公共 ID(命名空间)和系统 ID(相对位置)进行解析的。这就是它出错的地方。由于目录文件中的绑定,优先使用公共 ID。这使我们general.xsd再次直接进入文件。

例如,一般模式如下:

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.foobar.com/general"
xmlns:gen="http://www.foobar.com/general"
elementFormDefault="qualified" attributeFormDefault="qualified">

    <!-- Including some definitions from another schema in the same location -->
    <xs:include schemaLocation="simple-types.xsd" />

    <!-- Remaining stuff... -->

</xs:schema>

并且使用该模式的模式如下:

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.foobar.com/sub"
xmlns:sub="http://www.foobar.com/sub"
xmlns:gen="http://www.foobar.com/general"
elementFormDefault="qualified" attributeFormDefault="qualified">

    <xs:import namespace="http://www.foobar.com/general" />

    <!-- Remaining stuff... -->

</xs:schema>

当 XJC 解析最后一个模式时,会发生这种情况:

  1. 解析本地定义。
  2. 遇到对导入模式定义的引用。
  3. 检查导入,没有找到系统 ID,只有公共 ID ( http://www.foobar.com/general)。
  4. 检查目录。
  5. 查找公共 ID 与 的绑定classpath:/com/foobar/schemas/general.xsd
  6. 解析导入模式中的定义。
  7. 遇到对包含架构 (simple-types.xsd) 的定义的引用。
  8. 检查包括,查找系统 ID。
  9. 检查系统 ID 的目录,但公共 ID 是隐式的。
  10. 查找公共 ID 与 的绑定classpath:/com/foobar/schemas/general.xsd,它优先于系统 ID。
  11. 包含的架构定义的解析失败。

OASIS XML 目录规范中描述了尝试解析的顺序的详细信息:https ://www.oasis-open.org/committees/entity/spec.html#s.ext.ent 。这需要一些解释,但您会发现,如果首选的解析方法是公共 ID,那么即使有系统 ID,在目录文件中绑定时,公共 ID 也会优先。

因此,解决方案是指定系统 ID 是首选的解析方法,而不是在导入中提供系统 ID,以便使用目录的公共 ID 绑定并依赖于包含的相关系统 ID。在 OASIS XML 目录格式中,您可以使用属性prefer="system"。在 OASIS TR9401 目录格式中,您可以使用OVERRIDE no. 显然默认值是公开/是。

所以我的目录文件就变成了:

OVERRIDE no
PUBLIC "http://www.foobar.com/general" "classpath:/com/foobar/schemas/general.xsd"

现在常规目录解析器工作正常。我不再需要定制的了。但是,我不会猜到公共 ID 在包含模式时仍用于解析,并且优先于系统 ID。我原以为公共 ID 只会用于导入,如果解析失败,系统 ID 仍会被考虑。只有在自定义解析器中添加一些日志才能显示这一点。


简短的回答:添加OVERRIDE no为 TR9401 目录文件中的第一个指令,或prefer="system"添加到 XML 目录文件中。不要schemaLocation<xs:import />指令中指定,而是将命名空间绑定到目录文件中的正确模式位置。确保<xs:include />使用包含架构的相对路径。

另一个有趣的事情是:XJC 使用的目录解析器不仅可以处理classpath:URI,还可以处理maven:URI,这与 Maven 工件相关。如果您将其用作构建工具,则非常有用。 http://confluence.highsource.org/display/MJIIP/User+Guide#UserGuide-Usingcatalogs

于 2012-10-17T13:12:42.273 回答
2

使用Maven 2.2.1对我有用 org.jvnet.jaxb2.maven2.resolver.tools.ClasspathCatalogResolver

这是一个示例配置:

<plugin>
    <groupId>org.jvnet.jaxb2.maven2</groupId>
    <artifactId>maven-jaxb2-plugin</artifactId>
    <version>0.8.0</version>
    <executions>
        <execution>
            <id>executionId</id>
            <goals>
                <goal>generate</goal>
            </goals>
            <configuration>
                <schemaDirectory>src/main/resources/META-INF/schemas</schemaDirectory>
                <generatePackage>com.company.project.data</generatePackage>
                <bindingDirectory>src/main/jaxb</bindingDirectory>
                <catalog>src/main/jaxb/catalog.cat</catalog>
                <catalogResolver>org.jvnet.jaxb2.maven2.resolver.tools.ClasspathCatalogResolver</catalogResolver>
                <verbose>false</verbose>
                <extension>true</extension>
                <episodes>
                    <episode>
                        <groupId>com.company.project</groupId>
                        <artifactId>xsd-common-types</artifactId>
                        <version>${xsd-common-types.version}</version>
                    </episode>
                </episodes>
            </configuration>
        </execution>
    </executions>
    <dependencies>
        <dependency>
            <groupId>com.company.project</groupId>
            <artifactId>xsd-common-types</artifactId>
            <version>${xsd-common-types.version}</version>
        </dependency>
    </dependencies>
</plugin>

使此配置与 Maven 3 一起使用会导致 org.xml.sax.SAXParseException

于 2013-02-27T04:15:13.447 回答