JAX-WS Loading WSDL from jar
Asked Answered
C

13

46

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) it fetches the WSDL from the remote server every time the service is initialized. I expect this helps provide some versioning support etc., but it's not what I want.

I've added the wsdllocation arg to wsimport to point the generated classes to a local resource. The following snippet is the URL loading for the WSDL resource from ApplicationService.java.

baseUrl = net.example.ApplicationService.class.getResource(".");
url = new URL(baseUrl, "service.wsdl");

I'm pretty sure that should have no problems pointing to a resource stored inside a jar in the net/example/resources package, and the jar itself is constructed as expected. However the service will not load... specifically, I get a NullPointerException when I call ApplicationService.getPort();

Is this possible? or just a wild goose chase?

Children answered 19/4, 2009 at 3:20 Comment(0)
I
51

Yes this is most definitely possible as I have done it when creating clients through javax.xml.ws.EndpointReference, a WS-A related class. I have added a classpath reference to the WSDL to the WS-A EndPointReference and the Metro implementation of JAX-WS loaded it just fine. Whether loading the WSDL from the WS-A EndPointReference or from a file or http URL, your JAX-WS implementation should use the same WSDL parsing code as all you are doing is resolving URLs.

The best approach for you is probably to do something like the following:

URL wsdlUrl = MyClass.class.getResource(
            "/class/path/to/wsdl/yourWSDL.wsdl");

Service yourService= Service.create(
            wsdlUrl,
            ...);

Where ... represents the QName of a WSDL service inside of your WSDL. Now the important thing to remember is that your WSDL needs to be complete and valid. This means that if your WSDL imports XSD files or other WSDLs, the URLs must be correct. If you included your imported WSDL and XSDs in the same JAR as the WSDL file, you should use relative URLs for the imports and keep all of your imports in the same JAR file. The JAR URL handler does not treat the relative URLs as relative with respect to the classpath but rather to relative within the JAR file so you cannot have imports in your WSDL that run across JARs unless you implement a custom URL handler and your own prefix to do classpath based resolution of the imports. If your WSDL imports external resources, that is OK, but you are signing yourself up for maintenance issues if those resources ever move. Even using a static copy of the WSDL from your classpath is contrary to the spirit of WSDL, Web services, and JAX-WS, but there are times when it is necessary.

Finally, if you embed a static WSDL, I suggest that you at least make the service endpoint configurable for testing and deployment purposes. The code to reconfigure the endpoint of your Web service client is as follows:

  YourClientInterface client = yourService.getPort(
            new QName("...", "..."),
            YourClientInterface.class);
  BindingProvider bp = (BindingProvider) client;
  bp.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY,
                "http://localhost:8080/yourServiceEndpoint");
Impedimenta answered 14/5, 2009 at 14:14 Comment(4)
It seems for me that needs to be WSBindingProvider, not BindingProvider.Stercoricolous
On second thought, no, BindingProvider is right there, and that's what it needs to be. It's just that my IDE likes to find strange internal classes to use, and I like to type strange things that make it hard for my IDE to find the classes it should find, like BindingProvider.Stercoricolous
unfortunately, it doesn't work on Java9 when building a custom runtime image with jlinkEndocardium
didnt work for meEsque
D
17

Atleast for the recent JAX-WS you don't need to do any schema catalogs or programmatic wsdl location setting IF you put the WSDL in the JAR and then set wsimport wsdlLocation to the relative resource path of the WSDL in the JAR. That is JAX-WS uses Java's builtin Class.getResource to load the WSDL.

If your using Maven its something like:

  <plugin>
    <groupId>org.jvnet.jax-ws-commons</groupId>
    <artifactId>jaxws-maven-plugin</artifactId>
    <version>2.3</version>
    <executions>
      <execution>
        <goals>
          <goal>wsimport</goal>
        </goals>
        <!-- Following configuration will invoke wsimport once for each wsdl. -->
        <configuration>
            <!--- VERY IMPORTANT THAT THE PATH START WITH '/' -->
    <wsdlLocation>/com/adamgent/ws/blah.wsdl</wsdlLocation>
    <wsdlDirectory>${basedir}/src/main/resources/com/adamgent/ws</wsdlDirectory>
    <wsdlFiles><wsdlFile>blah.wsdl</wsdlFile></wsdlFiles>
       </configuration>
      </execution>
    </executions>
  </plugin>

