Popular searches
//

Spring Boot & Apache CXF – SOAP on steroids fueled by cxf-spring-boot-starter

11.10.2016 | 13 minutes reading time

You haven´t read any of this blog series’ articles yet? Perfectly fine – because the best is yet to come. We´ll now combine Spring Boot and Apache CXF in their own spring-boot-starter. By doing so, we’ll have our SOAP endpoints up and running even faster, while at the same time leveraging the power of all introduced features!

Spring Boot & Apache CXF – Tutorial

Part 1: Spring Boot & Apache CXF – How to SOAP in 2016
Part 2: Spring Boot & Apache CXF – Testing SOAP web services
Part 3: Spring Boot & Apache CXF – XML validation and custom SOAP faults
Part 4: Spring Boot & Apache CXF – Logging & Monitoring with Logback, Elasticsearch, Logstash & Kibana
Part 5: Spring Boot & Apache CXF – SOAP on steroids fueled by cxf-spring-boot-starter

In the preceding parts we learned a lot about how to work with Spring Boot and Apache CXF. There´s only one problem left: With every new SOAP endpoint we start from the first blog post´s steps and have to walk through all of them every time. We now have all those current technologies, but this is not feeling right and we somehow contravene DRY . But luckily enough, the Spring Boot guys also thought about this problem. Because it is possible to build a custom spring-boot-starter which brings in everything necessary for a special use case.

But wait! Isn´t there an official Apache CXF spring-boot-starter?

In the meantime the Apache CXF developers also became aware the absence of their own spring-boot-starter and just released one (thanks again for pointing that out Stéphane Nicoll 🙂 ). If one takes a deeper look at this spring-boot-starter, it soon becomes clear that it´s only focused on Apache CXF. Which is absolutely fine! But sadly for us this means that the spring-boot-starter just initializes Apache CXF. And that´s it. As described in the first article of this blog post series, this concerns the following code we can spare:

1@Bean
2public ServletRegistrationBean dispatcherServlet() {
3    return new ServletRegistrationBean(new CXFServlet(), "/soap-api/*");
4}
5@Bean(name=Bus.DEFAULT_BUS_ID)
6public SpringBus springBus() {      
7    return new SpringBus();
8}

As mentioned, this is perfectly fine from the viewpoint of Apache CXF! But for us – embedded inside our enterprisey environments – this is not enough. Because with everything else, we are just left alone.

Introducing our own spring-boot-starter

Because of all this we decided to develop our own spring-boot-starter for Apache CXF that also brings in all the necessary surrounding technologies. We open-sourced it on GitHub.

But wait! Isn´t a spring-boot-starter a Spring developer´s exclusive way to make functionality available through Spring Boot? Hell no! Besides this huge list of starters from the Spring guys themselves, it´s possible for every developer to write their own one. And there are already quite a few on the official community contributions list .

The Spring Boot developers describe how to build your own spring-boot-starter on docs.spring.io . Additionally a colleague of mine summarized all the necessary steps in his blog article.

If you´re going to build your own starter, you´ll have the chance to deep-dive into the Spring Boot technology and gain a more profound understanding about this awesome framework. At the same time technically focused libraries aren´t developed multiple times and they can be used in all those projects with the same requirements. So building spring-boot-starters doesn´t contradict the thoughts behind the Microservices movement – it actually supports them. The exchange of technical libraries – ideally over GitHub – is explicitly encouraged.

cxf-spring-boot-starter

Let´s get straight to the point: Similar to the spring-boot-starter-batch-web , we made the cxf-spring-boot-starter available on GitHub. It makes our developer´s life a whole lot easier and lightens our workload by doing all the stuff we otherwise have to do on our own. This includes:

  • Initializing and setting up all Apache CXF components ( of course with 100% Spring Java configuration 😉 )
  • Extremely easy SOAP message Logging configuration
  • Extraction of all in- and out-going SOAP XML messages ready for your Elastic-Stack (including own custom fields and correlation of all log events relating to one SOAP request)
  • Providing a builder for custom SOAP faults that will respond in case of non-XML-schema-compliant requests
  • Comprehensive support for Unit, Integration and Single System Integration tests including assistance with invalid XML requests

