A journey from Apache CXF 2.2 to 3.2

I have used Apache CXF 2.2 to develop Web services for some R&D projects that I took part in. At that time, it was a choice between Apache Axis/Axis2 and CXF (formerly Codehaus XFire project).

I eventually decided to get along with CXF due to its simplicity, quite clear documentation, good support for document-style Web services (+) and many standards, especially JAX-WS and JAX-RS.

Moreover, Apache CXF also embraces smooth integration with Spring Framework (big plus for me as I was using Spring Web MVC 3 to develop the Web front-end). Everything went well for me on modelling and developing Web services based on JAX-WS for both directions: WSDL-first and Java-first.

In my projects, I designed the WSDLs and used WSDL2Java Maven plugin or command line to generate skeleton Java code of the Web services. The Java service implementations were kept separately in a package/folder to avoid any code overwriting. CXF services are so easy to configure with Spring Framework 3.0. The implementations of the services were loaded as Spring managed beans. The expose of a service is done via a CXF directive <jaxws:endpoint> of which the implementor attribute refers to the corresponding bean.

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:jaxws="http://cxf.apache.org/jaxws">
  <import resource="classpath:META-INF/cxf/cxf.xml" />
  <import resource="classpath:META-INF/cxf/cxf-extension-soap.xml" />
  <import resource="classpath:META-INF/cxf/cxf-extension-jaxws.xml" />
  <import resource="classpath:META-INF/cxf/cxf-servlet.xml" />    
  <bean id="CreditWorthinessImpl"
        class="westbank.ws.impl.CreditWorthinessImpl"
        p:dataAccessObject-ref="dataAccessObject">
  </bean>
  <jaxws:endpoint id="CreditWorthiness"
                  implementor="#CreditWorthinessImpl" 
                  address="/CreditWorthiness">
  </jaxws:endpoint>
</beans>

In the Web app configuration web.xml, CXF Servlet must be loaded by the container.

