Clients like REST part 2

October 8th, 2010 § Leave a Comment

This post is the second one of a mini-series:

Clients Like REST part 1 Apache HTTP Commons Client
Clients Like REST part 2 Native J2SE API Client
Clients Like REST part 3 JAX-WS client API

This post will show you how to access the same service – defined through the interface IAutoStatService – using the native J2SE API and in particular how to take full advantage of the HttpURLConnection class. I think that this is a pretty powerful concept: You can access a web service from a Java client without a 3rd party web-service library and without the need to write some type of parser.

Here are some notes about this client implementation:

  1. It relies on the lowest common denominator to all java apps only, theĀ  JDK
  2. To be complete I will mention that I am using the Jackson JSON processor to construct JSON strings from a POJO and vice versa de-serializing a JSON string coming on an input stream into a POJO; it’s just a convenience tool that does not contradict the above point (using just the native J2SE API)
  3. Again, there is nothing in this API that is REST-specific or even Web Services-specific; this point is worth stressing, by properly setting up the @javax.ws.rs.Path annotation on the server class the client will be able to call the addAutoStatistics() operation, for example, simply by constructing a POST request in the form http://<host>:<port>/autoStats/AutoStatService/add
  4. Since we are mainly relying on the HttpURLConnection class you will be forced to think in terms of the details of the HTTP protocol: What to set for Content-Type, Accept or Content-Length request properties? How to decode the HTTP response code? etc… But it’s not that bad and as a bonus your code would be easily portable to another language since these constructs are pretty generic
  5. You are not limited to a particular data format: if the server side produces JSON, you can de-serialize JSON into POJO, if the server side produces XML, you can de-serialize XML into POJO or use XQuery, etc…
  6. The testHttpURLConnection(HttpURLConnection connection) method is completely optional but helpful when debugging. It’s mainly included to show you how the various parts of the HTTP message (header/body) are constructed in each of the following cases: POST, DELETE and GET

package com.apptotest.client;

import static org.junit.Assert.*;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.Permission;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import org.codehaus.jackson.map.ObjectMapper;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

/**
 * TODO: We'll use a standard J2SE client (based on {@link HttpURLConnection} to connect
 * and test the following REST operations:
 * - POST
 * - DELETE
 * - GET
 * - PUT
 * This type of coding requires (some) help from the Jackson JSON library to interpret the
 * {@link InputStream}.
 */
public class AutoStatServiceImplJ2SEClientTest {
 private static final String hostname = "http://localhost:7650/autoStats/AutoStatService";
 private URL url;
 private HttpURLConnection conn;
 private InputStream in;
 private OutputStream out;
 private ObjectMapper mapper;

 @Before
 public void setUp() throws Exception {
     mapper = new ObjectMapper();
 }

 @After
 public void tearDown() throws Exception {
     if (in != null) { in.close(); }
     if (out != null) { out.close(); }
     if (conn != null) { conn.disconnect(); }
 }

