Showing posts with label Apache CXF. Show all posts
Showing posts with label Apache CXF. Show all posts

Sunday, January 11, 2015

JAX-WS - CXF logging request and response SOAP messages using Log4j

apache cxf logo
CXF uses Java SE Logging for both client- and server-side logging of SOAP requests and responses. Logging is activated by use of separate in/out interceptors that can be attached to the requester and/or provider as required. These interceptors can be specified either programmatically (via Java code and/or annotations) or via use of configuration files. The following code sample shows how to configure CXF interceptors using Log4j for the Hello World web service from a previous post.

Tools used:
  • CXF 3.0
  • Spring 4.1
  • Log4j 1.2
  • Jetty 8
  • Maven 3

Specifying the interceptors via configuration files offers two benefits over programmatic configuration:
  1. Logging requirements can be altered without needing to recompile the code
  2. No Apache CXF-specific APIs need to be added to your code, which helps it remain interoperable with other JAX-WS compliant web service stacks

For this example Log4j will be used which is is a Java-based logging utility. As a best practice the CXF java.util.logging calls will first be redirected to SLF4J (Simple Logging Facade for Java) as described here. In other words a 'META-INF/cxf/org.apache.cxf.Logger' file will be created on the classpath containing the following:
org.apache.cxf.common.logging.Slf4jLogger

As the Hello World example uses Spring, the commons-logging calls from the Spring framework will also be redirected to SLF4J using jcl-over-slf4j. Now that all logging calls of both CXF and Spring are redirected to SLF4J, Log4j will be plugged into SLF4J using the slf4j-log4j12 adaptation layer. The picture below illustrates the described approach.
cxf logging using log4j

The below Maven POM file contains the needed dependencies for the SLF4 bridge (jcl-over-slf4j), Log4j adaptation layer (slf4j-log4j12) and Log4j (log4j). In addition it contains all other needed dependencies and plugins needed in order to run the example.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>info.source4code</groupId>
<artifactId>jaxws-jetty-cxf-logging-log4j</artifactId>
<version>1.0</version>
<packaging>war</packaging>

<name>JAX-WS - CXF logging request and response SOAP messages using Log4j</name>
<url>http://www.source4code.info/2015/01/jaxws-cxf-logging-request-response-soap-messages-log4j.html</url>

<prerequisites>
<maven>3.0</maven>
</prerequisites>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.6</java.version>

<slf4j.version>1.7.10</slf4j.version>
<log4j.version>1.2.17</log4j.version>
<junit.version>4.12</junit.version>
<cxf.version>3.0.3</cxf.version>
<spring.version>4.1.4.RELEASE</spring.version>

<maven-compiler-plugin.version>3.1</maven-compiler-plugin.version>
<jetty-maven-plugin.version>8.1.14.v20131031</jetty-maven-plugin.version>
<maven-failsafe-plugin.version>2.6</maven-failsafe-plugin.version>
</properties>

<dependencies>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
<!-- JUnit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<!-- CXF -->
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-jaxws</artifactId>
<version>${cxf.version}</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http</artifactId>
<version>${cxf.version}</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<!-- Jetty is needed if you're are not using the CXFServlet -->
<artifactId>cxf-rt-transports-http-jetty</artifactId>
<version>${cxf.version}</version>
</dependency>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
<exclusions>
<exclusion>
<artifactId>commons-logging</artifactId>
<groupId>commons-logging</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
<exclusions>
<exclusion>
<artifactId>commons-logging</artifactId>
<groupId>commons-logging</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
<exclusions>
<exclusion>
<artifactId>commons-logging</artifactId>
<groupId>commons-logging</groupId>
</exclusion>
</exclusions>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
<!-- jetty-maven-plugin -->
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>${jetty-maven-plugin.version}</version>
<configuration>
<connectors>
<connector
implementation="org.eclipse.jetty.server.nio.SelectChannelConnector">
<port>9090</port>
</connector>
</connectors>
<stopPort>8005</stopPort>
<stopKey>STOP</stopKey>
<daemon>true</daemon>
</configuration>
<executions>
<execution>
<id>start-jetty</id>
<phase>pre-integration-test</phase>
<goals>
<goal>start</goal>
</goals>
</execution>
<execution>
<id>stop-jetty</id>
<phase>post-integration-test</phase>
<goals>
<goal>stop</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- maven-failsafe-plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>${maven-failsafe-plugin.version}</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- cxf-codegen-plugin -->
<plugin>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-codegen-plugin</artifactId>
<version>${cxf.version}</version>
<executions>
<execution>
<id>generate-sources</id>
<phase>generate-sources</phase>
<configuration>
<sourceRoot>${project.build.directory}/generated/cxf</sourceRoot>
<wsdlOptions>
<wsdlOption>
<wsdl>${project.basedir}/src/main/resources/wsdl/helloworld.wsdl</wsdl>
<wsdlLocation>classpath:wsdl/helloworld.wsdl</wsdlLocation>
</wsdlOption>
</wsdlOptions>
</configuration>
<goals>
<goal>wsdl2java</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

Log4j configuration

The log4j environment is fully configurable programmatically. However, it is far more flexible to configure log4j using configuration files. Currently, configuration files can be written in XML or in Java properties (key=value) format. For this example a configuration script in XML is used as shown below. The script will write all logging events to a file except for the request and response SOAP messages that will be written to a different file.

Two appenders are configured to write logging events to a rolling file. The first file 'jaxws-jetty-cxf-logging.log' will contain all log events except for the ones emitted by the CXF logging interceptors as these are going to be written to 'jaxws-jetty-cxf-logging-ws.log'.

The last section contains the different loggers and the level at which information is logged. The log level of the 'org.apache.cxf.services' logger needs to be set to INFO in order to activate the SOAP logging events. In addition the WS_LOG_FILE appender needs to be referenced in order to write the SOAP logging events to a different file. Note the additivity="false" which makes sure the log events are not written to appenders attached to its ancestors (in this case APP_LOG_FILE).

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration debug="false"
xmlns:log4j='http://jakarta.apache.org/log4j/'>

<!-- APPENDERS -->
<appender name="APP_LOG_FILE" class="org.apache.log4j.DailyRollingFileAppender">
<param name="file" value="jaxws-jetty-cxf-logging.log" />
<param name="DatePattern" value="'.'yyyy-MM-dd" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d{HH:mm:ss.SSS} %-5p [%t][%c{1}] %m%n" />
</layout>
</appender>

<appender name="WS_LOG_FILE" class="org.apache.log4j.DailyRollingFileAppender">
<param name="file" value="jaxws-jetty-cxf-logging-ws.log" />
<param name="DatePattern" value="'.'yyyy-MM-dd" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d{HH:mm:ss.SSS} %-5p [%t][%c{1}] %m%n" />
</layout>
</appender>

<!-- LOGGERS -->
<logger name="info.source4code.soap.http.cxf">
<level value="INFO" />
</logger>

<logger name="org.springframework">
<level value="WARN" />
</logger>
<logger name="org.apache.cxf">
<level value="WARN" />
</logger>

<!-- level INFO needed to log SOAP messages -->
<logger name="org.apache.cxf.services" additivity="false">
<level value="INFO" />
<!-- specify a dedicated appender for the SOAP messages -->
<appender-ref ref="WS_LOG_FILE" />
</logger>

<root>
<level value="WARN" />
<appender-ref ref="APP_LOG_FILE" />
</root>

</log4j:configuration>

Requester

In order to activate the logging interceptors provided by the CXF framework, there are two options. For the requester(client) the option where all logging interceptors are configured manually will be illustrated. The other option, where the logging feature is used to configure all interceptors, will be shown in the provider section down below. For more information on the difference between interceptors and features check out this post.

First an instance of the abstract AbstractLoggingInterceptor class is created to enable pretty printing of the SOAP messages. Next, instances of the LoggingInInterceptor and LoggingOutInterceptor are specified which have as parent the previously defined abstractLoggingInterceptor instance. In a last step the interceptors are added to the CXF bus, which is the backbone of the CXF architecture that manages the respective inbound and outbound message and fault interceptor chains for all client and server endpoints. The interceptors are added to both in/out and respective fault interceptor chains as shown below.
Note that interceptors can be added to a client, server or bus.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cxf="http://cxf.apache.org/core"
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/core http://cxf.apache.org/schemas/core.xsd
http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">

