Build RESTful Web services and dynamic Web applications with the multi-tier architecture

Continue your training on building RESTful Web services and dynamic Web applications using the multi-tier architecture. This article gives you hands-on experience on designing and building components in each tier and how the components are tied together. It provides an example of how RESTful Web services, Asynchronous JavaScript and XML (Ajax), and Spring Web Flow work together to produce a desktop-like rich and responsive Web interface. It also demonstrates how client programs such as Ruby scripts utilize the RESTful Web services to upload and download the user data from the server.

Share:

Bruce Sun (bsun@ucar.edu), 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).



30 June 2009

Also available in Chinese Japanese Spanish

Introduction

In the previous article, I discussed the multi-tier architecture to build RESTful Web services and dynamic Web applications. I proposed a Resource Request Handler (RRH) in the Presentation Layer for Ajax/Google Web Toolkit (GWT) and calls from external client applications, and a Browser Request Handler (BRH) for processing the request from the browser and generating output to display in the browser. Both handlers share a common Business Logic Layer, which, in turn, interacts with the Data Access Layer. In the sample application, the application tier is built using Java™ code. This article uses the Jersey framework for RESTful Web services; Spring frameworks for MVC, navigation, and JDBC; and MySQL as the database. Eclipse is used as an IDE. The sample application will be deployed in Tomcat. The sample application is a simple fiction application for National Center for Atmospheric Research (NCAR) administrators to sign up NCAR employees.


The scenario

In this scenario, the administrator uses the browser interface to sign up new NCAR employees. At NCAR, there are four labs, each with various divisions:

  • The Computational and Information Systems Laboratory
  • The Earth and Sun Systems Laboratory
  • The Earth Observing Laboratory
  • The Research Applications Laboratory

The sign-up interface in the application includes the following fields:

  • username
  • password
  • last name
  • first name
  • e-mail
  • the lab and division where the employee is working

Within these fields, the lab and division fields are both selectable option menus, and the username must be unique. If a username has already been used and the administrator tries to enter it again, the browser will display a warning and the username field will be cleared.

The list of items in the division option menu depends on the lab selected in the lab option menu. When the interface first opens, the division field is disabled. After the administrator selects the lab, the division option menu is enabled, but only contains the divisions for the selected lab. After the administrator fills in the information and clicks Submit, the system will add the new user to the MySQL database and a success message is displayed.

The system administrator needs to be able to run a batch process to upload new users as well as download users that are already signed up. The batch program can be implemented using Ruby, Python, Pearl, or Java code. I use Ruby as an example in this article. No authentication is required for either the browser interface or RESTful Web services.

Note: The fictional application is not production-ready. Exception handling, logging, authentication and data validation, to list a few, are likely needed in order for it to be used in the real world.


The components

Table 1 lists the components and how they are organized in the folder structure.

Table 1. The folder structure
TierLayers and scriptsFile Location
Client TierAjax ScriptsWebRoot/js
JSP PagesWebRoot/WEB-INF/jsp
Ruby ScriptsClient/ruby
Presentation TierPresentation Layer - Browser Request Handlersrc/edu/ucar/cisl/ncarUsers/presentation/brh
Presentation Layer - Resource request handlersrc/edu/ucar/cisl/ncarUsers /presentation/rrh
Business Logic Layersrc/edu/ucar/cisl/ncarUsers/bll
Data Access Layersrc/edu/ucar/cisl/ncarUsers/dal
Data store TierMySql Scriptsdb/setup.sql

Download the source code and unzip it to your C drive. You should now see a new folder, C:\ncarUsers. Under this folder, you can see the complete folder structure listed in Table 1. The download file includes all source code as well as all required libraries for MySQL Connector/J Driver 5.1, Jersey 1.0, Spring Framework 2.5.5, and Spring Web Flow 2.0.2, all of which should be sufficient for this demo, unless you need to try out the newer releases.


Set up the environment

Download the following software packages and install them according to the installation guides on their respective Web sites. (See Resources for links.)

  1. Apache Tomcat 6.x
  2. MySQL 5.1
  3. Eclipse IDE for Java EE Developers (I was using the Eclipse Europa release, but the newer version would work also.)
  4. Ruby

After the Ruby installation, run the command gem install –remote to download and install the json (Figure 1) and rest-open-uri libraries.

Figure 1. Ruby gen install json library
Console screenshot showing execution of the Ruby post-installation commands.

Create the database

I use the MySQL database in this article. To create a MySQL server instance, use the MySQL Server Instance Configuration Wizard . The default name of the instance is MYSQL. To start the MySQL command-line tool, run mysql –u root –p MYSQL. (You need to provide the password set in the previous step for the root login.) Next run source c:/ncarUsers/db/setup.sql in the command-line tool to create the database (ncar_users), a MySQL user (tutorial) with the password (tutorial), and tables for this article (Figure 2). The script also inserts the data into lab and division tables.

Figure 2. Run script file setup.sql
Command window showing the mysql -u root -p MYSQL command

Configure Tomcat Server in Eclipse

To configure the Tomcat Server:

  1. Open Eclipse and select File > New > Other.
  2. Select Server from the list.
    Figure 3. Configure Tomcat server in Eclipse--Step 1
    Screen shot shows menu selection of Server in the wizard.
  3. Click Next. In the window, select Apache > Tomcat v6.0 Server.
    Figure 4. Configure Tomcat server in Eclipse--Step 2
    Screenshot showing selection of the Tomcat 6.0 server.
  4. Click Next. In the next window, click Browse… and select the location of Tomcat installation.
    Figure 5. Configure Tomcat server in Eclipse--Step 3
    Screenshot showing configuration of the Tomcat server. Tomcat installation directory is set to 'C:\apache-tomcat-6-6.0.16'
  5. Click Finish.

