Настройка префикса JAX-WS для ответа SOAP
Цель
Я реализую веб-сервис для довольно старого (но грустно неизменяемого) интерфейса. У меня проблема, когда клиент, который вызывает мой сервис, ожидает определенное пространство имен в ответе SOAP, и мне трудно изменить его, чтобы он соответствовал.
Учитывая пример приветствия, я хочу это:
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Body>
<ns2:helloResponse xmlns:ns2="http://test/">
<return>Hello Catchwa!</return>
</ns2:helloResponse>
</S:Body>
</S:Envelope>
выглядеть следующим образом:
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Body>
<customns:helloResponse xmlns:customns="http://test/">
<return>Hello Catchwa!</return>
</customns:helloResponse>
</S:Body>
</S:Envelope>
Я нашел нечто похожее на то, что я пытаюсь сделать здесь, но у меня возникли проблемы с тем, чтобы аналогичный код выполнялся правильно. (Я хотел бы придерживаться Metro и не должен меняться на cxf или ось)
Исполнение
Моя реализация JAXBContextFactory
, которая возвращает JAXBRIContext
выглядит следующим образом:
import com.sun.xml.bind.api.JAXBRIContext;
import com.sun.xml.bind.api.TypeReference;
import com.sun.xml.ws.api.model.SEIModel;
import com.sun.xml.ws.developer.JAXBContextFactory;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.JAXBException;
import javax.xml.namespace.QName;
public class HelloJaxbContext implements JAXBContextFactory
{
@Override
public JAXBRIContext createJAXBContext(SEIModel seim, List<Class> classesToBind, List<TypeReference> typeReferences) throws JAXBException {
List<Class> classList = new ArrayList<Class>();
classList.addAll(classesToBind);
List<TypeReference> refList = new ArrayList<TypeReference>();
for (TypeReference tr : typeReferences) {
refList.add(new TypeReference(new QName(tr.tagName.getNamespaceURI(), tr.tagName.getLocalPart(), "customns"), tr.type, tr.annotations));
}
return JAXBRIContext.newInstance(classList.toArray(new Class[classList.size()]), refList, null, seim.getTargetNamespace(), false, null);
}
}
Некоторые тестовые коды для веб-службы просто:
import com.sun.xml.ws.developer.UsesJAXBContext;
import javax.jws.WebService;
import javax.jws.WebMethod;
import javax.jws.WebParam;
@WebService(serviceName = "Hello")
@UsesJAXBContext(value = HelloJaxbContext.class)
public class Hello
{
@WebMethod(operationName = "hello")
public String hello(@WebParam(name = "name") String txt)
{
return "Hello " + txt + "!";
}
}
Вопросы
В Tomcat 7.0.32 и Glassfish 3.1.2 с использованием jaxws-rt 2.2.7 (из Maven) приведенный выше код не влияет на вывод моей веб-службы (префикс пространства имен по-прежнему "ns2" ).
Ответы
Ответ 1
Если вы начали с WSDL старой службы и сгенерировали все различные аннотированные классы запросов и ответов JAXB с помощью wsimport
, то в сгенерированном пакете вы должны найти package-info.java
, например
@javax.xml.bind.annotation.XmlSchema(namespace = "http://test/")
package com.example.test;
JAXB предоставляет механизм, позволяющий вам предлагать сопоставления префикса в аннотации @XmlSchema
, поэтому вы можете попробовать изменить package-info.java
на чтение
@javax.xml.bind.annotation.XmlSchema(namespace = "http://test/",
xmlns = {
@javax.xml.bind.annotation.XmlNs(prefix = "customns",
namespaceURI="http://test/")
}
)
package com.example.test;
и посмотрите, не имеет ли это значение для сгенерированных сообщений. Это также имеет преимущество - быть чистой спецификацией JAXB (то есть не зависит от настраиваемого контекста, специфичного для RI factory).
Если вам нужно повторно запустить wsimport
, вы можете помешать ему перезаписать измененный package-info
, передав параметр -npa
в xjc
(это говорит ему не генерировать package-info.java
, а вместо этого все необходимые настройки namespace
для аннотаций на уровне класса). Именно то, как вы это делаете, зависит от того, как вы выполняете wsimport
.
:
wsimport -B-npa ....
Ant:
<wsimport wsdl="..." destdir="..." .... >
<xjcarg value="-npa" />
</wsimport>
Maven:
<plugin>
<groupId>org.jvnet.jax-ws-commons</groupId>
<artifactId>jaxws-maven-plugin</artifactId>
<version>2.2</version>
<executions>
<execution>
<goals>
<goal>wsimport</goal>
</goals>
<configuration>
<xjcArgs>
<xjcArg>-npa</xjcArg>
</xjcArgs>
</configuration>
</execution>
</executions>
</plugin>
Ответ 2
Рекомендуемый/стандартный способ добиться того, чего вы пытаетесь достичь, с помощью SOAPMessage Handler. Они аналогичны фильтрам веб-приложений java (которые теоретически могут также работать здесь), поскольку они используются для реализации схемы Цепочки ответственности. Например, в вашем случае вы можете получить следующее:
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.namespace.QName;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPException;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;
public class SOAPBodyHandler implements SOAPHandler<SOAPMessageContext> {
static final String DESIRED_NS_PREFIX = "customns";
static final String DESIRED_NS_URI = "http://test/";
static final String UNWANTED_NS_PREFIX = "ns";
@Override
public Set<QName> getHeaders() {
//do nothing
return null;
}
@Override
public boolean handleMessage(SOAPMessageContext context) {
if ((boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY)) { //Check here that the message being intercepted is an outbound message from your service, otherwise ignore.
try {
SOAPEnvelope msg = context.getMessage().getSOAPPart().getEnvelope(); //get the SOAP Message envelope
SOAPBody body = msg.getBody();
body.removeNamespaceDeclaration(UNWANTED_NS_PREFIX);
body.addNamespaceDeclaration(DESIRED_NS_PREFIX, DESIRED_NS_URI);
} catch (SOAPException ex) {
Logger.getLogger(SOAPBodyHandler.class.getName()).log(Level.SEVERE, null, ex);
}
}
return true; //indicates to the context to proceed with (normal)message processing
}
@Override
public boolean handleFault(SOAPMessageContext context) {
//do nothing
return null;
}
@Override
public void close(MessageContext context) {
//do nothing
}
}
В объявлении класса <Внедрение > Bean добавьте
@HandlerChain(file = "handler-chain.xml")
Аннотации, приведенные выше, являются ссылкой на файл конфигурации, который на самом деле позволяет вашему обработчику ударить. Файл конфигурации будет выглядеть примерно так.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<javaee:handler-chains xmlns:javaee="http://java.sun.com/xml/ns/javaee"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<javaee:handler-chain>
<javaee:handler>
<javaee:handler-class>your.handler.FQN.here</javaee:handler-class>
</javaee:handler>
</javaee:handler-chain>
</javaee:handler-chains>
Попробуйте это дома. Этот специальный код НЕ тестировался
Ответ 3
В эталонной реализации я сделал это, чтобы наконец заставить его работать. См. schemaLocation игнорируется при сортировке JAXB-классов с использованием Metro