<jaxws:client id="helloWorldRequesterBean"
serviceClass="info.source4code.services.helloworld.HelloWorldPortType"
address="${helloworld.address}">
</jaxws:client>

<!-- abstractLoggingInterceptor that will enable pretty printing of messages -->
<bean id="abstractLoggingInterceptor" abstract="true">
<property name="prettyLogging" value="true" />
</bean>

<!-- logging interceptors that will log all received/sent messages -->
<bean id="loggingInInterceptor" class="org.apache.cxf.interceptor.LoggingInInterceptor"
parent="abstractLoggingInterceptor">
</bean>
<bean id="loggingOutInterceptor" class="org.apache.cxf.interceptor.LoggingOutInterceptor"
parent="abstractLoggingInterceptor">
</bean>

<!-- add the logging interceptors to the CXF bus -->
<cxf:bus>
<cxf:inInterceptors>
<ref bean="loggingInInterceptor" />
</cxf:inInterceptors>
<cxf:inFaultInterceptors>
<ref bean="loggingInInterceptor" />
</cxf:inFaultInterceptors>
<cxf:outInterceptors>
<ref bean="loggingOutInterceptor" />
</cxf:outInterceptors>
<cxf:outFaultInterceptors>
<ref bean="loggingOutInterceptor" />
</cxf:outFaultInterceptors>
</cxf:bus>

</beans>

Provider

Activating the interceptors at provider(server) side will be done using the LoggingFeature that is supplied with the CXF framework.

First an instance of the abstract LoggingFeature class is created with the prettyLogging set to true in order to enable pretty printing of the SOAP messages. As with the interceptors, the feature is added to the CXF bus in order to activate them as shown below.
Note that features can be added to a client, server or a bus.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cxf="http://cxf.apache.org/core"
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/core http://cxf.apache.org/schemas/core.xsd
http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">

<jaxws:endpoint id="helloWorldProviderBean"
implementor="info.source4code.soap.http.cxf.HelloWorldImpl"
address="/helloworld">
</jaxws:endpoint>

<!-- loggingFeature that will log all received/sent messages -->
<bean id="loggingFeature" class="org.apache.cxf.feature.LoggingFeature">
<property name="prettyLogging" value="true" />
</bean>

<!-- add the loggingFeature to the cxf bus -->
<cxf:bus>
<cxf:features>
<ref bean="loggingFeature" />
</cxf:features>
</cxf:bus>

</beans>

Testing

Testing of the service is done using a unit and an integration test case. For the unit test case the provider is created without Spring (using JaxWsServerFactoryBean), as such the logging feature needs to be added programmatically as shown below.
package info.source4code.soap.http.cxf;

import static org.junit.Assert.assertEquals;
import info.source4code.services.helloworld.Person;

import org.apache.cxf.feature.LoggingFeature;
import org.apache.cxf.jaxws.JaxWsServerFactoryBean;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:/META-INF/spring/context-requester.xml" })
public class HelloWorldImplTest {

private static String ENDPOINT_ADDRESS = "http://localhost:9090/s4c/services/helloworld";

@Autowired
private HelloWorldClient helloWorldClient;

@BeforeClass
public static void setUpBeforeClass() throws Exception {
// start the HelloWorld service using jaxWsServerFactoryBean
JaxWsServerFactoryBean jaxWsServerFactoryBean = new JaxWsServerFactoryBean();

// adding loggingFeature to print the received/sent messages
LoggingFeature loggingFeature = new LoggingFeature();
loggingFeature.setPrettyLogging(true);

jaxWsServerFactoryBean.getFeatures().add(loggingFeature);

// setting the implementation
HelloWorldImpl implementor = new HelloWorldImpl();
jaxWsServerFactoryBean.setServiceBean(implementor);
// setting the endpoint
jaxWsServerFactoryBean.setAddress(ENDPOINT_ADDRESS);
jaxWsServerFactoryBean.create();
}

@Test
public void testSayHello() {
Person person = new Person();
person.setFirstName("John");
person.setLastName("Doe");

assertEquals("Hello John Doe!",
helloWorldClient.sayHello(person));
}
}

Run the example by opening a command prompt and executing following Maven command:
mvn verify

