Friday, October 2, 2015

REST client tutorial with Java & Jersey

In my previous post I talked about REST APIs and presented a tutorial on how to create a REST API using Java and Spring. If you missed it, I suggest you take a look at it first here. We used the services of the API using Chrome’s POSTMAN to directly call the exposed URLs. In this post I will create a client to call these services programmatically. To keep things interesting I will use another popular framework for REST web services, Jersey.

The application we built in the previous tutorial is used to manage (geek) diary entries. The application exposed a REST API which can be used to perform CRUD operations on diary entries. Now we will create a client for performing the CRUD operations.

The requirements for replicating and running this tutorial are:


  • JDK 1.7 or higher
  • Maven 
  • Git
  • The 'Geek Diary' services to be deployed and running
  • and use of IDE such as eclipse is also advised
Let's look now at the structure of our REST client tutorial, as is shown in the following figure:




The project is pretty straight forward. In the model package we have the DiaryEntries POJO from the previous tutorial. The util package has the DataTransformationHandler class which contains methods to convert between JSON-Object-Maps, the client package contains methods for calling the CRUD operations via HTTP and we have a demo class to run the application.

The pom file for the Maven project is presented below:
<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/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.tasosmartidis</groupId>
  <artifactId>rest-client-tutorial</artifactId>
  <version>0.1</version>
  <name>rest-client-tutorial</name>
  
  <dependencies>
    <!-- Jersey -->
    <dependency>
        <groupId>com.sun.jersey</groupId>
        <artifactId>jersey-client</artifactId>
        <version>1.18.3</version>
    </dependency>   
    <!-- Codehaus Jackson --> 
    <dependency>
        <groupId>org.codehaus.jackson</groupId>
        <artifactId>jackson-mapper-asl</artifactId>
        <version>1.9.13</version>
    </dependency> 
    <!-- JUnit -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.11</version>
    </dependency>
  </dependencies>
</project>

The POJO for our diary entries is presented in the source code below. We overridden the 'toString()' method to be able to print the entries in the demo program.
package com.tasosmartidis.rest_client_tutorial.model;


public class DiaryEntry {

    private String entryId;
    private String entryTitle;
    private String entryText;
    
    public String getEntryId() {
        return entryId;
    }
    public void setEntryId(String entryId) {
        this.entryId = entryId;
    }
    public String getEntryTitle() {
        return entryTitle;
    }
    public void setEntryTitle(String entryTitle) {
        this.entryTitle = entryTitle;
    }
    public String getEntryText() {
        return entryText;
    }
    public void setEntryText(String entryText) {
        this.entryText = entryText;
    }
    
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((entryId == null) ? 0 : entryId.hashCode());
        result = prime * result
                + ((entryTitle == null) ? 0 : entryTitle.hashCode());
        return result;
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        DiaryEntry other = (DiaryEntry) obj;
        if (entryId == null) {
            if (other.entryId != null)
                return false;
        } else if (!entryId.equals(other.entryId))
            return false;
        if (entryTitle == null) {
            if (other.entryTitle != null)
                return false;
        } else if (!entryTitle.equals(other.entryTitle))
            return false;
        return true;
    }
    
    @Override
    public String toString(){
        return "[Entry-id: " + entryId + ", entry-title: " + entryTitle + ", entry-text...]";
    }
    
}


Our client application will have to transform data format between JSON to objects and Maps, so we created a few methods for such conversions using the Jackson library.
package com.tasosmartidis.rest_client_tutorial.util;

import java.util.HashMap;
import java.util.Map;

import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.ObjectWriter;
import org.codehaus.jackson.type.TypeReference;

import com.tasosmartidis.rest_client_tutorial.model.DiaryEntry;

public class DataTransformationHandler {
    
    private ObjectMapper mapper;
    
    public DataTransformationHandler() {
        this.mapper = new ObjectMapper();
    }

    public String convertObjectToJson(Object objectToMap) {
        String mappedJson=null;     
        
        try{
            ObjectWriter objectWriter = mapper.writer().withDefaultPrettyPrinter();
            mappedJson = objectWriter.writeValueAsString(objectToMap);
        }catch(Exception ex) {
            ex.printStackTrace();
        } 
        
        return mappedJson;
    }
    
    public DiaryEntry convertJsonToDiaryEntry(String inputJson) {
        DiaryEntry transformedDiaryEntry = null;
        try{ 
            transformedDiaryEntry = mapper.readValue(inputJson, DiaryEntry.class);
        }catch(Exception ex) {
            ex.printStackTrace();
        } 
        return transformedDiaryEntry;
    }
    
