The best option is to use jax-ws-catalog.xml
When you compile the local WSDL file , override the WSDL location and set it to something like
http://localhost/wsdl/SOAService.wsdl
Don't worry this is only a URI and not a URL , meaning you don't have to have the WSDL available at that address.
You can do this by passing the wsdllocation option to the wsdl to java compiler.
Doing so will change your proxy code from
static {
URL url = null;
try {
URL baseUrl;
baseUrl = com.ibm.eci.soaservice.SOAService.class.getResource(".");
url = new URL(baseUrl, "file:/C:/local/path/to/wsdl/SOAService.wsdl");
} catch (MalformedURLException e) {
logger.warning("Failed to create URL for the wsdl Location: 'file:/C:/local/path/to/wsdl/SOAService.wsdl', retrying as a local file");
logger.warning(e.getMessage());
}
SOASERVICE_WSDL_LOCATION = url;
}
to
static {
URL url = null;
try {
URL baseUrl;
baseUrl = com.ibm.eci.soaservice.SOAService.class.getResource(".");
url = new URL(baseUrl, "http://localhost/wsdl/SOAService.wsdl");
} catch (MalformedURLException e) {
logger.warning("Failed to create URL for the wsdl Location: 'http://localhost/wsdl/SOAService.wsdl', retrying as a local file");
logger.warning(e.getMessage());
}
SOASERVICE_WSDL_LOCATION = url;
}
Notice file:// changed to http:// in the URL constructor.
Now comes in jax-ws-catalog.xml. Without jax-ws-catalog.xml jax-ws will indeed try to load the WSDL from the location
http://localhost/wsdl/SOAService.wsdl
and fail, as no such WSDL will be available.
But with jax-ws-catalog.xml you can redirect jax-ws to a locally packaged WSDL whenever it tries to access the WSDL @
http://localhost/wsdl/SOAService.wsdl
.
Here's jax-ws-catalog.xml
<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog" prefer="system">
<system systemId="http://localhost/wsdl/SOAService.wsdl"
uri="wsdl/SOAService.wsdl"/>
</catalog>
What you are doing is telling jax-ws that when ever it needs to load WSDL from
http://localhost/wsdl/SOAService.wsdl
, it should load it from local path wsdl/SOAService.wsdl.
Now where should you put wsdl/SOAService.wsdl and jax-ws-catalog.xml ? That's the million dollar question isn't it ?
It should be in the META-INF directory of your application jar.
so something like this
ABCD.jar
|__ META-INF
|__ jax-ws-catalog.xml
|__ wsdl
|__ SOAService.wsdl
This way you don't even have to override the URL in your client that access the proxy. The WSDL is picked up from within your JAR, and you avoid having to have hard-coded filesystem paths in your code.
More info on jax-ws-catalog.xml
http://jax-ws.java.net/nonav/2.1.2m1/docs/catalog-support.html
Hope that helps
I almost can't believe that this was such a difficult problem to solve!
I've been googling like mad to find a solution to exactly this problem! Then I've been struggling really hard to find a solution on my own. By debugger-stepping through the java-6-openjdk's default javax.xml.ws.spi.Provider implementation (the "factory" in the JRE that creates the javax.xml.ws.Endpoint objects that you use for publishing web services) I finally learnt some things, which helped me to craft a solution that at least works in Java SE, at least in my current JRE, which is:
java version "1.6.0_33"
OpenJDK Runtime Environment (IcedTea6 1.13.5) (6b33-1.13.5-1ubuntu0.12.04)
OpenJDK Server VM (build 23.25-b01, mixed mode)
Whether this solution is usable in Java EE I don't know yet.
Here is how I solved it:
package myservice;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Arrays;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.xml.ws.Endpoint;
public class App
{
private static final String MY_SERVICE_XSD = "/wsdl/MyService.xsd";
public static void main( String[] args )
{
Endpoint ep = Endpoint.create(new MyEndpointImpl());
ep.setMetadata(Arrays.asList(sourceFromResource(MY_SERVICE_XSD)));
ep.publish("http://localhost:8080/svc/hello");
}
private static Source sourceFromResource(String name) {
URL resource = App.class.getResource(name);
String systemId = resource.toExternalForm();
InputStream inputStream;
try {
inputStream = resource.openStream();
} catch (IOException e) {
throw new RuntimeException("Failed to create InputStream from resource \""+ name +"\"", e);
}
return new StreamSource(inputStream, systemId);
}
}
The crucial thing is that I first use method Endpoint#create (not Endpoint#publish) to get an unpublished Endpoint. Then I add the XSD-file as "meta data" to the (still unpublished) Endpoint (code "ep.setMetaData(...)"). Then I publish the endpoint (code "ep.publish(...)").
Now when I access http://localhost:8080/svc/hello?wsdl
I get:
<definitions targetNamespace="http://somewhere.net/my/namespace" name="MyService">
<types>
<xsd:schema>
<xsd:import namespace="http://somewhere.net/my/namespace"
schemaLocation="http://localhost:8080/svc/hello?xsd=1"/>
</xsd:schema>
</types>
...
</definitions>
and my XSD-file is available from http://localhost:8080/svc/hello?xsd=1
!
Note that my MyService.wsdl file on disk still contains:
<xsd:schema>
<xsd:import namespace="http://somewhere.net/my/namespace"
schemaLocation="MyService.xsd"></xsd:import>
</xsd:schema>
Best Answer
Where are you placing the jax-ws-catalog.xml and the WSDL files ?
You mentioned that you're using a WAR, but within a WAR the location of jax-ws-catalog.xml depends on whether you're trying to access it as a WS client or as a WS Endpoint publisher (server).
If you're accessing external Web Services from within your WAR, then your jax-ws-catalog.xml needs to go in app.war/WEB-INF/classes/META-INF directory, Alternatively if you have a contract first web service deployed within your app, and that has the @WSDLLocation annotation, then for that WSDL, jax-ws looks for jax-ws-catalog.xml in app.jar/META-INF
So for client WSDL location app.war/WEB-INF/classes/META-INF/jax-ws-catalog.xml for contract first WS Endpoints with @WSDLLocation annotation the app.war/META-INF
If you want to do both, i.e. publish (contract first) as well as consume Web Services, then you need to put jax-ws-catalog.xml + WSDLs in both the locations, i.e. app.jar/WEB-INF/classes/META-INF and app.jar/META-INF
I have successfully used the above approach in many of my projects, which simultaneously publish as well as consume web services, and I can guarantee it works as expected.