Developing and Testing Java RESTful Web Service - Part 2
There are 2 ways to run our web service, run it in an embedded Jetty server or use CXF JAXRSServerFactoryBean, the former one is preferred as it can do much more such as load default data before test, and clean data after test.
1. Start an embedded Jetty server
public class JettyUtils {
public static final int PORT = 8080;
public static final String CONTEXT = "/rs-example";
public static final String BASE_URL = "http://localhost:" + PORT + "/" + CONTEXT;
public static Server buildTestServer(int port, String contextPath) {
Server server = new Server(port);
WebAppContext webContext = new WebAppContext("src/main/webapp/",
contextPath);
webContext.setClassLoader(Thread.currentThread()
.getContextClassLoader());
server.setHandler(webContext);
server.setStopAtShutdown(true);
// we also copy web.xml to src/test/resources
((WebAppContext) server.getHandler())
.setDescriptor("src/test/resources/web.xml");
return server;
}
public static void main(String[] args) throws Exception {
Server server = buildTestServer(PORT, CONTEXT);
server.start();
System.out.println("Hit Enter in console to stop server");
if (System.in.read() != 0) {
server.stop();
System.out.println("Server stopped");
}
}
}
During development, we can run this program to start Jetty server, and use Firefox Plugin poster to test our web service manually. After install the Firefox plugin, go to Tools->Poster (or click ctrl+alt+P) to open the poster window. In the window, you can send all kinds (Get, Post, Delete and etc) of requests.
2. Run Functional Test in the embedded Jetty server
package org.codeexample.functional.rs;
@Ignore public class BaseFunctionalTestCase {
protected static final String BASE_URL = JettyUtils.BASE_URL;
private static Server server;
@BeforeClass public static void start() throws Exception {
server = JettyUtils
.buildTestServer(JettyUtils.PORT, JettyUtils.CONTEXT);
server.start();
}
@AfterClass public static void stop() throws Exception {
server.stop();
}
}
Other tasks can be done in the start and stop methods.
package org.codeexample.functional.rs;
public class FriendWebServiceClientTest extends BaseFunctionalTestCase {
private static FriendWebServiceClient friendWebServiceClient;
@BeforeClass public static void setUpClient() throws Exception {
friendWebServiceClient = new FriendWebServiceCxfClient();
friendWebServiceClient.setBaseUrl(BASE_URL);
}
@Test public void addFirend() {
PersonDTO Steve = new PersonDTO("Steve", "Baker",
"fakeSteve@gmail.com", Person.GENDER_MALE);
Long friendId = friendWebServiceClient.addFriend(1L, Steve);
System.out.println(friendId);
// Remove the added friend
friendWebServiceClient.removeFriend(1L, friendId);
}
@Test public void getFriends() {
List persons = friendWebServiceClient.getFriends(1L); Assert.assertEquals(2, persons.size());
System.err.println(persons);
}
@Test public void getFriend() {
PersonDTO friend = friendWebServiceClient.getFriend(1L, 2L);
Assert.assertNotNull(friend);
Assert.assertEquals("Paul", friend.getFirstName());
Assert.assertEquals("Graham", friend.getFamilyName());
System.out.println(friend);
}
}
3. Develop Web Service Client
There can be many ways to access RESTful web service: use 1. Apache CXF WebClient, 2. Apache HttpClient, 3. HttpURLConnection, Apache CXF WebClient is the simplest way, Apache HttpClient and HttpURLConnection can be used in other situations, such as in old JDK or J2SE environment.
package org.codeexample.rs.client;
public interface FriendWebServiceClient {
public abstract void setBaseUrl(String baseUrl);
public abstract List getFriends(Long userId); public abstract PersonDTO getFriend(Long userId, Long friendId);
public abstract Long addFriend(Long userId, PersonDTO friendDTO);
public abstract void removeFriend(Long userId, Long friendId);
}
We can choose one that fits our requirements best.
3.1 Use Apache CXF WebClient to Access Web Service
WebClient uses the HTTP-centric approach to communicate with the RESFTful service.
package org.codeexample.rs.client;
public class FriendWebServiceCxfClient implements FriendWebServiceClient {
private String baseUrl;
@Required public void setBaseUrl(String baseUrl)
{this.baseUrl = baseUrl;}
public List getFriends(Long userId) { List personDTO = new ArrayList(); Collection collection = (Collection) WebClient .create(baseUrl).path("friends/" + userId)
.accept(MediaType.APPLICATION_XML)
.getCollection(PersonDTO.class);
personDTO.addAll(collection);
return personDTO;
}
public PersonDTO getFriend(Long userId, Long friendId) {
PersonDTO personDTO = WebClient.create(baseUrl)
.path("friends/" + userId + "/" + friendId)
.accept(MediaType.APPLICATION_XML).get(PersonDTO.class);
return personDTO;
}
public Long addFriend(Long userId, PersonDTO friendDTO) {
Long firendId = WebClient.create(baseUrl).path("friends/" + userId)
.accept(MediaType.APPLICATION_XML).post(friendDTO, Long.class);
return firendId;
}
public void removeFriend(Long userId, Long friendId) {
WebClient.create(baseUrl).path("friends/" + userId + "/" + friendId)
.accept(MediaType.APPLICATION_XML).delete();
}
}
3.2 Use Apache Http Client to access Web Service
package org.codeexample.rs.client;
public class FriendWebServiceApacheHttpClient implements FriendWebServiceClient {
private String baseUrl;
@Required public void setBaseUrl(String baseUrl)
{ this.baseUrl = baseUrl; }
public List<PersonDTO> getFriends(Long userId) {
DefaultHttpClient httpClient = new DefaultHttpClient();
List<PersonDTO> friends = null;
HttpGet httpGet = new HttpGet(baseUrl + "/friends/" + userId);
httpGet.addHeader("accept", "application/xml");
ResponseHandler<List<PersonDTO>> responseHandler = new ResponseHandler<List<PersonDTO>>() {
public List<PersonDTO> handleResponse(HttpResponse response)
throws ClientProtocolException, IOException {
HttpEntity entity = response.getEntity();
if (entity != null) {
JAXBContext context;
try {
context = JAXBContext.newInstance(PersonDTO.class);
String str = EntityUtils.toString(entity);
StringReader sr = new StringReader(str);
// TODO this would cause UnmarshalException, fix it.
// javax.xml.bind.UnmarshalException: unexpected element
// (uri:"com.codeexample", local:"persons"). Expected
// elements are <{com.codeexample}person>
return (List<PersonDTO>) context.createUnmarshaller()
.unmarshal(sr);
} catch (JAXBException e) {
e.printStackTrace();
return null;
}
} else {
return new ArrayList<PersonDTO>();
}
}
};
try {
friends = httpClient.execute(httpGet, responseHandler);
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
httpClient.getConnectionManager().shutdown();
}
return friends;
}
public PersonDTO getFriend(Long userId, Long friendId) {
DefaultHttpClient httpClient = new DefaultHttpClient();
PersonDTO friend = null;
HttpGet httpGet = new HttpGet(baseUrl + "/friends/" + userId + "/"
+ friendId);
httpGet.addHeader("accept", "application/xml");
ResponseHandler<PersonDTO> responseHandler = new ResponseHandler<PersonDTO>() {
public PersonDTO handleResponse(HttpResponse response)
throws ClientProtocolException, IOException {
HttpEntity entity = response.getEntity();
if (entity != null) {
JAXBContext context;
try {
context = JAXBContext.newInstance(PersonDTO.class);
String str = EntityUtils.toString(entity);
StringReader sr = new StringReader(str);
return (PersonDTO) context.createUnmarshaller()
.unmarshal(sr);
} catch (JAXBException e) {
e.printStackTrace();
return null;
}
} else {
return null;
}
}
};
try {
friend = httpClient.execute(httpGet, responseHandler);
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
httpClient.getConnectionManager().shutdown();
}
return friend;
}
public Long addFriend(Long userId, PersonDTO friendDTO) {
Long friendId = null;
DefaultHttpClient httpClient = new DefaultHttpClient();
HttpPost httpPost = new HttpPost(baseUrl + "/friends/" + userId);
httpPost.addHeader("accept", "application/xml");
try {
StringWriter sw = new StringWriter();
JAXBContext context = JAXBContext.newInstance(PersonDTO.class);
context.createMarshaller().marshal(friendDTO, sw);
System.err.println(sw.toString());
StringEntity entity = new StringEntity(sw.toString());
entity.setContentType("application/xml");
httpPost.setEntity(entity);
ResponseHandler responseHandler = new BasicResponseHandler();
friendId = (Long) httpClient.execute(httpPost, responseHandler);
} catch (Exception e) {
e.printStackTrace();
}
return friendId;
}
public void removeFriend(Long userId, Long friendId) {
DefaultHttpClient httpClient = new DefaultHttpClient();
HttpDelete httpPost = new HttpDelete(baseUrl + "/friends/" + userId
+ "/" + friendId);
httpPost.addHeader("accept", "application/xml");
try {
httpClient.execute(httpPost);
} catch (Exception e) {
e.printStackTrace();
}
}
}
3.3 Use Apache Http Client to access Web Service
package org.codeexample.rs.client;
public class FriendWebServiceHttpClient implements FriendWebServiceClient {
private String baseUrl;
@Required public void setBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
}
@Override public List<PersonDTO> getFriends(Long userId) {
List<PersonDTO> friends = null;
try {
URL url = new URL(baseUrl + "/friends/" + userId);
HttpURLConnection connection = (HttpURLConnection) url
.openConnection();
connection.setRequestMethod("GET");
connection.setRequestProperty("Accept", "application/xml");
if (connection.getResponseCode() != 200) { throw new RuntimeException(
"Operation failed:” + connection.getResponseCode()); }
JAXBContext context = JAXBContext.newInstance(PersonDTO.class);
friends = (List<PersonDTO>) context.createUnmarshaller().unmarshal(
connection.getInputStream());
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (JAXBException e) {
e.printStackTrace();
}
return friends;
}
@Override public PersonDTO getFriend(Long userId, Long friendId) {
PersonDTO friend = null;
try {
URL url = new URL(baseUrl + "/friends/" + userId + "/" + friendId);
HttpURLConnection connection = (HttpURLConnection) url
.openConnection();
connection.setRequestMethod("GET");
connection.setRequestProperty("Accept", "application/xml");
if (connection.getResponseCode() != 200) { throw new RuntimeException(
"Operation failed:” + connection.getResponseCode()); }
JAXBContext context = JAXBContext.newInstance(PersonDTO.class);
friend = (PersonDTO) context.createUnmarshaller().unmarshal(
connection.getInputStream());
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (JAXBException e) {
e.printStackTrace();
}
return friend;
}
@Override public Long addFriend(Long userId, PersonDTO friendDTO) {
Long friendId = null;
HttpURLConnection connection = null;
try {
URL url = new URL(baseUrl + "/friends/" + userId);
connection = (HttpURLConnection) url.openConnection();
connection.setDoOutput(true);
connection.setInstanceFollowRedirects(false);
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/xml");
connection.setRequestProperty("accept", "application/xml");
StringWriter sw = new StringWriter();
JAXBContext context = JAXBContext.newInstance(PersonDTO.class);
context.createMarshaller().marshal(friendDTO, sw);
System.err.println(sw.toString());
OutputStream os = connection.getOutputStream();
os.write(sw.toString().getBytes());
os.flush();
BufferedReader reader = new BufferedReader(new InputStreamReader(
connection.getInputStream()));
friendId = new Long(reader.readLine());
// this would cause org.xml.sax.SAXParseException: Content is not
// allowed in prolog.]
// context = JAXBContext.newInstance(PersonDTO.class);
// friendId = (Long) context.createUnmarshaller().unmarshal(
// connection.getInputStream());
} catch (IOException e) {
e.printStackTrace();
} catch (JAXBException e) {
e.printStackTrace();
} finally {
connection.disconnect();
}
return friendId;
}
@Override public void removeFriend(Long userId, Long friendId) {
try {
URL url = new URL(baseUrl + "/friends/" + userId + "/" + friendId);
HttpURLConnection connection = (HttpURLConnection) url
.openConnection();
connection.setRequestMethod("DELETE");
System.err.println(connection.getResponseCode());
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
To test different web service client, just replace FriendWebServiceClient with corresponding client implementation class.