Build a dynamic organization tree using GWT and RESTful Web services

This article shows you how to use GWT Tree widgets to display an organizational structure of a company, and how to implement an RPC proxy to integrate with RESTful Web services that provide organizational data and employee data. The article also discusses a lazy loading strategy to ensure a faster start up, to reduce the data download time, and to minimize memory usage.

Bruce Sun, Java Architect, National Center for Atmospheric Research

Photo of Bruce SunBruce Sun is a Sun Microsystems certified Java architect. He has been developing Java-based Web applications since 1998. He is currently working as a Senior Software Engineer at the National Center for Atmospheric Research (NCAR).



19 January 2010

Also available in Chinese Japanese

Introduction

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 Resources for links.)

  1. Eclipse IDE for Java EE Developers Galileo (Eclipse 3.5)
  2. GWT 2.0
  3. GWT plug-in for Eclipse
  4. MySQL 5.1 or DB2® Express-C
  5. 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 Resources. 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, and database_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 Resources.

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:

  1. Select File > New > Web Application Project.
  2. Enter gwtRESTTutorialView in the Project Name field and edu.ucar.cisl.gwtRESTTutorialView in the Package field in the New Web Application Project window (see Figure 1).
  3. 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
Screen shot showing creation of gwtRESTTutorialView 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

Click to see code listing

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
Screen shot of the gwtRESTTutorialView project 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
Screen shot showing browser view of application with the NCAR company, various employees and entities underneath

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.


Downloads

DescriptionNameSize
RESTful Web servicesgwtRESTTutorialView.zip1365KB
Source codegwtRESTTutorial.war9233KB
Database setupsetup.sql8KB

Resources

Learn

Get products and technologies

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Web development on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Web development
ArticleID=462317
ArticleTitle=Build a dynamic organization tree using GWT and RESTful Web services
publish-date=01192010