четверг, 15 апреля 2010 г.

CXF: Шаг 0 - XSD to Java

Почему шаг 0?

Почему шаг нулевой: на мой взгляд этот шаг имеет нулевую пользу и если это возможно, нужно его пропустить. Почему?
  • Мы в этот файл вкладываем информацию о типах данных, которыми будем оперировать в интерфейсах. При преобразовании XSD to Java эта информация изменится (возможно довольно сильно), при преобразовании Java to WSDL получившаяся измененная информация еще больше исказится.
  • Все комментарии, разметка и прочая вспомогательная информация, заложенная в XSD, уйдет "в никуда".
  • Ну и куча "мелочей", от лишних 10 сек при компиляции до необходимости хранить знания об интерфейсе в двух раздельным местах на двух разных языках.
Если вышенаписанное не убедило отказаться от написания XSD или по другому не получается, то вот несколько рецептов "от головной боли":

1. Избегаем скользких моментов

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

1.1. Не используем встроенные типы

<xsd:element name="test-el">
  <xsd:complexType>
    <!-- type declaration -->
  </xsd:complexType>
</xsd:element>
Вот такие объявления могут сыграть очень плохую службу (в плоть до невалидного WSDL). Я бы посоветовал описывать только типы (не использовать xsd:element). Но уж если очень хочется, то делать это вот так:
<xsd:element name="test-el" type="test-el-type"; />
<xsd:complexType name="test-el-type">
  <!-- type declaration -->
</xsd:complexType>

1.2. Даем осмысленные имена XML namespase-ам

Все очень просто: targetNamespace превращается в имя java package – если написали targetNamespace="test", то сгенеренные классы будут лежать в не очень удобном месте. Конечно же, есть способы указать имя пакета, но это же лишние телодвижения...

1.3. Избегаем xsd:choise

<xsd:complexType name="result-type">
  <xsd:choice>
    <xsd:element name="long-num" type="xsd:long" />
    <xsd:element name="int-num" type="xsd:int" />
    <xsd:element name="string" type="xsd:string" />
  </xsd:choice>
</xsd:complexType>

Вот с такими конструкциями в CXF практически невозможно работать. "XSD to Java" преобразование происходит, ну скажем, так "сносно"
А вот получить такой же xsd:choise в результирующем WSDL я так и не смог: (

2. XJC или кастомизация генеренного кода

Если пункты 1.X были выполнены, то особых проблем возникнуть не должно. Но все же... есть пару задач где без кастомизации не обойтись. Вот пример простейшего фала:
<?xml version="1.0" encoding="UTF-8"?>
<jaxb:bindings 
  xmlns:xsd="http://www.w3.org/2001/XMLSchema"  
  xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
  xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"   
  xsd:schemaLocation="http://java.sun.com/xml/ns/jaxb http://java.sun.com/xml/ns/jaxb/bindingschema_2_0.xsd"
  jaxb:extensionBindingPrefixes="xjc" 
  version="2.0">

  <jaxb:globalBindings>
    <jaxb:serializable />
  </jaxb:globalBindings>

</jaxb:bindings>

2.1. XSD для файлов кастомизации (для редакторов с автоподсказкой)

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

2.2. implements Serializable

Для того, чтобы созданные классы были сериализуемыми, можно добавить в файл биндинга вот такую секцию:
<jaxb:globalBindings>
  <jaxb:serializable />
</jaxb:globalBindings>

2.3. Определение суперкласса

Для корректной имплементации hashCode(), equals() и др. штук, существуют магические плагины к xjc генератору, но:
- мне не удалось подключить их плагину org.codehaus.mojo: jaxb2-maven-plugin;
- иногда нужно/можно ввести суперкласс для классов доменной модели.
Поэтому вот такая запись в файле кастомизации может оказаться очень полезной:
<jaxb:globalBindings>
  <xjc:superClass name="org.test.base.BaseBean" />
</jaxb:globalBindings>

2.4. Избавляемся от XMLGregorianCalendar

Поля типа xsd:dateTime и ему подобные преобразуются в поля типа:
XMLGregorianCalendar. Наверное это замечательный класс, но я привык пользоваться java.util.Calendar. Для преодоления этого недоразумения можно, опять же, в глобальную секцию, вставить вот такие строки.
<jaxb:globalBindings>
  <jaxb:javaType
    name="java.util.Calendar"
    xmlType="xsd:dateTime" 
    parseMethod="javax.xml.bind.DatatypeConverter.parseDate"
    printMethod="javax.xml.bind.DatatypeConverter.printDate" />
</jaxb:globalBindings>
При генерации будет создаваться класс-конвертор, примерно такого содержания:
public class Adapter1 extends XmlAdapter<String, Calendar> {
  public Calendar unmarshal(String value) {
    return (javax.xml.bind.DatatypeConverter.parseDate(value));
  }
  public String marshal(Calendar value) {
    if (value == null) {
      return null;
    }
    return (javax.xml.bind.DatatypeConverter.printDate(value));
  }
}
А XSD объявление:
<xsd:element name="date" type="xsd:dateTime" />
Превращается в поле:
@XmlElement(required = true, type = String.class)
  @XmlJavaTypeAdapter(Adapter1 .class)
  @XmlSchemaType(name = "dateTime")
  protected Calendar date;
Вообще то, используя этот механизм, можно еще много чего сделать:)

