Construir los Componentes del Controlador
Introducción
Ahora que hemos entendido cómo construir los componentes del Modelo y de la Vista de nuestra aplicación, es hora de enfocarnos en los componentes del Controller. Struts incluye un servlet que implementa la función principal de mapeo de una solicitud URI a una clase Action. Por lo tanto, nuestras principales responsabilidades con el controlador son:
- Escribir una clase Action por cada solicitud lógica que podría ser recibida (extendida desde org.apache.action.Action).
- Configurar un ActionMapping (en XML) por cada solicitud lógica que podría ser enviada. El fichero de configuración XML normalmente se llama struts-config.xml.
- Actualizar el fichero del descriptor de despliegue de la aplicación Web (en XML) para nuestra aplicación para que incluya los componentes Struts necesarios.
- Añadir los componentes Struts apropiados a nuestra aplicación.
Clases Action
La clase Action define dos métodos que podrían ser ejecutados dependiendo de nuestro entorno servlet:
public ActionForward perform(ActionMapping mapping,
ActionForm form,
ServletRequest request,
ServletResponse response)
throws IOException, ServletException;
public ActionForward perform(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException;
La mayoría de los proyectos sólo usarán la versión "HttpServletRequest".
El objetivo de una clase Action es procesar una solicitud, mediante su método perform(), y devolver un objeto ActionForward que identifica dónde se debería reenviar el control (por ejemplo a una JSP) para proporcionar la respuesta apropiada. En el patrón de diseño MVC/Model 2, una clase Action típica implementará una lógica como ésta en su método perform():
- Validar el estado actual de la sesión del usuario (por ejemplo, chequear que el usuario ha hecho el login). Si la clase Action encuentra que no existe logon, la solicitud es reenviada a la página JSP que muestra las peticiones del nombre de usuario y la password para logging on. Esto podría ocurrir porque un usuario intente entrar "en el medio" de una aplicación (digamos, desde un bookmark), o porque la sesión ha expirado, y el contenedor servlet creó una nueva.
- Si la validación no se ha completado, valida las propiedades del bean formulario según sea necesario. Si se encuentra un problema, almacena las claves de los mensajes de error apropiados como un atributo de la petición, y reenvía el control de vuelta al formulario de entrada para que se puedan corregir los errores.
- Realizar el procesamiento requerido para tratar con esta solicitud (como grabar un fila de la base de datos). Esto se puede hacer mediante código lógico embebido dentro de la propia clase Action, pero generalmente debería realizarse llamando a un método apropiado del bean de lógica de negocio.
- Actualizar los objetos del lado del servidor que serán usados para crear la siguiente página del interface de usuario (normalmente beans del ámbio de solicitud o de sesion, dependiendo de cuánto tiempo necesitemos mantener estos ítems disponibles).
- Devolver un objeto ActionForward apropiado que identifica la página JSP usada para generar esta respuesta, basada en los beans actualizados recientemente. Típicamente adquiriremos una referencia a dicho objeto llamando a findForward() o al objeto ActionMapping que recibimos (si estamos usando un nombre lógico normal para este mapeo), o en el propio servlet controlador (si estamos usando un nombre lógico global para la aplicación).
Entre los problemas de diseño a recordar cuando codificamos clases Action incluimos los siguientes:
Además, queremos protegernos contra clases Action que son demasiado largas. La forma más fácil de hacer que esto suceda es embeber la lógica funcional en la propia clase Action, en vez codificarla en beans de lógica de negocio independientes. Además de hacer la propia clase Action dura de entender y de mantener, esta aproximación también hace díficil re-utilizar el código de la lógica de negocio, porque está embebido dentro de un componente (la clase Action) que está concebido para ser ejecutado en un entorno de aplicación Web.
Una Action puede dividirse en varios métodos locales, mientras que todas las propiedades necesarias sean pasadas en las firmas de métodos. La JVM maneja dichas propiedades usando la pila, y por eso son seguras ante los threads.
La aplicación de ejemplo incluida con Struts no cumple este principio, porque la propia lógica de negocio está embebida dentro de las clases Action. Esto debería considerarse un bug en el diseño de la aplicación de ejemplo, en vez de una característica intrínseca de la arquitectura , o una aproximación a emular.
La Implementación de ActionMapping
Para poder operar satisfactoriamente, el servlet controlador Struts necesita conocer varias cosas sobre como se debería mapear toda URI solicitada a una clase Action apropiada. El conocimiento requerido ha sido encapsulado en un interface Java, llamado ActionMapping, estas son las propiedades más importantes:
- type - nombre totalmente cualificado de la clase Java que implementa la clase Action usada por este mapeo.
- name - El nombre del bean de formulario definido en el fichero de configuración que usará este action.
- path - El path de la URI solicitada que corresponden con la selección de este mapeo.
- unknown - Seleccionado a true si este action debería ser configurado como por defecto para esta aplicación, para manejar todas las solicitudes no manejadas por otros action. Sólo un Action puede estar definido como por defecto dentro de una sóla aplicación.
- validate - Seleccionado a true si se debería llamar al método validate() de la action asociada con este mapeo.
- forward - El path de la URI solicitada a la que se pasa el control cuando se ha invocado su mapeo. Esto es una alternativa a declarar una propiedad type.
Fichero de Configuración de los Mapeos de Action
¿Cómo aprende el servlet controlador sobre los mapeos que queremos? Sería posible (pero tedioso) escribir una pequeña clase Java que simplemente ejemplarizara nuevos ejemplares de ActionMapping, y llamara a todos los métodos set() apropiados. Para hacer este proceso más sencillo, Struts incluye un módulo Digester que es capaz de leer la descripción basada en XML de los mapeos deseados, creando los objetos apropiados de la misma forma. Puedes encontrar más información sobre este Digester en la documentación del API
La responsabilidad del desarrollador es crear un fichero XML llamado struts-config.xml, y situarlo en el directorio WEB-INF de su aplicación. Este formato de documento está restringido por su definición en "struts-config_1_0.dtd". El elemento XML más exterior debe ser <struts-config>.
Dentro del elemento <struts-config>, hay dos elementos importantes que son usados para describir nuestras acciones:
- <form-beans>
Esta sección contiene nuestras definiciones de beans. Usamos un elemento <form-bean> por cada bean de formulario, que tiene los siguientes atributos importantes:
- name: Un identificador único para este bean, que será usado para referenciarlo en los correspondientes mapeos de acciones. Normalmente, es también el nombre del atributo de solicitud o sesión bajo el que se almacena este bean de formulario.
- type: El nombre totalmente cualificado de la clase Java de nuestro bean de formulario.
- <action-mappings>
Esta sección contiene nuestras definiciones de acciones. Usamos un elemento <action> por cada una de nuestras acciones que queramos definir. Cada elemento action requiere que se definan los siguientes atributos:
- path: El path a la clase action en relación al contexto de la aplicación.
- type: El nombre totalmente cualificado de la clase Java de nuestra clase Action.
- name: El nombre de nuestro elemento <form-bean> para usar con esta action.
El fichero struts-config.xml de la aplicación de ejemplo incluye las siguientes entradas de mapeo para la función "log on", que se usará para ilustrar los requerimientos. Oserva que las entradas para otras acciones se han dejado fuera:
<struts-config>
<form-beans>
<form-bean
name="logonForm"
type="org.apache.struts.example.LogonForm" />
</form-beans>
<global-forwards
type="org.apache.struts.action.ActionForward" />
<forward name="logon" path="/logon.jsp"
redirect="false" />
</global-forwards>
<action-mappings>
<action
path="/logon"
type="org.apache.struts.example.LogonAction"
name="logonForm"
scope="request"
input="/logon.jsp"
unknown="false"
validate="true" />
</action-mappings>
</struts-config>
Primero se define el bean formulario, Un bean básico de la clase "org.apache.struts.example.LogonForm" es mapeado al nombre lógico "logonForm". Este nombre se usa como un nombre de atributo de sesión o solicitud para el bean de formulario.
La sección "global-forwards" se usa para crear mapeos de nombres lógicos para páginas JSP usadas comunmente. Cada uno de estos reenvíos está disponible a través de una llamada a nuestro ejemplar de mapeo de action, por ejemplo actionMappingInstace.findForward("logicalName").
Como podemos ver, este mapeo corresponde con el path /logon (realmente, porque la aplicación de ejemplo usa mapeo de extensión, la URI que especificamos en una página JSP terminaría en /logon.do). Cuando se recibe una solicitud que corresponde con el path, se crea un ejemplar de LogonAction (sólo la primera vez). El Servlet controlador buscará un bean de ámbito de sesión bajo la clave logonForm, creando y guardando un bean de la clase especificada si es necesario.
Opcionales pero muy útiles son los elementos localizados en "forward". En la aplicación de ejemplo, muchas acciones incluyen un reenvio local "success" y/o "failure" como parte de un mapeo de Action.
<!-- Edit mail subscription -->
<action path="/editSubscription"
type="org.apache.struts.example.EditSubscriptionAction"
name="subscriptionForm"
scope="request"
validate="false">
<forward name="failure" path="/mainMenu.jsp"/>
<forward name="success" path="/subscription.jsp"/>
</action>
Usando estas dos propiedades extras, las clases Action de la aplicación de ejemplo son casi totalmente independientes de los nombres reales de las páginas JSP que son usadas por los diseñadores, Las páginas, pueden renombrarse (por ejemplo) durante un rediseño, con un mínimo impacto en las propias clases Action. Si los nombres de las páginas JSP "next" estuvieran codificados dentro de las clases Action, todas estas clases tendrían que ser modificadas. Por supuesto, podemos definir cualquier propiedad de reenvío local que tenga sentido para nuestra aplicación.
Una sección más de buen uso es la sección <data-sources>, que especifica las fuentes de datos que puede usar nuestra aplicación. Aquí podemos ver cómo especificar una fuente de datos para nuestra aplicación dentro de struts-config.xml:
<struts-config>
<data-sources>
<data-source
autoCommit="false"
description="Example Data Source Description"
driverClass="org.postgresql.Driver"
maxCount="4"
minCount="2"
password="mypassword"
url="jdbc:postgresql://localhost/mydatabase"
user="myusername"/>
</data-sources>
</struts-config>
Descriptor de Despliegue de la Aplicación Web
El paso final en la configuración de la aplicación es configurar el descriptor de despliegue (almacenado en el fichero WEB-INF/web.xml) para incluir todos los componentes Struts que son necesarios. Usando el descriptor de despliegue del la aplicación de ejemplo como guía, veremos que se necesitan crear o modificar la siguientes entradas.
Configurar el Ejemplar de Action Servlet
Añadimos una entrada definiendo el propio servlet action, junto con los parámetros de inicialización apropiados. Dicha entrada se podría parecer a esto:
<servlet>
<servlet-name>action</servlet-name>
<servlet-class>
org.apache.struts.action.ActionServlet
</servlet-class>
<init-param>
<param-name>application</param-name>
<param-value>
org.apache.struts.example.ApplicationResources
</param-value>
</init-param>
<init-param>
<param-name>config</param-name>
<param-value>
/WEB-INF/struts-config.xml
</param-value>
</init-param>
<init-param>
<param-name>debug</param-name>
<param-value>2</param-value>
</init-param>
<init-param>
<param-name>mapping</param-name>
<param-value>
org.apache.struts.example.ApplicationMapping
</param-value>
</init-param>
<load-on-startup>2</load-on-startup>
</servlet>
Los parámetros de inicialización soportados por el servlet controlador se describen abajo. Los corchetes cuadrados describen los valores por defecto que se asumen si no proporcionamos un valor para el parámetro de inicialización.
- application - El nombre de la clase Java para la clase base del paquete de recursos de la aplicación. [NONE].
- bufferSize - El tamaño del buffer de entrada usado para procesar uploads de ficheros. [4096].
- config - Path relativo al contexto del recurso XML que contiene nuestra información de configuración.[/WEB-INF/struts-config.xml].
- content - Tipo de contenido por defecto y codificación de caracteres a seleccionar en cada respuesta; podría ser sobreescrito por un servlet re-enviado o una página JSP. [text/html].
- debug - El nivel de detalle de depuración para este servlet, que controla cuanta información se pone en el log. [0].
- detail - El nivel de detalles de depuración para el Digester que utilizamos en initMapping(), que sale por System.out en lugar de servlet log. [0].
- factory - El nombre de la clase Java del MessageResourcesFactory usado para crear el objeto MessageResources de la aplicación. [org.apache.struts.util.PropertyMessageResourcesFactory].
- formBean - El nombre de la clase Java de la implementación de ActionFormBean a utilizar. [org.apache.struts.action.ActionFormBean].
- forward - el nombre de la clase Java de la implemetnación de ActionForward a utilizar. [org.apache.struts.action.ActionForward]. Podríamos usar aquí dos clases de conveniencia:
- org.apache.struts.action.ForwardingActionForward - Subclase de org.apache.struts.action.ActionForward que por defecto pone la propiead redirect a false (lo mismo que el valor por defecto de ActionForward).
- org.apache.struts.action.RedirectingActionForward - Subclase de org.apache.struts.action.ActionForward que por defecto pone la propiedad redirect a true.
- locale - Si se selecciona a true, y hay una sesión de usuario, indentifica y almacena un objeto java.util.Locale apropiado (bajo la clave estándard indentificada por Action.LOCALE_KEY) en la sesión de usuario si no hay ya un objeto Locale. [true]
- mapping - El nombre de la clase Java de la implementación del ActionMapping a utilizar. [org.apache.struts.action.ActionMapping]. Podríamos usar aquí dos clases de conveniencia:
- org.apache.struts.action.RequestActionMapping - Subclase de org.apache.struts.action.ActionMapping que por defecto deja la propiedad scope a "request".
- org.apache.struts.action.SessionActionMapping - Subclase de org.apache.struts.action.ActionMapping que por defecto deja la propiedad scope a "session". (Igual que el valor por defecto de ActionMapping).
- maxFileSize - El tamaño máximo (en bytes) para que un ficheo sea aceptado para upload. Puede expresarse como un número seguido por una K" "M", o "G", que serán interpretadas como kilobytes, megabytes, o gigabytes, respectivamente. [250M].
- multipartClass - El nombre totalmente cualificado de la clase de la implementación de MultipartRequestHandler usado para procesar uploads de ficheros. [org.apache.struts.upload.DiskMultipartRequestHandler].
- nocache - Si se selecciona a true, añade cabeceras HTTP a cada respuesta para evitar que el navegador almacene en el cahé cualquier respuesta generado o reenviada. [false].
- null - Si se selecciona a true, configura los recursos de nuestra aplicación a devolver null si se usa una clave de mensaje desconocida. De otra forma, se devolverá un mensaje de error incluyendo la clave errónea. [true].
- tempDir - El directorio de trabajo temporal usado cuando se procesan uploads de ficheros. [El directorio de trabajo proporcionado para esta aplicación web como atributo contexto del servlet].
- validate - ¿Estámos suando el nuevo formato de fichero de configuración? [true].
- validating - ¿Deberíamos usar un analizador con validación XML para procesar el fichero de configuración (altamente recomendado? [true].
Configurar el Mapeo del Servlet Action
Nota: El material de esta sección no es específico de Struts. La configuración del mapeo de servlets está definida en la Java Servlet Specification. Esta sección describe los significados más comunes de configuración de una aplicación Struts.
Hay dos aproximaciones comunes para definir las URLS que serán procesadas por el servlet controlador -- correspondencia de prefijo y correspondencia de extensión.
La correspondencia de prefijo significa que queremos que todas las URLs que empiecen con (después de la parte del path de contexto) un valor particular sean pasadas a este servlet. Dicha entrada se podría parecer a esto:
<servlet-mapping>
<servlet-name>action</servlet-name>
<url-pattern>/execute/*</url-pattern>
</servlet-mapping>
lo que significa que una URI que coincida con el path /logon descrito anteriormente podría parecerse a esto:
http://www.mycompany.com/myapplication/execute/logon
donde /myapplication es el path de contexto bajo el que se ha desplegado nuestra aplicación.
Por otro lado, en el mapeo por extensión, se renvian las URIs solicitadas al servlet action basándose en el hecho de que la URI termine en un punto seguido por un conjunto defindo por caracteres. Por ejemplo, el servlet de procesamiento JSP está mapeado al patrón *.jsp para que sea llamado cada vez que se solicite una página JSP. Para usar la extensión *.do (que implica "hacer algo"), la entrada de mapeo se podría parecer a esta:
<servlet-mapping>
<servlet-name>action</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
y una URI que corresponda con el path /logon descrito anteriormente se parecería a esto:
http://www.mycompany.com/myapplication/logon.do
Configurar la Librería de Etiquetas de Struts
Luego, debemos añadir una entrada definiendo la librería de etiquetas Struts. Actualmente hay cuatro librerías que vienen con Struts.
La librería struts-bean contiene etiquetas útiles para acceder a los beans y sus propiedades, así como para definir nuevos beans (basados en esos accesores) que son accesibles para el resto de la página mediante variables de scripting y atributos de ámbito de página. También se proporcionan mecanismos convenientes para crear nuevos beans basados en el valor de una cookie, de las cabeceras y de los parámetros.
La librería struts-html contiene etiquetas para crear formularios de entrada struts, así como otras etiquetas generalmente útiles en la creación de interfaces de usuario basados en HTML.
La librería struts-logic contiene etiquetas que son útiles para manejar la generación condicional de salida de texto, hacer bucles sobre colecciones de objetos para generación repetitiva de salida de texto y control del flujo de la aplicación.
La librería struts-template contiene etiquetas que definen un mecanismo de plantillas.
Abajo podemos ver cómo se definirían todas las librerías de etiquetas para usarlas en nuestra aplicación, en realidad, sólo deberíamos especificar las librerías que vayamos a utilizar:
<taglib>
<taglib-uri>
/WEB-INF/struts-bean.tld
</taglib-uri>
<taglib-location>
/WEB-INF/struts-bean.tld
</taglib-location>
</taglib>
<taglib>
<taglib-uri>
/WEB-INF/struts-html.tld
</taglib-uri>
<taglib-location>
/WEB-INF/struts-html.tld
</taglib-location>
</taglib>
<taglib>
<taglib-uri>
/WEB-INF/struts-logic.tld
</taglib-uri>
<taglib-location>
/WEB-INF/struts-logic.tld
</taglib-location>
</taglib>
<taglib>
<taglib-uri>
/WEB-INF/struts-template.tld
</taglib-uri>
<taglib-location>
/WEB-INF/struts-template.tld
</taglib-location>
</taglib>
Esto le dice al sistema JSP donde encontrar el descritor de librería de etiqueta para esta librería (en nuestro directorio WEB-INF de la aplicación, en vez de en algún lugar exterior en Internet).
Añadir Componentes Struts a nuestra Aplicación
Para usar Struts, debemos copiar los ficheros .tld que necesitamos en nuestro directorio WEB-INF, y copiar struts.jar (y todos los otros ficheros commons-*.jar) en nuestro directorio WEB-INF/lib.