As the devil is in the detail with the configuration of the jaxws-maven-plugin , it is completely encapsulated within a second component: the cxf-spring-boot-starter-maven-plugin . Now the generation of all necessary Java classes from the WSDL and its imported XSD files is going to be a cakewalk. Additionally the cxf-spring-boot-starter-maven-plugin scans your resource folder for the WSDL and ensures that every generated class is placed in the classpath. It also configures the jaxws-maven-plugin in a way that there will be no absolute paths inside the @WebServiceClient annotated class. This will for sure save you pains on your CI server.

Sounds good? Let´s go!

To show the advantages of the cxf-spring-boot-starter, I wish to propose the following approach to you: We completely set up an example project with all features from the ground up that touches every topic described in this blog series – but hopefully much faster. 😉 As usual there is an example project inside our tutorial repository , where you can reconstruct every step.

So let´s go! As mentioned in the first article we speed up the initial project creation using the Spring Initializr . Given a group and artifact we create our project and are ready to go. The generated POM is derived from the spring-boot-starter-parent. It depends on the spring-boot-starter-test and has the build plugin spring-boot-maven-plugin in place.

To embed our cxf-spring-boot-starter , we have to add the following dependency inside the appropriate section (the current version is 1.0.7.REALEASE):

1<dependencies>
2    <dependency>
3        <groupId>de.codecentric</groupId>
4        <artifactId>cxf-spring-boot-starter</artifactId>
5        <version>1.0.7.RELEASE</version>
6    </dependency>
7</dependencies>

Thereafter we also add the build plugin cxf-spring-boot-starter-maven-plugin inside our build section (1.0.7.RELEASE is also the current version):

1<build>
2    <plugins>
3        <plugin>
4            <groupId>de.codecentric</groupId>
5            <artifactId>cxf-spring-boot-starter-maven-plugin</artifactId>
6            <version>1.0.7.RELEASE</version>
7            <executions>
8                <execution>
9                    <goals>
10                        <goal>generate</goal>
11                    </goals>
12                </execution>
13            </executions>
14        </plugin>
15    </plugins>
16</build>

Due to a bug in logback 1.1.7 and the fact that we use the logstash-logback-encoder to extract our in- and outgoing SOAP XML messages for processing inside the Elastic Stack, we have to manually downgrade logback to 1.1.6 at the moment – which is only temporary and just until logback 1.1.8 is released:

1<properties>
2    <logback.version>1.1.6</logback.version>
3</properties>

Full speed ahead!

Now we throw our WSDL and XSD files somewhere inside of src/main/resources – where somewhere really means that you can choose any subfolder you want. 🙂 Inside our example project all files again can be found in src/main/resources/service-api-definition . Now we just need to start the generation of all necessary Java classes from the WSDL via a mvn generate-sources or alternatively import the project into our favorite IDE, which does that in background.

To complete the last step, we again need to implement the SEI . That´s the WeatherServiceEndpoint , where we start implementing our business logic later on. After that we create a @Configuration annotated class where we initialize our endpoint:

1@Configuration
2public class WebServiceConfiguration {
3 
4    @Autowired private SpringBus springBus;
5 
6    @Bean
7    public WeatherService weatherService() {
8        return new WeatherServiceEndpoint();
9    }
10 
11    @Bean
12    public Endpoint endpoint() {
13        EndpointImpl endpoint = new EndpointImpl(springBus, weatherService());
14        endpoint.setServiceName(weatherClient().getServiceName());
15        endpoint.setWsdlLocation(weatherClient().getWSDLDocumentLocation().toString());
16        endpoint.publish("/WeatherSoapService_1.0");
17        return endpoint;
18    }
19 
20    @Bean
21    public Weather weatherClient() {
22        return new Weather();
23    }
24}

As you can see, we don´t have to initialize Apache CXF. The cxf-spring-boot-starter does that for us. We just need to autowire the org.apache.cxf.bus.spring.SpringBus, which is needed to set up the endpoint.

