21

我正在尝试使用JAX WS为PayPal Express Checkout API实现简单的 Web 服务客户端。PayPal Express Checkout API提供WSDL文件,我可以使用CXF 的 wsdl2java实用程序从中生成 Java 类。

由于身份验证的原因,它需要在每个请求中添加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 请求的标头:

  1. 我不是很想使用com.sun.*包中的类,正如这里的回答中提到的:JAX-WS - 添加 SOAP 标头(主要是因为不同 JDK 之间可能存在可移植性问题)
  2. 我不想手动将该对象编组到嵌套的 javax.xml.soap.SOAPElement实例中,如此处的答案中所述: 如何使用 Java JAX-WS 添加 SOAP 标头
4

5 回答 5

33

因此,看起来我在结合来自SO的 JAX-WSJAXB相关答案时找到了可能的答案(如果有这些技术经验的人可以检查以下是否正确,我将不胜感激):

对我来说显而易见的是添加 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);

书签#1的解释:不应该编组标头对象本身,而是编组表示该对象的JAXBElement,否则会出现异常。应该使用从WSDL生成的ObjectFactory类之一来从原始对象创建所需的JAXBElement实例。(感谢@skaffman 的回答:没有由 JAXB 生成的 @XmlRootElement

还应该参考Martin Straus扩展此答案的答案

于 2012-05-19T15:09:17.600 回答
11

这个解决方案效果很好,但有一个问题。处理入站消息时会生成此错误:

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;
}
于 2012-12-19T22:06:00.230 回答
5

我创建了一个使用参数用户和密码作为标题的 Web 服务公开方法,如下所示:

@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 类生成。我希望这有帮助。

于 2012-05-20T15:24:41.173 回答
1

我找到了这个答案:

JAX-WS - 添加 SOAP 标头

基本上,您将 -XadditionalHeaders 添加到编译器选项中,并且标头中的对象也作为方法的参数出现在您生成的代码中。

于 2016-01-11T14:04:28.067 回答
1

如果您使用的是 maven,并且 jaxws-maven-plugin 您所要做的就是将 xadditionalHeaders 标志添加到 true 并且客户端将使用以标头作为输入的方法生成。

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

于 2016-03-11T18:44:15.100 回答