The result should be that a number of log files are created in the project root directory. Amongst these files there should be 'jaxws-jetty-cxf-logging-ws.log' and 'jaxws-jetty-cxf-logging-ws-test.log' which contain the exchanged SOAP messages.
06:26:41.718 INFO  [qtp1766911175-27][HelloWorld_PortType] Inbound Message
----------------------------
ID: 1
Address: http://localhost:9090/s4c/services/helloworld
Encoding: UTF-8
Http-Method: POST
Content-Type: text/xml; charset=UTF-8
Headers: {Accept=[*/*], Cache-Control=[no-cache], connection=[keep-alive], Content-Length=[229], content-type=[text/xml; charset=UTF-8], Host=[localhost:9090], Pragma=[no-cache], SOAPAction=[""], User-Agent=[Apache CXF 3.0.3]}
Payload: <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<person xmlns="http://source4code.info/services/helloworld">
<firstName>John</firstName>
<lastName>Doe</lastName>
</person>
</soap:Body>
</soap:Envelope>

--------------------------------------
06:26:41.786 INFO [qtp1766911175-27][HelloWorld_PortType] Outbound Message
---------------------------
ID: 1
Response-Code: 200
Encoding: UTF-8
Content-Type: text/xml
Headers: {}
Payload: <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<greeting xmlns="http://source4code.info/services/helloworld">
<text>Hello John Doe!</text>
</greeting>
</soap:Body>
</soap:Envelope>


github icon
If you would like to run the above code sample you can download the full source code and their corresponding JUnit and integration test cases here.

This concludes the CXF logging request and response messages using Log4j example. If you found this post helpful or have any questions or remarks, please leave a comment.

Saturday, January 10, 2015

JAX-WS - CXF logging request and response SOAP messages using Log4j2

apache cxf logo
CXF uses Java SE Logging for both client- and server-side logging of SOAP requests and responses. Logging is activated by use of separate in/out interceptors that can be attached to the requester and/or provider as required. These interceptors can be specified either programmatically (via Java code and/or annotations) or via use of configuration files. The following code sample shows how to configure CXF interceptors using Log4j2 for the Hello World web service from a previous post.

Tools used:
  • CXF 3.0
  • Spring 4.1
  • Log4j2 2.1
  • Jetty 8
  • Maven 3

Specifying the interceptors via configuration files offers two benefits over programmatic configuration:
  1. Logging requirements can be altered without needing to recompile the code
  2. No Apache CXF-specific APIs need to be added to your code, which helps it remain interoperable with other JAX-WS compliant web service stacks

For this example Log4j2 will be used which is an upgrade of the Log4j project. As a best practice the CXF java.util.logging calls will first be redirected to SLF4J (Simple Logging Facade for Java) as described here. In other words a 'META-INF/cxf/org.apache.cxf.Logger' file will be created on the classpath containing the following:
org.apache.cxf.common.logging.Slf4jLogger

As the Hello World example uses Spring, the commons-logging calls from the Spring framework will also be redirected to SLF4J using jcl-over-slf4j. Now that all logging calls of both CXF and Spring are redirected to SLF4J, Log4j2 will be plugged into SLF4J using the Log4j 2 SLF4J Binding. The picture below illustrates the described approach.
cxf logging using log4j2

The below Maven POM file contains the needed dependencies for the SLF4 bridge (jcl-over-slf4j), Log4j 2 SLF4J Binding (log4j-slf4j-impl) and Log4j2 (log4j-core). In addition it contains all other needed dependencies and plugins needed in order to run the example.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>info.source4code</groupId>
<artifactId>jaxws-jetty-cxf-logging-log4j2</artifactId>
<version>1.0</version>
<packaging>war</packaging>

<name>JAX-WS - CXF logging request and response SOAP messages using Log4j2</name>
<url>http://www.source4code.info/2015/01/jaxws-cxf-logging-request-response-soap-messages-log4j2.html</url>

<prerequisites>
<maven>3.0</maven>
</prerequisites>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.6</java.version>

<slf4j.version>1.7.10</slf4j.version>
<log4j2.version>2.1</log4j2.version>
<junit.version>4.12</junit.version>
<cxf.version>3.0.3</cxf.version>
<spring.version>4.1.4.RELEASE</spring.version>

<maven-compiler-plugin.version>3.1</maven-compiler-plugin.version>
<jetty-maven-plugin.version>8.1.16.v20140903</jetty-maven-plugin.version>
<maven-failsafe-plugin.version>2.17</maven-failsafe-plugin.version>
</properties>

<dependencies>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>${log4j2.version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>${log4j2.version}</version>
</dependency>
<!-- JUnit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<!-- CXF -->
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-jaxws</artifactId>
<version>${cxf.version}</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http</artifactId>
<version>${cxf.version}</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<!-- Jetty is needed if you're are not using the CXFServlet -->
<artifactId>cxf-rt-transports-http-jetty</artifactId>
<version>${cxf.version}</version>
</dependency>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
<exclusions>
<exclusion>
<artifactId>commons-logging</artifactId>
<groupId>commons-logging</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
<exclusions>
<exclusion>
<artifactId>commons-logging</artifactId>
<groupId>commons-logging</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
<exclusions>
<exclusion>
<artifactId>commons-logging</artifactId>
<groupId>commons-logging</groupId>
</exclusion>
</exclusions>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
<!-- jetty-maven-plugin -->
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>${jetty-maven-plugin.version}</version>
<configuration>
<connectors>
<connector
implementation="org.eclipse.jetty.server.nio.SelectChannelConnector">
<port>9090</port>
</connector>
</connectors>
<stopPort>8005</stopPort>
<stopKey>STOP</stopKey>
<daemon>true</daemon>
</configuration>
<executions>
<execution>
<id>start-jetty</id>
<phase>pre-integration-test</phase>
<goals>
<goal>start</goal>
</goals>
</execution>
<execution>
<id>stop-jetty</id>
<phase>post-integration-test</phase>
<goals>
<goal>stop</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- maven-failsafe-plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>${maven-failsafe-plugin.version}</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- cxf-codegen-plugin -->
<plugin>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-codegen-plugin</artifactId>
<version>${cxf.version}</version>
<executions>
<execution>
<id>generate-sources</id>
<phase>generate-sources</phase>
<configuration>
<sourceRoot>${project.build.directory}/generated/cxf</sourceRoot>
<wsdlOptions>
<wsdlOption>
<wsdl>${project.basedir}/src/main/resources/wsdl/helloworld.wsdl</wsdl>
<wsdlLocation>classpath:wsdl/helloworld.wsdl</wsdlLocation>
</wsdlOption>
</wsdlOptions>
</configuration>
<goals>
<goal>wsdl2java</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

Log4j2 configuration

Log4j2 can be configured in 1 of 4 ways:
  • Through a configuration file written in XML, JSON, or YAML.
  • Programmatically, by creating a ConfigurationFactory and Configuration implementation.
  • Programmatically, by calling the APIs exposed in the Configuration interface to add components to the default configuration.
  • Programmatically, by calling methods on the internal Logger class.

For this example a configuration script in XML is used as shown below. The script will write all logging events to a file except for the request and response SOAP messages that will be written to a different file.

After defining some properties that contain the layout pattern and file names, two appenders are configured to write logging events to a rolling file. The first file 'jaxws-jetty-cxf-logging.log' will contain all log events except for the ones emitted by the CXF logging interceptors as these are going to be written to 'jaxws-jetty-cxf-logging-ws.log'.

The last section contains the different loggers and the level at which information is logged. The log level of the 'org.apache.cxf.services' logger needs to be set to INFO in order to activate the SOAP logging events. In addition the WS_LOG_FILE appender needs to be referenced in order to write the SOAP logging events to a different file. Note the additivity="false" which makes sure the log events are not written to appenders attached to its ancestors (in this case APP_LOG_FILE).
<?xml version="1.0" encoding="UTF-8"?>
<Configuration monitorInterval="60">

<!-- PROPERTIES -->
<Properties>
<Property name="layout">%d{HH:mm:ss.SSS} %-5level [%thread][%logger{0}] %m%n</Property>
<Property name="logFile">jaxws-jetty-cxf-logging</Property>
<Property name="logFile-ws">jaxws-jetty-cxf-logging-ws</Property>
</Properties>

<!-- APPENDERS -->
<Appenders>
<RollingFile name="APP_LOG_FILE" fileName="${logFile}.log" filePattern="${logFile}.%d{yyyy-MM-dd}.log">
<TimeBasedTriggeringPolicy interval="1" modulate="true" />
<DefaultRolloverStrategy max="30" />
<PatternLayout pattern="${layout}" />
</RollingFile>

<RollingFile name="WS_LOG_FILE" fileName="${logFile-ws}.log" filePattern="${logFile-ws}.%d{yyyy-MM-dd}.log">
<TimeBasedTriggeringPolicy interval="1" modulate="true" />
<DefaultRolloverStrategy max="30" />
<PatternLayout pattern="${layout}" />
</RollingFile>
</Appenders>

<!-- LOGGERS -->
<Loggers>
<Logger name="info.source4code.soap.http.cxf" level="INFO" />

<Logger name="org.springframework" level="WARN" />
<Logger name="org.apache.cxf" level="WARN" />

<!-- level INFO needed to log SOAP messages -->
<Logger name="org.apache.cxf.services" level="INFO"
additivity="false">
<!-- specify a dedicated appender for the SOAP messages -->
<AppenderRef ref="WS_LOG_FILE" />
</Logger>

<!-- APPLICATION LOG LEVEL -->
<Root level="WARN">
<AppenderRef ref="APP_LOG_FILE" />
</Root>
</Loggers>

</Configuration>

Requester

In order to activate the logging interceptors provided by the CXF framework, there are two options. For the requester(client) the option where all logging interceptors are configured manually will be illustrated. The other option, where the logging feature is used to configure all interceptors, will be shown in the provider section down below. For more information on the difference between interceptors and features check out this post.

First an instance of the abstract AbstractLoggingInterceptor class is created to enable pretty printing of the SOAP messages. Next, instances of the LoggingInInterceptor and LoggingOutInterceptor are specified which have as parent the previously defined abstractLoggingInterceptor instance. In a last step the interceptors are added to the CXF bus, which is the backbone of the CXF architecture that manages the respective inbound and outbound message and fault interceptor chains for all client and server endpoints. The interceptors are added to both in/out and respective fault interceptor chains as shown below.
Note that interceptors can be added to a client, server or bus.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cxf="http://cxf.apache.org/core"
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/core http://cxf.apache.org/schemas/core.xsd
http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">

<jaxws:client id="helloWorldRequesterBean"
serviceClass="info.source4code.services.helloworld.HelloWorldPortType"
address="${helloworld.address}">
</jaxws:client>

<!-- abstractLoggingInterceptor that will enable pretty printing of messages -->
<bean id="abstractLoggingInterceptor" abstract="true">
<property name="prettyLogging" value="true" />
</bean>

<!-- logging interceptors that will log all received/sent messages -->
<bean id="loggingInInterceptor" class="org.apache.cxf.interceptor.LoggingInInterceptor"
parent="abstractLoggingInterceptor">
</bean>
<bean id="loggingOutInterceptor" class="org.apache.cxf.interceptor.LoggingOutInterceptor"
parent="abstractLoggingInterceptor">
</bean>

<!-- add the logging interceptors to the CXF bus -->
<cxf:bus>
<cxf:inInterceptors>
<ref bean="loggingInInterceptor" />
</cxf:inInterceptors>
<cxf:inFaultInterceptors>
<ref bean="loggingInInterceptor" />
</cxf:inFaultInterceptors>
<cxf:outInterceptors>
<ref bean="loggingOutInterceptor" />
</cxf:outInterceptors>
<cxf:outFaultInterceptors>
<ref bean="loggingOutInterceptor" />
</cxf:outFaultInterceptors>
</cxf:bus>

</beans>

Provider

Activating the interceptors at provider(server) side will be done using the LoggingFeature that is supplied with the CXF framework.

First an instance of the abstract LoggingFeature class is created with the prettyLogging set to true in order to enable pretty printing of the SOAP messages. As with the interceptors, the feature is added to the CXF bus in order to activate them as shown below.
Note that features can be added to a client, server or a bus.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cxf="http://cxf.apache.org/core"
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/core http://cxf.apache.org/schemas/core.xsd
http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">

<jaxws:endpoint id="helloWorldProviderBean"
implementor="info.source4code.soap.http.cxf.HelloWorldImpl"
address="/helloworld">
</jaxws:endpoint>

<!-- loggingFeature that will log all received/sent messages -->
<bean id="loggingFeature" class="org.apache.cxf.feature.LoggingFeature">
<property name="prettyLogging" value="true" />
</bean>

<!-- add the loggingFeature to the cxf bus -->
<cxf:bus>
<cxf:features>
<ref bean="loggingFeature" />
</cxf:features>
</cxf:bus>

</beans>

Testing

Testing of the service is done using a unit and an integration test case. For the unit test case the provider is created without Spring (using JaxWsServerFactoryBean), as such the logging feature needs to be added programmatically as shown below.
package info.source4code.soap.http.cxf;

import static org.junit.Assert.assertEquals;
import info.source4code.services.helloworld.Person;

import org.apache.cxf.feature.LoggingFeature;
import org.apache.cxf.jaxws.JaxWsServerFactoryBean;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:/META-INF/spring/context-requester.xml" })
public class HelloWorldImplTest {

private static String ENDPOINT_ADDRESS = "http://localhost:9090/s4c/services/helloworld";

@Autowired
private HelloWorldClient helloWorldClient;

@BeforeClass
public static void setUpBeforeClass() throws Exception {
// start the HelloWorld service using jaxWsServerFactoryBean
JaxWsServerFactoryBean jaxWsServerFactoryBean = new JaxWsServerFactoryBean();

// adding loggingFeature to print the received/sent messages
LoggingFeature loggingFeature = new LoggingFeature();
loggingFeature.setPrettyLogging(true);

jaxWsServerFactoryBean.getFeatures().add(loggingFeature);

// setting the implementation
HelloWorldImpl implementor = new HelloWorldImpl();
jaxWsServerFactoryBean.setServiceBean(implementor);
// setting the endpoint
jaxWsServerFactoryBean.setAddress(ENDPOINT_ADDRESS);
jaxWsServerFactoryBean.create();
}

@Test
public void testSayHello() {
Person person = new Person();
person.setFirstName("John");
person.setLastName("Doe");

assertEquals("Hello John Doe!",
helloWorldClient.sayHello(person));
}
}

Run the example by opening a command prompt and executing following Maven command:
mvn verify

The result should be that a number of log files are created in the project root directory. Amongst these files there should be 'jaxws-jetty-cxf-logging-ws.log' and 'jaxws-jetty-cxf-logging-ws-test.log' which contain the exchanged SOAP messages.
06:26:41.718 INFO  [qtp1766911175-27][HelloWorld_PortType] Inbound Message
----------------------------
ID: 1
Address: http://localhost:9090/s4c/services/helloworld
Encoding: UTF-8
Http-Method: POST
Content-Type: text/xml; charset=UTF-8
Headers: {Accept=[*/*], Cache-Control=[no-cache], connection=[keep-alive], Content-Length=[229], content-type=[text/xml; charset=UTF-8], Host=[localhost:9090], Pragma=[no-cache], SOAPAction=[""], User-Agent=[Apache CXF 3.0.3]}
Payload: <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<person xmlns="http://source4code.info/services/helloworld">
<firstName>John</firstName>
<lastName>Doe</lastName>
</person>
</soap:Body>
</soap:Envelope>

