I was playing with CXF 2.2 over the weekend. I would like to share my experience in building a simple blogger service using CXF which provides seamless access to SOAP and REST web services. You would soon realize how services design and development has become easier with this new CXF release. Lets dive into the crux.

The blogger service exposes reading and writing operations. All blogging operations (CRUD) are exposed as REST and read operations are exposed as SOAP. This approach enables service containment and provides controlled access to services. This service produces XML and JSON content and the negotiation is done by the client.

The class hierarchy is shown below.

Blogger class hierarchy

Maven dependencies for this project are:

        <dependency>
            <artifactId>cxf-rt-frontend-jaxws</artifactId>
            <groupId>org.apache.cxf</groupId>
            <version>2.2</version>
        </dependency>
        <dependency>
            <artifactId>cxf-rt-frontend-jaxrs</artifactId>
            <groupId>org.apache.cxf</groupId>
            <version>2.2</version>
        </dependency>
        <dependency>
            <artifactId>servlet-api</artifactId>
            <groupId>javax.servlet</groupId>
            <version>2.5</version>
        </dependency>
        <dependency>
            <artifactId>cxf-rt-transports-http</artifactId>
            <groupId>org.apache.cxf</groupId>
            <version>2.2</version>
        </dependency>
        <dependency>
            <artifactId>cxf-rt-transports-http-jetty</artifactId>
            <groupId>org.apache.cxf</groupId>
            <version>2.2</version>
        </dependency>

BlogReader interface enables REST and SOAP access for the blogger service read operations.

public interface BlogReader {
    @WebMethod
    @GET
    @Path("{id}")
    @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
    public Entry getEntry(@PathParam("id") Long id);

    @WebMethod
    @GET
    @Path("/list")
    @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
    public Entries getEntries();    
}

BlogWriter interface enables REST access for the blogger service write operations.

public interface BlogWriter {
    @POST
    Response addEntry(Entry entry);

    @PUT
    Response updateEntry(Entry entry);

    @DELETE
    @Path("{id}")
    Response deleteEntry(@PathParam("id") Long id);
}

Blogger interface unifies SOAP and REST access to the blogger service.

@Path("/")
@WebService
public interface Blogger extends BlogReader, BlogWriter {
}

Here is a trivial implementation of the blogger service. This implementation shows how resources can be made service oriented without any extra effort.

public class BloggerImpl implements Blogger {
    static Map<Long, Entry> blogDb = new TreeMap<Long, Entry>();
    private AtomicLong counter = new AtomicLong();

    public Response addEntry(Entry entry) {
        System.out.println("adding entry..");
        entry.setId(counter.incrementAndGet());
        blogDb.put(entry.getId(), entry);
        System.out.println("Created blog entry " + entry.getId());
        return Response.created(URI.create("/" + entry.getId())).build();
    }

    public Response updateEntry(Entry entry) {
        System.out.println("Updated blog entry " + entry.getId());
        Entry oldEntry = blogDb.get(entry.getId());
        Response r;
        if (oldEntry != null) {
            r = Response.ok().build();
            blogDb.put(entry.getId(), entry);
        } else {
            r = Response.notModified().build();
        }
        return r;
    }

    public Response deleteEntry(Long id) {
        System.out.println("Removed blog entry " + id);
        Entry entry = blogDb.get(id);
        Response r;
        if (entry != null) {
            r = Response.ok().build();
            blogDb.remove(id);
        } else {
            r = Response.notModified().build();
        }
        return r;        
    }

    public Entry getEntry(@PathParam("id") Long id) {
        System.out.println("Fetching blog entry " + id);
        Entry entry = blogDb.get(id);
        if (entry == null) {
          throw new WebApplicationException(Response.Status.NOT_FOUND);
        }
        return entry;
    }

    public Entries getEntries() {
        Entries entries = new Entries();
        entries.setEntry(blogDb.values());
        return entries;
    }
}

Entry and Entries classes are simple JAXB beans.

The Server code starts both the CXF REST and SOAP services which uses the embedded Jetty server. You could easily configure the content filtering just by suffixing to the URI (.xml or .json) as defined in the extension mappings.

public class Server {
    public static void main(String[] args) {
        startSoapServer();
        startRestServer();
    }

    private static void startRestServer() {
        JAXRSServerFactoryBean sf = new JAXRSServerFactoryBean();
        sf.setResourceClasses(Blogger.class);
        sf.getInInterceptors().add(new LoggingInInterceptor());
        sf.getOutInterceptors().add(new LoggingOutInterceptor());
        sf.setResourceProvider(Blogger.class, new SingletonResourceProvider(new BloggerImpl()));
        sf.setAddress("http://localhost:9000/rest/blog");
        Map<Object, Object> mappings = new HashMap<Object, Object>();
        mappings.put("xml", "application/xml");
        mappings.put("json", "application/json");
        sf.setExtensionMappings(mappings);
        sf.create();
        System.out.println("Rest Server started @ " + sf.getAddress());
    }

