Разрешение определения типов из импортированной схемы в 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>

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

Пока все хорошо. Этот проект компилируется без сучка и задоринки. Следует отметить, что помимо генерируемых 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.

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

Ответы

Ответ 1

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


Проблема заключается в файле каталога. Обратите внимание, как у этой строки была следующая строка:

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

Что это говорит? В нем говорится, что если встречается открытый идентификатор 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 анализирует эту последнюю схему, это происходит:

  • Разбор локальных определений.
  • Ссылка на определение из импортированной схемы.
  • Проверяет импорт, не находит идентификатор системы, только открытый идентификатор (http://www.foobar.com/general).
  • Проверяет каталог (ы).
  • Находит привязку открытого идентификатора к classpath:/com/foobar/schemas/general.xsd.
  • Определения парсинга в импортированной схеме.
  • Ссылка на определение из включенной схемы (simple-types.xsd).
  • Проверки включают, находит идентификатор системы.
  • Проверяет каталог для идентификатора системы, но публичный идентификатор неявный.
  • Находит привязку открытого идентификатора к classpath:/com/foobar/schemas/general.xsd, который предпочитает идентификатор системы.
  • Разрешение включенных определений схемы не выполняется.

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

Таким образом, решение состоит в том, чтобы указать, что системные идентификаторы являются предпочтительным методом разрешения, а не содержат идентификаторы системы в импорте, так что используется привязка к общедоступному идентификатору каталога и полагаются на относительные идентификаторы системы из включений. В формате каталога 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#UserGuide-Usingcatalogs

Ответ 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