--------------------------------------
06:26:41.786 INFO [qtp1766911175-27][HelloWorld_PortType] Outbound Message
---------------------------
ID: 1
Response-Code: 200
Encoding: UTF-8
Content-Type: text/xml
Headers: {}
Payload: <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<greeting xmlns="http://source4code.info/services/helloworld">
<text>Hello John Doe!</text>
</greeting>
</soap:Body>
</soap:Envelope>


github icon
If you would like to run the above code sample you can download the full source code and their corresponding JUnit and integration test cases here.

This concludes the CXF logging request and response messages using Log4j2 example. If you found this post helpful or have any questions or remarks, please leave a comment.

Wednesday, January 7, 2015

CXF - Feature vs Interceptor

When working with CXF you'll often see that the configuration of a certain capability can be done either via adding a feature or by adding a number of interceptors. In this post we'll briefly explain how the two are related to each other.

CXF Interceptor

Interceptors are the fundamental processing unit inside CXF. For example when a service is invoked, an InterceptorChain (an ordered list of interceptors) is created and invoked. Each interceptor gets a chance to do what they want with the incoming message. This can include reading it, transforming it, processing headers, validating the message, etc. Interceptors can be configured on the interceptor chains of CXF clients, CXF servers or the CXF bus.

When for example configuring logging of all messages on the CXF bus, we need to created the respective LoggingInInterceptor and LoggingOutInterceptor interceptors and add them to the different interceptor chains (InInterceptors, OutInterceptors, OutFaultInterceptors and InFaultInterceptors) which are in turn added to the CXF bus as shown below.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cxf="http://cxf.apache.org/core"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd">

<bean id="loggingInInterceptor" class="org.apache.cxf.interceptor.LoggingInInterceptor" />
<bean id="loggingOutInterceptor" class="org.apache.cxf.interceptor.LoggingOutInterceptor" />

<cxf:bus>
<cxf:inInterceptors>
<ref bean="loggingInInterceptor" />
</cxf:inInterceptors>
<cxf:inFaultInterceptors>
<ref bean="loggingInInterceptor" />
</cxf:inFaultInterceptors>
<cxf:outInterceptors>
<ref bean="loggingOutInterceptor" />
</cxf:outInterceptors>
<cxf:outFaultInterceptors>
<ref bean="loggingOutInterceptor" />
</cxf:outFaultInterceptors>
</cxf:bus>

</beans>

CXF Feature

A feature in CXF is a way of adding capabilities to a CXF Client, CXF Server or CXF Bus. In other words features provide a simple way to perform or configure a series of related tasks. These tasks can be things like adding a number of interceptors to a chain, configuring properties, setting up resources, etc.

For example, CXF ships with a LoggingFeature which does exactly the same as the configuration from the previous section. The feature encapsulates the creation of the different logging interceptors and then subsequently adding them to all the interceptor chains.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cxf="http://cxf.apache.org/core"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd">

<bean id="loggingFeature" class="org.apache.cxf.feature.LoggingFeature" />

<cxf:bus>
<cxf:features>
<ref bean="loggingFeature" />
</cxf:features>
</cxf:bus>

</beans>

Conclusion

If you need full control when enabling a certain CXF functionality then use interceptors. For example when you only want to log error messages, only configure the logging interceptors on the fault chains.

If you need the standard CXF functionality then use the corresponding feature. For example when you want to log all messages, then configure the logging feature as the notation will be shorter/simpler.

Sunday, September 21, 2014

JAX-WS - CXF logging request and response SOAP messages using Logback

apache cxf logo
CXF uses Java SE Logging for both client- and server-side logging of SOAP requests and responses. Logging is activated by use of separate in/out interceptors that can be attached to the requester and/or provider as required. These interceptors can be specified either programmatically (via Java code and/or annotations) or via use of configuration files. The following code sample shows how to configure CXF interceptors using Logback for the Hello World web service from a previous post.

Tools used:
  • CXF 3.0
  • Spring 4.1
  • Logback 1.1
  • Jetty 8
  • Maven 3

Specifying the interceptors via configuration files offers two benefits over programmatic configuration:
  1. Logging requirements can be altered without needing to recompile the code
  2. No Apache CXF-specific APIs need to be added to your code, which helps it remain interoperable with other JAX-WS compliant web service stacks

For this example Logback will be used which is a successor to the Log4j project. As a best practice the CXF java.util.logging calls will first be redirected to SLF4J (Simple Logging Facade for Java) as described here. In other words a 'META-INF/cxf/org.apache.cxf.Logger' file will be created on the classpath containing the following:
org.apache.cxf.common.logging.Slf4jLogger

As the Hello World example uses Spring, the commons-logging calls from the Spring framework will also be redirected to SLF4J using jcl-over-slf4j. Now that all logging calls of both CXF and Spring are redirected to SLF4J, Logback will receive them automatically as it natively implements SLF4J. The picture below illustrates the described approach.
cxf logging using logback

