07 noviembre, 2006

Model View Controller - Struts

...
Hasta aquí trabajamos con una metodología que llamamos Modelo 1. Si bien este modelo es muy simple presenta inconvenientes importantes a medida que el sistema que estamos desarrollando comienza a crecer.

Con esta metodología una página submitea los datos de su formulario a otra para que los procese. Nosotros la llamamos “Página de Proceso” y utilizamos el sufijo “PRO” para identificarla facilmente. La página PRO procesa y forwardea a otra página “de presentación”, la cual a su vez tiene otro formulario que submitea a otra página PRO y así sucesivamente.

Es evidente que esta forma de trabajo se pude tornar inmanejable para sistemas medianos y grantes porque todos los enlaces y vínculos de las diferentes pantallas y sus correspondientes procesos están distribuídos por toda la aplicación.


Model View Controller - MVC

Por lo anterior, para sistemas medianos y grandes lo más recomendable es aplicar la metodología llamada Modelo 2. Esta metodología no es más que aplicar el patrón de diseño MVC “Model View Controller”.

Analicemos el siguiente gráfico.


Podemos ver en el gráfico como las páginas JSP submitean sus datos (formularios, links, etc) a un servlet llamado ActionServlet. Este servlet consulta una tabla de configuración en la que se define una clase Java para procesar cada JSP, y diferentes destinos (forwards) según el resultado que se indique en el proceso Java.

Viendo los valores de la tabla podemos deducir que: A X1.jsp lo procesa X1Action.java. Si este dice “bien” entonces la próxima página a mostrar será X2.jsp. Pero si X1Action.java dice “mal” entonces la próxima página a visualizar será X1Err.jsp.

El patrón de diseño MVC permite separar la capa de presentación (view) de la capa de negocios (model) y controlar (controller) el paso de datos entre una capa y la otra.

Un gráfico completo de MVC es el siguiente:



En este gráfico podemos ver una página login.jsp que submitea los datos (usuario y password) de su formulario al ActionServlet que a su vez los envía a LoginAction.java. Esta clase invoca el método login del Facade (objeto que centraliza la lógica de la aplicación) y obtiene como retorno un objeto de transferencia de datos (DTO - "Data Transfer Object") el cual lo reenvia a la próxima página JSP (app.jsp).

Podemos diferenciar entonces las tres partes involucradas en MVC: el view (las páginas JSP), el model (desde el Facade para atrás) y el controller (los Action que hacen de intermediarios entre el view y el model).


Struts

Struts es un framework que implementa MVC. En esta implementación la tabla de vínculos es un archivo XML llamado struts-config.xml.

Para desarrollar un proyecto con Struts dentro de MyEclipse debemos crear un proyecto web y (clickeando con el botón derecho sobre el nombre del proyecto) agregarle MyEclipse --> “Struts Capabilities”.


En la próxima pantalla veremos lo siguiente:



Seleccionamos la opción "Struts 1.2" y modificamos el "Base package for new classes" poniendo "test.struts". Con esto, cada vez que el wizard tenga que crear una nueva clase la creará (por default) en este paquete.

El wizard incluirá varios archivos .jar en el /lib de la aplicación web. Todas las clases que utilizaremos en este curso están dentro del archivo struts.jar. También modifirará el descriptor (web.xml) de la aplicación (ver más abajo) y creará el archivo struts-config.xml dentro del directorio /WEB-INF.


Primer Ejemplo

Se trata de una pantalla de "Login". El usuario ingresa username y password. Si el password es correcto (en el ejemplo lo harcodeamos a “pepito”) entonces se mostrará la pantalla de ok. Si no coincide se mostrará la pantalla de error.

struts-config.xml (vista gráfica de MyEclipse)


El gráfico muestra que tenemos una página inicial: login.jsp. Esta página tendrá un formulario HTML cuyo submit enviará la información a una clase java: LoginAction.java. Esta clase debe procesar la información que recibe de login.jsp y determinar si está “bien” o “mal”. Si está bien entonces la próxima página que se visualizará será okLogin.jsp. Si está mal entonces se visualizará errLogin.jsp.

Decimos que bien y mal son forwards del action /login. Y que login.jsp es el input de dicho action.

Nota: como vimos más arriba, la responsabilidad de LoginAction.java no es procesar los datos. Su responsabilidad es invocar en el facade el método que corresponda pasándole los datos que recibió de la página JSP. En este ejemplo, para simplificar, el proceso de verificar si el password es correcto o no lo haremos en el action, pero esta no es la forma correcta de usar MVC.