<servlet>
  <servlet-name>cxf</servlet-name>
  <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
  <load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
  <servlet-name>cxf</servlet-name>
  <url-pattern>/services/*</url-pattern>
</servlet-mapping>

Upgrading Apache CXF

For a while I haven’t taken part in further WS development, i.e. not using Apache CXF a lot. One day I thought I could use the WS project to showcase and review my relevant knowledge. The codebase still works well after some mysterious library missing that I have not seen before. It is possible due to the built-in libraries for XML parsing that were implicitly used but now changed in the newer JRE. Apart from that, most of the libraries are also outdated. So I dedice to spend some time to exercise upgrading the project and refactor a bit its source code. Here I jot down some major points on upgrading Apache CXF.

The upgrading was not as easy and smooth as just changing the dependencies’ versions. A lot of conflicts or major refactoring happened. I had to read thoroughly the documentation on CXF site for migration and decided to go gradually over each major version of Apache CXF.

… from version 2.2 to 2.7 …

From Apache CXF 2.2 to 2.6, not so many changes are relevant for the project as I mainly used cxf-rt-frontend-jaxws. Another significant change since 2.4 causes errors for Spring / Jetty server regarding importing CXF’s XML resources. Recall the aforementioned Spring bean configuration where CXF service implementations were loaded and published? There are a number of <import> directives. These directives advise Spring to load the corresponding CXF resources. Now we only need “one to rule them all”.

<import resource="classpath:META-INF/cxf/cxf.xml" />

To upgrade to version 2.6, I have to revise the project with respect to the merging of cxf-common-utilites into cxf-api, the removal of cxf-rt-binding-http, and the refactoring that impact cxf-rt-core. Most of the other changes from 2.2 to 2.7 are for JAX-RS, which were used very little or none in my project.

… and to version 3.0+

Apache CXF 3.0 requires a rather disruptive change, from Spring Framework 3.0 to 3.2+. Again, I used standard Spring bean configurations which are still valid for Spring 3.2. Hence, the project works well with Spring 3.2.18-RELEASE. I only need to remove all version numbers in the Spring XML schemas. The major change I must deal with is to remove the dependency of cxf-api as it was merged with cxf-core.

Then there comes the most dramatic issue with CXF 3.0.16 (sic!). cxf-codegen-plugin used to generate Java code from WSDLs refused to work (which was no problem in earlier verions) and spit out errors like this.

[ERROR] Failed to execute goal org.apache.cxf:cxf-codegen-plugin:3.0.16:wsdl2java (generate-sources) on project loan-approval-portal: Execution generate-sources of goal org.apache.cxf:cxf-codegen-plugin:3.0.16:wsdl2java failed: org.apache.cxf.wsdl11.WSDLRuntimeException: Fail to create wsdl definition file:%3c?xml%20version=%221.0%22%20encoding=%22UTF-8%22?%3e: WSDLException: faultCode=PARSER_ERROR: Problem parsing 'file:%3c?xml%20version=%221.0%22%20encoding=%22UTF-8%22?%3e'.: java.io.FileNotFoundException: < (No such file or directory) -> [Help 1]
org.apache.maven.lifecycle.LifecycleExecutionException: Failed to execute goal org.apache.cxf:cxf-codegen-plugin:3.0.16:wsdl2java (generate-sources) on project loan-approval-portal: Execution generate-sources of goal org.apache.cxf:cxf-codegen-plugin:3.0.16:wsdl2java failed: org.apache.cxf.wsdl11.WSDLRuntimeException: Fail to create wsdl definition file:%3c?xml%20version=%221.0%22%20encoding=%22UTF-8%22?%3e: WSDLException: faultCode=PARSER_ERROR: Problem parsing 'file:%3c?xml%20version=%221.0%22%20encoding=%22UTF-8%22?%3e'.: java.io.FileNotFoundException: < (No such file or directory)

What puzzles me is that I have tried the command tool wsdl2java of Apache CXF 3.0.16 with each WSDL and, strangely, found no errors at all. After some extra trial-and-error effort, I eventually figured out that an extra option for cxf-codegen-plugin causes the error. The old/original plugin configuration in pom.xml is as following.

<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>${basedir}/src/main/java</sourceRoot>
        <wsdlRoot>${basedir}/src/main/webapp/WEB-INF/wsdl/</wsdlRoot>
        <defaultOptions>
          <validateWsdl>true</validateWsdl>
          <wsdlList>true</wsdlList>
          <defaultExcludesNamespace>true</defaultExcludesNamespace>
          <extraargs>
            <extraarg>-defaultValues</extraarg>
            <extraarg>-quiet</extraarg>
            <extraarg>-wsdlLocation</extraarg>
            <extraarg></extraarg>
          </extraargs>
        </defaultOptions>
      </configuration>
      <goals>
        <goal>wsdl2java</goal>
      </goals>
    </execution>
  </executions>
</plugin>

The option <wsdlList> is the culprit. It was declared boolean since CXF 2.2 to 2.7.0 as I found here. However, in the most recent documentation of CXF 3, wsdlList is still listed as an option for wsdl2java but no longer of type boolean (-wsdlList <wsdlurl>). To make it worse, the option is totally hidden/undocumented. So all I have to do is to disable the line <wsdlList>true</wsdlList> and cxf-codegen-plugin works again in CXF 3.0+.

The problems with cxf-codegen-plugin keep raising when upgrading CXF to 3.2.4. This time, it threw another exception though.

[ERROR] Failed to execute goal org.apache.cxf:cxf-codegen-plugin:3.2.4:wsdl2java (generate-sources) on project loan-approval-portal: Execution generate-sources of goal org.apache.cxf:cxf-codegen-plugin:3.2.4:wsdl2java failed: org.xml.sax.SAXNotRecognizedException: Property 'http://javax.xml.XMLConstants/property/accessExternalSchema' is not recognized. -> [Help 1]
org.apache.maven.lifecycle.LifecycleExecutionException: Failed to execute goal org.apache.cxf:cxf-codegen-plugin:3.2.4:wsdl2java (generate-sources) on project loan-approval-portal: Execution generate-sources of goal org.apache.cxf:cxf-codegen-plugin:3.2.4:wsdl2java failed: org.xml.sax.SAXNotRecognizedException: Property 'http://javax.xml.XMLConstants/property/accessExternalSchema' is not recognized.

We can see that the error is due to schema validation. Before, I wanted to make sure that the WSDLs were valid before generating Java code. Thus, I chose to enable validation by <validateWsdl>true</validateWsdl>. This option is actually the root cause of the aforementioned exception during validation. The problem seems to stem from new XML security properties in JAXB 1.5 introduced in Java 8. When I disable that option, the plugin works fine again (voila!). But that means I must live in a maybe-not-error-free world (D’oh!). But we are all, aren’t we?

After all of the above, I can get my project to work with Apache CXF 3.2.4 while keeping the business logic of services intact.

Update 2018-06-25: XML-less Spring Configuration

As deciding to switch to XML-less Spring configuration to learn more about Spring Java annotations, I also tried to migrate CXF settings, too. Here are some last updates.

Loading CXFServlet

public class MyWebApplicationInitializer implements org.springframework.web.WebApplicationInitializer {
  @Override
  public void onStartup(ServletContext container) throws ServletException {
    AnnotationConfigWebApplicationContext root = new AnnotationConfigWebApplicationContext();
    root.register(ServiceConfiguration.class);
    root.refresh();

    // Register and map the CXF servlet
    CXFServlet cxfServlet = new CXFServlet();
    ServletRegistration.Dynamic reg = container.addServlet("cxf", cxfServlet);
    reg.setLoadOnStartup(1);
    reg.addMapping("/services/*");
  }
}

Publishing Services

I configured the CXF bus used for publishing web services and defining JAX-WS service endpoints. Instead of importing existing CXF’s XML resources as many developers have chosen, I opted for a pure Java approach. What we have to do is to create a bean named ‘cxf’ (defined as org.apache.cxf.Bus.DEFAULT_BUS_ID) and use that bean for defining service endpoints.

import com.example.HelloWorldImpl;

import org.apache.cxf.Bus;
import org.apache.cxf.bus.spring.SpringBus;
import org.apache.cxf.jaxws.EndpointImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.xml.ws.Endpoint;

@Configuration
public class ServiceConfiguration {

  @Bean(name=Bus.DEFAULT_BUS_ID)
  public SpringBus cxf() {
      return new SpringBus();
  }

  @Bean
  public Endpoint helloWorld() {
    HelloWorldImpl implementor = new HelloWorldImpl();
    EndpointImpl endpoint = new EndpointImpl(cxf(), implementor);
    endpoint.publish("/HelloWorld");
    return endpoint;
  }
}

IMPORTANT Endpoint.publish() must be called to expose the service endpoint instead of Endpoint.setAddress() as above.

comments powered by Disqus