Не удается выполнить определение типов из импортированной схемы в 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 анализирует эту последнюю схему, это происходит:
- Разбор локальных определений.
- Обнаруживает ссылку на определение из импортированной схемы.
- Проверяет импорт, не находит системный идентификатор, только открытый идентификатор (
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. Это требует некоторой интерпретации, но вы обнаружите, что если предпочтительным методом разрешения являются публичные идентификаторы, они будут иметь приоритет при привязке в файле каталога, даже если есть системный идентификатор.
Решение, таким образом, состоит в том, чтобы указать, что системные идентификаторы являются предпочтительным методом разрешения, а не предоставлять системные идентификаторы в импорте, чтобы использовалась привязка общедоступных идентификаторов каталога и полагаться на относительные системные идентификаторы из включений. В формате каталога 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