使用纯JAX-WS添加SOAP头对象

21

我正在尝试使用JAX WS实现简单的PayPal Express Checkout API网络服务客户端。 PayPal Express Checkout API提供了一个WSDL文件,我可以使用CXF的wsdl2java工具生成Java类。

出于认证原因,每个请求都需要添加SOAP头。该头非常简单,应如此设置: 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 - Adding SOAP Headers(主要是由于可能在不同的JDK之间存在可移植性问题)
  2. 我不想像此处所述那样手动将该对象编组为嵌套javax.xml.soap.SOAPElement实例:How do I add a SOAP Header using Java JAX-WS
5个回答

34

看起来我通过结合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提供的答案:No @XmlRootElement generated by JAXB

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


3
我理解您的意思是:在设置处理程序链之前必须先创建和填充处理程序列表。调用setHandlerChain后添加的SOAP处理程序将不会注册。 - Jean-Bernard Pellerin
那么除了使用MessageHandler设置SOAPHeaders之外,您没有找到其他方法吗?您知道为什么不能像使用com.sun.*包一样做吗? - Aleš
我认为可以使用com.sun.*包中的类来完成。但是,这些类只存在于Sun(Oracle)的JVM中,而其他JVM实现中则不存在,因此使用com.sun.*包的代码将无法在其他JVM上运行。 - Yuriy Nakonechnyy
嘿@Yuva,我在Paypal这里也遇到了同样的问题。我已经使用wsdl创建了soap客户端,并尝试按照您的答案添加请求头,但是没有得到一些文件。这是我的帖子http://stackoverflow.com/questions/21185802/use-paypal-with-java - Java
请问您能否指导我吗?我的Web服务客户端中没有创建ObjectFactory,我已经在Eclipse中使用了简单的Web服务客户端选项。我还尝试了使用WSDL生成Java代码(Axis Generator),但是出现了一些文件未找到的异常。 - Java
显示剩余4条评论

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;
}

你好 Martin,我是 Web 服务的新手,请给我一个提示,什么是入站和出站消息? - Harmeet Singh Taara
你好,马丁。以下是我在尝试将jaxb元素添加到SOAP头中时遇到的问题。http://stackoverflow.com/questions/24320675/eway-payment-gateway-add-headers-using-soap-service-in-recuring-payment-using-j - Harmeet Singh Taara

5
我创建了一个 Web 服务,暴露了一个带有参数 user 和 password 的方法,如下所示:

我创建了一个暴露方法的 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生成的类。希望这能帮到你。


非常感谢 - 我会检查我的解决方案如何与您的示例Web服务实现配合使用,并在此处报告。 - Yuriy Nakonechnyy
关于WSAuthentication类 - 我已经成功创建并运行了一个Web服务,并向其发送了请求。但是,我遇到了类似于此问题:http://stackoverflow.com/questions/7380761/soap-header-using-jax-wsserver-side的问题,并且已经解决了它。尽管如此,这是一次愉快的经历,因此+1 :) - Yuriy Nakonechnyy

1
我找到了这个答案:
基本上,您需要在编译选项中添加-XadditionalHeaders,并且头文件中的对象也会出现在您生成的代码中,作为该方法的参数。
链接:JAX-WS - 添加SOAP标头

这是一个有趣的方法,看起来非常优雅,但是我不太明白在这种情况下该如何实现,您能否详细说明一下? - Yuriy Nakonechnyy

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接