JAX-WS WebServices with Apache CXF


Bhaskar S 12/24/2011


Introduction

Applications in an Enterprise are built over time and often build with different technologies running on heterogeneous hardware platforms. For example, the Accounts Department may have built its Interest Calculator System using Java running on Linux platform, while the Sales Department may have built its Customer Sales System using C# running on the Microsoft platform. Sooner or later there is a need to integrate these application systems for better Customer service. How do we integrate these two systems ? The answer is WebServices.

A WebService is nothing more than a client application sending a well defined request (with data) over HTTP to a server application and receiving a well defined response. That said, how do we make the C# application communicate with Java application ? The answer is using a platform-independent standard called SOAP. SOAP stands for Simple Object Access Protocol and is an XML based protocol for applications to exchange information over HTTP. And finally, how do we expose the service interface to the client ? The answer again is using a platform-independent standard called WSDL. WSDL stands for Web Services Description Language and is an XML document that describes the services offered and how to access them.

For building WebServices and Service Clients in Java, we use JAva API for XML Web Services (JAX-WS). JAX-WS is an API specification that aims to simplify WebServices development through the use of Java Annotations. Apache CXF is one of the popular open-source frameworks that implements the JAX-WS specification.


Setup

For our examples, we will need the following open-source products:

For the samples, I downloaded and set them up in the respective directories as follows:

We will develop two types of samples – one is a standalone WebService using just Apache CXF and the other is using Apache CXF that is deployed inside the Apache Tomcat web container.

NOTE :: All the setup is on a Linux desktop. It should be easy to setup on Windows too

We you download and extract Apache CXF in the directory “$HOME/Applications/apache-cxf-2.5.1”, you will see a directory called “lib” that contains jar files provided with the CXF distribution. For developing JAX-WS based WebServices, we only need the following jars:

The samples we develop will be located in the following directory: $HOME/Projects/Java/CXF-WebServices

This project directory will contain the following sub-directories:

There are two approaches for developing WebServices:

With the Code-first approach, we first develop the code for implementing the WebServices and then expose the WSDL.

With the Contract-first approach, we first start with the WSDL and then generate code from the WSDL using the tools provided by the Apache CXF framework.

We will begin with the Code-first approach to developing WebServices using the Apache CXF Framework.


Code-first Standalone WebServices

Our first example will be a standalone JAX-WS WebService using just the Apache CXF Framework. When we say standalone WebServices, it means that we will not be hosting it in a Web container like Apache Tomcat. The Apache CXF Framework by default uses Jetty server for exposing services. For our example, we choose the simple interest calculator and expose it as a WebService.

The first step is to define the WebService interface. In the JAX-WS parlance, it is called as the Service Endpoint Interface (SEI). SEI is nothing more than a Java interface that defines the business method(s) to be exposed as remote service(s).

The following defines WebService interface for the simple interest calculator:

Listing.1
package com.polarsparc.cxf.jaxws.standalone;

import javax.jws.WebService;

@WebService
public interface SimpleInterestCalculator {
public double calculateSimpleInterest(int months, double amount, double rate)
throws Exception;
}

Next step is to implement the WebService interface SimpleInterestCalculator.

The following Java class defines the service implementation:

Listing.2
package com.polarsparc.cxf.jaxws.standalone;

import javax.jws.WebService;

@WebService
public class SimpleInterestCalculatorImpl implements SimpleInterestCalculator {
@Override
public double calculateSimpleInterest(int months, double amount, double rate)
throws Exception {
validateParams(months, amount, rate);

double interest = (amount * rate * months)/(12 * 100);

return interest;
}

private void validateParams(int months, double amount, double rate)
throws Exception {
if (months <= 0) {
throw new Exception("Invalid parameter: months");
}

if (amount <= 0.0) {
throw new Exception("Invalid parameter: amount");
}

if (rate <= 0.0) {
throw new Exception("Invalid parameter: rate");
}
}
}

Notice the use of the Java annotation @WebService on both the interface as well as the implementation class. The purpose of this annotation is to mark the interface as the WebService interface and to mark the implementation class as the service endpoint. This annotation is declared right above the interface or the class.

The final step is to publish the implementation of SimpleInterestCalculator as a remote WebService, which can be accessed by clients via HTTP endpoint URL.

The following Java class uses the static “publish()” method of the JAX-WS javax.xml.ws.Endpoint class to publish the implementation of SimpleInterestCalculator as a Web Service on the specified HTTP endpoint URL:

Listing.3
package com.polarsparc.cxf.jaxws.standalone;

import javax.xml.ws.Endpoint;

public class SimpleInterestCalculatorService {
public static void main(String[] args) {
try {
String endpoint = "http://localhost:8080/SimpleInterestCalculator";

SimpleInterestCalculator implementor = new SimpleInterestCalculatorImpl();

System.out.printf("Ready to start SimpleInterestCalculator jax-ws service\n");

Endpoint.publish(endpoint, implementor);
}
catch (Exception ex) {
ex.printStackTrace(System.err);
}
}
}

NOTE :: By default, the Apache CXF Framework uses embedded Jetty web server to expose our WebService when we call the function Endpoint.publish(endpoint, implementor)

Now that we have implemented our first WebService, we need to start it up so we can use it. To start our WebService, let us create a script called “SimpleInterestService.sh” in the “bin” directory as follows:

CP=.:./build/classes:$CXF_HOME/lib/commons-logging-1.1.1.jar:
$CXF_HOME/lib/cxf-2.5.1.jar:$CXF_HOME/lib/geronimo-activation_1.1_spec-1.1.jar:
$CXF_HOME/lib/geronimo-annotation_1.0_spec-1.1.1.jar:
$CXF_HOME/lib/geronimo-jaxws_2.2_spec-1.1.jar:
$CXF_HOME/lib/geronimo-servlet_2.5_spec-1.1.2.jar:
$CXF_HOME/lib/geronimo-stax-api_1.0_spec-1.0.1.jar:
$CXF_HOME/lib/geronimo-ws-metadata_2.0_spec-1.1.3.jar:
$CXF_HOME/lib/jaxb-api-2.2.3.jar:
$CXF_HOME/lib/jaxb-impl-2.2.4-1.jar:
$CXF_HOME/lib/jetty-continuation-7.5.3.v20111011.jar:
$CXF_HOME/lib/jetty-http-7.5.3.v20111011.jar:
$CXF_HOME/lib/jetty-io-7.5.3.v20111011.jar:
$CXF_HOME/lib/jetty-server-7.5.3.v20111011.jar:
$CXF_HOME/lib/jetty-util-7.5.3.v20111011.jar:
$CXF_HOME/lib/msv-core-2011.1.jar:
$CXF_HOME/lib/neethi-3.0.1.jar:
$CXF_HOME/lib/stax2-api-3.1.1.jar:
$CXF_HOME/lib/woodstox-core-asl-4.1.1.jar:
$CXF_HOME/lib/wsdl4j-1.6.2.jar:
$CXF_HOME/lib/xmlschema-core-2.0.1.jar

java -cp $CP com.polarsparc.cxf.jaxws.standalone.SimpleInterestCalculatorService

Open a Terminal window and execute the script “bin/SimpleInterestService.sh” from the directory $HOME/Projects/Java/CXF-WebServices and we should see something as follows:

Output.1

Ready to start SimpleInterestCalculator jax-ws service
Dec 26, 2011 12:29:24 PM org.apache.cxf.service.factory.ReflectionServiceFactoryBean buildServiceFromClass
INFO: Creating Service {http://standalone.jaxws.cxf.polarsparc.com/}SimpleInterestCalculatorImplService from class com.polarsparc.cxf.jaxws.standalone.SimpleInterestCalculatorImpl
Dec 26, 2011 12:29:24 PM org.apache.cxf.endpoint.ServerImpl initDestination
INFO: Setting the server's publish address to be http://localhost:8080/SimpleInterestCalculator
2011-12-26 12:29:24.739:INFO:oejs.Server:jetty-7.5.3.v20111011
2011-12-26 12:29:24.805:INFO:oejs.AbstractConnector:Started SelectChannelConnector@localhost:8080 STARTING
2011-12-26 12:29:24.820:INFO:oejsh.ContextHandler:started o.e.j.s.h.ContextHandler{,null}

You can get the WSDL for this service by accessing the following URL: http://localhost:8080/SimpleInterestCalculator?wsdl

The following is the WSDL:

Output.2

<wsdl:definitions xmlns:ns1="http://schemas.xmlsoap.org/soap/http"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:tns="http://standalone.jaxws.cxf.polarsparc.com/"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
name="SimpleInterestCalculatorImplService"
targetNamespace="http://standalone.jaxws.cxf.polarsparc.com/">

<wsdl:types>
<xsd:schema xmlns:tns="http://standalone.jaxws.cxf.polarsparc.com/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
attributeFormDefault="unqualified"
elementFormDefault="unqualified"
targetNamespace="http://standalone.jaxws.cxf.polarsparc.com/">
<xsd:element name="Exception" type="tns:Exception"/>
<xsd:complexType name="Exception">
<xsd:sequence>
<xsd:element minOccurs="0" name="message" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:element name="calculateSimpleInterest" type="tns:calculateSimpleInterest"/>
<xsd:complexType name="calculateSimpleInterest">
<xsd:sequence>
<xsd:element name="arg0" type="xsd:int"/>
<xsd:element name="arg1" type="xsd:double"/>
<xsd:element name="arg2" type="xsd:double"/>
</xsd:sequence>
</xsd:complexType>
<xsd:element name="calculateSimpleInterestResponse"
type="tns:calculateSimpleInterestResponse"/>
<xsd:complexType name="calculateSimpleInterestResponse">
<xsd:sequence>
<xsd:element name="return" type="xsd:double"/>
</xsd:sequence>
</xsd:complexType>
</xsd:schema>
</wsdl:types>

<wsdl:message name="Exception">
<wsdl:part element="tns:Exception" name="Exception"></wsdl:part>
</wsdl:message>
<wsdl:message name="calculateSimpleInterest">
<wsdl:part element="tns:calculateSimpleInterest" name="parameters"></wsdl:part>
</wsdl:message>
<wsdl:message name="calculateSimpleInterestResponse">
<wsdl:part element="tns:calculateSimpleInterestResponse" name="parameters"></wsdl:part>
</wsdl:message>

<wsdl:portType name="SimpleInterestCalculator">
<wsdl:operation name="calculateSimpleInterest">
<wsdl:input message="tns:calculateSimpleInterest" name="calculateSimpleInterest"/>
<wsdl:output message="tns:calculateSimpleInterestResponse"
name="calculateSimpleInterestResponse"/>
<wsdl:fault message="tns:Exception" name="Exception"/>
</wsdl:operation>
</wsdl:portType>

<wsdl:binding name="SimpleInterestCalculatorImplServiceSoapBinding"
type="tns:SimpleInterestCalculator">
<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="calculateSimpleInterest">
<soap:operation soapAction="" style="document"/>
<wsdl:input name="calculateSimpleInterest">
<soap:body use="literal"/>
</wsdl:input>
<wsdl:output name="calculateSimpleInterestResponse">
<soap:body use="literal"/>
</wsdl:output>
<wsdl:fault name="Exception">
<soap:fault name="Exception" use="literal"/>
</wsdl:fault>
</wsdl:operation>
</wsdl:binding>

<wsdl:service name="SimpleInterestCalculatorImplService">
<wsdl:port binding="tns:SimpleInterestCalculatorImplServiceSoapBinding"
name="SimpleInterestCalculatorImplPort">
<soap:address location="http://localhost:8080/SimpleInterestCalculator"/>
</wsdl:port>
</wsdl:service>

</wsdl:definitions>

The WSDL wsdl:types element describes all the data types used in the web service. It uses XML schema to define the data types.

The WSDL wsdl:message element describes the request and response data that is exchanged between the service provider and the client. There are two types of messages: one is the input message which wraps the parameters passed to a service (request) and the other is the output message which wraps the return result (response) from the service.

The WSDL wsdl:portType element describes the various operations offered by a web service. In our example, it is just the one operation called “calculateSimpleInterest”. For each of the operations, it lists the input and output messages exchanged.

The WSDL wsdl:binding element describes the message format and transport specifics used by the web service. In our example, we are using SOAP over HTTP.

And, finally the WSDL wsdl:service element indicates the endpoint address of the web service. In our example, it is “http://localhost:8080/SimpleInterestCalculator”.

Now that we have deployed our WebService, we need a client to invoke our service. The following Java class defines the client implementation:

Listing.4
package com.polarsparc.cxf.jaxws.standalone;

import java.net.URL;

import javax.xml.namespace.QName;
import javax.xml.ws.Service;

public class SimpleInterestCalculatorClient {
public static void main(String[] args) {
try {
if (args.length != 3) {
System.out.printf("Usage: java %s <months> <amount> <rate>\n",
SimpleInterestCalculatorClient.class.getName());
System.exit(1);
}

QName serviceName = new QName("http://standalone.jaxws.cxf.polarsparc.com/",
"SimpleInterestCalculatorImplService");

String wsdl = "http://localhost:8080/SimpleInterestCalculator?wsdl";

Service service = Service.create(new URL(wsdl), serviceName); // [1]

QName portName = new QName("http://standalone.jaxws.cxf.polarsparc.com/",
"SimpleInterestCalculatorImplPort");

SimpleInterestCalculator client = service.getPort(portName,
SimpleInterestCalculator.class); // [2]

double interest = client.calculateSimpleInterest(Integer.valueOf(args[0]),
Double.valueOf(args[1]), Double.valueOf(args[2]));

System.out.printf("Amount: %s, Rate: %s, Months: %s, Interest: %.2f\n",
args[1], args[2], args[0], interest);
}
catch (Exception ex) {
ex.printStackTrace(System.err);
}
}
}

// [1] : The Service object is like a factory object that allows for the creation of client proxy for the web service.

// [2] : The getPort() method on the Service object returns the client proxy for the web service.

To test our web service client, let us create a script called “SimpleInterestClient.sh” in the “bin” directory as follows:

cd ~/ProductHome/bin/cmd

NOTE :: The client takes three command line arguments: months, amount and rate

Open a Terminal window and execute the script “bin/SimpleInterestClient.sh” from the directory $HOME/Projects/Java/CXF-WebServices with arguments 12 (months), 10000 (amount), and 2.75 (rate) and we should see something as follows:

Output.3

Dec 26, 2011 1:54:27 PM org.apache.cxf.service.factory.ReflectionServiceFactoryBean buildServiceFromWSDL
INFO: Creating Service {http://standalone.jaxws.cxf.polarsparc.com/}SimpleInterestCalculatorImplService from WSDL: http://localhost:8080/SimpleInterestCalculator?wsdl
Amount: 10000, Rate: 2.75, Months: 12, Interest: 275.00

CAUTION :: If the interface is not annotated as a Web service, we will encounter the following exception: javax.xml.ws.WebServiceException: Could not find wsdl:binding operation info for web method calculateSimpleInterest

Now, lets try another example. Execute the script “bin/SimpleInterestClient.sh” from the directory $HOME/Projects/Java/CXF-WebServices with arguments -12 (months), 10000 (amount), and 2.75 (rate) and we should see something as follows:

Output.4

Dec 26, 2011 1:55:14 PM org.apache.cxf.service.factory.ReflectionServiceFactoryBean buildServiceFromWSDL
INFO: Creating Service {http://standalone.jaxws.cxf.polarsparc.com/}SimpleInterestCalculatorImplService from WSDL: http://localhost:8080/SimpleInterestCalculator?wsdl
java.lang.Exception: Invalid parameter: months
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
at org.apache.cxf.jaxb.JAXBEncoderDecoder.unmarshallException(JAXBEncoderDecoder.java:456)
at org.apache.cxf.jaxb.JAXBEncoderDecoder.unmarshall(JAXBEncoderDecoder.java:595)
at org.apache.cxf.jaxb.io.DataReaderImpl.read(DataReaderImpl.java:156)
at org.apache.cxf.interceptor.ClientFaultConverter.processFaultDetail(ClientFaultConverter.java:153)
at org.apache.cxf.interceptor.ClientFaultConverter.handleMessage(ClientFaultConverter.java:80)
at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:263)
at org.apache.cxf.interceptor.AbstractFaultChainInitiatorObserver.onMessage(AbstractFaultChainInitiatorObserver.java:107)
at org.apache.cxf.binding.soap.interceptor.CheckFaultInterceptor.handleMessage(CheckFaultInterceptor.java:69)
at org.apache.cxf.binding.soap.interceptor.CheckFaultInterceptor.handleMessage(CheckFaultInterceptor.java:34)
at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:263)
at org.apache.cxf.endpoint.ClientImpl.onMessage(ClientImpl.java:799)
at org.apache.cxf.transport.http.HTTPConduit$WrappedOutputStream.handleResponseInternal(HTTPConduit.java:1627)
at org.apache.cxf.transport.http.HTTPConduit$WrappedOutputStream.handleResponse(HTTPConduit.java:1494)
at org.apache.cxf.transport.http.HTTPConduit$WrappedOutputStream.close(HTTPConduit.java:1402)
at org.apache.cxf.transport.AbstractConduit.close(AbstractConduit.java:56)
at org.apache.cxf.transport.http.HTTPConduit.close(HTTPConduit.java:649)
at org.apache.cxf.interceptor.MessageSenderInterceptor$MessageSenderEndingInterceptor.handleMessage(MessageSenderInterceptor.java:62)
at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:263)
at org.apache.cxf.endpoint.ClientImpl.doInvoke(ClientImpl.java:533)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:463)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:366)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:319)
at org.apache.cxf.frontend.ClientProxy.invokeSync(ClientProxy.java:88)
at org.apache.cxf.jaxws.JaxWsClientProxy.invoke(JaxWsClientProxy.java:134)
at $Proxy19.calculateSimpleInterest(Unknown Source)
at com.polarsparc.cxf.jaxws.standalone.SimpleInterestCalculatorClient.main(SimpleInterestCalculatorClient.java:26)

