Skip to content

Apache_CXF_Tutorial_ _Building_JAX WS,_JAXB_and_JPA based_web_service_with_Apache_CXF,_Spring_and_Hyperjaxb3

Alexey Valikov edited this page Aug 31, 2017 · 1 revision

Apache CXF Tutorial - Building JAX-WS, JAXB and JPA-based web service with Apache CXF, Spring and Hyperjaxb3

Icon Since 0.5.5.

Introduction

This tutorial demonstrates the usage of Hyperjaxb3 with Apache CXF in a WSDL-first scenario.

Assume we need to implement a very simple "customer service" as a JAX-WS service; this service must provide following operations to manage customer instances:

  • getCustomerById - gets a customer id, returns the customer with given id or throws a NoSuchCustomerException if customer could not be found;
  • updateCustomer - gets the customer object, inserts or updates it in the the database, returns the customer id;
  • deleteCustomerById - gets a customer id and removes the customer object from the database or throws NoSuchCustomerException if customer with this id could not be found.

In this tutorial we'll use Apache CXF to build this service. We'll use JPA for the persistence layer whereas entity annotations will be generated by Hyperjaxb3.

Defining the service

First of all we'll need to define the service.

The customer schema

We'll start with the XML schema for customer objects:

src/main/wsdl/customer.xsd

<xs:schema
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:tns="http://customerservice.example.com/model"
    elementFormDefault="qualified"
    attributeFormDefault="unqualified"
    targetNamespace="http://customerservice.example.com/model">
    <xs:complexType name="customer">
        <xs:sequence>
            <xs:element name="customerId" type="xs:int" minOccurs="0"/>
            <xs:element minOccurs="0" name="name" type="xs:string" />
            <xs:element maxOccurs="unbounded" minOccurs="0" name="address"
                nillable="true" type="xs:string" />
            <xs:element minOccurs="0" name="numOrders" type="xs:int" />
            <xs:element name="revenue" type="xs:double" />
            <xs:element minOccurs="0" name="test" type="xs:decimal" />
            <xs:element minOccurs="0" name="birthDate" type="xs:date" />
            <xs:element minOccurs="0" name="type" type="tns:customerType" />
        </xs:sequence>
    </xs:complexType>
    <xs:simpleType name="customerType">
        <xs:restriction base="xs:string">
            <xs:enumeration value="PRIVATE" />
            <xs:enumeration value="BUSINESS" />
        </xs:restriction>
    </xs:simpleType>
</xs:schema>

WSDL

Next, we'll need to write a WSDL file:

src/main/wsdl/CustomerService.wsdl

