barbitoff programmer`s blog

Здесь я публикую заметки из программерской жизни: грабли, на которые мне случилось наступить, проблемы, для которых было найдено элегантное (или не очень) решение, а также все, с чем мне пришлось столкнуться и чем хотелось бы поделиться =)
PS Если хотите меня поблагодарить - на странице есть 3 места, чтобы это сделать =)
Показаны сообщения с ярлыком Two classes have the same XML type name. Показать все сообщения
Показаны сообщения с ярлыком Two classes have the same XML type name. Показать все сообщения

вторник, 25 декабря 2012 г.

JAX-WS: обертка ответов от разных веб-методов в одинаковые теги

Задача:

В JAX-WS-веб-сервисе (используется имплементация JAX-WS RI)  есть несколько методов, возвращающих объекты одного типа (пусть это будет класс OperResultClass):
@WebMethod(operationName = "operationA")
public OperResultClass operationA(){...}
@WebMethod(operationName = "operationB")
public OperResultClass operationB(){...}
@WebMethod(operationName = "operationC")
public OperResultClass operationC(){...}...
Необходимо, чтобы XML, получаемый на их выходе, выглядел так:
<MyResponseWrapperTag xmlns="http://soap.response">
<MyResponseTag>
<!-- содержимое объекта-ответа -->
</MyResponseTag>
</MyResponseWrapperTag>
Т.е. необходимо задать для всех методов:
  1. Имя наружного тега и его пространство имен
  2. Имя внутреннего тега и его пространство имен

Решение:

Второй пункт делается просто: каждый метод аннотируется следующим образом:
@WebResult(name="MyResponseTag", targetNamespace="http://soap.response")
После этого мы будем иметь правильный внутренний тег, а вот наружный будет генерироваться JAX-WS`ом автоматически, основываясь на имени метода (он будет состоять из имени метода + "Response").

Для задания имени наружного тега используется аннотация @ResponseWrapper. Но использовать её так:
  @ResponseWrapper(
          localName="MyResponseWrapperTag",
          targetNamespace="http://soap.response"
        )
не получится - будет валиться исключение:
Two classes have the same XML type name "{http://soap.response}MyResponseWrapperTag". Use @XmlType.name and @XmlType.namespace to assign different names to them.
Тут дело в том, что для оберток вокруг ответа JAX-WS генерирует свои классы, по одному на каждый метод. Т.о, для каждого веб-метода будет использоваться свой класс, в то время как из-за @ResponseWrapper всем им будет назначен одно и тот же имя XML-типа. 
Выход из ситуации - самостоятельно задавать имя класса-обертки, чтобы JAX-WS не генерировал своих классов. Делается это следующим образом:
@ResponseWrapper(          className="my.MyResponseWrapper",
          localName="MyResponseWrapperTag",
          targetNamespace="http://soap.response"
        )
Теперь нужно создать этот самый класс-wrapper "my.MyResponseWrapper". Он будет выглядеть примерно так:
@XmlType(name="MyResponseWrapperType",namespace="http://soap.response")
@XmlAccessorType(XmlAccessType.FIELD)
public class MyResponseWrapper{
  @XmlElement(name="MyResponseTag",namespace="http://soap.response")
  public OperResultClass MyResponseTag;
}
Здесь важно, чтобы параметры аннотации @XmlElement совпадали с таковыми в аннотации @WebResult метода. Namespace можно не указывать, если он задан на уровне пакета в package-info.java:
@XmlSchema(namespace = "http://soap.response", elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED)
package my;
import javax.xml.bind.annotation.XmlSchema;



понедельник, 17 сентября 2012 г.

JAX-WS: IllegalAnnotationsException "Two classes have the same XML type name" на ровном месте

Проблема:

Есть веб-сервис, построенный на JAX-WS, у которого есть веб-метод, реализующий soap-операцию "op", тип его возврата - OpResponse: 
@WebService(serviceName = " MyService ")
public class MyService
{
...
@WebMethod(operationName = "op")
public OpResponse op(...) {...}
...
}
...
public class OpResponse { ... }
При развертывании этого веб-сервиса валится ошибка:
com.sun.xml.ws.transport.http.servlet.WSServletException: WSSERVLET11: failed to parse runtime descriptor: javax.xml.ws.WebServiceException: Unable to create JAXBContext
...
Caused by: javax.xml.ws.WebServiceException: Unable to create JAXBContext
...
Caused by: java.security.PrivilegedActionException: com.sun.xml.bind.v2.runtime.IllegalAnnotationsException: 1 counts of IllegalAnnotationExceptions
Two classes have the same XML type name "{...}opResponse". Use @XmlType.name and @XmlType.namespace to assign different names to them.
Хотя вроде бы никаких двух классов с одинаковым XML-типом в проекте нет (впрочем, XML-типы пока явно вообще нигде не фигурируют).

Причина:

Причину ошибки можно объяснить с двух сторон - с точки зрения классов или с точки зрения генерируемых jaxws`ом WSDL/XSD. Я приведу второе объяснение.
Генерируя WSDL, JAX-WS использует имя "opResponse" как имя XSD-типа ответа операции "op" (т.е. он просто конкатенирует имя операции и "Response"), который потом уже, в XSD-схеме, раскрывается с использованием XSD-типа, соответствующего возвращаемому Java-классу:
<operation name="op">
<input wsam:Action="http://.../opRequest" message="tns:op"/>
<output wsam:Action="http://.../opResponse" message="tns:opResponse"/>
</operation>
<xs:complexType name="opResponse">
<xs:sequence>
<xs:element name="return" type="tns:<responseClassName>" minOccurs="0"/>
</xs:sequence>
</xs:complexType> 
Здесь <responseClassName> - это имя класса, который является типом возврата метода "op", написанное со строчной первой буквы. В нашем случае это имя должно было бы быть равным снова "opResponse", т.к. тип возврата метода "op" - именно класс OpResponse, что сделало бы XSD-схему некорректной.

Решение:

Аннотировать класс OpResponse с помощью аннотации @XmlType, как и советует JAX-WS, так, чтобы имя XSD-типа, соответствующего классу OpReponse, отличалось от "opResponse", например:
@XmlType(name = "OpResponseType")
public class OpResponse {...}
Тогда схема примет вид:
...
<xs:complexType name="opResponse">
 <xs:sequence>
  <xs:element name="return" type="tns:OpResponseType" minOccurs="0"/>
 </xs:sequence>
</xs:complexType>
...
и веб-сервис стартанет без проблем.