 /**
 * corresponding to URL: http://localhost:7650/autoStats/AutoStatService/add
 * @throws Exception
 */
 @Test
 public final void testAddAutoStat() throws Exception {
     AutoStatistics stat = new AutoStatistics("BMW 335i", 9000007L, 150F, 4.7F, 13.3F, 119F);
     String jsonValue = mapper.writeValueAsString(stat);

     url = new URL(hostname + "/add");
     conn = (HttpURLConnection) url.openConnection();
     conn.setRequestMethod("POST");
     conn.setDoOutput(true);
     conn.setUseCaches(false);
     conn.setRequestProperty("Content-Type", "application/json");
     conn.setRequestProperty("Accept", "application/json");
     conn.setRequestProperty("Content-Length", Integer.toString(jsonValue.length()));
     conn.getOutputStream().write(jsonValue.getBytes());
     conn.getOutputStream().flush();
     conn.connect();

     testHttpURLConnection(conn);

     if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) {
         fail("POST method failed: " + conn.getResponseCode() + "\t" + conn.getResponseMessage());
     } else {
         InputStream responseContent = (InputStream) conn.getContent();
         AutoStatistics respStat1 = mapper.readValue(responseContent, AutoStatistics.class);
         assertNotNull(respStat1);
         assertEquals(stat, respStat1);
     }
 }

 /**
 * corresponding to URL: http://localhost:7650/cxfweb_ajax/cxf/rest/AutoStatService/delete/{id}
 * where {id} gets expanded by the REST template
 */
 @Test
 public final void testDeleteAutoStat() throws Exception {
     url = new URL(hostname + "/delete/1000001");
     conn = (HttpURLConnection) url.openConnection();
     conn.setRequestMethod("DELETE");
     conn.setDoOutput(true);
     conn.setUseCaches(false);
     conn.connect();

     testHttpURLConnection(conn);

     if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) {
         fail("DELETE method failed: " + conn.getResponseCode() + "\t" + conn.getResponseMessage());
     } else {
         InputStream responseContent = (InputStream) conn.getContent();
         AutoStatistics respStat1 = mapper.readValue(responseContent, AutoStatistics.class);
         assertNotNull(respStat1);
         assertEquals(new AutoStatistics(), respStat1);
     }
 }

 /**
 * corresponding URL: http://localhost:7650/autoStats/AutoStatService/all
 * @throws Exception
 */
 @Test
 public final void testGetAutoStats() throws Exception {
     url = new URL(hostname + "/all");
     conn = (HttpURLConnection) url.openConnection();
     conn.setRequestMethod("GET");
     conn.connect();
     in = conn.getInputStream();

     testHttpURLConnection(conn);

     if (conn.getContentType().equals("application/json")) {
         mapper = new ObjectMapper();
         AllAutoStatistics stats = mapper.readValue(in, AllAutoStatistics.class);
         assertNotNull(stats);
         assertNotNull(stats.getAllStats());
         Collection<AutoStatistics> allStats = (Collection<AutoStatistics>) stats.getAllStats();
         assertTrue(allStats.contains(new AutoStatistics("Alfa Romeo 8C Competizione", 1000002L, 181F, 4.2F, 12.4F, 105F)));
         assertTrue(allStats.contains(new AutoStatistics("Cadillac CTS-V", 1000004L, 191F, 4.1F, 12.3F, 114F)));
     }
 }

 /**
 * corresponding URL: http://localhost:7650/autoStats/AutoStatService/autostats/1000002
 * @throws Exception
 */
 @Test
 public final void testGetAutoStatAsXml() throws Exception {
     url = new URL(hostname + "/autostats/1000002");
     conn = (HttpURLConnection) url.openConnection();
     conn.setRequestMethod("GET");
     conn.setRequestProperty("Content-Type", "application/xml;charset=UTF-8");
     conn.setRequestProperty("Accept", "application/xml");
     conn.connect();
     in = conn.getInputStream();

     testHttpURLConnection(conn);

     if (conn.getContentType().equals("application/xml")) {
         char[] buff = new char[128];
         InputStreamReader isr = new InputStreamReader(in);
         StringBuilder builder = new StringBuilder();
         while (isr.read(buff) != -1) {
             builder.append(buff);
         }
         assertTrue(builder.length() > 0);
         String expectedResponse = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><AutoStatistics><id>1000002</id><maxSpeed>181.0</maxSpeed><name>Alfa Romeo 8C Competizione</name><quarterMileTimeInSecs>12.4</quarterMileTimeInSecs><sixtyToZeroDistanceInFt>105.0</sixtyToZeroDistanceInFt><zeroToSixtyTimeInSecs>4.2</zeroToSixtyTimeInSecs></AutoStatistics>";
         assertTrue(builder.toString().contains(expectedResponse));
     }
 }

 /**
 * corresponding URL: http://localhost:7650/autoStats/AutoStatService/autostats/1000002
 *
 * @throws Exception
 */
 @Test
 public final void testGetAutoStatAsJson() throws Exception {
     url = new URL(hostname + "/autostats/1000002");
     conn = (HttpURLConnection) url.openConnection();
     conn.setRequestMethod("GET");
     conn.connect();
     in = conn.getInputStream();

     testHttpURLConnection(conn);

     if (conn.getContentType().equals("application/json")) {
         mapper = new ObjectMapper();
         AutoStatistics stat = mapper.readValue(in, AutoStatistics.class);
         assertNotNull(stat);
         assertEquals("name", "Alfa Romeo 8C Competizione", stat.getName());
         assertEquals("id", 1000002L, stat.getId().longValue());
         assertEquals("maxSpeed", 181.0F, stat.getMaxSpeed().floatValue(), 0.0);
         assertEquals("zeroToSixtyTimeInSecs", 4.2F, stat.getZeroToSixtyTimeInSecs().floatValue(), 0.0);
         assertEquals("quarterMileTimeInSecs", 12.4F, stat.getQuarterMileTimeInSecs().floatValue(), 0.0);
         assertEquals("sixtyToZeroDistanceInFt", 105.0F, stat.getSixtyToZeroDistanceInFt().floatValue(), 0.0);
     }
 }

 /**
 * corresponding URL: http://localhost:7650/autoStats/AutoStatService/edit
 * @throws Exception
 */
 @Test
 public final void testUpdateAutoStat() throws Exception {
     AutoStatistics stat = new AutoStatistics("Audi A5 2.0T Quattro - Updated J2SE", 1000003L, 130F, 6.2F, 14.8F, 130F);
     String jsonValue = mapper.writeValueAsString(stat);

     url = new URL(hostname + "/edit");
     conn = (HttpURLConnection) url.openConnection();
     conn.setRequestMethod("POST");
     conn.setDoOutput(true);
     conn.setUseCaches(false);
     conn.setRequestProperty("Content-Type", "application/json");
     conn.setRequestProperty("Accept", "application/json");
     conn.setRequestProperty("Content-Length", Integer.toString(jsonValue.length()));
     conn.getOutputStream().write(jsonValue.getBytes());
     conn.getOutputStream().flush();
     conn.connect();

     testHttpURLConnection(conn);

     if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) {
         fail("POST method failed: " + conn.getResponseCode() + "\t" + conn.getResponseMessage());
     } else {
         InputStream responseContent = (InputStream) conn.getContent();
         AutoStatistics respStat1 = mapper.readValue(responseContent, AutoStatistics.class);
         assertNotNull(respStat1);
         assertEquals(stat, respStat1);

         // and test
         url = new URL(hostname + "/all");
         conn = (HttpURLConnection) url.openConnection();
         conn.setRequestMethod("GET");
         conn.connect();
         in = conn.getInputStream();
         AllAutoStatistics stats = mapper.readValue(in, AllAutoStatistics.class);
         assertNotNull(stats);
         assertNotNull(stats.getAllStats());
         Collection<AutoStatistics> allStats = stats.getAllStats();
         assertTrue(allStats.contains(new AutoStatistics("Audi A5 2.0T Quattro - Updated J2SE", 1000003L, 130F, 6.2F, 14.8F, 130F)));
     }
 }

 /**
 * @param connection
 * @throws Exception
 */
 private void testHttpURLConnection(HttpURLConnection connection) throws Exception {
     boolean connAllowUserInteraction = connection.getAllowUserInteraction();
     String connContentType = connection.getContentType();
     String connContentEncoding = connection.getContentEncoding();
     String connRequestMethod = connection.getRequestMethod();
     boolean connDoInput = connection.getDoInput();
     boolean connDoOutput = connection.getDoOutput();
     Permission connPermission = connection.getPermission();
     URL connURL = connection.getURL();
     Map<String, List<String>> connHeaderFields = connection.getHeaderFields();

     System.out.println("connAllowUserInteraction: " + connAllowUserInteraction);
     System.out.println("connContentType: " + connContentType);
     System.out.println("connContentEncoding: " + connContentEncoding);
     System.out.println("connRequestMethod: " + connRequestMethod);
     System.out.println("connDoInput: " + connDoInput);
     System.out.println("connDoOutput: " + connDoOutput);
     System.out.println("connPermission: " + connPermission);
     System.out.println("connURL: " + connURL);

     if (connHeaderFields != null) {
         Set<Entry<String, List<String>>> connHeaderFieldsEntries = connHeaderFields.entrySet();
         for (Entry<String, List<String>> entry : connHeaderFieldsEntries) {
             System.out.println("connHeaderField: " + entry);
         }
     }
 }

}

Advertisement

Tagged: , , , , , , , , , , , , ,

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s

What’s this?

You are currently reading Clients like REST part 2 at Computing Thoughts by Roger Rached.

meta

Follow

Get every new post delivered to your Inbox.