A Groovy Rest Client for the JPA, Jersey, Spring Integration Project

 Introduction

In this article we are presenting a simple Groovy client to use with our REST service created in the previous article: REST Project Digest and Generic Data Repository.  In that last article we constructed a REST service that uses JPA/Hibernate in the backend to service a simple Company Directory.  It is a two table one-to-many relationship between Companies and Persons.  In that article the client was written with a combination of html and JavaScript, utilizing the services of Protocol.js.

We are using Groovy to render a REST client using Java Swing application.  For communication we use HTTPClient from Apache.  We also use a JSON library for encoding and decoding the transmitted data.  Groovy is a wonderful utility for taking the drudgery out of using the Swing libraries.  Although Swing is an excellent implementation the result/effort ratio is low.

 

Design

This is a utility program without a great deal of function so the design is slim.  The progarm is broken into three parts: entity, REST client and a Swing script.  All the parts are written in Groovy.

Entities

There are two entities: Person and Client.  They are simple pojos. Part of the Person class is shown below the Company class is similarly straight forward.

public class Person{
	private int id;
	private String name;
	private int age;
	private Company company;

	public Person() {
	}
	public Person(String name,int age, Company company){
		this.name = name
		this.age = age
		this.company = company
	}
	public Person(int id, String name,int age, Company company){
		this.id = id
		this.name = name
		this.age = age
		this.company = company
	}
	:
	:

The REST Client

The rest client construction utlizes two tools: HTTPClient and a JSON library.  The HTTPClient software provides an efficent api for our POST, PUT, GET and DELETE commands.  The REST service we are using produces and consumes JSON objects, the JSON library helps us switch between JSON and beans in the application.

Below is the update method.  We did have some difficulties using the JSON library to it’s full potential due to confusion between Numbers and Strings.  The values from the REST service were surround, as Strings are, by quotations.  This resulted in our POJOs being populated with the ASCII numeric of the number.  In the snippet below we are using a brute force method to encode the JSON for transmission.

public List putUpdate(Person person) {
		def url = "http://localhost:8080/general/webresources/person/update"
		def brute ='{"id":"'+person.id+'","name":"'+person.name+'","age":"'+person.age+'","company":{"id":"'+person.company.id+'","name":"'+person.company.name+'"}}'

		PutMethod put = new PutMethod(url);
		RequestEntity entity = new StringRequestEntity(brute, "application/json", "ISO-8859-1");

		put.setRequestEntity(entity);
		execute(put);
		return getDirectory();
	}

The full class is below if you would like to take a closer look.

package com.persistent.client

import com.persistent.entity.Company
import com.persistent.entity.Person

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.httpclient.HttpMethodBase;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PutMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.DeleteMethod;
import org.apache.commons.httpclient.methods.StringRequestEntity;
import org.apache.commons.httpclient.methods.RequestEntity
import net.sf.json.groovy.JsonSlurper;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import groovy.text.SimpleTemplateEngine

/**
 * @author david
 *
 */

public class DirectoryClient{
	static String URL_BASE = "http://localhost:8080/general/webresources"
	def directories = [];

	public DirectoryClient() {

	}

	private void execute(HttpMethodBase method){
		HttpClient httpclient = new HttpClient();

		try {
			httpclient.executeMethod(method);
		} finally {
			// Release current connection to the connection pool once you are
			// done
			method.releaseConnection();
		}
	}

	public void deleteDelete(Integer id) {
		def url = URL_BASE+"/persons/"+id;
		DeleteMethod delete = new DeleteMethod(url);
		execute(delete);
	}

	public List putUpdate(Person person) {
		def url = URL_BASE+"/person/update"
		def brute ='{"id":"'+person.id+'","name":"'+person.name+'","age":"'+person.age+'","company":{"id":"'+person.company.id+'","name":"'+person.company.name+'"}}'

		PutMethod put = new PutMethod(url);
		RequestEntity entity = new StringRequestEntity(brute, "application/json", "ISO-8859-1");

		put.setRequestEntity(entity);
		execute(put);
		return getDirectory();
	}

	public List postAdd(Person person) {
		def url = URL_BASE+"/person"
			def brute ='{"id":"'+person.id+'","name":"'+person.name+'","age":"'+person.age+'","company":{"id":"'+person.company.id+'","name":"'+person.company.name+'"}}'
			PostMethod put = new PostMethod(url);
			RequestEntity entity = new StringRequestEntity(brute, "application/json", "ISO-8859-1");
			put.setRequestEntity(entity);
			execute(put);
			return getDirectory();
	}

	public List getCompanies() {
		def url = URL_BASE+"/companies"

		def get = new GetMethod(url)
		get.setRequestHeader("Accept", "application/json")

		def client = new HttpClient()
		client.executeMethod(get)
		JSONObject jsonObject = JSONObject.fromObject( get.getResponseBodyAsString().toString() );
		JSONArray jsonArray = JSONArray.fromObject(jsonObject.get("company"));
		return jsonArray;
	}