El gráfico anterior es una representación del archivo struts-config.xml. MyEclipse permite verlo en forma gráfica o bien ver directamente el código XML. Para comenzar (creo yo) la vista gráfica es más entendible pero a medida que uno comienza a dominar el framework es más simple trabajar directamente con XML.

struts-config.xml
   1:
2:<?xml version="1.0" encoding="UTF-8"?>
3:<!DOCTYPE struts-config PUBLIC "-//Apache Software
4:Foundation//DTD Struts Configuration 1.2//EN"
5:"http://struts.apache.org/dtds/struts-config_1_2.dtd">
6:
7:<struts-config>
8: <data-sources />
9: <form-beans />
10: <global-exceptions />
11: <global-forwards />
12:
13: <action-mappings>
14:
15: <action
16: path="/login"
17: type="test.struts.action.LoginAction"
18: input="/login.jsp">
19: <forward name="bien" path="/okLogin.jsp" />
20: <forward name="mal" path="/errLogin.jsp" />
21: </action>
22:
23: </action-mappings>
24:
25: <message-resources
26: parameter="test.struts.ApplicationResources"
27: />
28:</struts-config>
29:

En el archivo struts-config.xml tendremos varias ocurrencias del TAG action. Justamente cada ocurrencia de este TAG representa una acción que vamos a procesar.

Notemos que en los forward relacionan un nombre lógico (“bien”, “mal”) con un recurso físico (okLogin.jsp, errLogin.jsp). También podemos ver que type indica la clase que procesará la información e input la página desde donde se envía la información a LoginAction.java.

Veamos el código de cada una de las otras partes involucradas en este gráfico: login.jsp, LoginAction.java, okLogin.jsp, errLogin.jsp y (obviamente) web.xml (sin el cual nada de esto sería posible)

login.jsp
   1:
2:<html>
3:<body>
4: <form action="login.do">
5: Usuario <input type="text" name="usuario"><br>
6: Clave <input type="password" name="clave"><br>
7: <input type="submit" value="Login">
8: </form>
9:</body>
10:</html>
11:

Notemos que el formulario de login.jsp submitea a "login.do". Veremos luego que en el archivo web.xml el wizard de struts de MyEclipse agregó un url-pattern que vincula *.do con el ActionServlet. Esto significa que cualquier request que llegue a la aplicación pidiendo algún recurso que termine con .do será enviado automaticamente al ActionServlet.

Por ejemplo: http://localhost:8080/HMAPP/login.do

Este request llegará al ActionServlet. El ActionServlet va a eliminar el .do y con el /login buscará dentro de los action-mappings definidos en struts-config.xml para saber que clase tiene que instanciar para procesar el request.

Al submitear en el formulario a login.do estamos enviando la información del form HTML al ActionServlet y este (leyendo el struts-config.xml) la enviará a LoginAction.java.

web.xml
   1:
2:<?xml version="1.0" encoding="UTF-8"?>
3:<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
4:xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5:version="2.4"
6:xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
7:http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
8:
9: <servlet>
10: <!-- define un servlet bajo el nombre action -->
11: <!-- cuya clase es ActionServlet -->
12: <servlet-name>action</servlet-name>
13: <servlet-class>
14: org.apache.struts.action.ActionServlet
15: </servlet-class>
16:
17: <init-param>
18: <param-name>config</param-name>
19: <param-value>
20: /WEB-INF/struts-config.xml
21: </param-value>
22: </init-param>
23: <init-param>
24: <param-name>debug</param-name>
25: <param-value>3</param-value>
26: </init-param>
27: <init-param>
28: <param-name>detail</param-name>
29: <param-value>3</param-value>
30: </init-param>
31: <load-on-startup>0</load-on-startup>
32: </servlet>
33:
34: <!-- Vincula *.do con el servlet action -->
35: <servlet-mapping>
36: <servlet-name>action</servlet-name>
37: <url-pattern>*.do</url-pattern>
38: </servlet-mapping>
39:</web-app>
40:

Veamos la clase LoginAction.java. Esta clase debe heredar de la clase base Action y sobreescribir el método execute. El ActionServlet va a invocar a este método para que nuestra clase procese la información.

loginAction.java
   1:
