Logging SOAP client messages from CXF in Spring Boot

Since getting this to work was more troublesome that it should have been, here are the details.

First, the following dependencies will be required (I am using 2.7.18):

<dependency>
	<groupId>org.apache.cxf</groupId>
	<artifactId>cxf-api</artifactId>
	<version>${cxf.runtime.version}</version>
</dependency>
<dependency>
	<groupId>org.apache.cxf</groupId>
	<artifactId>cxf-rt-transports-http</artifactId>
	<version>${cxf.runtime.version}</version>
</dependency>
<dependency>
	<groupId>org.apache.cxf</groupId>
	<artifactId>cxf-rt-frontend-jaxws</artifactId>
	<version>${cxf.runtime.version}</version>
</dependency>

Next, we need a Spring configuration class that sets up the required CXF classes:


@Configuration
@ImportResource({ "classpath:META-INF/cxf/cxf.xml", "classpath:META-INF/cxf/cxf-extension-soap.xml" })
public class CxfConfig {

	@Autowired
	private SpringBus springBus;

	@PostConstruct
	public void activateLoggingFeature() {
           springBus.getInInterceptors().add(logInInterceptor());
    	   springBus.getInFaultInterceptors().add(logInInterceptor());
    	   springBus.getOutInterceptors().add(logOutInterceptor());
    	   springBus.getOutFaultInterceptors().add(logOutInterceptor());
	}

	@Bean
	public LoggingFeature loggingFeature() {
    	   LoggingFeature logFeature = new LoggingFeature();
    	   logFeature.setPrettyLogging(true);
    	   logFeature.initialize(springBus);
    	   springBus.getFeatures().add(logFeature);
    	   return logFeature;
	}

	public AbstractLoggingInterceptor logInInterceptor() {
	    return new LoggingInInterceptor();
	}

	public AbstractLoggingInterceptor logOutInterceptor() {
		LoggingOutInterceptor logOutInterceptor = new LoggingOutInterceptor();
		logOutInterceptor.setPrettyLogging(true);
		return logOutInterceptor;
	}

}

Finally, we need to add CXF components to the logging framework config (e.g. logback.xml)

...
   <logger name="org.apache.cxf" level="INFO"/>
