How to avoid the need to specify the WSDL location in a CXF or JAX-WS generated webservice client?

When I generate a webservice client using wsdl2java from CXF (which generates something similar to wsimport), via maven, my services starts with codes like this:

@WebServiceClient(name = "StatusManagement", 
                  wsdlLocation = "c:/some_absolute_path_to_a_wsdl_file.wsdl",
                  targetNamespace = "http://tempuri.org/") 
public class StatusManagement extends Service {

    public final static URL WSDL_LOCATION;
    public final static QName SERVICE = new QName("http://tempuri.org/", "StatusManagement");
    public final static QName WSHttpBindingIStatus = new QName("http://tempuri.org/", "WSHttpBinding_IStatus");
    static {
        URL url = null;
        try {
            url = new URL("c:/some_absolute_path_to_a_wsdl_file.wsdl");
        } catch (MalformedURLException e) {
            System.err.println("Can not initialize the default wsdl from c:/some_absolute_path_to_a_wsdl_file.wsdl");
            // e.printStackTrace();
        }
        WSDL_LOCATION = url;
    }

The hardcoded absolute path really sucks. The generated class won't work in any other computer other than mine.

The first idea is to put the WSDL file (plus everything it imports, other WSDLs and XSDs) somewhere in a jar-file and classpath it. But we want to avoid this. Since all that thing was generated by CXF and JAXB based in the WSDLs and XSDs, we see no point in needing to know the WSDL at runtime.

The wsdlLocation attribute is intended to override the WSDL location (at least this is what i readed somewhere), and it default value is "". Since we are using maven, we tried to include <wsdlLocation></wsdlLocation> inside the configuration of CXF to try to force the source generator to leave the wsdlLocation blank. However, this simply makes it ignore the XML tag because it is empty. We did a really ugly shameful hack, using <wsdlLocation>" + "</wsdlLocation>.

This changes other places too:

@WebServiceClient(name = "StatusManagement", 
                  wsdlLocation = "" + "",
                  targetNamespace = "http://tempuri.org/") 
public class StatusManagement extends Service {

    public final static URL WSDL_LOCATION;
    public final static QName SERVICE = new QName("http://tempuri.org/", "StatusManagement");
    public final static QName WSHttpBindingIStatus = new QName("http://tempuri.org/", "WSHttpBinding_IStatus");
    static {
        URL url = null;
        try {
            url = new URL("" + "");
        } catch (MalformedURLException e) {
            System.err.println("Can not initialize the default wsdl from " + "");
            // e.printStackTrace();
        }
        WSDL_LOCATION = url;
    }

So, my questions are:

  1. Does we really need a WSDL location even if all the classes were generated by CXF and JAXB? If yes, why?

  2. If we do not really need the WSDL location, what is the proper and clean way to make CXF not generate it and avoiding it entirely?

  3. What bad side effects we could get with that hack? We still can't test that to see what happens, so if someone could say in advance, it would be nice.


Solution 1:

I finally figured out the right answer to this question today.

<plugin>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-codegen-plugin</artifactId>
    <version>${cxf.version}</version>
    <executions>
        <execution>
            <id>generate-sources</id>
            <phase>generate-sources</phase>
            <configuration> 
                <sourceRoot>${project.build.directory}/generated-sources/cxf</sourceRoot>
                <wsdlOptions>
                    <wsdlOption>
                        <wsdl>${project.basedir}/src/main/resources/wsdl/FooService.wsdl</wsdl>
                        <wsdlLocation>classpath:wsdl/FooService.wsdl</wsdlLocation>
                    </wsdlOption>
                </wsdlOptions>
            </configuration>
            <goals>
                <goal>wsdl2java</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Notice that I have prefixed the value in wsdlLocation with classpath:. This tells the plugin that the wsdl will be on the classpath instead of an absolute path. Then it will generate code similar to this:

@WebServiceClient(name = "FooService", 
                  wsdlLocation = "classpath:wsdl/FooService.wsdl",
                  targetNamespace = "http://org/example/foo") 
public class Foo_Service extends Service {

    public final static URL WSDL_LOCATION;

    public final static QName SERVICE = new QName("http://org/example/foo", "Foo");
    public final static QName FooSOAPOverHTTP = new QName("http://org/example/foo", "Foo_SOAPOverHTTP");
    static {
        URL url = Foo_Service.class.getClassLoader().getResource("wsdl/FooService.wsdl");
        if (url == null) {
            java.util.logging.Logger.getLogger(Foo_Service.class.getName())
                .log(java.util.logging.Level.INFO, 
                     "Can not initialize the default wsdl from {0}", "classpath:wsdl/FooService.wsdl");
        }       
        WSDL_LOCATION = url;
    }

Note that this only works with version 2.4.1 or newer of the cxf-codegen-plugin.

Solution 2:

We use

wsdlLocation = "WEB-INF/wsdl/WSDL.wsdl"

In other words, use a path relative to the classpath.

I believe the WSDL may be needed at runtime for validation of messages during marshal/unmarshal.