Thursday, 22 May 2008

JAX-WS 2.1 - Dynamically changing endpoint address for Yahoo Webservices

At Shopzilla I am currently developing an application that uses Yahoo Webservices (EWS 4.0). One frustration I had today was that they have a special location service that you need to call to get IP address before you can call any other service. I have used wsimport from JDK6 to generate JAXB annotated classes from WSDL document but then I realised that all WSDL documents apart from LocationService.wsdl have a fake port definition in their WSDL file. Something like. USE_ADDRESS_FROM_LOCATION_SERVICE. Now JAX-WS relies heavily on WSDL also at runtime so I couldnt dynamically change the endpoint address. I thought about downloading WSDL file and replacing this string: USE_ADDRESS_FROM_LOCATION_SERVICE with an actual endpoint address but this solution seemed to be hacked and I thought to my self -> there must be a better way of doing this. After researching, poking around and googling I found it!

In JAX-WS every port which you can get from XXXServiceService is a proxy that implements BindingProvider.

You have to cast your port to BindingProvider and get public abstract java.util.Map getResponseContext(); from it. Request context is just a map mapping a string to an object.
When you do that you have to put BindingProvider.ENDPOINT_ADDRESS_PROPERTY property to that request context with the full endpoint address and voila! It works like a charm!


Anonymous said...

Your findings are in line with mine. What I did is, I did NOT use wsimport as I am still using JDK1.5, and created the Service instance as svc = Service.create(QName serviceName) and used svc to add the desired endpoint as svc.addPort(portName, SOAPBinding.SOAP11HTTP_BINDING, endpointAddress). This worked lika charm as well, to sending SOAP message using "Dispatch" API. Of course, prior to doing this, we already received a hard copy of WSDL file to know the Message format the service is expecting. Previously, I used Service.create(WSDL_location_URL), which in turn requires the WSDL file to be available from Service Hosted web server or locally on the disc. I had to resort to using NO WSDL file at all, as the first time fetched WSDL had a different endpoint address in it than where the WSDL gooten from at runtime. And, it seems, JAX-WS "Service.create(WSDL-loc-URL) fails to create a service, if the actual Web Service is NOT hosted on the same host as where the WSDl is being served from. Any thoughts on this ?.

freedev said...

After you have created the port try using the following line:
((BindingProvider)port).getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, "");

this worked very well when the WS is hosted on a different machine.

Anonymous said...

My comment may be a little bit off-topic, but do you have any idea if Service.create() can be instructed to use HTTP POST instead of HTTP GET when connecting to retrieve the WSDL file? For some strange reason, the remote service I'm accessing is configured to accept POST only ...


Liviu said...

Never mind my question, I figured it out in the end. As someone may face the same dilema, here is the solution:

String address = http://"+serverName+":"+serverPort+"/soap/billing";
QName serviceName = new QName("urn:billing1.xsd", "billing1");
QName portName = new QName("urn:billing1.xsd", "billingPort");

//URL url = new URL(address);
//Service service = Service.create(url, serviceName); // this calls HTTP GET
Service service = Service.create(serviceName);
service.addPort(portName, SOAPBinding.SOAP11HTTP_BINDING, address);
Dispatch<SOAPMessage> dispatch = service.createDispatch(portName, SOAPMessage.class, Service.Mode.MESSAGE);
Map<String, Object> reqContext = dispatch.getRequestContext();
reqContext.put(MessageContext.HTTP_REQUEST_METHOD, "POST");