Create Web project ncarUsers in Eclipse

To create the Web project:

  1. Select File > New > Project…. Open Web.
  2. Click on Dynamic Web Project.
    Figure 6. Create Web project ncarUsers in Eclipse--Step 1
    Screen shot shows selection of Dynamic Web Project
  3. Click Next. In the new window, enter ncarUsers in the project name field.
    Figure 7. Create Web project ncarUsers in Eclipse--Step 2
    Screen shot of Dynamic Web Project configuration. Project name is set to 'ncarUsers'
  4. Click Next.
  5. Click Next again in the Project Facets window.
  6. In the Web Module window, change WebContent to WebRoot in the Content Directory field.
  7. Click Finish.
    Figure 8. Create Web project ncarUsers in Eclipse--Step 3
    Screen shot of Web Module configuration. Content Directory has been set to 'WebRoot'

Import the files from the article download

To import the files:

  1. In Project Explorer, right click ncarUsers and choose Import > Import ….
  2. In the Import window, click General > File System (Figure 9).
    Figure 9. Import the article download to ncarUsers project--Step 1
    Screen shot shows the Import screen. File system is highlighted.
  3. Click Next.
  4. In the File System window, click Browse ..., and choose C:\ncarUsers.
  5. Select the checkbox by ucarUsers (Figure 10).
    Figure 10. Import the article download to ncarUsers project--Step 2
    Screen shot shows the File System import screen with 'C:\ncarUsers\src\edu' selected. The checkbox next to it is checked.'

After the imports the Project Explorer should look like the Figure 11.

Figure 11. Result of project import
Screen shot shows the Eclipse screen.

If you would like to skip the following sections implementing domain objects, the Data Access Layer (DAL), the Business Logic Layer (BLL), the Presentation Layer including the Browser Request Handler and the Resource Request Handler, and client applications, you can skip to the section on Running the application from Eclipse.


Implement Domain Objects

Domain objects model the application problem domain. I implemented three domain objects: User (Listing1), Lab (Listing 2), and Division (Listing 3).

Listing 1. edu.ucar.cisl.ncarUsers.domain.User
1.	package edu.ucar.cisl.ncarUsers.domain;

2.	import java.io.Serializable;

3.	public class User implements Serializable {
4.	   protected int ID;
5.	   protected String userName;
6.	   protected String password;
7.	   protected String firstName;
8.	   protected String lastName;
9.	   protected String email;
10.	   protected int lab;
11.	   protected int division;

12.	... //getters and setters
13.	}
Listing 2. edu.ucar.cisl.ncarUsers.domain.Lab
1.	package edu.ucar.cisl.ncarUsers.domain;
2.	import java.io.Serializable;

3.	public class Lab implements Serializable {
4.	    protected int ID;
5.	    protected String shortName;
6.	    protected String name;
7.	    protected String description;

8.	    ... //getters and setters   
9.	}
Listing 3. edu.ucar.cisl.ncarUsers.domain.Division
1.	package edu.ucar.cisl.ncarUsers.domain;

2.	import java.io.Serializable;

3.	public class Division implements Serializable {
4.	    protected int ID;
5.	    protected String shortName;
6.	    protected String name;
7.	    protected String description;
8.	    protected int labID;

9.	    ... //getters and setters    
10.	}

Implement Data Access Layer

In the Data Access Layer (DAL), I created three data access objects: UserDAO, LabDAO, and DivisionDAO. Data access objects may or may not match the domain objects. Listing 4 shows the UserDAO interface and Listing 5 shows its implementation in which Spring JDBC framework is used to perform insert/update (line 21) and query (line 30). An inner class has been implemented for query (lines 31-44) to map returned the ResultSet object to the User object. LabDAO and DivisionDAO are implemented in the same way.

Listing 4. edu.ucar.cisl.ncarUsers.dal.UseDAO
1.    package edu.ucar.cisl.ncarUsers.dal;

2.    ...//imports

3.    public interface UserDAO
4.    {
5.      public User getUser(String s);    
6.      public void addUser(User user);
7.      public ArrayList<User> getAllUsers();
8.    }
Listing 5. edu.ucar.cisl.ncarUsers.dal.UserDAOJDBCImpl
1.   package edu.ucar.cisl.ncarUsers.dal;
2.   ...//imports

