Думаю многие Java-разработчики которые хоть раз сталкивались с Web-сервисами
, использовали генерацию Java
DTO
классов по описанию XML Schema
(XSD
). Jaxb с этим справляется на ура, не важно как его использовать, через xjc
или wsimport
вызов из командной строки, maven или gradle плагины.
Так быстро и просто сгенерировать классы из XSD
схемы. Но вот одна проблема — практически полностью пропадают описания, имевшиеся в исходной схеме!
Практически, потому что Javadoc
описание будет только у самого класса, в фиксированном формате (где не разделить описание и фрагмент XML
без регулярок скажем), описание полей (филдов) отсутствуют полностью. А если вам они, как мне, нужны ещё и во время выполнения (runtime
) — тут совсем беда.
Именно с этим, пришлось побороться, как ни странно, задача заняла много времени, и в результате я написал плагин, который и хотел бы представить в надежде что он может кому-то сэкономить несколько часов в будущем.
Краткий обзор возможностей Jaxb
Jaxb имеет большую историю и неплохое официальное описание, в том числе по добавлению поведения в генерируемые классы.
Основной инструмент для вызова генерации классов из командной строки — xjc также имеет не самое маленькое количество ключей. Впрочем ни один из них, не для нашего случая.
Конечно нельзя не упомянуть про -b
, где можно предоставить свой биндинг. И это весьма мощное оружие, особенно в купе с множественными плагинами. Весьма неплохой блог пост (на английском) — рекомендую к прочтению как краткое введение. Но биндинг в большинстве своём ограничен статическими значениями присваемых аннтотаций или задаваемых имён, с указанием элементов, к которым он применяется. В моей проблеме это не помогает.
Jaxb (XJC) плагины
Пока искал готовое решение, нашёл множество плагинов, которые расширяют генерацию. Полагаю их обзор выходит за рамки данного поста. Что важно, делающего то что нужно именно мне, я так и не нашёл.
Но зато, пока читал ответы и вопросы на http://stackoverflow.com/, нашёл несколько вопросов, подобного рода, например:
- Using JAXB to handle schema annotations.
- How to make generated classes contain Javadoc from XML Schema documentation
- How can I generate a class from which I can retrieve the XML of a node as a String
и ни одного полного ответа, в некоторых за много лет!
Зато, возвращаясь к теме возможностей, оказалось что имеется API
для написания плагинов! Низкоуровневое, местами запутанное, почти без документации… Но могу сказать при это весьма продвинутое, то есть вмешаться можно прямо много в какие процессы. Кстати в ответах на него часто ссылаются, для многих нестандартных случаев. Ну я и попробовал написать плагин.
Для тех, кому интересны подробности процесса написания своих плагинов, могу порекомендовать статьи:
- Creating an XJC plugin
- How to Implement Your Own XJC Plugin to Generate toString(), equals(), and hashCode() Methods
- StackOverflow очень подробные ответы:
Что хотел и зачем
Для одной из наших интеграций заказчик предоставил архив с XSD
файлами, по которым мы должны были сгенерировать модель в MDM Unidata, с которой мы работаем, для дальнейшей настройки правил качества.
Отмечу что самаMDM
система проприетарная, вряд ли многие с ней знакомы, но это не имеет особого значения. Суть в том что мы должны были создать поXSD
описаниям справочники и реестры. При этом у реестров есть поля, в которых используется как идентификатор (имя поля), так и "отображаемое имя" — название, понятное человеку, на русском. Простую аналогию можно провести (и это вероятно также полезный пример для применения) с РСУБД в терминах которой можно считать что мы хотели сделать таблицы БД, но при этом дать описания (comment
) для каждого столбца для последующего использования.
Мой план был такой:
- Генерируем классы по
XSD
с помощьюXJC
- Затем берём прекрасную библиотеку reflections и итерируем по всем объектам, рекурсивно спускаясь по полям.
Всё быстро заработало для латинских имён, которые брались из филдов (field
) классов. А вот русское, человекопонятное описание было взять неоткуда!
Править описания вручную — не вариант, потому что вложенных полей десятки тысяч.
Первой попыткой было написать подобный парсер на Groovy
самостоятельно, выдирая описания из XSD
. И в общем он был реализован. Но быстро выяснилось что имеется множество случаев, где требуется дополнительная обработка — рекурсивные вызовы, поддержка расширений XSD
1.1 в виде restriction
/extension
(с поддержкой наследования и переопределения), разные типы из которых генерируются поля классов вроде <element>
и <attribute>
, <sequence>
, <choose>
и много других мелочей. Реализация обрастала дополнениями, а стройности ей не прибавлялось.
В итоге я вернулся к мысли написать плагин xjc-documentation-annotation-plugin, который вам и представляю, в надежде что он будет кому-то полезен кроме меня!
xjc-documentation-annotation-plugin
Всё выложено на гитхабе: https://github.com/Hubbitus/xjc-documentation-annotation-plugin
Там же есть инструкции, тесты и отдельный демо-проект для gradle
с примером использования.
Полагаю нет смысла копировать сюда описание оттуда, просто вкратце покажу что именно он делает.
Скажем есть такой XSD
фрагмент:
<xs:complexType name="Customer">
<xs:annotation>
<xs:documentation>Пользователь</xs:documentation>
</xs:annotation>
<xs:sequence>
<xs:element name="name" type="xs:string">
<xs:annotation>
<xs:documentation>Фамилия и имя</xs:documentation>
</xs:annotation>
</xs:element>
</xs:sequence>
</xs:complexType>
По умолчанию XJC
сгенерит из него класс вида (геттеры, сеттеры и часть не имеющих к делу мелочей опущены для читаемости):
/**
* Пользователь
*
* <p>Java class for Customer complex type.
*
* <p>The following schema fragment specifies the expected content contained within this class.
*
* <pre>
* <complexType name="Customer">
* <complexContent>
* <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
* <sequence>
* <element name="name" type="{http://www.w3.org/2001/XMLSchema}string"/>
* <element name="age" type="{http://www.w3.org/2001/XMLSchema}positiveInteger"/>
* </sequence>
* </restriction>
* </complexContent>
* </complexType>
* </pre>
*
*
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "Customer", propOrder = {
"name",
"age"
})
public class Customer {
@XmlElement(required = true)
protected String name;
@XmlElement(required = true)
@XmlSchemaType(name = "positiveInteger")
protected BigInteger age;
}
С плагином же вы получите (Javadoc
остался тот же, опустим для простоты):
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "Customer", propOrder = {
"name",
"age"
})
@XsdInfo(name = "Пользователь", xsdElementPart = "<complexType name=\"Customer\">\n <complexContent>\n <restriction base=\"{http://www.w3.org/2001/XMLSchema}anyType\">\n <sequence>\n <element name=\"name\" type=\"{http://www.w3.org/2001/XMLSchema}string\"/>\n <element name=\"age\" type=\"{http://www.w3.org/2001/XMLSchema}positiveInteger\"/>\n </sequence>\n </restriction>\n </complexContent>\n</complexType>")
public class Customer {
@XmlElement(required = true)
@XsdInfo(name = "Фамилия и имя")
protected String name;
@XmlElement(required = true)
@XmlSchemaType(name = "positiveInteger")
@XsdInfo(name = "Возраст")
protected BigInteger age;
}
Обратите внимание на добавленные аннотации @XmlType
.
Использовать это затем просто, как любую другую аннотацию:
XsdInfo xsdAnnotation = CadastralBlock.class.getDeclaredAnnotation(XsdInfo.class);
System.out.println("XSD description: " + xsdAnnotation.name());