Как я могу передать данные обратно из обработчика 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 экземпляр, а не контекст мыла.

Другие вопросы по тегам