3.   public class UserDAOJDBCImpl extends SimpleJdbcDaoSupport 
          implements UserDAO {
4.        public getUser(String s) {
5.            String criteria="USERNAME = '" + s + "'";
6.            ArrayList<User> users=getUsers(criteria);
7.            if (users.size() > 0)
8.                return users.get(0);
9.            else
10.                return null;
11.        }

12.        public void addUser(User user) {
13.            Object objs[] = new Object[7];
14.            objs[0] = user.getUserName();
15.            objs[1] = user.getPassword();
16.            objs[2] = user.getEmail();
17.            objs[3] = user.getFirstName();
18.            objs[4] = user.getLastName();
19.            objs[5] = user.getLab();
20.            objs[6] = user.getDivision();

21.            this.getJdbcTemplate().update("insert into USER (USERNAME, 
                    PASSWORD, EMAIL, FIRST_NAME, LAST_NAME, LAB, DIVISION )
                     values (?, ?, ?, ?, ?, ?, ?)", objs);
22.            }

23.        public ArrayList<User> getAllUsers(){
24.            return getUsers(null);
25.        }

26.        protected ArrayList<User> getUsers(String criteria)   {
27.            String query="select ID, USERNAME, PASSWORD, EMAIL, 
                      FIRST_NAME, LAST_NAME, LAB, DIVISION from USER";
28.            if (criteria != null && criteria.trim().length() > 0)
29.                query= query + " Where " + criteria;
30.                Collection users = this.getJdbcTemplate().query(query,
31.                    new RowMapper() {
32.                        public Object mapRow(ResultSet rs, int rowNum) throws 
                                 SQLException {
33.                            User user = new User();
34.                            user.setID(rs.getInt("ID"));
35.                            user.setUserName(rs.getString("USERNAME"));
36.                            user.setPassword(rs.getString("PASSWORD"));
37.                            user.setEmail(rs.getString("EMAIL"));
38.                            user.setFirstName(rs.getString("FIRST_NAME"));
39.                            user.setLastName(rs.getString("LAST_NAME"));
40.                            user.setLab(rs.getInt("LAB"));
41.                            user.setDivision(rs.getInt("DIVISION"));
42.                            return user;
43.                         }
44.                     });
45.                ArrayList<User> results= new ArrayList <User>();
46.                Iterator it=users.iterator();
47.                while (it.hasNext())
48.                    results.add((User)it.next());

49.                return results;      
50.            }
51.       }

Implement Business Logic Layer

The Business Logic Layer (BLL) is where the business rules are centralized. This layer also handles the requests from the Presentation Layer and interacts with the DAL to retrieve the data from the back end and request the DAL to perform data persistence. I implemented three manager classes: one for each domain object. Listings 6 and 7 show the UserManager interface and its implementation. Implementations for LabManager and DivisionManager are very similar to UserManager.

Listing 6. edu.ucar.cisl.ncarUsers.bll.UserManager
1.	package edu.ucar.cisl.ncarUsers.bll;

2.	...//imports

3.	public interface UserManager {
4.	    public User getUser(String userName);	
5.	    public void addUser(User user);	
6.	    public ArrayList<User> getAllUsers();
7.	}
Listing 7. edu.ucar.cisl.ncarUsers.bll.UserManagerImpl
1.	package edu.ucar.cisl.ncarUsers.bll;

2.	...//imports

3.	public class UserManagerImpl implements UserManager {
4.	    protected UserDAO userDao;

5.	    public User getUser(String userName)	{
6.	        return userDao.getUser(userName);
7.	    }

8.	    public void addUser(User user)	{
9.	        userDao.addUser(user);
10.	    }
  
11.	    public UserDAO getUserDao() {
12.	        return userDao;
13.	    }

14.	    public void setUserDao(UserDAO userDao) {
15.	        this.userDao = userDao;
16.	    }

17.	    public ArrayList<User> getAllUsers() {
18.	        return userDao.getAllUsers();
19.	    }	
20.	 }

Implement Presentation Layer

Browser Request Handler

A browser interface is required to allow the NCAR administrators to add users on the Web. I use Spring MVC and the Spring Web Flow framework to implement the Browser Request Handler. There are numerous articles and tutorials that have been published on Spring Web Flow, and you can find several in Resources.

Listing 8 configures the Spring MVC servlet. This servlet will serve all the requests from the browser except the ones from Ajax. As a good practice, URIs for all such requests always start with /brh, whereas URLs for all requests from RESTful Web service client programs, including Ajax clients, always start with /rrh.

Listing 8. Servlet definition to use Spring MVC and Spring Web Flow in /Web-Inf/web.xml
1.<servlet>
2.    <servlet-name>ncarUsers</servlet-name>
3.    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
4.    <init-param>
5.        <param-name>contextConfigLocation</param-name>
6.        <param-value>/WEB-INF/ncarUsers-spring-config.xml</param-value>
7.    </init-param>
8.</servlet>    
9.<servlet-mapping>
10.    <servlet-name>ncarUsers</servlet-name>
11.    <url-pattern>*.htm</url-pattern>
12.</servlet-mapping>

Listing 9 configures Spring Web Flow. It maps view names to JavaServer Pages (JSP) files (lines 6-9) and registers flows defined in flow config files (lines 11-13).

Listing 9. /WEB-INF/ncarUsers-spring-config.xml
1.<?xml version="1.0" encoding="UTF-8"?>
2.<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:flow="http://www.springframework.org/schema/webflow-config"
xsi:schemaLocation="
      http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
      http://www.springframework.org/schema/webflow-config
      http://www.springframework.org/schema/webflow-config/spring-webflow-config-1.0.xsd">
3.    <bean name="/flow.htm" 
       class="org.springframework.webflow.executor.mvc.FlowController">
4.        <property name="flowExecutor" ref="flowExecutor"/>
5.    </bean>

6.    <bean id="viewResolver" 
       class="org.springframework.web.servlet.view.InternalResourceViewResolver">
7.        <property name="prefix" value="/WEB-INF/jsp/"/>
8.        <property name="suffix" value=".jsp"/>
9.    </bean>
10.    <flow:executor id="flowExecutor" registry-ref="flowRegistry"/>
11.    <flow:registry id="flowRegistry">
12.        <flow:location path="/WEB-INF/flows/**-flow.xml"/>
13.    </flow:registry>    
14.</beans>

I implemented one form class (Listing 10) and one form action class (Listing 11) to facilitate the Add User page flow. The form class contains the data to be displayed in the browser interface and stores the data NCAR administrators fill in the form as shown in addUser.jsp (Listing 12). Spring tag libraries are used to bind the form data with fields in HTML form. Form action class contains behavior for form actions.

