Не удается выполнить определение типов из импортированной схемы в XJC

У меня есть этот API, использующий JAXB для удобного использования объектных моделей, сгенерированных из XML-схем компилятором XJC (XML-to-Java) через именованные ссылки. Он абстрагируется от создания 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), который вы можете увидеть в приведенном выше извлечении POM. Его содержимое:

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-plugin вместо 21. Без разницы.

Оглядываясь в Интернете, кажется, что люди сталкивались с этой проблемой, по крайней мере, с 2006 года, и это может быть связано с некоторыми проблемами решателя Xerces. Я надеюсь, что это не та ошибка, которая скрывалась в течение 6 лет, и никто не заботился об этом. У кого-то еще есть предложения? Может быть, кто-то столкнулся с той же проблемой и нашел решение? Единственный обходной путь, о котором я могу подумать, - это использовать svn: externals, чтобы перетащить общую схему в подпроект и просто восстановить там классы, но это грязно и будет работать только тогда, когда вы сможете подключиться к нашему репозиторию svn.

Большое спасибо заранее за чтение этого длинного поста. Помните, что я взял все вышеперечисленное из существующих проектов и заменил некоторые пространства имен и другие элементы на анонимность, поэтому возможны некоторые опечатки.

2 ответа

Решение

Этот ответ был отредактирован. Раньше у меня было решение с использованием пользовательского преобразователя каталога. Тем не менее, я нашел актуальную проблему сейчас. Объяснение следующее. Для версии TL;DR, которая предоставляет решение, прокрутите до конца этого ответа.


Проблема с файлом каталога. Обратите внимание, как появилась эта строка:

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

Что это говорит? Это говорит о том, что если публичный ID http://www.foobar.com/general встречается, системный идентификатор для схемы classpath:/com/foobar/schemas/general.xsd, Все идет нормально. Если мы возьмем schemaLocation атрибут из нашего <xs:import /> элементы, единственное, что остается, это открытый идентификатор (пространство имен URN), и файл каталога сообщает нам, где найти схему для него.

Проблема возникает, когда эта схема затем использует <xs:include /> элементы. Они включают файлы схемы с тем же целевым пространством имен. Они указывают системный идентификатор (относительное местоположение). Таким образом, вы ожидаете, что это будет использоваться для разрешения. Однако запись вызовов в распознаватель каталогов показывает, что запросы на разрешение выполняются как с общедоступным идентификатором (пространство имен), так и с идентификатором системы (относительное местоположение). И вот где это идет не так. Общедоступному идентификатору отдается предпочтение из-за привязки в файле каталога. И это приводит нас прямо к 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. Проверяет импорт, не находит системный идентификатор, только открытый идентификатор (http://www.foobar.com/general).
  4. Проверяет каталог (ы).
  5. Находит привязку публичного идентификатора к classpath:/com/foobar/schemas/general.xsd,
  6. Разбор определений в импортированной схеме.
  7. Обнаруживает ссылку на определение из включенной схемы (simple-types.xsd).
  8. Проверки включают, находит идентификатор системы.
  9. Проверяет каталог (ы) на наличие системного идентификатора, но открытый идентификатор неявный.
  10. Находит привязку публичного идентификатора к classpath:/com/foobar/schemas/general.xsd, который имеет преимущество перед идентификатором системы.
  11. Не удается разрешить включенные определения схемы.

Подробная информация о порядке, в котором делается попытка разрешения, описана в спецификации OASIS для каталогов XML: https://www.oasis-open.org/committees/entity/spec.html. Это требует некоторой интерпретации, но вы обнаружите, что если предпочтительным методом разрешения являются публичные идентификаторы, они будут иметь приоритет при привязке в файле каталога, даже если есть системный идентификатор.

Решение, таким образом, состоит в том, чтобы указать, что системные идентификаторы являются предпочтительным методом разрешения, а не предоставлять системные идентификаторы в импорте, чтобы использовалась привязка общедоступных идентификаторов каталога и полагаться на относительные системные идентификаторы из включений. В формате каталога OASIS XML вы можете использовать атрибут prefer="system", В формате каталога OASIS TR9401 вы можете использовать OVERRIDE no, Видимо по умолчанию это public/yes.

Таким образом, мой файл каталога становится:

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

Теперь обычный резолвер каталога работает нормально. Мне больше не нужен пользовательский. Однако я бы не догадался, что открытый идентификатор все еще используется для разрешения при включении схем и имеет приоритет над системным идентификатором. Я бы подумал, что общедоступный идентификатор будет использоваться только для импорта, и что системный идентификатор все равно будет учитываться в случае неудачного разрешения. Только добавление некоторой регистрации к пользовательскому распознавателю показало это.


Краткий ответ: добавить 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

Использование 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

Другие вопросы по тегам