<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions name="CustomerServiceService"
    targetNamespace="http://customerservice.example.com/service"
    xmlns:tns="http://customerservice.example.com/service"
    xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/">
    <wsdl:types>
        <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
            xmlns:tns="http://customerservice.example.com/service"
            xmlns:cns="http://customerservice.example.com/model"
            attributeFormDefault="unqualified"
            elementFormDefault="qualified"
            targetNamespace="http://customerservice.example.com/service">

            <xs:import schemaLocation="customer.xsd" namespace="http://customerservice.example.com/model"/>
            <!-- ... -->
            <xs:element name="getCustomerById" type="tns:getCustomerById" />
            <xs:complexType name="getCustomerById">
                <xs:sequence>
                    <xs:element minOccurs="0" name="customerId" type="xs:int" />
                </xs:sequence>
            </xs:complexType>
            <xs:element name="getCustomerByIdResponse" type="tns:getCustomerByIdResponse" />
            <xs:complexType name="getCustomerByIdResponse">
                <xs:sequence>
                    <xs:element minOccurs="0" name="return" type="cns:customer" />
                </xs:sequence>
            </xs:complexType>
            <!-- ... -->
            <xs:element name="updateCustomer" type="tns:updateCustomer" />
            <xs:complexType name="updateCustomer">
                <xs:sequence>
                    <xs:element minOccurs="0" name="customer" type="cns:customer" />
                </xs:sequence>
            </xs:complexType>
            <xs:element name="updateCustomerResponse" type="tns:updateCustomerResponse" />
            <xs:complexType name="updateCustomerResponse">
                <xs:sequence>
                    <xs:element minOccurs="0" name="customerId" type="xs:int" />
                </xs:sequence>
            </xs:complexType>
            <!-- ... -->
            <xs:element name="deleteCustomerById" type="tns:deleteCustomerById" />
            <xs:complexType name="deleteCustomerById">
                <xs:sequence>
                    <xs:element minOccurs="0" name="customerId" type="xs:int" />
                </xs:sequence>
            </xs:complexType>         
            <!-- ... -->
            <xs:element name="NoSuchCustomer" type="tns:NoSuchCustomer" />
            <xs:complexType name="NoSuchCustomer">
                <xs:sequence>
                    <xs:element name="customerId" nillable="true" type="xs:int" />
                </xs:sequence>
            </xs:complexType>
        </xs:schema>
    </wsdl:types>
    <wsdl:message name="getCustomerById">
        <wsdl:part name="parameters" element="tns:getCustomerById"/>
    </wsdl:message>
    <wsdl:message name="getCustomerByIdResponse">
        <wsdl:part name="parameters" element="tns:getCustomerByIdResponse"/>
    </wsdl:message>
    <wsdl:message name="updateCustomer">
        <wsdl:part name="parameters" element="tns:updateCustomer"/>
    </wsdl:message>
    <wsdl:message name="updateCustomerResponse">
        <wsdl:part name="parameters" element="tns:updateCustomerResponse"/>
    </wsdl:message>
    <wsdl:message name="deleteCustomerById">
        <wsdl:part name="parameters" element="tns:deleteCustomerById"/>
    </wsdl:message>   
    <wsdl:message name="NoSuchCustomerException">
        <wsdl:part name="NoSuchCustomerException" element="tns:NoSuchCustomer"/>
    </wsdl:message>
    <wsdl:portType name="CustomerService">
        <wsdl:operation name="updateCustomer">
            <wsdl:input name="updateCustomer" message="tns:updateCustomer"/>
            <wsdl:output name="updateCustomerResponse" message="tns:updateCustomerResponse"/>
        </wsdl:operation>
        <wsdl:operation name="deleteCustomerById">
            <wsdl:input name="deleteCustomerById" message="tns:deleteCustomerById"/>
            <wsdl:fault name="NoSuchCustomerException" message="tns:NoSuchCustomerException"/>
        </wsdl:operation>
        <wsdl:operation name="getCustomerById">
            <wsdl:input name="getCustomerById" message="tns:getCustomerById"/>
            <wsdl:output name="getCustomerByIdResponse" message="tns:getCustomerByIdResponse"/>
            <wsdl:fault name="NoSuchCustomerException" message="tns:NoSuchCustomerException"/>
        </wsdl:operation>
    </wsdl:portType>
    <wsdl:binding name="CustomerServiceServiceSoapBinding"
        type="tns:CustomerService">
        <soap:binding style="document"
            transport="http://schemas.xmlsoap.org/soap/http" />
        <wsdl:operation name="updateCustomer">
            <soap:operation soapAction="" style="document" />
            <wsdl:input name="updateCustomer">
                <soap:body use="literal" />
            </wsdl:input>
            <wsdl:output name="updateCustomerResponse">
                <soap:body use="literal" />
            </wsdl:output>
        </wsdl:operation>
        <wsdl:operation name="deleteCustomerById">
            <soap:operation soapAction="" style="document" />
            <wsdl:input name="deleteCustomerById">
                <soap:body use="literal" />
            </wsdl:input>
            <wsdl:fault name="NoSuchCustomerException">
                <soap:fault name="NoSuchCustomerException" use="literal" />
            </wsdl:fault>
        </wsdl:operation>
        <wsdl:operation name="getCustomerById">
            <soap:operation soapAction="" style="document" />
            <wsdl:input name="getCustomerById">
                <soap:body use="literal" />
            </wsdl:input>
            <wsdl:output name="getCustomerByIdResponse">
                <soap:body use="literal" />
            </wsdl:output>
            <wsdl:fault name="NoSuchCustomerException">
                <soap:fault name="NoSuchCustomerException" use="literal" />
            </wsdl:fault>
        </wsdl:operation>
    </wsdl:binding>
    <wsdl:service name="CustomerServiceService">
        <wsdl:port name="CustomerServicePort" binding="tns:CustomerServiceServiceSoapBinding">
            <soap:address location="http://localhost:8080/CustomerServicePort" />
        </wsdl:port>
    </wsdl:service>
</wsdl:definitions>

Adding customizations

JAX-WS binding customizations

