суббота, 13 марта 2010 г.

Как мы “бадались” с CXF

Недавно на проекте попробовали перевести веб сервисы с XFire на CXF.
Нь дя... впечатлений получилось много:) Сейчас попробую вкратце описать их.


Цели и исходные условия

Есть WEB application - представляет собой нечто, предоставляющее веб сервисы
Есть клиенты 3-x типов: Java (XFire), Java (CXF) и Flex.
Задачу можно сформулировать следующим образом – перевести веб приложение
на CXF без изменений на клиентах.

Трудности (и не только)

Вот некоторые трудности (и не только), с которыми пришлось столкнутся:

1. Да здравствует Java 5!

Мы используем «XSD first" схему, т.е. вначале мы создаем XML схемы с описанием используемых сущностей, потом генерируем Java классы. Вот тут то получилась первая неожиданность: XFire для биндинга использует JAXB 1.0, а CXF – JAXB2 и это дает следующие бонусы:
  • generics, т.е. JAXB2 генерирует нормальные
    параметризированные коллекции и это очень удобно!
  • перечисления (enum)
По поводу последнего пункта: допустим есть XSD:
<xsd:simpleType name="valueType">
    <xsd:restriction base="xsd:string">
      <xsd:enumeration value="TYPE_1"/>
      <xsd:enumeration value="TYPE_2"/>
      <xsd:enumeration value="TYPE_3"/>
    </xsd:restriction>
  </xsd:simpleType> 
..
<xsd:element name=”type” type=”valueType” />

XFire (точнее JAXB) преобразовывает это в строку, т.е. получается просто
и нeзатейлево:
String getType();
void setType(String type);/* Вот сюда можно передать что угодно*/
CXF (точнее JAXB2) в этом случае генерирует enum (со всеми вытекающими удобствами).

2. Краткость сестра таланта

XFire генерировал великое множество интерфейсов, фибрику (класс ObjectFactory) и такое же великое множество классов имплементаций. В итоге пользоваться "Этим" как DOM было не слишком то и удобно. JAXB2 генерирует аннотированные классы (POJO), и работать с такой моделью стало на порядок приятней.

3. JSR 181 аннотации вместо Aegis XML

XFire использует Aegis биндинг, а это значит что для объявления веб сервиса необходимо написать java интерфейс, а рядышком положить XML файл, примерно вот с таким контентом:
<mappings>
  <mapping uri="" name="">
    <method name="methodName">
      <return-type mappedName=""/>
      <parameter index="1" mappedName="param1"/>
      <parameter index="2" mappedName="param2"/>
    </method>
  </mapping>
</mappings>
По моему, не очень удобно когда информация об интерфейсе находится в 2 местах – в Java и XML файлах.
CXF предлагает использовать JSR 181 аннотации, а это значит что открыв Java интерфейс можно получить/задать всю необходимую информацию. И это удобно (да простят меня "фанаты" XML)!

4.Как передать/получить Map<string, string> ? И подобные вопросы

Вот здесь то появилась первая трудность – CXF не умет мапить Map, DataSource, java.util.Calendar и некоторые другие вещи. Пришлось немного почитать чтобы восполнить этот «пробел»
  • Маппинг Map. Используем аннотации @XmlJavaTypeAdapter и класс XmlAdapter. Неудобство заключается в том, что необходимо создать 3 дополнительных классов: XML представление и
    class String2String{
     List<Entry> entry = null;
    }
    class Entry {
     String key = null;
     String value = null;
    }
    
    И класс адаптер, преобразующий объект типа String2String в объект типа Map<String, String>
  • DataSource (передача бинарных данных). По умолчанию CXF предлагает использовать byte[], что не очень удобно(мягко говоря). Немного почитав документацию, обнаружился класс DataHandler (... и настроение улучшилось :) )
  • Calendar. Поумолчанию CXF предлагает использовать XMLGregorianCalendar. "Проблема" решилась применением кастомизации биндинга. Например читаем вот тут .

5. Краткость сестра таланта – часть2


Выяснилась неприятная «наклонность» XFire генерировать кучу оберток. Например для методов
void someMethod(String [] ids);
//или
void someMethod(List<String> ids);
CXF в WSDL помещает нечто подобное
<xsd:element name="ids" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />

Но вот XFire считает совершенно необходимым обернуть коллекцию ids:
<xsd:element name="ids">
  <xsd:complexType>
    <xsd:sequence>
      <xs:element name="string" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
    </xsd: sequence >
  </xsd:complexType>
</xsd:element>
Ну просто замечательно, блин! Пришлось вручную создавать классы оберток, и в методы вместо String[] ids передавать нечто вроде: ArrayOfString ids…

6. Подключаем исходные XSD в WSDL

В XFire была замечательная возможность подключение наших XSD в результирующий WSDL (c полным сохранением "вида схемы", комментариев и т.д.). В CXF такое сделать не получилось. В итоге получаем двойное преобразование XSD to Java и Java to XSD. Понятно, что в этом случае начальные и конечные схемы чуть отличаются, все комментарии теряются... ну и вообще, двойная генерация как то не очень солидно смотрится. Возможно здесь подход "Java first" показал бы себя намного лучше.

7. И еще кое что...

Отдельные приключения были с маппингом исключений(java exception в wsdl failt), с работой утилиты wsdl2java с XSD конструкциями choise, с получением нужных namespace-ов в итоговом wsdl и другими «мелкими» недоразуменияыми.


Итого:

Результат – сервер перевести получилось, а вот обойтись без изменения клиентов не получилось (хотя эти изменения довольно незначительные). Вообщем то, как сказал наш лид: «...выяснилось что это возможно, хотя и заняло много времени...».

А лично у меня осталось довольно таки противоречивое впечатление от CXF + JAXB2. С одной стороны вроди бы и круто: тут тебе и Java 5, и использование POJO и вооще, многие запчасти сейчас уже вошли в Core Java… Но с другой стороны наблюдается некая недоработанность и неуправляемость фреймверка: поведение утилит довольно сильно меняется от версии к версии, причем речь идет о изменениях в 3-ем знаке (2.2.5 и 2.2.6); не все механизмы очевидны и управляемы, да и документация как то не очень - простые вещи описаны ну очень подробно, а вот что то посложнее – "go to google".

Вот так вот.

Вот связанные статьи:
Шаг 0 - XSD to Java
Шаг 1 - Описание Java модели и SEI (//TODO)
Шаг 2 - Генерация клиента (WSDL to Java) (//TODO)

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

  1. блоггер не врапит <>, во всяком случае он не делает этого по-умолчанию.

    ОтветитьУдалить
  2. Нь дя... Поправить то поправил, но как то не очень удобно получается...

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