2:package test.struts.action;
3:import javax.servlet.http.*;
4:import org.apache.struts.action.*;
5:
6:// esta clase hereda Action (de Struts)
7:public class LoginAction extends Action
8:{
9: // escribir un action basicamente es escribir una
10: // clase que herede de Action y que sobrescriba el
11: // metodo execute(), que al retornar una instancia
12: // de ActionForward le indica al ActionServlet
13: // cual es la proxima pagina que debe mostrar
14: public ActionForward execute(
15: ActionMapping mapping
16: , ActionForm form
17: , HttpServletRequest request
18: , HttpServletResponse response)
19: {
20: String usuario=request.getParameter("usuario");
21: String clave=request.getParameter("clave");
22:
23: // si el password es "pepito" entonces esta ok
24: if( clave.equals("pepito") )
25: {
26: return mapping.findForward("bien");
27: }
28: else
29: {
30: return mapping.findForward("mal");
31: }
32: }
33:}
34:

El método execute (heredado de Action) en LoginAction.java debe retornar un ActionForward. Este objeto lo obtenemos a partir del objeto mapping (que recibimos como parámetro). El método findForward de este objeto retorna un ActionForward direccionado al destino físico especificado en struts-config.xml.

Por último, veamos okLogin.jsp y errLogin.jsp.

okLogin.jsp
   1:
2:<html>
3: <body>
4: :O)
5: </body>
6:</html>
7:

errLogin.jsp
   1:
