Showing posts with label Code Sample. Show all posts
Showing posts with label Code Sample. 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, November 12, 2014

EasyMock - unit testing FacesContext using PowerMock, JUnit and Maven

easymock logo
JSF defines the FacesContext abstract base class for representing all of the contextual information associated with processing an incoming request, and creating the corresponding response. When writing unit test cases for a JSF application there might be a need to mock some of FacesContext static methods. The following post will illustrate how to do this using PowerMock, which is a framework that allows you to extend mock libraries like EasyMock with extra capabilities. In this case the capability to mock the static methods of FacesContext.

Tools used:
  • JUnit 4.11
  • EasyMock 3.2
  • PowerMock 1.5
  • Maven 3

The code sample is built and run using Maven. Specified below is the Maven POM file which contains the needed dependencies for JUnit, EasyMock and PowerMock. In addition the PowerMock support module for JUnit ('powermock-module-junit4') and the PowerMock API for EasyMock ('powermock-api-easymock') dependencies need to be added as specified here.

As the FacesContext class is used in this code sample, dependencies to the EL (Expression Language) API and JSF specification API are also included.

Note that the version of JUnit is not the latest as there seems to be a bug where PowerMock doesn't recognize the correct JUnit version when using JUnit 4.12.

<?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>easymock-powermock-facescontext</artifactId>
<version>1.0</version>
<packaging>jar</packaging>

<name>EasyMock - Mocking FacesContext using PowerMock</name>
<url>http://www.source4code.info/2014/11/easymock-mocking-facescontext-using.html</url>

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

<junit.version>4.11</junit.version>
<easymock.version>3.2</easymock.version>
<powermock.version>1.5.6</powermock.version>

<el.version>2.2.1-b04</el.version>
<jsf.version>2.2.8-02</jsf.version>

<maven-compiler-plugin.version>3.1</maven-compiler-plugin.version>
</properties>

<dependencies>
<!-- JUnit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<!-- EasyMock -->
<dependency>
<groupId>org.easymock</groupId>
<artifactId>easymock</artifactId>
<version>${easymock.version}</version>
<scope>test</scope>
</dependency>
<!-- PowerMock -->
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-easymock</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
<!-- EL (Unified Expression Language) -->
<dependency>
<groupId>javax.el</groupId>
<artifactId>el-api</artifactId>
<version>${el.version}</version>
<scope>test</scope>
</dependency>
<!-- JSF -->
<dependency>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-api</artifactId>
<version>${jsf.version}</version>
</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>
</plugins>
</build>
</project>

The SomeBean class below contains two methods that make use of FacesContext. The first addMessage() method will create a new FacesMessage and add it to the FacesContext. The second logout() method will invalidate the current session.

package info.source4code.mockito;

import javax.faces.application.FacesMessage;
import javax.faces.application.FacesMessage.Severity;
import javax.faces.bean.ManagedBean;
import javax.faces.context.FacesContext;

@ManagedBean
@SessionScoped
public class SomeBean {

public void addMessage(Severity severity, String summary, String detail) {
FacesContext.getCurrentInstance().addMessage(null,
new FacesMessage(severity, summary, detail));
}

public String logout() {
FacesContext.getCurrentInstance().getExternalContext()
.invalidateSession();

return "logout?faces-redirect=true";
}
}

Next is the SomeBeanTest JUnit test class. The class is annotated using two annotations. The first @RunWith annotation tells JUnit to run the test using PowerMockRunner. The second @PrepareForTest annotation tells PowerMock to prepare to mock the FacesContext class. If there are multiple classes to be prepared for mocking, they can be specified using a comma separated list.

In the setup() method a number of objects are specified that are similar for the two test cases. The mockStatic() method is called in order to tell PowerMock to mock all static methods of the given FacesContext class. In addition the FacesContext and ExternalContext mock objects are created.

Next are the two test cases which follow the basic EasyMock testing steps:
StepAction
1Call expect(mock.[method call]).andReturn([result]) for each expected call
2Call mock.[method call], then EasyMock.expectLastCall() for each expected void call
3Call replay(mock) to switch from “record” mode to “playback” mode
4Call the test method
5Call verify(mock) to assure that all expected calls happened

In addition to this, the first addMessage() test case uses the Capture capability of EasyMock in order to test whether a FacesMessage with the correct values was added to the FacesContext. The second testLogout() test case checks if the correct redirect was returned.

package info.source4code.easymock;

import static org.easymock.EasyMock.capture;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.expectLastCall;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;