And that´s it! We are now able to start our SimpleBootCxfApplication via a “RUN” inside our IDE or just execute a mvn spring-boot:run inside our console. A short look at http://localhost:8080/soap-api assures us that Apache CXF is completely up and running and our web service endpoint is registered. Real SOAP calls with SoapUI will work. Try it!

Testing SOAP web services

Now that we have covered all key aspects of article one , let´s have a look at the second one about testing SOAP web services . We´ll focus on the Single System Integration tests, because inside of them the impact of the cxf-spring-boot-starter is the biggest of all test variants. In a Single System Integration test we have to have a JAX-WS client that is capable of calling our local server. And therefore we need the URL of our local SOAP endpoint. Since one of the features of the cxf-spring-boot-starter is to automatically set the web service´s URL, we need to know how to obtain it from the starter.

Therefore we take the WeatherServiceXmlFileSystemTest from the second blog post. It will be configured with the new SimpleBootCxfSystemTestConfiguration – and there´s the point we have to look into. We autowire the de.codecentric.cxf.configuration.CxfAutoConfiguration – then we are able to get our required URL via cxfAutoConfiguration.getBaseUrl(). After that the configuration of our JAX-WS client looks like this:

1@Autowired private CxfAutoConfiguration cxfAutoConfiguration;
2 
3@Bean
4public WeatherService weatherServiceSystemTestClient() {
5    JaxWsProxyFactoryBean jaxWsProxyFactory = new JaxWsProxyFactoryBean();
6    jaxWsProxyFactory.setServiceClass(WeatherService.class);
7    jaxWsProxyFactory.setAddress("http://localhost:8090" + cxfAutoConfiguration.getBaseUrl() + SimpleBootCxfConfiguration.SERVICE_URL);
8    return (WeatherService) jaxWsProxyFactory.create();
9}

To adjust the basic URL of our CXF endpoints, it´s enough to extend our application.properties with the soap.service.base.url property – expanded with an appropriate path like:

1soap.service.base.url=/my-custom-service-api

The title of the CXF generated service overview site displayed when entering a root URL like http://localhost:8080/my-custom-service-api could be configured just as easily. This comes in handy when that site is published to consumers of your services and you want to give it a customized name. Just add the cxf.servicelist.title property. You can see both properties in action inside the example project´s application.properties .

Our test case WeatherServiceXmlFileSystemTest is only changing slightly. The cxf-spring-boot-starter comes with the convenient utility class de.codecentric.cxf.common.XmlUtils that does all the (un-) marshalling of XML and JAX-B objects inside our test cases:

1@RunWith(SpringRunner.class)
2@SpringBootTest(
3        classes=SimpleBootCxfSystemTestApplication.class,
4        webEnvironment= SpringBootTest.WebEnvironment.DEFINED_PORT,
5        properties = {"server.port=8090"}
6)
7public class WeatherServiceXmlFileSystemTest {
8 
9    @Autowired private WeatherService weatherServiceSystemTestClient;
10 
11    @Value(value="classpath:requests/GetCityForecastByZIPTest.xml")
12    private Resource getCityForecastByZIPTestXml;
13 
14    @Test
15    public void getCityForecastByZIP() throws WeatherException, IOException, BootStarterCxfException {
16        // Given
17        GetCityForecastByZIP getCityForecastByZIP = XmlUtils.readSoapMessageFromStreamAndUnmarshallBody2Object(getCityForecastByZIPTestXml.getInputStream(), GetCityForecastByZIP.class);
18 
19        // When
20        ForecastReturn forecastReturn = weatherServiceSystemTestClient.getCityForecastByZIP(getCityForecastByZIP.getForecastRequest());
21 
22        // Then
23        assertNotNull(forecastReturn);
24        assertEquals(true, forecastReturn.isSuccess());
25        ...
26    }
27}

