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.
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
- 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.
Table 1 lists the components and how they are organized in the folder structure.
Table 1. The folder structure
| Tier | Layers and scripts | File Location |
|---|---|---|
| Client Tier | Ajax Scripts | WebRoot/js |
| JSP Pages | WebRoot/WEB-INF/jsp | |
| Ruby Scripts | Client/ruby | |
| Presentation Tier | Presentation Layer - Browser Request Handler | src/edu/ucar/cisl/ncarUsers/presentation/brh |
| Presentation Layer - Resource request handler | src/edu/ucar/cisl/ncarUsers /presentation/rrh | |
| Business Logic Layer | src/edu/ucar/cisl/ncarUsers/bll | |
| Data Access Layer | src/edu/ucar/cisl/ncarUsers/dal | |
| Data store Tier | MySql Scripts | db/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.
Download the following software packages and install them according to the installation guides on their respective Web sites. (See Resources for links.)
- Apache Tomcat 6.x
- MySQL 5.1
- Eclipse IDE for Java EE Developers (I was using the Eclipse Europa release, but the newer version would work also.)
- 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
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
Configure Tomcat Server in Eclipse
To configure the Tomcat Server:
- Open Eclipse and select File > New > Other.
- Select Server from the list.
Figure 3. Configure Tomcat server in Eclipse--Step 1
- Click Next. In the window, select Apache > Tomcat v6.0
Server.
Figure 4. Configure Tomcat server in Eclipse--Step 2
- Click Next. In the next window, click Browse… and select the
location of Tomcat installation.
Figure 5. Configure Tomcat server in Eclipse--Step 3
- Click Finish.
Create Web project ncarUsers in Eclipse
To create the Web project:
- Select File > New > Project…. Open Web.
- Click on Dynamic Web Project.
Figure 6. Create Web project ncarUsers in Eclipse--Step 1
- Click Next. In the new window, enter
ncarUsersin the project name field.
Figure 7. Create Web project ncarUsers in Eclipse--Step 2
- Click Next.
- Click Next again in the Project Facets window.
- In the Web Module window, change WebContent to WebRoot in the Content Directory field.
- Click Finish.
Figure 8. Create Web project ncarUsers in Eclipse--Step 3
Import the files from the article download
To import the files:
- In Project Explorer, right click ncarUsers and choose Import > Import ….
- In the Import window, click General > File System (Figure 9).
Figure 9. Import the article download to ncarUsers project--Step 1
- Click Next.
- In the File System window, click Browse ..., and choose C:\ncarUsers.
- Select the checkbox by ucarUsers (Figure 10).
Figure 10. Import the article download to ncarUsers project--Step 2
After the imports the Project Explorer should look like the Figure 11.
Figure 11. Result of project import
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.
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. }
|
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. }
|
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> </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> </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 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. }
|
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. }
|
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])
|
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:
- 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.
- Select localhost > Tomcat v6.0 Server at localhost.
Figure 12. Run Tomcat inside Eclipse
- 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.
- 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
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.
- Click Submit. A new user is created and a Signup
Confirmation page is displayed.
Figure 14. NCAR New Signup Confirmation page
- Click Add New User.
- 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.
- After you create a few users, open a command prompt. Run
the
downloadUsers.rbRuby script to download the users' data (Figure 15) . You can use the downloaded file as a template to add a few new users. Then useuploadUsers.rbto upload the new users to the application server.
Figure 15. Run downloadUsers.rb script to download the users' data
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
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
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.
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.
| Description | Name | Size | Download method |
|---|---|---|---|
| Source code | ncarUsers.zip | 5987KB | HTTP |
Information about download methods
Learn
- Rod Johnson's article "Introduction
to the Spring Framework" describes what Spring hopes to achieve
and how it can help you develop Java EE applications.
- See the related article, "A
multi-tier architecture for building RESTful Web
services" (developerWorks, June 2009) to get an overview of the
concept of REST and RESTful Web services, then discuss a multi-tier
architecture to build both RESTful Web services and dynamic Web applications.
- The tutorial "Spring jdbc and
DAO" explains how to implement the design pattern Data Access Objects to access a Database with JDBC. In addition the tutorial shows how to use Inversion of control to improve your code quality.
- The "The Complete Spring
Tutorial shows how you can integrate struts, spring, and hibernate in your
Web application.
- "Spring Web
Flow" provides an introduction to building a Web application using Spring Web Flow and the Spring Framework.
- The Introduction to
RESTful Web Services and Jersey describes the REST architecture,
RESTful Web services, and Sun's reference implementation for JAX-RS, which is referred to as Jersey.
- "Implementing RESTful Web Services in Java" shows you how to write
RESTful Web services in Java that conform to the Jersey specification.
Get products and technologies
- Download
Apache Tomcat 6.x.
- Download
MySQL 5.1.
- Download
Eclipse IDE for Java EE Developers.
- Download Ruby.
Comments (Undergoing maintenance)






