Tuesday, December 25, 2012

Creating REST based Webservice with Apache CXF

As you know, there are 3 types of webservice out there named, RPC/Encoded type, Document/Literal/SOA type and REST type. But, the most supported ones are the RPC and Document types.

REST type gives you a lot of flexibility. This is how to create the server and the corresponding client using Apache CXF.



Prerequisites

You should have installed:
  • Sun Java 6
  • Maven 3
OK, let's get started.

Creating a REST based Webservice

1) On Maven pom.xml please include:
<packaging>war</packaging>

<properties>
    <cxf.version>2.6.0</cxf.version>
    <spring.version>3.0.5.RELEASE</spring.version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

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

    <dependency>
        <groupId>org.apache.cxf</groupId>
        <artifactId>cxf-rt-transports-http</artifactId>
        <version>${cxf.version}</version>
        <scope>runtime</scope>
    </dependency>

    <!-- REST based WS: Required libraries during compile time since Java 6 
        doesn't include REST specification libraries. -->
    <dependency>
        <groupId>org.apache.servicemix.specs</groupId>
        <artifactId>org.apache.servicemix.specs.jsr311-api-1.1</artifactId>
        <version>1.6.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.servicemix.specs</groupId>
        <artifactId>org.apache.servicemix.specs.jaxb-api-2.1</artifactId>
        <version>1.6.0</version>
    </dependency>

    <!-- REST based WS: Required libraries. In OSGi environment, these may be provided -->
    <dependency>
        <groupId>org.apache.cxf</groupId>
        <artifactId>cxf-rt-frontend-jaxrs</artifactId>
        <version>${cxf.version}</version>
        <scope>runtime</scope>
    </dependency>
</dependencies>


2) Create an interface defining a webservice
The primary annotations for SOAP based WS are: @Path, @Produces and a group defining the REST methods (@POST, @PUT, etc)
import javax.ws.rs.FormParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;

@Path("/calculator")  // REST based WS: Path of this resource (or service)
@Produces({"application/xml", "application/json"})  // REST based WS: Response format
public interface CalculatorService {

    @POST  // REST based WS: Called using POST method
    @Path("/sum")  // REST based WS: Path of a sub resource
    public int sum(
        @FormParam(value = "a") // REST based WS: Define a form parameter (http://uri.com/sum?a=2)
            int a,
        @FormParam(value = "b")
            int b);

    @POST
    @Path("/algorithm/{code}")
    public Data process(
        @PathParam("code") // REST based WS: This argument is taken from a path
            String dCode,
            Data dData);
}

3) Add XML annotations for complex object
Default constructor is mandatory!!!
@XmlRootElement(name = "data")
public class Data {

  @XmlElement(name = "entry")
  public Collection getEntries() {
    return entries;
  }   

  public void setEntries(Collection entries) {
    this.entries = entries;
  }

}
@XmlRootElement
public class FieldEntry {
  // Create setter getter methods for 'field' and 'value'
}

4) Create the implementation
public class MyCalculatorServiceImpl implements CalculatorService

5) Create Spring context files
Inside resources directory (src/main/resources) create context files:
5.1) META-INF/spring/application-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
  xsi:schemaLocation="
  http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

  <bean id="calculatorService"
    class="michsan.research.cxf.wsserver.impl.MyCalculatorServiceImpl" />
</beans>

5.2) META-INF/spring/ws-context.xml (Watch for namespace!)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
  xmlns:jaxrs="http://cxf.apache.org/jaxrs"
  xsi:schemaLocation="
  http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
  http://cxf.apache.org/jaxrs http://cxf.apache.org/schemas/jaxrs.xsd">

  <import resource="classpath:META-INF/cxf/cxf.xml" />
  <!-- REST based WS and SOAP based WS: Required -->
  <import resource="classpath:META-INF/cxf/cxf-extension-*.xml" />

  <!--    if (Simple Web Application) { -->
   <import resource="classpath:META-INF/cxf/cxf-servlet.xml"/>
  <!--    } -->
 
  <!-- REST based WS: Root context -->
  <jaxrs:server id="wsServiceRoot" address="/rest">
    <jaxrs:serviceBeans>
      <ref bean="calculatorService" />
    </jaxrs:serviceBeans>
    <jaxrs:extensionMappings>
      <entry key="xml" value="application/xml" />
      <entry key="json" value="application/json" />
    </jaxrs:extensionMappings>
  </jaxrs:server>

</beans>


6) Create web.xml file
In src/main/webapp/WEB-INF/web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

  <display-name>CXF Calculator Service</display-name>
  <description>Learning how to create CXF Webservice server</description>

  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath*:META-INF/spring/*-context.xml</param-value>
  </context-param>

  <!-- Creates the Spring Container shared by all Servlets and Filters -->
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

  <servlet>
    <servlet-name>cxfServlet</servlet-name>
    <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>

  <servlet-mapping>
    <servlet-name>cxfServlet</servlet-name>
    <url-pattern>/ws/*</url-pattern>
  </servlet-mapping>

</web-app>


7) Deploy (or mvn tomcat:run)
8) See it running in http://localhost:8080/cxf-ws-server/ws

Consuming the REST based Webservice

1) On Maven pom.xml
<dependencies>
  <dependency>
    <artifactId>cxf-rt-frontend-jaxrs</artifactId>
    <groupId>org.apache.cxf</groupId>
    <version>2.6.0</version>
  </dependency>
</dependencies>

2) Understand how to invoke the service.
Usually the service provider gives you a page describing how to use the service. If this is the case: Data sent and received is in the following format
<data>
  <entry>
    <field>name</field>
    <value>JACK</value>
  </entry>
  <entry>
    <field>cif</field>
    <value>343434</value>
  </entry>
</data>
Next, you must use complex object Data (see previous post)
3) On static main:
Given that center of all REST services is on http://localhost:8080/cxf-ws-server/ws/rest and the exact REST resource is on calculator/algorithm/{NAME_OF_ALGORITHM} using POST method and XML format, then in main method:
String baseUri = "http://localhost:8080/cxf-ws-server/ws/rest";
WebClient client = WebClient.create(baseUri);
client.accept("application/xml");

// Service 1 ------------------------------
client.reset().path("calculator/sum");

Form form = new Form();
form.set("a", 4);
form.set("b", 9);

Response response = client.form(form);

if (response.getStatus() == 200) { // OK
    InputStream inStream = (InputStream) response.getEntity();
    BufferedReader reader = new BufferedReader(new InputStreamReader(
            inStream));
    System.out.println(reader.readLine());
    reader.close();
}


// Service 2 ------------------------------
client.reset().path("calculator/algorithm/INTEGRAL");

Data requestData = new Data();
requestData.add("arg1", "5");
requestData.add("arg2", "8");

Data responseData = client.post(requestData, Data.class);
// Print a response value
System.out.println(responseData.getEntries().iterator().next().getValue());

For testing the WS: http://niftybits.wordpress.com/2009/08/26/recipe-unit-testing-apache-cxf-restful-services/

2 comments:

ayu sari said...
This comment has been removed by the author.
ayu sari said...

Sehari-hari saya bekerja sebagai SPG menjual produk kosmetik terbaik di indonesia. Di sela waktu kerja saya browsing dengan hape untuk berkunjung ke berbagai blog. Saya mendapatkan banyak manfaat dan tambahan pengetahuan dengan membaca artikel seperti di blog ini. Trimakasih sudah berbagi.