Build a dynamic organization tree using GWT and RESTful Web services
The trend of Web application development over the last few years
has been to create rich Internet applications, most of which are
implemented using Asynchronous JavaScript + XML (Ajax). But, it
hasn't been easy because of the nature of JavaScript coding.
It is especially difficult to build large-scale Web applications. Here's
where GWT comes into play: It lets you build rich and responsive Web
interfaces using Java programming
instead of Ajax. GWT also provides all the benefits of Java development, such
as excellent IDE support with advanced debugging capabilities. GWT
can dramatically improve your productivity and enrich your users'
experience. In this article, I explain how to create a GWT application in
Eclipse and how to use the GWT Tree
and TreeItem
widgets to create a
sample organizational structure at the University Corporation of
Atmosphere Research (UCAR). I explain how to implement lazy loading, how to
integrate with RESTful Web services, and how to implement GWT
callbacks and custom exceptions. JSON has been used as data format for
RESTful Web services.
Software requirements
To begin, you'll need to download the following software packages and install them according to the installation guides on their respective Web sites. (See Related topics for links.)
- Eclipse IDE for Java EE Developers Galileo (Eclipse 3.5)
- GWT 2.0
- GWT plug-in for Eclipse
- MySQL 5.1 or DB2® Express-C
- Tomcat 6.x
RESTful Web services
RESTful Web services provide organizational data for the GWT client. In this article, I don't discuss the steps to demonstrate how to implement RESTful Web services; all you need to do is set up the database and deploy the WAR file to your Tomcat server. You also might need to change a few database attributes such as database host, login, and password in the configuration file. The RESTful Web services are implemented using the multi-tier architecture I discussed in the two articles "A multi-tier architecture for building RESTful Web services" and "Build RESTful Web services and dynamic Web applications with the multi-tier architecture."
Set up the database
I used MySQL Community Server 5.1 as the database for this article. However, you could also use DB2 Express-C or others. To use MySQL, you'll find a download link in Related topics. Download and install it on your chosen host if you haven't done so. Then create a database called gwtresttutorial and a user named gwtresttutorial with the password gwtresttutorial. Connect to the gwtresttutorial database and log in as gwtresttutorial. Download the sql script from Download and run the script to create tables and insert the data into the tables.
To connect the RESTful Web service server to a DB2 Express-C or to another DB2-variant database, the configuration is very similar to the one described for MySQL in Listing 1, with the following changes:
- Use the com.ibm.db2.jcc.DB2Driver as the
driverClassName
. - Use jdbc:db2://<host>:<port>/<database_name> as the URL where the
host
is the name of the host where DB2 Express-C is installed,port
is the port number to access the database, anddatabase_name
is the name of the database instance. - Copy the db2jcc.jar and db2jcc_license_cu.jar files from the DB2 Express-C installation directory to the WEB-INF/lib directory.
- You might need to modify the setup.sql script from the download to use DB2 syntax.
Deploy the WAR file to the Tomcat server
Download the WAR file from the Download section and save it to your Tomcat folder: <TOMCAT_HOME>/webapps, where TOMCAT_HOME is where your Tomcat server is installed. If you don't have Tomcat installed, you can download it from Related topics.
After you deploy the WAR file to the
<TOMCAT_HOME>/webapps directory, the Tomcat server will unzip the
WAR file into the
gwtRESTTutorial folder if it is running. Check
<TOMCAT_HOME>/webapps/gwtRESTTutorial/WEB-INF/classes/applicationContext.xml
(Listing 1) to make sure the values to configure the dataSource
bean match
the ones you are using for your MySQL database. Note that you might need to restart
the Tomcat server if any changes are
made.
Listing 1. Configure the dataSource bean in applicationContext.xml
1. <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> 2. <property name="driverClassName" value="com.mysql.jdbc.Driver"/> 3. <property name="url" value="jdbc:mysql://localhost:3306/gwtresttutorial"/> 4. <property name="username" value="gwtresttutorial"/> 5. <property name="password" value="gwtresttutorial"/> 6. </bean>
Access RESTful Web services
Two RESTful Web services have been implemented for
this article. The first one is to provide information about an
employee. The URI to access this Web service is:
http://localhost:8080/gwtRESTTutorial/rrh/employees/<EMP_ID>, where
EMP_ID
is the ID for an employee. A JSON string
that contains the detailed
employee data is returned. Listing 2 gives an example of the returned
JSON string.
Listing 2. Sample JSON data returned from employee RESTful Web service
1. { 2. "id":20, 3. "firstName":"Robert", 4. "nickName":"Bob", 5. "lastName":"Sunny", 6. "title":"SE", 7. "phone":"303-123-1234", 8. "email":bobs@ucar.edu 9. }
The
second Web service is to provide the information about an organizational unit.
Its URI is
http://localhost:8080/gwtRESTTutorial/rrh/organizations/<ORG_ID>,
where ORG_ID
is the ID for an organizational
unit. Like the employee data, a
JSON string that contains the detailed organizational data is returned
(Listing 3). The detailed data contains the ID, acronym, name, lead name, lead
title, and total number of employees within the organization as well as its
sub-organizations of all levels. It also contains an array of data for
employees working in the organizational unit and a separate array of data
for the immediate sub-organization units. The employee data and
sub-organization data contain only the ID and the display
name.
Listing 3. Sample JSON data returned from organization RESTful Web service
1. { 2. "id":1, 3. "acronym":"NCAR", 4. "name":"National Center for Atmospheric Research", 5. "leadName":"Dan Bush -Director", 6. "leadTitle":"Director", 7. "totalEmployees":15, 8. "employees": 9. [{ 10. "id":2, 11. "displayName":"Dan Bush - Director" 12. }, 13. { 14. "id":3, 15. "displayName":"Lori Stanley - Deputy Director" 16. }], 17. "subOrgs": 18. [{ 19. "id":3, 20. "displayName":"CISL" 21. }, 22. { 23. "id":5, 24. "displayName":"EOL" 25. }, 26. { 27. "id":6, 28. " displayName ":"RAL" 29. }, 30. { 31. "id":4, 32. "displayName":"ESSL" 33. }] 34. }
Create the GWT application in Eclipse
Eclipse with GWT support is the environment used for developing the GWT application in this article. In Eclipse:
- Select File > New > Web Application Project.
- Enter
gwtRESTTutorialView
in the Project Name field andedu.ucar.cisl.gwtRESTTutorialView
in the Package field in the New Web Application Project window (see Figure 1). - Select Use Default SDK and choose GWT-2.0.0 or a newer version in Google SDKs.
Figure 1. Create the New Web Application Project

