Jaxb (XJC) генерация классов из XML Schema (XSD) с описаниями классов и полей в виде аннотаций. XJC плагин

    Думаю многие 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/, нашёл несколько вопросов, подобного рода, например:


    1. Using JAXB to handle schema annotations.
    2. How to make generated classes contain Javadoc from XML Schema documentation
    3. How can I generate a class from which I can retrieve the XML of a node as a String

    и ни одного полного ответа, в некоторых за много лет!


    Зато, возвращаясь к теме возможностей, оказалось что имеется API для написания плагинов! Низкоуровневое, местами запутанное, почти без документации… Но могу сказать при это весьма продвинутое, то есть вмешаться можно прямо много в какие процессы. Кстати в ответах на него часто ссылаются, для многих нестандартных случаев. Ну я и попробовал написать плагин.


    Для тех, кому интересны подробности процесса написания своих плагинов, могу порекомендовать статьи:



    Что хотел и зачем


    Для одной из наших интеграций заказчик предоставил архив с XSD файлами, по которым мы должны были сгенерировать модель в MDM Unidata, с которой мы работаем, для дальнейшей настройки правил качества.


    Отмечу что сама MDM система проприетарная, вряд ли многие с ней знакомы, но это не имеет особого значения. Суть в том что мы должны были создать по XSD описаниям справочники и реестры. При этом у реестров есть поля, в которых используется как идентификатор (имя поля), так и "отображаемое имя" — название, понятное человеку, на русском. Простую аналогию можно провести (и это вероятно также полезный пример для применения) с РСУБД в терминах которой можно считать что мы хотели сделать таблицы БД, но при этом дать описания (comment) для каждого столбца для последующего использования.

    Мой план был такой:


    1. Генерируем классы по XSD с помощью XJC
    2. Затем берём прекрасную библиотеку 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>
     * &lt;complexType name="Customer"&gt;
     *   &lt;complexContent&gt;
     *     &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&gt;
     *       &lt;sequence&gt;
     *         &lt;element name="name" type="{http://www.w3.org/2001/XMLSchema}string"/&gt;
     *         &lt;element name="age" type="{http://www.w3.org/2001/XMLSchema}positiveInteger"/&gt;
     *       &lt;/sequence&gt;
     *     &lt;/restriction&gt;
     *   &lt;/complexContent&gt;
     * &lt;/complexType&gt;
     * </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());

    Рабочий пример есть в тестах: 1, 2.

    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 5

      +1
      Интересное решение! Как кстати решаете проблему отсутствия jaxb классов в jdk11?
        +1

        Так а нет проблемы!
        Хотя в демо-проекте и указано sourceCompatibility = 1.8 это лишь чтобы не поднимать требования напрасно.


        Т.к. явно указаны зависимости как API, таки имплементации, нет никаких проблем. Тесты нормально проходят под openjdk 11: image


        Ворнинги от groovy это лишь запуск тестов, они могут быть спокойно проигнорированы, подробнее в его багтрекере баг GROOVY-8339, то есть это никак не относится к плагину и Jaxb.

          +1
          Спасибо за наводку, кстати! По версиям очень похоже на glassfish jaxb. Надо будет выяснить, являются ли эти библиотеки GPL 1.1 именно GPL with classpath exception. Так как мне боязно включать без этого пункта в рабочие проекты зависимости с GPL…
            +1
            Если честно, не совсем понял. На сколько я вижу обе под одинаковыми лицензиями. Или есть разница? Если вам это правда нужно, я могу попробовать адаптировать на использование других имплементаций. Тогда хотелось бы подробнее понять потребность. Но это, полагаю, уже лучше в issue.
              0
              Спасибо, не стоит менять имплементации, так как всегда можно сделать exclude из зависимостей. Просто с правовыми аспектами миграции JAXB в jdk11 я до сих пор не разобрался, думал может вы в курсе.

      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

      Самое читаемое