import javax.faces.application.FacesMessage;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;

import org.easymock.Capture;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.easymock.PowerMock;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

@RunWith(PowerMockRunner.class)
@PrepareForTest({ FacesContext.class })
public class SomeBeanTest {

private SomeBean someBean;

private FacesContext facesContext;
private ExternalContext externalContext;

@Before
public void setUp() throws Exception {
someBean = new SomeBean();

// mock all static methods of FacesContext using PowerMockito
PowerMock.mockStatic(FacesContext.class);

facesContext = createMock(FacesContext.class);
externalContext = createMock(ExternalContext.class);
}

@Test
public void testAddMessage() {
// create Capture instances for the clientId and FacesMessage parameters
// that will be added to the FacesContext
Capture<String> clientIdCapture = new Capture<String>();
Capture<FacesMessage> facesMessageCapture = new Capture<FacesMessage>();

expect(FacesContext.getCurrentInstance()).andReturn(facesContext)
.once();
// expect the call to the addMessage() method and capture the arguments
facesContext.addMessage(capture(clientIdCapture),
capture(facesMessageCapture));
expectLastCall().once();

// replay the class (not the instance)
PowerMock.replay(FacesContext.class);
replay(facesContext);

someBean.addMessage(FacesMessage.SEVERITY_ERROR, "error",
"something went wrong");

// verify the class (not the instance)
PowerMock.verify(FacesContext.class);
verify(facesContext);

// check the value of the clientId that was passed
assertNull(clientIdCapture.getValue());

// retrieve the captured FacesMessage
FacesMessage captured = facesMessageCapture.getValue();
// check if the captured FacesMessage contains the expected values
assertEquals(FacesMessage.SEVERITY_ERROR, captured.getSeverity());
assertEquals("error", captured.getSummary());
assertEquals("something went wrong", captured.getDetail());
}

@Test
public void testLogout() {
expect(FacesContext.getCurrentInstance()).andReturn(facesContext)
.once();
expect(facesContext.getExternalContext()).andReturn(externalContext)
.once();
// expect the call to the invalidateSession() method
externalContext.invalidateSession();
expectLastCall().once();

// replay the class (not the instance)
PowerMock.replay(FacesContext.class);
replay(facesContext);
replay(externalContext);

assertEquals("logout?faces-redirect=true", someBean.logout());

// verify the class (not the instance)
PowerMock.verify(FacesContext.class);
verify(facesContext);
verify(externalContext);
}
}

In order to run the above test cases, open a command prompt and execute following Maven command:

mvn test


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

This concludes the mocking FacesContext using EasyMock and PowerMock example. If you found this post helpful or have any questions or remarks, please leave a comment.

Monday, November 10, 2014

Mockito - unit testing FacesContext using PowerMock, JUnit and Maven

mockito logo
JSF defines the FacesContext abstract base class for representing all of the contextual information associated with processing an incoming request, and creating the corresponding response. When writing unit test cases for a JSF application there might be a need to mock some of the FacesContext static methods. The following post will illustrate how to do this using PowerMock, a framework that allows you to extend mock libraries like Mockito with extra capabilities. In this case the capability to mock the static methods of FacesContext.

Tools used:
  • JUnit 4.11
  • Mockito 1.10
  • PowerMock 1.5
  • Maven 3

The code sample is built and run using Maven. Specified below is the Maven POM file which contains the needed dependencies for JUnit, Mockito and PowerMock. In addition the PowerMock support module for JUnit ('powermock-module-junit4') and the PowerMock API for Mockito ('powermock-api-mockito') dependencies need to be added as specified here.

As the FacesContext class is used in this code sample, dependencies to the EL (Expression Language) API and JSF specification API are also included.

Note that the version of JUnit is not the latest as there seems to be a bug where PowerMock doesn't recognize the correct JUnit version when using JUnit 4.12.
<?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>mockito-powermock-facescontext</artifactId>
<version>1.0</version>
<packaging>jar</packaging>

<name>Mockito - Mocking FacesContext using PowerMock</name>
<url>http://www.source4code.info/2014/11/mockito-mocking-facescontext-using.html</url>

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

<junit.version>4.11</junit.version>
<mockito.version>1.10.8</mockito.version>
<powermock.version>1.5.6</powermock.version>

<el.version>2.2.1-b04</el.version>
<jsf.version>2.2.8-02</jsf.version>

<maven-compiler-plugin.version>3.1</maven-compiler-plugin.version>
</properties>

