Developers, Developers, Developers! Maksim Sorokin IT Blog

26Jul/11Off

Maven + Apache Felix + CXF: Creating a RESTful Webservice with CXF. Consuming an Object

This is the following post in Maven + Apache Felix + CXF + DOSGi series.

Here we continue working with RESTful webservices. In the last post I showed how to returne an Object in XML with JAXB, and this post will based on it. In this post I will show how to consume an object. The title is rather misleading, but what I meant is "consuming XML and converting it to an object". Again, it is assumed that you followed the setup from the first post in the series. You will find attached sources in the end of the post.

We will have the same Felix Launcher project as in the previous post. To remind you, we will use CXF DOSGi single bundle distribution, because it contains all the necessary dependencies inside its stomach. So here is pom.xml for the "felixLauncher" project (refer to the first post in series):

<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/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>dk.sorokin.maksim</groupId>
  <artifactId>felixLauncher</artifactId>
  <version>1.0.0-SNAPSHOT</version>

  <name>Felix Launcher</name>

  <properties>
    <felix.bundlerepository.version>1.6.4</felix.bundlerepository.version>
    <felix.gogo.version>0.8.0</felix.gogo.version>
    <felix.framework.version>3.2.2</felix.framework.version>
  </properties>

  <build>
    <plugins>
      <plugin>
        <artifactId>maven-clean-plugin</artifactId>
        <version>2.4.1</version>
        <configuration>
          <filesets>
            <fileset>
              <directory>bundle</directory>
            </fileset>
          </filesets>
        </configuration>
      </plugin>

      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-dependency-plugin</artifactId>
        <version>2.2</version>
        <executions>
          <execution>
            <id>copy</id>
            <phase>generate-resources</phase>
            <goals>
              <goal>copy</goal>
            </goals>
            <configuration>
              <artifactItems>
                <artifactItem>
                  <groupId>org.apache.cxf.dosgi</groupId>
                  <artifactId>cxf-dosgi-ri-singlebundle-distribution</artifactId>
                  <version>1.2</version>
                </artifactItem>
                <artifactItem>
                  <groupId>org.apache.felix</groupId>
                  <artifactId>org.apache.felix.gogo.command</artifactId>
                  <version>${felix.gogo.version}</version>
                </artifactItem>
                <artifactItem>
                  <groupId>org.apache.felix</groupId>
                  <artifactId>org.apache.felix.gogo.runtime</artifactId>
                  <version>${felix.gogo.version}</version>
                </artifactItem>
                <artifactItem>
                  <groupId>org.apache.felix</groupId>
                  <artifactId>org.apache.felix.gogo.shell</artifactId>
                  <version>${felix.gogo.version}</version>
                </artifactItem>
                <artifactItem>
                  <groupId>org.osgi</groupId>
                  <artifactId>org.osgi.compendium</artifactId>
                  <version>4.2.0</version>
                </artifactItem>
              </artifactItems>
              <outputDirectory>bundle</outputDirectory>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>

  <dependencies>
    <dependency>
      <groupId>org.apache.felix</groupId>
      <artifactId>org.apache.felix.main</artifactId>
      <version>${felix.framework.version}</version>
    </dependency>

    <dependency>
      <groupId>org.ops4j.pax.url</groupId>
      <artifactId>pax-url-assembly</artifactId>
      <version>1.3.3</version>
    </dependency>
  </dependencies>
</project>

Note, that we do the same workaround to downloaded CXF DOSGi single bundle as in previous post by deleting lib\org.apache.servicemix.specs.jaxb-api-2.1-1.3.0.jar jar and adding javax.xml.bind.annotation,javax.xml.bind.annotation.adapters to Import-Package in MANIFEST.MF. It is because there is a JAXB api included in single bundle distribution, which clashes with Java JAXB api (which is embedded in JRE). Otherwise you will see something like:

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.

Our test bundle will be called test.bundle, so config.properties file will be something like:

felix.auto.deploy.action=install,start
felix.log.level=1

org.osgi.framework.storage.clean=onFirstInit

felix.auto.start.1 = \
 assembly:/C:/projects/test.bundle/target/classes