We specified an invalid month and hence the service rejected our request.

Hurray !!! We have successfully deployed and tested our first web service using Apache CXF.

Now, let us revisit the WSDL for our web service. From the WSDL discussed above, we see that the Apache CXF Web Service framework assigned all the names. For example, in wsdl:types, the data elements are named “arg0”, “arg1”, and “arg2” respectively. Similarly, in wsdl:service, the service is named “SimpleInterestCalculatorImplService” and the port “SimpleInterestCalculatorImplPort”. To make the names more human friendly, we can specify the appropriate names in the service interface as well as the service implementation using annotations. We will use the same example as above to explore the annotations.

The following defines Web Service interface for the simple interest calculator:

Listing.5
package com.polarsparc.cxf.jaxws.standalone;

import javax.jws.WebParam;
import javax.jws.WebService;

@WebService(name="SimpleInterestCalculator",
targetNamespace="http://polarsparc.com/")
public interface SimpleInterestCalculator2 {
public double calculateSimpleInterest2(@WebParam(name="months") int months,
@WebParam(name="amount") double amount,
@WebParam(name="rate") double rate)
throws Exception;
}

Notice the use of the annotation @WebService which is defined by javax.jws.WebService to specify some attributes. The following are the list of attributes one can specify using the @WebService annotation:

Attribute Description
name Specifies the name of the service interface and maps to the name attribute of the WSDL wsdl:portType element
targetNamespace Specifies the target namespace for the service
serviceName Specifies the name of the remote service and maps to the name attribute of the WSDL wsdl:service element
wsdlLocation Specifies the URI where the service WSDL is located
portName Specifies the name of the publised endpoint and maps to the name attribute of the WSDL wsdl:port element

For our example SEI, we have specified the name and targetNamespace attributes. For the name attribute, we have specified “SimpleInterestCalculator” and for the targetNamespace, we have specified an appropriate namespace of “http://polarsparc.com/” instead of the default namespace of “http://standalone.jaxws.cxf.polarsparc.com/”.

Also, notice the use of the annotation @WebParam which is defined by javax.jws.WebParam to specify the name attribute. The following are the list of attributes one can specify using the @WebParam annotation:

Attribute Description
name Specifies the name of the parameter
targetNamespace Specifies the target namespace for the parameter
mode Specifies the mode (IN, OUT, INOUT) of the parameter. The default is IN

For our example SEI, we have specified the name attribute. So instead of the default parameter names of “arg0”, “arg1”, and “arg2”, it will be the more friendlier parameter names of “months”, “amount”, and “rate” respectively.

Next step is to implement the Web Service interface SimpleInterestCalculator2. The following Java class defines the service implementation:

Listing.6
package com.polarsparc.cxf.jaxws.standalone;

import javax.jws.WebService;

@WebService(name="SimpleInterestCalculator",
targetNamespace="http://polarsparc.com/",
serviceName="SimpleInterestCalculatorService",
portName="SimpleInterestCalculatorPort")
public class SimpleInterestCalculatorImpl2 implements SimpleInterestCalculator2 {
@Override
public double calculateSimpleInterest2(int months, double amount, double rate)
throws Exception {
validateParams(months, amount, rate);

double interest = (amount * rate * months)/(12 * 100);

return interest;
}

private void validateParams(int months, double amount, double rate)
throws Exception {
if (months <= 0) {
throw new Exception("Invalid parameter: months");
}

if (amount <= 0.0) {
throw new Exception("Invalid parameter: amount");
}

if (rate <= 0.0) {
throw new Exception("Invalid parameter: rate");
}
}
}

Notice the use of the Java annotation @WebService to specify the target namespace, the service name and the port name.

The following class publishes the implementation of SimpleInterestCalculator2 as a remote Web Service:

Listing.7
package com.polarsparc.cxf.jaxws.standalone;

import javax.xml.ws.Endpoint;

public class SimpleInterestCalculatorService2 {
public static void main(String[] args) {
try {
String endpoint = "http://localhost:8080/SimpleInterestCalculator2";

SimpleInterestCalculator2 implementor = new SimpleInterestCalculatorImpl2();

System.out.printf("Ready to start SimpleInterestCalculator2 jax-ws service\n");

Endpoint.publish(endpoint, implementor);
}
catch (Exception ex) {
ex.printStackTrace(System.err);
}
}
}

To start our enhanced web service, let us create a script called “SimpleInterestService2.sh” in the “bin” directory as follows:

CP=.:./build/classes:$CXF_HOME/lib/commons-logging-1.1.1.jar:
$CXF_HOME/lib/cxf-2.5.1.jar:$CXF_HOME/lib/geronimo-activation_1.1_spec-1.1.jar:
$CXF_HOME/lib/geronimo-annotation_1.0_spec-1.1.1.jar:
$CXF_HOME/lib/geronimo-jaxws_2.2_spec-1.1.jar:
$CXF_HOME/lib/geronimo-servlet_2.5_spec-1.1.2.jar:
$CXF_HOME/lib/geronimo-stax-api_1.0_spec-1.0.1.jar:
$CXF_HOME/lib/geronimo-ws-metadata_2.0_spec-1.1.3.jar:
$CXF_HOME/lib/jaxb-api-2.2.3.jar:
$CXF_HOME/lib/jaxb-impl-2.2.4-1.jar:
$CXF_HOME/lib/jetty-continuation-7.5.3.v20111011.jar:
$CXF_HOME/lib/jetty-http-7.5.3.v20111011.jar:
$CXF_HOME/lib/jetty-io-7.5.3.v20111011.jar:
$CXF_HOME/lib/jetty-server-7.5.3.v20111011.jar:
$CXF_HOME/lib/jetty-util-7.5.3.v20111011.jar:
$CXF_HOME/lib/msv-core-2011.1.jar:
$CXF_HOME/lib/neethi-3.0.1.jar:
$CXF_HOME/lib/stax2-api-3.1.1.jar:
$CXF_HOME/lib/woodstox-core-asl-4.1.1.jar:
$CXF_HOME/lib/wsdl4j-1.6.2.jar:
$CXF_HOME/lib/xmlschema-core-2.0.1.jar

java -cp $CP com.polarsparc.cxf.jaxws.standalone.SimpleInterestCalculatorService2

Kill the script “SimpleInterestService.sh” if it is still running.

Open a Terminal window and execute the script “bin/SimpleInterestService2.sh” from the directory $HOME/Projects/Java/CXF-WebServices and we should see something as follows:

Output.5