	public List getDirectory() {
		def url = URL_BASE+"/persons"
		def get = new GetMethod(url)
		get.setRequestHeader("Accept", "application/json")

		def client = new HttpClient()
		client.executeMethod(get)
		JSONObject jsonObject = JSONObject.fromObject( get.getResponseBodyAsString().toString() );
		JSONArray personJSONArray = JSONArray.fromObject(jsonObject.get("person"));

		def companies = getCompanies();
		def companiesNew = []

		for(Company c in companies) {
			def id = c.id;
		    def count = 0;
		    def personArray = [] 

			for(Person dude in personJSONArray) {
				if(id == dude.company.id) {
					// unfortunately the id for the JSON Company object is returned as a String such that 1 becomes 50 in ascii when converted to an integer
					// hence we re-construct the object correctly.
				    def person = new Person(id:dude.id.toInteger(),name:dude.name,age:dude.age.toInteger(),company:new Company(id:c.id.toInteger(),name:c.name));
					personArray.add(person);
				}
			}
		    companiesNew.add(new Company(id:c.id.toInteger(),name:c.name,employees:personArray))
		}

		return companiesNew;
	}
	public List getDirectories() {
		return directories;
	}
}

Groovy Swing Client

This is one place that Groovy really shines.  It makes Swing a far more productive tool.  In this example I have choosen a scripting style to produce the user interface.  One could have also created a class but give the utility nature of the exersise I choose to use the less formal approach.  Although Groovy does reduce the code required to produce your end result it does not relinquish you of the need to understand Swing framework.

import java.awt.Dimension
import java.util.LinkedHashMap
import javax.swing.*
import javax.swing.tree.DefaultMutableTreeNode as TreeNode
import groovy.swing.SwingBuilder
import groovy.model.ValueHolder
import groovy.model.DefaultTableColumn
import groovy.model.DefaultTableModel
import groovy.model.PropertyModel
import javax.swing.event.TableModelListener
import javax.swing.event.ListSelectionListener
import java.awt.BorderLayout
import java.awt.FlowLayout;
import javax.swing.JComboBox

import com.persistent.client.DirectoryClient
import com.persistent.entity.Person
import com.persistent.entity.Company

def directoryClient = new DirectoryClient();

def directoryList = directoryClient.getDirectory()

def employeeList = [];
def employeeTable;
def swing = new SwingBuilder()
def nameTF;
def ageTF;
def combo;
JTree directoryTree
Company currentCompany;
Person selectedPerson;

def addButton;
def updateButton;
def deleteButton;
def clearButton;

def updateTree = {
		directoryTree.model.root.removeAllChildren()
		directoryList.each {company ->
		    def node = new TreeNode(company)
		    company.employees.each { employee -> node.add( new TreeNode(employee));}
		    directoryTree.model.root.add(node)
		}
		directoryTree.model.reload(directoryTree.model.root)
}

def selectEmployee2 = { person ->
	if( person != null) {
		nameTF.text = person.name;
		ageTF.text = person.age;
		println "Select id with: "+person.company.id
		println "Or name: "+person.company.name

		println "Is the index"+directoryList.indexOf(person.company)
		// Too cool for school
		// The statement below find in the directoryList the company that the person is associated with and we
		// use that company to find the index in the list of that company.
		combo.selectedIndex = directoryList.indexOf(directoryList.find(){it.id==person.company.id})
		selectedPerson = person
		  deleteButton.visible = true;
 		  addButton.visible = false;
 		  updateButton.visible = true;
	}
};

def selectEmployee = { employeeId ->
	selectEmployee2(currentCompany.employees.get(employeeId))
}

def setEmployeeTableModel = { company ->
		employeeList = company.employees;
	    def listModel = new ValueHolder(employeeList)
	    def model = new DefaultTableModel(listModel)
	    model.addColumn(new DefaultTableColumn("Identifier", new PropertyModel(model.rowModel, "id")))
	    model.addColumn(new DefaultTableColumn("Name", new PropertyModel(model.rowModel, "name")))
	    model.addColumn(new DefaultTableColumn("Age", new PropertyModel(model.rowModel, "age")))
	    swing.table1.setModel(model)
	    currentCompany = company;
}
def selectFirstInCompany = { company ->
//Initialize the employee table and Employee section using first company in directory
//and the first employee in the companies employ.
setEmployeeTableModel(company);
selectEmployee2(company.employees.get(0))
}

swing.frame(title: 'CompanyDirectories', defaultCloseOperation: JFrame.DISPOSE_ON_CLOSE,
    size: [900, 350], show: true, locationRelativeTo: null) {
    lookAndFeel("system")
    menuBar() {
        menu(text: "File", mnemonic: 'F') {
            menuItem(text: "Exit", mnemonic: 'X', actionPerformed: {dispose() })
        }
    }
    splitPane {
        scrollPane(constraints: "left", preferredSize: [160, -1]) {
            directoryTree = tree(rootVisible: false)
        }
        splitPane(orientation:JSplitPane.VERTICAL_SPLIT, dividerLocation:40) {
        	scrollPane(constraints: "top") { textArea(id:'companyInfo') }
            scrollPane(constraints:"bottom") {
            	panel {
            		borderLayout()

            		employeeTable = table(autoCreateColumnsFromModel:true,id:'table1',constraints:BorderLayout.NORTH) {
            			current.selectionModel.addListSelectionListener({e ->
            				if(e.source.minSelectionIndex >-1){
            					selectEmployee(e.source.minSelectionIndex);
            				}
            			} as ListSelectionListener)
            		tableModel(list:employeeList) {
            			propertyColumn(header:'Name', propertyName:'name')
            			propertyColumn(header:'Age',propertyName:'age')
            		}
            	}
            	panel(constraints:BorderLayout.CENTER){
            	}
            	panel(constraints:BorderLayout.SOUTH){
            				name = label()
            				name.text = "Name:"
            				nameTF = textField()
            				nameTF.columns = 15

            				age = label()
            				age.text = "Age:"
            				ageTF = textField()
            				ageTF.columns = 2

            				combo = comboBox(items:directoryList)

            			    updateButton = button(
            			            text:'Update',
            			            actionPerformed: {
            			    			selectedPerson.age = ageTF.text.toInteger()
            			    			selectedPerson.name = nameTF.text
            			    			selectedPerson.company = combo.model.getSelectedItem();
            			    			println "The selected company is: "+selectedPerson.company
            			    			println "The id of the selected company is: "+selectedPerson.company.id
            			    			println "The name of the selected company is: "+selectedPerson.company.name

            			    			directoryList = null;
            			                directoryList = directoryClient.putUpdate(selectedPerson);
            			                updateTree()
            			                selectFirstInCompany(directoryList.get(0))
            			            }
            			        )
            			     addButton = button(
            			            text:'Add',
            			            actionPerformed: {
            							selectedPerson = new Person(nameTF.text,ageTF.text.toInteger(),combo.model.getSelectedItem())
            			                directoryClient.postAdd(selectedPerson);
            			                directoryList = directoryClient.getDirectory();
            			                updateTree()
            			                selectFirstInCompany(directoryList.get(0))
            			            }
            			        )
            			     addButton.visible = false
            			     clearButton = button(
            			    		 text:'Clear',
            			    		 actionPerformed: {
							ageTF.text = '';
	            			    		 nameTF.text=''
							deleteButton.visible = false;
							addButton.visible = true;
							updateButton.visible = false;
            			    		 }
            			    	)
            			    deleteButton = button(
            			    		 text:'Delete',
            			    		 actionPerformed: {
            			    			 ageTF.text = '';
            			    			 nameTF.text=''
            			    			directoryClient.deleteDelete(selectedPerson.id);
             			                directoryList = directoryClient.getDirectory();
             			                updateTree()
             			                selectFirstInCompany(directoryList.get(0))

            			    		 }
            			    	)
            			}
            }
           }

        }
    }
}

directoryTree.valueChanged = {
	node = (TreeNode) directoryTree.getLastSelectedPathComponent();
	if (node != null) {
		if(node.getUserObject() instanceof Person) {
			Person aselectedPerson = (Person)node.getUserObject();
			// The parent node is the Company to whom the selected person belongs.
			parentNode = node.getParent();
			currentCompany = parentNode.getUserObject();
			selectEmployee2(aselectedPerson);
		    	if( aselectedPerson.company instanceof Company) {
				def theDirectory = directoryList.find{ it.id == aselectedPerson.company.id }
				if( theDirectory != null ) {
		    			setEmployeeTableModel(theDirectory)
				}
			} else {
				println "UNEXPECTED: "+parentNode.getClass().getName()
			}
		} else {
		    	// the node has a key and a value.  The key is this id of the company selected.  The value is the Company object.
		    	if( node.getUserObject() instanceof Company) {
		    		setEmployeeTableModel(node.getUserObject())

			}
		}
		swing.companyInfo.text = currentCompany.name;
	}
}

updateTree()
selectFirstInCompany(directoryList.get(0))
swing.companyInfo.text = currentCompany.name;

A Picture of the User Interface in Action

Source Code and Instructions

Here is the source code in a jar file. I suppose that my choice of file name is questionable but here it is: groovy.jar.

Installation

  1. Dowload the jar file
  2. Extract the jar file with the command: jar xvf groovy.jar
  3. There will now be a directory called groovy. Navigate into groovy and execute: mvn install.  This will pull in the required jar files and build the project.
  4. Start the REST service of the previous article.
  5. Start the Groovy REST client with this command: mvn exec:java

 

Conclusion

 That’s it!  A simple user interface into a REST service utilizing Groovy.

 

About The Author

David Sells is a computer consultant in Toronto, Ontario specializing in Java Enterprise Development. His clients have included companies such as: IBM, Goldman Sachs, Merrill Lynch and Deutsche Bank.

Sun Certified Enterprise Architect for the Java Platform, Enterprise Edition 5

Contact: david@persistentdesigns.com

Leave a Reply

 

 

 

You can use these HTML tags

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>