I\'m writing a fat client that makes use of a SOAP service for some features (bug reporting etc.)
I\'ve got JAX-WS working fine, but by default (in netbeans at least
I replaced the WSDL location before building the client jar here it goes.
<copy todir="@{dest-dir}/@{dir-package}" verbose="@{verbose}">
<fileset dir="@{dir-wsdl}" includes="*.wsdl,*.xsd" />
</copy>
<echo message="Replacing Service to point to correct WSDL path..." />
<replaceregexp match="new URL(.*)" replace='Class.class.getResource("@{name-wsdl}");' flags="gm">
<fileset dir="@{source-dest-dir}">
<include name="@{dir-package}/*Service.java" />
</fileset>
</replaceregexp>
<replaceregexp match="catch (.*)" replace='catch (Exception ex) {' flags="gm">
<fileset dir="@{source-dest-dir}">
<include name="@{dir-package}/*Service.java" />
</fileset>
</replaceregexp>
Here's my hack-y workaround.
I unpack the WSDL from the jar and write it to a file near the jar:
File wsdl = new File("../lib/service.wsdl");
InputStream source = getClass().getResource("resources/service.wsdl").openStream();
FileOutputStream out = new FileOutputStream(wsdl);
byte[] buffer = new byte[512];
int read;
while((read = source.read(buffer)) >= 0) {
out.write(buffer, 0, read);
}
Then point the service classes to file:../lib/service.wsdl
.
This works, but I'd appreciate if anyone can show me a more elegant solution.
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:
wget --output-document=wsdl_raw.xml $WSDL_URL
xmllint --format wsdl_raw.xml > wsdl.xml
for nice formatting./cxf/bin/wsdl2java -d output/ -client -validate wsdl.xml
and import into your projectVerify 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!).
I stumbled upon the same issue. The JAXWS generate client code uses the MyService.class.getResource(".")
trick to load the wsdl file... but after testing this only seems to work if the class file is in a directory on the filesytem. If the class file is in a JAR this call returns null for the URL.
It sounds like a bug in the JDK since if you build your URL like this:
final URL url = new URL( MyService.class.getResource( MyService.class.getSimpleName() + ".class"), "myservice.wsdl");
then it also works if the class and wsdl are bundled in a jar.
I guess most people will actually bundle in a jar!
If your classpath has "." in it, then Class.getResource(".") will return the URL of the directory from which you executed the java command. Else, it will return a null. Adjust the wsdllocation accordingly.
No need to complicate anything, just use jar classloader
ClassLoader cl = SomeServiceImplService.class.getClassLoader();
SERVICE_WSDL_LOCATION = cl.getResource("META-INF/wsdls/service.wsdl");
Try it!