Now, let's develop our test.bundle bundle, which would be able to consume XML and automatically transform it into an object. pom.xml file will look the same as in previous post:

<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/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>dk.sorokin.maksim</groupId>
  <artifactId>test.bundle</artifactId>
  <version>1.0.0-SNAPSHOT</version>

  <name>Test Bundle</name>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.felix</groupId>
        <artifactId>maven-bundle-plugin</artifactId>
        <executions>
          <execution>
            <id>generate-resources</id>
            <goals>
              <goal>manifest</goal>
            </goals>
            <configuration>
              <instructions>
                <Bundle-Name>${project.name}</Bundle-Name>
                <Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
                <Bundle-Activator>test.bundle.internal.Activator</Bundle-Activator>
                <Export-Package>test.bundle</Export-Package>
              </instructions>
            </configuration>
          </execution>
        </executions>
      </plugin>

      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-jar-plugin</artifactId>
        <version>2.3.1</version>
        <configuration>
          <archive>
            <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
          </archive>
        </configuration>
      </plugin>
    </plugins>
  </build>

  <dependencies>
    <dependency>
      <groupId>org.apache.felix</groupId>
      <artifactId>org.osgi.core</artifactId>
      <version>1.4.0</version>
    </dependency>

    <dependency>
      <groupId>org.apache.servicemix.specs</groupId>
      <artifactId>org.apache.servicemix.specs.jsr311-api-1.0</artifactId>
      <version>1.8.0</version>
    </dependency>
  </dependencies>
</project>

Now we create a class, an object of which we want to get. It will be test.bundle.MyMessage:

package test.bundle;

import java.io.StringReader;

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

@XmlRootElement
public class MyMessage {

  private int field;

  private String message;

  public MyMessage() {
    // needed for JAXB
  }

  // used when webservice is called with GET method
  public MyMessage(String query) {
    System.out.println(query);
    try {
      JAXBContext context = JAXBContext.newInstance(getClass());
      Unmarshaller unmarshaller = context.createUnmarshaller();
      StringReader sr = new StringReader(query);
      MyMessage request = (MyMessage) unmarshaller.unmarshal(sr);
      field = request.getField();
      message = request.getMessage();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

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

  @XmlElement
  public int getField() {
    return field;
  }

  // needed for JAXB
  public void setField(int field) {
    this.field = field;
  }

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

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

Notice MyMessage(String query) constructor. It is being called, when someone contacts a @GET webservice. If you will not have this constructor, you will see the following message, when you will call a webservice expecting to get this class:

Parameter Class test.bundle.MyMessage has no constructor with single String parameter, static valueOf(String) or fromString(String) methods

The interface for the webservice:

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("saySomething/{name}")
  @Produces(MediaType.TEXT_PLAIN)
  String saySomething(@PathParam("name") MyMessage message);
}

And implementation for it:

package test.bundle.internal;

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

public class MyServiceImpl implements MyService {

  public String saySomething(MyMessage message) {
    System.out.println(message);
    return "I got this message: " + message.getMessage() + "  and an integer: " + message.getField();
  }
}

Activator for test.bundle:

package test.bundle.internal;

import java.util.Dictionary;
import java.util.Hashtable;

import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;

import test.bundle.MyService;

public class Activator implements BundleActivator {

  public void start(BundleContext context) throws Exception {
    Dictionary<String, String> restProps = new Hashtable<String, String>();

    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.address", "http://localhost:8080/");
    context.registerService(MyService.class.getName(), new MyServiceImpl(), restProps);
  }

  public void stop(BundleContext context) throws Exception {
    //
  }
}

Now run mvn install on test.bundle project and launch "felixLauncher". Then go to http://localhost:8080/myService/saySomething/%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22UTF-8%22%20standalone%3D%22yes%22%3F%3E%3CmyMessage%3E%3Cfield%3E1%3C%2Ffield%3E%3Cmessage%3EHello%2C%20Max%3C%2Fmessage%3E%3C%2FmyMessage%3E and you should see the following output:

I got this message: Hello, Max  and an integer: 1

Here are the sources

Comments (0) Trackbacks (0)

Sorry, the comment form is closed at this time.

Trackbacks are disabled.