...
---------------------------
ID: 12
Address: ... something ....
Encoding: UTF-8
Http-Method: POST
Content-Type: text/xml
Headers: {Accept=[*/*], SOAPAction=[""]}
Payload: <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>

     ... data here ....

  </soap:Body>
</soap:Envelope>

--------------------------------------

Reactor Pattern and its relation to Observer

The semantics of RxJava refer to the Observer pattern.  What exactly is the relation?

Here’a nice simple example from the Couchbase documentation:

Observable
    .just("doc1", "doc2", "doc3";)
    .flatMap(bucket::get)
    .subscribe(document -> System.out.println("Got: " + document));

You begin with any kind of stream, for which you want map each member to something new.  Syntactically, this is just the same as the Java 8 streaming API, which also has map and flatMap, and also allows a fork/join. Whilst in Java, the stream functions over a collection, in Rx, the stream is designed to handle events occurring over time, terminating on the receipt of a completion event.

This requires that mapping items (in the example, calling bucket.get for each item) in the stream will return Observables, rather than results or values.  The point of this is that we can add one or more subscribers that will get notified when the result of each element mapping is complete. A Reactor framework will typically allow subscribers for specific observable events – success and failure, and completion of the event stream, etc. These can be handled according to the type of Observable (HTTP request, TCP request, database call etc.).

This is an implementation of the Observer pattern, where observers (subscribers) register for specific event notifications. It is particularly relevant for microservices, where intermediate results are to be collected and then assembled into a final result.

Unable to find contextual data of type: javax.servlet.ServletContext

The combination of Spring Boot, RestEasy and Swagger 2 is not exactly a happy combination. Both Spring Boot and Swagger seems to prefer Jersey to RestEasy.  However, in my case as in others, RestEasy is mandated by factors outside my control.

Starting with Andy Wilkinson’s Spring Boot RestEasy (https://github.com/wilkinsona/spring-boot-resteasy), and then adding the latest Swagger 2 libraries (io.swagger), I was plagued by the error: Unable to find contextual data of type: javax.servlet.ServletContext

It seems the new io.swagger.jaxrs.listing.ApiListingResource causes RestEasy 3.0.9 to require a ThreadLocal reference to ServletContext which basically is not there, hence the error, which does not occur with the older com.wordnik.swagger.jaxrs.listing.ApiListingResource.

To get around this, I basically had to supply RestEasy with a thread local ServletContext myself, using a Jax-rs ContainerRequestFilter. The full project source is here: https://github.com/aosullivan/springboot-resteasy-swagger

The key Filter is as follows:

@ConditionalOnWebApplication
@Component
@Provider
@ServerInterceptor
public class ResteasyServletContext implements ContainerRequestFilter {

    @Inject 
    ServletContext sc;
    
    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {
		ResteasyProviderFactory.getContextDataMap().put(ServletContext.class, sc);
    }

}

Following this, everything seemed to work, and I could bring up swagger-ui and access the Jax-rs endpoints through it.

This condition is needed to support the spring SpringJUnit4ClassRunner, if you use that, or other contexts where the web container is not actually instantiated:

@ConditionalOnWebApplication

I get CORS issues when accessing swagger-ui from Chrome, which I can get around using the Chrome CORS plugin, https://chrome.google.com/webstore/detail/allow-control-allow-origi/nlfbmbojpeacfghkpbjhddihlkkiljbi?hl=en.  This didn’t happen under Jersey.

Configuring DateFormat with Spring Boot and RestEasy

This is so finely tuned, I pity the fool who has to make it work (me).

A couple of things to check. First, ensure you Application (with main method) is in a package a level above components to be autowired, especially the @Producer. Also, ensure it has these annotations:

@ComponentScan
@SpringBootApplication

Second, create a configuration type which is both a @Provider and a @Component.

Ensure that class imports of configuration classes such as ObjectMap are only from the package com.fasterxml.jackson. Don’t import anything from Jackson1, org.codehaus.jackson. Don’t mix Jackson1 and 2 and keep 1 out of the implementation.

I’m including the imports so there is no ambiguity about this.

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.joda.JodaMapper;

import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;

import org.springframework.stereotype.Component;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.TimeZone;

@Provider
@Component
public class DateObjectMapperProvider implements ContextResolver<ObjectMapper> {
    private DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");

    @Override
    public ObjectMapper getContext(Class<?> type) {
        dateFormat.setLenient(false);
        dateFormat.setTimeZone(TimeZone.getTimeZone("PST"));
        
        final ObjectMapper result = new JodaMapper();
        result.setDateFormat(dateFormat);
        return result;
    }

}

Maven dependencies, including Swagger. I have not included Spring.


<swagger.version>1.3.12</swagger.version>
<resteasy.version>3.0.9.Final</resteasy.version>
...
                         <dependency>
				<groupId>org.jboss.resteasy</groupId>
				<artifactId>resteasy-spring</artifactId>
				<version>${resteasy.version}</version>
			</dependency>
			<dependency>
				<groupId>org.jboss.resteasy</groupId>
				<artifactId>resteasy-jackson2-provider</artifactId>
				<version>${resteasy.version}</version>
			</dependency>
			<dependency>
				<groupId>org.jboss.resteasy</groupId>
				<artifactId>resteasy-validator-provider-11</artifactId>
				<version>${resteasy.version}</version>
			</dependency>
			<dependency>
				<groupId>org.jboss.resteasy</groupId>
				<artifactId>resteasy-multipart-provider</artifactId>
				<version>${resteasy.version}</version>
			</dependency>
			<dependency>
				<groupId>org.jboss.resteasy</groupId>
				<artifactId>resteasy-jaxb-provider</artifactId>
				<version>${resteasy.version}</version>
			</dependency>

			<dependency>
				<groupId>com.wordnik</groupId>
				<artifactId>swagger-jaxrs_2.10</artifactId>
				<version>${swagger.version}</version>
			</dependency>
			<dependency>
				<groupId>com.wordnik</groupId>
				<artifactId>swagger-core_2.10</artifactId>
				<version>${swagger.version}</version>
			</dependency>
			
			<dependency>
				<groupId>com.mangofactory</groupId>
				<artifactId>swagger-springmvc</artifactId>
				<version>1.0.2</version>
			</dependency>
	
			<dependency>
				<groupId>org.ajar</groupId>
				<artifactId>swagger-spring-mvc-ui</artifactId>
				<version>0.4</version>
			</dependency>			

		<dependency>
		<groupId>com.fasterxml.jackson.datatype</groupId>
			<artifactId>jackson-datatype-joda</artifactId>
		</dependency>
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-databind</artifactId>
		</dependency>

Set it up this way and you should be golden.

Concise List Creation in Java

I came across some interesting code today to set up a list of objects in Java that I thought was worth highlighting for its conciseness.

List<ServerAddress> list = new ArrayList<>() {{
        add(new ServerAddress("127.0.0.1", 27017));
        add(new ServerAddress("127.0.0.1", 27027));
        add(new ServerAddress("127.0.0.1", 27037));
    }
}

Essentially we subclass a new list instance as an anonymous inner class with an initializer block (since, being anonymous, it cannot have a constructor). In that initializer, we call add on the list for each item we wish to add. I thought this was a good, concise way of creating the populated list, allowing fluent API style without the need for external libraries like op4j.

Ant, Sonar and Jacoco Working Example

Much to my chagrin, legacy code exists. Sometimes, not infrequently enough, it is built using an Ant script. The following is an anonymized example of a simple Ant project with unit tests running a Sonar target.

The project structure is simple:

/src
/test
build.xml
ivy.xml

Classpaths (dependencies) are managed through Ivy. The script will download and install Ivy under your home folder if required. Also, regarding Ivy:

  • use Ant’s <setproxy> if the process hangs while attempting to download Ivy or any of the dependencies
  • add an Ivy plugin to your favourite IDE if you are managing your code there
  • specify your local binary artifact repo, if you have one, using ivysettings.xml

If you don’t want to use Ivy, just replace the classpathref values below with whatever you use for your classpaths.

The most difficult part was getting Sonar to pick up the unit test metrics. The following message can generally be ignored; it shows up even when all the unit test analysis has been successful and I wasted a fair amount of time googling information on it:

No information about coverage per test.

The integration between jacoco and the Sonar plugin is not well documented and is pretty tricksy, but here are the essential points:

  • jacoco:coverage generates coverage metrics and stores them in a binary file specified by the destfile attribute (typically called ‘jacoco.exec’). The Sonar plugin then looks for this file using the property sonar.jacoco.reportPath
  • jacoco:coverage writes test execution data (total number of tests, execution times etc.) NOT to the jacoco.exec binary but to separate XML files. It uses the same format as Maven’s Surefire plugin which ensures they will work with Sonar. The files are created ONLY if you include the node <formatter type="xml"/>. The files are written to the folder specified by the batchtest ‘todir’ attribute. The Sonar plugin looks for these using the property sonar.junit.reportsPath.
  • jacoco:report generates coverage HTML and XML reports but these are not actually used by the Sonar plugin. In the below example, I am using this task to generate an HTML report but, to repeat, this is not needed by the Sonar plugin.

With all that said, here is the full sample build.xml:

<project basedir="." xmlns:sonar="antlib:org.sonar.ant" xmlns:ivy="antlib:org.apache.ivy.ant">

	<property name="src.dir" value="src" />
	<property name="test.src.dir" value="test" />
	<property name="build.dir" value="build" />
	<property name="classes.dir" value="${build.dir}/classes" />
	<property name="test.classes.dir" value="${build.dir}/test-classes" />
	<property name="reports.dir" value="${build.dir}/reports" />

        <!-- Sonar connection details here-->

	<property name="sonar.projectKey" value="org.adrian:hello" />
	<property name="sonar.projectVersion" value="0.0.1-SNAPSHOT" />
	<property name="sonar.projectName" value="Adrian Hello" />

	<property name="sonar.sources" value="${src.dir}" />
	<property name="sonar.binaries" value="${classes.dir}" />
	<property name="sonar.tests" value="${test.src.dir}" />

	<property name="sonar.junit.reportsPath" value="${reports.dir}/junit" />
	<property name="sonar.dynamicAnalysis" value="reuseReports" />
	<property name="sonar.java.coveragePlugin" value="jacoco" />
	<property name="sonar.jacoco.reportPath" value="${build.dir}/jacoco.exec" />

	<target name="clean" description="Cleanup build files">
		<delete dir="${build.dir}"/>
	</target>

	<target name="ivy-check">
		<available file="${user.home}/.ant/lib/ivy.jar" property="ivy.isInstalled"/>
	</target>

	<target name="bootstrap" description="Install ivy" depends="ivy-check" unless="ivy.isInstalled">
		<mkdir dir="${user.home}/.ant/lib"/>
		<get dest="${user.home}/.ant/lib/ivy.jar" src="http://search.maven.org/remotecontent?filepath=org/apache/ivy/ivy/2.3.0/ivy-2.3.0.jar"/>
	</target>

	<target name="resolve" depends="bootstrap" description="Download dependencies and setup classpaths">
		<ivy:resolve/>
		<ivy:report todir='${reports.dir}/ivy' graph='false' xml='false'/>

		<ivy:cachepath pathid="compile.path" conf="compile"/>
		<ivy:cachepath pathid="test.path"    conf="test"/>
		<ivy:cachepath pathid="build.path"   conf="build"/>
	</target>

	<target name="init" depends="resolve" description="Create build directories">
		<mkdir dir="${classes.dir}"/>
		<mkdir dir="${test.classes.dir}"/>
		<mkdir dir="${reports.dir}/"/>
		<mkdir dir="${reports.dir}/junit"/>
	</target>

	<target name="compile" depends="init" description="Compile source code">
		<javac srcdir="${src.dir}" destdir="${classes.dir}" includeantruntime="false" debug="true" classpathref="compile.path"/>
	</target>

	<target name="compile-tests" depends="compile" description="Compile test source code">
		<javac srcdir="${test.src.dir}" destdir="${test.classes.dir}" includeantruntime="false" debug="true">
			<classpath>
				<path refid="test.path"/>
				<pathelement path="${classes.dir}"/>
			</classpath>
		</javac>
	</target>
		
	<target name="junit" depends="compile-tests" description="Run unit tests and code coverage reporting">
		<taskdef uri="antlib:org.jacoco.ant" resource="org/jacoco/ant/antlib.xml" classpathref="build.path"/>
		<jacoco:coverage destfile="${build.dir}/jacoco.exec" xmlns:jacoco="antlib:org.jacoco.ant">
			<junit haltonfailure="no" fork="true" forkmode="once">
				<classpath>
					<path refid="test.path"/>
					<pathelement path="${classes.dir}"/>
					<pathelement path="${test.classes.dir}"/>
				</classpath>
				<formatter type="xml"/>
				<batchtest todir="${reports.dir}/junit">
					<fileset dir="${test.src.dir}">
						<include name="**/*Test*.java"/>
					</fileset>
				</batchtest>
			</junit>
		</jacoco:coverage>
	</target>
		
	<target name="test-report" depends="junit"> 
		<taskdef uri="antlib:org.jacoco.ant" resource="org/jacoco/ant/antlib.xml"  classpathref="build.path"/>
		<jacoco:report xmlns:jacoco="antlib:org.jacoco.ant">
			<executiondata>
				<file file="${build.dir}/jacoco.exec" />
			</executiondata>

			<structure name="JaCoCo Ant Example">
				<classfiles>
					<fileset dir="${classes.dir}" />
				</classfiles>
				<sourcefiles encoding="UTF-8">
					<fileset dir="${src.dir}" />
				</sourcefiles>
			</structure>
			
			<html destdir="${reports.dir}" />
		</jacoco:report>
	</target>

	<target name="sonar" depends="test-report" description="Upload metrics to Sonar">
		<taskdef uri="antlib:org.sonar.ant" resource="org/sonar/ant/antlib.xml" classpathref="build.path"/>

		<ivy:cachepath pathid="sonar.libraries" conf="compile"/>

		<sonar:sonar xmlns:sonar="antlib:org.sonar.ant"/>
	</target>

	<target name="clean-all" depends="clean" description="Additionally purge ivy cache">
		<ivy:cleancache/>
	</target>
</project>

Here is the ivy.xml file, specifying compilation, test (i.e. junit) and ‘build’ (i.e. jacoco and sonar) dependencies:

<ivy-module version="2.0">
    <info organisation="org.adrian" module="demo"/>

    <configurations defaultconfmapping="compile->default">
        <conf name="compile" description="Required to compile application"/>
        <conf name="test"    description="Required for test only" extends="compile"/>
        <conf name="build"   description="Build dependencies"/>
    </configurations>

    <dependencies>
        <!-- compile dependencies -->
        <-- set up your classpath here>

        <!-- test dependencies -->
        <dependency org="junit" name="junit" rev="4.11" conf="test->default"/>

        <!-- build dependencies -->
        <dependency org="org.codehaus.sonar-plugins" name="sonar-ant-task" rev="2.2" conf="build->default"/>
        <dependency org="org.jacoco" name="org.jacoco.ant" rev="0.7.2.201409121644" conf="build->default"/> 

        <!-- Global exclusions -->
        <exclude org="org.apache.ant"/>
    </dependencies>
</ivy-module>

In production code I would move most of the Ant properties to an external build.properties file, but here I have bundled it all together.