In order to simplify the generated code a bit, it may make sense to customize the binding of XML Schema date and dateTime types to map onto java.util.Date instead of javax.xml.datatype.XMLGregorianCalendar:

src/main/wsdl/binding.xml

<jaxws:bindings
    wsdlLocation="CustomerService.wsdl"
    xmlns:jaxws="http://java.sun.com/xml/ns/jaxws"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
    xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">

    <jaxws:bindings node="wsdl:definitions/wsdl:types/xs:schema">
        <jaxb:globalBindings>
            <jaxb:javaType name="java.util.Date" xmlType="xs:dateTime"
                parseMethod="org.apache.cxf.tools.common.DataTypeAdapter.parseDateTime"
                printMethod="org.apache.cxf.tools.common.DataTypeAdapter.printDateTime"/>
            <jaxb:javaType name="java.util.Date" xmlType="xs:date"
                parseMethod="org.apache.cxf.tools.common.DataTypeAdapter.parseDate"
                printMethod="org.apache.cxf.tools.common.DataTypeAdapter.printDate"/>
        </jaxb:globalBindings>
    </jaxws:bindings>
</jaxws:bindings>

Hyperjaxb3 binding customizations

If you look at the generated files, you may notice that Hyperjaxb3 has generated annotation not only in the com.example.customerservice.model.Customer class, but also in the classes of the com.example.customerservice.service package (com.example.customerservice.service,GetCustomerById and so on). Since we only want to persiste the customer, the latter is not desirable, so we'll need to customize the bindings in order to disable Hyperjaxb3 for the com.example.customerservice.service package. To achieve this, we'll need to use the hj:ignored-package customization (see Ignoring packages for more information).

Another customization would be to use the customerId element as identifier property (see Selecting the identifier property for more information).

Finally, we'll enable eager fetching by default with hj:default-one-to-many element (see Customizing default mappings for more information).

Below is the binding.xjb file with these customizations:

src/main/wsdl/binding.xjb

<jaxb:bindings version="1.0" xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
    xmlns:hj="http://hyperjaxb3.jvnet.org/ejb/schemas/customizations"
    xmlns:orm="http://java.sun.com/xml/ns/persistence/orm"
    jaxb:extensionBindingPrefixes="xjc hj orm"
    xmlns:test="urn:test">
    
    <jaxb:bindings schemaLocation="customer.xsd" node="/xsd:schema">
        <hj:ignored-package name="com.example.customerservice.service"/>
        <hj:persistence>
            <hj:default-one-to-many fetch="EAGER"/>
        </hj:persistence>
        <jaxb:bindings node="xsd:complexType[@name='customer']/xsd:sequence/xsd:element[@name='customerId']">
            <hj:id>
                <orm:generated-value strategy="AUTO"/>
            </hj:id>
        </jaxb:bindings>
    </jaxb:bindings>
</jaxb:bindings>

Generating the code

Invoking Hyperjaxb3 from cxf-codegen-plugin

Apache CXF implements WSDL-to-Java code generation with the cxf-codegen-plugin. Hyperjaxb3 can be invoked from this plugin as a normal XJC plugin.

Icon See the Using Hyperjaxb3 with Apache CXF for more information.

Here's how this plugin will be configured in our case (within project/build/plugins):

A fragment of pom.xml/build/plugins

<plugin>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-codegen-plugin</artifactId>
    <version>2.2.6</version>
    <executions>
        <execution>
            <id>generate-sources</id>
            <phase>generate-sources</phase>
            <goals>
                <goal>wsdl2java</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <wsdlOptions>
            <wsdlOption>
                <wsdl>${basedir}/src/main/wsdl/CustomerService.wsdl</wsdl>
                <bindingFiles>
                    <bindingFile>${basedir}/src/main/wsdl/binding.xml</bindingFile>
                    <bindingFile>${basedir}/src/main/wsdl/binding.xjb</bindingFile>
                </bindingFiles>
                <extraargs>
                    <extraarg>-xjc-XhashCode</extraarg>
                    <extraarg>-xjc-Xequals</extraarg>
                    <extraarg>-xjc-Xhyperjaxb3-ejb</extraarg>
                </extraargs>
            </wsdlOption>
        </wsdlOptions>
    </configuration>
    <dependencies>
        <dependency>
            <groupId>org.jvnet.jaxb2_commons</groupId>
            <artifactId>jaxb2-basics</artifactId>
            <version>0.5.3</version>
        </dependency>
        <dependency>
            <groupId>org.jvnet.hyperjaxb3</groupId>
            <artifactId>hyperjaxb3-ejb-plugin</artifactId>
            <version>0.5.5</version>
        </dependency>
    </dependencies>