    private static void startSoapServer() {
        JaxWsServerFactoryBean sf = new JaxWsServerFactoryBean();
        sf.setServiceClass(BlogReader.class);
        BloggerImpl blogger = new BloggerImpl();
        sf.setServiceBean(blogger);
        sf.setAddress("http://localhost:9000/soap/blog");
        sf.getInInterceptors().add(new LoggingInInterceptor());
        sf.getOutInterceptors().add(new LoggingOutInterceptor());
        sf.create();
        System.out.println("Soap Server started @ " + sf.getAddress());
    }
}

CXF JAXRS client API comes in different flavors. In this example, I used the WebClient which acts like a browser in interacting with web resources. The API client.back(true) if true then goes back to baseURI otherwise it goes back to a previous path segment, just like browsers. The tests are self-explanatory. I was able to perform all the CRUD operations using the WebClient API.

public class RestClient {
    public static void main(String[] args) {
        String baseUri = "http://localhost:9000/rest/blog";
        WebClient client = WebClient.create(baseUri);

        Entry entry = new Entry();
        entry.setDate(new Date());
        entry.setTitle("CXF in Action!");
        entry.setContent("CXF rocks!");
        Response response = client.post(entry);

        System.out.println("POST Response : " + response.getStatus());
        System.out.println("Location : " + response.getMetadata().getFirst("Location"));

        client.path("/1.xml");
        entry = client.get(Entry.class);
        System.out.println("entry.id : " + entry.getId() );

        client.back(true);
        entry.setContent("CXF 2.2 rocks!");
        response = client.put(entry);
        System.out.println("PUT Response : " + response.getStatus());

        client.path("/1.xml");
        entry = client.get(Entry.class);
        System.out.println("entry.id : " + entry.getContent());

        client.back(true);
        client.path("/1.xml");
        response = client.delete();
        System.out.println("DELETE Response : " + response.getStatus());

        client.back(true);
        response = client.post(entry);
        System.out.println("POST Response : " + response.getStatus());
        System.out.println("Location : " + response.getMetadata().getFirst("Location"));
    }
}

The SoapClient queries the added blog entries from the REST interface.

public class SoapClient {
    public static void main(String[] args) {
        JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
        factory.getInInterceptors().add(new LoggingInInterceptor());
        factory.getOutInterceptors().add(new LoggingOutInterceptor());

        factory.setServiceClass(BlogReader.class);
        factory.setAddress("http://localhost:9000/soap/blog");
        BlogReader blogger = (BlogReader) factory.create();
        Entry entry = blogger.getEntry(2L);
        System.out.println("entry title : " + entry.getTitle());
        System.out.println("entry content : " + entry.getContent());
    }
}

But, this gives you an idea how CXF services can promote reusability across various design approaches and making life simpler for developers. I know this is fairly basic example and definitely requires further refining. I will keep this for another post. Stay tuned.

[Update: 3/25] The delete method would throw “java.net.ProtocolException: HTTP method DELETE doesn’t support output” due to a bug in HttpConduit in CXF 2.2. This got fixed in the latest trunk. Thanks to Sergey for fixing this. In order to use the 2.2.1 snapshot repository, you need to define the following maven snapshot repository in your POM with dependency version 2.2.1-SNAPSHOT.

        <repository>
            <name>Apache CXF snapshots repository</name>
            <url>https://repository.apache.org/content/repositories/snapshots</url>
            <id>cxf-snapshots</id>
        </repository>

Possibly Related Posts:


6 Responses to “CXF 2.2 in Action – services design simplified”

  • Boni

    Do you have the source for this tutorial? What class is Entry in the code?

  • David M. Karr

    When the original application is configured with Spring, it can be a little difficult to figure out how to map to the equivalent configuration directly from the API, as you’re doing here.

    For instance, In my app I have jaxrs:serviceBeans and jaxrs:providers. The service beans are my controllers, and the one provider I have is an instance of “org.codehaus.jackson.jaxrs.JacksonJsonProvider”. My app works fine with this, but I’m having trouble translating this to the setup I need for the test. JacksonJsonProvider isn’t a “ResourceProvider”, so I guess the Spring config causes a wrapper to be created, but I can’t tell how I would do that here.

    Also, I’m a little confused by the fact that you’re essentially using the same class for both your service bean and for your resource provider.

  • Arul

    Hi David,

    I believe this is already answered by Sergey in the CXF Userlist.

    Let me know if I can help with anything else.

    -Arul