The below Maven POM file contains the needed dependencies for the SLF4 bridge (jcl-over-slf4j) and Logback (logback-classic). In addition it contains all other needed dependencies and plugins needed in order to run the example.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>info.source4code</groupId>
<artifactId>jaxws-jetty-cxf-logging-logback</artifactId>
<version>1.0</version>
<packaging>war</packaging>

<name>JAX-WS - CXF logging request and response SOAP messages using Logback</name>
<url>http://www.source4code.info/2014/09/jaxws-cxf-logging-request-response-soap-messages-logback.html</url>

<prerequisites>
<maven>3.0</maven>
</prerequisites>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.6</java.version>

<slf4j.version>1.7.10</slf4j.version>
<logback.version>1.1.2</logback.version>
<junit.version>4.12</junit.version>
<cxf.version>3.0.3</cxf.version>
<spring.version>4.1.4.RELEASE</spring.version>

<maven-compiler-plugin.version>3.1</maven-compiler-plugin.version>
<jetty-maven-plugin.version>8.1.16.v20140903</jetty-maven-plugin.version>
<maven-failsafe-plugin.version>2.17</maven-failsafe-plugin.version>
</properties>

<dependencies>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>
<!-- JUnit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<!-- CXF -->
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-jaxws</artifactId>
<version>${cxf.version}</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http</artifactId>
<version>${cxf.version}</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<!-- Jetty is needed if you're are not using the CXFServlet -->
<artifactId>cxf-rt-transports-http-jetty</artifactId>
<version>${cxf.version}</version>
</dependency>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
<exclusions>
<exclusion>
<artifactId>commons-logging</artifactId>
<groupId>commons-logging</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
<exclusions>
<exclusion>
<artifactId>commons-logging</artifactId>
<groupId>commons-logging</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
<exclusions>
<exclusion>
<artifactId>commons-logging</artifactId>
<groupId>commons-logging</groupId>
</exclusion>
</exclusions>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
<!-- jetty-maven-plugin -->
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>${jetty-maven-plugin.version}</version>
<configuration>
<connectors>
<connector
implementation="org.eclipse.jetty.server.nio.SelectChannelConnector">
<port>9090</port>
</connector>
</connectors>
<stopPort>8005</stopPort>
<stopKey>STOP</stopKey>
<daemon>true</daemon>
</configuration>
<executions>
<execution>
<id>start-jetty</id>
<phase>pre-integration-test</phase>
<goals>
<goal>start</goal>
</goals>
</execution>
<execution>
<id>stop-jetty</id>
<phase>post-integration-test</phase>
<goals>
<goal>stop</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- maven-failsafe-plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>${maven-failsafe-plugin.version}</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- cxf-codegen-plugin -->
<plugin>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-codegen-plugin</artifactId>
<version>${cxf.version}</version>
<executions>
<execution>
<id>generate-sources</id>
<phase>generate-sources</phase>
<configuration>
<sourceRoot>${project.build.directory}/generated/cxf</sourceRoot>
<wsdlOptions>
<wsdlOption>
<wsdl>${project.basedir}/src/main/resources/wsdl/helloworld.wsdl</wsdl>
<wsdlLocation>classpath:wsdl/helloworld.wsdl</wsdlLocation>
</wsdlOption>
</wsdlOptions>
</configuration>
<goals>
<goal>wsdl2java</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

Logback Configuration

Logback can be configured either programmatically or with a configuration script expressed in XML or Groovy format. For this example a configuration script in XML is used as shown below. The script will write all logging events to a file except for the request and response SOAP messages that will be written to a different file.

After defining some properties that contain the layout pattern and file names, two appenders are configured to write logging events to a rolling file. The first file 'jaxws-jetty-cxf-logging.log' will contain all log events except for the ones emitted by the CXF logging interceptors as these are going to be written to 'jaxws-jetty-cxf-logging-ws.log'.

The last section contains the different loggers and the level at which information is logged. The log level of the 'org.apache.cxf.services' logger needs to be set to INFO in order to activate the SOAP logging events. In addition the WS_LOG_FILE appender needs to be referenced in order to write the SOAP logging events to a different file. Note the additivity="false" which makes sure the log events are not written to appenders attached to its ancestors (in this case APP_LOG_FILE).
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds">

<!-- PROPERTIES -->
<property name="layout" value="%d{HH:mm:ss.SSS} %-5level [%thread][%logger{0}] %m%n" />
<property name="logFile" value="jaxws-jetty-cxf-logging" />
<property name="logFile-ws" value="jaxws-jetty-cxf-logging-ws" />

<!-- APPENDERS -->
<appender name="APP_LOG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<File>${logFile}.log</File>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${logFile}.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${layout}</pattern>
</encoder>
</appender>

<appender name="WS_LOG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${logFile-ws}.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${logFile-ws}.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${layout}</pattern>
</encoder>
</appender>

<!-- LOGGERS -->
<logger name="info.source4code.soap.http.cxf" level="INFO" />

<logger name="org.springframework" level="WARN" />
<logger name="org.apache.cxf" level="WARN" />

<!-- level INFO needed to log SOAP messages -->
<logger name="org.apache.cxf.services" level="INFO"
additivity="false">
<!-- specify a dedicated appender for the SOAP messages -->
<appender-ref ref="WS_LOG_FILE" />
</logger>

<!-- APPLICATION LOG LEVEL -->
<root level="WARN">
<appender-ref ref="APP_LOG_FILE" />
</root>

</configuration>

Requester

In order to activate the logging interceptors provided by the CXF framework, there are two options. For the requester(client) the option where all logging interceptors are configured manually will be illustrated. The other option, where the logging feature is used to configure all interceptors, will be shown in the provider section down below. For more information on the difference between interceptors and features check out this post.

First an instance of the abstract AbstractLoggingInterceptor class is created to enable pretty printing of the SOAP messages. Next, instances of the LoggingInInterceptor and LoggingOutInterceptor are specified which have as parent the previously defined abstractLoggingInterceptor instance. In a last step the interceptors are added to the CXF bus, which is the backbone of the CXF architecture that manages the respective inbound and outbound message and fault interceptor chains for all client and server endpoints. The interceptors are added to both in/out and respective fault interceptor chains as shown below.
Note that interceptors can be added to a client, server or bus.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cxf="http://cxf.apache.org/core"
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/core http://cxf.apache.org/schemas/core.xsd
http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">

<jaxws:client id="helloWorldRequesterBean"
serviceClass="info.source4code.services.helloworld.HelloWorldPortType"
address="${helloworld.address}">
</jaxws:client>

<!-- abstractLoggingInterceptor that will enable pretty printing of messages -->
<bean id="abstractLoggingInterceptor" abstract="true">
<property name="prettyLogging" value="true" />
</bean>

<!-- logging interceptors that will log all received/sent messages -->
<bean id="loggingInInterceptor" class="org.apache.cxf.interceptor.LoggingInInterceptor"
parent="abstractLoggingInterceptor">
</bean>
<bean id="loggingOutInterceptor" class="org.apache.cxf.interceptor.LoggingOutInterceptor"
parent="abstractLoggingInterceptor">
</bean>

<!-- add the logging interceptors to the CXF bus -->
<cxf:bus>
<cxf:inInterceptors>
<ref bean="loggingInInterceptor" />
</cxf:inInterceptors>
<cxf:inFaultInterceptors>
<ref bean="loggingInInterceptor" />
</cxf:inFaultInterceptors>
<cxf:outInterceptors>
<ref bean="loggingOutInterceptor" />
</cxf:outInterceptors>
<cxf:outFaultInterceptors>
<ref bean="loggingOutInterceptor" />
</cxf:outFaultInterceptors>
</cxf:bus>

</beans>

Provider

Activating the interceptors at provider(server) side will be done using the LoggingFeature that is supplied with the CXF framework.

First an instance of the abstract LoggingFeature class is created with the prettyLogging set to true in order to enable pretty printing of the SOAP messages. As with the interceptors, the feature is added to the CXF bus in order to activate them as shown below.
Note that features can be added to a client, server or a bus.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cxf="http://cxf.apache.org/core"
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/core http://cxf.apache.org/schemas/core.xsd
http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">

<jaxws:endpoint id="helloWorldProviderBean"
implementor="info.source4code.soap.http.cxf.HelloWorldImpl"
address="/helloworld">
</jaxws:endpoint>

