J'essaie d'implémenter un client de service Web simple pour API Paypal Express Checkout en utilisant JAX WS . API Paypal Express Checkout fournit [~ # ~] wsdl [~ # ~] fichier, à partir duquel j'ai pu pour générer Java classes en utilisant wsdl2Java de CXF utilitaire.
Pour des raisons d'authentification, il faut ajouter un en-tête SOAP à chaque demande. Cet en-tête est assez simple et devrait ressembler à ceci: https://cms.Paypal.com/us/cgi-bin/?cmd=_render-content&content_ID=developer/e_howto_api_ECSOAPAPIBasics#id09C3I0CF0O6
Généré à partir de [~ # ~] wsdl [~ # ~] les classes incluent ebay.apis.eblbasecomponents.CustomSecurityHeaderType classe qui représente l'en-tête que je dois ajouter à chaque demande.
La question est donc: comment puis-je ajouter une instance créée manuellement de la classe CustomSecurityHeaderType à SOAP en-tête de la demande en tenant compte des éléments suivants conditions:
Donc, il semble que j'ai trouvé une réponse possible en combinant JAX-WS & [~ # ~] jaxb [~ # ~] réponses liées de [~ # ~] donc [~ # ~] (j'apprécierais vraiment que quelqu'un expérimenté dans ces technologies puisse vérifier si ce qui suit est correct):
La chose évidente pour moi est d'y ajouter SOAP gestionnaire de messages et de modifier l'en-tête de 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);
Explication de bookmark # 1: on ne doit pas marshaler l'objet d'en-tête lui-même, mais JAXBElement représentant cet objet, car sinon on obtiendra une exception. Il faut utiliser l'une des classes ObjectFactory qui sont générées à partir de [~ # ~] wsdl [~ # ~] pour créer les instances nécessaires JAXBElement à partir des objets originaux. (Merci @skaffman pour la réponse: Aucun @XmlRootElement généré par JAXB )
Martin Straus
réponse qui étend celle-ciCette solution fonctionne très bien, mais il y a un hic. Il génère cette erreur lorsque le message entrant est traité:
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)
Ainsi, la solution consiste à vérifier si le message est traité si le message sortant, comme ceci:
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;
}
J'ai créé une méthode d'exposition de service Web avec un utilisateur et un mot de passe params comme en-tête comme ceci:
@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);
}
}
Essayez de le compiler et de tester généré à partir de la classe WSDL. J'espère que ça aide.
J'ai trouvé cette réponse:
JAX-WS - Ajout de SOAP Headers
Fondamentalement, vous ajoutez -XadditionalHeaders aux options du compilateur et les objets dans les en-têtes apparaissent également dans votre code généré en tant que paramètres de la méthode.
Si vous utilisez maven et que le plug-in jaxws-maven-plugin vous n'avez qu'à ajouter le drapeau xadditionalHeaders à true et le client sera généré avec les méthodes qui ont les en-têtes en entrée.
https://jax-ws-commons.Java.net/jaxws-maven-plugin/wsimport-mojo.html#xadditionalHeaders