2:<html>
3: <body>
4: :O(
5: </body>
6:</html>
7:


Formularios y Custom Tags

Struts permite validar la información que submitea la página JSP. Esta validación se puede realizar del lado del server o del lado del cliente. En este caso estudiaremos la validación del lado del server.

Para validar la información que submitea la página podemos agregar un punto intermedio entre la página JSP y el Action que la procesa: el formulario.

El formulario no es más que una clase java que extienda a la clase ActionForm (de Struts), con tantos atributos como campos tenga el form HTML de la página, sus setters y getters, y un método (sobreescrito) para validar la información que contienen los atributos.



El ActionServlet envia primero al formulario la información submiteada por la página. Si este da el ok entonces la información será enviada al Action. Pero si el form no la acepta entonces el ActionServlet forwardeará a la página que originó la entrada de la información: (identificada por el parámetro input en el struts-config.xml).

Volviendo al ejemplo, necesitaremos crear una clase LoginForm.java.

Si lo hacemos a través del wizard de MyEclipse, haciendo "botón derecho" sobre la vista gráfica del struts-config.xml podremos ver la opción "New Form".



Luego veremos la siguiente pantalla.



En esta pantalla ingresamos el "Use case" (login) y los demás campos se completarán solos. Esto nos va a crear una clase LoginForm.java en el paquete test.struts.form.

Con el botón Add podemos agregar los atributos que queremos que el tenga el formulario.

La clase generada es la siguiente:

LoginForm.java
   1:
2:package test.struts.form;
3:
4:import javax.servlet.http.HttpServletRequest;
5:import org.apache.struts.action.*;
6:
7:public class LoginForm extends ActionForm
8:{
9:
10: private String usuario;
11: private String clave;
12:
13: public ActionErrors validate(
14: ActionMapping mapping,
15: HttpServletRequest request)
16: {
17: ActionErrors errores=new ActionErrors();
18: if( usuario.trim().length()<=0 )
19: {
20:
21: errores.add("usuario"
22: ,new ActionMessage(
23: "login.usr.noCompleta"));
24: }
25: if( clave.trim().length()<=0 )
26: {
27: errores.add("clave"
28: ,new ActionMessage(
29: "login.pwd.noCompleta"));
30: }
31: return errores;
32: }
33:
34: public void reset(ActionMapping mapping
35: , HttpServletRequest request)
36: {
37: clave="";
38: }
39:
40: public String getUsuario()
41: {
42: return usuario;
43: }
44: public void setUsuario(String usuario)
45: {
46: this.usuario = usuario;
47: }
48: public String getClave()
49: {
50: return clave;
51: }
52: public void setClave(String clave)
53: {
54: this.clave = clave;
55: }
56:}
57:

Vemos que el form (LoginForm.java) extiende a ActionForm (de Struts). Tiene un atributo para cada uno de los campos del formulario HTML, sus stters y getters.

La clase también sobreescribe dos métodos: reset y validate. El primero simplemente permite resetear los valores de los atributos. Debemos notar que solo reseteamos el atributo clave. Esto se debe a que, si el login no es correcto, no queremos que el usuario tenga que volver a tipear su username. Esta característica es muy util cuando tenemos que manejar formularios grandes (como veremos en el siguiente ejemplo).

En el método validate tenemos que retornar una instancia de ActionErrors (de Struts) para indicarle al ActionServlet si existen errores o no. ActionErrors es una especie de Hashtable que contiene instancias de ActionMessage (de Struts) relacionadas con una key. La key es el nombre del campo en el formulario HTML, y para inicializar el mensaje de error que queremos mostrar se utiliza el archivo ApplicationResources.properties que debemos completar como se muestra a continuación:

ApplicationResources.properties
   1:
2:# con "#" podemos escribir comentarios
3:# formato:
4:# nomVar=valor de la variable (sin comillas)
5:
6:login.usr.noCompleta=<br>Ingrese un nombre de usuario
7:login.pwd.noCompleta=<br>Debe ingresar su password
8:

En este archivo definimos los mensajes que queremos mostrar en las páginas JSP. Los mensajes están asociados a una key. Esta key es la que usamos para instanciar el ActionMessage que usamos en el método validate de LoginForm.java.

El form (LoginForm.java) está relacionado con LoginAction.java. Esta relación se representa en el struts-config.xml que (como creamos el form con el wizard) ya debe estar modificado.

Veamos las modificaciones:

struts-config.xml (define y relaciona el formulario loginForm)
   1:
2:<?xml version="1.0" encoding="UTF-8"?>
3:<!DOCTYPE struts-config PUBLIC "-//Apache Software
4:Foundation//DTD Struts Configuration 1.2//EN"
5:"http://struts.apache.org/dtds/struts-config_1_2.dtd">
6:
7:<struts-config>
8: <data-sources />
9:
10: <!-- en esta seccion se definen los forms -->
11: <form-beans>
12: <form-bean name="loginForm"
13: type="test.struts.form.LoginForm" />
14: </form-beans>
15:
16: <global-exceptions />
17: <global-forwards />
18:
19: <action-mappings>
20:
21: <!-- notemos que ahora tenemos el atributo -->
22: <!-- name que relaciona el form (loginForm) -->
23: <!-- con este action mapping -->
24: <action path="/login"
25: type="test.struts.action.LoginAction"
26: input="/login.jsp"
27: name="loginForm">
28: <forward name="bien" path="/okLogin.jsp" />
29: <forward name="mal" path="/errLogin.jsp" />
30: </action>
31:
32: </action-mappings>
33:
34: <message-resources
35: parameter="test.struts.ApplicationResources" />
36:</struts-config>
37:

Como vemos, el action está relacionado con un formulario. En este caso es loginForm.

Ahora modificaremos la página JSP para mostrar (cuando corresponda) los mensajes de error enviados por el formulario. Para esto tenemos que utilizar una librería de custom tags provista por struts.

login.jsp (usa custom tags de Struts)
   1:
2:<%@ taglib uri="/WEB-INF/struts-html.tld"
3: prefix="html"%>
4:
5:<html:html>
6:<html:errors/>
7:<body>
8: <html:form action="/login.do">
9: Usuario <html:text property="usuario" /> <br>
10: Clave <html:password property="clave" /> <br>
11: <html:submit value="Login" />
12: </html:form>
13:</body>
14:</html:html>
15:

El resultado es:



Ahora, para finalizar vamos a mejorar la página login.jsp para que se vea de esta forma:



La única diferencia es que utilizamos una tabla para alinear mejor los campos del formulario y separamos los mensajes de error poniendo cada uno debajo del campo que corresponde. El código es el siguiente:

login.jsp (separa los mensajes según el campo)
   1:
2:<%@ taglib uri="/WEB-INF/struts-html.tld"
3: prefix="html"%>
4:
5:<html:html>
6:
7:
8:<body>
9: <html:form action="/login.do">
10: <table>
11: <tr>
12: <td>
13: Usuario
14: </td>
15: <td>
16: <html:text property="usuario" />
17: <font color="red">
18: <html:errors property="usuario" />
19: </font>
20: </td>
21: </tr>
22: <tr>
23: <td>
24: Clave
25: </td>
26: <td>
27: <html:password property="clave" />
28: <font color="red">
29: <html:errors property="clave" />
30: </font>
31: </td>
32: </tr>
33: <tr>
34: <td colspan="2">
35:
36: <html:submit value="Login" />
37:
38: </td>
39: </tr>
40: </table>
41: </html:form>
42:</body>
43:</html:html>
44:

Siguiente Página -->



.