</plugin>

Note that we'll also need to make configure the maven-compiler-plugin to the 1.5 compatibility level (project/build/pluginManagement):

A fragment of pom.xml/project/build/pluginManagement

<plugins>
    <plugin>
        <inherited>true</inherited>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
            <source>1.5</source>
            <target>1.5</target>
        </configuration>
    </plugin>
</plugins>

Since Hyperjaxb3 generates certain resources which must be included into the resulting artifact (for instance, the META-INF/persistence.xml descriptor), we have to configure the resources of our build:

A fragment of pom.xml/project/build

<resources>
    <resource>
        <directory>src/main/wsdl</directory>
    </resource>
    <resource>
        <directory>src/main/resources</directory>
    </resource>
    <resource>
        <directory>target/generated-sources/cxf</directory>
        <includes>
            <include>META-INF/persistence.xml</include>
        </includes>
    </resource>
</resources>

What was generated?

Now if you generate the sources using the mvn clean generate-sources command, CXF will create the target/generated-sources/cxf directory with the following sub-directories:

  • com/example/customerservice/model - customer model classes. Hyperjaxb3 has annotated the Customer class with JPA annotations.
  • META-INF/persistence.xml - JPA persistence descriptor generated by Hyperjaxb3.
  • com/example/customerservice/service - customer service classes. CustomerService is the interface we'll need to implement, it is annotated with JAX-WS annotations.
  • org/w3/_2001/xmlschema - artificial package with date and dateTime adapters.

Implementing the service

Writing the service implementation

