Добавьте объект заголовка SOAP, используя чистый JAX-WS

Я пытаюсь реализовать простой клиент веб-службы дляPayPal Express Checkout API с помощьюJAX WS. PayPal Express Checkout API обеспечиваетWSDL файл, из которого я смог генерировать классы Java, используяCXF's wsdl2java полезность.

Из соображений аутентификации требует добавленияSOAP Header на каждый запрос. Этот заголовок довольно прост и должен выглядеть вот так: https://cms.paypal.com/us/cgi-bin/?cmd=_render-content&content_ID=developer/e_howto_api_ECSOAPAPIBasics#id09C3I0CF0O6

Сгенерировано изWSDL классы включаютebay.apis.eblbasecomponents.CustomSecurityHeaderType класс, который представляет заголовок, который мне нужно добавить к каждому запросу.

Вопрос в том, как добавить вручную созданный экземплярCustomSecurityHeaderType Класс для заголовка SOAP-запроса с учетом следующих условий:

I'm not very eager to use classes from com.sun.* package as mentioned in answer here: JAX-WS - Adding SOAP Headers (mainly because of possible portability issues between different JDK's) I don't want to manually marshal that object into nested javax.xml.soap.SOAPElement instances as mentioned in answer here: How do I add a SOAP Header using Java JAX-WS

Ответы на вопрос(5)

Решение Вопроса

JAX-WS & Амп;JAXB связанные ответы отSO (Я был бы очень признателен, если бы кто-то, имеющий опыт работы с этими технологиями, мог проверить, правильно ли следующее):

Для меня очевидным является добавление обработчика сообщений SOAP и изменение заголовкаSOAPMessage экземпляр в нем:

import javax.xml.ws.Binding;
import javax.xml.ws.BindingProvider;
import javax.xml.ws.handler.Handler;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.Marshaller;
import javax.xml.soap.SOAPHeader;
import ebay.api.paypalapi.ObjectFactory; // class generated by wsdl2java

// following class is generated by wsdl2java utility Service class
final PayPalAPIInterfaceService payPalService = new PayPalAPIInterfaceService();
final PayPalAPIAAInterface expressCheckoutPort = payPalService.getPayPalAPIAA();
final Binding binding = ((BindingProvider) expressCheckoutPort).getBinding();
List<Handler> handlersList = new ArrayList<Handler>();

// now, adding instance of Handler to handlersList which should do our job:
// creating header instance
final CustomSecurityHeaderType headerObj = new CustomSecurityHeaderType();
final UserIdPasswordType credentials = new UserIdPasswordType();
credentials.setUsername("username");
credentials.setPassword("password");
credentials.setSignature("signature");
headerObj.setCredentials(credentials);

// bookmark #1 - please read explanation after code
final ObjectFactory objectFactory = new ObjectFactory();
// creating JAXBElement from headerObj
final JAXBElement<CustomSecurityHeaderType> requesterCredentials = objectFactory.createRequesterCredentials(headerObj);

handlersList.add(new SOAPHandler<SOAPMessageContext>() {
    @Override
    public boolean handleMessage(final SOAPMessageContext context) {        
        try {
            // checking whether handled message is outbound one as per Martin Strauss answer
            final Boolean outbound = (Boolean) context.get("javax.xml.ws.handler.message.outbound");
            if (outbound != null && outbound) {
                // obtaining marshaller which should marshal instance to xml
                final Marshaller marshaller = JAXBContext.newInstance(CustomSecurityHeaderType.class).createMarshaller();
                // adding header because otherwise it's null
                final SOAPHeader soapHeader = context.getMessage().getSOAPPart().getEnvelope().addHeader();
                // marshalling instance (appending) to SOAP header's xml node
                marshaller.marshal(requesterCredentials, soapHeader);
            }
        } catch (final Exception e) {
            throw new RuntimeException(e);
        }
        return true;
    }

    // ... default implementations of other methods go here

});

// as per Jean-Bernard Pellerin's comment setting handlerChain list here, after all handlers were added to list
binding.setHandlerChain(handlersList);