2.5. Кастомизация имен классов

Иногда, особенно когда пытаешься замаскировать слона под собаку, требуется изменить имена некоторых результирующих классов. Например, по каким то причинам, имя TestElType нас не устраивает, а очень-очень хочется SuperMegaClass. Тогда пишем следующее
<jaxb:bindings schemaLocation="..\xsd\main.xsd">
  <jaxb:bindings node=".//xsd:complexType[@name='test-el-type']">
    <jaxb:class name="SuperMegaClass" />
  </jaxb:bindings>
</jaxb:bindings> 

Пояснения:
В первой секции jaxb:bindings мы с помощью элемента schemaLocation указывает xsd файл, над которым проводятся издевательства.
Во вложенном теге jaxb:bindings с помощью атрибута node указываем на интересующий тип/элемент.

2.6. И снова xsd:choise

В результате обработки вот такой конструкции
<xsd:complexType name="result-type">
  <xsd:choice>
    <xsd:element name="long-num" type="xsd:long" />
    <xsd:element name="int-num" type="xsd:int" />
    <xsd:element name="string" type="xsd:string" />
  </xsd:choice>
</xsd:complexType>


Получится вот такой чудо-код:
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "result-type", propOrder = {
    "longNumOrIntNumOrString"
})
public class ResultType {

  @XmlElements({
    @XmlElement(name = "string", type = String.class),
    @XmlElement(name = "long-num", type = Long.class),
    @XmlElement(name = "int-num", type = Integer.class)
  })
  protected List<Serializable> longNumOrIntNumOrString;

Особенно радует имя поля ...
Чтобы придать имени боле вразумительный вид, можем задать вот такую кастомизацию...
<jaxb:bindings schemaLocation="..\xsd\main.xsd">
  <jaxb:bindings node=".//xsd:complexType[@name='result-type']">
    <jaxb:bindings node=".//xsd:choice">
      <jaxb:property name="resultItems" />
    </jaxb:bindings>
  </jaxb:bindings>
</jaxb:bindings>

Назначение первых двух jaxb:bindings аналогично предыдущему пункту (указать xsd файл и имя элемента/типа). Далее мы указываем на интересующее будущее поле (node=".//xsd:choice") и задаем ему нужное имя. Если вдруг "повезло" и у вас в одном типе несколько элементов xsd:choice, то в прийдется каждому такому элементу назначить id и в строке node=".//xsd:choice" подправить XPath(уточнить условие).

4 комментария:

  1. Спасибо большое, но так и не понял где взять схему http://java.sun.com/xml/ns/jaxb/xjc

    Я сейчас пытаюсь пропатчить XJC Eclipse plugin, завязывая его на JDT и убирая совсем уж заметные косяки. В том числе хочу добавить в XML каталог нужные схемы - чтобы править *.xsd в нормальном редакторе и оффлайн.

