Hosting a CXF SOAP webservice in an existing Jetty instance

In the previous post we looked at creating a web service, exposing SOAP and running in a CXF created instance of Jetty, but what if you already have an application with an existing instance of Jetty running. All you want to do is drop a SOAP capable service into this instance.

Like most of the things I’ve discovered working with CXF (and Spring), it’s not that difficult, once you know how. It’s the finding out “how” that seems more difficult :)

First off, much credit goes to this post Embedded Jetty and Apache CXF: Secure REST Services With Spring Security or more specifically the source code for this post, here.

I’m not interested (at this time) in security, so we’ll ignore that and just concern ourselves with injecting a service into an instance of Jetty created by the application itself (not by CXF).

First up, let’s create our pom.xml

<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/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.putridparrot.core</groupId>
  <artifactId>cxfws</artifactId>
  <packaging>jar</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>cxfws</name>
  <url>http://maven.apache.org</url>

  <properties>
    <cxf.version>3.0.1</cxf.version>
    <spring.version>4.1.0.RELEASE</spring.version>
    <jetty.version>9.4.2.v20170220</jetty.version>
  </properties>

  <dependencies>

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

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>${spring.version}</version>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>${spring.version}</version>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>${spring.version}</version>
    </dependency>

    <dependency>
      <groupId>org.eclipse.jetty</groupId>
      <artifactId>jetty-server</artifactId>
      <version>${jetty.version}</version>
    </dependency>

    <dependency>
      <groupId>org.eclipse.jetty</groupId>
      <artifactId>jetty-servlet</artifactId>
      <version>${jetty.version}</version>
    </dependency>
  </dependencies>
</project>

As usual, run mavn install to grab the dependencies.

Now create the usual folder structure for our source, /src/main/java and within this your package folders, i.e. com/putridparrot/core.

We’re going to create four files, two of which we’ve used in previous posts, HelloWorld and HelloWorldImpl, I’ll include those here first just for completeness

HelloWorld.java

package com.putridparrot.core;

import javax.jws.WebParam;
import javax.jws.WebService;

@WebService
public interface HelloWorld {
    String reply(@WebParam(name="text") String text);
}

HelloWorldImpl.java

package com.putridparrot.core;

import javax.jws.WebParam;
import javax.jws.WebService;

@WebService(endpointInterface = "com.putridparrot.core.HelloWorld", serviceName="HelloWorld")
public class HelloWorldImpl implements HelloWorld {
    public String reply(@WebParam(name = "text") String text) {
        return "Hello " + text;
    }
}

As per the example from the referenced post Embedded Jetty and Apache CXF: Secure REST Services With Spring Security we’re going to create the configuration in code, so create AppConfig.java and place the following in this file

package com.putridparrot.core;

import org.apache.cxf.bus.spring.SpringBus;
import org.apache.cxf.endpoint.Server;
import org.apache.cxf.jaxws.JaxWsServerFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;

@Configuration
public class AppConfig {
    @Bean( destroyMethod = "shutdown" )
    public SpringBus cxf() {
        return new SpringBus();
    }

    @Bean @DependsOn( "cxf" )
    public Server getServer() {
        JaxWsServerFactoryBean factory = new JaxWsServerFactoryBean();
        factory.setServiceBean(getService());
        return factory.create();
    }

    @Bean
    public HelloWorldImpl getService() {
        return new HelloWorldImpl();
    }
}

Finally create App.java and place the following inside it

package com.putridparrot.core;

import org.apache.cxf.transport.servlet.CXFServlet;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;

public class App {
    public static void main( String[] args ) throws Exception {

        Server server = new Server( 9000 );

        ServletHolder servletHolder = new ServletHolder( new CXFServlet() );
        ServletContextHandler context = new ServletContextHandler();
        context.setContextPath( "/" );
        context.addServlet( servletHolder, "/*" );
        context.addEventListener( new ContextLoaderListener() );

        context.setInitParameter( "contextClass", AnnotationConfigWebApplicationContext.class.getName() );
        context.setInitParameter( "contextConfigLocation", AppConfig.class.getName() );

        server.setHandler( context );
        server.start();
        server.join();

        System.in.read();

        System.exit(0);
    }
}

Once this is built, run App.main and you should be able to use your web browser to navigate to http://localhost:9000/HelloWorld?wsdl and see the wsdl definitions for this service.

Great, but what’s it all doing?

Let’s start with the App.main method. The server variable is our Jetty instance. So really the only thing we’re interested here is which port have we assigned the instance to and the server.setHandler method call. This is where we embed our servlet context into the Jetty engine. From what I can understand, this basically allows us a way to intercept calls and pass to our specific servlet, in this case (ultimately) our CXFServlet.

The ServletHolder, as the name suggests, holds the servlet object that we want to use and will be added to the ServletContextHandler. As you can see this is where the bulk of the code is. The setContextPath call sets up the root level path of our context, i.e. changing to context.setContextPath( “/ws” ) means our webservice will now be located off of the ws path, thus http://localhost:9000/ws/HelloWorld?wsdl.

When we call addServlet we can, in essence, extend the root path further, so for example context.addServlet( servletHolder, “/services/*” ) will simply moved the servlet to your root path/services.

We add the spring ContextLoaderListener to the ServletContext and this is where we can setup the parameters required for spring to bring in our webservices etc.

The ContextLoaderListener “Create a new ContextLoaderListener that will create a web application context based on the “contextClass” and “contextConfigLocation” servlet context-params.” we’ll need to supplied the contextClass and contextConfigLocation parameters and that’s exactly what we do in the two setInitParameter lines of code.

The contextClass is going to use the web annotations (hence our webservices are annotated) and the contextConfigLocation will use our AppConfig class (instead of a config. file).

Configuration class

The AppConfig class supplies the configuration for the ContextLoaderListener and is first annotated with the Configuration, although removing this seemed to have no effect. What does have a big effect is the requirement for a bean named cxf – if this is missing you’ll get the following exception


Exception in thread “main” org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named ‘cxf’ is defined

In our case we return a SpringBus

Next up we supply a getServer bean which depends on the cxf bean – this simply means the cxf bean should be created before the getServer bean. This code creates the web service implementation (we could have created a REST implementation using JAXRSServerFactoryBean). Within this code we set the factory bean to use the web service supplied via our getService method (which above is marked as a bean but needn’t be). The we simply return the result of the create method and we’re done.