Listing 10. edu.ucar.cisl.ncarUsers.presentation.brh.AddUserForm
1.	package edu.ucar.cisl.ncarUsers.presentation.brh;

2.	...//imports

3.	public class AddUserForm implements Serializable {
4.	    protected ArrayList<Lab> labs;
5.	    protected User user;

6.	    public AddUserForm() {
7.	    }

8.	    ... //getters and setters

9.	}
Listing 11. edu.ucar.cisl.ncarUsers.presentation.brh.AddUserFormAction
1.	package edu.ucar.cisl.ncarUsers.presentation.brh;

2.	...//imports

3.	public class AddUserFormAction extends FormAction {
4.	    protected UserManager userManager;
5.	    protected LabManager labManager;

6.	    public AddUserFormAction() {
7.	        userManager = null;
8.	    }

9.	    public Event initForm(RequestContext context) throws Exception {
10.	        AddUserForm form = (AddUserForm) getFormObject(context);
11.	        form.setLabs(this.labManager.getLabs());
12.	        form.setUser(new User());
13.	        return success();
14.	    }

15.	    public Event submit(RequestContext context) throws Exception {
16.	        AddUserForm form = (AddUserForm) getFormObject(context);
17.	        User user = form.getUser();
18.	        userManager.addUser(user);
19.	        return success();
20.	    }

21.	    public Event addNewUser(RequestContext context) throws Exception {
22.	        initForm(context);
23.	        return success();
24.	    }

25.	    ... //getters and setters

26.	}
Listing 12. /WEB-INF/jsp/addUser.jsp
1.<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
2.<%@ page language="java"%>
3.<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
4.<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>

5.<html>
6.<head>
7.<title>NCAR New User Registration</title>
8.<script language="JavaScript" src="js/addUserAjax.js"></script>
9.</head>
10.<body>
11.<h1>NCAR New User Registration</h1>
12.<form:form commandName="addUserForm" method="post" 
          action="flow.htm">
13.<input type="hidden" name="_flowExecutionKey" 
          value="${flowExecutionKey}">
14.<table>
15.<tr>
16.    <td>Username:</td>
17.    <td align="left">
18.        <form:input path="user.userName" id="userName" 
                     onblur="validateUsername();" />
19.   </td>
20.</tr>
21.<tr>
22.    <td>Password:</td>
23.    <td align="left">
24.        <form:input path="user.password" id="password" />
25.    </td>
26.</tr>
27.<tr>
28.     <td>&nbsp;</td>
29.</tr>
30.<tr>
31.    <td>First Name:</td>
32.    <td align="left">
33.        <form:input path="user.firstName" id="email" />
34.    </td>
35.</tr>
36.<tr>
37.    <td>Last Name:</td>
38.    <td align="left">
39.        <form:input path="user.lastName" id="email" />
40.    </td>
41.</tr>
42.<tr>
43.    <td>Email:</td>
44.    <td align="left">
45.        <form:input path="user.email" id="email" />
46.    </td>
47.</tr>
48.<tr>
49.    <td>Lab:</td>
50.    <td align="left">
51.        <form:select id="lab" path="user.lab" onclick="updateDivisions();">
52.            <form:option value="0" label="--Please Select--" />
53.            <form:options items="${addUserForm.labs}" itemValue="ID" 
                        itemLabel="name" />
54.        </form:select>
55.    </td>
56.</tr>
57.<tr>
58.    <td>Division:</td>
59.    <td align="left">
60.        <form:select id="division" path="user.division" disabled="true">
61.        </form:select>
62.    </td>
63.</tr>
64.<tr>
65.    <td>&nbsp;</td>
66.</tr>
67.<tr>
68.    <td colspan="2" align="center">
69.        <input type="submit" name="_eventId_submit" value="Submit">
70.    </td>
71.</tr>
72.</table>
73.</form:form>
74.</body>
75.</html>

The form action class AddUserFormAction is configured in Listing 13. It uses form class (line 3), and userManager and labManager classes (lines 6,7) from the BLL. Both manager classes are configured in Spring configuration file applicationContext.xml, which will be discussed later.

Listing 13. /WEB-INF/flows/addUser-beans.xml
1.  <beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
2.      <bean id="addUserFormAction" 
                class="edu.ucar.cisl.ncarUsers.presentation.brh.AddUserFormAction">
3.          <property name="formObjectName" value="addUserForm"/>
4.          <property name="formObjectClass" 
                    value="edu.ucar.cisl.ncarUsers.presentation.brh.AddUserForm"/>
5.          <property name="formObjectScope" value="FLOW"/>
6.          <property name="userManager" ref="userManager"/>  
7.          <property name="labManager" ref="labManager"/>          
8.      </bean>    
9.  </beans>

Listing 14 defines the states and actions that transit the states in the flow.

Listing 14. /WEB-INF/flows/addUser-flow.xml
1.	<?xml version="1.0" encoding="UTF-8"?>
2.	<flow xmlns=http://www.springframework.org/schema/webflow
         xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance
              xsi:schemaLocation="http://www.springframework.org/schema/webflow
         http://www.springframework.org/schema/webflow/spring-webflow-1.0.xsd">

3.	    <start-state idref="addUser"/>

4.	    <view-state id="addUser" view="addUser">
5.	        <render-actions>
6.	            <action bean="addUserFormAction" method="initForm"/>
7.	        </render-actions>
8.	        <transition on="submit" to="submit">
9.	            <action bean="addUserFormAction" method="bind"/>
    </transition>
