JAX-WS client : what's the correct path to access the local WSDL?
Solution 1:
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.wsdland 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
Solution 2:
One other approach that we have taken successfully is to generate the WS client proxy code using wsimport (from Ant, as an Ant task) and specify the wsdlLocation attribute.
<wsimport debug="true" keep="true" verbose="false" target="2.1" sourcedestdir="${generated.client}" wsdl="${src}${wsdl.file}" wsdlLocation="${wsdl.file}">
</wsimport>
Since we run this for a project w/ multiple WSDLs, the script resolves the $(wsdl.file} value dynamically which is set up to be /META-INF/wsdl/YourWebServiceName.wsdl relative to the JavaSource location (or /src, depending on how you have your project set up). During the build proess, the WSDL and XSDs files are copied to this location and packaged in the JAR file. (similar to the solution described by Bhasakar above)
MyApp.jar
|__META-INF
|__wsdl
|__YourWebServiceName.wsdl
|__YourWebServiceName_schema1.xsd
|__YourWebServiceName_schmea2.xsd
Note: make sure the WSDL files are using relative refrerences to any imported XSDs and not http URLs:
<types>
<xsd:schema>
<xsd:import namespace="http://valueobject.common.services.xyz.com/" schemaLocation="YourWebService_schema1.xsd"/>
</xsd:schema>
<xsd:schema>
<xsd:import namespace="http://exceptions.util.xyz.com/" schemaLocation="YourWebService_schema2.xsd"/>
</xsd:schema>
</types>
In the generated code, we find this:
/**
* This class was generated by the JAX-WS RI.
* JAX-WS RI 2.2-b05-
* Generated source version: 2.1
*
*/
@WebServiceClient(name = "YourService", targetNamespace = "http://test.webservice.services.xyz.com/", wsdlLocation = "/META-INF/wsdl/YourService.wsdl")
public class YourService_Service
extends Service
{
private final static URL YOURWEBSERVICE_WSDL_LOCATION;
private final static WebServiceException YOURWEBSERVICE_EXCEPTION;
private final static QName YOURWEBSERVICE_QNAME = new QName("http://test.webservice.services.xyz.com/", "YourService");
static {
YOURWEBSERVICE_WSDL_LOCATION = com.xyz.services.webservice.test.YourService_Service.class.getResource("/META-INF/wsdl/YourService.wsdl");
WebServiceException e = null;
if (YOURWEBSERVICE_WSDL_LOCATION == null) {
e = new WebServiceException("Cannot find '/META-INF/wsdl/YourService.wsdl' wsdl. Place the resource correctly in the classpath.");
}
YOURWEBSERVICE_EXCEPTION = e;
}
public YourService_Service() {
super(__getWsdlLocation(), YOURWEBSERVICE_QNAME);
}
public YourService_Service(URL wsdlLocation, QName serviceName) {
super(wsdlLocation, serviceName);
}
/**
*
* @return
* returns YourService
*/
@WebEndpoint(name = "YourServicePort")
public YourService getYourServicePort() {
return super.getPort(new QName("http://test.webservice.services.xyz.com/", "YourServicePort"), YourService.class);
}
/**
*
* @param features
* A list of {@link javax.xml.ws.WebServiceFeature} to configure on the proxy. Supported features not in the <code>features</code> parameter will have their default values.
* @return
* returns YourService
*/
@WebEndpoint(name = "YourServicePort")
public YourService getYourServicePort(WebServiceFeature... features) {
return super.getPort(new QName("http://test.webservice.services.xyz.com/", "YourServicePort"), YourService.class, features);
}
private static URL __getWsdlLocation() {
if (YOURWEBSERVICE_EXCEPTION!= null) {
throw YOURWEBSERVICE_EXCEPTION;
}
return YOURWEBSERVICE_WSDL_LOCATION;
}
}
Perhaps this might help too. It's just a different approach that does not use the "catalog" approach.
Solution 3:
For those who are still coming for solution here, the easiest solution would be to use <wsdlLocation>
, without changing any code. Working steps are given below:
- Put your wsdl to resource directory like :
src/main/resource
-
In pom file, add both wsdlDirectory and wsdlLocation(don't miss / at the beginning of wsdlLocation), like below. While wsdlDirectory is used to generate code and wsdlLocation is used at runtime to create dynamic proxy.
<wsdlDirectory>src/main/resources/mydir</wsdlDirectory> <wsdlLocation>/mydir/my.wsdl</wsdlLocation>
-
Then in your java code(with no-arg constructor):
MyPort myPort = new MyPortService().getMyPort();
-
For completeness, I am providing here full code generation part, with fluent api in generated code.
<plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>jaxws-maven-plugin</artifactId> <version>2.5</version> <dependencies> <dependency> <groupId>org.jvnet.jaxb2_commons</groupId> <artifactId>jaxb2-fluent-api</artifactId> <version>3.0</version> </dependency> <dependency> <groupId>com.sun.xml.ws</groupId> <artifactId>jaxws-tools</artifactId> <version>2.3.0</version> </dependency> </dependencies> <executions> <execution> <id>wsdl-to-java-generator</id> <goals> <goal>wsimport</goal> </goals> <configuration> <xjcArgs> <xjcArg>-Xfluent-api</xjcArg> </xjcArgs> <keep>true</keep> <wsdlDirectory>src/main/resources/package</wsdlDirectory> <wsdlLocation>/package/my.wsdl</wsdlLocation> <sourceDestDir>${project.build.directory}/generated-sources/annotations/jaxb</sourceDestDir> <packageName>full.package.here</packageName> </configuration> </execution> </executions>