Объяснениеbookmark #1: one should marshal not the header object itself, but JAXBElement представляя этот объект, потому что в противном случае вы получите исключение. Нужно использовать один изObjectFactory классы, которые генерируются изWSDL для создания необходимогоJAXBElement экземпляры из оригинальных объектов. (Спасибо @skaffman за ответ:Нет @XmlRootElement, созданного JAXB )

One should also refer to Martin Straus answer which extends this one
 Yura28 мая 2013 г., 09:36
Отредактировано соответственно - спасибо за замечание :)
 24 сент. 2013 г., 00:37
Таким образом, вам не удалось найти какой-либо другой способ, кроме как просто использовать MessageHandler для установки SOAPHeaders? Знаете ли вы причину, по которой это нельзя сделать так же, как при использовании пакетов com.sun. *?
 12 апр. 2013 г., 14:27
Я думаю, что вы должны включить в свой код исходящую проверку Мартина Страуса.
 Yura24 мая 2012 г., 16:50
Управляемый для вызова метода веб-службы API PayPal и получения успешного ответа, следовательно, принимает этот ответ.
 27 мая 2013 г., 18:16
От пользователя:stackoverflow.com/users/2425676/kai-witte Я считаю, что список обработчиков должен быть создан и заполнен в первую очередь, а затем установлен в качестве цепочки обработчиков. SOAPHandlers, которые добавляются после вызова sethandlerChain, не регистрируются.

но здесь есть одна загвоздка. Он генерирует эту ошибку при обработке входящего сообщения:

dic 19, 2012 7:00:55 PM com.sun.xml.messaging.saaj.soap.impl.EnvelopeImpl addHeader
SEVERE: SAAJ0120: no se puede agregar una cabecera si ya hay una
Exception in thread "main" javax.xml.ws.WebServiceException: java.lang.RuntimeException: com.sun.xml.messaging.saaj.SOAPExceptionImpl: Can't add a header when one is already present.
    at com.sun.xml.ws.handler.ClientSOAPHandlerTube.callHandlersOnResponse(ClientSOAPHandlerTube.java:167)
    at com.sun.xml.ws.handler.HandlerTube.processResponse(HandlerTube.java:174)
    at com.sun.xml.ws.api.pipe.Fiber.__doRun(Fiber.java:1074)
    at com.sun.xml.ws.api.pipe.Fiber._doRun(Fiber.java:979)
    at com.sun.xml.ws.api.pipe.Fiber.doRun(Fiber.java:950)
    at com.sun.xml.ws.api.pipe.Fiber.runSync(Fiber.java:825)
    at com.sun.xml.ws.client.Stub.process(Stub.java:443)
    at com.sun.xml.ws.client.sei.SEIStub.doProcess(SEIStub.java:174)
    at com.sun.xml.ws.client.sei.SyncMethodHandler.invoke(SyncMethodHandler.java:119)
    at com.sun.xml.ws.client.sei.SyncMethodHandler.invoke(SyncMethodHandler.java:102)
    at com.sun.xml.ws.client.sei.SEIStub.invoke(SEIStub.java:154)
    at $Proxy38.wsRdyCrearTicketDA(Unknown Source)
    at ar.com.fit.fides.remedy.api.ws.ServicioCreacionTickets.crearTicket(ServicioCreacionTickets.java:55)
    at ar.com.fit.fides.remedy.api.ws.ConectorRemedyWS.crearTicket(ConectorRemedyWS.java:43)
    at ar.com.fit.fides.remedy.api.ws.ConectorRemedyWS.main(ConectorRemedyWS.java:90)
Caused by: java.lang.RuntimeException: com.sun.xml.messaging.saaj.SOAPExceptionImpl: Can't add a header when one is already present.
    at ar.com.fit.fides.remedy.api.ws.AuthenticationHandler.handleMessage(AuthenticationHandler.java:50)
    at ar.com.fit.fides.remedy.api.ws.AuthenticationHandler.handleMessage(AuthenticationHandler.java:23)
    at com.sun.xml.ws.handler.HandlerProcessor.callHandleMessageReverse(HandlerProcessor.java:341)
    at com.sun.xml.ws.handler.HandlerProcessor.callHandlersResponse(HandlerProcessor.java:214)
    at com.sun.xml.ws.handler.ClientSOAPHandlerTube.callHandlersOnResponse(ClientSOAPHandlerTube.java:161)
    ... 14 more