    ОтветитьУдалить
  2. Вот например, "в комплекте" JDK 1.6, есть схемы, которые лежат в файле ${JDK_HOME}\lib\tools.jar пакет \com\sun\tools\internal\xjc\reader\xmlschema\bindinfo.
    Но это скорее для ознакомления...
    У меня авто заполнения тоже не получилось... Так что могу посоветовать
    http://java.sun.com/webservices/docs/1.5/tutorial/doc/JAXBUsing4.html
    и
    https://jaxb.dev.java.net/nonav/2.0/binding-customization/
    А дальше пробовать (благо там запоминать не так уж и много).

    ОтветитьУдалить
  3. А по поводу эклипса - очень понравилось вот что:
    1. Качаем сборку для java EE (http://www.eclipse.org/downloads/)
    2. Ставим m2eclipse (http://m2eclipse.sonatype.org/sites/m2e)
    3. Создаем maven проект на основе, ну скажем вот такого pom.xml
    <project
    xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.test.ws</groupId>
    <artifactId>cxf_01</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>
    <name>Test CXF</name>
    <description>Test CXF hacks</description>

    <properties>
    <cxf.version>2.2.7</cxf.version>
    </properties>


    <build>
    <resources>
    <resource>
    <directory>${basedir}/src/main/xsd</directory>
    </resource>
    <resource>
    <directory>${basedir}/src/main/xjb</directory>
    </resource>
    </resources>
    <plugins>
    <plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>jaxb2-maven-plugin</artifactId>
    <version>1.3</version>
    <executions>
    <execution>
    <goals>
    <goal>xjc</goal>
    </goals>
    </execution>
    </executions>
    <configuration>
    <schemaFiles>main.xsd</schemaFiles>
    <bindingFiles>main.xml</bindingFiles>
    <extension>true</extension>
    </configuration>
    </plugin>
    <!-- Don't forget Java 5!! -->
    <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
    <source>1.5</source>
    <target>1.5</target>
    </configuration>
    </plugin>
    </plugins>
    </build>

    <dependencies>
    <dependency>
    <groupId>com.sun.xml.bind</groupId>
    <artifactId>jaxb-impl</artifactId>
    <version>2.1.12</version>
    </dependency>
    <dependency>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-rt-frontend-jaxws</artifactId>
    <version>${cxf.version}</version>
    </dependency>
    <dependency>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-rt-transports-http</artifactId>
    <version>${cxf.version}</version>
    </dependency>
    <!-- Jetty is needed if you're are not using the CXFServlet -->
    <dependency>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-rt-transports-http-jetty</artifactId>
    <version>${cxf.version}</version>
    </dependency>
    </dependencies>
    </project>

    4. Правой клавишей по проекту --> Maven --> Update Project Configuration.

    И eclipse дальше сам все сделает (и classpath сформирует, и ресурсы скомпилирует)...

    ОтветитьУдалить
  4. VarangaOfficial - информация о препарате от грибка варанга - мы работаем только с официальными источниками, и предоставляем вниманию наших пользователей исключительно проверенные, не подвергающиеся сомнениям, факты. Воспользовавшись данным ресурсом, вы сможете узнать всеисчерпывающую информацию касающуюся представленного средства. Лично увидеть данные о проведенных клинических исследований, прочесть реальные отзывы пациентов и медицинского персонала. Изучить инструкцию по применению, прочитать особенности и методы работы мази, понять, почему крем Варанга настолько эффективен, где нужно заказывать оригинальный препарат и, как избежать покупки подделки. Мы очень тщательно проверяем размещаемые данные. Предоставляем нашим пользователям сведения, которые берутся только из подлинных источников. Если вы обнаружили у себя признаки появления грибкового заболевани или уже довольно продолжительное время, без ощутимых результатов пытаетесь избавиться от этого досадного недуга, на нашем сайте вы отыщете быстрый и легкий способ решения проблемы. Приобщайтесь и живите полноценной, здоровой жизнью. Благодаря нам, все ответы на самые волнующие вопросы, теперь собраны в одном месте на удобной в использовании и высоко информационном ресурсе.

    ОтветитьУдалить