Как я могу передать данные обратно из обработчика SOAP клиенту веб-сервиса?
(В продолжение этого вопроса: получение необработанного XML-ответа от клиента веб-службы Java)
У меня есть обработчик сообщений SOAP, который может получить необработанный XML ответа веб-службы. Мне нужно вставить этот XML в клиент веб-сервиса, чтобы я мог выполнить некоторые XSL-преобразования для ответа перед отправкой его в путь. У меня возникают проблемы с поиском хорошего способа получения данных из обработчика SOAP, который перехватывает входящие сообщения и делает необработанный XML доступным для сгенерированного (из WSDL) клиента веб-службы. Есть идеи, если это вообще возможно?
Я придумал что-то вроде этого:
public class CustomSOAPHandler implements javax.xml.ws.handler.soap.SOAPHandler<javax.xml.ws.handler.soap.SOAPMessageContext>
{
private String myXML;
public String getMyXML()
{
return myXML;
}
...
public boolean handleMessage(SOAPMessageContext context)
{
...
myXML = this.getRawXML(context.getMessage());
}
//elsewhere in the application:
...
myService.doSomething(someRequest);
for (Handler h: ((BindingProvider)myService).getBinding().getHandlerChain())
{
if (h instanceof CustomSOAPHandler )
{
System.out.println("HandlerResult: "+ ((CustomSOAPHandler )h).getMyXML());
}
}
В очень простых тестах это работает. Но это решение похоже на дешевый хак. Мне не нравится устанавливать необработанный XML как член обработчика цепочки, и у меня есть чувство, что это противоречит многим другим лучшим практикам. У кого-нибудь есть более элегантный способ сделать это?
2 ответа
Решением было использование JAXB для преобразования объектов обратно в XML. Я действительно не хотел этого делать, потому что клиенту веб-сервиса кажется избыточным получать XML, преобразовывать его в POJO, только чтобы этот POJO был преобразован обратно в XML, но это работает.
Два варианта, которые мне показались полезными, описаны здесь. Я еще не получил ответ о том, было ли хорошо использование ThreadLocal или нет, но я не понимаю, почему это не должно быть.
Мой метод secoond, который был добавлен к первоначальному вопросу, заключался в том, чтобы идти по пути обработчика. Во время отладки выноски WS я заметил, что карта invocationProperties имеет ответ SOAP как часть внутренней структуры пакета в объекте responseContext, но, похоже, нет никакого способа добраться до него. ResponseContext был набором пар имя-значение. Однако, когда я прочитал исходный код ResponseContext в этом месте, я увидел, что в коде метода get есть комментарий о возврате нулевого значения, если он не может найти свойство Application Scoped, в противном случае он будет читать его из пакета invocationProperties, который, казалось, был тем, что я хотел. Итак, я рассказал о том, как установить область действия для пары ключ / значение (Google: настройка свойства application-scope для jaxws), чтобы контекст представлял ее низко, и это было в спецификации jax-ws, на которую я ссылался в другая нить.
Я также немного прочитал о пакете, https://jax-ws.java.net/nonav/jax-ws-20-fcs/arch/com/sun/xml/ws/api/message/Packet.html.
Я надеюсь, что это имеет какой-то смысл для вас. Я был обеспокоен тем, что три не будет чем-то, против чего можно было бы использовать JAXB, если бы вызов веб-службы привел к отказу Soap, и я действительно хотел зарегистрировать этот пакет, так как он возвращался из Платежного шлюза, который по сей день имеет номер недокументированных результатов.
Удачи.
Пример обработчика, который раздает тела сообщения запроса / ответа:
public class MsgLogger implements SOAPHandler<SOAPMessageContext> {
public static String REQEST_BODY = "com.evil.request";
public static String RESPONSE_BODY = "com.evil.response";
@Override
public Set<QName> getHeaders() {
return null;
}
@Override
public boolean handleMessage(SOAPMessageContext context) {
SOAPMessage msg = context.getMessage();
Boolean beforeRequest = (Boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream(32_000);
context.getMessage().writeTo(baos);
String key = beforeRequest ? REQEST_BODY : RESPONSE_BODY;
context.put(key, baos.toString("UTF-8"));
context.setScope(key, MessageContext.Scope.APPLICATION);
} catch (SOAPException | IOException e) { }
return true;
}
@Override
public boolean handleFault(SOAPMessageContext context) {
return true;
}
@Override
public void close(MessageContext context) { }
}
Чтобы зарегистрировать обработчик и использовать сохраненные свойства:
BindingProvider provider = (BindingProvider) port;
List<Handler> handlerChain = bindingProvider.getBinding().getHandlerChain();
handlerChain.add(new MsgLogger());
bindingProvider.getBinding().setHandlerChain(handlerChain);
Req req = ...;
Rsp rsp = port.serviceCall(req); // call WS Port
// Access saved message bodies:
Map<String, Object> responseContext = provider.getResponseContext();
String reqBody = (String) responseContext.get(MsgLogger.REQEST_BODY);
String rspBody = (String) responseContext.get(MsgLogger.RESPONSE_BODY);
TL; DR
Метро JAX WS RI документы говорит о MessageContext.Scope.APPLICATION
имущество:
Объект контекста сообщения также может содержать свойства, установленные клиентом или поставщиком. Например, порт прокси и диспетчерские объекты расширяются
BindingProvider
, Объект контекста сообщения может быть получен из обоих для представления контекста запроса или ответа. Свойства, установленные в контексте запроса, могут быть прочитаны обработчиками, а обработчики могут установить свойства объектов контекста сообщения, передаваемых им. Если эти свойства установлены с областью действияMessageContext.Scope.APPLICATION
тогда они будут доступны клиенту в контексте ответа. На стороне сервера объект контекста передается в метод invoke объектаProvider
,
metro-jax-ws/jaxws-ri/rt/src/main/java/com/sun/xml/ws/api/message/Packet.java
содержит свойство:
/**
* Lazily created set of handler-scope property names.
*
* <p>
* We expect that this is only used when handlers are present
* and they explicitly set some handler-scope values.
*
* @see #getHandlerScopePropertyNames(boolean)
*/
private Set<String> handlerScopePropertyNames;
С другой стороны metro-jax-ws/jaxws-ri/rt/src/main/java/com/sun/xml/ws/client/ResponseContext.java
это реализация Map
с:
public boolean containsKey(Object key) {
if(packet.supports(key))
return packet.containsKey(key); // strongly typed
if(packet.invocationProperties.containsKey(key))
// if handler-scope, hide it
return !packet.getHandlerScopePropertyNames(true).contains(key);
return false;
}
В SOAPHandler
мы можем пометить свойство как APPLICATION
вместо дефолта MessageContext.Scope.HANDLER
:
/**
* Property scope. Properties scoped as <code>APPLICATION</code> are
* visible to handlers,
* client applications and service endpoints; properties scoped as
* <code>HANDLER</code>
* are only normally visible to handlers.
*/
public enum Scope {APPLICATION, HANDLER};
от:
/**
* Sets the scope of a property.
*
* @param name Name of the property associated with the
* <code>MessageContext</code>
* @param scope Desired scope of the property
* @throws java.lang.IllegalArgumentException if an illegal
* property name is specified
*/
public void setScope(String name, Scope scope);
В качестве альтернативы, вместо того, чтобы помещать детали запроса / ответа в контекст мыла (в моем случае это не сработало), вы можете поместить его в ThreadLocal
. Значит тебе нужноSOAPHandler
, что @gavenkoa описал (ty), но добавьте его в ThreadLocal
экземпляр, а не контекст мыла.