<!-- loggingFeature that will log all received/sent messages -->
<bean id="loggingFeature" class="org.apache.cxf.feature.LoggingFeature">
<property name="prettyLogging" value="true" />
</bean>

<!-- add the loggingFeature to the cxf bus -->
<cxf:bus>
<cxf:features>
<ref bean="loggingFeature" />
</cxf:features>
</cxf:bus>

</beans>

Testing

Testing of the service is done using a unit and an integration test case. For the unit test case the provider is created without Spring (using JaxWsServerFactoryBean), as such the logging feature needs to be added programmatically as shown below.
package info.source4code.soap.http.cxf;

import static org.junit.Assert.assertEquals;
import info.source4code.services.helloworld.Person;

import org.apache.cxf.feature.LoggingFeature;
import org.apache.cxf.jaxws.JaxWsServerFactoryBean;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:/META-INF/spring/context-requester.xml" })
public class HelloWorldImplTest {

private static String ENDPOINT_ADDRESS = "http://localhost:9090/s4c/services/helloworld";

@Autowired
private HelloWorldClient helloWorldClient;

@BeforeClass
public static void setUpBeforeClass() throws Exception {
// start the HelloWorld service using jaxWsServerFactoryBean
JaxWsServerFactoryBean jaxWsServerFactoryBean = new JaxWsServerFactoryBean();

// adding loggingFeature to print the received/sent messages
LoggingFeature loggingFeature = new LoggingFeature();
loggingFeature.setPrettyLogging(true);

jaxWsServerFactoryBean.getFeatures().add(loggingFeature);

// setting the implementation
HelloWorldImpl implementor = new HelloWorldImpl();
jaxWsServerFactoryBean.setServiceBean(implementor);
// setting the endpoint
jaxWsServerFactoryBean.setAddress(ENDPOINT_ADDRESS);
jaxWsServerFactoryBean.create();
}

@Test
public void testSayHello() {
Person person = new Person();
person.setFirstName("John");
person.setLastName("Doe");

assertEquals("Hello John Doe!",
helloWorldClient.sayHello(person));
}
}

Run the example by opening a command prompt and executing following Maven command:
mvn verify

