Automatically generate Java Web service clients with Axis2, XFire, CXF, and Java 6, including WSDL compatibility checks
Like it or not, service-oriented architecture (SOA) is a hot topic, and SOAP-based Web services have emerged as the most common implementation of SOA. But, as James Governor put it, “SOA reality brings SOA problems.” You can mitigate these problems by creating useful Web service clients, and also by thoroughly testing your Web services on both the server side and the client side. WSDL files play a central role in both of these activities, so in this article Klaus P. Berg presents an extensible toolset that facilitates client-side WSDL processing using Gant and Groovy.
As part of a cross-platform Web service testing team responsible for testing functional aspects as well as the performance, load, and robustness of Web services, I recently realized the need for a small, easy-to-use, command-line-based solution for WSDL processing. I wanted the toolset to help testers and developers check and validate WSDL 1.1 files coming from different sources for compatibility with various Web service frameworks, as well as generating test stubs in Java to make actual calls. For the Java platform, that meant using Java 6 wsimport
, Axis2, XFire, and CXF. (We also needed an environment based on Visual Studio .Net, and C# that tested WSDL and the services themselves in a pure-Windows environment — but that’s another story not suited to an article in a Java magazine.)
We started client-side test development with XFire, but then switched to Axis2 because of changing customer requirements in our agile project. (Axis2 was considered to be more popular and widespread than XFire.) We also used ksoap2 — a lightweight Web service framework especially for the Java ME developer. We didn’t expand the toolset to use ksoap2
because it has no WSDL-to-Java code generator.
Besides being controllable via simple commands, the toolset had to be able to integrate at least the WSDL checker into an automated build environment like Ant. One solution would have been to develop everything as a set of Ant targets. But executing everything with Ant is cumbersome when tasks become more complex, and you need control structures like if-then-else
or for loops
.
Even using ant-contrib
binds you to XML-structures that are not easy to read, although you will have more functionality available. Anyhow, in the end you might need to implement some jobs as Ant tasks. All of this is possible, of course, but I was looking for a more elegant solution. Finally, I decided to use Groovy and a smart combination of Groovy plus Ant, called Gant. The components I have developed for the resulting Toolset can be divided into two groups:
- The Gant part is responsible for providing some “targets” for the tester’s everyday work, including the WSDL-checker and a Java parser/modifier component.
- The WSDL-checker part is implemented with Groovy, but callable inside an Ant environment (via Groovy’s Ant task) as part of the daily build process
That is an overview of the programming and scripting languages I used to build the Groovy and Gant Toolset. Now let’s consider the technologies in detail.
Overview of Ant, Groovy, and Gant
Apache Ant is a software tool for automating software build processes. It is similar to make
but is written in the Java language, requires the Java platform, and is best suited to building Java projects. Ant is based on an XML description of targets and their dependencies. Targets include tasks that every project needs, like clean
, compile
, javadoc
, and jar
. Ant is the de-facto standard build tool for Java, although Maven is making inroads.
Groovy is an object-oriented programming and scripting language for the Java platform, with features like those of Perl, Ruby, and Python. Groovy sources are dynamically compiled to Java bytecode that works seamlessly with your own Java code or third-party libraries. By means of the Groovy compiler, you can also produce bytecode for other Java projects. It is fair to say that I am biased towards Groovy compared to other scripting languages such as Perl or Ruby. While other people’s preferences and experiences may be different from mine, the integration between Groovy and Java code is thorough and smooth. It was also easy, coming from Java, to get familiar with the Groovy syntax. What made Groovy especially interesting for solving my problems was its integration with Ant, via AntBuilder.
Gant (shorthand for Groovy plus Ant) is a build tool and Groovy module. It is used for scripting Ant tasks using Groovy instead of XML to specify the build logic. A Gant build specification is just a Groovy script and so — to quote Gant author Russel Winder — can deliver “all the power of Groovy to bear directly, something not possible with Ant scripts.” While it might be considered a competitor to Ant, Gant relies on Ant tasks to actually do things. Really it is an alternative way of doing builds using Ant, but using a programming language, instead of XML, to specify the build rules. Consider using Gant if your Ant XML file is becoming too complex, or if you need the features and control structures of a scripting language that cannot be easily expressed using the Ant syntax.
Before you start examining the code in the next sections, here’s a word about the OS and Java environment for the tools presented in this article, as well as a preview of what you will find if you download the toolset and associated files.
What you see is what you get — toolset contents and prerequisites
All the code for the Groovy and Gant Toolset has been tested on a Windows XP and Windows 2003 Server OS with Java 5 and Java 6 using Eclipse 3.2.2 as my favorite IDE. In order to play with the toolset in its entirety, you will need to install Java 6, Axis2-1.3, XFire-1.2.6, CXF-2.0.2, Groovy-1.0, and Gant-0.3.1. But you can configure the toolset using a normal property file and exclude WSDL checks for frameworks you are not interested in, including Java 6’s wsimport
.
Installing the frameworks is simple: just extract their corresponding archive files. I assume you have either Java 5 or Java 6 running on your machine. You can use the Java 6 wsimport
WSDL checker even with Java 5; you just need Java 6’s rt.jar
and tools.jar
as an add-on in a directory of your choice. Installing Groovy and Gant usually takes a matter of minutes. If you follow my advice of placing Groovy add-ons — including the gant-0.3.1.jar
file — in your <user.home>/.groovy/lib
directory you do not even have to touch your original Groovy installation.
See this article’s Resources section for the following artifacts:
build.gant
is the Gant script that contains Gant targets with Groovy syntax.build.properties
is needed to customize the Gant script and the Groovy WSDL checker.- A set of jar files (including the Gant module) to be placed in your
<user.home>/.groovy/lib
directory to enrich Groovy for the toolset. - An Eclipse Java project called JavaParser that is used to scan the generated Axis2 stub in order to modify it for use with HTTPS, chunking, etcetera. Two
SSLProtocolFactory
classes are included with this distribution for use with Axis2 and Java 6 (orksoap2
). They will be helpful when it comes to cipher suite handling. - An Eclipse Groovy project called WSDL-Checker that contains Groovy classes that check WSDL files by calling Web service framework code generators and analyzing the output files. WSDL-Checker also validates WSDL files using the CXF validator tool (if you have CXF installed and enabled). This project was created using the Eclipse-Groovy-Plugin.
- A small Ant script that demonstrates how to call the Groovy WSDL checker from Ant as well as how to handle the checker’s response.
- A directory with sample WSDL files.
- An Eclipse project to call a public Web service (GlobalWeather) provided as a JUnit test; it takes a generated Axis2 stub that has been modified by JavaParser (one JUnit test with Axis2 data binding “
adb
” and one with “xmlbeans
“). - An Eclipse workspace preferences file (
Groovy_Gant.epf
) to assist you in setting all the needed Eclipse classpath variables and user libraries. - Two Groovy scripts that call public Web services by means of the Groovy SOAP extension — just so you can see what Groovy has to offer in this area
Once you’ve set up your environment you’ll be ready to begin familiarizing yourself with the Groovy and Gant Toolset. For those who need it, here is a quick primer on the client side of Web services, which you will need to understand in order to follow the discussion in the remainder of the article.
The client side of Web services
A WSDL file incorporates all the information that is needed to create a Web service client. In order to create the client, a Web service framework’s code generator reads the WSDL file. Based on the definitions found in the WSDL file it creates a Java stub or proxy class that mimics the interface of the Web service. Depending on the switches, this stub code can become very large and include all the referenced data types as inner classes. A better option is to let the generator create a couple of classes in different packages that will be the basis for javadoc generation later on. All the resulting classes are Java source code that you can study. Nevertheless, it is generated code, and as such it has its own “look.”
For functional and performance testing it might be necessary to modify the client stub. This is especially true when it comes to using HTTPS, instead of HTTP, to control the client-side debug level via a parameter (instead of using an XML config file), or to deal with HTTP chunking. (HTTP 1.1 supports chunked encoding, which allows HTTP messages to be broken up into several parts. Chunking is most often used by the server for responses, but clients can also chunk large requests.)
Particularly during testing, you may decide to just say “OK” to all self-signed SSL certificates. Therefore, when using HTTPS, you could need a specific SSL Socket Factory that allows for accepting all certificates in test mode. For performance tests, you might even want to control the number of opened connections, and you will want to re-use the same connection for different calls coming from the same client. This article is accompanied by a Java 5 parser and an Axis2 client stub modifier to deal with such scenarios.
Because you will get the parser/modifier as source code, you can customize it to “inject” some code for features not covered by my solution. What’s more, you can use the whole code as a blueprint to write your own modifier for frameworks other than Axis2.
Gant targets for WSDL processing
A Gant script — build.gant
— is the basis for all the features of the Groovy and Gant Toolset. Every task a user can perform is expressed as a Gant target. Using the directory that contains build.gant
as your working dir, you can get an overview of all the supported targets, together with a description, by typing gant
or gant -T
in your command shell. The gant
command refers to the “default” target you can implement, the gant -T
feature (t stands for “table of contents”) comes for free as part of the Gant implementation.
Listing 1. Calling Gant from the command line
>gant USAGE: gant available (checks your build.properties settings for available frameworks) gant wsdls (prints all wsdl files with their target endpoints, together with wsdl file 'shortnames' to be used by other targets regex) gant [-D "wsdl=<wsdl_shortname_regex>"] javagen (generate Axis2 based Java code from wsdl, compile, provide javadoc, and generate necessary jar/zip files) gant [-D "wsdl=<wsdl_shortname_regex>"] check (check one or more wsdl files for compatibility with installed code generators & validator) gant [-D "wsdl=<wsdl_shortname_regex>"] validate (validate one or more wsdls files using the CXF validator tool (if CXF is installed)) gant [-D collect] alljars (generates a directory with all Axis2 client jars, src/javadoc-ZIPs, and xsb resource files if xmlbeans is used) gant -D "wsdl=<wsdl_shortname_regex>" [-D "replace=<old>,<new>"] ns2p (prints a namespace-to-package mapping for a wsdl file) Produced listings in your 'results' directory: output-<tool>.txt & error-<tool>.txt with infos/errors/warnings for the code generation
The following commands are available as features of the Groovy and Gant Toolset:
gant available
examines yourbuild.properties
file for available and enabled frameworksgant wsdls
examines the wsdl directory and produces a sorted list of all available WSDL files together with a “short name” for every service to be used by other commands, as well as the target endpoint(s) for the service. (This command delivers an output, as shown in Listing 2.)gant [-D "wsdl=<regex>"] check
uses the code generators configured bybuild.properties
to check all WSDL files, or a regular-expression matching subset of all WSDL files, and produces a summary of error/warning messages.gant [-D "wsdl=<regex>"] validate
is the same as “check
but instead of the code generators only the CXF validator tool is used (if CXF is installed). It can check the WSDL file’s conformance to the WSDL and SOAP schema as well as look at some WS-I Basic Profile recommendations.gant [-D "wsdl=<regex>"] javagen
generates Axis2 source code for all WSDL files, or a regular-expression matching subset thereof. It then modifies the client stub, compiles all the code, and generates javadoc information, and a jar file with the compiled bytecode.gant [-D collect] alljars
generates a directory containing thebytecode-jars/src-zip
, andjavadoc-zip
archives for all WSDL files. If thecollect
option is specified it will not calljavagen
for every WSDL file, and will only copy jars and ZIPs to the directory defined inbuild.properties
.gant -D "wsdl=<regex>" [-D "replace=<old>,<new>"] ns2p
examines a WSDL file and prints a namespace-to-package mapping, using the replacement you provide with the optional “replace” parameter This feature is useful if – for test purpose – you need access to application APIs that have no SOAP interface but only a business delegate (known from the Core J2EE Patterns). But — unfortunately — these delegates will require other delegate jar files that contain classes with just the same package names as you provide by your WSDL-based Axis2 generated code. Therefore, I did improve the Axis2 code generators package creation in order to assist you in generating “unique” package names for your SOAP parts that will not conflict with business delegate classes in your Eclipse project’s classpath. In other words: you can even mix business delegate service calls and SOAP-based service calls in the same Eclipse project. A sample output would look like this:>gant -D "wsdl=Logon" -D "replace=myProject,myProject_soap" ns2p ---> Provided regex matched 'Logon' using the following replacement: myProject=myProject_soap checking G:/JavaWorld/Groovy_Gant/wsdl/security/webservice/svc-logon/wsdl/Logon.wsdl http://myProject.myCompany.com/runtime=com.myCompany.myProject_soap.runtime http://myProject.myCompany.com/logon=com.myCompany.myProject_soap.logon http://myProject.myCompany.com/remoting/soap/types/headerelements=com.myCompany.myProject_soap.remoting.soap.types.headerelements http://myProject.myCompany.com/logon/generated/interf=com.myCompany.myProject_soap.logon.generated.interf
Copy this output in a file “ns2p.properties” and save it in the same directory where “Logon.wsdl” is located. Then the
javagen
command will take care of this mapping when generating your Axis2 client stub for the “Logon” service.
All Gant targets are available as commands for the user and can be combined like so:
gant -D "wsdl=GlobalW.+" -D collect javagen alljars
Listing 2. Sample output from the ‘gant wsdls’ command
G:JavaWorldGroovy_Gant>gant wsdlslist all wsdlsAWSECommerceService [amazon/webservice/AWSECommerceService/wsdl/AWSECommerceService.wsdl] [<soap:address location="http://soap.amazon.com/onca/soap?Service=AWSECommerceService"/>]AddNumbers [handlers/webservice/svc-addNumber/wsdl/AddNumbers.wsdl] [<soap:address location="http://localhost:9000/handlers/AddNumbersService/AddNumbersPort" />]Callback [callback/webservice/svc-callback/wsdl/Callback.wsdl] [<soap:address location="http://localhost:9005/CallbackContext/CallbackPort"/> <soap:address location="http://localhost:9000/SoapContext/SoapPort"/>]CurrencyConverter [public-services/webservice/svc-lookup/wsdl/CurrencyConverter.wsdl] [<soap:address location="http://glkev.webs.innerhost.com/glkev_ws/Currencyws.asmx" />]GlobalWeather [public-services/webservice/svc-lookup/wsdl/GlobalWeather.wsdl] [<soap:address location="http://www.webservicex.net/globalweather.asmx" />]HelloWorld [hello-world/webservice/svc-hello-world/HelloWorld.wsdl] [<soap:address location="https://localhost:9001/SoapContext/SoapPort"/>]JMSGreeterService [jms-greeter/webservice/svc-greeter/wsdl/JMSGreeterService.wsdl] []Logon [security/webservice/svc-logon/wsdl/Logon.wsdl] [<soap:address location="http://localhost:4708/com/myCompany/myProject/logon/generated/interf/Logon"/>]YellowPages [public-services/webservice/svc-lookup/wsdl/YellowPages.wsdl] [<soap:address location="http://ws.soatrader.com/delimiterbob.com/0.1/YellowPages"/>]G:JavaWorldGroovy_Gant>
Gant configuration using build.properties
Like an Ant file, the Gant execution is configured by a property file called build.properties
which resides in the same directory as build.gant
, as shown in Listing 3.
Listing 3. Gant configuration using build.properties
# Property file to customize the Gant script for WSDL processing.# ------------------------------------------------------------------------------# Tell Gant which WSDL checker you have installed# ------------------------------------------------------------------------------axis.available=yesxfire.available=yescxf.available=yeswsimport.available=yes# ------------------------------------------------------------------------------# Axis2 information# ------------------------------------------------------------------------------axis2.install.dir=./ThirdPartyTools/axis2-1.3axis2.lib.dir=${axis2.install.dir}/libaxis2.version=1.3# ------------------------------------------------------------------------------# XFire information# ------------------------------------------------------------------------------xfire.install.dir=./ThirdPartyTools/xfire-1.2.6xfire.lib.dir=${xfire.install.dir}/libxfire.version=1.2.6ant.jar=./ThirdPartyTools/groovy-1.0/lib/ant-1.6.5.jar# ------------------------------------------------------------------------------# CXF information# ------------------------------------------------------------------------------cxf.install.dir=./ThirdPartyTools/apache-cxf-2.0.2-incubatorcxf.lib.dir=${cxf.install.dir}/libcxf.version=2.0.2# ------------------------------------------------------------------------------# Java6 information (only necessary if you are running Java5)# ------------------------------------------------------------------------------java6.lib.dir=./ThirdPartyTools/Java6/lib# ------------------------------------------------------------------------------# Java parser information# ------------------------------------------------------------------------------java-parser.install.dir=./JavaParser# ------------------------------------------------------------------------------# Code generation information# ------------------------------------------------------------------------------wsdl.root.dir=./wsdlstubs.package.prefix=com.mycompany.myproject_axis# if 'namespace-to-package replacement' is enabled,# we try to provide a suitable mapping for Axis2# but - consider as an alternative - using a 'ns2p.properties' file per wsdl file# with the 'gant ns2p' command output as a basisns2p.replace=nons2p.namespace=myproject.mycompany.comns2p.package=com.mycompany.myproject_axis# Axis2 data binding= adb|xmlbeansaxis.data.binding=adboutput.axis.file=./results/output-axis.txterror.axis.file=./results/error-axis.txtoutput.axis.codeGenerator=./GeneratedCode_axisoutput.xfire.file=./results/output-xfire.txterror.xfire.file=./results/error-xfire.txtoutput.xfire.codeGenerator=./GeneratedCode_xfireoutput.cxf.file=./results/output-cxf.txterror.cxf.file=./results/error-cxf.txtoutput.cxf.codeGenerator=./GeneratedCode_cxfoutput.wsimport.file=./results/output-wsimport.txterror.wsimport.file=./results/error-wsimport.txtoutput.wsimport.codeGenerator=./GeneratedCode_wsimportoutput.validator.file=./results/output-validator.txterror.validator.file=./results/error-validator.txtgenerated.code.dir=./GeneratedCodeclient.jar.dir=./ClientJarFiles# ------------------------------------------------------------------------------# Javadoc information# ------------------------------------------------------------------------------javadoc.enabled=yesjavadoc.packageNames=com.*,tools.*,org.*,net.*project.copyright=Copyright © 2007 - Mycompany.com# ------------------------------------------------------------------------------# Optional proxy information# ------------------------------------------------------------------------------proxy.enabled=noproxy.host=myProxyHostproxy.port=81
Expressing dependencies
With Gant you can express dependencies as you would do with Ant, and you can call all available Ant tasks that are provided by Groovy’s ant-1.6.5jar
(found in the <GROOVY_HOME>/lib
directory). You do not have to create a Groovy AntBuilder for this purpose because Gant does it for you.
Looking at the build.gant
script excerpts in Listing 4, you can see that a Gant script is really a Groovy script.
Listing 4. Gant script (excerpt from build.gant)
import org.apache.commons.lang.StringUtils ... boolean wsdlRegexProvided = false boolean wsdlRegexOK = false def wsdlFilesMatchingRegexList = [] def readProperties() { Ant.property(file: 'build.properties') def props = Ant.project.properties return props; } def antProperty = readProperties() long startTime = System.currentTimeMillis() // Classpath for Java code generation tool def java2wsdl_classpath = Ant.path { fileset(dir: antProperty.'axis2.lib.dir') { include(name: '*.jar') } } def generateJavaCode = { wsdlFile, javaSrcDir -> def ns2pValues = '' if (antProperty.'ns2p.replace'.equals("yes")) { //TODO: consider replacing regex by XmlSlurper def pattern = ~/^.*(?:targetNamespace=")([^"]+)"/ def matcher = pattern.matcher('') def ns2pMap = [:] new File(wsdlFile).eachLine { matcher.reset(it) while (matcher.find()) { String namespace = matcher.group(1) if (!ns2pMap.containsKey(namespace)) { ns2pMap.put(namespace, (namespace-'http://').replace('/', '.').replaceAll(antProperty.'ns2p.namespace',antProperty.'ns2p.package')) } } } Iterator iter = ns2pMap.keySet().iterator(); StringBuilder sbuf = new StringBuilder(256) while (iter.hasNext()) { String key = iter.next(); String value = ns2pMap.get(key) sbuf.append(key) sbuf.append('=') sbuf.append(value) sbuf.append(',') } if (sbuf.length() > 0) { ns2pValues = sbuf.replace(sbuf.length()-1, sbuf.length(), '').toString() } else { println 'No namespace-to-package replacement possible: nothing matched' } } def wsdlFileDir = StringUtils.substringBeforeLast(wsdlFile, '/') def stubPackageSuffix = (wsdlFile - wsdlFileDir - '/' - '.wsdl').toLowerCase() + '.soap.stubs' def outputDir = javaSrcDir-'/src' // Axis2 will generate a /src dir for us def outFile = new File("${antProperty.'error.axis.file'}") outFile << wsdlFile + NL Ant.java(classname: 'org.apache.axis2.wsdl.WSDL2Java', classpath: java2wsdl_classpath, fork: true, output: "${antProperty.'output.axis.file'}", error: "${antProperty.'error.axis.file'}", append: "yes", resultproperty: "taskResult_$wsdlFile") { if (antProperty.'proxy.enabled'.equals("yes")) { println "Using proxy ${antProperty.'proxy.host'}:${antProperty.'proxy.port'}" jvmarg (value: "-Dhttp.proxyHost=${antProperty.'proxyhost'}") jvmarg (value: "-Dhttp.proxyPort=${antProperty.'proxyport'}") } arg (value: '-uri') arg (value: wsdlFile) arg (value: '-d') arg (value: "${antProperty.'axis.data.binding'}") arg (value: '-o') arg (value: outputDir) arg (value: '-p') arg (value: "${antProperty.'stubs.package.prefix'}.$stubPackageSuffix") arg (value: '-u') arg (value: '-s') if (new File("$wsdlFileDir/ns2p.properties").exists()) { println 'using provided namespace-to-package mapping per wsdl file' arg (value: '-ns2p') arg (value: "$wsdlFileDir/ns2p.properties") } else if (antProperty.'ns2p.replace'.equals("yes")) { println 'using provided namespace-to-package mapping for ALL wsdl files that should be processed' arg (value: '-ns2p') arg (value: ns2pValues) } arg (value: '-t') } print "$wsdlFile " if (Ant.project.properties."taskResult_$wsdlFile" != '0') { println '... ERROR' } else { println '... OK' } }
Gant specialities
You’ll note that Gant targets are Groovy closures. Inside closures you can define new closures and assign them to variables, but you are not allowed to declare a method. So, the following is allowed:
target ( ) { def Y = { }}
But this isn’t:
target ( ) { def Y ( ) { }}
Gant has two other characteristic features. First, every target has a name and a description, for instance
target (alljars: 'generate a directory with all client jar/src/doc/res archives') { depends(javagen) println alljars_description
To access this description string, you can use the variable “<target-name>_description
“, which is created by Gant for your convenience.
Second, if you want to hand over a WSDL regular expression “property” specified in the command line to the build.gant
script, you can use the -D "wsdl=<regex>"
idiom. The syntax is similar to that used for Java VM command-line property setting: java -Dproperty=value
, but on Windows you really need the space after “-D
“, because Gant uses the Apache Commons CLI library. You can access this property in your Gant script by using the following “try/catch” trick (demonstrated for the property wsdl
) that is evaluated in the init
target:
target (init: 'check wsdl regex match') { try { // referencing 'wsdl' in this try block checks for the existence of this variable // if you call Gant with '>gant -D "wsdl=<wsdl_target'>" then the variable 'wsdl' // will be created by Gant for you, otherwise the catch block will be executed def wsdlRootDir = new File(antProperty.'wsdl.root.dir') def wsdlList = [] boolean atLeastOneMatchingWsdlFileFound = false wsdlRootDir.eachFileRecurse{ if (it.isFile() && it.name.endsWith('.wsdl')) { def wsdlFile = it.canonicalPath.replace('', '/') def startIndex = wsdlFile.indexOf('/wsdl') + 1 def endIndex = wsdlFile.lastIndexOf('.') def shortname = StringUtils.substringAfterLast(wsdlFile, '/')-'.wsdl' if (shortname ==~ wsdl) { println "---> Provided regex matched '$shortname'" wsdlFilesMatchingRegexList << wsdlFile atLeastOneMatchingWsdlFileFound = true return; } } } wsdlRegexProvided = true if (atLeastOneMatchingWsdlFileFound) { wsdlRegexOK = true } else { println "tWarning: No wsdl file found that matches regex pattern '$wsdl'!" wsdlRegexOK = false } } catch (Exception e) { // no regex evaulation necessary } }
In build.gant
Groovy’s syntax is used to handle foreach
loops, proxy usage, and regular expressions to narrow the WSDL processing to a few files only. The WSDL checker and validator, however, are a set of pure Groovy files that have the facade WsdlChecker.main ()
as a central entry point. Although Groovy comes with nice GString
s and a powerful “GDK” (Groovy Development Kit) it appears, that Jakarta Commons Lang StringUtils is still a valuable add-on.
Checking WSDL files — a Groovy task
As I mentioned in the introduction, the Groovy and Gant Toolset should help testers and developers check WSDL files coming from different sources for compatibility with a variable set of Web service frameworks, including WSDL validation. Testing for compatibility, in this context, means: call every Web service framework’s WSDL-to-Java code generator and check the resulting files for exceptions, errors, and warnings. If CXF is installed and enabled in the build.properties
file, then you can run CXF’s validator tool, too. All these tasks are performed by pure-Groovy classes. The design is simple: every framework’s code generator and its output handling is mapped to a Groovy class. Each class provides two basic methods that are called by the WsdlChecker
“controller” class:
def checkWsdl(wsdlURI)
calls the corresponding code generator for a WSDL file.boolean findErrorsOrWarnings()
tells the “controller” if errors or warnings are found in the generator output files. If so, it prints them to the console.
In Java you would define an interface that your concrete (code generator strategy) classes would implement (you could also use an abstract class). In Groovy, however, you don’t need to declare explicit interfaces — though you could. Instead, you create a list of “checker” objects (depending on the Web service frameworks installed and enabled in build.properties
) making use of Groovy closures, as shown in Listing 5.
Listing 5. Groovy closures in the WsdlChecker class (excerpt)
def wsdlcheck() { def sortedList = wsdlList.sort() for (wsdlLongName in sortedList) { def wsdlURI = wsdlLongName.suffix availableCheckers.each{ it.checkWsdl(wsdlURI) } } // print individual results and report overall result to Ant (errors only) new File('wsdl_errors.txt').write(Boolean.toString(findWsdlErrors()))}boolean findWsdlErrors() { println() boolean errorsFoundInAnyChecker = false availableCheckers.each{ errorsFoundInAnyChecker = errorsFoundInAnyChecker | it.findErrorsOrWarnings() } return errorsFoundInAnyChecker}
A typical output in your shell would look like this:
Logon.wsdl... Axis2 check done.Logon.wsdl... XFire check done.Logon.wsdl... Java 6 wsimport check done.Logon.wsdl... CXF check done.Logon.wsdl... CXF validator check done.No Axis2 errors foundNo XFire errors foundJava6 wsimport warnings found in: security/webservice/svc-logon/wsdl/Logon.wsdl: src-resolve: Cannot resolve the name 'impl_1:RuntimeException2' to a(n) 'type definition' component.No Java6 wsimport errors foundNo CXF errors foundCXF Validation passed
Using the Groovy-Eclipse-Plugin, you can start this WsdlChecker.groovy
program just like a “normal” Java program. The plugin will create a separate Groovy entry in your Eclipse environment’s Run
settings.
Running the WSDL checker from inside Ant
It is also possible to run the WsdlChecker
as part of an Ant target. To see how you can incorporate the Groovy classes in an Ant script, look at the following Ant build.xml
, which is part of the Groovy-WSDL-Checker Eclipse project:
Listing 6. Calling the Groovy WSDL checker from inside Ant
<project name="wsdl2java check" default="check-wsdls" basedir="."> <taskdef resource="net/sf/antcontrib/antcontrib.properties"/> <taskdef name="groovy" classname="org.codehaus.groovy.ant.Groovy"> <classpath location="lib/groovy-all-1.0.jar" /> </taskdef> <target name="check-wsdls"> <groovy src="src/tools/webservices/wsdl/checker/WsdlChecker.groovy"> <classpath> <pathelement location="bin-groovy"/> <pathelement location="lib/commons-lang-2.3.jar"/> </classpath> </groovy> <loadfile property="wsdlErrorsFound" srcfile="wsdl_errors.txt"></loadfile> <if>istrue value="${wsdlErrorsFound}" /> <then> <echo message="we found errors in wsdl files" /> </then> <else> <echo message="NO errors found in wsdl files" /> </else> </if> </target></project>
The Groovy checker is controlled by the same build.properties
file as is build.gant
. If you want to work with the Groovy classes in the Eclipse project in Resources (instead of calling the checker via build.gant
regex to control which WSDL file to check/validate, and the checker’s mode
parameter (that tells the tool whether to check for compatibility using the installed code generators or to validate with the CXF validator tool). Please note, that I could not set any values in Ant’s property hashtable that would be available to Ant afterwards, because in Groovy you are working with a copy of the table. Therefore, I chose to deliver the results via Ant’s loadfile
task. Working with Ant’s limited condition handling capabilities just isn’t my cup of tea. That’s why I decided to use ant-contrib
with its if-then-else
feature to demonstrate the checker’s result evaluation in Listing 6.
Code generation and modification — Gant and Java play together
So far, you have only seen the possibility of generating Java source code from WSDL files and checking the generated output using different Web service frameworks. I’ll conclude my introduction to the Groovy and Gant Toolset with a more concrete example based on the generated code of Axis2. The corresponding Gant target in the Groovy and Gant Toolset is called javagen
.
To make working with the client stub more comfortable, I’ll show you how to do much more than generate code with Axis2’s wsdl2Java
tool You will first generate source code, but then you will modify it with a Java parser/modifier. After that you will compile it using Sun’s javac
(so your JAVA_HOME
environment variable should point to the JDK rather that the JRE root directory), generate javadoc information, and finally produce a jar file containing the compiled code. If you’re using xmlbeans
you will also get an xsb
resources jar file to be included in your project’s classpath.
This jar file — together with the parser/modifier bytecode delivered as japa.jar
— is the basis for client-side unit or performance tests. Code modification allows you to provide the user with a handy client stub factory that incorporates, and encapsulates, Log4J debug-level setting, HTTP/HTTPS handling (including chunking and adaption to proper cipher suite exchange), and connection control (for the underlying Jakarta httpclient
).
About JavaCC
It took me some time to find a suitable Java parser. I wanted one that would be capable of handling Java 5 syntax, that was free, and that would let me plug in my source code modification feature. I chose JavaCC. You can also get the parser source, together with my modifications/add-ons, from the Resources section: see JavaParser Eclipse project.
I extended two of the original files: JavaParser.java
and DumpVisitor.java
. The first one provides the entry point for Groovy as a main method and calls the DumpVisitor
for the Axis2-generated client stub. As you may guess, the latter is based on the Gang of Four Visitor pattern. (You may have to look closely in order to spot the changes I made to reach my targets, however. Anyone who has used the Visitor pattern knows that its usage is not easily digested!)
Additionally, I provide a package, tools.webservices.wsdl.stubutil
, that incorporates all the code that is required by the modified client stub to compile and work properly. You can generate all of this as a jar file; just use the japa.jardesc
file, which is part of the JavaParser Eclipse project for your (and my) convenience.
For those who want to count on Java 6 Web service support based on java.net.URL
, I have included a special HttpSSLSocketFactory.java
class as part of the Eclipse JavaParser project that takes care of the cipher suite handling (“accept all certificates”). You can use an instance of this class to set the default SSLSocketFactory
calling, for example, javax.net.ssl.HttpsURLConnection.setDefaultSSLSocketFactory
. Socket factories are used when creating sockets for secure HTTPS URL connections. Creating a client stub typically looks like this:
GlobalWeather stub = GlobalWeatherStub.createStub(ProtocolType.http, Level.INFO, Chunking.yes,"http://www.webservicex.net/globalweather.asmx", "Connection1");
Not-so-stupid bean property settings
My Gant script, build.gant
, calls the Axis2 wsdl2java
code generator with the flag -s
(synchronous calls only) and the hint to use the default Axis2 data binding ADB
(Axis2 Databinding Framework). This will produce service-method calls with properties wrapped as Java beans. Imagine you have a lot of properties that are optional for your call, and one of the required properties tells the Web service to ignore the others. With a normal RPC, you would set parameters to null (or with xmlbeans
you can try the WSDL “nillable
” attribute), but this is not possible with ADB. During serialization, every object property is tested to be not equal to null. Therefore, you might write many lines of source code doing nothing more than just setting a “default” value for every property of your bean before handing it over to your Axis2 stub. To overcome this “stupid work” I have written a smart Java utility class, NOB
(NullObjectBean
), that impressively demonstrates the power of using reflection and Jakarta commons — the beanutils project in this case — assuming that performance is not a requirement when you are setting your bean properties. Populating a bean with “default” values is now as easy as calling:
WebConferenceDTO webConferenceDTO = (WebConferenceDTO) NOB.create(new WebConferenceDTO());
As a current limitation, the NOB
utility can only provide values for “beans” that have a public default (empty) constructor, that have public final constant objects, or that have “simple” interface types as properties. So the algorithm would not work, for instance, for the xmlbeans
data binding where interfaces with factory methods are created. (But it could be extended to do so!) Nevertheless, having this class and the Groovy and Gant Toolset in place, nothing stops you from playing around with WSDL and the client-side part of Web services.
Listing 7. Helper class ‘NOB’ simplifies Axis2 ADB bean handling
package tools.webservices.wsdl.stubutil;import .../** * Provide a special kind of "Null Object Bean (NOB)" where every primitive and non primitive bean * property has a default value not equal 'null'. * * Whereas the Null Object pattern would provide , we simply want to abstract the handling of Axis2 * ADB 'null' parameters away from the client. * Because this class is used in the Axis2 environment with ADB as the default data binding, it * checks if the bean that is handed over to our Factory method implements * 'org.apache.axis2.databinding.ADBBean'. You can easily disable this check if you want to use this * class in another context or replace the check if you need such a helper class for, e.g., XMLBeans * data binding. * * @author Klaus-Peter Berg (Klaus P. Berg@web.de) */public class NOB { private static int recursionCounter = 0; private NOB() { // hide constructor, we are a helper class with static methods only } /** * Create a Java bean object that can be used as a "Null Object Bean" in Axis2 parameters calls * using ADB data binding. * We do our best to process property types as interfaces and helper classes that provide constants, too, * but abstract classes are ignored. So, every effort is made to create a useful "default" Null Object bean, * but I cannot guarantee that Axis2.serialize() will be able to really handle it propertly ;-) * * @param emptyBean * the bean that should be populated with non-null property values as a default * @return the "populated Null Object Bean" to be casted to the bean type you will actually need */ @SuppressWarnings("unchecked") public static Object create(final Object emptyBean) { recursionCounter++; final boolean firstCall = recursionCounter == 1; if (firstCall && !org.apache.axis2.databinding.ADBBean.class.isAssignableFrom(emptyBean .getClass())) { throw new IllegalArgumentException( "'emptyBean' argument must implement 'org.apache.axis2.databinding.ADBBean'"); } final BeanMap beanMap = new BeanMap(emptyBean); final Iterator<String> keyIterator = beanMap.keyIterator(); while (keyIterator.hasNext()) { final String propertyName = keyIterator.next(); if (beanMap.get(propertyName) == null) { final Class propertyType = beanMap.getType(propertyName); try { if (propertyType.isArray()) { final Class<?> componentType = propertyType.getComponentType(); final Object propertyArray = Array.newInstance(componentType, 1); beanMap.put(propertyName, propertyArray); } else { final Object propertyValue = ConstructorUtils.invokeConstructor( propertyType, new Object[0]); beanMap.put(propertyName, create(propertyValue)); } } catch (final NoSuchMethodException e) { if (propertyType.isInterface()) { processInterfaceType(beanMap, propertyName, propertyType); } else { processHelperClassType(beanMap, propertyName, propertyType); } } catch (final IllegalAccessException e) { throw new RuntimeException(e); } catch (final InvocationTargetException e) { throw new RuntimeException(e); } catch (final InstantiationException e) { processInterfaceType(beanMap, propertyName, propertyType); } } } recursionCounter--; return beanMap.getBean(); } private static void processHelperClassType(final BeanMap beanMap, final String propertyName, final Class propertyType) { // Class.newInstance() will throw an InstantiationException // if an attempt is made to create a new instance of the // class and the zero-argument constructor is not visible. // Therefore, we look for public final constants of the type we need, // because we assume that we process a helper class with constants (only)... final Field[] fields = propertyType.getDeclaredFields(); for (final Field field : fields) { final int mod = field.getModifiers(); final boolean acceptField = Modifier.isPublic(mod) && Modifier.isStatic(mod) && Modifier.isFinal(mod) == true && field.getType().equals(propertyType); if (acceptField) { try { beanMap.put(propertyName, field.get(null)); break; // we will take the first constant that satifies our needs } catch (final Exception e1) { throw new RuntimeException(e1); } } } } private static void processInterfaceType(final BeanMap beanMap, final String propertyName, final Class propertyType) { if (propertyType.isInterface()) { final Object interfaceToImplement = java.lang.reflect.Proxy.newProxyInstance(Thread .currentThread().getContextClassLoader(), new Class[] { propertyType }, new java.lang.reflect.InvocationHandler() { public Object invoke(@SuppressWarnings("unused") Object proxy, @SuppressWarnings("unused") Method method, @SuppressWarnings("unused") Object[] args) throws Throwable { return new Object(); } }); beanMap.put(propertyName, interfaceToImplement); } else if (Modifier.isAbstract(propertyType.getModifiers())) { // ignore abstract class: we cannot create an instance of it ;-) } }}
In conclusion
A famous saying often attributed to Vladimir Ilyich Lenin (founder of the communist party and once president of Russia) is “Trust is acceptable, but control is better.” Many developers will agree that this is true for software development. So, when you are dealing with WSDL files to generate client-side code for Web services, it is always better to do some kind of WSDL “checks” first. Note that checking isn’t quite the same as testing: checking is more about laying a solid groundwork for the functional and/or performance tests to come.
In this article I have presented a small toolset based on Groovy, Gant, and Java that could support your daily work in this area, especially if you are a tester. If you are a developer who wishes to tackle WSDL problems in a broad spectrum of Web service frameworks like Axis2, XFire, CXF, or with Java 6, my scripts and classes may give you new insights into how to do it. However you use the Groovy and Gant Toolset, I hope it will leave you with far fewer problems to worry about while you create the next Web service killer app.
Another goal of this article, and the toolset, is to help you tap into the fascinating world of Groovy and Gant, which I have merely outlined here. Both could be good additions to your bag of Java programming tricks. Finally, please note that a single article cannot cover the whole spectrum of Web service frameworks and tools, so please forgive me if I did not mention your favorites. Perhaps they will show up soon in another JavaWorld article.
Klaus P. Berg has an MS (diploma) in electrical engineering and applied informatics from the University of Karlsruhe in Germany. He was an architect and implementor for projects at Siemens focused on Java GUI development with Swing and Java Web Start as well as acting on the server side creating Java based intranet applications Now he works as a senior engineer in the area of software quality, functional and performance testing, mainly for J2EE software.
Related content
- how-toFull-stack development with Java, React, and Spring Boot, Part 2 Explore the popular and powerful React + Java + Spring stack by incorporating a service layer and a MongoDB instance for data persistence.By Matthew TysonJul 31, 20248 minsJavaReactWeb Development
- featureGet ready for more Java licensing changes The license for Oracle JDK 17 will revert to the Oracle Technology Network License Agreement in September, and users will have some decisions to make. By Simon RitterJul 29, 20245 minsJavaProgramming LanguagesSoftware Development
- newsVisual Studio Code Java extension backs JDK 23 The newest Java extension for VS Code also brings dependency management without a build tool and support for Markdown in JavaDoc.By Paul KrillJul 24, 20241 minVisual Studio CodeJavaDevelopment Tools
- how-toFull-stack development with Java, React, and Spring Boot, Part 1 React and Java come together seamlessly in this three-part introduction to full-stack development with React and Spring Boot. Part 1 gets you started with a basic application framework that you can customize as needed.By Matthew TysonJul 24, 20249 minsReactJavaSoftware Development
- Resources
- Videos