    public Map<String, DiaryEntry> convertJsonToMap(String inputJson) {
        Map<String, DiaryEntry> transformedMap = null;
        try{ 
            transformedMap = mapper.readValue(inputJson, 
                                         new TypeReference<HashMap<String,DiaryEntry>>(){});
        }catch(Exception ex) {
            ex.printStackTrace();
        }
        
        return transformedMap;
    }
}


Now the most important class, the actual REST client. As you can see in the following source code, we have created methods for all the methods of the REST API, in order to create, retrieve, update and delete information from the repository. In each method, a web resource for a specified url is created, the type of the consumed and produced media types are defined along with the HTTP method with the resources to be communicated.
package com.tasosmartidis.rest_client_tutorial.client;

import java.util.Map;

import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import com.tasosmartidis.rest_client_tutorial.model.DiaryEntry;
import com.tasosmartidis.rest_client_tutorial.util.DataTransformationHandler;

public class DiaryServiceClient {

    private Client client;
    private WebResource webResource;
    private DataTransformationHandler dataHandler;
    private final String baseUrl = "http://localhost:8080/rest-api-tutorial/service/geek-diaries/entry/";
    
    public DiaryServiceClient() {
        this.client = new Client();
        this.dataHandler = new DataTransformationHandler();
    }
    
