Here is one which works for me (in particular, via http and https). Case of JAX-WS of Oracle JDK 1.8.0_51 working with classes created by Apache CXF 3.1.1.
Note that the remote WSDL is obtained only on first invocation in any case. Depending on the usage pattern (long running program) this may be entirely acceptable.
The basics:
- Download the WSDL from the remote host and store as file:
wget --output-document=wsdl_raw.xml $WSDL_URL
- You may want to
xmllint --format wsdl_raw.xml > wsdl.xml
for nice formatting
- Generate client classes using the command line tool:
./cxf/bin/wsdl2java -d output/ -client -validate wsdl.xml
and import into your project
Verify that service definitions for both http and https exist in the WSDL file. In my case, the provider did not have one for https (but did accept https traffic), and I had to add it manually. Something along these lines should be in the WSDL:
<wsdl:service name="fooservice">
<wsdl:port binding="tns:fooserviceSoapBinding" name="FooBarWebServicePort">
<soap:address location="http://ws.example.com/a/b/FooBarWebService"/>
</wsdl:port>
</wsdl:service>
<wsdl:service name="fooservice-secured">
<wsdl:port binding="tns:fooserviceSoapBinding" name="FooBarWebServicePort">
<soap:address location="https://ws.example.com/a/b/FooBarWebService"/>
</wsdl:port>
</wsdl:service>
CXF should have generated a class that implements javax.xml.ws.Service
called for example Fooservice
, with appropriate constructors:
public class Fooservice extends Service {
public Fooservice(URL wsdlLocation) {
super(wsdlLocation, SERVICE);
}
public Fooservice(URL wsdlLocation, QName serviceName) {
super(wsdlLocation, serviceName);
}
public Fooservice() {
super(WSDL_LOCATION, SERVICE);
}
...etc...
Somewhere in your code (here, some Groovy for easier reading), you initialize the above Service
instance, then invoke a port. Here, depending on the flag called secure
, we use https or http:
static final String NAMESPACE = 'com.example.ws.a.b'
static final QName SERVICE_NAME_HTTP = new QName(NAMESPACE, 'fooservice')
static final QName SERVICE_NAME_HTTPS = new QName(NAMESPACE, 'fooservice-secured')
Fooservice wsService
File wsdlfile = new File('/somewhere/on/disk/wsdl.xml')
// If the file is missing there will be an exception at connect
// time from sun.net.www.protocol.file.FileURLConnection.connect
// It should be possible to denote a resource on the classpath
// instead of a file-on-disk. Not sure how, maybe by adding a handler
// for a 'resource:' URL scheme?
URI wsdlLocationUri = java.nio.file.Paths(wsdlfile.getCanonicalPath()).toUri()
if (secure) {
wsService = new Fooservice(wsdlLocationUri.toURL(), SERVICE_NAME_HTTPS)
}
else {
wsService = new Fooservice(wsdlLocationUri.toURL(), SERVICE_NAME_HTTP)
}
SomeServicePort port = wsService.getSomeServicePort()
port.doStuff()
The alternative, which downloads the WSDL on a connection that is separate from the connection used for service invocation (use tcpdump -n -nn -s0 -A -i eth0 'tcp port 80'
to observe the traffic) is to just do:
URI wsdlLocationUri
if (secure) {
wsdlLocationUri = new URI('https://ws.example.com/a/b/FooBarWebService?wsdl')
}
else {
wsdlLocationUri = new URI('http://ws.example.com/a/b/FooBarWebService?wsdl')
}
Fooservice wsService = new Fooservice(wsdlLocationUri.toURL(), SERVICE_NAME_HTTP)
SomeServicePort port = wsService.getSomeServicePort()
port.doStuff()
Note that this actually properly uses https if the wsdlLocationUri
specifies https, in spite of the fact that the wsService
has been initialized with SERVICE_NAME_HTTP
. (Not sure why - does the service use the scheme employed for retrieving the WSDL resource?)
And that's about it.
For debugging the connection, pass:
-Dcom.sun.xml.internal.ws.transport.http.client.HttpTransportPipe.dump=true
-Dcom.sun.xml.internal.ws.transport.http.HttpAdapter.dump=true
to the JVM on the command line. This will write information from the http connection code to stdout (unfortunately NOT to java.util.logging
. Oracle, please!).