Java template for WSDL-first web services using CXF (for Maven2 and Eclipse)

This took me a while to put together so I thought I’d post it. I wanted the simplest possible template for building a web service in Java. I wanted it to be JAX-WS compliant, so I used the CXF open source implementation which is not only compliant, but also flexible and fast. I also wanted the template to be WSDL first, meaning that I should be able to edit the WSDL by hand to maintain total control over the service contract, then from that, generate Java code to make it easy to fill in the implementation.  (I consider that to be an important part of web service best practices. Doing it the other way – automatically generating WSDL from code – is simpler, but results in messy, sometimes incorrect WSDL that limits your ability to change web service implementations later.) Furthermore, I didn’t want to edit any generated code. I wanted to be able to fill in the implementation details by inheriting from a generated class or implementing a generated interface. Finally, I wanted to take advantage of Maven to build the project, but also be able to work on it in Eclipse, taking advantage of its Web Tools Platform (WTP) to allow synchronization with a live application server. Here’s the result in just under 300 lines of code. (Or you can cut to the chase and just download the zip file and follow the instructions at the end of this posting.)

First, here is the trade.xsd schema file containing the input and output datatypes used by the web services:

<?xml version="1.0" encoding="UTF-8"?>

<xsd:schema targetNamespace="http://com.joemo.schema.trade" xmlns="http://com.joemo.schema.trade"
	xmlns:xsd="http://www.w3.org/2001/XMLSchema">

	<!-- web service input types -->

	<xsd:element name="trade">
		<xsd:complexType>
			<xsd:sequence>
				<xsd:element name="security" type="xsd:string" minOccurs="1" maxOccurs="1" />
				<xsd:element name="quantity" type="xsd:integer" minOccurs="1" maxOccurs="1" />
				<!-- note the use of "unbounded"; comments can occur multiple times -->
				<xsd:element name="comments" type="comment" minOccurs="1" maxOccurs="unbounded" />
			</xsd:sequence>
		</xsd:complexType>
	</xsd:element>

	<xsd:complexType name="comment">
		<xsd:sequence>
			<xsd:element name="message" type="xsd:string" minOccurs="1" maxOccurs="1" />
			<xsd:element name="author" type="xsd:string" minOccurs="1" maxOccurs="1" />
		</xsd:sequence>
	</xsd:complexType>

	<!-- web service output types -->

	<xsd:element name="status">
		<xsd:complexType>
			<xsd:sequence>
				<xsd:element name="id" type="xsd:string" minOccurs="1" maxOccurs="1" />
				<xsd:element name="message" type="xsd:string" minOccurs="1" maxOccurs="1" />
			</xsd:sequence>
		</xsd:complexType>
	</xsd:element>

</xsd:schema>

Next, we need the trade.wsdl file which imports the schema file and completes the WSDL definition:

<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions targetNamespace="http://com.joemo.schema.tradeservice"
	xmlns="http://com.joemo.schema.tradeservice"
	xmlns:tr="http://com.joemo.schema.trade"
	xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
	xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:xsd="http://www.w3.org/2001/XMLSchema">

	<wsdl:types>
		<xsd:schema targetNamespace="http://com.joemo.schema.tradeservice">
			<xsd:import namespace="http://com.joemo.schema.trade" schemaLocation="trade.xsd" />
		</xsd:schema>
	</wsdl:types>

	<wsdl:message name="tradeInput">
		<wsdl:part name="trade" element="tr:trade" />
	</wsdl:message>

	<wsdl:message name="tradeOutput">
		<wsdl:part name="status" element="tr:status" />
	</wsdl:message>

	<wsdl:portType name="TradeService">
		<wsdl:operation name="book">
			<wsdl:input message="tradeInput" />
			<wsdl:output message="tradeOutput" />
		</wsdl:operation>
	</wsdl:portType>

	<wsdl:binding name="TradeServiceHTTPBinding" type="TradeService">
		<wsdlsoap:binding style="document"
			transport="http://schemas.xmlsoap.org/soap/http" />
		<wsdl:operation name="book">
			<wsdlsoap:operation soapAction="" />
			<wsdl:input>
				<wsdlsoap:body use="literal" />
			</wsdl:input>
			<wsdl:output>
				<wsdlsoap:body use="literal" />
			</wsdl:output>
		</wsdl:operation>
	</wsdl:binding>

	<wsdl:service name="TradeServicePorts">
		<wsdl:port binding="TradeServiceHTTPBinding" name="TradeService">
			<wsdlsoap:address
				location="http://localhost:9084/tradeService/TradeServicePorts" />
		</wsdl:port>
	</wsdl:service>

