Compile and invoke your webservice client (generated by XFire) in a running JVM html

Compile and invoke your webservice client (generated by XFire) in a running JVM

Generating XFire client files isn’t that special, but with Java 6 it is possible to compile those files immediately and invoke the webservice within one application. When I told this idea to a colleague he was a bit worried, because there are tools out there that can do this for you. But it’s just an exercise. I don’t play enough with XFire and testing new Java 6 features is also something that’s on my list for a very long time.
What I want to do is provide a WSDL url, generate the client files, compile them and invoke a method on that client. Of course without any manual steps!

In this blog we take the following steps:

  • dissect the XFire command line WsGen application
  • generate the client files
  • compile the client files
  • invoke the web service

Dissecting WsGen

WsGen is the class you invoke when client files need to be generated from a WSDL. A detailed description of WsGen and the command parameters can be found here. I wanted to generate the files from within my application (without doing a Runtime.exec), so I had to find out how the XFire guys did it. Luckily XFire is open source and their subversion repository is browsable. What WsGen basically does is checking the command line parameters, returning errors or print a help message. Well that’s just what we don’t need for now, let’s throw that away. After removing that code this is the code we need:


Wsdl11Generator generator = new Wsdl11Generator();
generator.setOutputDirectory(outputDirectory);
generator.setWsdl(wsdl);
generator.setExplicitAnnotation(explicit);
generator.setOverwrite(overwrite);
generator.setGenerateServerStubs(serverStubs);

try {
    generator.generate();
catch (Exception e) {
    throw new BuildException(e);
}

The overwrite, explicitAnnotation and generateServerStubs are not needed for this example, so we can set them to false. That leaves us with the output location and the WSDL url.

Generate the client files

When we run this piece of code generation code it generates a list of files deep inside some directories. Since our compiler needs every file (with its location) we have to list all the files. Now I can show you something I learned in school (yes that happens and I think it is so special that I wanted to tell it). When things with trees (a directory tree) need to be done you can do it the difficult way or the recursive way. Let’s do it the recursive way. I have a List of File objects that needs to be filled recursively:


private void listFilesRecursive(List<File> fileList, final File dir) {
    File[] files = dir.listFiles();
    for (File f : files) {
        if (f.isDirectory()) {
            listFilesRecursive(fileList, f);
        else {
            if (f.getName().endsWith(".java")) {
                fileList.add(f);
            }
        }
    }
}

The function calls itself when it finds a directory and the stop-condition is a file that is not a directory, when that file name ends with .java we add it to fileList and otherwise nothing happens.

Compile the client files

The list of files must be compiled in the next step. I found this great blog entry about compiling files in Java 6.
However, one very important part is missing. When you watch closely you see that the cl variable is not defined. This is where things can get horribly wrong. I got so stuck that I almost gave up. The Java class loader is a strange thing. There actually is a whole hierarchy that defines which class loader is loading your files. When you compiled your classes with Class Loader Y and you want to load them with Class Loader X it might not happen. It’s very complicated and I couldn’t find much information about the class loaders.

After adding the class loader
an
d my listFilesRecursive method to the Juixe code I ended up with this piece of code:

JavaCompiler jc = ToolProvider.getSystemJavaCompiler();
ClassLoader cl = Thread.currentThread().getContextClassLoader();
StandardJavaFileManager sjfm = jc.getStandardFileManager(null, null, null);

File dir = new File(filesLocation);

List<File> files = new ArrayList<File>();
listFilesRecursive(files, dir);

Iterable<? extends JavaFileObject> fileObjects = sjfm.getJavaFileObjects(files.toArray(new File[]{}));
jc.getTask(null, sjfm, null, null, null, fileObjects).call();
sjfm.close()

 

Invoking the Web Service

This last step is a bit strange because I haven’t really thought about what I want to do now. A possible idea is to extract all the methods from the webservice and display the input parameters on a form on a web page. This is a lot of work and I’m already creating an application that already exists.

I make use of a publicly available web service. The WSDL can be found here.

When you access a web service you invoke a method on a Client object. What we know from XFire is that a the class name of the client ends with Client. So we look up the client class and make an instance of that object:


String cn = generator.getClientClassName();
Object client = Class.forName(cn).newInstance();

getClientClassName is a method that searches for the client class. The client class has a method called getEndpoints. This method must be invoked to receive a Collection with end points:


Class clientClass = client.getClass();
Method method = clientClass.getMethod("getEndpoints");
Collection<Endpoint> col = (Collectionmethod.invoke(client);

System.out.println("End points:");
for (Endpoint e : col) {
    System.out.println("  " + e.getName());
}

The web services I tried all returned an end points that ends with LocalEndPoint and another one, we need the other one. This method/endpoint needs to be invoked:


        Method call = clientClass.getMethod("getMidnightTraderSoap");
        Object o = call.invoke(client);

        System.out.println(o);
        Method[] methods = o.getClass().getMethods();

        for (Method method1 : methods) {
            System.out.println("   :" + method1);
        }

From this method we need to get the correct method(s). Let’s remove the methods that belong to java.lang.Object and the methods that do something with java.lang.reflect are also not needed
We end up with the following list:

   :public final com.strikeiron.ServiceInfoOutput $Proxy21.getServiceInfo(com.strikeiron.ws.LicenseInfo,javax.xml.ws.Holder)
   :public final void $Proxy21.getRemainingHits(com.strikeiron.ws.LicenseInfo)
   :public final com.strikeiron.StatusCodeOutput $Proxy21.getAllStatuses(com.strikeiron.ws.LicenseInfo,javax.xml.ws.Holder)
   :public final com.strikeiron.MidnightTraderOutput
$Proxy21.lookupFinancialNews(com.strikeiron.MidnightTraderRequest,com.strikeiron.ws.LicenseInfo,javax.xml.ws.Holder)

These methods

can also be invoked the way I described earlier, you just have to decide where to get your parameters from and what you want to do with the result.

Conclusion

At first sight it seems a waste of time to generate something that already exists. But these kind of exercises give a good insight in what is possible with Java 6 and I learned a lot about how web services work. I also think it’s really cool that you can make changes to the classes in your JVM. It’s like changing the wheels of your car when you run at full speed on a motor way.

Sources

http://xfire.codehaus.org/Client+and+Server+Stub+Generation+from+WSDL
http://fisheye.codehaus.org/browse/~raw,r=2183/xfire/trunk/xfire/xfire-generator/src/main/org/codehaus/xfire/gen/WsGen.java
http://www.juixe.com/techknow/index.php/2006/12/13/java-se-6-compiler-api/