Ready to start SimpleInterestCalculator2 jax-ws service
Dec 26, 2011 2:53:16 PM org.apache.cxf.service.factory.ReflectionServiceFactoryBean buildServiceFromClass
INFO: Creating Service {http://polarsparc.com/}SimpleInterestCalculatorService from class com.polarsparc.cxf.jaxws.standalone.SimpleInterestCalculator2
Dec 26, 2011 2:53:17 PM org.apache.cxf.endpoint.ServerImpl initDestination
INFO: Setting the server's publish address to be http://localhost:8080/SimpleInterestCalculator2
2011-12-26 14:53:17.220:INFO:oejs.Server:jetty-7.5.3.v20111011
2011-12-26 14:53:17.257:INFO:oejs.AbstractConnector:Started SelectChannelConnector@localhost:8080 STARTING
2011-12-26 14:53:17.271:INFO:oejsh.ContextHandler:started o.e.j.s.h.ContextHandler{,null}

Now, let us get the WSDL for this service by accessing the following URL: http://localhost:8080/SimpleInterestCalculator2?wsdl

The following is the WSDL:

Output.6

<wsdl:definitions xmlns:ns1="http://schemas.xmlsoap.org/soap/http"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:tns="http://polarsparc.com/"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
name="SimpleInterestCalculatorService"
targetNamespace="http://polarsparc.com/">

<wsdl:types>
<xsd:schema xmlns:tns="http://polarsparc.com/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
attributeFormDefault="unqualified"
elementFormDefault="unqualified"
targetNamespace="http://polarsparc.com/">
<xsd:element name="Exception" type="tns:Exception"/>
<xsd:complexType name="Exception">
<xsd:sequence>
<xsd:element minOccurs="0" name="message" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:element name="calculateSimpleInterest2" type="tns:calculateSimpleInterest2"/>
<xsd:complexType name="calculateSimpleInterest2">
<xsd:sequence>
<xsd:element name="months" type="xsd:int"/>
<xsd:element name="amount" type="xsd:double"/>
<xsd:element name="rate" type="xsd:double"/>
</xsd:sequence>
</xsd:complexType>
<xsd:element name="calculateSimpleInterest2Response"
type="tns:calculateSimpleInterest2Response"/>
<xsd:complexType name="calculateSimpleInterest2Response">
<xsd:sequence>
<xsd:element name="return" type="xsd:double"/>
</xsd:sequence>
</xsd:complexType>
</xsd:schema>
</wsdl:types>

<wsdl:message name="Exception">
<wsdl:part element="tns:Exception" name="Exception"></wsdl:part>
</wsdl:message>
<wsdl:message name="calculateSimpleInterest2">
<wsdl:part element="tns:calculateSimpleInterest2" name="parameters"></wsdl:part>
</wsdl:message>
<wsdl:message name="calculateSimpleInterest2Response">
<wsdl:part element="tns:calculateSimpleInterest2Response" name="parameters"></wsdl:part>
</wsdl:message>

<wsdl:portType name="SimpleInterestCalculator">
<wsdl:operation name="calculateSimpleInterest2">
<wsdl:input message="tns:calculateSimpleInterest2" name="calculateSimpleInterest2"/>
<wsdl:output message="tns:calculateSimpleInterest2Response"
name="calculateSimpleInterest2Response"/>
<wsdl:fault message="tns:Exception" name="Exception"></wsdl:fault>
</wsdl:operation>
</wsdl:portType>

<wsdl:binding name="SimpleInterestCalculatorServiceSoapBinding"
type="tns:SimpleInterestCalculator">
<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="calculateSimpleInterest2">
<soap:operation soapAction="" style="document"/>
<wsdl:input name="calculateSimpleInterest2">
<soap:body use="literal"/>
</wsdl:input>
<wsdl:output name="calculateSimpleInterest2Response">
<soap:body use="literal"/>
</wsdl:output>
<wsdl:fault name="Exception">
<soap:fault name="Exception" use="literal"/>
</wsdl:fault>
</wsdl:operation>
</wsdl:binding>

<wsdl:service name="SimpleInterestCalculatorService">
<wsdl:port binding="tns:SimpleInterestCalculatorServiceSoapBinding"
name="SimpleInterestCalculatorPort">
<soap:address location="http://localhost:8080/SimpleInterestCalculator2"/>
</wsdl:port>
</wsdl:service>

</wsdl:definitions>

This version of the WSDL has more human friendly names compared to the previous version.

The following Java class defines the client implementation using SimpleInterestCalculator2:

Listing.8
package com.polarsparc.cxf.jaxws.standalone;

import java.net.URL;

import javax.xml.namespace.QName;
import javax.xml.ws.Service;

public class SimpleInterestCalculatorClient2 {
public static void main(String[] args) {
try {
if (args.length != 3) {
System.out.printf("Usage: java %s <months> <amount> <rate>\n",
SimpleInterestCalculatorClient2.class.getName());
System.exit(1);
}

QName serviceName = new QName("http://polarsparc.com/",
"SimpleInterestCalculatorService");

String wsdl = "http://localhost:8080/SimpleInterestCalculator2?wsdl";

Service service = Service.create(new URL(wsdl), serviceName);

QName portName = new QName("http://polarsparc.com/", "SimpleInterestCalculatorPort");

SimpleInterestCalculator2 client = service.getPort(portName,
SimpleInterestCalculator2.class);

double interest = client.calculateSimpleInterest2(Integer.valueOf(args[0]),
Double.valueOf(args[1]), Double.valueOf(args[2]));

System.out.printf("Amount: %s, Rate: %s, Months: %s, Interest: %.2f\n",
args[1], args[2], args[0], interest);
}
catch (Exception ex) {
ex.printStackTrace(System.err);
}
}
}

To test our enhanced web service client, let us create a script called “SimpleInterestClient2.sh” in the “bin” directory as follows:

CP=.:./build/classes:$CXF_HOME/lib/commons-logging-1.1.1.jar:
$CXF_HOME/lib/cxf-2.5.1.jar:$CXF_HOME/lib/geronimo-activation_1.1_spec-1.1.jar:
$CXF_HOME/lib/geronimo-annotation_1.0_spec-1.1.1.jar:
$CXF_HOME/lib/geronimo-jaxws_2.2_spec-1.1.jar:
$CXF_HOME/lib/geronimo-servlet_2.5_spec-1.1.2.jar:
$CXF_HOME/lib/geronimo-stax-api_1.0_spec-1.0.1.jar:
$CXF_HOME/lib/geronimo-ws-metadata_2.0_spec-1.1.3.jar:
$CXF_HOME/lib/jaxb-api-2.2.3.jar:
$CXF_HOME/lib/jaxb-impl-2.2.4-1.jar:
$CXF_HOME/lib/jetty-continuation-7.5.3.v20111011.jar:
$CXF_HOME/lib/jetty-http-7.5.3.v20111011.jar:
$CXF_HOME/lib/jetty-io-7.5.3.v20111011.jar:
$CXF_HOME/lib/jetty-server-7.5.3.v20111011.jar:
$CXF_HOME/lib/jetty-util-7.5.3.v20111011.jar:
$CXF_HOME/lib/msv-core-2011.1.jar:
$CXF_HOME/lib/neethi-3.0.1.jar:
$CXF_HOME/lib/stax2-api-3.1.1.jar:
$CXF_HOME/lib/woodstox-core-asl-4.1.1.jar:
$CXF_HOME/lib/wsdl4j-1.6.2.jar:
$CXF_HOME/lib/xmlschema-core-2.0.1.jar

java -cp $CP com.polarsparc.cxf.jaxws.standalone.SimpleInterestCalculatorClient2 $1 $2 $3

NOTE :: The client takes three command line arguments: months, amount and rate

Open a Terminal window and execute the script “bin/SimpleInterestClient2.sh” from the directory $HOME/Projects/Java/CXF-WebServices with arguments 12 (months), 10000 (amount), and 2.75 (rate) and we should see something as follows:

Output.7

Dec 26, 2011 2:54:56 PM org.apache.cxf.service.factory.ReflectionServiceFactoryBean buildServiceFromWSDL
INFO: Creating Service {http://polarsparc.com/}SimpleInterestCalculatorService from WSDL: http://localhost:8080/SimpleInterestCalculator2?wsdl
Amount: 10000, Rate: 2.75, Months: 12, Interest: 275.00

Until now we have been passing individual parameters to our Simple Interest WebService method “calculateSimpleInterest”. What if we want to use an object to encapsulate the simple interest details ? Let us check that option as well.

The following Java class defines the SimpleInterestDTO object implementation:

Listing.9
package com.polarsparc.cxf.jaxws.standalone;

import java.io.Serializable;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name="SimpleInterestDTO",
namespace="http://polarsparc.com/")
public class SimpleInterestDTO implements Serializable {
private static final long serialVersionUID = 1L;

private int months;
private double amount;
private double rate;

public SimpleInterestDTO() {
this.months = 0;
this.amount = 0.0;
this.rate = 0.0;
}

public SimpleInterestDTO(int months, double amount, double rate) {
this.months = months;
this.amount = amount;
this.rate = rate;
}

public int getMonths() {
return this.months;
}

public void setMonths(int months) {
this.months = months;
}

public double getAmount() {
return this.amount;
}

public void setAmount(double amount) {
this.amount = amount;
}

public double getRate() {
return this.rate;
}

public void setRate(double rate) {
this.rate = rate;
}
}

Notice the use of the Java annotation @XmlRootElement to specify the name and as well as the namespace. This is the annotation from Java Architecture for XML Binding (JAXB). JAXB allows us to convert a Java object into XML and vice-versa. Internally, Apache CXF Framework uses JAXB for converting data between Java and XML. When the WebService call is made, the SimpleInterestDTO object is serialized into XML using JAXB and transported as SOAP over HTTP. On the service provider end, Apache CXF converts the XML data back into the SimpleInterestDTO object using JAXB before invoking the service method. The annotation @XmlRootElement maps the SimpleInterestDTO object to the XML root element “<dto>”. The attributes of the SimpleInterestDTO object are by default mapped as XML elements with the same name as the attributes in the object.

The following defines WebService interface using the SimpleInterestDTO object:

Listing.10
package com.polarsparc.cxf.jaxws.standalone;

import javax.jws.WebService;
import javax.jws.WebParam;

@WebService(name="SimpleInterestCalculator",
targetNamespace="http://polarsparc.com/")
public interface SimpleInterestCalculator3 {
public double calculateSimpleInterest3(@WebParam(name="dto") SimpleInterestDTO dto)
throws Exception;
}

Next step is to implement the WebService interface SimpleInterestCalculator3. The following Java class defines the service implementation:

Listing.11
package com.polarsparc.cxf.jaxws.standalone;

import javax.jws.WebService;

@WebService(name="SimpleInterestCalculator",
targetNamespace="http://polarsparc.com/",
serviceName="SimpleInterestCalculatorService",
portName="SimpleInterestCalculatorPort")
public class SimpleInterestCalculatorImpl3 implements SimpleInterestCalculator3 {
@Override
public double calculateSimpleInterest3(SimpleInterestDTO dto)
throws Exception {
validateParams(loan);

double interest = (dto.getAmount() * dto.getRate() * dto.getMonths())/(12 * 100);

return interest;
}

private void validateParams(SimpleInterestDTO dto)
throws Exception {
if (dto.getMonths() <= 0) {
throw new Exception("Invalid parameter: months");
}

if (dto.getAmount() <= 0.0) {
throw new Exception("Invalid parameter: amount");
}

if (dto.getRate() <= 0.0) {
throw new Exception("Invalid parameter: rate");
}
}
}

The following class publishes the implementation of SimpleInterestCalculator3 as a remote WebService:

Listing.12
package com.polarsparc.cxf.jaxws.standalone;

import javax.xml.ws.Endpoint;

public class SimpleInterestCalculatorService3 {
public static void main(String[] args) {
try {
String endpoint = "http://localhost:8080/SimpleInterestCalculator3";

SimpleInterestCalculator3 implementor = new SimpleInterestCalculatorImpl3();

System.out.printf("Ready to start SimpleInterestCalculator3 jax-ws service\n");

Endpoint.publish(endpoint, implementor);
}
catch (Exception ex) {
ex.printStackTrace(System.err);
}
}
}

To start our WebService using the SimpleInterestDTO object, let us create a script called “SimpleInterestService3.sh” in the “bin” directory as follows:

CP=.:./build/classes:$CXF_HOME/lib/commons-logging-1.1.1.jar:
$CXF_HOME/lib/cxf-2.5.1.jar:$CXF_HOME/lib/geronimo-activation_1.1_spec-1.1.jar:
$CXF_HOME/lib/geronimo-annotation_1.0_spec-1.1.1.jar:
$CXF_HOME/lib/geronimo-jaxws_2.2_spec-1.1.jar:
$CXF_HOME/lib/geronimo-servlet_2.5_spec-1.1.2.jar:
$CXF_HOME/lib/geronimo-stax-api_1.0_spec-1.0.1.jar:
$CXF_HOME/lib/geronimo-ws-metadata_2.0_spec-1.1.3.jar:
$CXF_HOME/lib/jaxb-api-2.2.3.jar:
$CXF_HOME/lib/jaxb-impl-2.2.4-1.jar:
$CXF_HOME/lib/jetty-continuation-7.5.3.v20111011.jar:
$CXF_HOME/lib/jetty-http-7.5.3.v20111011.jar:
$CXF_HOME/lib/jetty-io-7.5.3.v20111011.jar:
$CXF_HOME/lib/jetty-server-7.5.3.v20111011.jar:
$CXF_HOME/lib/jetty-util-7.5.3.v20111011.jar:
$CXF_HOME/lib/msv-core-2011.1.jar:
$CXF_HOME/lib/neethi-3.0.1.jar:
$CXF_HOME/lib/stax2-api-3.1.1.jar:
$CXF_HOME/lib/woodstox-core-asl-4.1.1.jar:
$CXF_HOME/lib/wsdl4j-1.6.2.jar:
$CXF_HOME/lib/xmlschema-core-2.0.1.jar

java -cp $CP com.polarsparc.cxf.jaxws.standalone.SimpleInterestCalculatorService3

Kill the script SimpleInterestService2.sh if it is still running.

Open a Terminal window and execute the script “bin/SimpleInterestService3.sh” from the directory $HOME/Projects/Java/CXF-WebServices and we should see something as follows:

Output.8

Ready to start SimpleInterestCalculator3 jax-ws service
Dec 27, 2011 8:07:38 PM org.apache.cxf.service.factory.ReflectionServiceFactoryBean buildServiceFromClass
INFO: Creating Service {http://polarsparc.com/}SimpleInterestCalculatorService from class com.polarsparc.cxf.jaxws.standalone.SimpleInterestCalculator3
Dec 27, 2011 8:07:38 PM org.apache.cxf.endpoint.ServerImpl initDestination
INFO: Setting the server's publish address to be http://localhost:8080/SimpleInterestCalculator3
2011-12-27 20:07:38.975:INFO:oejs.Server:jetty-7.5.3.v20111011
2011-12-27 20:07:39.012:INFO:oejs.AbstractConnector:Started SelectChannelConnector@localhost:8080 STARTING
2011-12-27 20:07:39.025:INFO:oejsh.ContextHandler:started o.e.j.s.h.ContextHandler{,null}

Now, let us get the WSDL for this service by accessing the following URL: http://localhost:8080/SimpleInterestCalculator3?wsdl

The following is the WSDL:

Output.9

<wsdl:definitions xmlns:ns1="http://schemas.xmlsoap.org/soap/http"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:tns="http://polarsparc.com/"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
name="SimpleInterestCalculatorService"
targetNamespace="http://polarsparc.com/">

<wsdl:types>
<xs:schema xmlns:tns="http://polarsparc.com/"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
attributeFormDefault="unqualified"
elementFormDefault="unqualified"
targetNamespace="http://polarsparc.com/">
<xs:element name="SimpleInterestDTO" type="tns:simpleInterestDTO"/>
<xs:complexType name="simpleInterestDTO">
<xs:sequence>
<xs:element name="amount" type="xs:double"/>
<xs:element name="months" type="xs:int"/>
<xs:element name="rate" type="xs:double"/>
</xs:sequence>
</xs:complexType>
<xs:element name="Exception" type="tns:Exception"/>
<xs:complexType name="Exception">
<xs:sequence>
<xs:element minOccurs="0" name="message" type="xs:string"/>
</xs:sequence>
</xs:complexType>
<xs:element name="calculateSimpleInterest3" type="tns:calculateSimpleInterest3"/>
<xs:complexType name="calculateSimpleInterest3">
<xs:sequence>
<xs:element minOccurs="0" name="dto" type="tns:simpleInterestDTO"/>
</xs:sequence>
</xs:complexType>
<xs:element name="calculateSimpleInterest3Response"
type="tns:calculateSimpleInterest3Response"/>
<xs:complexType name="calculateSimpleInterest3Response">
<xs:sequence>
<xs:element name="return" type="xs:double"/>
</xs:sequence>
</xs:complexType>
</xs:schema>
</wsdl:types>

<wsdl:message name="Exception">
<wsdl:part element="tns:Exception" name="Exception"></wsdl:part>
</wsdl:message>
<wsdl:message name="calculateSimpleInterest3">
<wsdl:part element="tns:calculateSimpleInterest3" name="parameters"></wsdl:part>
</wsdl:message>
<wsdl:message name="calculateSimpleInterest3Response">
<wsdl:part element="tns:calculateSimpleInterest3Response" name="parameters"></wsdl:part>
</wsdl:message>

<wsdl:portType name="SimpleInterestCalculator">
<wsdl:operation name="calculateSimpleInterest3">
<wsdl:input message="tns:calculateSimpleInterest3" name="calculateSimpleInterest3"/>
<wsdl:output message="tns:calculateSimpleInterest3Response"
name="calculateSimpleInterest3Response"/>
<wsdl:fault message="tns:Exception" name="Exception"></wsdl:fault>
</wsdl:operation>
</wsdl:portType>

<wsdl:binding name="SimpleInterestCalculatorServiceSoapBinding"
type="tns:SimpleInterestCalculator">
<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="calculateSimpleInterest3">
<soap:operation soapAction="" style="document"/>
<wsdl:input name="calculateSimpleInterest3">
<soap:body use="literal"/>
</wsdl:input>
<wsdl:output name="calculateSimpleInterest3Response">
<soap:body use="literal"/>
</wsdl:output>
<wsdl:fault name="Exception">
<soap:fault name="Exception" use="literal"/>
</wsdl:fault>
</wsdl:operation>
</wsdl:binding>

<wsdl:service name="SimpleInterestCalculatorService">
<wsdl:port binding="tns:SimpleInterestCalculatorServiceSoapBinding"
name="SimpleInterestCalculatorPort">
<soap:address location="http://localhost:8080/SimpleInterestCalculator3"/>
</wsdl:port>
</wsdl:service>

</wsdl:definitions>

In this version of the WSDL, notice the complex type named calculateSimpleInterest3 - it indicates that an object of type SimpleInterestDTO is passed to the service method.

The following Java class defines the client implementation using SimpleInterestCalculator3:

Listing.13
package com.polarsparc.cxf.jaxws.standalone;

import java.net.URL;

import javax.xml.namespace.QName;
import javax.xml.ws.Service;

public class SimpleInterestCalculatorClient3 {
public static void main(String[] args) {
try {
if (args.length != 3) {
System.out.printf("Usage: java %s <months> <amount> <rate>\n",
SimpleInterestCalculatorClient3.class.getName());
System.exit(1);
}

QName serviceName = new QName("http://polarsparc.com/",
"SimpleInterestCalculatorService");

String wsdl = "http://localhost:8080/SimpleInterestCalculator3?wsdl";

Service service = Service.create(new URL(wsdl), serviceName);

QName portName = new QName("http://polarsparc.com/", "SimpleInterestCalculatorPort");

SimpleInterestCalculator3 client = service.getPort(portName,
SimpleInterestCalculator3.class);

SimpleInterestDTO dto = new SimpleInterestDTO(Integer.valueOf(args[0]),
Double.valueOf(args[1]), Double.valueOf(args[2]));

double interest = client.calculateSimpleInterest3(dto);

System.out.printf("Amount: %s, Rate: %s, Months: %s, Interest: %.2f\n",
args[1], args[2], args[0], interest);
}
catch (Exception ex) {
ex.printStackTrace(System.err);
}
}
}

To test our WebService client using the SimpleInterestDTO object, let us create a script called “SimpleInterestClient3.sh” in the “bin” directory as follows:

CP=.:./build/classes:$CXF_HOME/lib/commons-logging-1.1.1.jar:
$CXF_HOME/lib/cxf-2.5.1.jar:$CXF_HOME/lib/geronimo-activation_1.1_spec-1.1.jar:
$CXF_HOME/lib/geronimo-annotation_1.0_spec-1.1.1.jar:
$CXF_HOME/lib/geronimo-jaxws_2.2_spec-1.1.jar:
$CXF_HOME/lib/geronimo-servlet_2.5_spec-1.1.2.jar:
$CXF_HOME/lib/geronimo-stax-api_1.0_spec-1.0.1.jar:
$CXF_HOME/lib/geronimo-ws-metadata_2.0_spec-1.1.3.jar:
$CXF_HOME/lib/jaxb-api-2.2.3.jar:
$CXF_HOME/lib/jaxb-impl-2.2.4-1.jar:
$CXF_HOME/lib/jetty-continuation-7.5.3.v20111011.jar:
$CXF_HOME/lib/jetty-http-7.5.3.v20111011.jar:
$CXF_HOME/lib/jetty-io-7.5.3.v20111011.jar:
$CXF_HOME/lib/jetty-server-7.5.3.v20111011.jar:
$CXF_HOME/lib/jetty-util-7.5.3.v20111011.jar:
$CXF_HOME/lib/msv-core-2011.1.jar:
$CXF_HOME/lib/neethi-3.0.1.jar:
$CXF_HOME/lib/stax2-api-3.1.1.jar:
$CXF_HOME/lib/woodstox-core-asl-4.1.1.jar:
$CXF_HOME/lib/wsdl4j-1.6.2.jar:
$CXF_HOME/lib/xmlschema-core-2.0.1.jar

java -cp $CP com.polarsparc.cxf.jaxws.standalone.SimpleInterestCalculatorClient3 $1 $2 $3

NOTE :: The client takes three command line arguments: months, amount and rate

Open a Terminal window and execute the script “bin/SimpleInterestClient3.sh” from the directory $HOME/Projects/Java/CXF-WebServices with arguments 12 (months), 10000 (amount), and 2.75 (rate) and we should see something as follows:

Output.10

Dec 27, 2011 8:07:59 PM org.apache.cxf.service.factory.ReflectionServiceFactoryBean buildServiceFromWSDL
INFO: Creating Service {http://polarsparc.com/}SimpleInterestCalculatorService from WSDL: http://localhost:8080/SimpleInterestCalculator3?wsdl
Amount: 10000, Rate: 2.75, Months: 12, Interest: 275.00

Until now all our examples were standalone JAX-WS WebServices that used the embedded Jetty server in the Apache CXF Framework for exposing our services. We will now look at exposing our WebSevice via the Apache Tomcat Web container.

Code-first WebServices in Tomcat

Until now all our examples were standalone JAX-WS WebServices that used the embedded Jetty server in the Apache CXF Framework for exposing our services.

In this section, we will expose our WebSevice via the Apache Tomcat Web container. The way to deploy our WebService in Tomcat is to create and deploy a Web Application Archive (war) file. All Web Application Archives follow a well defined directory structure which is as follows:

The Web Application Archive (file with .war extension) is nothing more than a JAR file of the above specified directory structure with all the files.

The first thing we need for the Web Application is the XML deployment descriptor called web.xml.

The following is the resources/web.xml for our CXF WebServices deployment in Tomcat:

Listing.14
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<display-name>CXF Web Services</display-name>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>WEB-INF/cxf-servlet.xml</param-value>
</context-param>
<servlet>
<servlet-name>CXFServlet</servlet-name>
<servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>CXFServlet</servlet-name>
<url-pattern>/services/*</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>60</session-timeout>
</session-config>
</web-app>

The Web deployment descriptor XML file is stored in the “resources” directory of our project.

By default, the Apache CXF Framework uses Spring to initialize and wire-up the various components and extensions to make the runtime environment ready for use. When we deploy Apache CXF in Tomcat, there needs to be some component that can kick start Spring to bring the CXF runtime alive. That is exactly what the servlet listener class org.springframework.web.context.ContextLoaderListener does. But how does the ContextLoaderListener know what components and extensions of CXF to load ? This is where the XML configuration file cxf-servlet.xml comes into play.

The following is the resources/cxf-servlet.xml for our CXF WebServices deployment in Tomcat:

Listing.15
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jaxws="http://cxf.apache.org/jaxws"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://cxf.apache.org/jaxws
http://cxf.apache.org/schemas/jaxws.xsd">

<import resource="classpath:META-INF/cxf/cxf.xml"/>

<jaxws:endpoint
implementor="com.polarsparc.cxf.jaxws.webapp.SimpleInterestCalculatorImpl4"
address="/SimpleInterestCalculator" id="SimpleInterestCalculator"/>
</beans>

As we can see, the cxf-servlet.xml configuration file imports the necessary CXF XML configuration file cfx.xml. In addition, this is also the configuration file where we specify all the service endpoints (jaxws:endpoint) we want enabled and exposed. In our example, we have specified the financial calculator SimpleInterestCalculator to be exposed as the service endpoint. This configuration XML file is stored in the “resources” directory of our project.

The last piece of the puzzle is the org.apache.cxf.transport.servlet.CXFServlet. It serves as an adapter that intercepts incoming requests and routes to the appropriate service endpoints.

The following defines the SEI for the simple interest calculator:

Listing.16
package com.polarsparc.cxf.jaxws.webapp;

import javax.jws.WebService;
import javax.jws.WebMethod;
import javax.jws.WebResult;
import javax.jws.WebParam;

@WebService(name="SimpleInterestCalculator",
targetNamespace="http://polarsparc.com/")
public interface SimpleInterestCalculator4 {
@WebMethod(operationName="calculateSimpleInterest")
@WebResult(name="interest")
public double calculateSimpleInterest4(@WebParam(name="months") int months,
@WebParam(name="amount") double amount,
@WebParam(name="rate") double rate)
throws Exception;
}

The following Java class defines the service implementation:

Listing.17
package com.polarsparc.cxf.jaxws.webapp;

import javax.jws.WebService;

@WebService(name="SimpleInterestCalculator",
targetNamespace="http://polarsparc.com/",
serviceName="SimpleInterestCalculatorService",
portName="SimpleInterestCalculatorPort")
public class SimpleInterestCalculatorImpl4 implements SimpleInterestCalculator4 {
@Override
public double calculateSimpleInterest4(int months, double amount, double rate)
throws Exception {
validateParams(months, amount, rate);

double interest = (amount * rate * months)/(12 * 100);

return interest;
}

private void validateParams(int months, double amount, double rate)
throws Exception {
if (months <= 0) {
throw new Exception("Invalid parameter: months");
}

if (amount <= 0.0) {
throw new Exception("Invalid parameter: amount");
}

if (rate <= 0.0) {
throw new Exception("Invalid parameter: rate");
}
}
}

To create the Web Application Archive (file with .war extension) for deployment into Tomcat, we use the ANT build script.

The following is the ANT build XML build.xml:

Listing.18
<project name="CXF-WebServices" default="build" basedir=".">
<property environment="env"/>

<property name="cxf.home" location="${env.HOME}/Programs/apache-cxf-2.5.1"/>
<property name="src.dir" location ="${basedir}/src"/>
<property name="res.dir" location ="${basedir}/resources"/>
<property name="build.dir" location ="${basedir}/build"/>
<property name="build.classes.dir" location ="${build.dir}/classes"/>
<property name="temp.dir" location="${build.dir}/temp"/>
<property name="web.dir" location="${temp.dir}/WEB-INF"/>
<property name="web.lib.dir" location="${web.dir}/lib"/>
<property name="web.classes.dir" location="${web.dir}/classes"/>

<path id="cxf.classpath">
<pathelement location="${basedir}"/>
<pathelement location="${build.classes.dir}"/>
<fileset dir="${cxf.home}/lib">
<include name="**/*.jar"/>
</fileset>
</path>

<target name="build" depends="compile,war,clean">
<echo>Building the .war file for web deployment.</echo>
</target>

<target name="compile">
<javac destdir="${build.classes.dir}">
<src path="${src.dir}"/>
<classpath>
<path refid="cxf.classpath"/>
</classpath>
</javac>
</target>

<target name="war" depends="compile">
<mkdir dir="${temp.dir}"/>
<mkdir dir="${web.dir}"/>
<mkdir dir="${web.lib.dir}"/>
<mkdir dir="${web.classes.dir}"/>
<copy file="${res.dir}/web.xml" todir="${web.dir}"/>
<copy file="${res.dir}/cxf-servlet.xml" todir="${web.dir}"/>
<copy todir="${web.lib.dir}">
<fileset dir="${cxf.home}/lib">
<exclude name="servlet-api-*.jar" />
<exclude name="geronimo-servlet_*.jar" />
<exclude name="jetty-*.jar"/>
<exclude name="WHICH_JARS" />
</fileset>
</copy>
<copy todir="${web.classes.dir}">
<fileset dir="${build.classes.dir}" includes="**/webapp/*.class" />
</copy>
<war destfile="${build.dir}/CXFWebServices.war"
webxml="${web.dir}/web.xml" basedir="${temp.dir}">
<lib dir="${web.lib.dir}" />
<classes dir="${web.classes.dir}" />
</war>
</target>

<target name="clean" depends="war">
<delete dir="${temp.dir}"/>
</target>
</project>

When we run the ANT build script, it creates the deployable WAR file called CXFWebServices.war in the project “build” directory. To deploy this WAR file in Tomcat, simply copy the WAR file in the Tomcat “webapps” directory. Remember from Part-1 that we had setup the Apache Tomcat web container in the directory $HOME/Programs/apache-tomcat-6.0.35.

After we copy CXFWebServices.war into $HOME/Programs/apache-tomcat-6.0.35/webapps directory and start the Tomcat server, we see the following in the Tomcat log file:

Output.11

Dec 30, 2011 10:19:09 PM org.apache.catalina.core.AprLifecycleListener init
INFO: The APR based Apache Tomcat Native library which allows optimal performance in production environments
was not found on the java.library.path: /usr/lib/jvm/java-6-sun-1.6.0.26/jre/lib/amd64/server:
/usr/lib/jvm/java-6-sun-1.6.0.26/jre/lib/amd64:/usr/lib/jvm/java-6-sun-1.6.0.26/jre/../lib/amd64:
/usr/java/packages/lib/amd64:/usr/lib64:/lib64:/lib:/usr/lib
Dec 30, 2011 10:19:09 PM org.apache.coyote.http11.Http11Protocol init
INFO: Initializing Coyote HTTP/1.1 on http-8080
Dec 30, 2011 10:19:09 PM org.apache.catalina.startup.Catalina load
INFO: Initialization processed in 427 ms
Dec 30, 2011 10:19:09 PM org.apache.catalina.core.StandardService start
INFO: Starting service Catalina
Dec 30, 2011 10:19:09 PM org.apache.catalina.core.StandardEngine start
INFO: Starting Servlet Engine: Apache Tomcat/6.0.35
Dec 30, 2011 10:19:09 PM org.apache.catalina.startup.HostConfig deployDescriptor
INFO: Deploying configuration descriptor manager.xml
Dec 30, 2011 10:19:10 PM org.apache.catalina.startup.HostConfig deployDescriptor
INFO: Deploying configuration descriptor host-manager.xml
Dec 30, 2011 10:19:10 PM org.apache.catalina.startup.HostConfig deployWAR
INFO: Deploying web application archive CXFWebServices.war
Dec 30, 2011 10:19:10 PM org.springframework.web.context.ContextLoader initWebApplicationContext
INFO: Root WebApplicationContext: initialization started
Dec 30, 2011 10:19:10 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing Root WebApplicationContext: startup date [Fri Dec 30 22:19:10 EST 2011]; root of context hierarchy
Dec 30, 2011 10:19:10 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from ServletContext resource [/WEB-INF/cxf-servlet.xml]
Dec 30, 2011 10:19:10 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [META-INF/cxf/cxf.xml]
Dec 30, 2011 10:19:10 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [META-INF/cxf/cxf-servlet.xml]
Dec 30, 2011 10:19:10 PM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO: Pre-instantiating singletons in
org.springframework.beans.factory.support.DefaultListableBeanFactory@35f38fc6: defining beans [cxf,org.apache.cxf.bus.spring.BusWiringBeanFactoryPostProcessor,
org.apache.cxf.bus.spring.Jsr250BeanPostProcessor,org.apache.cxf.bus.spring.BusExtensionPostProcessor,
org.apache.cxf.binding.soap.SoapBindingFactory,org.apache.cxf.binding.soap.SoapTransportFactory,
org.apache.cxf.binding.soap.customEditorConfigurer,SimpleInterestCalculator]; root of factory hierarchy
Dec 30, 2011 10:19:10 PM org.apache.cxf.service.factory.ReflectionServiceFactoryBean buildServiceFromClass
INFO: Creating Service {http://polarsparc.com/}SimpleInterestCalculatorService from class com.polarsparc.cxf.jaxws.webapp.SimpleInterestCalculator4
Dec 30, 2011 10:19:11 PM org.apache.cxf.endpoint.ServerImpl initDestination
INFO: Setting the server's publish address to be /SimpleInterestCalculator
Dec 30, 2011 10:19:11 PM org.springframework.web.context.ContextLoader initWebApplicationContext
INFO: Root WebApplicationContext: initialization completed in 1212 ms

You can get the WSDL for this service by accessing the following URL: http://localhost:8080/CXFWebServices/services/SimpleInterestCalculator?wsdl

The following is the WSDL:

Output.12

<wsdl:definitions xmlns:ns1="http://schemas.xmlsoap.org/soap/http"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:tns="http://polarsparc.com/"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
name="SimpleInterestCalculatorService"
targetNamespace="http://polarsparc.com/">

<wsdl:types>
<xs:schema xmlns:tns="http://polarsparc.com/"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
attributeFormDefault="unqualified"
elementFormDefault="unqualified"
targetNamespace="http://polarsparc.com/">
<xs:element name="calculateSimpleInterest" type="tns:calculateSimpleInterest"/>
<xs:element name="calculateSimpleInterestResponse"
type="tns:calculateSimpleInterestResponse"/>
<xs:complexType name="calculateSimpleInterest">
<xs:sequence>
<xs:element name="months" type="xs:int"/>
<xs:element name="amount" type="xs:double"/>
<xs:element name="rate" type="xs:double"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="calculateSimpleInterestResponse">
<xs:sequence>
<xs:element name="interest" type="xs:double"/>
</xs:sequence>
</xs:complexType>
<xs:element name="Exception" type="tns:Exception"/>
<xs:complexType name="Exception">
<xs:sequence>
<xs:element minOccurs="0" name="message" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:schema>
</wsdl:types>

<wsdl:message name="calculateSimpleInterest">
<wsdl:part element="tns:calculateSimpleInterest" name="parameters">
</wsdl:part>
</wsdl:message>
<wsdl:message name="Exception">
<wsdl:part element="tns:Exception" name="Exception"></wsdl:part>
</wsdl:message>
<wsdl:message name="calculateSimpleInterestResponse">
<wsdl:part element="tns:calculateSimpleInterestResponse" name="parameters">
</wsdl:part>
</wsdl:message>

<wsdl:portType name="SimpleInterestCalculator">
<wsdl:operation name="calculateSimpleInterest">
<wsdl:input message="tns:calculateSimpleInterest" name="calculateSimpleInterest">
</wsdl:input>
<wsdl:output message="tns:calculateSimpleInterestResponse"
name="calculateSimpleInterestResponse"></wsdl:output>
<wsdl:fault message="tns:Exception" name="Exception"></wsdl:fault>
</wsdl:operation>
</wsdl:portType>

<wsdl:binding name="SimpleInterestCalculatorServiceSoapBinding"
type="tns:SimpleInterestCalculator">
<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="calculateSimpleInterest">
<soap:operation soapAction="" style="document"/>
<wsdl:input name="calculateSimpleInterest">
<soap:body use="literal"/>
</wsdl:input>
<wsdl:output name="calculateSimpleInterestResponse">
<soap:body use="literal"/>
</wsdl:output>
<wsdl:fault name="Exception">
<soap:fault name="Exception" use="literal"/>
</wsdl:fault>
</wsdl:operation>
</wsdl:binding>

<wsdl:service name="SimpleInterestCalculatorService">
<wsdl:port binding="tns:SimpleInterestCalculatorServiceSoapBinding"
name="SimpleInterestCalculatorPort">
<soap:address
location="http://localhost:8080/CXFWebServices/services/
SimpleInterestCalculator"/>
</wsdl:port>
</wsdl:service>

</wsdl:definitions>

The following Java class defines the client implementation using the Service Endpoint Interface (SEI) SimpleInterestCalculator4:

Listing.19
package com.polarsparc.cxf.jaxws.webapp;

import java.net.URL;

import javax.xml.namespace.QName;
import javax.xml.ws.Service;

public class SimpleInterestCalculatorClient4 {
public static void main(String[] args) {
try {
if (args.length != 3) {
System.out.printf("Usage: java %s <months> <amount> <rate>\n",
SimpleInterestCalculatorClient4.class.getName());
System.exit(1);
}

QName serviceName = new QName("http://polarsparc.com/",
"SimpleInterestCalculatorService");

String wsdl = "http://localhost:8080/CXFWebServices/services/
SimpleInterestCalculator?wsdl";

Service service = Service.create(new URL(wsdl), serviceName);

QName portName = new QName("http://polarsparc.com/",
"SimpleInterestCalculatorPort");

SimpleInterestCalculator4 client = service.getPort(portName,
SimpleInterestCalculator4.class);

double interest = client.calculateSimpleInterest4(Integer.valueOf(args[0]),
Double.valueOf(args[1]), Double.valueOf(args[2]));

System.out.printf("Amount: %s, Rate: %s, Months: %s, Interest: %.2f\n",
args[1], args[2], args[0], interest);
}
catch (Exception ex) {
ex.printStackTrace(System.err);
}
}
}

To test our web service client, let us create a script called “SimpleInterestClient4.sh” in the “bin” directory as follows:

CP=.:./build/classes:$CXF_HOME/lib/commons-logging-1.1.1.jar:
$CXF_HOME/lib/cxf-2.5.1.jar:$CXF_HOME/lib/geronimo-activation_1.1_spec-1.1.jar:
$CXF_HOME/lib/geronimo-annotation_1.0_spec-1.1.1.jar:
$CXF_HOME/lib/geronimo-jaxws_2.2_spec-1.1.jar:
$CXF_HOME/lib/geronimo-servlet_2.5_spec-1.1.2.jar:
$CXF_HOME/lib/geronimo-stax-api_1.0_spec-1.0.1.jar:
$CXF_HOME/lib/geronimo-ws-metadata_2.0_spec-1.1.3.jar:
$CXF_HOME/lib/jaxb-api-2.2.3.jar:
$CXF_HOME/lib/jaxb-impl-2.2.4-1.jar:
$CXF_HOME/lib/jetty-continuation-7.5.3.v20111011.jar:
$CXF_HOME/lib/jetty-http-7.5.3.v20111011.jar:
$CXF_HOME/lib/jetty-io-7.5.3.v20111011.jar:
$CXF_HOME/lib/jetty-server-7.5.3.v20111011.jar:
$CXF_HOME/lib/jetty-util-7.5.3.v20111011.jar:
$CXF_HOME/lib/msv-core-2011.1.jar:
$CXF_HOME/lib/neethi-3.0.1.jar:
$CXF_HOME/lib/stax2-api-3.1.1.jar:
$CXF_HOME/lib/woodstox-core-asl-4.1.1.jar:
$CXF_HOME/lib/wsdl4j-1.6.2.jar:
$CXF_HOME/lib/xmlschema-core-2.0.1.jar

java -cp $CP com.polarsparc.cxf.jaxws.webapp.SimpleInterestCalculatorClient4 $1 $2 $3

Open a Terminal window and execute the script “bin/SimpleInterestClient4.sh” from the directory $HOME/Projects/Java/CXF-WebServices with arguments 12 (months), 10000 (amount), and 2.75 (rate) and we should see something as follows:

Output.13

Dec 30, 2011 10:46:39 PM org.apache.cxf.service.factory.ReflectionServiceFactoryBean buildServiceFromWSDL
INFO: Creating Service {http://polarsparc.com/}SimpleInterestCalculatorService from WSDL: http://localhost:8080/CXFWebServices/services/SimpleInterestCalculator?wsdl
Amount: 10000, Rate: 2.75, Months: 12, Interest: 275.00

Yippy !!! We have successfully deployed and tested out WebService in Tomcat.

As we indicated earlier, the Apache CXF Framework uses Spring to initialize and wire-up the various components. That said, we can simplify the client code by using Spring configuration.

The following is the Spring configuration file cxf-http-client.xml that we will use with our simplified client code:

Listing.20
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jaxws="http://cxf.apache.org/jaxws"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://cxf.apache.org/jaxws
http://cxf.apache.org/schema/jaxws.xsd">

<bean id="interestClient" factory-bean="interestClientFactory" factory-method="create"/>

<bean id="interestClientFactory" class="org.apache.cxf.jaxws.JaxWsProxyFactoryBean">
<property name="serviceClass"
value="com.polarsparc.cxf.jaxws.webapp.SimpleInterestCalculator4" />
<property name="address" value="http://localhost:8080/CXFWebServices/services/
SimpleInterestCalculator?wsdl" />
</bean>
</beans>

As we can see, the cxf-http-client.xml Spring configuration file indicates the Service Endpoint Interface (SEI) and the WSDL location in the client factory. This configuration file is stored in the “resources” directory of our project.

The following Java class defines the simplified client implementation using Spring for configuration and using the Service Endpoint Interface (SEI) SimpleInterestCalculator4:

Listing.21
package com.polarsparc.cxf.jaxws.webapp;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SimpleInterestCalculatorClient5 {
public static void main(String[] args) {
try {
if (args.length != 4) {
System.out.printf("Usage: java %s <config> <months> <amount> <rate>\n",
SimpleInterestCalculatorClient5.class.getName());
System.exit(1);
}

ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext(args[0]);

SimpleInterestCalculator4 client =
(SimpleInterestCalculator4) context.getBean("interestClient");

double interest = client.calculateSimpleInterest4(Integer.valueOf(args[1]),
Double.valueOf(args[2]), Double.valueOf(args[3]));

System.out.printf("Amount: %s, Rate: %s, Months: %s, Interest: %.2f\n",
args[2], args[3], args[1], interest);
}
catch (Exception ex) {
ex.printStackTrace(System.err);
}
}
}

Notice the use of Spring ClassPathXmlApplicationContext to load the Spring based client configuration file.

To test our simplified web service client, let us create a script called “SimpleInterestClient5.sh” in the “bin” directory as follows:

CP=.:./build/classes:$CXF_HOME/lib/commons-logging-1.1.1.jar:
$CXF_HOME/lib/cxf-2.5.1.jar:$CXF_HOME/lib/geronimo-activation_1.1_spec-1.1.jar:
$CXF_HOME/lib/geronimo-annotation_1.0_spec-1.1.1.jar:
$CXF_HOME/lib/geronimo-jaxws_2.2_spec-1.1.jar:
$CXF_HOME/lib/geronimo-servlet_2.5_spec-1.1.2.jar:
$CXF_HOME/lib/geronimo-stax-api_1.0_spec-1.0.1.jar:
$CXF_HOME/lib/geronimo-ws-metadata_2.0_spec-1.1.3.jar:
$CXF_HOME/lib/jaxb-api-2.2.3.jar:
$CXF_HOME/lib/jaxb-impl-2.2.4-1.jar:
$CXF_HOME/lib/jetty-continuation-7.5.3.v20111011.jar:
$CXF_HOME/lib/jetty-http-7.5.3.v20111011.jar:
$CXF_HOME/lib/jetty-io-7.5.3.v20111011.jar:
$CXF_HOME/lib/jetty-server-7.5.3.v20111011.jar:
$CXF_HOME/lib/jetty-util-7.5.3.v20111011.jar:
$CXF_HOME/lib/msv-core-2011.1.jar:
$CXF_HOME/lib/neethi-3.0.1.jar:
$CXF_HOME/lib/stax2-api-3.1.1.jar:
$CXF_HOME/lib/woodstox-core-asl-4.1.1.jar:
$CXF_HOME/lib/wsdl4j-1.6.2.jar:
$CXF_HOME/lib/xmlschema-core-2.0.1.jar

java -cp $CP com.polarsparc.cxf.jaxws.webapp.SimpleInterestCalculatorClient4 $1 $2 $3

NOTE :: The client script includes the spring framework related dependency jars

Open a Terminal window and execute the script “bin/SimpleInterestClient5.sh” from the directory $HOME/Projects/Java/CXF-WebServices with arguments resources/cxf-http-client.xml (config), 12 (months), 10000 (amount), and 2.75 (rate) and we should see something as follows:

Output.14

Jan 3, 2012 5:21:39 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@2a0ecd7e: startup date [Tue Jan 03 17:21:39 EST 2012]; root of context hierarchy
Jan 3, 2012 5:21:39 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [resources/cxf-http-client.xml]
Jan 3, 2012 5:21:39 PM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@220ca470: defining beans [client,clientFactory]; root of factory hierarchy
Jan 3, 2012 5:21:40 PM org.apache.cxf.service.factory.ReflectionServiceFactoryBean buildServiceFromClass
INFO: Creating Service {http://polarsparc.com/}SimpleInterestCalculator4Service from class com.polarsparc.cxf.jaxws.webapp.SimpleInterestCalculator4
Amount: 10000, Rate: 2.75, Months: 12, Interest: 275.00

Thus far the consumer (client) and the service provider (WebService) have been communicating over HTTP. In the following section, we will use HTTP over SSL (HTTPS) for communication between the consumer (client) and the service provider (WebService).

The following are the steps to enable SSL over HTTP:

Step. 1

Generate our own SSL certificate and store it in a keystore. For this, we will use the Java keytool command.

The following is the command to generate the necessary SSL certificate in a keystore file called cxfcerts.jks which is stored in the directory “resources”. Run this command from the project directory $HOME/Projects/Java/CXF-WebServices:

keytool -gentkey -alias cxfcerts -keystore resources/cxfcerts.jks -keypass cxfkey -storepass csfkey

The keytool command will prompt you with a series of questions before generating the SSL certificate in the specified keystore file.

The following is a screenshot of executing the keytool command:

Keytool Image
Figure.1

Step. 2

Copy the SSL keystore file “resources/cxfcerts.jks” to the “webapps” directory under the Tomcat installation directory.

Step. 3

Enable HTTPS protocol in the Tomcat server configuration. The Tomcat server configuration is defined in the XML file “conf/server.xml”. Open the file “conf/server.xml” in an editor and locate the following line(s):

Listing.22
    <!-- Define a SSL HTTP/1.1 Connector on port 8443
This connector uses the JSSE configuration, when using APR, the
connector should be using the OpenSSL style configuration
described in the APR documentation -->
<!--
<Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true"
maxThreads="150" scheme="https" secure="true"
clientAuth="false" sslProtocol="TLS" />
-->

Uncomment the XML element “<Connector>” and modify it to look like the following line(s):

Listing.23
    <!-- Define a SSL HTTP/1.1 Connector on port 8443
This connector uses the JSSE configuration, when using APR, the
connector should be using the OpenSSL style configuration
described in the APR documentation -->
<Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true"
maxThreads="150" scheme="https" secure="true"
clientAuth="false" sslProtocol="TLS"
keystoreFile="webapps/cxfcerts.jks" keystorePass="cxfkey" />

Notice the additional attributes that specify the keystore file and the keystore password we used in the keytool command.

Step. 4

Update the Web Application XML deployment descriptor called web.xml.

The following is the modified web.xml for our CXF WebServices deployment in Tomcat that will use SSL over HTTP:

Listing.24
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<display-name>CXF Web Services</display-name>

<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>WEB-INF/cxf-servlet.xml</param-value>
</context-param>

<servlet>
<servlet-name>CXFServlet</servlet-name>
<servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>CXFServlet</servlet-name>
<url-pattern>/services/*</url-pattern>
</servlet-mapping>

<servlet-mapping>
<servlet-name>CXFServlet</servlet-name>
<url-pattern>/secure-services/*</url-pattern>
</servlet-mapping>

<session-config>
<session-timeout>60</session-timeout>
</session-config>

<security-constraint>
<web-resource-collection>
<web-resource-name>Secure CXF Web Services</web-resource-name>
<url-pattern>/secure-services/*</url-pattern>
</web-resource-collection>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
</web-app>

Notice that we are using the URL prefix “/secure-services/*” for SSL.

Step. 5

Rebuild the WAR file called CXFWebServices.war and deploy it into our Tomcat “webapps” directory.

Step. 6

Start the Tomcat server and we see the following in the Tomcat log file:

Output.15

Jan 5, 2012 9:08:04 PM org.apache.catalina.core.AprLifecycleListener init
INFO: The APR based Apache Tomcat Native library which allows optimal performance in production environments
was not found on the java.library.path: /usr/lib/jvm/java-6-sun-1.6.0.26/jre/lib/amd64/server:
/usr/lib/jvm/java-6-sun-1.6.0.26/jre/lib/amd64:/usr/lib/jvm/java-6-sun-1.6.0.26/jre/../lib/amd64:
/usr/java/packages/lib/amd64:/usr/lib64:/lib64:/lib:/usr/lib
Jan 5, 2012 9:08:04 PM org.apache.coyote.http11.Http11Protocol init
INFO: Initializing Coyote HTTP/1.1 on http-8080
Jan 5, 2012 9:08:04 PM org.apache.coyote.http11.Http11Protocol init
INFO: Initializing Coyote HTTP/1.1 on http-8443
Jan 5, 2012 9:08:04 PM org.apache.catalina.startup.Catalina load
INFO: Initialization processed in 645 ms
Jan 5, 2012 9:08:04 PM org.apache.catalina.core.StandardService start
INFO: Starting service Catalina
Jan 5, 2012 9:08:04 PM org.apache.catalina.core.StandardEngine start
INFO: Starting Servlet Engine: Apache Tomcat/6.0.35
Jan 5, 2012 9:08:04 PM org.apache.catalina.startup.HostConfig deployDescriptor
INFO: Deploying configuration descriptor manager.xml
Jan 5, 2012 9:08:04 PM org.apache.catalina.startup.HostConfig deployDescriptor
INFO: Deploying configuration descriptor host-manager.xml
Jan 5, 2012 9:08:04 PM org.apache.catalina.startup.HostConfig deployWAR
INFO: Deploying web application archive CXFWebServices.war
Jan 5, 2012 9:08:04 PM org.springframework.web.context.ContextLoader initWebApplicationContext
INFO: Root WebApplicationContext: initialization started
Jan 5, 2012 9:08:04 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing Root WebApplicationContext: startup date [Thu Jan 05 15:08:04 EST 2012]; root of context hierarchy
Jan 5, 2012 9:08:05 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from ServletContext resource [/WEB-INF/cxf-servlet.xml]
Jan 5, 2012 9:08:05 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [META-INF/cxf/cxf.xml]
Jan 5, 2012 9:08:05 PM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@2031688f:
defining beans [cxf,org.apache.cxf.bus.spring.BusWiringBeanFactoryPostProcessor,org.apache.cxf.bus.spring.Jsr250BeanPostProcessor,
org.apache.cxf.bus.spring.BusExtensionPostProcessor,SimpleInterestCalculator]; root of factory hierarchy
Jan 5, 2012 9:08:05 PM org.apache.cxf.service.factory.ReflectionServiceFactoryBean buildServiceFromClass
INFO: Creating Service {http://polarsparc.com/}SimpleInterestCalculatorService from class com.polarsparc.cxf.jaxws.webapp.SimpleInterestCalculator4
Jan 5, 2012 9:08:06 PM org.apache.cxf.endpoint.ServerImpl initDestination
INFO: Setting the server's publish address to be /SimpleInterestCalculator
Jan 5, 2012 9:08:06 PM org.springframework.web.context.ContextLoader initWebApplicationContext
INFO: Root WebApplicationContext: initialization completed in 1149 ms
Jan 5, 2012 9:08:06 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing Root WebApplicationContext: startup date [Thu Jan 05 15:08:06 EST 2012]; parent: Root WebApplicationContext
Jan 5, 2012 9:08:06 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from URL [jndi:/localhost/CXFWebServices/WEB-INF/cxf-servlet.xml]
Jan 5, 2012 9:08:06 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [META-INF/cxf/cxf.xml]
Jan 5, 2012 9:08:06 PM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@636f2067:
defining beans [cxf,org.apache.cxf.bus.spring.BusWiringBeanFactoryPostProcessor,org.apache.cxf.bus.spring.Jsr250BeanPostProcessor,
org.apache.cxf.bus.spring.BusExtensionPostProcessor,SimpleInterestCalculator]; parent: org.springframework.beans.factory.support.DefaultListableBeanFactory@2031688f
Jan 5, 2012 9:08:06 PM org.apache.cxf.service.factory.ReflectionServiceFactoryBean buildServiceFromClass
INFO: Creating Service {http://polarsparc.com/}SimpleInterestCalculatorService from class com.polarsparc.cxf.jaxws.webapp.SimpleInterestCalculator4
Jan 5, 2012 9:08:06 PM org.apache.cxf.endpoint.ServerImpl initDestination
INFO: Setting the server's publish address to be /SimpleInterestCalculator
Jan 5, 2012 9:08:06 PM org.apache.catalina.startup.HostConfig deployDirectory
INFO: Deploying web application directory ROOT
Jan 5, 2012 9:08:06 PM org.apache.catalina.startup.HostConfig deployDirectory
INFO: Deploying web application directory docs
Jan 5, 2012 9:08:06 PM org.apache.catalina.startup.HostConfig deployDirectory
INFO: Deploying web application directory examples
Jan 5, 2012 9:08:06 PM org.apache.coyote.http11.Http11Protocol start
INFO: Starting Coyote HTTP/1.1 on http-8080
Jan 5, 2012 9:08:06 PM org.apache.coyote.http11.Http11Protocol start
INFO: Starting Coyote HTTP/1.1 on http-8443
Jan 5, 2012 9:08:06 PM org.apache.jk.common.ChannelSocket init
INFO: JK: ajp13 listening on /0.0.0.0:8009
Jan 5, 2012 9:08:06 PM org.apache.jk.server.JkMain start
INFO: Jk running ID=0 time=0/13 config=null
Jan 5, 2012 9:08:06 PM org.apache.catalina.startup.Catalina start
INFO: Server startup in 1863 ms

You can get the WSDL for this service by accessing the following URL: https://localhost:8443/CXFWebServices/services/SimpleInterestCalculator?wsdl

As we can see from the Tomcat log output, the SSL transport on port 8443 has successfully started and so is our CXF WebService.

Step. 7

Create a new Spring configuration file for our WebService client to use SSL.

The following is the Spring configuration file cxf-https-client.xml that we will use with our simplified client code:

Listing.25
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:http="http://cxf.apache.org/transports/http/configuration"
xmlns:sec="http://cxf.apache.org/configuration/security"
xsi:schemaLocation="http://cxf.apache.org/transports/http/configuration
http://cxf.apache.org/schemas/configuration/http-conf.xsd
http://cxf.apache.org/configuration/security
http://cxf.apache.org/schemas/configuration/security.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">

<import resource="classpath:META-INF/cxf/cxf.xml"/>

<bean id="interestClient" factory-bean="interestClientFactory" factory-method="create"/>

<bean id="interestClientFactory" class="org.apache.cxf.jaxws.JaxWsProxyFactoryBean">
<property name="serviceClass"
value="com.polarsparc.cxf.jaxws.webapp.SimpleInterestCalculator4" />
<property name="address"
value="https://localhost:8443/CXFWebServices/secure-services/
SimpleInterestCalculator?wsdl"/>
</bean>

<http:conduit name="*.http-conduit">
<http:tlsClientParameters disableCNCheck="true">
<sec:keyManagers keyPassword="cxfkey">
<sec:keyStore type="JKS" password="cxfkey" file="resources/cxfcerts.jks"/>
</sec:keyManagers>
<sec:trustManagers>
<sec:keyStore type="JKS" password="cxfkey" file="resources/cxfcerts.jks"/>
</sec:trustManagers>
</http:tlsClientParameters>
</http:conduit>
</beans>

As we can see, the cxf-https-client.xml Spring configuration file specifies the various SSL parameters and the SSL based WSDL location in the client factory. This configuration file is stored in the “resources” directory of our project.

Step. 8

Test the WebService client using SSL. Open a Terminal window and execute the script “bin/SimpleInterestClient5.sh” from the directory $HOME/Projects/Java/CXF-WebServices with arguments resources/cxf-https-client.xml (config), 12 (months), 10000 (amount), and 2.75 (rate) and we should see something as follows:

Output.16

Jan 5, 2012 9:37:54 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@4def8cf3:
startup date [Thu Jan 05 15:37:54 EST 2012]; root of context hierarchy
Jan 5, 2012 9:37:55 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [resources/cxf-https-client.xml]
Jan 5, 2012 9:37:55 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [META-INF/cxf/cxf.xml]
Jan 5, 2012 9:37:55 PM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@69d95da8:
defining beans [cxf,org.apache.cxf.bus.spring.BusWiringBeanFactoryPostProcessor,org.apache.cxf.bus.spring.Jsr250BeanPostProcessor,
org.apache.cxf.bus.spring.BusExtensionPostProcessor,client,clientFactory,*.http-conduit]; root of factory hierarchy
Jan 5, 2012 9:37:55 PM org.apache.cxf.service.factory.ReflectionServiceFactoryBean buildServiceFromClass
INFO: Creating Service {http://polarsparc.com/}SimpleInterestCalculator4Service from class com.polarsparc.cxf.jaxws.webapp.SimpleInterestCalculator4
Amount: 10000, Rate: 2.75, Months: 12, Interest: 275.00

Hooray !!! We did it !!! We have successfully tested the SSL (HTTPS) communication between the consumer (client) and the service provider (WebService).

There are some quirks that we should be aware of with the client specific Spring configuration file cxf-https-client.xml.

Remember in Step 1, we created our own self-signed SSL certificate. As a result, if we do not include the attribute disableCNCheck=”true” on the <http:tlsClientParameters> element, we will encounter the following exception: Caused by: java.io.IOException: IOException invoking https://localhost:8443/CXFWebServices/secure-services/SimpleInterestCalculator?wsdl: The https URL hostname does not match the Common Name (CN) on the server certificate. To disable this check (NOT recommended for production) set the CXF client TLS configuration property "disableCNCheck" to true.

If we do not include <import resource=”classpath:META-INF/cxf/cxf.xml”/>, we will encounter the following exception: Caused by: javax.net.ssl.SSLHandshakeException: SSLHandshakeException invoking https://localhost:8443/CXFWebServices/secure-services/SimpleInterestCalculator?wsdl: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

Contract-first WebServices

All our examples thus far was based on the Simple Interest Calculator. For the Contract-first WebServices, we will use the example of the Monthly Mortgage Payment Calculator. In the Contract-first WebServices, we start with the WSDL and then generate code using the tool(s) that come with the Apache CXF Framework.

We will first define a WSDL contract for the Monthly Mortgage Payment Calculator. The following is the WSDL XML file called resources/MonthlyMortgageCalculator.xml located in the “resources” directory of our project:

Listing.26
<wsdl:definitions
name="MonthlyMortgageCalculator"
targetNamespace="http://polarsparc.com/"
xmlns:ns1="http://schemas.xmlsoap.org/soap/http"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:tns="http://polarsparc.com/"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">

<wsdl:types>
<xs:schema
attributeFormDefault="unqualified"
elementFormDefault="unqualified"
targetNamespace="http://polarsparc.com/"
xmlns:tns="http://polarsparc.com/"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="monthlyMortgageRequest" type="tns:monthlyMortgageRequest"/>
<xs:element name="monthlyMortgageResponse" type="tns:monthlyMortgageResponse"/>
<xs:complexType name="monthlyMortgageRequest">
<xs:sequence>
<xs:element name="years" type="xs:int"/>
<xs:element name="amount" type="xs:double"/>
<xs:element name="rate" type="xs:double"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="monthlyMortgageResponse">
<xs:sequence>
<xs:element name="payment" type="xs:double"/>
</xs:sequence>
</xs:complexType>
</xs:schema>
</wsdl:types>

<wsdl:message name="monthlyMortgageRequest">
<wsdl:part element="tns:monthlyMortgageRequest" name="parameters"></wsdl:part>
</wsdl:message>
<wsdl:message name="monthlyMortgageResponse">
<wsdl:part element="tns:monthlyMortgageResponse" name="parameters"></wsdl:part>
</wsdl:message>

<wsdl:portType name="MonthlyMortgageCalculator">
<wsdl:operation name="calculateMonthlyMortgage">
<wsdl:input message="tns:monthlyMortgageRequest" name="monthlyMortgageRequest">
</wsdl:input>
<wsdl:output message="tns:monthlyMortgageResponse"
name="monthlyMortgageResponse"></wsdl:output>
</wsdl:operation>
</wsdl:portType>

<wsdl:binding name="MonthlyMortgageCalculatorSoapBinding"
type="tns:MonthlyMortgageCalculator">
<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="calculateMonthlyMortgage">
<soap:operation soapAction="" style="document"/>
<wsdl:input name="monthlyMortgageRequest">
<soap:body use="literal"/>
</wsdl:input>
<wsdl:output name="monthlyMortgageResponse">
<soap:body use="literal"/>
</wsdl:output>
</wsdl:operation>
</wsdl:binding>

<wsdl:service name="MonthlyMortgageCalculatorService">
<wsdl:port binding="tns:MonthlyMortgageCalculatorSoapBinding"
name="MonthlyMortgageCalculatorPort">
<soap:address location="http://localhost:8080/CXFWebServices/services/
MonthlyMortgageCalculator"/>
</wsdl:port>
</wsdl:service>

</wsdl:definitions>

As we can see the Monthly Mortgage Payment Calculator interface defines one operation called “calculateMonthlyMortgage” which accepts an input parameter of type “MonthlyMortgageRequest” and returns an output of type “MonthlyMortgageResponse”.

The type “MonthlyMortgageRequest” is a complex data type that encapsulates 3 primitive data types “int” for years, “double” for principal amount, and “double” for the interest rate.

The type “MonthlyMortgageResponse” is a complex data type that encapsulates the primitive data type “double” for the monthly payment.

Now that we have defined the Monthly Mortgage Payment Calculator WebService interface, the next step is to generate Java code from this WSDL file. To do that, we will use the tool called wsdl2java that is supplied with the Apache CXF Framework. We can either use the command line tool or use an ANT task which can be called from the ANT build script. We choose the option to use the ANT task.

The following is our modified ANT build XML build.xml:

Listing.27
<project name="CXF-WebServices" default="build" basedir=".">
<property environment="env"/>

<property name="cxf.home" location="${env.HOME}/Programs/apache-cxf-2.5.1"/>
<property name="src.dir" location ="${basedir}/src"/>
<property name="res.dir" location ="${basedir}/resources"/>
<property name="build.dir" location ="${basedir}/build"/>
<property name="build.classes.dir" location ="${build.dir}/classes"/>
<property name="temp.dir" location="${build.dir}/temp"/>
<property name="web.dir" location="${temp.dir}/WEB-INF"/>
<property name="web.lib.dir" location="${web.dir}/lib"/>
<property name="web.classes.dir" location="${web.dir}/classes"/>

<path id="cxf.classpath">
<pathelement location="${basedir}"/>
<pathelement location="${build.classes.dir}"/>
<fileset dir="${cxf.home}/lib">
<include name="**/*.jar"/>
</fileset>
</path>

<target name="build" depends="compile,war,clean">
<echo>Building the .war file for web deployment.</echo>
</target>

<target name="compile">
<javac includeantruntime="false" destdir="${build.classes.dir}">
<src path="${src.dir}"/>
<classpath>
<path refid="cxf.classpath"/>
</classpath>
</javac>
</target>

<target name="generated">
<java classname="org.apache.cxf.tools.wsdlto.WSDLToJava" fork="true">
<arg value="-p"/>
<arg value="com.polarsparc.cxf.jaxws.generated"/>
<arg value="-keep"/>
<arg value="-impl"/>
<arg value="-d"/>
<arg value="src"/>
<arg value="${res.dir}/MonthlyMortgageCalculator.wsdl"/>
<classpath>
<path refid="cxf.classpath"/>
</classpath>
</java>
</target>

<target name="war" depends="compile,generated">
<mkdir dir="${temp.dir}"/>
<mkdir dir="${web.dir}"/>
<mkdir dir="${web.lib.dir}"/>
<mkdir dir="${web.classes.dir}"/>
<copy file="${res.dir}/web.xml" todir="${web.dir}"/>
<copy file="${res.dir}/cxf-servlet.xml" todir="${web.dir}"/>
<copy todir="${web.lib.dir}">
<fileset dir="${cxf.home}/lib">
<exclude name="servlet-api-*.jar" />
<exclude name="geronimo-servlet_*.jar" />
<exclude name="jetty-*.jar"/>
<exclude name="WHICH_JARS" />
</fileset>
</copy>
<copy todir="${web.classes.dir}">
<fileset dir="${build.classes.dir}"
includes="**/webapp/*.class,**/generated/*.class" />
</copy>
<war destfile="${build.dir}/CXFWebServices.war" webxml="${web.dir}/web.xml"
basedir="${temp.dir}">
<lib dir="${web.lib.dir}" />
<classes dir="${web.classes.dir}" />
</war>
</target>

<target name="clean" depends="war">
<delete dir="${temp.dir}"/>
</target>
</project>

We have highlighted the sections that were added to the build.xml file to enable code generation for the given WSDL resources/MonthlyMortgageCalculator.xml using the Apache CXF Framework provided ANT task org.apache.cxf.tools.wsdlto.WSDLToJava. We have specified a number of options for the ANT task, which are described in the following table:

Options Description
-p This option specifies the package name to be used for the generated code. If not specified, it will use the value of the targetNamespace attribute from the WSDL element. We have used this option so that the generated code is in the package com.polarsparc.cxf.jaxws.generated
-keep This option specifies that the code generator not overwrite any exisiting files
-impl This option specifies that code be generated for the Service Endpoint Interface implementation
-d This option specifies the directory where the code is to be generated. For our case we have specified it to be the “src” directory

When we execute the ANT build script for the target “generated”, we will see the following files in the “com/polarsparc/cxf/jaxws/generated” directory under “src”:

  1. MonthlyMortgageCalculator.java

  2. MonthlyMortgageRequest.java

  3. MonthlyMortgageResponse.java

  4. ObjectFactory.java

  5. MonthlyMortgageCalculatorImpl.java

  6. MonthlyMortgageCalculatorService.java

  7. package-info.java

The 1st file MonthlyMortgageCalculator.java is the Service Endpoint Interface (SEI), which looks as follows:

Listing.28
package com.polarsparc.cxf.jaxws.generated;

import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;
import javax.xml.bind.annotation.XmlSeeAlso;

/**
* This class was generated by Apache CXF 2.5.1
* 2012-01-06T22:30:30.356-05:00
* Generated source version: 2.5.1
*
*/
@WebService(targetNamespace = "http://polarsparc.com/", name = "MonthlyMortgageCalculator")
@XmlSeeAlso({ObjectFactory.class})
@SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.BARE)
public interface MonthlyMortgageCalculator {
@WebResult(name = "monthlyMortgageResponse", targetNamespace = "http://polarsparc.com/",
partName = "parameters")
@WebMethod
public MonthlyMortgageResponse calculateMonthlyMortgage(
@WebParam(partName = "parameters", name = "monthlyMortgageRequest",
targetNamespace = "http://polarsparc.com/")
MonthlyMortgageRequest parameters
);
}

The 2nd file MonthlyMortgageRequest.java is the Request Wrapper object, which looks as follows:

Listing.29
package com.polarsparc.cxf.jaxws.generated;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlType;

/**
* <p>Java class for monthlyMortgageRequest complex type.
*
* <p>The following schema fragment specifies the expected content contained within this
class.
*
* <pre>
* &lt;complexType name="monthlyMortgageRequest">
* &lt;complexContent>
* &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
* &lt;sequence>
* &lt;element name="years" type="{http://www.w3.org/2001/XMLSchema}int"/>
* &lt;element name="amount" type="{http://www.w3.org/2001/XMLSchema}double"/>
* &lt;element name="rate" type="{http://www.w3.org/2001/XMLSchema}double"/>
* &lt;/sequence>
* &lt;/restriction>
* &lt;/complexContent>
* &lt;/complexType>
* </pre>
*
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "monthlyMortgageRequest", propOrder = {
"years",
"amount",
"rate"
})
public class MonthlyMortgageRequest {
protected int years;
protected double amount;
protected double rate;

/**
* Gets the value of the years property.
*
*/
public int getYears() {
return years;
}

/**
* Sets the value of the years property.
*
*/
public void setYears(int value) {
this.years = value;
}

/**
* Gets the value of the amount property.
*
*/
public double getAmount() {
return amount;
}

/**
* Sets the value of the amount property.
*
*/
public void setAmount(double value) {
this.amount = value;
}

/**
* Gets the value of the rate property.
*
*/
public double getRate() {
return rate;
}

/**
* Sets the value of the rate property.
*
*/
public void setRate(double value) {
this.rate = value;
}
}

The 3rd file MonthlyMortgageResponse.java is the Response Wrapper object, which looks as follows:

Listing.30
package com.polarsparc.cxf.jaxws.generated;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlType;

/**
* <p>Java class for monthlyMortgageResponse complex type.
*
* <p>The following schema fragment specifies the expected content contained within this
class.
*
* <pre>
* &lt;complexType name="monthlyMortgageResponse">
* &lt;complexContent>
* &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
* &lt;sequence>
* &lt;element name="payment" type="{http://www.w3.org/2001/XMLSchema}double"/>
* &lt;/sequence>
* &lt;/restriction>
* &lt;/complexContent>
* &lt;/complexType>
* </pre>
*
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "monthlyMortgageResponse", propOrder = {
"payment"
})
public class MonthlyMortgageResponse {
protected double payment;

/**
* Gets the value of the payment property.
*
*/
public double getPayment() {
return payment;
}

/**
* Sets the value of the payment property.
*
*/
public void setPayment(double value) {
this.payment = value;
}
}

The 4th file ObjectFactory.java is the factory object for creating the request and response wrapper objects, which looks as follows:

Listing.31
package com.polarsparc.cxf.jaxws.generated;

import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.XmlElementDecl;
import javax.xml.bind.annotation.XmlRegistry;
import javax.xml.namespace.QName;

/**
* This object contains factory methods for each
* Java content interface and Java element interface
* generated in the com.polarsparc.cxf.jaxws.generated package.
* <p>An ObjectFactory allows you to programatically
* construct new instances of the Java representation
* for XML content. The Java representation of XML
* content can consist of schema derived interfaces
* and classes representing the binding of schema
* type definitions, element declarations and model
* groups. Factory methods for each of these are
* provided in this class.
*
*/
@XmlRegistry
public class ObjectFactory {
private final static QName _MonthlyMortgageResponse_QNAME =
new QName("http://polarsparc.com/", "monthlyMortgageResponse");
private final static QName _MonthlyMortgageRequest_QNAME =
new QName("http://polarsparc.com/", "monthlyMortgageRequest");

/**
* Create a new ObjectFactory that can be used to create new instances of
* schema derived classes for package: com.polarsparc.cxf.jaxws.generated
*
*/
public ObjectFactory() {
}

/**
* Create an instance of {@link MonthlyMortgageRequest }
*
*/
public MonthlyMortgageRequest createMonthlyMortgageRequest() {
return new MonthlyMortgageRequest();
}

/**
* Create an instance of {@link MonthlyMortgageResponse }
*
*/
public MonthlyMortgageResponse createMonthlyMortgageResponse() {
return new MonthlyMortgageResponse();
}

/**
* Create an instance of {@link JAXBElement }
* {@code <}{@link MonthlyMortgageResponse }{@code >}}
*
*/
@XmlElementDecl(namespace = "http://polarsparc.com/", name = "monthlyMortgageResponse")
public JAXBElement<MonthlyMortgageResponse>
createMonthlyMortgageResponse(MonthlyMortgageResponse value) {
return new JAXBElement<MonthlyMortgageResponse>(_MonthlyMortgageResponse_QNAME,
MonthlyMortgageResponse.class, null, value);
}

/**
* Create an instance of {@link JAXBElement }
* {@code <}{@link MonthlyMortgageRequest }{@code >}}
*
*/
@XmlElementDecl(namespace = "http://polarsparc.com/", name = "monthlyMortgageRequest")
public JAXBElement<MonthlyMortgageRequest>
createMonthlyMortgageRequest(MonthlyMortgageRequest value) {
return new JAXBElement<MonthlyMortgageRequest>(_MonthlyMortgageRequest_QNAME,
MonthlyMortgageRequest.class, null, value);
}
}

The 5th file MonthlyMortgageCalculatorImpl.java is an empty skeleton for the Service Endpoint Interface (SEI) implementation class, which looks as follows:

Listing.32
/**
* Please modify this class to meet your needs
* This class is not complete
*/

package com.polarsparc.cxf.jaxws.generated;

import java.lang.Math;

import java.util.logging.Logger;

/**
* This class was generated by Apache CXF 2.5.1
* 2012-01-06T22:30:30.310-05:00
* Generated source version: 2.5.1
*
*/

@javax.jws.WebService(
serviceName = "MonthlyMortgageCalculatorService",
portName = "MonthlyMortgageCalculatorPort",
targetNamespace = "http://polarsparc.com/",
wsdlLocation = "/home/bswamina/Projects/Java/CXF-WebServices/resources/
MonthlyMortgageCalculator.wsdl",
endpointInterface = "com.polarsparc.cxf.jaxws.generated.MonthlyMortgageCalculator")

public class MonthlyMortgageCalculatorImpl implements MonthlyMortgageCalculator {
private static final Logger LOG =
Logger.getLogger(MonthlyMortgageCalculatorImpl.class.getName());

/* (non-Javadoc)
* @see com.polarsparc.cxf.jaxws.generated.MonthlyMortgageCalculator
* #calculateMonthlyMortgage
* (com.polarsparc.cxf.jaxws.generated.MonthlyMortgageRequest parameters )
*/
public com.polarsparc.cxf.jaxws.generated.MonthlyMortgageResponse
calculateMonthlyMortgage(MonthlyMortgageRequest parameters) {
LOG.info("Executing operation calculateMonthlyMortgage");
System.out.println(parameters);
try {
com.polarsparc.cxf.jaxws.generated.MonthlyMortgageResponse _return = null;
return _return;
} catch (java.lang.Exception ex) {
ex.printStackTrace();
throw new RuntimeException(ex);
}
}
}

The remaining files are not that important for our Monthly Mortgage Calculator service.

As we indicated earlier, the 5th file MonthlyMortgageCalculatorImpl.java is just a skeleton of the implementation class. We need to modify it by filling in the missing logic. The following is the modified MonthlyMortgageCalculatorImpl.java file:

Listing.33
package com.polarsparc.cxf.jaxws.generated;

import java.lang.Math;

import java.util.logging.Logger;

/**
* This class was generated by Apache CXF 2.5.1
* 2012-01-06T22:30:30.310-05:00
* Generated source version: 2.5.1
*
*/

@javax.jws.WebService(
serviceName = "MonthlyMortgageCalculatorService",
portName = "MonthlyMortgageCalculatorPort",
targetNamespace = "http://polarsparc.com/",
wsdlLocation = "/home/bswamina/Projects/Java/CXF-WebServices/resources/
MonthlyMortgageCalculator.wsdl",
endpointInterface = "com.polarsparc.cxf.jaxws.generated.MonthlyMortgageCalculator")

public class MonthlyMortgageCalculatorImpl implements MonthlyMortgageCalculator {
private static final Logger LOG =
Logger.getLogger(MonthlyMortgageCalculatorImpl.class.getName());

/* (non-Javadoc)
* @see com.polarsparc.cxf.jaxws.generated.MonthlyMortgageCalculator
* #calculateMonthlyMortgage
* (com.polarsparc.cxf.jaxws.generated.MonthlyMortgageRequest parameters )
*/
public com.polarsparc.cxf.jaxws.generated.MonthlyMortgageResponse
calculateMonthlyMortgage(MonthlyMortgageRequest parameters) {
LOG.info("Executing operation calculateMonthlyMortgage");
System.out.println(parameters);
try {
com.polarsparc.cxf.jaxws.generated.MonthlyMortgageResponse _return =
(new ObjectFactory()).createMonthlyMortgageResponse();
_return.payment = MonthlyMortgagePayment(parameters);
return _return;
} catch (java.lang.Exception ex) {
ex.printStackTrace();
throw new RuntimeException(ex);
}
}

/*
* The formula for computing the monthly mortgage payment (M) is:
* P * J / (1 - (1 / (1 + J)^N))
* where:
* P is the Mortgage Amount
* J is the monthly interest. If I is the interest per annum, then J = I / 12 * 100
* N is the loan period in months. If Y is the no of years, then N = Y * 12
*/
private double MonthlyMortgagePayment(MonthlyMortgageRequest parameters)
throws Exception {
double N = parameters.years * 12;
double P = parameters.amount;
double J = parameters.rate / 1200;
return P * (J / (1 - (1/Math.pow(1+J, N))));
}
}

We have highlighted the sections that were added to the MonthlyMortgageCalculatorImpl.java file.

Now that we have all the pieces, we will rebuild the WAR file called CXFWebServices.war and deploy it into our Tomcat “webapps” directory.

When we restart the Tomcat server, we see the following in the Tomcat log file:

Output.17

Jan 7, 2012 5:50:02 PM org.apache.catalina.core.AprLifecycleListener init
INFO: The APR based Apache Tomcat Native library which allows optimal performance in production environments
was not found on the java.library.path: /usr/lib/jvm/java-6-sun-1.6.0.26/jre/lib/amd64/server:
/usr/lib/jvm/java-6-sun-1.6.0.26/jre/lib/amd64:/usr/lib/jvm/java-6-sun-1.6.0.26/jre/../lib/amd64:
/usr/java/packages/lib/amd64:/usr/lib64:/lib64:/lib:/usr/lib
Jan 7, 2012 5:50:03 PM org.apache.coyote.http11.Http11Protocol init
INFO: Initializing Coyote HTTP/1.1 on http-8080
Jan 7, 2012 5:50:03 PM org.apache.coyote.http11.Http11Protocol init
INFO: Initializing Coyote HTTP/1.1 on http-8443
Jan 7, 2012 5:50:03 PM org.apache.catalina.startup.Catalina load
INFO: Initialization processed in 860 ms
Jan 7, 2012 5:50:03 PM org.apache.catalina.core.StandardService start
INFO: Starting service Catalina
Jan 7, 2012 5:50:03 PM org.apache.catalina.core.StandardEngine start
INFO: Starting Servlet Engine: Apache Tomcat/6.0.35
Jan 7, 2012 5:50:03 PM org.apache.catalina.startup.HostConfig deployDescriptor
INFO: Deploying configuration descriptor manager.xml
Jan 7, 2012 5:50:03 PM org.apache.catalina.startup.HostConfig deployDescriptor
INFO: Deploying configuration descriptor host-manager.xml
Jan 7, 2012 5:50:03 PM org.apache.catalina.startup.HostConfig deployWAR
INFO: Deploying web application archive CXFWebServices.war
Jan 7, 2012 5:50:04 PM org.springframework.web.context.ContextLoader initWebApplicationContext
INFO: Root WebApplicationContext: initialization started
Jan 7, 2012 5:50:04 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing Root WebApplicationContext: startup date [Sat Jan 07 17:50:04 EST 2012]; root of context hierarchy
Jan 7, 2012 5:50:04 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from ServletContext resource [/WEB-INF/cxf-servlet.xml]
Jan 7, 2012 5:50:04 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [META-INF/cxf/cxf.xml]
Jan 7, 2012 5:50:05 PM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@61128f5a:
defining beans [cxf,org.apache.cxf.bus.spring.BusWiringBeanFactoryPostProcessor,org.apache.cxf.bus.spring.Jsr250BeanPostProcessor,
org.apache.cxf.bus.spring.BusExtensionPostProcessor,SimpleInterestCalculator,MonthlyMortgageCalculator];
root of factory hierarchy
Jan 7, 2012 5:50:05 PM org.apache.cxf.service.factory.ReflectionServiceFactoryBean buildServiceFromClass
INFO: Creating Service {http://polarsparc.com/}SimpleInterestCalculatorService from class com.polarsparc.cxf.jaxws.webapp.SimpleInterestCalculator4
Jan 7, 2012 5:50:05 PM org.apache.cxf.endpoint.ServerImpl initDestination
INFO: Setting the server's publish address to be /SimpleInterestCalculator
Jan 7, 2012 5:50:05 PM org.apache.cxf.service.factory.ReflectionServiceFactoryBean buildServiceFromWSDL
INFO: Creating Service {http://polarsparc.com/}MonthlyMortgageCalculatorService from WSDL:
/home/bswamina/Projects/Java/CXF-WebServices/resources/MonthlyMortgageCalculator.wsdl
Jan 7, 2012 5:50:05 PM org.apache.cxf.endpoint.ServerImpl initDestination
INFO: Setting the server's publish address to be /MonthlyMortgageCalculator
Jan 7, 2012 5:50:05 PM org.springframework.web.context.ContextLoader initWebApplicationContext
INFO: Root WebApplicationContext: initialization completed in 1292 ms
Jan 7, 2012 5:50:05 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing Root WebApplicationContext: startup date [Sat Jan 07 17:50:05 EST 2012];
parent: Root WebApplicationContext
Jan 7, 2012 5:50:06 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from URL [jndi:/localhost/CXFWebServices/WEB-INF/cxf-servlet.xml]
Jan 7, 2012 5:50:06 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [META-INF/cxf/cxf.xml]
Jan 7, 2012 5:50:06 PM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@5585c0de:
defining beans [cxf,org.apache.cxf.bus.spring.BusWiringBeanFactoryPostProcessor,org.apache.cxf.bus.spring.Jsr250BeanPostProcessor,
org.apache.cxf.bus.spring.BusExtensionPostProcessor,SimpleInterestCalculator,MonthlyMortgageCalculator]; parent: org.springframework.beans.factory.support.DefaultListableBeanFactory@61128f5a
Jan 7, 2012 5:50:06 PM org.apache.cxf.service.factory.ReflectionServiceFactoryBean buildServiceFromClass
INFO: Creating Service {http://polarsparc.com/}SimpleInterestCalculatorService from class com.polarsparc.cxf.jaxws.webapp.SimpleInterestCalculator4
Jan 7, 2012 5:50:06 PM org.apache.cxf.endpoint.ServerImpl initDestination
INFO: Setting the server's publish address to be /SimpleInterestCalculator
Jan 7, 2012 5:50:06 PM org.apache.cxf.service.factory.ReflectionServiceFactoryBean buildServiceFromWSDL
INFO: Creating Service {http://polarsparc.com/}MonthlyMortgageCalculatorService from WSDL:
/home/bswamina/Projects/Java/CXF-WebServices/resources/MonthlyMortgageCalculator.wsdl
Jan 7, 2012 5:50:06 PM org.apache.cxf.endpoint.ServerImpl initDestination
INFO: Setting the server's publish address to be /MonthlyMortgageCalculator
Jan 7, 2012 5:50:06 PM org.apache.catalina.startup.HostConfig deployDirectory
INFO: Deploying web application directory ROOT
Jan 7, 2012 5:50:06 PM org.apache.catalina.startup.HostConfig deployDirectory
INFO: Deploying web application directory docs
Jan 7, 2012 5:50:06 PM org.apache.catalina.startup.HostConfig deployDirectory
INFO: Deploying web application directory examples
Jan 7, 2012 5:50:06 PM org.apache.coyote.http11.Http11Protocol start
INFO: Starting Coyote HTTP/1.1 on http-8080
Jan 7, 2012 5:50:06 PM org.apache.coyote.http11.Http11Protocol start
INFO: Starting Coyote HTTP/1.1 on http-8443
Jan 7, 2012 5:50:06 PM org.apache.jk.common.ChannelSocket init
INFO: JK: ajp13 listening on /0.0.0.0:8009
Jan 7, 2012 5:50:06 PM org.apache.jk.server.JkMain start
INFO: Jk running ID=0 time=0/14 config=null
Jan 7, 2012 5:50:06 PM org.apache.catalina.startup.Catalina start
INFO: Server startup in 2839 ms

As we can see from the Tomcat log output, our Monthly Mortgage Payment service has successfully started.

The following Java class defines the client implementation using the Service Endpoint Interface (SEI) MonthlyMortgageCalculator:

Listing.34
import javax.xml.namespace.QName;
import javax.xml.ws.Service;

import com.polarsparc.cxf.jaxws.generated.MonthlyMortgageCalculator;
import com.polarsparc.cxf.jaxws.generated.MonthlyMortgageRequest;
import com.polarsparc.cxf.jaxws.generated.MonthlyMortgageResponse;

public class MonthlyMortgageCalculatorClient {
public static void main(String[] args) {
try {
if (args.length != 3) {
System.out.printf("Usage: java %s <years> <amount> <rate>\n",
MonthlyMortgageCalculatorClient.class.getName());
System.exit(1);
}

QName serviceName = new QName("http://polarsparc.com/"
"MonthlyMortgageCalculatorService");

String wsdl = "http://localhost:8080/CXFWebServices/services/
MonthlyMortgageCalculator?wsdl";

Service service = Service.create(new URL(wsdl), serviceName);

QName portName = new QName("http://polarsparc.com/",
"MonthlyMortgageCalculatorPort");

MonthlyMortgageCalculator client = service.getPort(portName,
MonthlyMortgageCalculator.class);

MonthlyMortgageRequest request = new MonthlyMortgageRequest();
request.setYears(Integer.valueOf(args[0]));
request.setAmount(Double.valueOf(args[1]));
request.setRate(Double.valueOf(args[2]));

MonthlyMortgageResponse response = client.calculateMonthlyMortgage(request);

System.out.printf("Amount: %s, Rate: %s, Years: %s, Payment: %.2f\n",
args[1], args[2], args[0], response.getPayment());
}
catch (Exception ex) {
ex.printStackTrace(System.err);
}
}
}

To test our web service client, let us create a script called “MortgageCalculator.sh” in the “bin” directory as follows:

CP=.:./build/classes:$CXF_HOME/lib/commons-logging-1.1.1.jar:
$CXF_HOME/lib/cxf-2.5.1.jar:$CXF_HOME/lib/geronimo-activation_1.1_spec-1.1.jar:
$CXF_HOME/lib/geronimo-annotation_1.0_spec-1.1.1.jar:
$CXF_HOME/lib/geronimo-jaxws_2.2_spec-1.1.jar:
$CXF_HOME/lib/geronimo-servlet_2.5_spec-1.1.2.jar:
$CXF_HOME/lib/geronimo-stax-api_1.0_spec-1.0.1.jar:
$CXF_HOME/lib/geronimo-ws-metadata_2.0_spec-1.1.3.jar:
$CXF_HOME/lib/jaxb-api-2.2.3.jar:
$CXF_HOME/lib/jaxb-impl-2.2.4-1.jar:
$CXF_HOME/lib/jetty-continuation-7.5.3.v20111011.jar:
$CXF_HOME/lib/jetty-http-7.5.3.v20111011.jar:
$CXF_HOME/lib/jetty-io-7.5.3.v20111011.jar:
$CXF_HOME/lib/jetty-server-7.5.3.v20111011.jar:
$CXF_HOME/lib/jetty-util-7.5.3.v20111011.jar:
$CXF_HOME/lib/msv-core-2011.1.jar:
$CXF_HOME/lib/neethi-3.0.1.jar:
$CXF_HOME/lib/stax2-api-3.1.1.jar:
$CXF_HOME/lib/woodstox-core-asl-4.1.1.jar:
$CXF_HOME/lib/wsdl4j-1.6.2.jar:
$CXF_HOME/lib/xmlschema-core-2.0.1.jar

java -cp $CP com.polarsparc.cxf.jaxws.webapp.MonthlyMortgageCalculatorClient $1 $2 $3

Open a Terminal window and execute the script “bin/MortgageCalculator.sh” from the directory $HOME/Projects/Java/CXF-WebServices with arguments 15 (years), 150000 (amount), and 3.75 (rate) and we should see something as follows:

Output.18

Jan 7, 2012 6:07:07 PM org.apache.cxf.service.factory.ReflectionServiceFactoryBean buildServiceFromWSDL
INFO: Creating Service {http://polarsparc.com/}MonthlyMortgageCalculatorService from WSDL:
http://localhost:8080/CXFWebServices/services/MonthlyMortgageCalculator?wsdl
Amount: 150000, Rate: 3.75, Years: 15, Payment: 1090.83

YES !!! We have successfully deployed and tested our Contract-first WebService in Tomcat.

With this we conclude our journey into JAX-WS WebServices with Apache CXF Framework.