</wsdl:definitions>

Now we need a Maven project file that will take this WSDL and generate the Java code. Here’s what the pom.xml file looks like. It’s long and messy but it does a lot. It specifies all the dependencies and the compiler level, includes the rule to generate Java code from WSDL whenever necessary, and includes Jetty and WTP support for testing and running the web services in different environments.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.joemo</groupId>
	<artifactId>ws-example</artifactId>
	<packaging>war</packaging>
	<version>0.1</version>
	<name>ws-example</name>
	<url>http://maven.apache.org</url>
	<properties>
		<cxf.version>2.1</cxf.version>
		<spring.version>2.5</spring.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.apache.cxf</groupId>
			<artifactId>cxf-rt-core</artifactId>
			<version>${cxf.version}</version>
		</dependency>
		<dependency>
			<groupId>org.apache.cxf</groupId>
			<artifactId>cxf-rt-frontend-jaxws</artifactId>
			<version>${cxf.version}</version>
		</dependency>
		<dependency>
			<groupId>org.apache.cxf</groupId>
			<artifactId>cxf-rt-transports-http</artifactId>
			<version>${cxf.version}</version>
		</dependency>
		<dependency>
			<groupId>org.apache.cxf</groupId>
			<artifactId>cxf-common-utilities</artifactId>
			<version>${cxf.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-core</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-beans</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.4</version>
			<scope>test</scope>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<!-- Use Java 5 -->
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<configuration>
					<source>1.5</source>
					<target>1.5</target>
				</configuration>
			</plugin>

			<!-- CXF WSDL-to-Java code generation -->
			<plugin>
				<groupId>org.apache.cxf</groupId>
				<artifactId>cxf-codegen-plugin</artifactId>
				<version>2.0.6</version>
				<executions>
					<execution>
						<id>generate-sources</id>
						<phase>generate-sources</phase>
						<configuration>
							<sourceRoot>${basedir}/target/generated/src/main/java</sourceRoot>
							<wsdlOptions>
								<wsdlOption>
									<wsdl>src/main/resources/trade.wsdl</wsdl>
								</wsdlOption>
							</wsdlOptions>
						</configuration>
						<goals>
							<goal>wsdl2java</goal>
						</goals>
					</execution>
				</executions>
			</plugin>
			<!-- Jetty support for testing -->
			<plugin>
				<groupId>org.mortbay.jetty</groupId>
				<artifactId>maven-jetty-plugin</artifactId>
			</plugin>
		</plugins>
		<!-- Eclipse WTP support -->
		<pluginManagement>
			<plugins>
				<plugin>
					<artifactId>maven-eclipse-plugin</artifactId>
					<configuration>
						<wtpversion>2.0</wtpversion>
						<wtpapplicationxml>true</wtpapplicationxml>
						<wtpmanifest>true</wtpmanifest>
						<downloadSources>true</downloadSources>
						<downloadJavadocs>true</downloadJavadocs>
						<projectNameTemplate>[artifactId]-[version]</projectNameTemplate>
						<manifest>${basedir}/src/main/resources/META-INF/MANIFEST.MF</manifest>
					</configuration>
				</plugin>
			</plugins>
		</pluginManagement>
	</build>
</project>

Among other things, the rules in this pom.xml file will generate a Java interface called TradeService (based on the names in the WSDL file). The only code we will have to write is the implementation of this interface. Although this generation is done automatically by any Maven commands that need it (e.g. mvn package or mvn install) you might want to force it to be done sooner rather than later, so that you can refresh your Eclipse project with the generated code, enabling Eclipse to recognize the interface that you’re trying to implement. You can do this using the commands:

mvn generate-sources
mvn eclipse:clean eclipse:eclipse

This generates Java code from the WSDL, then regenerates the Eclipse project files, after which you should be able to refresh the project in Eclipse. If you see errors about libraries not being found, you may need to configure Eclipse to know about your Maven repository, i.e. select Eclipse / Window / Preferences / Java / Build Path / Classpath Variables, then enter the appropriate settings, e.g.

Name: M2_REPO
Path: C:/Documents and Settings/MyAccount/.m2/repository

Once the project is properly configured in Eclipse, you can fill in the implementation:

package com.joemo.service;

import trade.schema.joemo.com.Comment;
import trade.schema.joemo.com.Status;
import trade.schema.joemo.com.Trade;
import tradeservice.schema.joemo.com.TradeService;

public class TradeServiceImpl implements TradeService {

	public Status book(Trade trade) {
		System.out.print ("Booking security ");
		System.out.print (trade.getSecurity());
		System.out.print (", quantity ");
		System.out.print (trade.getQuantity());
		System.out.println();
		if (trade.getComments() != null) {
			System.out.println ("Comments:");
			for (Comment c : trade.getComments()) {
				System.out.print (c.getAuthor());
				System.out.print (": ");
				System.out.print (c.getMessage());
				System.out.println();
			}
		}
		Status s = new Status();
		s.setId("12345");
		s.setMessage("ok");
		return s;
	}

}

We are almost done. We still need a web.xml file which will direct SOAP requests to the CXF infrastructure:

<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_9" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:appContext.xml</param-value>
	</context-param>
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>
	<servlet>
		<servlet-name>dispatcher</servlet-name>
		<servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>
	<servlet-mapping>
		<servlet-name>dispatcher</servlet-name>
		<url-pattern>/*</url-pattern>
	</servlet-mapping>
</web-app>

Finally, we need the appContext.xml file, which is the Spring configuration file loaded by CXF that defines the web service endpoint:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
	xmlns:jee="http://www.springframework.org/schema/jee" xmlns:jaxws="http://cxf.apache.org/jaxws"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd"
	default-dependency-check="none" default-lazy-init="false">

	<!-- Load the needed resources that are present in the cxf* jars -->
	<import resource="classpath:META-INF/cxf/cxf.xml" />
	<import resource="classpath:META-INF/cxf/cxf-extension-soap.xml" />
	<import resource="classpath:META-INF/cxf/cxf-servlet.xml" />

	<!-- Hook up the web service -->
	<jaxws:endpoint id="ws-example" implementor="com.joemo.service.TradeServiceImpl"
		address="/ws-example" />

</beans>

That’s everything. There are six files, only one of which contains any Java code. You need to make sure you put each file in the right place:

ws-example/pom.xml
ws-example/src/main/resources/trade.xsd
ws-example/src/main/resources/trade.wsdl
ws-example/src/main/resources/appContext.xml
ws-example/src/main/webapp/WEB-INF/web.xml
ws-example/src/main/java/com/joemo/service/TradeServiceImpl.java

A zip file of this example is available for download here. To build and run it, you will need Maven to be installed on your development system. Unzip the file, and in the directory containing the pom.xml file, run the command:

mvn jetty:run

That will generate the Java code from the WSDL, build the example, and run the web service in the Jetty container. You should be able to visit the URL http://localhost:8080/ws-example/ws-example?wsdl from a web browser and see the WSDL for the web service, test the web service using SoapUI, and so on.

Alternatively you can run the command:

mvn eclipse:eclipse

and follow the directions from my earlier blog entry to run the example using Eclipse WTP, which will allow you to edit the code while keeping it synchronized with a live application server.

Good luck! If you encounter any problems using this template, please email me or post a comment so that I can look into it and revise the instructions if necessary.

19 Comments

  1. Thought this was extremely helpful page for someone who knows Java but needed that first helping hand for a Spring WS. Thank you!

    Only minor comment I would make is that a reference to mvn package for other app servers (I deployed on Jonas) might be useful?

  2. I am getting this exception:

    [INFO] java.lang.RuntimeException: Fail to create wsdl definition from : file:/E:/1O2/Checkout/besys
    temevolution/trunk/Development/Code/BePortalCommonFramework/src/main/resources/profileManagementServ
    ice.wsdl/
    Caused by : WSDLException: faultCode=PARSER_ERROR: Problem parsing ‘file:/E:/1O2/Checkout/besystemev
    olution/trunk/Development/Code/BePortalCommonFramework/src/main/resources/profileManagementService.w
    sdl/’.: org.xml.sax.SAXParseException: Premature end of file.

    [INFO] ————————————————————————

  3. Thank you for the great tutorial, Joe!
    Your example worked on my machine from the first try. It’s just great considering I know merely nothing about web services :-)
    Tanya

  4. one final dash – a client for your cute service:

    package au.com.mycompany;

    import java.math.BigInteger;

    import org.apache.cxf.interceptor.LoggingInInterceptor;
    import org.apache.cxf.interceptor.LoggingOutInterceptor;
    import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;

    import trade.schema.joemo.com.Status;
    import trade.schema.joemo.com.Trade;
    import tradeservice.schema.joemo.com.TradeService;

    /**
    * Client.java
    */
    public final class Client {

    private Client() {
    // empty
    }

    public static void main(String args[]) throws Exception {
    JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();

    factory.getInInterceptors().add(new LoggingInInterceptor());
    factory.getOutInterceptors().add(new LoggingOutInterceptor());
    factory.setServiceClass(TradeService.class);
    factory.setAddress(“http://localhost:8080/ws-example”);

    TradeService client = (TradeService) factory.create();

    Trade t = new Trade();
    t.setSecurity(“ASX”);
    t.setQuantity(BigInteger.valueOf(10));

    Status s = client.book(t);
    System.out.println(s.getMessage());

    }

    }

  5. Since the domain objects are automatically created, what happens when we want to modify them – like adding hibernate annotations? Now, each time the schema changes, we generate the domain objects again, and we’ll have to add/merge the annotations all over again. Is there any other way that you would recommend, like automatically generating the hibernate annotations too, based on a property file or something? Any thoughts on the same is appreciated.

  6. Hi Bharath, good question. Editing automatically-generated files is asking for trouble. One solution is to create a parallel set of domain classes that you maintain manually, with code to convert to and from the automatically generated ones. It’s clumsy, but then you can freely add behavior, annotations, etc.

    Another solution that specifically addresses persistence is HyperJAXB (https://hyperjaxb.dev.java.net/). I haven’t tried it yet but it looks interesting. I’ve seen email exchanges suggesting that it can be used with CXF (http://mail-archives.apache.org/mod_mbox/cxf-users/200906.mbox/%3C200906081022.01176.dkulp@apache.org%3E).

  7. Joe,

    Thanks for pointing out hyperjaxb to me. I’ve successfully integrated that with maven to produce the JPA annotations in the schema objects. So I just drop the wsdl/xsd file into the resource and the cxf webservice is ready to be implemented. It did take some head-banging since hyperjaxb3 is not that well documented, IMO. But it’s all cool in the end :) If anyone tries the same and stuck with it somewhere, I could help.

  8. Excellent – thanks for the offer. Would you be willing to post or email me the relevant parts of the pom.xml file? I could make it available for download.

  9. That’s a great post Joe, very helpful and extremely well written!
    by the way, just for the sake on friendly package names (as opposed to reverse DNS) I would suggest adding to the pom file a -p in the wsdlOption section.

  10. I’ve attached the relevant parts of the pom.xml file to use hyperJAXB.
    Also, you’ll have to include the attached bindings.xjb (JAXB bindings file) that maps your schema to specific tables/columns in your resources folder. This will be used to generate the JPA annotations for the domain classes, in your case, Trade, Comments, etc. I’ve assumed the table/column names here.

    Of course, this is assuming that you have configured your appContext.xml with a hibernate session factory for your projects use.

    Let me know if you need any clarifications.

    Bharath.

  11. Ok, here’s another one. I tried adding spring transaction annotations to the service interface methods generated by the cxf-codegen-plugin. But somehow, when I add -xjc-Xannotate as an ‘extraarg’ under the ‘wsdlOption’ tag and try to generate the code, it fails with:
    org.apache.cxf.tools.common.ToolException: XJC reported ‘BadCommandLineException’ for -xjc argument:-extension -Xannotate

    Note that this method works with maven-jaxb2-plugin for generating automatic annotations. Somehow, the xjc that’s with the cxf-codegen-plugin doesn’t recognize this as valid. This is not a big deal right now since I can just add the @Transaction annotations to my service implementation class instead of the automatically generated CXF service interfaces. But it would be nice to have this work nevertheless.

    Anyone have any thoughts on this?

  12. Hey, I am new to web services – I have worked with Oracle web services, which have a web service endpoint where the operations can be invoked- does this web service have something like this? Is there another way to invoke the web service than through using the SOAP-UI tool?

  13. Hey Bharath/Joe

    I am trying to implement the web services similar to the way you have described except that my entity classes (with hibernate annotations) live separately than the cxf generated classes.
    From your post I see that you managed to handle transaction management at the service level. Can you please provide an example of how that can be done and what changes are needed to pom.xml, appContext.xml and EntityManager to handle transaction management?

    -John

  14. i didn’t use the maven but I manually build this project by doing the wsdl2java and created all the component but when I deployed to tomcat, the page shows up but keep getting the 404 page not found for http://localhost:8080/ws-example/ws-example?wsdl

    Do you have your project without in maven. I will include my project too.

Leave a Reply

Your email address will not be published. Required fields are marked *