Construcción de servicios web RESTful y aplicaciones Web dinámicas con la arquitectura multinivel

Continúe capacitándose en la construcción de servicios web RESTful y aplicaciones Web dinámicas usando la arquitectura multinivel. Este artículo proporciona experiencia práctica del diseño y la construcción de componentes en cada nivel y cómo se vinculan estos componentes. Se brinda un ejemplo de la forma en que los servicios web RESTful, Ajax (Asynchronous JavaScript And XML) y Spring Web Flow trabajan juntos para producir una interfaz Web de alta calidad y capacidad de respuesta, similar a una interfaz de escritorio. También se muestra cómo programas clientes, como Ruby Scripts, usan servicios web RESTful para cargar y descargar los datos de usuarios del servidor.

Introducción

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.


Escenario

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)
  • e-mail
  • 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.


Los componentes

La Tabla 1 muestra una lista de los componentes y su organización en la estructura de carpetas.

Tabla 1. Estructura de carpetas
NivelCapas y scriptsUbicación del archivo
Nivel clienteScripts AjaxWebRoot/js
Páginas JSPWebRoot/WEB-INF/jsp
Scripts RubyClient/ruby
Capa PresentationCapa Presentation - Browser Request Handlersrc/edu/ucar/cisl/ncarUsers/presentation/brh
Capa Presentation - Resource Request Handlersrc/edu/ucar/cisl/ncarUsers/presentation/rrh
Capa Business Logicsrc/edu/ucar/cisl/ncarUsers/bll
Capa Data Accesssrc/edu/ucar/cisl/ncarUsers/dal
Capa Data StoreMySql Scriptsdb/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.


Configuración del entorno

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)

  1. Apache Tomcat 6.x
  2. MySQL 5.1
  3. Eclipse IDE for Java EE Developers (usé la versión Eclipse Europa, pero también se puede utilizar la última versión)
  4. 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
Biblioteca Ruby gen install json

Creación de la base de datos

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
Ejecución del archivo script setup.sql

Configuración del servidor Tomcat en Eclipse

Para configurar el servidor Tomcat:

  1. Abra Eclipse y seleccione File > New > Other.
  2. Seleccione Server de la lista.
    Figura 3. Configuración del servidor Tomcat en Eclipse - Paso 1
    Configuración del servidor Tomcat en Eclipse - Paso 1
  3. Haga clic en Next. En la ventana, seleccione Apache > Tomcat v6.0 Server.
    Figura 4. Configuración del servidor Tomcat en Eclipse - Paso 2
    Configuración del servidor Tomcat en Eclipse - Paso 2
  4. 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
    Configuración del servidor Tomcat en Eclipse - Paso 3
  5. Haga clic en Finish.

Creación del proyecto Web ncarUsers en Eclipse

Para crear el proyecto Web:

  1. Seleccione File > New > Project… . Open (abrir) Web.
  2. Haga clic en Dynamic Web Project (proyecto Web dinámico).
    Figura 6. Creación del proyecto Web ncarUsers en Eclipse - Paso 1
    Creación del proyecto Web ncarUsers en Eclipse - Paso 1
  3. Haga clic en Next. En la nueva ventana, ingrese ncarUsers en el campo Project Name (nombre del proyecto).
    Figura 7. Creación del proyecto Web ncarUsers en Eclipse - Paso 2
    Creación del proyecto Web ncarUsers en Eclipse - Paso 2
  4. Haga clic en Next.
  5. Haga clic en Next nuevamente en la ventana Project Facets (facetas del proyecto).
  6. En la ventana Web Module (modulo Web), cambie WebContent por WebRoot en el campo Content Directory (directorio de contenidos).
  7. Haga clic en Finish.
    Figura 8. Creación del proyecto Web ncarUsers en Eclipse - Paso 3
    Creación del proyecto Web ncarUsers en Eclipse - Paso 3

Importe los archivos desde la descarga de este artículo

Para importar los archivos:

  1. En el Project Explorer, haga clic con el botón derecho en ncarUsers y seleccione Import > Import ….
  2. 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
    Importación de la descarga del artículo hacia el proyecto ncarUsers - Paso 1
  3. Haga clic en Next.
  4. En la ventana File System, haga clic en Browse ..., y seleccione C:\ncarUsers.
  5. 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
    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
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

Browser Request Handler

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>&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>

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>

Resource Request Handler

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.   }

Aplicaciones clientes

Ajax

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.    }

Scripts Ruby

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])

Vinculación

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:

  1. 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.
  2. Seleccione localhost > Tomcat v6.0 Server at localhost.
    Figura 12. Ejecutar Tomcat dentro de Eclipse
    Ejecutar Tomcat dentro de Eclipse
  3. 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).
  4. 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)
    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.

  5. 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)
    Página NCAR New Signup Confirmation (nueva confirmación de registro en NCAR)
  6. Haga clic en Add New User (agregar nuevo usuario).
  7. 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.
  8. Cree algunos usuarios y luego abra una solicitud de comando. Ejecute el script Ruby downloadUsers.rb para descargar los datos de usuarios (Figura 15) . Puede usar el archivo descargado como plantilla para agregar otros usuarios. Luego use uploadUsers.rb para cargar los nuevos usuarios en el servidor de aplicaciones.
    Figura 15. Ejecute el script downloadUsers.rb para descargar los datos de usuarios
    Ejecute el script downloadUsers.rb para descargar los datos de usuarios

¿Qué sucede internamente?

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
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)
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.


Conclusión

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.


Descargar

DescripciónNombretamaño
Source codencarUsers.zip5987KB

Recursos

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

Comentarios

developerWorks: Ingrese

Los campos obligatorios están marcados con un asterisco (*).


¿Necesita un IBM ID?
¿Olvidó su IBM ID?


¿Olvidó su Password?
Cambie su Password

Al hacer clic en Enviar, usted está de acuerdo con los términos y condiciones de developerWorks.

 


La primera vez que inicie sesión en developerWorks, se creará un perfil para usted. La información en su propio perfil (nombre, país/región y nombre de la empresa) se muestra al público y acompañará a cualquier contenido que publique, a menos que opte por la opción de ocultar el nombre de su empresa. Puede actualizar su cuenta de IBM en cualquier momento.

Toda la información enviada es segura.

Elija su nombre para mostrar



La primera vez que inicia sesión en developerWorks se crea un perfil para usted, teniendo que elegir un nombre para mostrar en el mismo. Este nombre acompañará el contenido que usted publique en developerWorks.

Por favor elija un nombre de 3 - 31 caracteres. Su nombre de usuario debe ser único en la comunidad developerWorks y debe ser distinto a su dirección de email por motivos de privacidad.

Los campos obligatorios están marcados con un asterisco (*).

(Por favor elija un nombre de 3 - 31 caracteres.)

Al hacer clic en Enviar, usted está de acuerdo con los términos y condiciones de developerWorks.

 


Toda la información enviada es segura.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=90
Zone=Lotus, SOA y servicios web
ArticleID=425565
ArticleTitle=Construcción de servicios web RESTful y aplicaciones Web dinámicas con la arquitectura multinivel
publish-date=07292011