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