Caused by: com.sun.xml.messaging.saaj.SOAPExceptionImpl: Can't add a header when one is already present.
    at com.sun.xml.messaging.saaj.soap.impl.EnvelopeImpl.addHeader(EnvelopeImpl.java:128)
    at com.sun.xml.messaging.saaj.soap.impl.EnvelopeImpl.addHeader(EnvelopeImpl.java:108)
    at ar.com.fit.fides.remedy.api.ws.AuthenticationHandler.handleMessage(AuthenticationHandler.java:45)

Итак, решение состоит в том, чтобы проверить, обрабатывается ли сообщение, если исходящее сообщение, как это:

public boolean handleMessage(SOAPMessageContext context) {
    try {
        Boolean outbound = (Boolean) context.get("javax.xml.ws.handler.message.outbound");
        if (outbound != null && outbound) {
            // obtaining marshaller which should marshal instance to xml
            final Marshaller marshaller = JAXBContext.newInstance(AuthenticationInfo.class).createMarshaller();
            // adding header because otherwise it's null
            final SOAPHeader soapHeader = context.getMessage().getSOAPPart().getEnvelope().addHeader();
            // marshalling instance (appending) to SOAP header's xml node
            marshaller.marshal(info, soapHeader);
        }
    } catch (final Exception e) {
        throw new RuntimeException(e);
    }
    return true;
}
 20 июн. 2014 г., 06:45
Здравствуйте, Мартин, я новичок в веб-сервисах, пожалуйста, дайте мне подсказку, что такое входящие и исходящие сообщения?
 Yura20 дек. 2012 г., 09:53
Отличное дополнение к моему ответу - большое спасибо!
 20 июн. 2014 г., 07:46
Привет Мартин, Следующее - моя проблема, когда я пытаюсь добавить элемент jaxb в заголовок SOAP.stackoverflow.com/questions/24320675/…

d в качестве заголовка:

@WebService(serviceName="authentication")
public class WSAuthentication {
   String name = null;
   String password = null;

   public WSAuthentication() {
       super();
   }

   public WSAuthentication(String name, String password) {
       this.name = name;
       this.password = password;
   }

   private static String getData(WSAuthentication sec) {
       System.out.println("********************* AUTHENTICATION ********************" + "\n" + 
       "**********USER: " + sec.name + "\n" + 
       "******PASSWORD: " + sec.password + "\n" + 
       "******************************** AUTHENTICATION ****************************");
       return sec.name + " -- " + sec.password;
   }

   @WebMethod(operationName="security", action="authenticate")
   @WebResult(name="answer")
   public String security(@WebParam(header=true, mode=Mode.IN, name="user") String user, @WebParam(header=true, mode=Mode.IN, name="password") String password) {
        WSAuthentication secure = new WSAuthentication(user, password);
        return getData(secure);
     }
}

Попробуйте скомпилировать его и протестировать сгенерированный из класса WSDL. Надеюсь, это поможет.

 Yura24 мая 2012 г., 16:49
Что касается класса WSAuthentication - мне удалось создать и запустить из него веб-сервис и отправить ему запросы. Однако я столкнулся с проблемой, похожей на эту:stackoverflow.com/questions/7380761/… и мог бы решить это. Тем не менее, это был хороший опыт, таким образом +1 :)
 Yura22 мая 2012 г., 14:04
Большое спасибо - я проверю, как мое решение работает с вашим примером реализации веб-сервиса, и сообщу здесь.

все, что вам нужно сделать, это добавить флаг xadditionalHeaders в true, и клиент будет сгенерирован с методами, которые имеют заголовки в качестве ввода.

https://jax-ws-commons.java.net/jaxws-maven-plugin/wsimport-mojo.html#xadditionalHeaders

JAX-WS - Добавление заголовков SOAP

По сути, вы добавляете -XadditionalHeaders к параметрам компилятора, и объекты в заголовках также появляются в сгенерированном коде как параметры метода.

 Yura11 янв. 2016 г., 19:53
Это интересный подход, и он выглядит довольно изящно, однако я не понимаю, как сделать это именно в этом случае, не могли бы вы рассказать подробнее?

Ваш ответ на вопрос