<dependencies>
<!-- JUnit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<!-- Mockito -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<!-- PowerMock -->
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
<!-- EL (Unified Expression Language) -->
<dependency>
<groupId>javax.el</groupId>
<artifactId>el-api</artifactId>
<version>${el.version}</version>
<scope>test</scope>
</dependency>
<!-- JSF -->
<dependency>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-api</artifactId>
<version>${jsf.version}</version>
</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>
</plugins>
</build>
</project>

The SomeBean class below contains two methods that make use of FacesContext. The first addMessage() method will create a new FacesMessage and add it to the FacesContext. The second logout() method will invalidate the current session.
package info.source4code.mockito;

import javax.faces.application.FacesMessage;
import javax.faces.application.FacesMessage.Severity;
import javax.faces.bean.ManagedBean;
import javax.faces.context.FacesContext;

@ManagedBean
@SessionScoped
public class SomeBean {

public void addMessage(Severity severity, String summary, String detail) {
FacesContext.getCurrentInstance().addMessage(null,
new FacesMessage(severity, summary, detail));
}

public String logout() {
FacesContext.getCurrentInstance().getExternalContext()
.invalidateSession();

return "logout?faces-redirect=true";
}
}

The SomeBeanTest JUnit test class is used to test the above. The class is annotated using two annotations. The first @RunWith annotation tells JUnit to run the test using PowerMockRunner. The second @PrepareForTest annotation tells PowerMock to prepare to mock the FacesContext class. If there are multiple classes to be prepared for mocking, they can be specified using a comma separated list.

Mockito provides the @Mock annotation which is a shorthand for mocks creation. In the below test class it is used to create the FacesContext and ExternalContext mocks. Note that the previous @RunWith(PowerMockRunner.class) annotation will take care of initializing fields annotated with Mockito annotations

In the setup() method a number of objects are specified that are similar for the two test cases. The mockStatic() method is called in order to tell PowerMock to mock all static methods of the given FacesContext class. We then use the when() method to specify what instance to return in case the getCurrentInstance() method is called on FacesContext. The same is done for the getExternalContext() method.
Note that because of the 'org.mockito.Mockito.when' import there is no Mockito class name in front of the static when() method.
The first addMessage() test case uses the ArgumentCaptor capability of Mockito in order to test whether a FacesMessage with the correct values was added to the FacesContext. The second testLogout() test case checks if the correct redirect was returned.
package info.source4code.mockito;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import javax.faces.application.FacesMessage;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

@RunWith(PowerMockRunner.class)
@PrepareForTest({ FacesContext.class })
public class SomeBeanTest {

private SomeBean someBean;

@Mock
private FacesContext facesContext;
@Mock
private ExternalContext externalContext;

@Before
public void setUp() throws Exception {
someBean = new SomeBean();

// mock all static methods of FacesContext using PowerMockito
PowerMockito.mockStatic(FacesContext.class);

when(FacesContext.getCurrentInstance()).thenReturn(facesContext);
when(facesContext.getExternalContext()).thenReturn(externalContext);
}

@Test
public void testAddMessage() {
// create Captor instances for the clientId and FacesMessage parameters
// that will be added to the FacesContext
ArgumentCaptor<String> clientIdCaptor = ArgumentCaptor
.forClass(String.class);
ArgumentCaptor<FacesMessage> facesMessageCaptor = ArgumentCaptor
.forClass(FacesMessage.class);

// run the addMessage() method to be tested
someBean.addMessage(FacesMessage.SEVERITY_ERROR, "error",
"something went wrong");

// verify if the call to addMessage() was made and capture the arguments
verify(facesContext).addMessage(clientIdCaptor.capture(),
facesMessageCaptor.capture());

// check the value of the clientId that was passed
assertNull(clientIdCaptor.getValue());

// retrieve the captured FacesMessage
FacesMessage captured = facesMessageCaptor.getValue();
// check if the captured FacesMessage contains the expected values
assertEquals(FacesMessage.SEVERITY_ERROR, captured.getSeverity());
assertEquals("error", captured.getSummary());
assertEquals("something went wrong", captured.getDetail());
}

@Test
public void testLogout() {
assertEquals("logout?faces-redirect=true", someBean.logout());
}
}

In order to run the above test cases, open a command prompt and execute following Maven command:
mvn test


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

This concludes the mocking FacesContext using Mockito and PowerMock example. If you found this post helpful or have any questions or remarks, please leave a comment.

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.