Developers, Developers, Developers! Maksim Sorokin IT Blog

24Jul/11Off

Maven + Apache Felix + CXF: Creating a RESTful Webservice with CXF. Returning and Object.

It is another post in series Maven + Apache Felix + CXF + DOSGi.

This example is based on a post about simple JAX-RS webservice with CXF. But this post describes a way how to return a more complex object, instead of a String.

First, we create a class in our test.bundle bundle MyMessage in package test.bundle:

package test.bundle;

public class MyMessage {

  private String message;

  public MyMessage(String message) {
    this.message = message;
  }

  public String getMessage() {
    return message;
  }
}

We will return this object in XML format to REST webservice requester. We will use JAXB, which is by default supported in CXF. We annotate the MyMessage:

package test.bundle;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class MyMessage {

  private String message;

  public MyMessage() {
    // needed for JAXB
  }

  public MyMessage(String message) {
    this.message = message;
  }

  @XmlElement
  public String getMessage() {
    return message;
  }

  // needed for JAXB
  public void setMessage(String message) {
    this.message = message;
  }
}

Then we define a web service interface class MyService in test.bundle package:

package test.bundle;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("myService")
public interface MyService {

  @GET
  @Path("sayHello/{name}")
  @Produces(MediaType.TEXT_PLAIN)
  MyMessage sayHello(@PathParam("name") String name);
}

And we define an impelmentation for it in MyServiceImpl class in test.bundle.internal package:

package test.bundle.internal;

import test.bundle.MyMessage;
import test.bundle.MyService;

public class MyServiceImpl implements MyService {

  public MyMessage sayHello(String name) {
    return new MyMessage("Hello " + name);
  }
}

Then we have to do is to register custom message body providers. You can read more on those on CXF website. Create a class MyMessageWriterProvider in package test.bundle.internal:

package test.bundle.internal;

import java.io.IOException;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;

import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;

import test.bundle.MyMessage;

public class MyMessageWriterProvider implements MessageBodyWriter<MyMessage> {

  @Override
  public long getSize(MyMessage message, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
    return 0;
  }

  @Override
  public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
    return true;
  }

  @Override
  public void writeTo(MyMessage message, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType,
      MultivaluedMap<String, Object> headers, OutputStream os) throws IOException, WebApplicationException {
    try {
      JAXBContext context = JAXBContext.newInstance(type);
      Marshaller marshaller = context.createMarshaller();
      marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
      marshaller.marshal(message, os);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

And the last thing is to register it in the Activator

restProps.put("service.exported.interfaces", "*");
restProps.put("service.exported.configs", "org.apache.cxf.rs");
restProps.put("service.exported.intents", "HTTP");
restProps.put("org.apache.cxf.rs.provider", MyMessageWriterProvider.class.getName());
restProps.put("org.apache.cxf.rs.address", "http://localhost:8080/");
context.registerService(MyService.class.getName(), new MyServiceImpl(), restProps);

Now run mvn clean install on "felixLauncher" and "test.bundle" projects.

The last problem is that DOSGi single bundle distribution contains JAXB api inside. And that clashes with JRE JAXB api (since it is included in JRE). So you may see the following message:

javax.xml.bind.JAXBException: ClassCastException: attempting to cast bundle://1.0:36/javax/xml/bind/JAXBContext.class to jar:file:/C:/Program%20Files/Java/jdk1.6.0_21/jre/lib/rt.jar!/javax/xml/bind/JAXBContext.class.  Please make sure that you are specifying the proper ClassLoader.
	at javax.xml.bind.ContextFinder.handleClassCastException(ContextFinder.java:96)

There are some ways around this problem. But in this case will just modify cxf distributable. Go to bundle folder in "felixLauncher" project. Open cxf-dosgi-ri-singlebundle-distribution-1.2 jar and remove lib/org.apache.servicemix.specs.jaxb-api-2.1-1.3.0.jar jar. Then modify META-INF/MANIFEST.MF inside cxf single bundle distribution and add "javax.xml.bind.annotation,javax.xml.bind.annotation.adapters" to Import-Package section.

Run "Felix Launcher" (refer to the first post).

Go to http://localhost:8080/myService/sayHello/Max. You should see the following output:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<myMessage>
    <message>Hello Max</message>
</myMessage>

Here are the zipped sources.

Comments (0) Trackbacks (0)

Sorry, the comment form is closed at this time.

Trackbacks are disabled.