For the example above you would thus put the WSDL using Maven project layout here src/main/resources/com/adamgent/ws.

Make sure the WSDL gets in the JAR for Maven like:

<build>
      <resources>
        <resource>
          <directory>src/main/resources</directory>
        </resource>
      </resources> ....

Now your wsimport generated code and WSDL are in a self contained JAR. To use the service you do not have to set the WSDL location and is as easy as:

BlahService myService = new BlayService_Service().getBlahServicePort();

It should be trivial to map this over to ANT's wsimport.

Dezhnev answered 19/8, 2013 at 22:1 Comment(3)
If this wsdl refers to other xsds via relative paths, will that also be picked from the jar ? eg: wsdl refers to an xsd at schemaLocation="../../xsds/MyXsd.xsd"Ravelment
The <!--- VERY IMPORTANT THAT THE PATH START WITH '/' --> part is what did the trick. Other answers suggest prefixing the path with classpath: or using the path without a prefixing /, but that doesn't work.Substrate
<wsdlLocaltion> only works if generating from 1 WSDL file. If <wsdlFiles> contains more than 1 <wsdlFile> it does not work.Nomothetic
H
6

Maybe a bit late, but I found a quite simple solution which worked to solve this problem, but this involved a change in the generated code of the Service class:

If the following line in the Service class

baseUrl = net.example.ApplicationService.class.getResource(".");

is changed to

baseUrl = net.example.ApplicationService.class.getResource("");

it works fine even with a WSDL that is packed within a JAR. Not sure about the exact supposed behaviour of getResource() in either of this cases, but I didn't experience any problems with this approach so far, on multiple OS and Java versions.

Helbon answered 20/11, 2009 at 14:40 Comment(1)
I'm trying this now a few years later and am sorry to report that it is not working for me. getResource(".") AND getResource("") both return nullRosalia
B
5

What you describe is a bug in JAX-WS: JAX_WS-888 - Wrong code for resolving the URL for a custom wsdlLocation.

It was fixed for V2.2, so just setting wsdlLocation, as you write, should work now.

Burghley answered 9/4, 2013 at 21:57 Comment(0)
O
4

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.

Ostiole answered 6/5, 2009 at 17:40 Comment(0)
E
3

An other answer is to use the

new Service(wsdllocation, servicename );

to get the Service Object.

This is how I solved the problem.

Ephrayim answered 8/1, 2010 at 7:11 Comment(0)
I
2

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!

Involucre answered 9/6, 2009 at 14:35 Comment(1)
I don't think this will work... the getResource call finds itself so when you're composing the URL you're trying to attach the WSDL part of the URL to the end of the Service part of the URL. I think the correct solution is: com.wsdl.MyClass.getResource("MyWebService.wsdl");Rosalia
S
2

I replaced the WSDL location before building the client jar here it goes.

  1. Copy the WSDL's to classes dir.
  2. Replace the Service class refer to WSDL using classpath.
  3. build the client stubs.
  4. jar the stubs.
<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>
Sepsis answered 24/6, 2011 at 9:22 Comment(0)
P
2

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!

Pierian answered 29/11, 2018 at 14:27 Comment(1)
Tried a few solutions that were more complicated and then found this one. ...Worked like a charm.Britnibrito
C
1

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.

Children answered 19/4, 2009 at 15:8 Comment(0)
D
1

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!).

Deteriorate answered 2/9, 2015 at 16:9 Comment(0)
R
0

My solution was to modify the generated Service. You have to change wsdlLocation in the header annotation and the instantion block looks like this:

    static {
    URL url = null;
    url = com.ups.wsdl.xoltws.ship.v1.ShipService.class.getResource("Ship.wsdl");
    SHIPSERVICE_WSDL_LOCATION = url;
    }

I place the wsdl file in the bin directory next to the ShipService class

Rosalia answered 22/5, 2014 at 21:21 Comment(0)
D
0

Although you can get it to work with some manipulations, I would recommend not doing it and keeping it the way you have right now.

Web Service endpoint providers should provide a WSDL as part of their contract. The code you generate should be pulling from the WSDL from the server itself.

At deployment on WebSphere you can change the endpoints to other endpoints from the deployment UI. Other application servers you may need to find out the vendor specific binding XML to do it it.

It only happens on initialization so the impact to your overall application should be negligible.

Dornick answered 24/8, 2015 at 19:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.