During the code generation step CXF has generated the customer service interface which we'll need to implement for the server side. Here's this interface (annotations are removed for better readability:

CustomerService.java

public interface CustomerService {

    public Customer getCustomerById(Integer customerId) throws NoSuchCustomerException;

    public Integer updateCustomer(Customer customer);

    public void deleteCustomerById(Integer customerId) throws NoSuchCustomerException;
}

Since Hyperjaxb3 has turned the Customer class into a compliant JPA entity, we can implement the customer service as a simple JPA DAO:

CustomerServiceImpl.java

@Transactional
public class CustomerServiceImpl extends JpaDaoSupport implements
        CustomerService {

    @Transactional(propagation = Propagation.REQUIRED, readOnly = false)
    public void deleteCustomerById(final Integer customerId)
            throws NoSuchCustomerException {

        final Customer customer = getCustomerById(customerId);
        getJpaTemplate().remove(customer);
    }

    @Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
    public Customer getCustomerById(final Integer customerId)
            throws NoSuchCustomerException {

        final Customer customer = getJpaTemplate().find(Customer.class,
                customerId);

        if (customer == null) {
            NoSuchCustomer noSuchCustomer = new NoSuchCustomer();
            noSuchCustomer.setCustomerId(customerId);
            throw new NoSuchCustomerException(
                    "Did not find any matching customer for id [" + customerId
                            + "].", noSuchCustomer);

        } else {
            return customer;
        }
    }

    @Transactional(propagation = Propagation.REQUIRED, readOnly = false)
    public Integer updateCustomer(Customer customer) {
        final Customer mergedCustomer = getJpaTemplate().merge(customer);
        return mergedCustomer.getCustomerId();
    }

}

Configuring the service

Application context configuration

Now that customer service is implemented, we'll need to configure this service in the Spring application context.

First of all, we'll need to configure the persistence layer: data source, entity manager factory, transaction manager:

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:jaxws="http://cxf.apache.org/jaxws"
    xmlns:soap="http://cxf.apache.org/bindings/soap"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
            http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
            http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
            http://cxf.apache.org/bindings/soap http://cxf.apache.org/schemas/configuration/soap.xsd
            http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">

    <bean
        class="org.springframework.web.context.support.ServletContextPropertyPlaceholderConfigurer" />

    <!-- ... -->
    
    <bean name="javax.persistence.EntityManagerFactory"
        class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="javax.sql.DataSource" />
        <property name="persistenceUnitName" value="com.example.customerservice.model" />
        <property name="persistenceProviderClass" value="org.hibernate.ejb.HibernatePersistence" />
        <property name="jpaProperties">
            <props>
                <prop key="hibernate.hbm2ddl.auto">create-drop</prop>
                <prop key="hibernate.dialect">org.hibernate.dialect.HSQLDialect</prop>
            </props>
        </property>
    </bean>
    
    <bean name="javax.sql.DataSource"
        class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="org.hsqldb.jdbcDriver" />
        <property name="url"
            value="jdbc:hsqldb:file:${org.jvnet.hyperjaxb3.ejb.samples.customerservicecxf.webAppRoot:target/temp}/WEB-INF/database/database" />
        <property name="username" value="sa" />
        <property name="password" value="" />
    </bean>

    <bean name="org.springframework.transaction.PlatformTransactionManager"
        class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="javax.persistence.EntityManagerFactory" />
    </bean>

    <tx:annotation-driven
        transaction-manager="org.springframework.transaction.PlatformTransactionManager" />


</beans>

Note the usage of the com.example.customerservice.model persistence unit - this persistence unit was generated by Hyperjaxb3.

In this sample setup we use an embedded HSQLDB database which will be stored under WEB-INF/database directory of the web application. The org.jvnet.hyperjaxb3.ejb.samples.customerservicecxf.webAppRoot property will be provided by the org.springframework.web.util.WebAppRootListener configured in the web.xml.

What we need to do next is to configure the customer service and to add the JAX-WS endpoint for this service:

<jaxws:endpoint
    name="com.example.customerservice.service.CustomerServiceServer"
    xmlns:customer="http://customerservice.example.com/service"
    address="/CustomerServicePort"
    serviceName="customer:CustomerServiceService"
    endpointName="customer:CustomerServiceEndpoint">
    <jaxws:implementor>
        <ref bean="com.example.customerservice.service.CustomerService" />
    </jaxws:implementor>
    <jaxws:features>
        <bean class="org.apache.cxf.feature.LoggingFeature" />
    </jaxws:features>
</jaxws:endpoint>

<bean name="com.example.customerservice.service.CustomerService"
    class="com.example.customerservice.service.CustomerServiceImpl">
    <property name="entityManagerFactory" ref="javax.persistence.EntityManagerFactory" />
</bean>

web.xml configuration

In the web.xml we'll need to configure the CXFServlet with the appropriate application context:

<?xml version="1.0" encoding="UTF-8"?>
<web-app
    version="2.4"
    xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

    <display-name>CXF Customer Service Sample</display-name>

    <!-- ... -->

    <servlet>
        <servlet-name>org.apache.cxf.transport.servlet.CXFServlet</servlet-name>
        <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
        <init-param>
            <param-name>config-location</param-name>
            <param-value>classpath:com/example/customerservice/service/applicationContext.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>org.apache.cxf.transport.servlet.CXFServlet</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
    
</web-app>

A minor addition is the WebAppRootListener which exposes the location of the web application via the webAppRootKey property. As mentione above, we'll need this in order to place the HSQLDB database files under WEB-INF/database.

<context-param>
    <param-name>webAppRootKey</param-name>
    <param-value>org.jvnet.hyperjaxb3.ejb.samples.customerservicecxf.webAppRoot</param-value>
</context-param>

<listener>
    <listener-class>org.springframework.web.util.WebAppRootListener</listener-class>
</listener>

Testing the service

Now it's high time to do some testing. I will not demonstrate unit testing since it's quite trivial. Instead, we'll take a look into integration testing, with a real server instance.

For this purpose we'll use the Hifaces20 Testing package which provides convenient infrastructure for starting a servlet container (like Jetty) directly from tests.

Smoke test

First of all, let's check that our application starts at all:

ApplicationStartsIT.java

public class ApplicationStartsIT {

    @Rule
    public MethodRule webAppEnvironmentRule = WebAppEnvironmentRule.INSTANCE;

    @PropertiesWebAppEnvironmentConfig("src/test/resources/main-web.properties")
    public WebAppEnvironment webAppEnvironment;

    @Test
    public void checkApplicationStarts() throws IOException{

        Assert.assertTrue(webAppEnvironment.isStarted());
        Assert.assertNotNull(URLUtils.getContentAsString(new URL(webAppEnvironment.getBaseUrl() + "/CustomerServicePort?wsdl")));
    }
}

This test uses the web application configuration from the main-web.properties file:

src/test/resource/main-web.properties

webapp.host=127.0.0.1
webapp.port=8080
webapp.contextPath=
webapp.home=src/main/webapp

If everything is configured allright, the application should start without any problems.

More thorough customer service test

Now let's implement a more complicated test which inserts, queries and deletes a customer. For such a test, it makes sense to create a "test" configuration of the application which uses an in-memory HSQL database instead of the file-based configured by default:

src/test/resources/com/example/customerservice/service/test/applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
            http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
            
    <import resource="classpath*:com/example/customerservice/service/applicationContext.xml"/>

    <bean name="javax.sql.DataSource"
        class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="org.hsqldb.jdbcDriver" />
        <property name="url"
            value="jdbc:hsqldb:mem:WEB-INF/database/database" />
        <property name="username" value="sa" />
        <property name="password" value="" />
    </bean>

</beans>

To use this "testing" applicationContext.xml we'll need to add a testing configuration of the web application:

Fragment of src/test/webapp/web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"
    xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

    <!-- ... -->

    <servlet>
        <servlet-name>org.apache.cxf.transport.servlet.CXFServlet</servlet-name>
        <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
        <init-param>
            <param-name>config-location</param-name>
            <param-value>
                classpath:com/example/customerservice/service/test/applicationContext.xml
            </param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <!-- ... -->
    
</web-app>

src/test/resources/test-web.properties

webapp.host=127.0.0.1
webapp.port=8080
webapp.contextPath=
webapp.home=src/test/webapp

Note the webapp.home pointing to a different web app location.

Now we can implement the test. First of all we'll need to create the client-side instance of the customer service. We'll do this manually using the JaxWsProxyFactoryBean:

final JaxWsProxyFactoryBean jaxWsProxyFactoryBean = new JaxWsProxyFactoryBean();
jaxWsProxyFactoryBean.setServiceClass(CustomerService.class);
jaxWsProxyFactoryBean.setAddress(webAppEnvironment.getBaseUrl()
    + "/CustomerServicePort");

final CustomerService customerService = (CustomerService) jaxWsProxyFactoryBean
    .create();

After that we can use the customer service to perform some operations - insert, retrieve and remove the customer. Here's the full test:

CustomerServiceIT.java

public class CustomerServiceIT {

    @Rule
    public MethodRule webAppEnvironmentRule = WebAppEnvironmentRule.INSTANCE;

    @PropertiesWebAppEnvironmentConfig("src/test/resources/test-web.properties")
    public WebAppEnvironment webAppEnvironment;

    @Test
    public void checkCustomerService() throws Exception {

        final JaxWsProxyFactoryBean jaxWsProxyFactoryBean = new JaxWsProxyFactoryBean();
        jaxWsProxyFactoryBean.setServiceClass(CustomerService.class);
        jaxWsProxyFactoryBean.setAddress(webAppEnvironment.getBaseUrl()
                + "/CustomerServicePort");

        final CustomerService customerService = (CustomerService) jaxWsProxyFactoryBean
                .create();

        final Customer originalCustomer = new Customer();

        // originalCustomer.setCustomerId(1);
        originalCustomer.setName("Scott Tiger");
        originalCustomer.getAddress().add("Hauptstr. 6");
        originalCustomer.getAddress().add("76133 Karlsruhe");
        originalCustomer.getAddress().add("Germany");
        originalCustomer.setNumOrders(15);
        originalCustomer.setRevenue(1234.56);
        originalCustomer.setTest(BigDecimal.valueOf(7890));
        originalCustomer.setBirthDate(DataTypeAdapter.parseDate("1970-01-01"));
        originalCustomer.setType(CustomerType.BUSINESS);

        final Integer customerId = customerService
                .updateCustomer(originalCustomer);

        assertNotNull(customerId);

        final Customer retrievedCustomer = customerService
                .getCustomerById(customerId);

        assertEquals(originalCustomer.getName(), retrievedCustomer.getName());
        assertEquals(originalCustomer.getAddress(), retrievedCustomer
                .getAddress());

        customerService.deleteCustomerById(retrievedCustomer.getCustomerId());

    }
}

Configuring integration tests in the Maven build

The integration tests we've implemented won't run by default in Maven builds. In order to invoke them, we'll need to configure the maven-failsafe-plugin in the integration-test phase:

pom.xml/project/build/plugins

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-failsafe-plugin</artifactId>
    <executions>
        <execution>
            <id>integration-test</id>
            <phase>integration-test</phase>
            <goals>
                <goal>integration-test</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Resources

TODO

Clone this wiki locally