10.	    </view-state>

11.	    <view-state id="summary" view="summary">
12.	        <transition on="addNewUser" to="addNewUser" />
13.	    </view-state>

14.	    <action-state id="submit">
15.	        <action bean="addUserFormAction" method="submit"/>
    <transition on="success" to="summary"/>
16.	    </action-state>

17.	    <action-state id="addNewUser">
18.	        <action bean="addUserFormAction" method="addNewUser"/>
19.	        <transition on="success" to="addUser"/>
20.	    </action-state>    

21.	    <import resource="addUser-beans.xml"/>    

22.	</flow>

Resource Request Handler

Resource classes decide what will be exposed as RESTful Web services to the client applications. Jersey makes it easy to implement RESTful Web services in RRH. It uses annotations to map the resource class with a URI and to map standard HTTP methods in an HTTP request to the methods in the resource class. To use Jersey, a special servlet needs to be configured in the web.xml file (Listing 15). When the servlet is initialized, it crawls the classes in the edu.ucar.cisl.ncarUsers.presentation.rrh package to locate all the resource classes and map them to annotated URIs. All the requests for RESTful Web services start with /rrh and will be processed by this servlet.

Listing 15. Servlet definition to use Jersey in /Web-Inf/web.xml
1.  <servlet>
2.    <servlet-name>rrh</servlet-name>
3.    <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
4.    <init-param>
5.        <param-name>com.sun.jersey.config.property.packages</param-name>
6.        <param-value>edu.ucar.cisl.ncarUsers.presentation.rrh</param-value>
7.    </init-param>
8.  </servlet>
9.  <servlet-mapping>
10.    <servlet-name>rrh</servlet-name>
11.    <url-pattern>/rrh/*</url-pattern>
12.  </servlet-mapping>

Three resource classes have been implemented: UsersResource (Listing 16), UserResource (Listing17), and DivisionResource (Listing 18). In Listing 16, the annotation @Path before the class definition on line 3 maps the UsersResource class to URI /users. The annotations @GET and @Produces(application/json) before the method getUsersAsJsonArray on lines 21, 22 indicate this method will handle the HTTP GET request and its response content type is JSON. The annotations @PUT and @Consumes("text/plain") before the method putUsers on lines 41, 42 indicate the method will handle the HTTP PUT request and the content type in the input HTTP body is expected to be plain text. In this case, it is a flat file format with one user per line. The attributes for each user are separated by a vertical line(|).

Listing 16. edu.ucar.cisl.ncarUsers.presentation.rrh.UsersResource
1.  package edu.ucar.cisl.ncarUsers.presentation.rrh;

2.  ...//imports

3.  @Path("/users/")
4.  public class UsersResource {
5.     protected @Context UriInfo uriInfo;
6.     protected UserManager userManager;
7.     protected DivisionManager divisionManager;
8.     protected LabManager labManager;

9.     public UsersResource() {
10.        userManager = (UserManager)
11.          BeanFactory.getInstance().getBean("userManager");
12.        divisionManager = (DivisionManager)
13.          BeanFactory.getInstance().getBean("divisionManager");
14.        labManager = (LabManager)
15.          BeanFactory.getInstance().getBean("labManager");
16.  }

17.     @Path("{username}/")
18.     public UserResource getUser(@PathParam("username") String userName) {
19.        return new UserResource(uriInfo, userManager, userName);
20.     }

21.     @GET
22.     @Produces("application/json")
23.     public JSONArray getUsersAsJsonArray() throws JSONException {
24.        ArrayList<User> users = this.userManager.getAllUsers();
25.        JSONArray usersArray = new JSONArray();
26.        for (User user : users) {
27.          JSONObject obj = new JSONObject();
28.          obj.put("USERNAME", user.getUserName());
29.          obj.put("PASSWORD", user.getPassword());
30.          obj.put("FIRST_NAME", user.getFirstName());
31.          obj.put("LAST_NAME", user.getLastName());
32.          obj.put("EMAIL", user.getEmail());
33.          String labName=labManager.getLabName(user.getLab());
34.          obj.put("LAB", labName);
35.          String divisionName= divisionManager.getDivisionName(user.getDivision());
36.          obj.put("DIVISION", divisionName);
37.          usersArray.put(obj);
38.        }
39.        return usersArray;
40.     }

41.     @PUT
42.     @Consumes("text/plain")
43.     public Response putUsers(String input) throws IOException {
44.        Reader reader=new StringReader(input);
45.        BufferedReader br=new BufferedReader(reader);
46.        while (true) {
47.          String line=br.readLine();
48.          if (line == null)
49.             break;
50.          processUser(line);      
51.        }    
52.        return Response.created(uriInfo.getAbsolutePath()).build();
53.  }

54.  /********************************
If the user exists, update it. Otherwise, create a new one
@param input
   */
55.     protected void processUser(String input)
56.     {
57.        StringTokenizer token=new StringTokenizer(input, "|");
58.        String userName=token.nextToken();
59.        String password=token.nextToken();
60.        String firstName=token.nextToken();
61.        String lastName=token.nextToken();
62.        String email=token.nextToken();
63.        String labName=token.nextToken();
64.        String divisionName=token.nextToken();

65.        int lab=this.labManager.getLabID(labName);
66.        int division=this.divisionManager.getDivisionID(divisionName);
67.        User user=this.userManager.getUser(userName);
68.        if (user == null)
69.          user=new User();

70.        user.setUserName(userName);
71.        user.setPassword(password);
72.        user.setFirstName(firstName);
73.        user.setLastName(lastName);
74.        user.setEmail(email);
75.        user.setLab(lab);
76.        user.setDivision(division);

77.        this.userManager.addUser(user);    
78.     }
79.  }