The result should be that a number of log files are created in the project root directory. Amongst these files there should be 'jaxws-jetty-cxf-logging-ws.log' and 'jaxws-jetty-cxf-logging-ws-test.log' which contain the exchanged SOAP messages.
06:26:41.718 INFO  [qtp1766911175-27][HelloWorld_PortType] Inbound Message
----------------------------
ID: 1
Address: http://localhost:9090/s4c/services/helloworld
Encoding: UTF-8
Http-Method: POST
Content-Type: text/xml; charset=UTF-8
Headers: {Accept=[*/*], Cache-Control=[no-cache], connection=[keep-alive], Content-Length=[229], content-type=[text/xml; charset=UTF-8], Host=[localhost:9090], Pragma=[no-cache], SOAPAction=[""], User-Agent=[Apache CXF 3.0.3]}
Payload: <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<person xmlns="http://source4code.info/services/helloworld">
<firstName>John</firstName>
<lastName>Doe</lastName>
</person>
</soap:Body>
</soap:Envelope>

--------------------------------------
06:26:41.786 INFO [qtp1766911175-27][HelloWorld_PortType] Outbound Message
---------------------------
ID: 1
Response-Code: 200
Encoding: UTF-8
Content-Type: text/xml
Headers: {}
Payload: <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<greeting xmlns="http://source4code.info/services/helloworld">
<text>Hello John Doe!</text>
</greeting>
</soap:Body>
</soap:Envelope>


github icon
If you would like to run the above code sample you can download the full source code and their corresponding JUnit and integration test cases here.

This concludes the CXF logging request and response messages using Logback example. If you found this post helpful or have any questions or remarks, please leave a comment.

Saturday, August 23, 2014

JAX-WS - CXF contract first Hello World web service using Jetty and Maven

apache cxf logo
Apache CXF is an open source services framework. CXF helps to build and develop services using front end programming APIs like JAX-WS and JAX-RS. These services can speak a variety of protocols such as SOAP, XML/HTTP or RESTful HTTP and work over a variety of transports such as HTTP or JMS. The following post illustrates a basic example in which we will configure, build and run a Hello World contract first client and web service using CXF, Maven and Jetty.

Tools used:
  • CXF 3.0
  • Spring 4.1
  • Maven 3
  • Jetty 8

A web service can be developed using one of two approaches:
  1. Start with a WSDL contract and generate Java objects to implement the service.
  2. Start with a Java object and service enable it using annotations.

For new development the preferred approach is to first design your services using the Web Services Description Language (WSDL) and then generate the code to implement them. This approach enforces the concept that a service is an abstract entity that is implementation neutral. For the following example we will use a Hello World service that is defined by the WSDL shown below. It takes as input a persons first and last name and as result returns a greeting.
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<wsdl:definitions targetNamespace="http://source4code.info/services/helloworld"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://source4code.info/services/helloworld"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
name="HelloWorld">

<wsdl:types>
<schema targetNamespace="http://source4code.info/services/helloworld"
xmlns="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://source4code.info/services/helloworld"
elementFormDefault="qualified" attributeFormDefault="unqualified"
version="1.0">

<element name="person">
<complexType>
<sequence>
<element name="firstName" type="xsd:string" />
<element name="lastName" type="xsd:string" />
</sequence>
</complexType>
</element>

<element name="greeting">
<complexType>
<sequence>
<element name="text" type="xsd:string" />
</sequence>
</complexType>
</element>
</schema>
</wsdl:types>

<wsdl:message name="sayHelloRequest">
<wsdl:part name="person" element="tns:person"></wsdl:part>
</wsdl:message>

<wsdl:message name="sayHelloResponse">
<wsdl:part name="greeting" element="tns:greeting"></wsdl:part>
</wsdl:message>

<wsdl:portType name="HelloWorld_PortType">
<wsdl:operation name="sayHello">
<wsdl:input message="tns:sayHelloRequest"></wsdl:input>
<wsdl:output message="tns:sayHelloResponse"></wsdl:output>
</wsdl:operation>
</wsdl:portType>

<wsdl:binding name="HelloWorld_Binding" type="tns:HelloWorld_PortType">
<soap:binding style="document"
transport="http://schemas.xmlsoap.org/soap/http" />
<wsdl:operation name="sayHello">
<wsdl:input>
<soap:body use="literal" />
</wsdl:input>
<wsdl:output>
<soap:body use="literal" />
</wsdl:output>
</wsdl:operation>
</wsdl:binding>

<wsdl:service name="HelloWorld_Service">
<wsdl:port name="HelloWorld_Port" binding="tns:HelloWorld_Binding">
<soap:address
location="http://localhost:9090/s4c/services/helloworld" />
</wsdl:port>
</wsdl:service>
</wsdl:definitions>

Next is the Maven POM file which contains the needed dependencies. At the bottom of the list we find the CXF dependencies. The 'cxf-rt-transports-http-jetty' dependency is only needed in case the CFXServlet is not used. As the example includes a JUnit test that runs without CXFServlet we need to add this dependency.

CXF supports the Spring 2.0 XML syntax, which makes it easy to declare endpoints which are backed by Spring and inject clients into application code. It is also possible to use CXF without Spring but it may take a little extra effort. A number of things like policies and annotation processing are not wired in without Spring. To take advantage of those you would need to do some pre-setup coding. The example below will use Spring to create both requester (client) and provider (service) as such the needed Spring dependencies are included.

In addition to a unit test case we will also create an integration test case for which an instance of Jetty will be started that will host the above Hello World service. In order to achieve this the 'jetty-maven-plugin' has been added which is configured to be started/stopped, before/after the integration-test phase of Maven. The '<daemon>true</daemon>' configuration option forces Jetty to execute only while Maven is running, instead of running indefinitely.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>info.source4code</groupId>
<artifactId>jaxws-jetty-cxf-helloworld</artifactId>
<version>1.0</version>
<packaging>war</packaging>

<name>JAX-WS - CXF contract first Hello World web service using Jetty and Maven</name>
<url>http://www.source4code.info/2014/08/jaxws-cxf-contract-first-hello-world-jetty-maven.html</url>

<prerequisites>
<maven>3.0</maven>
</prerequisites>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.6</java.version>

<slf4j.version>1.7.10</slf4j.version>
<logback.version>1.1.2</logback.version>
<junit.version>4.12</junit.version>
<cxf.version>3.0.3</cxf.version>
<spring.version>4.1.4.RELEASE</spring.version>

<maven-compiler-plugin.version>3.1</maven-compiler-plugin.version>
<jetty-maven-plugin.version>8.1.16.v20140903</jetty-maven-plugin.version>
<maven-failsafe-plugin.version>2.17</maven-failsafe-plugin.version>
</properties>

<dependencies>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>
<!-- JUnit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<!-- CXF -->
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-jaxws</artifactId>
<version>${cxf.version}</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http</artifactId>
<version>${cxf.version}</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<!-- Jetty is needed if you're are not using the CXFServlet -->
<artifactId>cxf-rt-transports-http-jetty</artifactId>
<version>${cxf.version}</version>
</dependency>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
<exclusions>
<exclusion>
<artifactId>commons-logging</artifactId>
<groupId>commons-logging</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
<exclusions>
<exclusion>
<artifactId>commons-logging</artifactId>
<groupId>commons-logging</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
<exclusions>
<exclusion>
<artifactId>commons-logging</artifactId>
<groupId>commons-logging</groupId>
</exclusion>
</exclusions>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
<!-- jetty-maven-plugin -->
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>${jetty-maven-plugin.version}</version>
<configuration>
<connectors>
<connector
implementation="org.eclipse.jetty.server.nio.SelectChannelConnector">
<port>9090</port>
</connector>
</connectors>
<stopPort>8005</stopPort>
<stopKey>STOP</stopKey>
<daemon>true</daemon>
</configuration>
<executions>
<execution>
<id>start-jetty</id>
<phase>pre-integration-test</phase>
<goals>
<goal>start</goal>
</goals>
</execution>
<execution>
<id>stop-jetty</id>
<phase>post-integration-test</phase>
<goals>
<goal>stop</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- maven-failsafe-plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>${maven-failsafe-plugin.version}</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- cxf-codegen-plugin -->
<plugin>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-codegen-plugin</artifactId>
<version>${cxf.version}</version>
<executions>
<execution>
<id>generate-sources</id>
<phase>generate-sources</phase>
<configuration>
<sourceRoot>${project.build.directory}/generated/cxf</sourceRoot>
<wsdlOptions>
<wsdlOption>
<wsdl>${project.basedir}/src/main/resources/wsdl/helloworld.wsdl</wsdl>
<wsdlLocation>classpath:wsdl/helloworld.wsdl</wsdlLocation>
</wsdlOption>
</wsdlOptions>
</configuration>
<goals>
<goal>wsdl2java</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

CXF includes a Maven plugin called 'cxf-codegen-plugin' which can generate Java artifacts from a WSDL. In the above POM the 'wsdl2java' goal is configured to run in the generate-sources phase. By running the below Maven command, CXF will generate the Java artifacts. Each '<wsdlOption>' element corresponds to a WSDL that needs generated artifacts.
mvn generate-sources
In order to avoid a hard-coded absolute path towards the configured WSDL in the generated Java artifacts, specify a '<wsdlLocation>' element using the classpath reference as shown above.
After running the 'mvn generate-sources' command you should be able to find back a number of auto generated classes amongst which the HelloWorldPort interface as shown below. In the next sections we will use this interface to implement both the client and service.

java objects generated from wsdl

Creating the Requester (client)

Creating a CXF client using Spring is done by specifying a jaxws:client bean as shown in the below 'cxf-requester.xml'. In addition to a bean name, the service interface and the service address (or URL) need to be specified. The result of below configuration is a bean that will be created with the specified name, implementing the service interface and invoking the remote SOAP service under the covers.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns: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">

<jaxws:client id="helloworldRequesterBean"
serviceClass="info.source4code.services.helloworld.HelloWorldPortType"
address="${helloworld.address}">
</jaxws:client>

</beans>

For this example the CXF Spring configuration is kept in a separate file that is imported in the main 'context-requester.xml' shown below. In addition to this, annotation based configuration is enabled and a 'cxf.properties' file is loaded which contains the address at which the Hello World service was made available in the previous section.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

<!-- import the cxf requester configuration -->
<import resource="classpath:META-INF/spring/cxf-requester.xml" />

<!-- enables annotation based configuration -->
<context:annotation-config />
<!-- scans for annotated classes in the com.company package -->
<context:component-scan base-package="info.source4code.soap.http.cxf" />

<!-- allows for ${} replacement in a spring xml configuration from a
.properties file on the classpath -->
<context:property-placeholder location="classpath:cxf.properties" />

</beans>

The actual client code is specified in the HelloWorldClient class which exposes a sayHello() method that takes as input a Person object. The helloworldRequesterBean previously defined in the 'cxf-requester.xml' is auto wired using the corresponding annotation.

package info.source4code.soap.http.cxf;

import info.source4code.services.helloworld.Greeting;
import info.source4code.services.helloworld.HelloWorldPortType;
import info.source4code.services.helloworld.Person;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class HelloWorldClient {

private static final Logger LOGGER = LoggerFactory
.getLogger(HelloWorldClient.class);

@Autowired
private HelloWorldPortType helloworldRequesterBean;

public String sayHello(Person person) {
Greeting greeting = helloworldRequesterBean.sayHello(person);

String result = greeting.getText();
LOGGER.info("result={}", result);
return result;
}
}

Creating the Provider (service)

Creating a CXF service using Spring is done by specifying a jaxws:endpoint bean as shown in the below 'cxf-provider.xml'. In addition to a bean name, the implementer class name and the address at which the service will be published need to be specified. In other words the below configuration tells CXF how to route requests received by the servlet to the service-implementation code.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns: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">

<jaxws:endpoint id="helloWorldProviderBean"
implementor="info.source4code.soap.http.cxf.HelloWorldImpl"
address="/helloworld">
</jaxws:endpoint>

</beans>

For the provider, the CXF Spring configuration is kept in a separate file that is imported in the main 'context-provider.xml' shown below.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

<!-- import the cxf provider configuration -->
<import resource="classpath:META-INF/spring/cxf-provider.xml" />

</beans>

Java web applications use a deployment descriptor file to determine how URLs map to servlets and other information. This file is named 'web.xml', and resides in the app's WAR under the 'WEB-INF' directory. The below 'web.xml' defines the CXFServlet and maps all request on '/s4c/services/*' to this servlet.

In this example, a Spring context is used to load the previous defined CXF provider configuration. Spring can be integrated into any Java-based web framework by declaring the ContextLoaderListener and using a contextConfigLocation to set which context file(s) to load.
Note that if a specific 'contextConfigLocation' context parameter is not specified, CXF looks for a context with the file name 'cxf-servlet.xml'.
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">

<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:META-INF/spring/context-provider.xml</param-value>
</context-param>

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

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

<servlet-mapping>
<servlet-name>cxf</servlet-name>
<url-pattern>/s4c/services/*</url-pattern>
</servlet-mapping>

</web-app>

The HelloWorldImpl class contains the implementation of the Hello World service. It implements the sayHello() method of the HelloWorldPort interface which was automatically generated by the 'cxf-codegen-plugin'. The method takes a Person object as input and generates a Greeting object that is returned.

package info.source4code.soap.http.cxf;

import info.source4code.services.helloworld.Greeting;
import info.source4code.services.helloworld.HelloWorldPortType;
import info.source4code.services.helloworld.ObjectFactory;
import info.source4code.services.helloworld.Person;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HelloWorldImpl implements HelloWorldPortType {

private static final Logger LOGGER = LoggerFactory
.getLogger(HelloWorldImpl.class);

@Override
public Greeting sayHello(Person person) {

String firstName = person.getFirstName();
LOGGER.debug("firstName={}", firstName);
String lasttName = person.getLastName();
LOGGER.debug("lastName={}", lasttName);

ObjectFactory factory = new ObjectFactory();
Greeting response = factory.createGreeting();

String greeting = "Hello " + firstName + " " + lasttName + "!";
LOGGER.info("greeting={}", greeting);

response.setText(greeting);
return response;
}
}

Testing

In order to verify the correct working of our service implementation the below HelloWorldImplTest contains a unit test case that uses the JaxWsServerFactoryBean to easily create a server endpoint. The bean needs an endpoint address and the web service implementation class. When the create() method is called it starts an embedded standalone Jetty server.

SpringJUnit4ClassRunner is used in order to allow auto-wiring the HelloWorldClient bean. The actual test simply calls the sayHello() method of the client and then verifies the response.

package info.source4code.soap.http.cxf;

import static org.junit.Assert.assertEquals;
import info.source4code.services.helloworld.Person;

import org.apache.cxf.jaxws.JaxWsServerFactoryBean;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:/META-INF/spring/context-requester.xml" })
public class HelloWorldImplTest {

private static String ENDPOINT_ADDRESS = "http://localhost:9090/s4c/services/helloworld";

@Autowired
private HelloWorldClient clientBean;

@BeforeClass
public static void setUpBeforeClass() throws Exception {
// start the HelloWorld service using jaxWsServerFactoryBean
HelloWorldImpl implementor = new HelloWorldImpl();
JaxWsServerFactoryBean jaxWsServerFactoryBean = new JaxWsServerFactoryBean();
jaxWsServerFactoryBean.setAddress(ENDPOINT_ADDRESS);
jaxWsServerFactoryBean.setServiceBean(implementor);
jaxWsServerFactoryBean.create();
}

@Test
public void testSayHello() {

Person person = new Person();
person.setFirstName("Jane");
person.setLastName("Doe");

assertEquals("Hello Jane Doe!", clientBean.sayHello(person));
}
}

In addition to a unit test an HelloWorldImplIT integration test is defined for which an instance of Jetty will be started, using the 'jetty-maven-plugin', that will host the Hello World service. The test case looks identical to the previous one except that in this case no server endpoint needs to be created.
Note that by default, the Maven integration-test phase runs test classes named '**/IT*.java, **/*IT.java, and **/*ITCase.java'.

package info.source4code.soap.http.cxf;

import static org.junit.Assert.assertEquals;
import info.source4code.services.helloworld.Person;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:/META-INF/spring/context-requester.xml" })
public class HelloWorldImplIT {

@Autowired
private HelloWorldClient clientBean;

@Test
public void testSayHello() {

Person person = new Person();
person.setFirstName("John");
person.setLastName("Doe");

assertEquals("Hello John Doe!", clientBean.sayHello(person));
}
}

In order to run the above example open a command prompt and execute following Maven command:
mvn verify

Maven will download the needed dependencies, compile the code and run the unit test case. Subsequent Maven will start a Jetty server instance and run the integration test case. The result should be a successful build during which two log files are generated that contain the logs of the requester 'jaxws-jetty-cxf-helloworld-test.log' and provider 'jaxws-jetty-cxf-helloworld.log'.

-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running info.source4code.soap.http.cxf.HelloWorldImplTest
20:10:55.878 INFO [main][ReflectionServiceFactoryBean] Creating Service {http://cxf.http.soap.source4code.info/}HelloWorldImplService from class info.source4code.services.helloworld.HelloWorldPortType
20:10:56.339 INFO [main][ServerImpl] Setting the server's publish address to be http://localhost:9090/s4c/services/helloworld
20:10:56.748 INFO [main][ReflectionServiceFactoryBean] Creating Service {http://source4code.info/services/helloworld}HelloWorldPortTypeService from class info.source4code.services.helloworld.HelloWorldPortType
20:10:56.972 DEBUG [qtp1531692262-13][HelloWorldImpl] firstName=Jane
20:10:56.973 DEBUG [qtp1531692262-13][HelloWorldImpl] lastName=Doe
20:10:56.973 INFO [qtp1531692262-13][HelloWorldImpl] greeting=Hello Jane Doe!
20:10:56.986 INFO [main][HelloWorldClient] result=Hello Jane Doe!
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.623 sec

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

[INFO]
[INFO] --- maven-war-plugin:2.2:war (default-war) @ jaxws-jetty-cxf-helloworld ---
[INFO] Packaging webapp
[INFO] Assembling webapp [jaxws-jetty-cxf-helloworld] in [D:\source4code\code\jaxws-jetty-cxf-helloworld\target\jaxws-jetty-cxf-helloworld-1.0]
[INFO] Processing war project
[INFO] Copying webapp resources [D:\source4code\code\jaxws-jetty-cxf-helloworld\src\main\webapp]
[INFO] Webapp assembled in [257 msecs]
[INFO] Building war: D:\source4code\code\jaxws-jetty-cxf-helloworld\target\jaxws-jetty-cxf-helloworld-1.0.war
[INFO] WEB-INF\web.xml already added, skipping
[INFO]
[INFO] >>> jetty-maven-plugin:8.1.16.v20140903:start (start-jetty) > validate @ jaxws-jetty-cxf-helloworld >>>
[INFO]
[INFO] <<< jetty-maven-plugin:8.1.16.v20140903:start (start-jetty) < validate @ jaxws-jetty-cxf-helloworld <<<
[INFO]
[INFO] --- jetty-maven-plugin:8.1.16.v20140903:start (start-jetty) @ jaxws-jetty-cxf-helloworld ---
[INFO] Configuring Jetty for project: JAX-WS - CXF contract first Hello World web service using Jetty and Maven
[INFO] webAppSourceDirectory not set. Defaulting to D:\source4code\code\jaxws-jetty-cxf-helloworld\src\main\webapp
[INFO] Reload Mechanic: automatic
[INFO] Classes = D:\source4code\code\jaxws-jetty-cxf-helloworld\target\classes
[INFO] Context path = /
[INFO] Tmp directory = D:\source4code\code\jaxws-jetty-cxf-helloworld\target\tmp
[INFO] Web defaults = org/eclipse/jetty/webapp/webdefault.xml
[INFO] Web overrides = none
[INFO] web.xml file = file:/D:/source4code/code/jaxws-jetty-cxf-helloworld/src/main/webapp/WEB-INF/web.xml
[INFO] Webapp directory = D:\source4code\code\jaxws-jetty-cxf-helloworld\src\main\webapp
2015-01-11 20:10:58.622:INFO:oejs.Server:jetty-8.1.16.v20140903
2015-01-11 20:10:59.069:INFO:oejpw.PlusConfiguration:No Transaction manager found - if your webapp requires one, please configure one.
2015-01-11 20:11:00.728:INFO:/:No Spring WebApplicationInitializer types detected on classpath
2015-01-11 20:11:01.027:INFO:/:Initializing Spring root WebApplicationContext
2015-01-11 20:11:01.804:WARN:oejsh.RequestLogHandler:!RequestLog
2015-01-11 20:11:01.818:INFO:oejs.AbstractConnector:Started SelectChannelConnector@0.0.0.0:9090
[INFO] Started Jetty Server
[INFO]
[INFO] --- maven-failsafe-plugin:2.17:integration-test (default) @ jaxws-jetty-cxf-helloworld ---
[INFO] Failsafe report directory: D:\source4code\code\jaxws-jetty-cxf-helloworld\target\failsafe-reports

-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running info.source4code.soap.http.cxf.HelloWorldImplIT
20:11:02.969 INFO [main][ReflectionServiceFactoryBean] Creating Service {http://source4code.info/services/helloworld}HelloWorldPortTypeService from class info.source4code.services.helloworld.HelloWorldPortType
20:11:03.790 INFO [main][HelloWorldClient] result=Hello John Doe!
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.676 sec - in info.source4code.soap.http.cxf.HelloWorldImplIT

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

[WARNING] File encoding has not been set, using platform encoding Cp1252, i.e. build is platform dependent!
[INFO]
[INFO] --- jetty-maven-plugin:8.1.16.v20140903:stop (stop-jetty) @ jaxws-jetty-cxf-helloworld ---
[INFO]
[INFO] --- maven-failsafe-plugin:2.17:verify (default) @ jaxws-jetty-cxf-helloworld ---
[INFO] Failsafe report directory: D:\source4code\code\jaxws-jetty-cxf-helloworld\target\failsafe-reports
[WARNING] File encoding has not been set, using platform encoding Cp1252, i.e. build is platform dependent!
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 10.410 s
[INFO] Finished at: 2015-01-11T20:11:03+01:00
[INFO] Final Memory: 17M/238M
[INFO] ------------------------------------------------------------------------


github icon
If you would like to run the above code sample you can download the full source code and their corresponding JUnit and integration test cases here.

This concludes the CXF contract first Hello World web service example. If you found this post helpful or have any questions or remarks, please leave a comment.