В этой статье я расскажу о создании самодокументируемого web сервиса (jax-ws), который использует элементы XSD Restrictions.
В книге «Программист прагматик» есть глава, посвящённая документации. В ней сказано, что должен быть один источник информации, а на основе него генерироваться производные формы этой информации. например, в одном формате хранится вся информация о структуре таблиц (это может быть файл любого подходящего или собственного формата), а на основе неё генерируются сами таблицы, документация по этим таблицам, DAO объекты для работы с таблицами. Плюсы это подхода в том, между производными формами представления информации не будет рассинхронизации, ведь единственное место, где будет редактироваться информация — это тот первоначальный источник.
Минусы в том, что все заинтересованные люди должны уметь править этот файл и должны быть конвертеры в нужные форматы.
В web services эти понятия называется contract first и contract last. Contract first — когда сначала создаётся wsdl, а по ней генерируется Java код. Contract last — наоборот, сначала Java код, а по нему wsdl.
Про плюсы и минусы можно почитать по ссылкам: docs.spring.io/spring-ws/sites/1.5/reference/html/why-contract-first.html stackoverflow.com/questions/763827/which-is-the-better-approach-to-web-services-contract-first-or-contract-last
В этой статье я рассмотрю создание SOAP Web Service с самодокументацией, что бы получить похожее решение — вся информация о web services будет содержаться в нём самом. Получается этакий «contract last» — сначала пишется код, а потом по нему получается документация в виде wsdl, которая содержит максимум информации об этом web сервисе — комментарии ко всем методам и типам, с которыми сервис работает и большую часть логики проверки на корректность данных. Для чего это решение может быть полезно? В том случае, когда вся разработка сосредоточена на написании кода и wsdl генерируется на основе него, а не наоборот. Или когда сложно содержать документацию изначально в wsdl. Но если веб методы пишутся по аналитике, то эту аналитику лучше всё же хранить в wsdl. Т.е. сначала wsdl, а по ней генерируются методы, классы и читаемая документация. Иначе будут проблемы проверки соответствия уже написанного сервиса с тех. заданием.
Проверку на корректность входных данных тоже лучше вынести на сторону xsd, что бы можно было проверить запрос без подключения в web сервису и что бы эта логика была видна (в xsd), а не была зашита где то в недрах сервиса.
На текущий момент получить wsdl можно из любого jax-ws приложения. Но стандартные jax-ws/jaxb аннотации не поддерживают документацию к веб сервисам — для этого нужно пользоваться возможностями, которые предоставляют сторонние framework'и. А конкретно — apache CXF (jax-ws) предоставляет аннотацию WSDLDocumentation, которая применяется к веб методам и в которой указывается описание веб метода, которое в последствие попадёт в wsdl. Для документирования xml сущностей, которые будут использоваться в веб методах, используется библиотека jaxb-facets ( dsg.tuwien.ac.at/staff/hummer/tools/jaxb-facets.html, github.com/whummer/jaxb-facets). Она позволяет, так же как CXF для веб методов, документировать классы и поля xml bean'ов. По мимо этого она добавляет поддержку XSD Restrictions — дополнительных ограничений на значения полей xml. (описание XSD Restrictions находится здесь www.w3schools.com/schema/schema_facets.asp) Это будет полезно, когда нужна логика проверки корректности значений сложнее чем обязательный/не обязательный — т.к. jaxb не в полной мере поддерживает возможности xsd по части валидации. А конкретно, будет возможно указать ограничения на минимальное, максимальное значения, использовать регулярные выражения, использовать различные способы обработки строк на пустые значения, установить ограничения на длину значения.
В качестве примера создадим простой веб сервис с несколькими методами.
В статье я опишу конкретные примеры с кусками кода. Весь же сервис можно скачать по ссылке dl.dropboxusercontent.com/u/7519092/jax-ws-example.zip Это IDEA maven проект. В IDEA можно открыть либо импортом IDEA проекта либо импортом maven проекта. В pom.xml настроена генерация wsdl файла с трансформацией его в html с помощью xslt, но к сожалению, этот wsdl не содержит тега documentation из jaxb-facets, хотя в генерируемом wsdl (доступным по ссылке <путь к приложению>/web_service/WebService?wsdl) этот тег есть. Так же в проекта отличается применение аннотаций — там аннотации применяются к get методам, а в этих примерах к полям класса. Это сделано для того, что бы сократить статью. Что бы применять аннотации к полям класса нужно либо не делать get методы либо указать @XmlAccessorType(XmlAccessType.FIELD).
В качестве примера рассмотрим несколько конкретных задач
Для date:
Сгенерируемая xsd схема:
(Аннотация Facets из библиотеки jaxb-facets)
У нас нужно было использовать т.к. СУБД поддерживала не весть диапазон дат, доступных в Java. Т.е. в web service можно передать дат, например, 1000-01-01 и в Java она корректно распарсится, но при записи в CУБД будет ошибка, т.к. она не поддерживает такой диапазон дат. И что бы ограничить принимаемые значения вводится это ограничение.
Для даты максимальная дата указана с учётом часового пояса. Нужно, что бы поддерживались выходные даты 9999-12-31 с часовым поясом (например 9999-12-31+04:00). Если этого не делать, то при сериализации этого значения (при отдаче клиенту) будет ошибка xsd validation.
Нужно, например, когда поле является уникальным идентификатором определённой длинны или каким то другим идентификатором конкретной длинны — номер паспорта, карточки.
Хотя в случае id можно выделить отдельный тип Id:
И использовать его так:
Так у нас будет явно указано, что это переменная — уникальный идентификатор, при этом все ограничение будут работать.
Некоторые необязательные поля нужно обнулять. Для строк это делается очень просто — шлётся пустой тег (e.g. <name/>) и при unmarshalling'е (преобразование xml запроса в java объекты) получим просто пустую строку (String.isEmpty() == true). И её вполне можно интерпретировать как строку, которую нужно установить в null.
Но если нужно обнулить числа или даты, то при передаче пустого тега будет ошибка:
Unmarshalling Error: cvc-datatype-valid.1.2.1: '' is not a valid value for 'integer'
Это вполне объяснимо — пустой тег нельзя представить в виде числа (null не считается — null это отсутствие тега).
Решить это можно с помощью default значения для поля
При этом если передать пустой тег (<id/>) в Java переменная id примет значение -2147483648 (это Integer.MIN_VALUE). Это решение немного похоже Null Object Pattern — когда нужно указать, что явно передаётся null значение (а не просто не указали значение), то передаём специальный null объект.
en.wikipedia.org/wiki/Null_Object_pattern
Для дат этот же подход будет выглядеть так:
Документирование полей делается с помощью аннотации Documentation (из библиотеки jaxb-facets).
Документирование методов web service делается с помощью аннотации WSDLDocumentation из CXF.
В дальнейшем описание к этим полям и методам можно посмотреть в wsdl.
Для преобразовании сгенерированной wsdl в читаемый вид воспользуемся файлом xslt tomi.vanek.sk/index.php?page=wsdl-viewer Так же SoapUI может генерировать читаемый html. Всё это описано на StackOverflow:
stackoverflow.com/questions/686103/generating-html-documentation-from-wsdl Преобразование в html на этапе сборки реализовано в pom.xml Дальше эта страница будет доступна по адресу <путь к приложению>/WebService.html
Не сколько замечаний по генерации html:
Параметры web методов корректно отображаются только если указан @SOAPBinding(style = SOAPBinding.Style.RPC).
Аннотация Documentation попадает в wsdl и, соответственно, в документацию в html только при runtime генерации wsdl из работающего сервиса. При сборке wsdl не содержит документации. Почему так — не знаю. Скорее всего ошибка генератора wsdl.
Вкратце расскажу о остальных возможностях jaxb-facets, которые я использовал:
Проверка значения по регулярному выражению — Facets.pattern.
Установка логики обработки пробельных символов — Facets.whiteSpace. Пробельные символы можно учитывать, убирать, заменять на пробелы (для табов и символов переноса строки). Работает только при проверке в xsd, т.е. в Java код попадёт значение как есть, с пробельными символами. Полезно будет для проверки обязательности заполнения строкового поля, когда поле заполненное пробелами, тоже должно интерпретироваться как пустое.
Вообще jaxb-facets поддерживает все xsd restrinction. Ознакомиться с ними можно в конце страницы www.w3schools.com/schema/schema_facets.asp Так же в последней версии (2.2.6) появилась поддержка xs:assert. Пример xs:assert с сайта IBM ( www.ibm.com/developerworks/ru/library/x-xml11pt2 ):
Повторюсь, вообще если пишется сервис «с нуля» то лучше сделать корректный wsdl, а по нему уже генерировать Java код и документацию. Но если уже есть написанный web сервис, то добавление в него XSD Restrictions позволит сделать его более прозрачным со стороны клиента — он будет видеть логику проверки входных значений (хотя, понятно, не всю — в некоторых случаях нельзя проверить корректность запросов без обращения к СУБД или каким то связанным ресурсам). Плюс генерация документации позволит удобно сравнить соответствие реальных web методов со своими параметрами, тех. заданию. Без этого пришлось бы проверять или методом «тыка» — смотря запросы и ответы web методов, либо разбираться в хитросплетениях сгенерируемой wsdl.
Описание задачи
В книге «Программист прагматик» есть глава, посвящённая документации. В ней сказано, что должен быть один источник информации, а на основе него генерироваться производные формы этой информации. например, в одном формате хранится вся информация о структуре таблиц (это может быть файл любого подходящего или собственного формата), а на основе неё генерируются сами таблицы, документация по этим таблицам, DAO объекты для работы с таблицами. Плюсы это подхода в том, между производными формами представления информации не будет рассинхронизации, ведь единственное место, где будет редактироваться информация — это тот первоначальный источник.
Минусы в том, что все заинтересованные люди должны уметь править этот файл и должны быть конвертеры в нужные форматы.
В web services эти понятия называется contract first и contract last. Contract first — когда сначала создаётся wsdl, а по ней генерируется Java код. Contract last — наоборот, сначала Java код, а по нему wsdl.
Про плюсы и минусы можно почитать по ссылкам: docs.spring.io/spring-ws/sites/1.5/reference/html/why-contract-first.html stackoverflow.com/questions/763827/which-is-the-better-approach-to-web-services-contract-first-or-contract-last
В этой статье я рассмотрю создание SOAP Web Service с самодокументацией, что бы получить похожее решение — вся информация о web services будет содержаться в нём самом. Получается этакий «contract last» — сначала пишется код, а потом по нему получается документация в виде wsdl, которая содержит максимум информации об этом web сервисе — комментарии ко всем методам и типам, с которыми сервис работает и большую часть логики проверки на корректность данных. Для чего это решение может быть полезно? В том случае, когда вся разработка сосредоточена на написании кода и wsdl генерируется на основе него, а не наоборот. Или когда сложно содержать документацию изначально в wsdl. Но если веб методы пишутся по аналитике, то эту аналитику лучше всё же хранить в wsdl. Т.е. сначала wsdl, а по ней генерируются методы, классы и читаемая документация. Иначе будут проблемы проверки соответствия уже написанного сервиса с тех. заданием.
Проверку на корректность входных данных тоже лучше вынести на сторону xsd, что бы можно было проверить запрос без подключения в web сервису и что бы эта логика была видна (в xsd), а не была зашита где то в недрах сервиса.
Варианты решения
На текущий момент получить wsdl можно из любого jax-ws приложения. Но стандартные jax-ws/jaxb аннотации не поддерживают документацию к веб сервисам — для этого нужно пользоваться возможностями, которые предоставляют сторонние framework'и. А конкретно — apache CXF (jax-ws) предоставляет аннотацию WSDLDocumentation, которая применяется к веб методам и в которой указывается описание веб метода, которое в последствие попадёт в wsdl. Для документирования xml сущностей, которые будут использоваться в веб методах, используется библиотека jaxb-facets ( dsg.tuwien.ac.at/staff/hummer/tools/jaxb-facets.html, github.com/whummer/jaxb-facets). Она позволяет, так же как CXF для веб методов, документировать классы и поля xml bean'ов. По мимо этого она добавляет поддержку XSD Restrictions — дополнительных ограничений на значения полей xml. (описание XSD Restrictions находится здесь www.w3schools.com/schema/schema_facets.asp) Это будет полезно, когда нужна логика проверки корректности значений сложнее чем обязательный/не обязательный — т.к. jaxb не в полной мере поддерживает возможности xsd по части валидации. А конкретно, будет возможно указать ограничения на минимальное, максимальное значения, использовать регулярные выражения, использовать различные способы обработки строк на пустые значения, установить ограничения на длину значения.
Реализация
В качестве примера создадим простой веб сервис с несколькими методами.
В статье я опишу конкретные примеры с кусками кода. Весь же сервис можно скачать по ссылке dl.dropboxusercontent.com/u/7519092/jax-ws-example.zip Это IDEA maven проект. В IDEA можно открыть либо импортом IDEA проекта либо импортом maven проекта. В pom.xml настроена генерация wsdl файла с трансформацией его в html с помощью xslt, но к сожалению, этот wsdl не содержит тега documentation из jaxb-facets, хотя в генерируемом wsdl (доступным по ссылке <путь к приложению>/web_service/WebService?wsdl) этот тег есть. Так же в проекта отличается применение аннотаций — там аннотации применяются к get методам, а в этих примерах к полям класса. Это сделано для того, что бы сократить статью. Что бы применять аннотации к полям класса нужно либо не делать get методы либо указать @XmlAccessorType(XmlAccessType.FIELD).
Примеры
В качестве примера рассмотрим несколько конкретных задач
Ограничение на максимальное и минимальные значение даты и времени.
Для date:
@XmlSchemaType(name = "date")
@Facets(minInclusive = "1900-01-01", maxInclusive = "9999-12-31Z")
Date date;
Сгенерируемая xsd схема:
<xs:element minOccurs="0" name="date">
<xs:simpleType>
<xs:restriction base="xs:date">
<xs:maxInclusive value="9999-12-31Z"/>
<xs:minInclusive value="1900-01-01"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
@XmlSchemaType(name = "dateTime")
@Facets(minInclusive = "1900-01-01T00:00:00", maxInclusive = "9999-12-31T23:59:59")
Date dateTime;
<xs:element minOccurs="0" name="dateTime">
<xs:simpleType>
<xs:restriction base="xs:dateTime">
<xs:maxInclusive value="9999-12-31T23:59:59"/>
<xs:minInclusive value="1900-01-01T00:00:00"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
(Аннотация Facets из библиотеки jaxb-facets)
У нас нужно было использовать т.к. СУБД поддерживала не весть диапазон дат, доступных в Java. Т.е. в web service можно передать дат, например, 1000-01-01 и в Java она корректно распарсится, но при записи в CУБД будет ошибка, т.к. она не поддерживает такой диапазон дат. И что бы ограничить принимаемые значения вводится это ограничение.
Для даты максимальная дата указана с учётом часового пояса. Нужно, что бы поддерживались выходные даты 9999-12-31 с часовым поясом (например 9999-12-31+04:00). Если этого не делать, то при сериализации этого значения (при отдаче клиенту) будет ошибка xsd validation.
Ограничение на конкретную длину поля
@Facets(length = 16)
String id;
<xs:element minOccurs="0" name="id">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:length value="16"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
Нужно, например, когда поле является уникальным идентификатором определённой длинны или каким то другим идентификатором конкретной длинны — номер паспорта, карточки.
Хотя в случае id можно выделить отдельный тип Id:
public class Id {
@Facets(length = 16)
@XmlAttribute
String attr;
}
И использовать его так:
Id id;
Так у нас будет явно указано, что это переменная — уникальный идентификатор, при этом все ограничение будут работать.
Обнуление полей
Некоторые необязательные поля нужно обнулять. Для строк это делается очень просто — шлётся пустой тег (e.g. <name/>) и при unmarshalling'е (преобразование xml запроса в java объекты) получим просто пустую строку (String.isEmpty() == true). И её вполне можно интерпретировать как строку, которую нужно установить в null.
Но если нужно обнулить числа или даты, то при передаче пустого тега будет ошибка:
Unmarshalling Error: cvc-datatype-valid.1.2.1: '' is not a valid value for 'integer'
Это вполне объяснимо — пустой тег нельзя представить в виде числа (null не считается — null это отсутствие тега).
Решить это можно с помощью default значения для поля
@XmlElement(defaultValue = "-2147483648")
Integer id;
<xs:element default="-2147483648" minOccurs="0" name="id" type="xs:int"/>
При этом если передать пустой тег (<id/>) в Java переменная id примет значение -2147483648 (это Integer.MIN_VALUE). Это решение немного похоже Null Object Pattern — когда нужно указать, что явно передаётся null значение (а не просто не указали значение), то передаём специальный null объект.
en.wikipedia.org/wiki/Null_Object_pattern
Для дат этот же подход будет выглядеть так:
@XmlSchemaType(name = "date")
@XmlElement(defaultValue = "1900-01-01")
Date date;
<xs:element default="1900-01-01" minOccurs="0" name="date" type="xs:date"/>
@XmlSchemaType(name = "dateTime")
@XmlElement(defaultValue = "1900-01-01T00:00:00")
Date dateTime;
<xs:element default="1900-01-01T00:00:00" minOccurs="0" name="dateTime" type="xs:dateTime"/>
Документирование
Документирование полей делается с помощью аннотации Documentation (из библиотеки jaxb-facets).
@XmlSchemaType(name = "date")
@Documentation("Пример ограничения даты")
@Facets(minInclusive = "1900-01-01", maxInclusive = "9999-12-31Z")
Date date;
<xs:element minOccurs="0" name="date">
<xs:annotation>
<xs:documentation>Пример ограничения даты</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:restriction base="xs:date">
<xs:maxInclusive value="9999-12-31Z"/>
<xs:minInclusive value="1900-01-01"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
Документирование методов web service делается с помощью аннотации WSDLDocumentation из CXF.
@WSDLDocumentation("Пример документации для метода. Метод показывает работы с XML Schema Restrictions")
String facetsExampleMethod(FacetsExample request);
<wsdl:operation name="facetsExampleMethod">
<wsdl:documentation>
Пример документации для метода. Метод показывает работы с XML Schema Restrictions
</wsdl:documentation>
<wsdl:input message="tns:facetsExampleMethod" name="facetsExampleMethod"/>
<wsdl:output message="tns:facetsExampleMethodResponse" name="facetsExampleMethodResponse"/>
</wsdl:operation>
В дальнейшем описание к этим полям и методам можно посмотреть в wsdl.
Генерация документации по сервису (wsdl to html)
Для преобразовании сгенерированной wsdl в читаемый вид воспользуемся файлом xslt tomi.vanek.sk/index.php?page=wsdl-viewer Так же SoapUI может генерировать читаемый html. Всё это описано на StackOverflow:
stackoverflow.com/questions/686103/generating-html-documentation-from-wsdl Преобразование в html на этапе сборки реализовано в pom.xml Дальше эта страница будет доступна по адресу <путь к приложению>/WebService.html
Не сколько замечаний по генерации html:
Параметры web методов корректно отображаются только если указан @SOAPBinding(style = SOAPBinding.Style.RPC).
Аннотация Documentation попадает в wsdl и, соответственно, в документацию в html только при runtime генерации wsdl из работающего сервиса. При сборке wsdl не содержит документации. Почему так — не знаю. Скорее всего ошибка генератора wsdl.
Остальные возможности jaxb-facets
Вкратце расскажу о остальных возможностях jaxb-facets, которые я использовал:
Проверка значения по регулярному выражению — Facets.pattern.
Установка логики обработки пробельных символов — Facets.whiteSpace. Пробельные символы можно учитывать, убирать, заменять на пробелы (для табов и символов переноса строки). Работает только при проверке в xsd, т.е. в Java код попадёт значение как есть, с пробельными символами. Полезно будет для проверки обязательности заполнения строкового поля, когда поле заполненное пробелами, тоже должно интерпретироваться как пустое.
Вообще jaxb-facets поддерживает все xsd restrinction. Ознакомиться с ними можно в конце страницы www.w3schools.com/schema/schema_facets.asp Так же в последней версии (2.2.6) появилась поддержка xs:assert. Пример xs:assert с сайта IBM ( www.ibm.com/developerworks/ru/library/x-xml11pt2 ):
<xs:element name="dimension">
<xs:complexType>
<xs:attribute name="height" type="xs:int"/>
<xs:attribute name="width" type="xs:int"/>
<xs:assert test="@height < @width"/>
</xs:complexType>
</xs:element>
Замечания
Повторюсь, вообще если пишется сервис «с нуля» то лучше сделать корректный wsdl, а по нему уже генерировать Java код и документацию. Но если уже есть написанный web сервис, то добавление в него XSD Restrictions позволит сделать его более прозрачным со стороны клиента — он будет видеть логику проверки входных значений (хотя, понятно, не всю — в некоторых случаях нельзя проверить корректность запросов без обращения к СУБД или каким то связанным ресурсам). Плюс генерация документации позволит удобно сравнить соответствие реальных web методов со своими параметрами, тех. заданию. Без этого пришлось бы проверять или методом «тыка» — смотря запросы и ответы web методов, либо разбираться в хитросплетениях сгенерируемой wsdl.