Developing and Testing Java RESTful Web Service - Part 1
In this document, I would like to outline the process of developing and testing a REST web service.
There are several JAX-RS Implementations, in this article, I choose Apache CXF:
1. CXF supports transformation of primitive types.
2. CXF supports development of both REST and SOAP styles web service.
1. Create the project
Run the following maven command to create a simple web application.
mvn archetype:create -DarchetypeGroupId=org.apache.maven.archetypes -DarchetypeArtifactId=maven-archetype-webapp -DgroupId=com.codeexample -DartifactId=jax-rs-example
Then edit pom.xml to add third-party libraries of the project:
<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.codeexample</groupId>
<artifactId>jax-rs-example</artifactId>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>
<name>jax-rs-example Maven Webapp</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.version>3.0.4.RELEASE</spring.version>
<cxf.version>2.3.3</cxf.version>
<jetty.version>6.1.25</jetty.version>
<slf4j.version>1.6.1</slf4j.version>
</properties>
<dependencies>
<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-transports-http-jetty</artifactId>
<version>${cxf.version}</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.1.1</version>
</dependency>
<!-- spring begin -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- spring end -->
<!-- logging begin -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.16</version>
<exclusions>
<exclusion>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
</exclusion>
<exclusion>
<groupId>javax.jms</groupId>
<artifactId>jms</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jdmk</groupId>
<artifactId>jmxtools</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jmx</groupId>
<artifactId>jmxri</artifactId>
</exclusion>
<exclusion>
<groupId>oro</groupId>
<artifactId>oro</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- logging end -->
<!-- jetty -->
<dependency>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty</artifactId>
<version>${jetty.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jsp-2.1-jetty</artifactId>
<version>${jetty.version}</version>
<scope>test</scope>
</dependency>
<!-- pojo copy (usually for webservice) -->
<dependency>
<groupId>net.sf.dozer</groupId>
<artifactId>dozer</artifactId>
<version>5.3.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8.2</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<finalName>jax-rs-example</finalName>
<plugins>
<!-- enable java 6 -->
<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>
</project>
Incorporate cxf-rt-frontend-jaxrs would in turn pull in other CXF modules such cxf-api, cxf-rt-core, cxf-rt-transports-http and cxf-rt-bindings-xml as well as the following 3rd-party dependencies: javax.ws.rs.jsr311-api, org.springframework/spring-core/3.0.5-RELEASE (and other core Spring dependencies).
The only thing needed to be noticed is the use of ‘exclusions’, this is explained in this article.
2. Define web service interface and implementation class:
package org.codeexample.rs.server;
public interface FriendWebService {
public static final String CHARSET = ";charset=UTF-8";
public abstract List getFriends(Long userId);
public abstract PersonDTO getFriend(Long userId, Long friendId);
public abstract Long addFirend(Long userId, PersonDTO friendDTO);
public abstract Response removeFirend(Long userId, Long friendId);
}
package org.codeexample.rs.server;
@Component @Path("/friends") public class FriendWebRSServiceImpl implements
FriendWebService {
@Autowired private FriendService friendService;
@Autowired private DozerBeanMapper dozer;
private static Logger logger = LoggerFactory
.getLogger(FriendWebRSServiceImpl.class);
@GET @Path("{userId}") @Produces({ MediaType.APPLICATION_JSON,
MediaType.APPLICATION_XML + CHARSET }) public List getFriends(
@PathParam("userId") Long userId) {
try {
List friends = friendService.getFriends(userId);
List friendDTOs = new ArrayList();
for (Person friend : friends) {
friendDTOs.add(dozer.map(friend, PersonDTO.class));
}
return friendDTOs;
} catch (UserNotFoundException e) {
String message = "Unable to find " + userId;
logger.error(message, e);
throw buildException(Status.NOT_FOUND.getStatusCode(), message);
} catch (RuntimeException e) {
logger.error(e.getMessage(), e);
throw new WebApplicationException();
}
}
@GET @Path("{userId}/{friendId}") @Produces({ MediaType.APPLICATION_JSON,
MediaType.APPLICATION_XML + CHARSET }) public PersonDTO getFriend(
@PathParam("userId") Long userId,
@PathParam("friendId") Long friendId) {
try {
Person friend = friendService.getFriend(userId, friendId);
return dozer.map(friend, PersonDTO.class);
} catch (UserNotFoundException e) {
String message = "Unable to find " + userId + " or " + friendId;
logger.error(message, e);
throw buildException(Status.NOT_FOUND.getStatusCode(), message);
} catch (RuntimeException e) {
logger.error(e.getMessage(), e);
throw new WebApplicationException();
}
}
@POST @Path("{userId}") @Consumes({ MediaType.APPLICATION_JSON,
MediaType.APPLICATION_XML, MediaType.APPLICATION_XML + CHARSET }) @Produces({
MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML + CHARSET }) public Long addFirend(
@PathParam("userId") Long userId, PersonDTO friendDTO) {
try {
Person friend = dozer.map(friendDTO, Person.class);
return friendService.addFirend(userId, friend);
} catch (UserNotFoundException e) {
String message = "Unable to find " + userId;
logger.error(message, e);
throw buildException(Status.NOT_FOUND.getStatusCode(), message);
} catch (RuntimeException e) {
logger.error(e.getMessage(), e);
throw new WebApplicationException();
}
}
@DELETE @Path("{userId}/{friendId}") public Response removeFirend(
@PathParam("userId") Long userId,
@PathParam("friendId") Long friendId) {
try {
friendService.removeFirend(userId, friendId);
return Response.ok().build();
} catch (UserNotFoundException e) {
String message = "Unable to find " + userId;
logger.error(message, e);
throw buildException(Status.NOT_FOUND.getStatusCode(), message);
} catch (RuntimeException e) {
logger.error(e.getMessage(), e);
throw new WebApplicationException();
}
}
public void setFriendService(FriendService friendService) {
this.friendService = friendService;
}
private WebApplicationException buildException(int status, String message) {
return new WebApplicationException(Response.status(status)
.entity(message).type(MediaType.TEXT_PLAIN).build());
}
}
The implementation class is annotated with @Component, as we want Spring to autowire its dependency, also annotated with JAX-RS application, such as @Path, @GET, @POST, @Consumes, @Produces, @PathParam to bind specific URI patterns and HTTP operations to individual methods of your Java class.
3. Service Class:
package org.codeexample.service;
public interface FriendService {
public List getFriends(Long userId) throws UserNotFoundException;
public Person getFriend(Long userId, Long friendId)
throws UserNotFoundException;
public Long addFirend(Long userId, Person friend)
throws UserNotFoundException;
public void removeFirend(Long userId, Long friendId)
throws UserNotFoundException;
}
package org.codeexample.service;
@Component public class FriendServiceImpl implements FriendService {
private static Map persons = new HashMap();
private static Long personIdSequence = 0L;
static {
Person jerry = new Person("Jerry", "Weinberg", "fake1@gmail.com",
Person.GENDER_MALE);
jerry.setId(increasePersonIdSequence());
Map friends = new HashMap();
Person paul = new Person("Paul", "Graham", "fake2@gmail.com",
Person.GENDER_MALE);
paul.setId(increasePersonIdSequence());
friends.put(paul.getId(), paul);
Person david = new Person("David", "Wood", "fake3@gmail.com",
Person.GENDER_MALE);
david.setId(increasePersonIdSequence());
friends.put(david.getId(), david);
jerry.setFriends(friends);
persons.put(jerry.getId(), jerry);
}
@Override public List getFriends(Long userId)
throws UserNotFoundException {
if (persons.containsKey(userId)) {
List friends = new ArrayList();
friends.addAll(persons.get(userId).getFriends().values());
return friends;
} else {
throw new UserNotFoundException(userId);
}
}
@Override public Person getFriend(Long userId, Long friendId)
throws UserNotFoundException {
if (persons.containsKey(userId)) {
Map friends = persons.get(userId).getFriends();
if (friends.containsKey(friendId)) {
return friends.get(friendId);
} else {
throw new UserNotFoundException(friendId);
}
} else {
throw new UserNotFoundException(userId);
}
}
@Override public Long addFirend(Long userId, Person friend)
throws UserNotFoundException {
if (persons.containsKey(userId)) {
friend.setId(increasePersonIdSequence());
persons.get(userId).getFriends().put(friend.getId(), friend);
return friend.getId();
} else {
throw new UserNotFoundException(userId);
}
}
@Override public void removeFirend(Long userId, Long friendId)
throws UserNotFoundException {
if (persons.containsKey(userId)) {
Map friends = persons.get(userId).getFriends();
if (friends.containsKey(friendId)) {
friends.remove(friendId);
} else {
throw new UserNotFoundException(friendId);
}
} else {
throw new UserNotFoundException(userId);
}
}
private static synchronized Long increasePersonIdSequence() {
return ++personIdSequence;
}
}
4. Domain entity class and DTO:
package org.codeexample.entity;
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private String firstName, FamilyName, email;
private int gender = GENDER_UNKOWN;
private Map friends = new HashMap();
public static int GENDER_UNKOWN = -1;
public static int GENDER_MALE = 0;
public static int GENDER_FEMALE = 1;
public Person() {}
public Person(String firstName, String familyName, String email, int gender) {
super();
this.firstName = firstName;
FamilyName = familyName;
this.email = email;
this.gender = gender;
}
}
package org.codeexample.rs.dto;
@XmlRootElement(name = "person", namespace = "com.codeexample") public class PersonDTO
implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private String firstName;
private String FamilyName;
private String email;
private int gender = Person.GENDER_UNKOWN;
public PersonDTO() {}
public PersonDTO(String firstName, String familyName, String email,
int gender) {
super();
this.firstName = firstName;
FamilyName = familyName;
this.email = email;
this.gender = gender;
}
@Override public String toString() {
return ToStringBuilder.reflectionToString(this);
}
}
5. Configuration Files:
src/main/webapp/WEB-INF/web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
version="2.4"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<display-name>jax-rs-example</display-name>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath*:/applicationContext-rs-server.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>
</servlet>
<servlet-mapping>
<servlet-name>CXFServlet</servlet-name>
<url-pattern>/cxf/*</url-pattern>
</servlet-mapping>
</web-app>
src/main/resources/applicationContext-rs-server.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:cxf="http://cxf.apache.org/core" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jaxrs="http://cxf.apache.org/jaxrs" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://cxf.apache.org/core
http://cxf.apache.org/schemas/core.xsd
http://cxf.apache.org/jaxrs
http://cxf.apache.org/schemas/jaxrs.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd"
default-lazy-init="false">
<import resource="classpath:META-INF/cxf/cxf.xml" />
<import resource="classpath:META-INF/cxf/cxf-extension-jaxrs-binding.xml" />
<import resource="classpath:META-INF/cxf/cxf-servlet.xml" />
<!-- use this and comment out the previous line if test web service in cxf
embedded server using JAXRSServerFactoryBean -->
<!-- <import resource="classpath:META-INF/cxf/cxf-extension-http-jetty.xml"
/> -->
<bean id="dozer" class="org.dozer.DozerBeanMapper" />
<bean id="friendWebServiceImpl" class="org.codeexample.rs.server.FriendWebRSServiceImpl" />
<jaxrs:server id="friendWebRSService" address="/">
<jaxrs:serviceBeans>
<ref bean="friendWebServiceImpl" />
</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>
<context:component-scan base-package="org.codeexample" />
</beans>
Resources