In Listing 16, the annotation @Path("{username}/") before the getUser method on line 17 indicates the string after /users/ and before the next forward slash, if it exists in the request URI, will be used as the value for the variable username, and an instance of child resource class UserResource will be returned by the getUser method. Jersey then invokes the methods in the UserResource class, annotated for respective HTTP methods. In Listing17, the getUser method, annotated with @GET and Produces("application/json"), will be invoked by Jersey for the GET method in the HTTP request to return a user data in a JSON format (lines 12-19).

Listing 17. edu.ucar.cisl.ncarUsers.presentation.rrh.UserResource
1. package edu.ucar.cisl.ncarUsers.presentation.rrh;

2. ...//imports

3. public class UserResource {
4.     protected String userName;
5.     protected UriInfo uriInfo;
6.     protected UserManager userManager;

7.     public UserResource(UriInfo uriInfo, UserManager userManager, String userName) {
8.         this.uriInfo = uriInfo;
9.         this.userName = userName;
10.         this.userManager = userManager;
11. }

12.     @GET
13.     @Produces("application/json")
14.     public JSONObject getUser() throws JSONException {
15.         JSONObject obj = new JSONObject();
16.         User user = this.userManager.getUser(userName);
17.         if (user != null) 
18.             obj.put("userName", user.getUserName()).put("email", user.getEmail());
           return obj;
19.     }
20. }

The Resource class DivisionsResource in Listing 18 is implemented in a very similar way. The class is annotated with the URI path /divisions/ on line 3. The getDivisions method, annotated with @GET and @ProduceName, returns a JSON array of divisions (lines 10-23).

Listing 18. edu.ucar.cisl.ncarUsers.presentation.rrh.DivisionsResource
1.   package edu.ucar.cisl.ncarUsers.presentation.rrh;

2.   ...//imports

3.   @Path("/divisions/")

4.   public class DivisionsResource {
5.       protected DivisionManager divisionManager;

6.       public DivisionsResource() {
7.           divisionManager = (DivisionManager)
8.               BeanFactory.getInstance().getBean("divisionManager");
9.       }

10.       @GET
11.       @Produces("application/json")
12.       public JSONArray getDivisions(@QueryParam("labID") String labID) 
13.           throws JSONException {
14.           int id = Integer.parseInt(labID);
15.           ArrayList<Division> divisions = this.divisionManager.getDivisions(id);
16.           JSONArray divisionsArray = new JSONArray();
17.           for (Division division : divisions) {
18.               JSONObject obj = new JSONObject();
19.               obj.put("ID", division.getID()).put("name", division.getName());
20.               divisionsArray.put(obj);
21.           }
22.           return divisionsArray;
23.       }
24.   }

Client Applications

Ajax

Ajax acts as a client to RESTFul Web services. They work together to help create desktop-like rich and responsive browser interfaces. In the sample application, I have used Ajax in two places: It checks to see if the user name already exists in the database on line 18 in Listing 12 and it requests a list of divisions for a given lab asynchronously and updates the division option menu without refreshing the page on line 51.

The JavaScripts have been listed in Listing 19. The validateUsername function from lines 2 to 13 sets up an XMLHttpRequest and sends it to the RESTful Web service to get the user data for a given username in the browser. The usernameCallback function from lines 14 to 27 is a callback function that processes the response from the RESTful Web service server. If the response contains the user data, it indicates a user with the given user name already exists. A warning message will display and the username field in the browser will be cleared.

The updateDivisions function from lines 28 to 39 sends a request to the RESTful Web service to get the divisions that the lab NCAR administrator selects in the lab option menu. The callback function updateDivisionsCallback from lines 40 to 55 processes the response and displays the returned division names in the Division option menu.

Listing 19. js/addUserAjax.js
1.    var req;
2.    function validateUsername() {
3.        var username = document.getElementById("userName");
4.        var url = "rrh/users/" + escape(username.value);
5.        if (window.XMLHttpRequest) {
6.            req = new XMLHttpRequest();
7.        } else if (window.ActiveXObject) {
8.            req = new ActiveXObject("Microsoft.XMLHTTP");
9.        }

10.        req.open("Get", url, true);
11.        req.onreadystatechange = usernameCallback;
12.        req.send(null);
13.    }

14.    function usernameCallback() {
15.        if (req.readyState == 4 && if (req.status == 200) {
16.            var jsonData = req.responseText;
17.            var myJSONObject = eval("(" + jsonData + ")");
18.            var un = myJSONObject.userName;
19.            var username = document.getElementById("userName");
20.            if (username.value == un) {
21.                alert("Warning: " + username.value + 
22.                   " exists already. Choose another username.");
23.                username.value = "";
24.                username.focus();
25.            }
26.        }
27.    }

28.    function updateDivisions() {
29.        var labSel = document.getElementById("lab");
30.        var url = "rrh/divisions/?labID=" + escape(labSel.value);
31.        if (window.XMLHttpRequest) {
32.            req = new XMLHttpRequest();
33.        } else if (window.ActiveXObject) {
34.            req = new ActiveXObject("Microsoft.XMLHTTP");
35.        }
36.        req.open("Get", url, true);
37.        req.onreadystatechange = updateDivisionsCallback;
38.        req.send(null);
39.    }

40.    function updateDivisionsCallback() {
41.        if (req.readyState == 4)&& req.status == 200) {
42.            var jsonData = req.responseText;
43.            var divisionsData = eval("(" + jsonData + ")");
44.            var divisionSel = document.getElementById("division");
45.            var length = divisionSel.length;
46.            for (var b = 0; b < length; b++) {
47.                divisionSel.options[b] = null;
48.            }
49.            for (var a = 0; a < divisionsData.length; a++) {
50.                divisionSel.options[a] = new 
51.                   Option(divisionsData[a].name, divisionsData[a].ID);
52.            }
53.            divisionSel.disabled = "";
54.        }
55.    }