OK, there´s another difference. But this has nothing to do with the cxf-spring-boot-starter, but with the new features around testing that come with Spring Boot 1.4.x, where most annotation classes are condensed into org.springframework.boot.test.context.SpringBootTest. This annotation substitutes all the other ones like @WebIntegrationTest, @SpringApplicationConfiguration, @ContextConfiguration and so forth (for more info see the spring.io blogpost ). Also now writing just SpringRunner instead of SpringJUnit4ClassRunner will reduce the time you need to type.

And that´s all you need for testing SOAP web services.

XML validation and custom SOAP faults

The third blog article´s conclusion (about the many steps that are necessary to configure custom SOAP faults that will respond when XML schema validation fails) wasn´t the best. But because this requirement is often found in enterprisey web service specifications (just look at BiPro spec), the cxf-spring-boot-starter makes life a lot easier for us. Basically we can forget every step from the third article and just implement the interface de.codecentric.cxf.xmlvalidation.CustomFaultBuilder. And that´s all!

In more detail: There are two methods we have to override. createCustomFaultMessage(FaultType faultType) gives us the possibility to tailor the fault messages inside of our SOAP faults. Due to the forwarded parameter of the type de.codecentric.cxf.common.FaultType we also know if it´s an XML schema validation error or if there´s just incorrect XML sent to our endpoint. Due to the design of the cxf-spring-boot-starter, we can also react to errors in a schema compliant way that have nothing to do with XML schema validation at all.
With the help of the second method createCustomFaultDetail(String originalFaultMessage, FaultType faultType) we are able to build our custom XML schema compliant error messages that will be placed inside the detail-Tag. We can see both in action inside the class WeatherFaultBuilder of our example project:

1@Component
2public class WeatherFaultBuilder implements CustomFaultBuilder {
3 
4    private de.codecentric.namespace.weatherservice.exception.ObjectFactory objectFactoryDatatypes = new de.codecentric.namespace.weatherservice.exception.ObjectFactory();
5 
6    @Override
7    public String createCustomFaultMessage(FaultType faultType) {
8        if(FaultType.SCHEME_VALIDATION_ERROR.equals(faultType))
9            return CustomIds.NON_XML_COMPLIANT.getMessage();
10        else if(FaultType.SYNTACTICALLY_INCORRECT_XML_ERROR.equals(faultType))
11            return CustomIds.COMPLETE_USELESS_XML.getMessage();
12        else
13            return CustomIds.SOMETHING_ELSE_WENT_TERRIBLY_WRONG.getMessage();
14    }
15 
16    @Override
17    public WeatherException createCustomFaultDetail(String originalFaultMessage, FaultType faultType) {
18        // Build SOAP-Fault detail <datatypes:WeatherException>
19        WeatherException weatherException = objectFactoryDatatypes.createWeatherException();
20        weatherException.setBigBusinessErrorCausingMoneyLoss(true);
21        setIdBasedUponFaultContent(faultType, weatherException);
22        weatherException.setExceptionDetails(originalFaultMessage);
23        weatherException.setUuid("ExtremeRandomNumber");
24        return weatherException;
25    }
26...
27}

Inside the method createCustomFaultDetail(String originalFaultMessage, FaultType faultType) we should be careful to respond with the correct exception type. Sometimes there are multiple exception types of the same name inside those specifications. Again, this can be found in the BiPro web services.

Everything that´s left here is to define this @Component annotated class as a Spring Bean. That´s it again 🙂

We for sure shouldn´t trust the author – we want to see a running test case! And also supporting this case the cxf-spring-boot-starter has something for you: the de.codecentric.cxf.soaprawclient.SoapRawClient. It allows us to send non-XML-schema-compliant XML against our endpoints. This is needed to provoke XML validation failures that will lead to our custom tailored SOAP faults.

To show this in action, we re-use the WeatherServiceXmlErrorSystemTest from the third article. It´s a Single System Integration test that´ll provoke the desired validation errors and checks if our endpoint reacts in the desired way. We´ll just expand it a bit. Besides the obligatory changes that come with Spring Boot 1.4.x we change the manually implemented constants into the cxf-spring-boot-starter provided de.codecentric.cxf.common.FaultType inside our WeatherServiceXmlErrorSystemTest . We should also assert with regard to the error text we provided inside our WeatherFaultBuilder :