    public DiaryEntry createDiaryEntry(String jsonToPost) {
        DiaryEntry createdEntry = null;
        
        try {
            // Create first a Web resource from the client
            webResource = client.resource(baseUrl);  

            // Make the POST request
            ClientResponse response = webResource.accept("application/json")
                                            .type("application/json")
                                            .post(ClientResponse.class, jsonToPost);

            // check response status code
            if (response.getStatus() != 200) {
                throw new RuntimeException("Failed : HTTP error code : " + response.getStatus());
            }

            // Map the response of the created DiaryEntry to be returned
            String responseJson = response.getEntity(String.class); 
            createdEntry = dataHandler.convertJsonToDiaryEntry(responseJson);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        
        return createdEntry;
    }
    
    public DiaryEntry getDiaryEntry(String entryId) {
        DiaryEntry returnedEntry = null;
        
        try {
            // Create first a Web resource from the client
            webResource = client.resource(baseUrl+entryId);  
            
            // Make the GET request
            ClientResponse response = webResource.accept("application/json")
                                            .type("application/x-www-form-urlencoded")
                                            .get(ClientResponse.class);
           
            // Check response status code
            if (response.getStatus() != 200) { 
                throw new RuntimeException("Failed : HTTP error code : " + response.getStatus());
            }
            
            // Map the response to the DiaryEntry to be returned
            String responseJson = response.getEntity(String.class);
            returnedEntry = dataHandler.convertJsonToDiaryEntry(responseJson);
        }catch(Exception ex) {
            ex.printStackTrace();
        }
        
        return returnedEntry;       
    }
    
    public Map<String, DiaryEntry> getDiaryEntries() {
         Map<String, DiaryEntry> resultList = null; 
        
         try {
                Client client = Client.create();
                WebResource webResource = client.resource(baseUrl);  

                // GET method
                ClientResponse response = webResource.accept("application/json")
                        .type("application/x-www-form-urlencoded").get(ClientResponse.class);

                // check response status code
                if (response.getStatus() != 200) {
                    throw new RuntimeException("Failed : HTTP error code : " + response.getStatus());
                }

                // display response
                String responseJson = response.getEntity(String.class); 
                resultList = dataHandler.convertJsonToMap(responseJson);
            } catch (Exception e) {
                e.printStackTrace();
            }
         
         return resultList;
    }
    
    public DiaryEntry updateDiaryEntry(String jsonToPut) {
        DiaryEntry updatedEntry = null;
        
        try {
            webResource = client.resource(baseUrl);  

            // Make the PUT call
            ClientResponse response = webResource.accept("application/json")
                                            .type("application/x-www-form-urlencoded")
                                            .type("application/json")
                                            .put(ClientResponse.class, jsonToPut);

            // Check response status code
            if (response.getStatus() != 200) {
                throw new RuntimeException("Failed : HTTP error code : " + response.getStatus());
            }

            // Map the response to the DiaryEntry to be returned
            String responseJson = response.getEntity(String.class); 
            updatedEntry = dataHandler.convertJsonToDiaryEntry(responseJson);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        
        return updatedEntry;
    }
    
    public String deleteDiaryEntry(String entryId) {
        String deletedEntryId = null;
        
        try {
            webResource = client.resource(baseUrl+entryId);  

            // Make the DELETE request
            ClientResponse response = webResource.accept("application/json")
                                            .type("application/x-www-form-urlencoded")
                                            .delete(ClientResponse.class);

            // Check response status code
            if (response.getStatus() != 200) {
                throw new RuntimeException("Failed : HTTP error code : " + response.getStatus());
            }

            // Return the id of the deleted entry
            deletedEntryId = response.getEntity(String.class); 
        } catch (Exception e) {
            e.printStackTrace();
        }
        return deletedEntryId;
    }
}


That was it, our REST client is ready! Let's use all the operations through a simple main class as shown below:
package com.tasosmartidis.rest_client_tutorial.client;

import java.util.Map;

import com.tasosmartidis.rest_client_tutorial.model.DiaryEntry;
import com.tasosmartidis.rest_client_tutorial.util.DataTransformationHandler;

public class RestClientDemo {

    public static void main(String[] args) {
        
        DataTransformationHandler dataHandler = new DataTransformationHandler();
        
        DiaryServiceClient client = new DiaryServiceClient();
        
        // Create a couple of entries to submit
        DiaryEntry entry1 = new DiaryEntry();
        entry1.setEntryTitle("The emergence of the 'API Driven Economy'");
        entry1.setEntryText("The API economy has drastically...");
        
        DiaryEntry entry2 = new DiaryEntry();
        entry2.setEntryTitle("The Internet of Things");
        entry2.setEntryText("The Internet of Things (IoT) is...");
        
        // Convert the POJOs to Json
        String entry1asJson = dataHandler.convertObjectToJson(entry1);
        String entry2asJson = dataHandler.convertObjectToJson(entry2);
        
        // CREATE new diary entries
        System.out.println("*******************************");
        System.out.println("Create entries:");
        System.out.println("*******************************");
        DiaryEntry createdEntry1 = client.createDiaryEntry(entry1asJson);
        DiaryEntry createdEntry2 = client.createDiaryEntry(entry2asJson);
        System.out.println(createdEntry1);
        System.out.println(createdEntry2);
        System.out.println();
        
        // RETRIEVE all of the created entries
        System.out.println("*******************************");
        System.out.println("Retrieve all entries:");
        System.out.println("*******************************");
        printMapEntries(client.getDiaryEntries());
        System.out.println();
        
        // RETRIEVE specific diary entry
        System.out.println("*******************************");
        System.out.println("Retrieve specific entry:");
        System.out.println("*******************************");
        System.out.println(client.getDiaryEntry(createdEntry1.getEntryId()));
        System.out.println();
        
        // UPDATE a diary entry
        // change the POJO to update entry
        createdEntry1.setEntryText("I changed the text!!");
        String updatedEntryAsJson = dataHandler.convertObjectToJson(createdEntry1);
        System.out.println("*******************************");
        System.out.println("Update entry:");
        System.out.println("*******************************");
        System.out.println(client.updateDiaryEntry(updatedEntryAsJson));
        System.out.println();
        
        // DELETE a specific diary entry 
        System.out.println("*******************************");
        System.out.println("Delete specific entry:");
        System.out.println("*******************************");
        System.out.println(client.deleteDiaryEntry(createdEntry2.getEntryId()));   
        System.out.println();
        
        // RETRIEVE all of the created entries (should be missing the deleted one)  
        System.out.println("*******************************");
        System.out.println("Retrieve all entries:");
        System.out.println("*******************************"); 
        printMapEntries(client.getDiaryEntries());
        System.out.println(); 

    }
    
    public static void printMapEntries(Map<String, DiaryEntry> map) {
        
        for (Map.Entry<String, DiaryEntry> entry : map.entrySet()) 
            System.out.println(entry.getKey() + "," + entry.getValue()); 
    }

}


In this demo we create 2 diary entries, retrieve them collectively and independently, update them and delete one of them. Running the program will print the following in the console:


Done! We developed our REST client and it performed as expected when using the CRUD operations. The code is available via github. Till next time!

1 comment:

  1. Thanks for the useful information, give more updates like First time I visit your site really nice, here after a daily visit.
    ecommerce website development company in chennai

    ReplyDelete