Pull to refresh

Самодокументированный JAX-WS с поддержкой XSD Restrictions

Reading time8 min
Views15K
В этой статье я расскажу о создании самодокументируемого 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:
@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.
Tags:
Hubs:
+13
Comments5

Articles