Thursday, April 12, 2012

Part 2: RESTful WS with Apache CXF - JSON

Overview:
This is Part 2 of the article on RESTful WS with Apache CXF. If you haven't looked at Part 1, I suggest you to do so.

This article looks at how you can do JSON/HTTP based RESTful WS with Apache CXF. We're using the CXF support on JAX-RS (JSR-311) for this. The sample code can be found here.

Prerequisite:JDK >= 1.5.x
Maven >= 2.2.x

Project Structure: Following is the project structure I have used (Maven WAR project).


Step 1: Maven Dependencies (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>org.fazlan</groupId> 
  <artifactId>org.fazlan.employee.cxf.jsonrest.service</artifactId> 
  <packaging>war</packaging> 
  <version>1.0-SNAPSHOT</version> 
  <name>org.fazlan.employee.cxf.jsonrest.service</name> 
  <url>http://maven.apache.org</url> 
  <build> 
    <finalName>CXFEmployeeJsonRESTService</finalName> 
    <plugins> 
      <plugin> 
        <groupId>org.apache.maven.plugins</groupId> 
        <artifactId>maven-compiler-plugin</artifactId> 
        <configuration> 
          <source>1.6</source> 
          <target>1.6</target> 
        </configuration> 
      </plugin> 
    </plugins> 
  </build> 
  <dependencies> 
    <dependency> 
      <groupId>junit</groupId> 
      <artifactId>junit</artifactId> 
      <version>3.8.1</version> 
      <scope>test</scope> 
    </dependency> 
    <!-- Spring framework --> 
    <dependency> 
      <groupId>org.springframework</groupId> 
      <artifactId>spring-core</artifactId> 
      <version>${org.springframework.version}</version> 
    </dependency> 
    <dependency> 
      <groupId>org.springframework</groupId> 
      <artifactId>spring-web</artifactId> 
      <version>${org.springframework.version}</version> 
    </dependency> 
    <dependency> 
      <groupId>junit</groupId> 
      <artifactId>junit</artifactId> 
      <version>4.8.1</version> 
    </dependency> 
    <dependency> 
      <groupId>org.apache.cxf</groupId> 
      <artifactId>cxf-rt-frontend-jaxrs</artifactId> 
      <version>${cxf.version}</version> 
    </dependency> 
    <dependency> 
      <groupId>org.apache.cxf</groupId> 
      <artifactId>cxf-rt-databinding-aegis</artifactId> 
      <!-- 2.4.4 or 2.5.0 --> 
      <version>${cxf.version}</version> 
    </dependency> 
    <dependency> 
      <groupId>org.apache.xmlbeans</groupId> 
      <artifactId>xmlbeans</artifactId> 
      <version>2.4.0</version> 
    </dependency> 
  </dependencies> 
  <properties> 
    <org.springframework.version>3.1.1.RELEASE</org.springframework.version> 
    <cxf.version>2.4.6</cxf.version> 
  </properties> 
</project> 

Step 2: Creating POJO Classes and Service Implementation

Employee.java
 package org.fazlan.employee.cxf.jsonrest.service.beans; 
import javax.xml.bind.annotation.XmlRootElement; 
import javax.xml.bind.annotation.XmlType; 
import java.io.Serializable; 
@XmlType( 
    name = "EmployeeType", 
    namespace = "org.fazlan.employee.cxf.jsonrest.service.beans", 
    propOrder = {"id", "firstName", "lastName"}) 
@XmlRootElement( 
    name = "Employee", 
    namespace = "org.fazlan.employee.cxf.jsonrest.service.beans") 
public class Employee implements Serializable { 
  private Long id; 
  private String firstName; 
  private String lastName; 
  public Employee() { 
  } 
  public Employee(Long id, String firstName, String lastName) { 
    this.id = id; 
    this.firstName = firstName; 
    this.lastName = lastName; 
  } 
  public Long getId() { 
    return id; 
  } 
  public void setId(Long id) { 
    this.id = id; 
  } 
  public String getFirstName() { 
    return firstName; 
  } 
  public void setFirstName(String firstName) { 
    this.firstName = firstName; 
  } 
  public String getLastName() { 
    return lastName; 
  } 
  public void setLastName(String lastName) { 
    this.lastName = lastName; 
  } 
  public boolean equals(Object o) { 
    if (this == o) return true; 
    if (!(o instanceof Employee)) return false; 
    Employee employee = (Employee) o; 
    return !(firstName != null ? !firstName.equals(employee.firstName) : employee.firstName != null) && 
        !(id != null ? !id.equals(employee.id) : employee.id != null) && 
        !(lastName != null ? !lastName.equals(employee.lastName) : employee.lastName != null); 
  } 
  public int hashCode() { 
    int result = id != null ? id.hashCode() : 0; 
    result = 31 * result + (firstName != null ? firstName.hashCode() : 0); 
    result = 31 * result + (lastName != null ? lastName.hashCode() : 0); 
    return result; 
  } 
  @Override 
  public String toString() { 
    return "Employee{" + 
        "id=" + id + 
        ", firstName='" + firstName + '\'' + 
        ", lastName='" + lastName + '\'' + 
        '}'; 
  } 
} 

Employees.java - This is used to hold a collection object of Employee.java
 package org.fazlan.employee.cxf.jsonrest.service.beans; 
import javax.xml.bind.annotation.XmlElement; 
import javax.xml.bind.annotation.XmlElementWrapper; 
import javax.xml.bind.annotation.XmlRootElement; 
import javax.xml.bind.annotation.XmlType; 
import java.util.Collection; 
@XmlType( 
    name = "EmployeesType", 
    namespace = "org.fazlan.employee.cxf.jsonrest.service.beans") 
@XmlRootElement( 
    name = "Employees", 
    namespace = "org.fazlan.employee.cxf.jsonrest.service.beans") 
public class Employees { 
  private Collection<Employee> employees; 
  public Employees() { 
  } 
  public Employees(Collection<Employee> employees) { 
    setEmployees(employees); 
  } 
  @XmlElement(name = "employee",required = true) 
  @XmlElementWrapper(name = "employees") 
  public Collection<Employee> getEmployees() { 
    return employees; 
  } 
  public void setEmployees(Collection<Employee> employees) { 
    this.employees = employees; 
  } 
} 

IEmployeeService.java - Service Interface
 package org.fazlan.employee.cxf.jsonrest.service.services; 
import org.fazlan.employee.cxf.jsonrest.service.beans.Employee; 
import org.fazlan.employee.cxf.jsonrest.service.beans.Employees; 
import javax.ws.rs.core.Response; 
public interface IEmployeeService { 
  Employee get(Long id); 
  Employees getAll(); 
  Response add(Employee employee); 
  Response update(Employee employee); 
  Response delete(Long id); 
} 


EmployeeService.java - Implementation of the service.

NOTE: This service class has been annotated to produce and consume certain methods in JSON(application/json) as the mime-type. Read more about Apache CXF - JAX-RS basics.

 package org.fazlan.employee.cxf.jsonrest.service.services; 
import org.fazlan.employee.cxf.jsonrest.service.beans.Employee; 
import org.fazlan.employee.cxf.jsonrest.service.beans.Employees; 
import javax.ws.rs.*; 
import javax.ws.rs.core.Response; 
import java.util.HashMap; 
import java.util.Map; 
@Path("/employeeservice/") 
@Produces("application/json") 
public class EmployeeService implements IEmployeeService { 
  private static final Map<Long, Employee> EMPLOYEE_MAP = new HashMap<Long, Employee>(); 
  private static long index = 3L; 
  static { 
    EMPLOYEE_MAP.put(1L, new Employee(1L, "First Name 1", "Last Name 1")); 
    EMPLOYEE_MAP.put(2L, new Employee(2L, "First Name 2", "Last Name 2")); 
  } 
  @GET 
  @Path("/get/{id}") 
  public Employee get(@PathParam("id") Long id) { 
    return EMPLOYEE_MAP.get(id); 
  } 
  @GET 
  @Path("/getall/") 
  public Employees getAll() { 
    return new Employees(EMPLOYEE_MAP.values()); 
  } 
  @POST 
  @Path("/add/") 
  @Consumes("application/json") 
  public Response add(Employee employee) { 
    System.out.println("Adding :" + employee); 
    employee.setId(index++); 
    update(employee); 
    return Response.status(Response.Status.OK).build(); 
  } 
  @PUT 
  @Path("/update/") 
  @Consumes("application/json") 
  public Response update(Employee employee) { 
    System.out.println("Updating :" + employee); 
    EMPLOYEE_MAP.put(employee.getId(), employee); 
    return Response.status(Response.Status.OK).build(); 
  } 
  @DELETE 
  @Path("/delete/{id}/") 
  public Response delete(@PathParam("id") Long id) { 
    Employee e = EMPLOYEE_MAP.remove(id); 
    System.out.println("Deleted :" + e); 
    return Response.status(Response.Status.OK).build(); 
  } 
} 


Read more about Apache CXF - JAX-RS basics.

Step 3: Exposing the Service Beans as RESTful Web Service
 <?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:jaxrs="http://cxf.apache.org/jaxrs" 
    xmlns:cxf="http://cxf.apache.org/core" 
    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 
              http://cxf.apache.org/core 
              http://cxf.apache.org/schemas/core.xsd"> 
  <!-- do not use import statements if CXFServlet init parameters link to this beans.xml --> 
  <import resource="classpath:META-INF/cxf/cxf.xml"/> 
  <import resource="classpath:META-INF/cxf/cxf-servlet.xml"/> 
  <jaxrs:server id="employeeService" address="/"> 
    <jaxrs:serviceBeans> 
      <ref bean="employeeServiceBean"/> 
    </jaxrs:serviceBeans> 
    <jaxrs:extensionMappings> 
      <entry key="xml" value="application/xml" /> 
      <entry key="json" value="application/json" /> 
    </jaxrs:extensionMappings> 
    <jaxrs:features> 
      <cxf:logging/> 
    </jaxrs:features> 
  </jaxrs:server> 
  <bean id="employeeServiceBean" class="org.fazlan.employee.cxf.jsonrest.service.services.EmployeeService"/> 
</beans> 

Step 4: Updating the web.xml to Register the CXF Servlet.
 <!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>CXF Web Service Web Application</display-name>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:spring/app-context.xml</param-value>
</context-param>
<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>/*</url-pattern>
</servlet-mapping>
</web-app>
Step 5: Building the Project
mvn clean install

Step 6: Deploying the Service
Now you can deploy this service on to any container. Here we'll deploy this on Tomcat server.

Step 7: Testing the RESTful Web Service using CURL

We're going to test our Web Service using CURL, which I found as a very easy way to send REST Calls. I found this very nice blog about RESTing with Curl.

Installing CURL

sudo apt-get install curl

-I am using Ubuntu, if you're using Win or Mac, please do the necessary to install curl.

Now, after successfully installing CURL, we are ready to test the services.

GET Resources:
- Retrieving all the employee resources.
 curl -v -H "Accept: application/json" http://localhost:8080/CXFEmployeeJsonRESTService/employeeservice/getall

- Retrieving a employee resource for a given id(id = 1).
 curl -v -H "Accept: application/json" http://localhost:8080/CXFEmployeeJsonRESTService/employeeservice/get/1

POST a Resource: Adding a new employee resource.
- Adding the new employee record in the add.json file.
 { 
  "ns1.Employee":{ 
    "id":1, 
    "firstName":"First Name1", 
    "lastName":"Last Name1" 
  } 
} 

 curl -v -H "Accept: application/json" -H "Content-type: application/json" -X POST -d @add.json http://127.0.0.1:8080/CXFEmployeeJsonRESTService/employeeservice/add


PUT a Resource: Updating an existing employee resource.
- Updating an existing employee record in the update.json file.
 { 
  "ns1.Employee":{ 
    "id":1, 
    "firstName":"Updated First Name1", 
    "lastName" :"Updated Last Name1" 
  } 
} 

 curl -v -H "Accept: application/json" -H "Content-type: application/json" -X PUT -d @update.json http://127.0.0.1:8080/CXFEmployeeJsonRESTService/employeeservice/update/

DELETE a Resource: Deleting an employee for a given id (id=2)
 curl -i -H "Accept: application/json" -X DELETE http://localhost:8080/CXFEmployeeJsonRESTService/employeeservice/delete/2/

Summary:
This tutorial looked at how we can create JSON RESTful Web Service using Apache CXF and test the services using CURL. You can find the sample code here.

7 comments:

  1. Hi there,

    I have an REST/XML service. Now, I was told to expose another REST/JSON service in addition to REST/XML one.

    But, I am getting Invalid JSON Namespace error. The XML Request/Response POJOs have a lot of Namespaces defined and I am only supposed to reuse them.

    Could you please let me know if there is a way to do this?

    I am attaching a part of my stacktrace for your reference.

    Caused by: java.lang.IllegalStateException: Invalid JSON namespace: http://soaservices.webex.com/EnterpriseObjects/EBO/Subscription/V1
    at org.codehaus.jettison.mapped.MappedNamespaceConvention.getJSONNamespace(MappedNamespaceConvention.java:182)
    at org.codehaus.jettison.mapped.MappedNamespaceConvention.createKey(MappedNamespaceConvention.java:189)
    at org.codehaus.jettison.mapped.MappedXMLStreamWriter.writeStartElement(MappedXMLStreamWriter.java:244)

    ReplyDelete
  2. I was able to deploy in Tomcat as you show this example , I am using windows , but I can't test it, is there any alternative using SOAP UI or other client service?

    ReplyDelete
  3. I'm working in windows. I can´t test with other tool like soap ui u other client rest? I've already deployed on tomcat . but I don't know how test the web service.
    best regards

    ReplyDelete
    Replies
    1. Firstly, if you simply wish to test the functionality of your code, then your Unit tests should be independent of frameworks - (read on Robert Martin a.k.a Uncle Bob's - Clean architecture). However, please not this article was not written with TDD(Test Driven Development) in mind - Nowhere near TDD.

      To answer your question, you can use JBoss RestEasy(http://www.jboss.org/resteasy).
      Refer,
      http://fazlansabar.blogspot.com.au/2012/12/restful-with-resteasy-part-14-client.html

      and also, the prequel to the same article http://fazlansabar.blogspot.com.au/2012/12/restful-with-resteasy-part-14-creating.html

      Cheers,
      Fazlan

      Delete
  4. you have created 2 different example for XML and for Json, can we make this in Single file which accepts(produces/cosumes) both json and xml to gether?

    ReplyDelete