Ruby scripts

The clients for RESTful Web services can be easily implemented in languages such as Perl, Ruby, Python, C, C#, or Java code. In this article, I used Ruby as an example. Listing 20 shows the Ruby script to download the users' data from the RESTful Web service and save each user data with attributes separated by a vertical line (|). The Ruby scripts in Listing 21 upload the users' data to the server from the file.

Listing 20. client/downloadUsersData.rb
54. #!/usr/bin/ruby

55. require 'rubygems'
56. require 'json'
57. require 'open-uri'
58. $KCODE = 'UTF8'

59. def download(filename)
60. file=File.new(filename, 'w')
61. base_uri = 'http://localhost:8080/ncarUsers/rrh/users/'

62. # Make the HTTP request and read the response entity-body as a JSON
63. # document.
64. json = open(base_uri).read

65. # Parse the JSON document into a Ruby data structure.
66. json = JSON.parse(json)

67. # Iterate over the data structure...
68. json.each { |r| file.puts r['USERNAME'] + '|' + r['PASSWORD'] + '|' +
                    r['FIRST_NAME'] +  '|' +  r['LAST_NAME'] + 
69. '|' + r['EMAIL'] + '|' +  r['LAB'] + '|' + r['DIVISION']; }
70. end

71. # Main program.
72. unless ARGV[0]
73. puts "Usage: #{$0} [file name]"
74. exit
75. end
76. download(ARGV[0])
Listing 21. client/uploadUsersData.rb
1. #!/usr/bin/ruby
2. require 'rubygems'
3. require 'rest-open-uri'
4. require 'uri'
5. require 'cgi'


6. def uploadUsers(content)
7. base_uri = 'http://localhost:8080/ncarUsers/rrh/users'
8. begin
9. response = open(base_uri, :method => :put, 'Content-Type' => 
                      "text/plain", :body => content)
10. rescue OpenURI::HTTPError => e
11. response_code = e.io.status[0].to_i
12. puts response_code 
13. if response_code !=  "200" 
a. puts "Sorry, Can't post the users"
14. else
a. raise e
15. end
16. end

17. end

18. def upload(filename)
19. File.open(filename) do |file|
20. content = file.read
21. uploadUsers(content)
22. end
23. end


24. # Main program.
25. unless ARGV[0]
26. puts "Usage: #{$0} [file name]"
27. exit
28. end
29. upload(ARGV[0])

Put together

Spring framework is used to tie together the components in the Data Access Layer, the Business Logic Layer, and the Presentation Layer. It uses Inversion of Control (IoC) to externalize the creation and management of component dependencies. Listing 22 shows the Spring configuration file that defines the components and their dependencies. It also configures data store and transaction manager.

Listing 22. applicationContext.xml
1.    <beans xmlns=http://www.springframework.org/schema/beans
2.        xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance
3.        xmlns:tx=http://www.springframework.org/schema/tx 
4.        xsi:schemaLocation="
5.           http://www.springframework.org/schema/beans
6.    http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
7.    http://www.springframework.org/schema/tx
8.    http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">

9.        <tx:annotation-driven/>
10.        <bean id="dataSource"
11.            class="org.springframework.jdbc.datasource.DriverManagerDataSource">
12.            <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
13.            <property name="url" value="jdbc:mysql://localhost:3306/ncar_users"/>
14.            <property name="username" value="tutorial"/>
15.            <property name="password" value="tutorial"/>
16.        </bean>

17.        <bean id="transactionManager" 
18.              class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
19.            <property name="dataSource" ref="dataSource"/>
20.        </bean>    
21.        <bean id="userManager"
22.            class="edu.ucar.cisl.ncarUsers.bll.UserManagerImpl">
23.            <property name="userDao"><ref local="userDao"/></property>
24.        </bean>
25.        <bean id="labManager"
26.            class="edu.ucar.cisl.ncarUsers.bll.LabManagerImpl">
27.            <property name="labDao"><ref local="labDao"/></property>
28.        </bean>
29.        <bean id="divisionManager"
30.            class="edu.ucar.cisl.ncarUsers.bll.DivisionManagerImpl">
31.            <property name="divisionDao"><ref local="divisionDao"/></property>
32.        </bean>    
33.        <bean id="userDao" class="edu.ucar.cisl.ncarUsers.dal.UserDAOJDBCImpl">
34.            <property name="dataSource"><ref local="dataSource"/></property>
35.        </bean>     
36.        <bean id="labDao" class="edu.ucar.cisl.ncarUsers.dal.LabDAOJDBCImpl">
37.            <property name="dataSource"><ref local="dataSource"/></property>
38.        </bean>     
39.        <bean id="divisionDao"
40.            class="edu.ucar.cisl.ncarUsers.dal.DivisionDAOJDBCImpl">
41.            <property name="dataSource"><ref local="dataSource"/></property>
42.        </bean>       
43.    </beans>

A context loader listener is configured in the web.xml file to load the applicationContext.xml file when the Web application is started (Listing 23).

