En el artículo anterior, analizamos la arquitectura multinivel para la construcción de servicios web RESTful y aplicaciones Web dinámicas. Propuse emplear un Resource Request Handler – RRH (controlador de solicitudes de recursos) en la capa Presentation (presentación) del kit de herramientas Web Ajax/Google (GWT) y para las llamadas de aplicaciones clientes externas, y un Browser Request Handler – BRH (controlador de solicitudes del navegador) para procesar la solicitud del navegador y generar datos de salida para mostrar en el navegador. Ambos controladores comparten una misma capa Business Logic (lógica de negocios), que, a su vez, interactúa con la capa Data Access (acceso a datos). En la aplicación de muestra, el nivel de la aplicación se construye usando código Java™. Este artículo usa framework Jersey para los servicios web RESTful; frameworks Spring para MVC, navegación y JDBC; y MySQL como base de datos. Se usa Eclipse como IDE. La aplicación de muestra se implementará en Tomcat. Se trata de una aplicación ficticia sencilla que permite a los administradores del Centro Nacional de Investigación Atmosférica (NCAR) registrar empleados del NCAR.
En este escenario, el administrador usa la interfaz de navegador para registrar nuevos empleados del NCAR. El NCAR cuenta con cuatro laboratorios que, a su vez, poseen diferentes divisiones:
- El Computational and Information Systems Laboratory
- El Earth and Sun Systems Laboratory
- El Earth Observing Laboratory
- El Research Applications Laboratory
La interfaz de registro de la aplicación posee los siguientes campos:
- username (nombre de usuario)
- password (contraseña)
- last name (apellido)
- first name (nombre)
- lab - division (laboratorio – división)
Los campos Lab y Division son menús de opciones seleccionables, y el Username debe ser único. Si el administrador intenta ingresar un nombre de usuario que ya ha sido utilizado, el navegador mostrará una advertencia y el campo Username quedará en blanco.
La lista de elementos del menú de opciones Division cambiará de acuerdo con el laboratorio seleccionado en el menú de opciones Lab. En principio, al abrir la interfaz, el campo Division se encontrará deshabilitado. Una vez que el administrador haya seleccionado el laboratorio, el menú de opciones Division se habilitará y sólo contendrá las divisiones correspondientes al laboratorio seleccionado. Luego de que el administrador complete la información requerida y haga clic en Submit (enviar), el sistema agregará el nuevo usuario a la base de datos MySQL y mostrará un mensaje que indique que la operación se ha realizado con éxito.
El administrador del sistema requiere ejecutar un proceso por lotes tanto para cargar nuevos usuarios, como para descargar usuarios que ya estén registrados. El programa por lotes puede implementarse usando Ruby, Python, Perl o código Java. En este artículo usé Ruby como ejemplo. No se requiere autenticación ni en la interfaz del navegador ni en los servicios web RESTful.
Nota: La aplicación ficticia no está lista para su producción. Para que esta aplicación pudiera utilizarse en la vida real, se requeriría de gestión de excepciones, inicio de sesión, autenticación, validación de datos y demás.
La Tabla 1 muestra una lista de los componentes y su organización en la estructura de carpetas.
Tabla 1. Estructura de carpetas
| Nivel | Capas y scripts | Ubicación del archivo |
|---|---|---|
| Nivel cliente | Scripts Ajax | WebRoot/js |
| Páginas JSP | WebRoot/WEB-INF/jsp | |
| Scripts Ruby | Client/ruby | |
| Capa Presentation | Capa Presentation - Browser Request Handler | src/edu/ucar/cisl/ncarUsers/presentation/brh |
| Capa Presentation - Resource Request Handler | src/edu/ucar/cisl/ncarUsers/presentation/rrh | |
| Capa Business Logic | src/edu/ucar/cisl/ncarUsers/bll | |
| Capa Data Access | src/edu/ucar/cisl/ncarUsers/dal | |
| Capa Data Store | MySql Scripts | db/setup.sql |
Descargue el código fuente y descomprímalo en su unidad C. Se generará una nueva carpeta: C:\ncarUsers. Dentro de esta carpeta, encontrará la estructura de carpetas completa mostrada en la Tabla 1. El archivo descargable contiene todo el código fuente y todas las bibliotecas requeridas por MySQL Connector/J Driver 5.1, Jersey 1.0, Spring Framework 2.5.5 y Spring Web Flow 2.0.2. El archivo proporciona todo lo necesario para realizar la demostración, a menos que desee probar las nuevas versiones.
Descargue los siguientes paquetes de software e instálelos siguiendo las guías de instalación de sus respectivos sitios Web. (Ver la sección Recursos para obtener los vínculos a los sitios Web)
- Apache Tomcat 6.x
- MySQL 5.1
- Eclipse IDE for Java EE Developers (usé la versión Eclipse Europa, pero también se puede utilizar la última versión)
- Ruby
Luego de la instalación de Ruby, ejecute el comando gem install
–remote para descargar o instalar el json (Figura 1) y las bibliotecas
rest-open-uri.
Figura 1. Biblioteca Ruby gen install json
En este artículo, usé la base de datos MySQL. Para crear una instancia de servidor
MySQL, use el MySQL Server Instance Configuration Wizard (asistente de configuración
de instancias de servidor MySQL). El nombre predeterminado de la instancia será
MYSQL. Para iniciar la herramienta de línea de comandos MySQL, ejecute mysql –u root –p MYSQL. (Deberá proporcionar la contraseña
establecida en el paso anterior para el inicio de sesión root). Luego, ejecute la fuente c:/ncarUsers/db/setup.sql en la herramienta de
línea de comandos para crear la base de datos (ncar_users), un usuario MySQL
(tutorial) con la contraseña (tutorial) y tablas para este artículo (Figura 2). El
script también inserta los datos en las tablas Lab y Division.
Figura 2. Ejecución del archivo script setup.sql
Configuración del servidor Tomcat en Eclipse
Para configurar el servidor Tomcat:
- Abra Eclipse y seleccione File > New > Other.
- Seleccione Server de la lista.
Figura 3. Configuración del servidor Tomcat en Eclipse - Paso 1
- Haga clic en Next. En la ventana, seleccione Apache > Tomcat
v6.0 Server.
Figura 4. Configuración del servidor Tomcat en Eclipse - Paso 2
- Haga clic en Next . En la ventana, haga clic en Browse… y seleccione
la ubicación de la instalación de Tomcat.
Figura 5. Configuración del servidor Tomcat en Eclipse - Paso 3
- Haga clic en Finish.
Creación del proyecto Web ncarUsers en Eclipse
Para crear el proyecto Web:
- Seleccione File > New > Project… . Open (abrir) Web.
- Haga clic en Dynamic Web Project (proyecto Web dinámico).
Figura 6. Creación del proyecto Web ncarUsers en Eclipse - Paso 1
- Haga clic en Next. En la nueva ventana, ingrese
ncarUsersen el campo Project Name (nombre del proyecto).
Figura 7. Creación del proyecto Web ncarUsers en Eclipse - Paso 2
- Haga clic en Next.
- Haga clic en Next nuevamente en la ventana Project Facets (facetas del proyecto).
- En la ventana Web Module (modulo Web), cambie WebContent por WebRoot en el campo Content Directory (directorio de contenidos).
- Haga clic en Finish.
Figura 8. Creación del proyecto Web ncarUsers en Eclipse - Paso 3
Importe los archivos desde la descarga de este artículo
Para importar los archivos:
- En el Project Explorer, haga clic con el botón derecho en ncarUsers y seleccione Import > Import ….
- En la ventana Import, haga clic en General > File System (Figura 9).
Figura 9. Importación de la descarga del artículo hacia el proyecto ncarUsers - Paso 1
- Haga clic en Next.
- En la ventana File System, haga clic en Browse ..., y seleccione C:\ncarUsers.
- Marque la casilla de verificación correspondiente a ncarUsers (Figure 10).
Figura 10. Importación de la descarga del artículo hacia el proyecto ncarUsers - Paso 2
Luego de haber realizado las importaciones, el Project Explorer deberá verse como el mostrado en la Figura 11.
Figura 11. Resultado de la importación del proyecto
Si lo desea, puede saltear las siguientes secciones acerca de la implementación de objetos de dominio, la capa Data Access (DAL), la capa Business Logic (BLL), la capa Presentation (que incluye el Browser Request Handler y el Resource Request Handler) y las aplicaciones clientes. En ese caso, pase directamente a la sección Ejecución de la aplicación desde Eclipse.
Implementación de objetos de dominio
Los objetos de dominio modelan el dominio de problemas de aplicaciones. Implementé tres objetos de dominio: User (Listado 1), Lab (Listado 2) y Division (Listado 3).
Listado 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. }
|
Listado 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. }
|
Listado 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. }
|
Implementación de la capa Data Access
En la capa Data Access (DAL), creé tres objetos de acceso a datos: UserDAO, LabDAO, and DivisionDAO. Los objetos de acceso a datos pueden o no coincidir con los
objetos de dominio. El Listado 4 muestra la interfaz UserDAO y el Listado 5 muestra
su implementación, en la cual se usa el framework Spring JDBC para realizar
funciones de insertar/actualizar (línea 21) y consultar (línea 30). Se implementó
una clase interna para que la función de consulta (líneas 31 a 44) mapee la
devolución del objeto ResultSet al objeto User. LabDAO y DivisionDAO se
implementaron de la misma manera.
Listado 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. } |
Listado 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. } |
Implementación de la capa Business Logic
En la capa Business Logic (BLL) se centralizan las reglas de negocios. Esta capa también gestiona las solicitudes de la capa Presentation e interactúa con la DAL para recuperar los datos del back-end y solicitar a la DAL que gestione la persistencia de los datos. Implementé tres clases de administradores, uno para cada objeto de dominio. Los Listados 6 y 7 muestran la interfaz UserManager y su implementación. Las implementaciones de LabManager y DivisionManager son muy similares a la de UserManager.
Listado 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. }
|
Listado 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. }
|
Implementación de la capa Presentation
Para que los administradores del NCAR puedan agregar usuarios en la Web se requiere de una interfaz de navegador. Usé Spring MVC y el framework Spring Web Flow para implementar el Browser Request Handler. Si lo desea, puede consultar numerosos artículos y tutoriales publicados en Spring Web Flow; muchos de ellos se citan en la sección de Recursos.
El Listado 8 configura el servlet Spring MVC. Este servlet atenderá todas las solicitudes del navegador, a excepción de las de Ajax. De acuerdo con las buenas prácticas, las URI de todas estas solicitudes siempre comienzan con /brh, mientras que las URL de todas las solicitudes de programas clientes de servicio web RESTful, inclusive de clientes Ajax, comienzan siempre con /rrh.
Listado 8. Definición del servlet para el uso de Spring MVC y Spring Web Flow en /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> |
El Listado 9 configura Spring Web Flow. Éste mapea las vistas de nombres a archivos JavaServer Pages (JSP) (líneas 6 a 9) y registra flujos definidos en archivos de configuración de flujo (líneas 11 a 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>
|
Implementé un formulario Class (clase) (Listado 10) y un formulario Action Class (clase de acción) (Listado 11) para facilitar el flujo de página Add User (agregar usuario). El formulario Class contiene los datos a mostrar en la interfaz del navegador y almacena los datos que completan los administradores del NCAR en el formulario, como se muestra en addUser.jsp (Listado 12). Para vincular los datos del formulario con los campos del formulario HTML, se usan bibliotecas de etiquetas Spring. El formulario Action Class contiene comportamientos correspondientes a las acciones de formulario.
Listado 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. }
|
Listado 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. }
|
Listado 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>
|
El Listado 13 configura el formulario Action Class AddUserFormAction. Éste usa el formulario Class (línea 3) y las
clases userManager y labManager (líneas 6 y 7) de la BLL. Ambas clases de administradores se
configuran en el archivo de configuración Spring applicationContext.xml, lo cual se
explicará más adelante.
Listado 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>
|
El Listado 14 define los estados y las acciones que transitan los estados en el flujo.
Listado 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>
|
Las clases de recursos deciden lo que se expondrá como servicios web RESTful en las aplicaciones de clientes. Jersey facilita la implementación de servicios web RESTful en el RRH usando anotaciones para mapear la clase de recursos con una URI y para mapear los métodos HTTP estándar de una solicitud HTTP hacia los métodos en la clase de recursos. Para poder usar Jersey, debe configurarse un servlet especial en el archivo web.xml (Listado 15). Cuando el servlet se inicializa, éste rastrea las clases del paquete edu.ucar.cisl.ncarUsers.presentation.rrh en busca de todas las clases de recursos y luego las mapea hacia las URI anotadas. Todas las solicitudes de servicios web RESTful, que comienzan con /rrh, serán procesadas por este servlet.
Listado 15. Definición del servlet para usar Jersey en /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> |
Se implementaron tres clases de recursos: UsersResource (Listado 16), UserResource (Listado
17) y DivisionResource (Listado18). En el Listado 16, la
anotación @Path ubicada antes de la definición de clase
en la línea 3 mapea la clase UsersResource hacia URI
/users. Las anotaciones @GET y @Produces(application/json) ubicadas antes del método getUsersAsJsonArray en las líneas 21 y 22 indican que este método
gestionará la solicitud HTTP GET y el tipo de contenido de
respuesta es JSON. Las anotaciones @PUT y @Consumes("text/plain") ubicadas antes del método putUsers en las líneas 41 y 42 indican que este método
gestionará la solicitud HTTP PUT y se espera que el tipo de
contenido del cuerpo HTTP de entrada sea texto plano. En este caso, se trata de un
formato de archivo plano que posee un usuario por línea. Los atributos de los
distintos usuarios están separados por una línea vertical (|).
Listado 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. }
|
En el Listado 16, la anotación @Path("{username}/"),
ubicada antes del método getUser en la línea 17, indica
que, si la cadena posterior a /users/ y previa a la siguiente barra existe en la
solicitud URI, ésta se usará como valor de la variable username, y el método getUser devolverá una
instancia de clase de recursos secundaria UserResource.
Luego, Jersey invocará los métodos de la clase UserResource anotados para los respectivos métodos HTTP. En el Listado
17, Jersey invocará el método getUser, con las
anotaciones @GET y Produces("application/json") para que el método GET de la solicitud HTTP
devuelva un dato de usuario en formato JSON (líneas 12 a 19).
Listado 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. }
|
La clase de recursos DivisionsResource del Listado 18 se
implementa de manera muy similar. En la clase se anota la ruta URI path /divisions/
en la línea 3. El método getDivisions, con las
anotaciones @GET y @ProduceName, devuelve un array JSON de divisiones (líneas 10 a 23).
Listado 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 actúa como cliente de los servicios web RESTFul. Ambos trabajan juntos para crear interfaces de navegador de alta calidad y capacidad de respuesta, similares a una interfaz de escritorio. En la aplicación de muestra, usé Ajax en dos oportunidades: para verificar si el nombre de usuario ya existe en la base de datos (línea 18 del Listado 12) y para solicitar la lista de divisiones de un laboratorio determinado de manera asincrónica y actualizar el menú de opciones Division sin que sea necesario actualizar la página (línea 51).
Los JavaScripts se detallan en el Listado 19. La función validateUsername de las líneas 2 a 13 establece una solicitud
XMLHttpRequest y la envía al servicio web RESTful para obtener los datos de usuario
de un determinado username en el navegador. La función usernameCallback de las líneas 14 a 27 es una función de devolución de
llamada que procesa la respuesta del servidor de servicio web RESTful. Si la
respuesta contiene los datos de usuario, se indica que ya existe un usuario con el
username que se ingresó. Se visualizará un mensaje de advertencia y el campo
username del navegador quedará en blanco.
La función updateDivisions de las líneas 28 a 39 envía una
solicitud al servicio web RESTful para obtener las divisiones que el administrador
de laboratorios NCAR seleccione en el menú de opciones Lab. La función callback updateDivisionsCallback de las líneas 40 a 55 procesa la
respuesta y muestra los nombres de división devueltos en el menú de opciones
Division.
Listado 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. }
|
Los clientes de servicios web RESTful pueden implementarse fácilmente en lenguajes como Perl, Ruby, Python, C, C# o código Java. En este artículo, usé Ruby como ejemplo. El Listado 20 muestra el script Ruby para cargar los datos de usuarios del servicio web RESTful y guardar los datos de los distintos usuarios separando sus atributos por una línea vertical (|). Los scripts Ruby del Listado 21 cargan los datos de usuarios en el servidor desde el archivo.
Listado 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])
|
Listado 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])
|
Se emplea framework Spring para vincular los componentes de la capa Data Access, la capa Business Logic y la capa Presentation. El framework Spring usa Inversión de Control (IoC) para externalizar la creación y la gestión de dependencias de componentes. El Listado 22 muestra el archivo de configuración Spring que define los componentes y sus dependencias. También se configuran el almacenamiento de datos y el administrador de transacciones.
Listado 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> |
Se configura un ContextLoaderListener en el archivo web.xml para cargar el archivo applicationContext.xml cuando se inicia la aplicación Web (Listing 23).
Listado 23. Configuración del ContextLoaderListener en web.xml para cargar 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>
|
Ejecución de la aplicación desde Eclipse
Para ejecutar la aplicación de muestra desde Eclipse:
- Haga clic con el botón derecho en el proyecto ncarUsers en el Project Explorer y seleccione Run As (ejecutar como) > Run On Server (ejecutar en el servidor) o Debug On Server (depurar en el servidor) si desea ejecutarlo en modo de depuración.
- Seleccione localhost > Tomcat v6.0 Server at localhost.
Figura 12. Ejecutar Tomcat dentro de Eclipse
- Haga clic en Finish. Se abrirá la pestaña Servers y mostrará que la aplicación de muestra se ha implementado en Tomcat y que el servidor se está ejecutando. Puede ver los mensajes generados por el servidor Tomcat seleccionando la pestaña Console (consola).
- Abra un navegador y navegue a: http://localhost:8080/ncarUsers. Haga clic en el
vínculo Sign Up New Users App (aplicación registrar nuevos usuarios).
Figura 13. Interfaz de navegador NCAR New User Registration (registro de nuevos usuarios)
En el menú de opciones Lab se visualizará —Please Select— (seleccionar) y el menú de opciones Division estará deshabilitado. Complete los campos de texto y seleccione un laboratorio. El menú de opciones Division ahora contendrá las divisiones correspondientes al laboratorio seleccionado. A continuación, seleccione una división.
- Haga clic en Submit. Se creará un nuevo usuario y se visualizará una
página de Signup Confirmation (confirmación de registro).
Figura 14. Página NCAR New Signup Confirmation (nueva confirmación de registro en NCAR)
- Haga clic en Add New User (agregar nuevo usuario).
- En la página NCAR New User Registration, ingrese el mismo nombre de usuario que se ingresó anteriormente en el campo username, luego pase al próximo campo. Aparecerá una ventana emergente advirtiendo que el nombre de usuario ya existe y el campo username quedará en blanco.
- Cree algunos usuarios y luego abra una solicitud de comando. Ejecute el script
Ruby
downloadUsers.rbpara descargar los datos de usuarios (Figura 15) . Puede usar el archivo descargado como plantilla para agregar otros usuarios. Luego useuploadUsers.rbpara cargar los nuevos usuarios en el servidor de aplicaciones.
Figura 15. Ejecute el script downloadUsers.rb para descargar los datos de usuarios
Al abrir la aplicación Sign Up New User, el Browser Request Handler de la capa
Presentation gestiona una solicitud de interfaz Web. Cuando usted ingresa datos en
todos los campos y presiona Submit, el objeto AddUserFormActiondel Browser Request Handler acepta la solicitud Submit.
Como muestra la Figura 16, este objeto transfiere los datos necesarios a la BLL, la
cual solicita a la capa Data Access guardar los datos en la base de datos MySQL.
Luego se presenta una página de confirmación al navegador.
Figura 16. Diagrama de secuencia Add New User
Cuando se ingresa un nombre de usuario, el script Ajax invoca un servicio web
RESTful. En este caso, los objetos UsersResource y UserResource de la capa Resource Request Handler gestionan
la solicitud. Si en la base de datos ya existe un usuario con el nombre de usuario
ingresado, se devuelve una estructura de datos JSON al navegador. El script Ajax
luego muestra un mensaje de advertencia y deja en blanco el campo username.
Cuando usted selecciona un campo del menú de opciones Lab, el script Ajax invoca un servicio web GET en DivisionsResource, en el Resource Request Handler, el cual devuelve un array de divisiones correspondientes al laboratorio seleccionado. La Figura 17 muestra la secuencia de solicitudes que se realizan entre los niveles luego de que el script Ajax envía una solicitud de servicio RESTful Web para obtener las divisiones de un determinado laboratorio y las muestra en el menú de opciones Division.
Figura 17. Diagrama de secuencia Get Divisions (obtener divisiones)
Los scripts Ruby usados para cargar y descargar datos de usuarios también son los
clientes de los servicios web RESTful. El script Ruby downloadUsers.rb envía una solicitud HTTP GET al
servidor, que devuelve los datos de usuarios en JSON, mientras que uploadUsers.rb envía una solicitud HTTP PUT que contiene los datos de usuarios en el cuerpo HTTP hacia el
servidor. El formato de datos es de un usuario por línea. Dentro de cada línea, los
atributos de los distintos usuarios están separados por una línea vertical (|).
Al igual que el Browser Request Handler, el Resource Request Handler proporciona una interfaz, aunque lo hace para clientes diferentes, y solicita a la capa Business Logic gestionar el procesamiento. La capa Business Logic, a su vez, solicita a la capa Data Access gestionar la persistencia de los datos.
Cada vez son más las exigencias con respecto a que las aplicaciones Web modernas proporcionen una interfaz de alta calidad y servicios web RESTful para que los clientes logren automatizar sus procesos. Este artículo explicó cómo usar la arquitectura multinivel analizada en el artículo "A multi-tier architecture for building RESTful Web services" (ver la sección Recursos) para construir tanto aplicaciones Web dinámicas como servicios web RESTful. También se describió la forma en que Ajax y los servicios web RESTful trabajan juntos para crear una interfaz Web de alta calidad y capacidad de respuesta, similar a una interfaz de escritorio. Se usó Eclipse, Jersey, framework Spring MVC, Spring Web Flow, framework Spring JBDC y MySQL. Los scripts Ruby se usaron como clientes de los servicios web RESTful.
Este artículo fue posible gracias al apoyo de la Fundación Nacional de la Ciencia en parte de la investigación, conforme al acuerdo de cooperación firmado con la University Corporation for Atmospheric Research. El Centro Nacional de Investigación Atmosférica es patrocinado por la Fundación Nacional de la Ciencia. Asimismo, quisiera agradecer a Markus Stobbs del NCAR, quien colaboró con la sugerencia de la aplicación de muestra y la edición del artículo.
| Descripción | Nombre | tamaño | Metodo de descarga |
|---|---|---|---|
| Source code | ncarUsers.zip | 5987KB | HTTP |
Información sobre métodos de descarga
Aprender
- El artículo de Rod Johnson "Introduction to the Spring Framework" describe los objetivos de Spring y la
forma en que puede ayudarlo a desarrollar sus aplicaciones Java EE.
- Consulte el artículo relacionado, "A multi-tier architecture for building
RESTful Web services" (developerWorks, Junio de 2009) para obtener una
visión general del concepto de REST y servicios web RESTful y luego analizar una
arquitectura multinivel para la construcción de servicios web RESTful y aplicaciones
Web dinámicas.
- El tutorial "Spring jdbc
and DAO" explica cómo implementar el patrón de diseño Data Access Objects
para acceder a una base de datos con JDBC. Además, este tutorial muestra cómo usar
inversión de control para mejorar la calidad de su código.
- "The Complete Spring
Tutorial Muestra cómo integrar Struts, Spring y Hibernate en su aplicación
Web.
- "Spring Web Flow"
brinda una introducción para la construcción de una aplicación Web usando Spring Web
Flow y el framework Spring.
- "Introduction to RESTful
Web Services and Jersey" describe la arquitectura REST, los servicios web
RESTful y la implementación de referencia de Sun para JAX-RS, denominada
Jersey.
- "Implementing RESTful Web Services in Java" muestra cómo escribir servicios
web RESTful en Java conformes a la especificación Jersey.
Obtener los productos y tecnologías
- Descargar Apache Tomcat
6.x.
- Descargar MySQL 5.1.
- Descargar Eclipse IDE for Java EE
Developers.
- Descargar Ruby.