1assertEquals(WeatherFaultBuilder.CUSTOM_ERROR_MSG, soapRawResponse.getFaultstringValue());

The test cases´ configuration changes also just slightly: It uses the CxfAutoConfiguration to get the needed URL:

1@Autowired private CxfAutoConfiguration cxfAutoConfiguration;
2 
3@Bean
4public SoapRawClient soapRawClient() throws BootStarterCxfException {
5    return new SoapRawClient(buildUrl(), WeatherService.class);
6}
7 
8private String buildUrl() {
9    // return something like http://localhost:8084/soap-api/WeatherSoapService
10    return "http://localhost:8087"
11            + cxfAutoConfiguration.getBaseUrl()
12            + SimpleBootCxfConfiguration.SERVICE_URL;
13}

If we run our WeatherServiceXmlErrorSystemTest now, everything should go green. Additionally we can marvel at the XML-schema-compliant error messages via SoapUI. And that´s it: We configured our custom SOAP faults in no time.

Logging & Monitoring with Logback, Elasticsearch, Logstash & Kibana

Now we’ve already reached this blog series´ last article . Particularly the subject of logging can cause a whole lot of work. And again the cxf-spring-boot-starter will support us effectively. Using it, all the logging is automatically configured to use slf4j and logback. If we want to see the in- and outgoing SOAP XML messages inside your logfile or console, we only have to set the property soap.messages.logging inside our application.properties.

If you want to throw your logs inside an Elastic Stack, the cxf-spring-boot-starter will take over most of the heavy lifting. The only requirements are setting the property

1soap.messages.extract=true

and having a logback-spring.xml in place. It can look exactly like the one in the previous article:

1<?xml version="1.0" encoding="UTF-8"?>
2<configuration>
3    <include resource="org/springframework/boot/logging/logback/base.xml"/>
4    <logger name="org.springframework" level="WARN"/>
5    <logger name="de.jonashackt.tutorial" level="DEBUG"/>
6    <logger name="org.apache.cxf" level="INFO"/>
7 
8    <!-- Logstash-Configuration -->
9    <appender name="logstash" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
10        <destination>192.168.99.100:5000</destination>
11        <encoder class="net.logstash.logback.encoder.LogstashEncoder">
12            <customFields>{"service_name":"WeatherService 1.0"}</customFields>
13            <fieldNames>
14                <message>log_msg</message>
15            </fieldNames>
16        </encoder>
17        <keepAliveDuration>5 minutes</keepAliveDuration>
18    </appender>
19 
20  <root level="INFO">
21        <appender-ref ref="logstash" />
22  </root>
23</configuration>

If you have a running Elastic Stack in place (there are some hints in the last article how you can get this up and running easily), that´s it.

But there´s more. All SOAP messages are placed into the custom fields soap-message-inbound and soap-message-outbound, which will be analysable much more easily. All those fields are defined in the enumeration de.codecentric.cxf.logging.ElasticsearchField. Furthermore all log events that are created through one SOAP request are automatically correlated and the cxf-spring-boot-starter tries to determine the SOAP service method’s name (although this just works for WSDL specification 1.1 at the moment) and places it into the custom field soap-method-name. Finally we have everything in place that´s needed to thoroughly log our SOAP messages.

More speed in SOAP service development!

Now we´ve got everything we need to develop our SOAP services even faster, while having all the features ready to support the needs of our enterprisey environments. With the cxf-spring-boot-starter we can provide a solution for every problem described in our four blog articles. And as you can see: that´s a lot.

But if you´re really missing something – no problem! The cxf-spring-boot-starter is waiting for your contribution! That´s the power of Open Source software – you benefit from it in your projects, and you can give something back.

Now: Have fun with the cxf-spring-boot-starter! We are waiting for feedback and pull requests. 🙂

share post

//

More articles in this subject area

Discover exciting further topics and let the codecentric world inspire you.