Listing 23. Context loader listener configuration in web.xml to load applicationContext.xml
1. <context-param>
2.     <param-name>contextConfigLocation</param-name>
3.     <param-value>/WEB-INF/classes/applicationContext.xml</param-value>
4. </context-param>

5. <listener>
6.     <listener-class>
              org.springframework.web.context.ContextLoaderListener</listener-class>
7. </listener>

Running the application from Eclipse

To run the sample application from Eclipse:

  1. Right-click on the ncarUsers project in the Project Explorer and select Run As > Run On Server or Debug On Server if you want to run it in debug mode.
  2. Select localhost > Tomcat v6.0 Server at localhost.
    Figure 12. Run Tomcat inside Eclipse
    Screen shot shows the Run On Server window with the option 'Choose an existing server' selected with the Tomcat v6.0 Server at localhost.
  3. Click Finish. This opens the Servers tab and shows that the sample application is deployed to Tomcat and that the server is running. You can switch to the Console tab to see the messages generated by the Tomcat server.
  4. Open a browser and navigate to: http://localhost:8080/ncarUsers. Click the link Sign Up New Users App.
    Figure 13. NCAR New User Registration browser interface
    Screen shot of the NCAR New User Registration in action. Blank text fields are provided for Username, Password, First Name, Last Name and email. A dropdown box is provided for Lab and a greyed-out dropdown box is provided for Division. A submit button rests at the bottom.

    You will notice the Lab option menu displays --Please Select--, and the Division option menu is disabled. Enter something in each of the text fields and select a lab. The Division option menu now contains the divisions for the selected lab. Next, select a division.

  5. Click Submit. A new user is created and a Signup Confirmation page is displayed.
    Figure 14. NCAR New Signup Confirmation page
    A screen shot shows the Signup Confirmation screen with a success message and an 'Add New User' button.
  6. Click Add New User.
  7. On the NCAR New User Registration page, type the same username in the username field, then move on to next field. A window will pop up warning that the username has been used and the username field will be cleared.
  8. After you create a few users, open a command prompt. Run the downloadUsers.rb Ruby script to download the users' data (Figure 15) . You can use the downloaded file as a template to add a few new users. Then use uploadUsers.rb to upload the new users to the application server.
    Figure 15. Run downloadUsers.rb script to download the users' data
    Console screen shot shows the downloadUsers.rb script dumping data to userData.txt.

What is happening behind the scenes?

When you open the Sign Up New User application, the Browser Request Handler in the Presentation Layer handles the request from a Web interface. After you enter data in all the fields and submit, the AddUserFormAction object in the Browser Request Handler accepts the Submit request. As illustrated in Figure 16, this object passes the necessary data to the BLL, which then requests the Data Access Layer to save the data to MySQL database. A confirmation page is then presented to the browser.

Figure 16. Add new user sequence diagram
A diagram shows the data flow from the client submission through the Resource Request Handler, the Business Logic Layer, the Data Access Layer and the Data Store, then back to the Resource Request Handler where the success message is displayed.

After you type a username, the Ajax script invokes a RESTful Web service. This time, the UsersResource and UserResource objects in the Resource Request Handler layer handle the request. A JSON data structure is returned to the browser if a user with given username already exists in the database. The Ajax script then displays a warning message and clears the username field.

When you select a lab option menu field, the Ajax script invokes a GET Web service in DivisionsResource in the Resource Request Handler, which returns an array of divisions for the selected lab. Figure 17 shows the sequence of requests between the tiers after the Ajax script sends a RESTful Web service request to get the divisions for given lab and displays them in the Division option menu.

Figure 17. Get divisions sequence diagram
A diagram shows the flow to get the list of divisions. The getDivisioh(int lab) function passes from the Client through the Resource Request Handler, the Business Logic Layer and the Data Access Layers. It becomes a select to the Data Store. A Result Set returns from the Data Store, becoming an Array List in the Data Access Layer. That Array List is passed back through the Business Logic Layer to the Resource Request Handler where it is transformed to a JSonArray and passed back to the client to populate the dropbox.

The Ruby scripts that are used to upload and download users' data are also the clients of RESTful Web services. The Ruby script downloadUsers.rb sends an HTTP GET request to the server, which returns the users' data in JSON, whereas uploadUsers.rb sends an HTTP PUT request with users' data contained in the HTTP body to the server. The data format is one user per line. Within each line, the attributes for each user are separated by a vertical line (|).

Like the Browser Request Handler, the Resource Request Handler provides an interface to, albeit, different clients, and requests that the Business Logic Layer handle the processing. The Business Logic Layer, in turn, requests that the Data Access Layer deal with data persistence.


Conclusion

Increasingly, modern Web applications are required to provide a rich interface as well RESTful Web services so that clients can automate the process. This article explained how to use the multi-tier architecture discussed in the article "A multi-tier architecture for building RESTful Web services" (see Resources) to build both dynamic Web applications and RESTful Web services. It also described how Ajax and RESTful Web services work together to create desktop-like rich and responsive interfaces. It uses Eclipse, Jersey, Spring MVC framework, Spring Web Flow, Spring JBDC framework, and MySQL. Ruby scripts are used as clients for RESTful Web services.

This paper 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. Additionally, I would like to thank Markus Stobbs from NCAR, who provided suggestions for selecting the sample application and editing for the article.


Download

DescriptionNameSize
Source codencarUsers.zip5987KB

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, SOA and web services
ArticleID=405297
ArticleTitle=Build RESTful Web services and dynamic Web applications with the multi-tier architecture
publish-date=06302009