The GWT plug-in in Eclipse automatically creates a sample remote service. You can optionally remove it by deleting the files GreetingService.java and GreetingServiceAsync.java in the edu.ucar.cisl.gwtRESTTutorialView.client package as well as GeetingServiceImpl.java in the edu.ucar.cisl.gwtRESTTutorialView.server package. You will also need to remove the servlet configuration in the web.xml file for the remote service and remove everything between <body> and </body> in the GwtRESTTutorialView.html file under the WAR folder.
The following sections cover the details about specific topics such as creating data beans, implementing the RPC proxy to access RESTful Web services and callbacks, and building the GWT Web interface. These components are located in the following four packages. (Create them in Eclipse if they don't exist.) The source codes are available for download in the Download section.
- edu.ucar.cisl.gwtRESTTutorialView.client.bean— Contains application Java beans for the client.
- edu.ucar.cisl.gwtRESTTutorialView.client.callback— Contains the implementation of the callback classes.
- edu.ucar.cisl.gwtRESTTutorialView.client— Contains the module entry class
GwtRESTTutorialView
. It also contains several other interfaces, classes, and image files that are used to create the GWT Web interface. The client-site classes for the RPC proxy are also located in this package. - edu.ucar.cisl.gwtRESTTutorialView.server— Contains the class for the server-side implementation of the RPC proxy.
Implement application data beans
In the article, I use a Tree
widget to display an organizational structure. In GWT, a Tree
widget
contains TreeItem
widgets, which are usually used as tree nodes. In this
case, a TreeItem
widget is used as either a tree node or a tree leaf to
represent an organizational unit and an employee, respectively. I implemented
an abstract base class ItemData
(Listing 4), which has three properties:
id
, displayName
, and dataReady
. id
is the ID
for the data item and is used to
build RESTful Web service requests. It identifies the resource in the RESTful
Web service server. The property displayName
is the name to be displayed.
The property dataReady
is a flag to indicate if the detail data has been
retrieved from the RESTful Web service server. It is used to help implement
lazy loading. When a TreeItem
widget is created, an ItemData
bean is
associated with the widget. It has only the resource ID and display name. The
detail data declared in the sub-classes is loaded only when a user
either selects the tree leaf or opens the tree node. An abstract method
buildURI
is used to build the URI for the
RESTful Web service request and will be
implemented by its sub-classes, EmployeeItemData
(Listing 5) and
OrganizationItemData
(Listing 6). EmployeeItemData
contains the detailed
information for an employee, whereas OrganizationItemData
contains the
detailed information for an organizational
unit.
Listing 4. edu.ucar.cisl.gwtRESTTutorialView.client.bean.ItemData
1. package edu.ucar.cisl.gwtRESTTutorialView.client.bean; 2. public abstract class ItemData { 3. protected int id = -1; 4. protected String displayName; 5. protected boolean dataReady = false; 6. ...//setters and getters 7. abstract public String buildUri(); 8. }
Listing 5. edu.ucar.cisl.gwtRESTTutorialView.client.bean.EmployeeItemData
1. package edu.ucar.cisl.gwtRESTTutorialView.client.bean; 2. public class EmployeeItemData extends ItemData { 3. protected String firstName; 4. protected String lastName; 5. protected String nickName; 6. protected String phone; 7. protected String email; 8. protected String title; 9. ...//setters and getters 10. public String buildUri(){ 11. return "http://localhost:8080/gwtRESTTutorial/rrh/employees/" + id; 12. } 13. }
Listing 6. edu.ucar.cisl.gwtRESTTutorialView.client.bean.OrganizationItemData
1. package edu.ucar.cisl.gwtRESTTutorialView.client.bean; 2. public class OrganizationItemData extends ItemData { 3. protected String name; 4. protected String leadName; 5. protected String leadTitle; 6. protected int totalEmployees; 7. ...//getters and setters 8. public String buildUri() { 9. return "http://localhost:8080/gwtRESTTutorial/rrh/organizations/" + id; 10. } 11. }
Implement the RPC proxy to request RESTful Web services
There are several strategies to integrate GWT with
RESTful Web services. If the RESTful Web service server is running on the
same domain and port, the obvious option is using the GWT RequestBuilder
class. However, the RequestBuilder
class can't
get around the Same Original Policy (SOP) limitation, which
prohibits making requests to the Web services server from
a different domain. To avoid the SOP limitation, I use an RPC proxy
strategy. With this strategy, the GWT client sends the RESTful Web
service request to the RPC remote service, which then passes the request to
the RESTful Web service server.
Create a custom exception class
A special custom exception is
needed so that the server can pass exceptions to the client. GWT provides
a very easy way to implement it. All you need to do is have the custom
exception class extend the Exception class and implement the IsSerializable
interface. The custom exception is shown in Listing 7.
Listing 7. edu.ucar.cisl.gwtRESTTutorialView.client.RESTfulWebServiceException
1. package edu.ucar.cisl.gwtRESTTutorialView.client; 2. import com.google.gwt.user.client.rpc.IsSerializable; 3. public class RESTfulWebServiceException extends Exception implements IsSerializable { 4. private static final long serialVersionUID = 1L; 5. private String message; 6. public RESTfulWebServiceException() { 7. } 8. public RESTfulWebServiceException(String message) { 9. super(message); 10. this.message = message; 11. } 12. public RESTfulWebServiceException(Throwable cause) { 13. super(cause); 14. } 15. public RESTfulWebServiceException(String message, Throwable cause) { 16. super(message, cause); 17. this.message = message; 18. } 19. public String getMessage() { 20. return message; 21. } 22. }
Create a remote service interface
For each remote service, GWT requires two interfaces
on the client side: a remote service interface and a remote service async
interface. The remote service interface must extend the GWT RemoteService
interface and define the signatures of the service methods that will be
exposed to the clients. The method parameters and return types must be
serializable.
The remote service interface for this article is very
simple (Listing 8). It declares only one method, invokeGetRESTfulWebService
. The method has two parameters, uri
and
contentType
. The former is the URI to identify the resource to request on
the RESTful Web service server. The latter indicates what content type is to
be expected for the result to be returned. The content type will be
a standard HTTP content type such as application/json, application/xml,
application/text, and so on. The method returns a string of content from
the HTTP response and throws a custom exception in the case of a
failure.
An annotation RemoteServiceRelativePath
needs to be added
to specify the URL path for the service (line 5 in Listing 8). A simple utility class
is created to easily get the instance of the async remote interface (lines
7-13 in Listing 8).
Listing 8. edu.ucar.cisl.gwtRESTTutorialView.client.RESTfulWebServiceProxy
1. package edu.ucar.cisl.gwtRESTTutorialView.client; 2. import com.google.gwt.core.client.GWT; 3. import com.google.gwt.user.client.rpc.RemoteService; 4. import com.google.gwt.user.client.rpc.RemoteServiceRelativePath; 5. @RemoteServiceRelativePath("RESTfulWebServiceProxy") 6. public interface RESTfulWebServiceProxy extends RemoteService { 7. public static class Util { 8. public static RESTfulWebServiceProxyAsync getInstance() { 9. RESTfulWebServiceProxyAsync 10. rs=(RESTfulWebServiceProxyAsync)GWT.create(RESTfulWebServiceProxy.class); 11. return rs; 12. } 13. } 14. 15. public String invokeGetRESTfulWebService(String uri, String contentType) 16. throws RESTfulWebServiceException; 17. }
Create a remote service asynchronous interface
A remote service asynchronous interface is based on
the remote service interface. A service's asynchronous interface must be
in the same package and have the same name, but with the suffix "Async."
There is a corresponding asynchronous method for each remote service
method. But asynchronous methods cannot have return types, and they must
always return void. Asynchronous methods not only must declare the same
parameters in the same order but also an extra parameter of the generic
type AsyncCallback<T>
, where T will be return type of the remote
service method . Asynchronous methods don't throw the exceptions. Listing
9 is the remote service asynchronous interface for the sample
application.
Listing 9. edu.ucar.cisl.gwtRESTTutorialView.client.RESTfulWebServiceProxyAsync
1. package edu.ucar.cisl.gwtRESTTutorialView.client; 2. import com.google.gwt.user.client.rpc.AsyncCallback; 3. public interface RESTfulWebServiceProxyAsync { 4. public void invokeGetRESTfulWebService(String uri, String contentType, AsyncCallback<String> callback); 5. }
Implement the proxy service on the server
Remote services are implemented in a server-side class
that extends GWT's RemoteServiceServlet
class. In the RESTful Web service proxy
(Listing 10), the class implements the remote service
invokeGetRESTfulWebService
. Based on the URI and content
type, this method builds an HTTP request and sends it to the RESTful Web service
server. If the response code is 200, it will buffer the content from the HTTP
response and use it as a return value for the method. Otherwise, it will
throw a custom exception. The method will catch other exceptions such as
MalformedURLException
and IOException
and throw the custom exception so
that it can be caught by the GWT client.
Listing 10. edu.ucar.cisl.gwtRESTTutorialView.server.RESTfulWebServiceProxyImpl
1. package edu.ucar.cisl.gwtRESTTutorialView.server; 2. import java.io.BufferedReader; 3. import java.io.IOException; 4. import java.io.InputStream; 5. import java.io.InputStreamReader; 6. import java.net.HttpURLConnection; 7. import java.net.MalformedURLException; 8. import java.net.URL; 9. import com.google.gwt.user.server.rpc.RemoteServiceServlet; 10. import edu.ucar.cisl.gwtRESTTutorialView.client.RESTfulWebServiceProxy; 11. import edu.ucar.cisl.gwtRESTTutorialView.client.RESTfulWebServiceException; 12. public class RESTfulWebServiceProxyImpl extends RemoteServiceServlet 13. implements RESTfulWebServiceProxy { 14. private static final long serialVersionUID = 1L; 15. public RESTfulWebServiceProxyImpl() { // must have 16. } 17. public String invokeGetRESTfulWebService(String uri, String contentType) 18. throws RESTfulWebServiceException { 19. try { 20. URL u = new URL(uri); 21. HttpURLConnection uc = (HttpURLConnection) u.openConnection(); 22. uc.setRequestProperty("Content-Type", contentType); 23. uc.setRequestMethod("GET"); 24. uc.setDoOutput(false); 25. int status = uc.getResponseCode(); 26. if (status != 200) 27. throw (new RESTfulWebServiceException("Invalid HTTP response status 28. code " + status + " from web service server.")); 29. InputStream in = uc.getInputStream(); 30. BufferedReader d = new BufferedReader(new InputStreamReader(in)); 31. String buffer = d.readLine(); 32. return buffer; 33. } 34. catch (MalformedURLException e) { 35. throw new RESTfulWebServiceException(e.getMessage(), e); 36. } 37. catch (IOException e) { 38. throw new RESTfulWebServiceException(e.getMessage(), e); 39. } 40. } 41. }
Implement callbacks
Callback examples in most GWT books and online tutorials are implemented as anonymous inner classes. In this article, I created callbacks as real classes. There are several advantages for this approach. It makes code much cleaner. It allows the client data to be associated with the callback class at run time. Callback classes offer much more flexibility, extensibility, and code reuse. For example, an error handling method can be implemented in a callback base class so that it can be used by all callbacks to ensure all remote service exceptions will be handled consistently. You can easily debug the code inside callback classes because not all IDEs support tracing in the inner classes.
In this
article, I created an abstract base class, RestServiceRpcCallback
(Listing 11) as well as two sub-classes, EmployeeRpcCallback
(Listing 12) and
OrganizationRpcCallback
(Listing 13). In Listing 11, the abstract base
class implements an interface AsyncCallback
.
The onSuccess
method will be called
if the request to the server is successful. Otherwise, the onFailure
method will be
called. The onFailure
method displays an error message passed over from the
server. The onSuccess
method will invoke the processResponse
method to process the
string returned from the RESTful Web service server. The abstract method
processResponse
will be implemented by the sub-classes. The abstract base
class has a member treeItem
that will be an
instance of the TreeItem
widget
in GWT and will be the client data with which the callback is associated
when the callback class is used. This class member will contain the
application object that stores employee data or organizational data
depending on what the TreeItem
widget represents. The TreeItem
widget will
also be used to help create sub-trees and position pop-up windows.
I
created an enum type, EventType
, and class member eventType
. This
class member is used to keep track of which event triggers the
request to the RESTful Web service server so that after the result comes back
from the RESTful Web service server, the callback will need it to decide
how to proceed.
Listing 11. edu.ucar.cisl.gwtRESTTutorialView.client.callback.RestServiceRpcCallback
1. package edu.ucar.cisl.gwtRESTTutorialView.client.callback; 2. import com.google.gwt.user.client.rpc.AsyncCallback; 3. import com.google.gwt.user.client.ui.TreeItem; 4. import com.google.gwt.user.client.Window; 5. public abstract class RestServiceRpcCallback implements AsyncCallback <String> { 6. TreeItem treeItem; 7. public enum EventType {SELECT_EVENT, STATE_CHANGE_EVENT}; 8. protected EventType eventType; 9. public EventType getEventType() { 10. return eventType; 11. } 12. public void setEventType(EventType eventType) { 13. this.eventType = eventType; 14. } 15. public TreeItem getTreeItem() { 16. return treeItem;} 17. public void setTreeItem(TreeItem treeItem) { 18. this.treeItem = treeItem; 19. } 20. public void onSuccess(String result) { 21. if (result == null) 22. return; 23. processResponse(result); 24. } 25. public void onFailure(Throwable caught) { 26. String msg=caught.getMessage(); 27. if (msg != null) 28. Window.alert(msg); 29. } 30. protected abstract void processResponse(String response); 31. }
The processResponse
method
in EmployeeRpcCallback
(lines 8 to 25 in Listing 12)
processes a string returned from the request to the RESTful Web service. The
string contains the employee data in JSON format. This method uses GWT
JSON utility classes to parse the JSON string, and stores the detailed
employee data in an application object EmployeeItemData
, which is contained in
class member treeItem
. Then it sets the dataReady
flag to be true to
indicate that there is no need to request employee data from the RESTfull
Web service next time the user clicks the node. At the end, the method brings
opens a pop-up window to display the details for the employee.
Listing 12. edu.ucar.cisl.gwtRESTTutorialView.client.callback.EmployeeRpcCallback
1. package edu.ucar.cisl.gwtRESTTutorialView.client.callback; 2. import com.google.gwt.json.client.JSONObject; 3. import com.google.gwt.json.client.JSONParser; 4. import com.google.gwt.json.client.JSONValue; 5. import edu.ucar.cisl.gwtRESTTutorialView.client.EmployeePopup; 6. import edu.ucar.cisl.gwtRESTTutorialView.client.bean.EmployeeItemData; 7. import edu.ucar.cisl.gwtRESTTutorialView.client.bean.ItemData; 8. public class EmployeeRpcCallback extends RestServiceRpcCallback { 9. protected void processResponse(String response) { 10. JSONValue jsonValue = JSONParser.parse(response); 11. ItemData iData = (ItemData) treeItem.getUserObject(); 12. JSONObject jobj = jsonValue.isObject(); 13. EmployeeItemData eItemData = (EmployeeItemData) iData; 14. eItemData.setId((int) jobj.get("id").isNumber().doubleValue()); 15. eItemData.setFirstName(jobj.get("firstName").isString().stringValue()); 16. eItemData.setNickName(jobj.get("nickName").isString().stringValue()); 17. eItemData.setLastName(jobj.get("lastName").isString().stringValue()); 18. eItemData.setPhone(jobj.get("phone").isString().stringValue()); 19. eItemData.setEmail(jobj.get("email").isString().stringValue()); 20. eItemData.setTitle(jobj.get("title").isString().stringValue()); 21. iData.setDataReady(true); 22. int left = treeItem.getAbsoluteLeft() + 50; 23. int top = treeItem.getAbsoluteTop() + 30; 24. EmployeePopup.show(left, top, (EmployeeItemData) eItemData); 25. } 26. }
The processResponse
method
in OrganizationRpcCallback
(lines 10 – 31 in Listing 13)
processes the organizational data returned from the RESTful Web service server.
Like the employee data, the organizational data is returned as a JSON
string also.
The organizational data contains detailed information for the
organizational unit
and some information for its employees in the organizational unit and
immediate sub-organizations. The method uses GWT JSON utility classes to
parse the JSON string, and stores the detailed organization data in
an application object OrganizationItemData
contained in class member
treeItem
. Then it sets the dataReady
flag to be true, which indicates that
the detailed organizational data is in the memory already. The method will
call the processEmployees
method to process the data for the employees within
the organizational unit, and calls processSubOrgs
to process the data for its
sub-organizations. At the end, if the event is Select
, it opens a pop-up window to display the
detailed organizational information such as full
name, leader name and title, and total number of employees, including those
working in all its sub-organizations.
The processEmployees
method (lines
44 – 54) processes a JSON array of employee data. It extracts id
and
displayName
for each employee, creates an application object
EmployeeItemData
, creates a TreeItem
widget, and binds the application
object with the widget.
The processSubOrgs
method (Lines 32 – 43)
processes each sub-organization in the JSON array. It extracts id
and
displayName
, and stores them in an application object OrganizationItemData
.
It then creates a TreeItem
widget and binds the application object with
the widget. It's common knowledge that in a desktop file manager application you can have
a folder regardless of whether it is empty. But in GWT, this action is not
supported. As part of the lazy loading strategy, when an organizational TreeItem
widget is created, you don't have the data to create all of its child
widgets. However, you need to make the widget look like an organization (tree
node), instead of like an employee (tree leaf). To work around this
limitation, I created a dummy child TreeItem
widget and set it to be
invisible (lines 39, 40).
Listing 13. edu.ucar.cisl.gwtRESTTutorialView.client.callback.OrganizationRpcCallback
1. package edu.ucar.cisl.gwtRESTTutorialView.client.callback; 2. import com.google.gwt.json.client.JSONArray; 3. import com.google.gwt.json.client.JSONObject; 4. import com.google.gwt.json.client.JSONParser; 5. import com.google.gwt.json.client.JSONValue; 6. import com.google.gwt.user.client.ui.TreeItem; 7. import edu.ucar.cisl.gwtRESTTutorialView.client.bean.EmployeeItemData; 8. import edu.ucar.cisl.gwtRESTTutorialView.client.bean.OrganizationItemData; 9. import edu.ucar.cisl.gwtRESTTutorialView.client.OrganizationPopup; 10. public class OrganizationRpcCallback extends RestServiceRpcCallback { 11. protected void processResponse(String response) { 12. JSONValue jsonValue = JSONParser.parse(response); 13. OrganizationItemData oItemData = (OrganizationItemData) treeItem.getUserObject(); 14. JSONObject jobj = jsonValue.isObject(); 15. oItemData.setId((int) jobj.get("id").isNumber().doubleValue()); 16. oItemData.setDisplayName(jobj.get("acronym").isString().stringValue()); 17. oItemData.setName(jobj.get("name").isString().stringValue()); 18. oItemData.setLeadName(jobj.get("leadName").isString().stringValue()); 19. oItemData.setLeadTitle(jobj.get("leadTitle").isString().stringValue()); 20. oItemData.setTotalEmployees((int) 21. obj.get("totalEmployees").isNumber().doubleValue()); 22. oItemData.setDataReady(true); 23. treeItem.setText(oItemData.getDisplayName()); 24. processEmployees(jobj.get("employees").isArray()); 25. processSubOrgs(jobj.get("subOrgs").isArray()); 26. if (getEventType() == EventType.SELECT_EVENT) { 27. int left = treeItem.getAbsoluteLeft() + 50; 28. int top = treeItem.getAbsoluteTop() + 30; 29. OrganizationPopup.show(left, top, (OrganizationItemData) oItemData); 30. } 31. } 32. protected void processSubOrgs(JSONArray jsonArray) { 33. for (int i = 0; i < jsonArray.size(); ++i) { 34. JSONObject jo = jsonArray.get(i).isObject(); 35. OrganizationItemData iData = new OrganizationItemData(); 36. iData.setId((int) jo.get("id").isNumber().doubleValue()); 37. iData.setDisplayName(jo.get("acronym").isString().stringValue()); 38. TreeItem child = treeItem.addItem(iData.getDisplayName()); 39. TreeItem dummy = child.addItem(""); 40. dummy.setVisible(false); 41. child.setUserObject(iData); 42. } 43. } 44. protected void processEmployees(JSONArray jsonArray) { 45. for (int i = 0; i < jsonArray.size(); ++i) { 46. JSONObject jo = jsonArray.get(i).isObject(); 47. EmployeeItemData eData = new EmployeeItemData(); 48. eData.setId((int) jo.get("id").isNumber().doubleValue()); 49. eData.setDisplayName(jo.get("name").isString().stringValue()); 50. eData.setDataReady(false); 51. TreeItem child = treeItem.addItem(eData.getDisplayName()); 52. child.setUserObject(eData); 53. } 54. } 55. }
Because the GWT JSON library is used to parse out the JSON string, you need to include it in the GWT module configuration file (Listing 14). This file also declares the entry point class for the module (line 6). The file is located in the edu.ucar.cisl.gwtRESTTutorialView package.
Listing 14. GwtRESTTutorialView.gwt.xml
1. <?xml version="1.0" encoding="UTF-8"?> 2. <module rename-to='gwtresttutorialview'> 3. <inherits name='com.google.gwt.user.User'/> 4. <inherits name="com.google.gwt.json.JSON"/> 5. <inherits name='com.google.gwt.user.theme.standard.Standard'/> 6. <entry-point class='edu.ucar.cisl.gwtRESTTutorialView.client.GwtRESTTutorialView'/> 7. <source path='client'/> 8. </module>
Declare the RESTful Web service proxy in the web.xml file
The RPC remote service is technically a servlet. All you must do is configure the servlet in the the web.xml file the same as you would any other servlet (Listing 15).
Listing 15. Part of web.xml to declare the RESTful Web service proxy remote service
1. <servlet> 2. <servlet-name>RESTfulWebServiceServlet</servlet-name> 3. <servlet-class> 4. edu.ucar.cisl.gwtRESTTutorialView.server.RESTfulWebServiceProxyImpl 5. </servlet-class> 6. </servlet> 7. <servlet-mapping> 8. <servlet-name>RESTfulWebServiceServlet</servlet-name> 9. <url-pattern>/gwtresttutorialview/RESTfulWebServiceProxy</url-pattern> 10. </servlet-mapping>
Implement the GWT client interface
Create a main window
Listing 16 lists the entry
point class for the module. This class must implement the EntryPoint
interface. The method onModuleLoad
is the first method to be executed
after the module is loaded. The class also implements the
SelectionHandler<TreeItem>
and OpenHandler<TreeItem>
interfaces to handle tree node selection and open events. In earlier
releases, GWT provided a lot of event listener interfaces. However, they have
been replaced by event handlers since the 1.6 release.
The method
onModuleLoad
instantiates a Tree
widget and a TreeItem
widget as the tree
widget's root to represent the highest level of an organization. An
application object OrganizationItemData
is created and is associated with
the root TreeItem
. The id
for the object is set to 1, and can be set to
any level of the organization to be used as a starting point. Because the root node is
meant to represent an organization instead of an employee, it needs to behave
and look like a tree node that can be opened. Currently, the GWT TreeItem
widget
doesn't provide this capability. As a work-around, I created a dummy
TreeItem
as a child of the root and set the dummy TreeItem
to be
invisible. Now when the state for the root TreeItem
is set to be open
(line 35), an Open
event is launched and the
onOpen
method is
invoked to create the first level organizational structure, including a list
of employees and sub-organizations. The Tree
Widget is added to RootPanel
,
which is the top container for all widgets in GWT applications.
Tree
widget event handler method onSelection
(lines
38-51) is called when
a user selects either the employee TreeItem
or
the organization TreeItem
widget. It
retrieves application item data from the widget and opens a pop-up
window to display the detailed data if the data has been loaded. Otherwise,
it calls invokeRESTfulWebService
to send a request to the proxy server.
The latter method is discussed in the next section.
Another
Tree
widget event handler method, onOpen
,
(lines 53-60) is called when
the user opens the organization TreeItem
widget. If the detailed data for
the organization, including employee data and immediate sub-organization
data, is not available, this method, like onSelection
, calls
invokeRESTfulWebService
to send a request to the proxy
server.
Listing 16. edu.ucar.cisl.gwtRESTTutorialView.client. GwtRESTTutorialView
1. package edu.ucar.cisl.gwtRESTTutorialView.client; 2. import com.google.gwt.core.client.EntryPoint; 3. import com.google.gwt.core.client.GWT; 4. import com.google.gwt.event.logical.shared.OpenEvent; 5. import com.google.gwt.event.logical.shared.OpenHandler; 6. import com.google.gwt.event.logical.shared.SelectionEvent; 7. import com.google.gwt.event.logical.shared.SelectionHandler; 8. import com.google.gwt.user.client.ui.RootPanel; 9. import com.google.gwt.user.client.ui.Tree; 10. import com.google.gwt.user.client.ui.TreeItem; 11. import com.google.gwt.user.client.ui.Tree.Resources; 12. import edu.ucar.cisl.gwtRESTTutorialView.client.bean.EmployeeItemData; 13. import edu.ucar.cisl.gwtRESTTutorialView.client.bean.ItemData; 14. import edu.ucar.cisl.gwtRESTTutorialView.client.bean.OrganizationItemData; 15. import edu.ucar.cisl.gwtRESTTutorialView.client.callback.EmployeeRpcCallback; 16. import edu.ucar.cisl.gwtRESTTutorialView.client.callback.OrganizationRpcCallback; 17. import edu.ucar.cisl.gwtRESTTutorialView.client.callback.RestServiceRpcCallback; 18. /**Entry point classes define <code>onModuleLoad()</code>. 19. */ 20. public class GwtRESTTutorialView implements EntryPoint, 21. SelectionHandler<TreeItem>, OpenHandler<TreeItem> { 22. final static String contentType="application/json"; 23. public void onModuleLoad() { 24. TreeItem root = new TreeItem("Root"); 25. ItemData iData = new OrganizationItemData(); 26. iData.setId(1); 27. root.setUserObject(iData); 28. TreeItem dummyItem = root.addItem(""); 29. dummyItem.setVisible(false); 30. Tree tree = new Tree((Resources) GWT.create(OrgTreeResource.class), true); 31. tree.addItem(root); 32. tree.addSelectionHandler(this); 33. tree.addOpenHandler(this); 34. RootPanel.get().add(tree); 35. root.setState(true, true); 36. } 37. @Override 38. public void onSelection(SelectionEvent<TreeItem> event) { 39. TreeItem item=event.getSelectedItem(); 40. ItemData iData = (ItemData) item.getUserObject(); 41. if (iData.isDataReady()) { 42. int left = item.getAbsoluteLeft() + 50; 43. int top = item.getAbsoluteTop() + 30; 44. if (iData instanceof EmployeeItemData) 45. EmployeePopup.show(left, top, (EmployeeItemData) iData); 46. else 47. OrganizationPopup.show(left, top, (OrganizationItemData) iData); 48. } else 49. invokeRESTfulWebService(item, 50. RestServiceRpcCallback.EventType.SELECT_EVENT); 51. } 52. @Override 53. public void onOpen(OpenEvent<TreeItem> event) { 54. TreeItem item = event.getTarget(); 55. ItemData iData = (ItemData) item.getUserObject(); 56. if (!iData.isDataReady()) { 57. invokeRESTfulWebService(item, 58. RestServiceRpcCallback.EventType.STATE_CHANGE_EVENT); 59. } 60. } 61. protected void invokeRESTfulWebService(TreeItem item, 62. RestServiceRpcCallback.EventType eventType) { 63. ItemData iData = (ItemData) item.getUserObject(); 64. RestServiceRpcCallback callback = null; 65. if (iData instanceof EmployeeItemData) 66. callback = new EmployeeRpcCallback(); 67. if (iData instanceof OrganizationItemData) 68. callback = new OrganizationRpcCallback(); 69. callback.setEventType(eventType); 70. callback.setTreeItem(item); 71. RESTfulWebServiceProxyAsync ls = RESTfulWebServiceProxy.Util.getInstance(); 72. ls.invokeGetRESTfulWebService(iData.buildUri(), contentType, callback); 73. } 74. }
Send a RESTful Web service request to the RPC proxy server
Method invokeRESTfulWebService
(lines 61–73)
sends a RESTful Web service request to the proxy server using an RPC service.
It first retrieves application item data from the TreeItem
widget and
instantiates a callback instance of either EmployeeRpcCallback
or
OrganizationItemData
depending on the nature of the application item data. It
then associates the TreeItem
widget and event type with the callback
instance so that it knows how to proceed after the data for the RESTful Web
service is returned.
As required by GWT, before a remote service is
called, an instance of the async remote interface must be created and
used to invoke the remote service, with all parameters declared in the
remote service and the instance of the callback class. Because the remote
service call is asynchronous and non-blocking, the GWT client does not
wait for a response from the service. It continues executing until it
receives an asynchronous callback from the remote server. The callback
informs the GWT application of whether the remote service call has
been executed successfully. The onSuccess
method is called if the
remote service is successful. Otherwise, the onFailure
method is
called with an instance of Throwable
, which contains a custom exception
passed from the server. the callback class will process the data returned
from the server.
Create custom tree images
It's very easy to
customize the tree images for the GWT Tree
widget. You simply need to create a
custom interface that extends the Tree.Resource
interface and redeclare
treeOpen
, treeClosed
, and treeLeaf
methods (Listing 17). Then use
GWT.create
to instantiate an instance of the new interface and pass it to
the Tree
widget constructor when the Tree
widget is created (Listing 16, line
30). Three image files with names starting with treeOpen, treeClosed and
treeLeaf, respectively, need to be placed in the same
folder.
Listing 17. edu.ucar.cisl.gwtRESTTutorialView.client.OrgTreeResource
1. package edu.ucar.cisl.gwtRESTTutorialView.client; 2. import com.google.gwt.resources.client.ImageResource; 3. import com.google.gwt.user.client.ui.Tree.Resources; 4. public interface OrgTreeResource extends Resources { 5. ImageResource treeOpen(); 6. ImageResource treeClosed(); 7. ImageResource treeLeaf(); 8. }
Implement pop-up windows
Two
pop-up windows are created to display detailed information for an employee
and an organizational unit. Listing 18 lists the implementation for the employee
pop-up window. The class extends the GWT PopupPanel
widget. It is implemented as a
singleton class. It uses six pairs of Label
widgets to display the labels
and values for first name, nickname, last name, title, phone, and e-mail. A
Grid
widget is used to handle the layout for Label
widgets. To display the
detailed employee data, all you need to do is to call the static method show
and
pass the location in terms of left and top offsets from the widget it
refers to. In this case, the reference widget is the TreeItem
widget the user
selects. The implementation for organizational pop-up window is similar (Listing
19).
Listing 18. edu.ucar.cisl.gwtRESTTutorialView.client.EmployeePopup
1. package edu.ucar.cisl.gwtRESTTutorialView.client; 2. import com.google.gwt.user.client.ui.Grid; 3. import com.google.gwt.user.client.ui.Label; 4. import com.google.gwt.user.client.ui.PopupPanel; 5. import edu.ucar.cisl.gwtRESTTutorialView.client.bean.EmployeeItemData; 6. public class EmployeePopup extends PopupPanel { 7. static protected EmployeePopup instance=null; 8. protected Grid grid = new Grid(6, 2); 9. protected Label firstNameLabel = new Label("First Name"); 10. protected Label firstNameValueLabel = new Label("First Name"); 11. protected Label nickNameLabel = new Label("Nickname"); 12. protected Label nickNameValueLabel = new Label("Nick Name"); 13. protected Label lastNameLabel = new Label("Last Name"); 14. protected Label lastNameValueLabel = new Label("Last Name"); 15. protected Label titleLabel = new Label("Title"); 16. protected Label titleValueLabel = new Label("Title"); 17. protected Label phoneLabel = new Label("Phone Number"); 18. protected Label phoneValueLabel = new Label("Phone Number"); 19. protected Label emailNameLabel = new Label("Email"); 20. protected Label emailValueLabel = new Label("Email"); 21. protected EmployeePopup() { 22. super(true); 23. grid.setWidget(0, 0, firstNameLabel); 24. grid.setWidget(0, 1, firstNameValueLabel); 25. grid.setWidget(1, 0, nickNameLabel); 26. grid.setWidget(1, 1, nickNameValueLabel); 27. grid.setWidget(2, 0, lastNameLabel); 28. grid.setWidget(2, 1, lastNameValueLabel); 29. grid.setWidget(3, 0, titleLabel); 30. grid.setWidget(3, 1, titleValueLabel); 31. grid.setWidget(4, 0, phoneLabel); 32. grid.setWidget(4, 1, phoneValueLabel); 33. grid.setWidget(5, 0, emailNameLabel); 34. grid.setWidget(5, 1, emailValueLabel); 35. grid.setWidth("300px"); 36. // grid.setHeight("400px"); 37. setWidget(grid); 38. } 39. public void setEmployeeData(EmployeeItemData iData) { 40. String firstName = iData.getFirstName(); 41. String lastName = iData.getLastName(); 42. String nickName = iData.getNickName(); 43. String phone = iData.getPhone(); 44. String email = iData.getEmail(); 45. String title = iData.getTitle(); 46. firstNameValueLabel.setText(firstName); 47. if (nickName != null && nickName.length() > 0) { 48. nickNameValueLabel.setVisible(true); 49. nickNameLabel.setVisible(true); 50. nickNameValueLabel.setText(nickName); 51. } 52. else { 53. nickNameValueLabel.setVisible(false); 54. nickNameLabel.setVisible(false); 55. } 56. lastNameValueLabel.setText(lastName); 57. phoneValueLabel.setText(phone); 58. emailValueLabel.setText(email); 59. titleValueLabel.setText(title); 60. } 61. protected static EmployeePopup getInstance() { 62. if (instance == null) 63. instance = new EmployeePopup(); 64. return instance; 65. } 66. public static void show(int leftOffset, int topOffset, EmployeeItemData eData) { 67. EmployeePopup popup = getInstance(); 68. popup.setEmployeeData(eData); 69. popup.setPopupPosition(leftOffset, topOffset); 70. popup.show(); 71. } 72. }
Listing 19. edu.ucar.cisl.gwtRESTTutorialView.client.OrganizationPopup
1. package edu.ucar.cisl.gwtRESTTutorialView.client; 2. import com.google.gwt.user.client.ui.Grid; 3. import com.google.gwt.user.client.ui.Label; 4. import com.google.gwt.user.client.ui.PopupPanel; 5. import edu.ucar.cisl.gwtRESTTutorialView.client.bean.OrganizationItemData; 6. public class OrganizationPopup extends PopupPanel { 7. static protected OrganizationPopup instance=null; 8. protected Grid grid = new Grid(3, 2); 9. protected Label nameLabel = new Label("Full Name"); 10. protected Label nameValueLabel = new Label("Full Name"); 11. protected Label leadNameLabel = new Label("Lead"); 12. protected Label leadNameValueLabel = new Label("Lead Name"); 13. protected Label totalEmployeesLabel = new Label("Total Employees"); 14. protected Label totalEmployeesValueLabel = new Label("Total Employees"); 15. public OrganizationPopup() { 16. super(true); 17. grid.setWidget(0, 0, nameLabel); 18. grid.setWidget(0, 1, nameValueLabel); 19. grid.setWidget(1, 0, leadNameLabel); 20. grid.setWidget(1, 1, leadNameValueLabel); 21. grid.setWidget(2, 0, totalEmployeesLabel); 22. grid.setWidget(2, 1, totalEmployeesValueLabel); 23. grid.setWidth("700px"); 24. setWidget(grid); 25. } 26. public void setOrganizationData(OrganizationItemData iData) { 27. nameValueLabel.setText(iData.getName()); 28. leadNameValueLabel.setText(iData.getLeadName()); 29. totalEmployeesValueLabel.setText(new 30. Integer(iData.getTotalEmployees()).toString()); 31. } 32. protected static OrganizationPopup getInstance() { 33. if (instance == null) 34. instance = new OrganizationPopup(); 35. return instance; 36. } 37. public static void show(int leftOffset, int topOffset,OrganizationItemData oData) { 38. OrganizationPopup popup = getInstance(); 39. popup.setOrganizationData(oData); 40. popup.setPopupPosition(leftOffset, topOffset); 41. popup.show(); 42. } 43. }
Put it all together
After all the classes are implemented, you should have the following folders and files under the src folder for the project in Eclipse (Figure 2).
Figure 2. Project folder in Eclipse

To run it, right-click on the project name in Project Explore, choose Run As > Web Application or Debug As > Web Application. Copy the URL from the Developer Mode window and paste it into your favorite browser. The application should look like Figure 3.
Figure 3. Org tree application in a browser

Conclusion
GWT can help Java developers build rich and responsive desktop-like applications, particularly large-scale Web apps. In this article, I demonstrated how to use GWT tree widgets to display the organizational structure of a company. I used an RPC proxy to integrate with RESTful Web services. JSON is data format used by RESTful Web services. Organizational data and employee data are loaded only when needed, and tree nodes (organizations) and leaves (employees) are dynamically created. Callbacks are implemented as real classes to help facilitate code reuse and associated with client data at run time. the tree images are customized to show organizations and employees, and pop-up windows are used to display the details for organizations and employees.
This article was made possible by research supported in part by the National Science Foundation, pursuant to its cooperative agreement with the University Corporation for Atmospheric Research. The National Center for Atmospheric Research is sponsored by the National Science Foundation.
Downloadable resources
- PDF of this content
- RESTful Web services (gwtRESTTutorialView.zip | 1365KB)
- Source code (gwtRESTTutorial.war | 9233KB)
- Database setup (setup.sql | 8KB)
Related topics
- Read the companion article "A multi-tier architecture for building RESTful Web services" (developerWorks, June 2009) to get an overview of the concept of REST and RESTful Web services, then compare them to RPC-style/SOAP-based Web services.
- See "Build RESTful Web services and dynamic Web applications with the multi-tier architecture" (developerWorks, June 2009) to continue your training on building RESTful Web services and dynamic Web applications using the multi-tier architecture.
- Learn more about GWT.
- Read "Simplify Ajax development using Cypal Studio for GWT " (developerWorks, June 2007) to learn how Cypal Studio for GWT helps create new GWT modules, supports the creation of remote procedure calls, and makes it easy to view and deploy your Web applications.
- "GWT fu, Part 1: Going places with Google Web Toolkit" (developerWorks, September 2009) is a two-part series that brings you up to speed on the latest version of GWT and shows you how to implement a desktop-like Web application.
- "Build a RESTful Web service" (developerWorks, July 2008) guides you step-by-step through the fundamental concepts of REST and building applications with Restlets.
- "Integrate XForms with the Google Web Toolkit, Part 1: Introducing GWT's JavaScript Native Interface" (developerWorks, September 2007) demonstrates how to use the Google Web Toolkit and XForms together to create a dynamic Web application.
- Download Apache Tomcat 6.x.
- Download MySQL 5.1.
- Download DB2 Express-C.
- Download Eclipse IDE for Java EE Developers.
- Download GWT and the GWT plug-in for Eclipse.