19 de junio de 2011

Struts 2 - Parte 1: Configuración

Cuando desarrollamos aplicaciones web es importante que estas puedan ser creadas de forma rápida y eficiente. Hoy en día existen muchos frameworks, los cuales nos proporcionan un cascarón para las aplicaciones. Nosotros solo debemos implementar la lógica propia de la aplicación, configurar el framework mediante algún mecanismo como anotaciones o archivos XML, y estos se encargan de hacer la mayor parte del trabajo tedioso o repetitivo.

Struts 2 es un framework para el desarrollo de aplicaciones web, el cual hace que la implementación de las mismas sea más sencillo, más rápido, y con menos complicaciones. Además hace que estas sean más robustas y flexibles. El objetivo de Struts 2 es muy sencillo: hacer que el desarrollo de aplicaciones web sea simple para los desarrolladores.

En esta serie de tutoriales veremos cómo desarrollar aplicaciones usando este framework web, cómo configurar el controlador que implementa Struts 2, y las distintas opciones que nos ofrece.

Struts 2 es un framework de presentación, dentro de las capas en las que se divide una aplicación en la arquitectura JEE, el cual implementa el controlador del patrón de diseño MVC (Modelo Vista Controlador), y que podemos configurar de varias maneras; además proporciona algunos componentes para la capa de vista. Por si fuera poco, proporciona una integración perfecta con otros frameworks para implementar la capa del modelo (como Hibernate y Spring).

Para hacer más fácil presentar datos dinámicos, el framework incluye una biblioteca de etiquetas web. Las etiquetas interactúan con las validaciones y las características de internacionalización del framework, para asegurar que las entradas son válidas, y las salidas están localizadas. La biblioteca de etiquetas puede ser usada con JSP, FreeMarker, o Velocity; también pueden ser usadas otras bibliotecas de etiquetas como JSTL y soporta el uso de componentes JSF.

Además permite agregarle funcionalidades, mediante el uso de plugins, de forma transparente, ya que los plugins no tienen que ser declarados ni configurados de ninguna forma. Basta con agregar al classpath el jar que contiene al plugin, y eso es todo.

Como dije antes: el objetivo de Struts 2 es hacer que el desarrollo de aplicaciones web sea fácil para los desarrolladores. Para lograr esto, Struts 2 cuenta con características que permiten reducir la configuración gracias a que proporciona un conjunto inteligente de valores por default. Además hace uso de anotaciones y proporciona una forma de hacer la configuración de manera automática si usamos una serie de convenciones (y si hacemos uso de un plugin especial).

Struts 2 no es precisamente el heredero de Struts 1, sino que es la mezcla de dos framewoks: WebWork 2 y Struts (aunque en realidad me parece que de Struts 1 solo tomó algunos nombres ^_^).


Componentes de Struts 2

Comencemos hablando un poco de los componentes que forman a Struts 2.

El corazón de Struts 2 es un filtro, conocido como el "FilterDispatcher". Este es el punto de entrada del framework. A partir de él se lanza la ejecución de todas las peticiones que involucran al framework.

Las principales responsabilidades del "FilterDispatcher" son:

  • Ejecutar los Actions, que son los manejadores de las peticiones.
  • Comenzar la ejecución de la cadena de interceptores (de la que hablaremos en un momento).
  • Limpiar el "ActionContext", para evitar fugas de memoria.

Struts 2 procesa las peticiones usando tres elementos principales:

  • Interceptores
  • Acciones
  • Resultados

Interceptores

Los interceptores son clases que siguen el patrón interceptor. Estos permiten que se implementen funcionalidades cruzadas o comunes para todos los Actions, pero que se ejecuten fuera del Action (por ejemplo validaciones de datos, conversiones de tipos, población de datos, etc.).

Los interceptores realizan tareas antes y después de la ejecución de un Action y también pueden evitar que un Action se ejecute (por ejemplo si estamos haciendo alguna validación que no se ha cumplido).

Sirven para ejecutar algún proceso particular que se quiere aplicar a un conjunto de Actions. De hecho muchas de las características con que cuenta Struts 2 son proporcionadas por los interceptores.

Si alguna funcionalidad que necesitamos no se encuentra en los interceptores de Struts podemos crear nuestro propio interceptor y agregarlo a la cadena que se ejecuta por default.

De la misma forma, podemos modificar la cadena de interceptores de Struts, por ejemplo para quitar un interceptor o modificar su orden de ejecución.

La siguiente tabla muestra solo algunos de los interceptores más importantes que vienen integrados y pre-configurados en Struts 2:

InterceptorNombreDescripción
AliasaliasPermite que los parámetros tengan distintos nombres entre peticiones.
ChainingchainingPermite que las propiedades del Action ejecutado previamente estén disponibles en el Action actual
CheckboxcheckboxAyuda en el manejo de checkboxes agregando un parámetro con el valor "false" para checkboxes que no están marcadas (o checadas)
Conversion ErrorconversionErrorColoca información de los errores convirtiendo cadenas a los tipos de parámetros adecuados para los campos del Action.
Create SessioncreateSessionCrea de forma automática una sesión HTTP si es que aún no existe una.
Execute and WaitexecAndWaitEnvía al usuario a una página de espera intermedia mientras el Action se ejecuta en background.
File UploadfileUploadHace que la carga de archivos sea más fácil de realizar.
LoggingloggerProporciona un logging (salida a bitácora) simple, mostrando el nombre del Action que se está ejecutando.
ParametersparamsEstablece los parámetros de la petición en el Action.
PrepareprepareLlama al método "prepare" en los acciones que implementan la interface "Preparable"
Servlet ConfigurationservletConfigProporciona al Action acceso a información basada en Servlets.
RolesrolesPermite que el Action se ejecutado solo si el usuario tiene uno de los roles configurados.
TimertimerProporciona una información sencilla de cuánto tiempo tardo el Action en ejecutarse.
ValidationvalidationProporciona a los Actions soporte para validaciones de datos.
WorkflowworkflowRedirige al result "INPUT" sin ejecutar el Action cuando una validación falla.



Cada interceptor proporciona una característica distinta al Action. Para sacar la mayor ventaja posible de los interceptores, un Action permite que se aplique más de un interceptor. Para lograr esto Struts 2 permite crear pilas o stacks de interceptores y aplicarlas a los Actions. Cada interceptor es aplicado en el orden en el que aparece en el stack. También podemos formar pilas de interceptores en base a otras pilas ^_^.

Una de estas pilas de interceptores es aplicada por default a todos los Actions de la aplicación (aunque si queremos, también podemos aplicar una pila particular a un Action).

La siguiente tabla muestra algunos de los stacks de interceptores que vienen pre-configurados con Struts 2:

Nombre del StackInterceptores IncluidosDescripción
basicStack exception, servletConfig, prepare, checkbox, multiselect, actionMappingParams, params, conversionErrorLos interceptores que se espera se usen en todos los casos, hasta los más básicos.
validationWorkflowStack basicStack, validation, workflowAgrega validación y flujo de trabajo a las características del stack básico.
fileUploadStack fileUpload, basicStackAgrega funcionalidad de carga de archivos a las características del stack básico.
paramsPrepareParamsStackalias, i18n, checkbox, multiselect, params, servletConfig, prepare, chain, modelDriven, fileUpload, staticParams, actionMappingParams, params, conversionError, validation, workflowProporciona un stack completo para manejo de casi cualquier cosa que necesitemos en nuestras aplicaciones. El interceptor "params" se aplica dos veces, la primera vez proporciona los parámetros antes de que el método "prepare" sea llamado, y la segunda vez re-aplica los parámetros a los objetos que hayan podido ser recuperados durante la fase de preparación.
defaultStack alias, servletConfig, i18n, prepare, chain, debugging, scopedModelDriven, modelDriven, fileUpload, checkbox, multiselect, staticParams, actionMappingParams, params, conversionError, validation, workflowEs la pila que se aplica por default a todos los Actions de la aplicación.
executeAndWaitStack execAndWait, defaultStack, execAndWaitProporciona al stack básico las características de execute and wait, lo cual funciona para aplicaciones en las que deben subirse archivos que pueden tardar algún tiempo.



Acciones

Las acciones o Actions son clases encargadas de realizar la lógica para servir una petición. Cada URL es mapeada a una acción específica, la cual proporciona la lógica necesaria para servir a cada petición hecha por el usuario.

Estrictamente hablando, las acciones no necesitan implementar una interface o extender de alguna clase base. El único requisito para que una clase sea considerada un Action es que debe tener un método que no reciba argumentos que regrese ya sea un String o un objeto de tipo Result. Por default el nombre de este método debe ser "execute" aunque podemos ponerle el nombre que queramos y posteriormente indicarlo en el archivo de configuración de Struts.

Cuando el resultado es un String (lo cual es lo más común), el Result correspondiente se obtiene de la configuración del Action. Esto se usa para generar una respuesta para el usuario, lo cual veremos un poco más adelante.

Los Actions pueden ser objetos java simples (POJOs) que cumplan con el requisito anterior, aunque también pueden implementar la interface "com.opensymphony.xwork2.Action" o extender una clase base que proporciona Struts 2: "com.opensymphony.xwork2.ActionSupport" (lo cual nos hace más sencilla su creación y manejo).

La interface Action proporciona un conjunto de constantes con los nombres de los Results más comunes. Esta interface luce de la siguiente forma:


public interface Action 
{
    public static final String SUCCESS = "success";
    public static final String NONE = "none";
    public static final String ERROR = "error";
    public static final String INPUT = "input";
    public static final String LOGIN = "login";
    
    public String execute() throws Exception;
}


La clase "ActionSupport" implementa la interface "Action" y contiene una implementación del método "execute()" que regresa un valor de "SUCCESS". Además proporciona unos cuantos métodos para establecer mensajes, tanto de error como informativos, que pueden ser mostrados al usuario.


Results

Después que un Action ha sido procesado se debe enviar la respuesta de regreso al usuario, esto se realiza usando results. Este proceso tiene dos componentes, el tipo del result y el result mismo.

El tipo del result indica cómo debe ser tratado el resultado que se le regresará al cliente. Por ejemplo un tipo de Result puede enviar al usuario de vuelta una JSP (lo que haremos más a menudo), otro puede redirigirlo hacia otro sitio, mientras otro puede enviarle un flujo de bytes (para descargar un archivo por ejemplo).

Entraremos en más detalles de los tipos de resultados de Struts 2 en otro tutorial. Por ahora solo hay que saber que el tipo de result por default es "dispatcher".

Un Action puede tener más de un result asociado. Esto nos permitirá enviar al usuario a una vista distinta dependiendo del resultado de la ejecución del Action. Por ejemplo en caso de que todo salga bien, enviaremos al usuario al result "sucess", si algo sale mal lo enviaremos al result "error", o si no tiene permisos lo enviaremos al result "denied".


Funcionamiento de Struts 2

Ahora que conocemos a grandes rasgos los componentes que forman Struts 2, veamos de forma general cómo son procesadas las peticiones por una aplicación desarrollada en Struts 2. La siguiente imagen nos ayudará con esto:



Los pasos que sigue una petición son:

  1. 1 - El navegador web hace una petición para un recurso de la aplicación (index.action, reporte.pdf, etc.). El filtro de Struts (al cual llamamos FilterDispatcher aunque esto no es del todo correcto, retomaré esto un poco más adelante) revisa la petición y determina el Action apropiado para servirla.
  2. 2 - Se aplican los interceptores, los cuales realizan algunas funciones como validaciones, flujos de trabajo, manejo de la subida de archivos, etc.
  3. 3 - Se ejecuta el método adecuado del Action (por default el método "execute"), este método usualmente almacena y/o regresa alguna información referente al proceso.
  4. 4 - El Action indica cuál result debe ser aplicado. El result genera la salida apropiada dependiendo del resultado del proceso.
  5. 5 - Se aplican al resultado los mismos interceptores que se aplicaron a la petición, pero en orden inverso.
  6. 6 - El resultado vuelve a pasar por el FilterDispatcher aunque este ya no hace ningún proceso sobre el resultado (por definición de la especificación de Servlets, si una petición pasa por un filtro, su respuesta asociada pasa también por el mismo filtro).
  7. 7 - El resultado es enviado al usuario y este lo visualiza.



(Vuelvo a poner la imagen para que no tengan que regresar para verla ^_^).

Este camino podría no siempre seguirse en orden. Por ejemplo, si el interceptor de validación de datos encuentra que hay algún problema, el Action no se ejecutará, y será el mismo interceptor el que se encargará de enviar un Result al usuario.

Otra característica importante de Struts 2 es que no hace uso del lenguaje de expresiones propio de las JSPs, sino que hace uso de OGNL (Object-Graph Navigation Language), un lenguaje de expresiones más poderoso. Hablaremos más de OGNL en el siguiente tutorial.

Comencemos viendo un pequeño ejemplo que nos mostrará cómo configurar nuestras aplicaciones con Struts 2. Haremos una configuración haciendo uso de un archivo de configuración en XML y otro haciendo uso de anotaciones.

Nota: Yo usaré dos proyectos, uno con un archivo XML y otro con anotaciones, para que el código de ambos no se mezcle.

Lo primero que haremos es crear una biblioteca llamada "Struts2". Para esto debemos descargar la última versión de Struts 2 (actualmente la 2.2.3) del sitio de descargas de Struts 2. Podemos descarga cualquiera de los paquetes. Yo les recomiendo que bajen el "lib" o el paquete "all".

Una vez que tengamos el archivo, vamos al NetBeans y nos dirigimos al menú "Tools -> Libraries".



En la ventana que se abre presionamos el botón "New Library...":



En esta nueva ventana colocamos como nombre de la biblioteca "Struts2" y como tipo dejamos "Class Libraries":



Ahora que tenemos nuestra biblioteca, presionamos el botón "Add Jar/Folder" para agregar los nuevos archivos que conformarán la biblioteca:



Agregamos los siguientes archivos:

  • commons-fileupload-1.2.2.jar
  • commons-io-2.0.1.jar
  • commons-lang-2.5.jar
  • freemarker-2.3.16.jar
  • javassist-3.11.0.GA.jar
  • ognl-3.0.1.jar
  • struts2-core-2.2.3.jar
  • xwork-core-2.2.3.jar

Adicionalmente crearemos una segunda biblioteca para cuando trabajemos con anotaciones. Seguimos el mismo proceso para crear la biblioteca, que en este caso se llamará "Struts2Anotaciones". Esta biblioteca debe incluir los siguientes jars:

  • asm-3.1.jar
  • asm-commons-3.1.jar
  • commons-fileupload-1.2.2.jar
  • commons-io-2.0.1.jar
  • commons-lang-2.5.jar
  • freemarker-2.3.16.jar
  • javassist-3.11.0.GA.jar
  • ognl-3.0.1.jar
  • struts2-core-2.2.3.jar
  • xwork-core-2.2.3.jar
  • struts2-convention-plugin-2.2.3.jar

O sea, todos los que tiene la biblioteca "Struts 2" + el jar "asm-3.1.jar" + el jar "asm-commons-3.1.jar" + el jar "struts2-convention-plugin-2.2.3.jar".

Ahora tenemos dos bibliotecas de Struts 2, una que usaremos cuando trabajemos con archivos de configuración en XML, y otra que usaremos cuando hagamos nuestra configuración con anotaciones:



Lo siguiente es crear un nuevo proyecto web en NetBeans. Para esto vamos al Menú "File->New Project...". En la ventana que se abre seleccionamos la categoría "Java Web" y en el tipo de proyecto "Web Application":



Le damos una ubicación y un nombre al proyecto, en mi caso será "Struts2Configuracion". Presionamos el botón "Next". En la siguiente pantalla deberemos configurar el servidor de aplicaciones que usaremos. Yo usaré la versión 7 de Tomcat (si no lo tienen configurado, pueden seguir los mismos pasos que en el tutorial de instalación de plugins en NetBeans). Como versión de Java EE usaré la 5 (pueden usar también la 6 si quieren, solo que en ese caso NetBeans no genera el archivo "web.xml" por default, y tendrán que crearlo a mano o usando el wizard correspondiente). De la misma forma, si quieren pueden modificar el context path de la aplicación (el context path es la ruta que se colocará en el navegador para acceder a nuestra aplicación desde nuestro servidor):



Presionamos el botón "Finish", con lo que veremos aparecer la página "index.jsp" en nuestro editor.



Ahora agregaremos la biblioteca "Struts2" que creamos hace un momento. Para esto hacemos clic derecho en el nodo "Libraries" del panel de proyectos. En el menú que aparece seleccionamos la opción "Add Library...":



En la ventana que aparece seleccionamos la biblioteca "Struts2" y presionamos "Add Library". Con esto ya tendremos los jars de Struts 2 en nuestro proyecto:



Lo siguiente que debemos hacer es configurar el filtro de Struts 2, para que todas las llamadas que se hagan a nuestra aplicación web pasen a través de este filtro. El filtro de Struts 2 es llamado "StrutsPrepareAndExecuteFilter". En versiones anteriores de Struts 2 se usaba un filtro llamado "FilterDispatcher" (es por eso que algunas veces hago referencia a este filtro usando ese nombre), sin embargo este ha sido marcado como deprecated y por lo tanto ya no debe ser usado.

Abrimos el archivo de configuración de nuestra aplicación web, el deployment descriptor conocido sencillamente como el archivo "web.xml".

Para la declaración del filtro de Struts 2, debemos usar el elemento "<filter>", dentro del cual debemos indicar un nombre para el filtro, por convención usamos el nombre "struts2", y la clase que implementa dicho filtro, que en nuestro caso es "org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter".

Después será necesario indicar qué peticiones pasarán por el filtro de Struts 2. Para eso usamos el elemento "<filter-mapping>". Aquí indicamos que queremos que TODAS las peticiones que se hagan al sitio pasen a través del filtro, usando el patrón de URL "/*".

La declaración y mapeo del filtro quedan de la siguiente forma:


<filter>
  <filter-name>struts2</filter-name>
  <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>

<filter-mapping>
  <filter-name>struts2</filter-name>
   <url-pattern>/*</url-pattern>
</filter-mapping>

Ahora modificaremos la página "index.jsp", para probar que la configuración funciona correctamente. En esta página colocaremos solo un texto que diga: "Bienvenido a Struts 2".

El siguiente paso es realizar la configuración de Struts 2. Existen dos formas de hacer esto: la primera es usando un archivo de configuración en XML, y la segunda es mediante anotaciones. Primero veremos cómo configurarlo usando un archivo de configuración XML.


Configuración de Struts 2 Usando un Archivo XML

Para esta configuración debemos crear un archivo llamado "struts.xml", que debe ser colocado en el paquete raíz de la aplicación. En este archivo se especifica la relación entre la URL de una petición, la clase Java que ejecutará la acción de la petición, y la página JSP que se encargará de mostrar la respuesta a la petición del usuario.

Para crear el archivo hacemos clic derecho sobre el nodo "Source Packages" en el panel de proyectos. En el menú contextual que nos aparece seleccionamos la opción "New -> XML Document":



Le damos como nombre del archivo "struts", el wizard se encargará de colocarle la extensión ".xml":



Presionamos el botón "Next" y en la siguiente pantalla seleccionamos la opción "Well-formed Document". Presionamos el botón "Finish" y tendremos un archivo XML, al cual debemos quitarle todo el contenido.

Dentro de este archivo se configuran algunas constantes que modifican, de una manera mínima, el comportamiento del filtro de Struts. También se configuran paquetes, que son conjuntos de acciones que podemos organizar debajo de un mismo namespace. Dentro de los paquetes podemos definir un conjunto de acciones, cada una de las cuales responderá a una petición. Y dentro de las acciones podemos definir resultados, que son las páginas o vistas a las que se enviará al usuario dependiendo del resultado de una acción (como ya habíamos dicho: existen muchos tipos de resultados, no solo páginas JSP, por ejemplo podemos hacer que un usuario descargue un archivo, o que visualice un reporte o una gráfica; incluso es posible redirigir a un usuario a otra acción).

Este archivo de configuración debe iniciar con el elemento "<struts>":


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
    "http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>

</struts>


Dentro de estas etiquetas se configuran los demás elementos.

Lo siguiente que haremos será configurar una constante que le indicará a Struts que nos encontramos en la etapa de desarrollo, con esto generará más mensajes de salida para que sepamos si estamos haciendo algo mal, y nos mostrará los errores de una forma más clara. Para esto establecemos la constante "struts.devMode" con un valor de "true":


<constant name="struts.devMode" value="true" />


Otra constante útil que podemos definir es "struts.configuration.xml.reload". Esta constante permite que cuando modifiquemos los archivos de configuración de Struts 2 no tengamos que volver a hacer un deploy de la aplicación para que los cambios tomen efecto:


<constant name="struts.configuration.xml.reload" value="true" />



Hasta ahora nuestro archivo de configuración debe verse de la siguiente manera:


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
    "http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
    <constant name="struts.devMode" value="true" />
    <constant name="struts.configuration.xml.reload" value="true" />
    
</struts>


Ahora definimos nuestro primer paquete, usando el elemento "<package>". Los paquetes nos permiten tener una configuración común para un conjunto de Actions. Por ejemplo, se pude definir el conjunto de interceptores que se aplican sobre ellos, resultados comunes, manejo de excepciones, etc.

Cada uno de los paquetes que declaremos debe tener un nombre y extender de algún otro paquete. Opcionalmente también pueden tener un namespace, el cual es un fragmento que deberá será agregado a la URL de la petición para hacer referencia a ese paquete. Por ejemplo, si definimos un paquete con el namespace "administracion", y dentro de este hay un Action cuyo nombre es "registro", para ejecutar ese Action deberemos hacer una petición a la siguiente URL:


http://servidor:puerto/aplicacion/administracion/registro.action


Cuando extendemos de otro paquete heredamos su configuración (de hecho podemos tener paquetes abstractos con una configuración general, y hacer que varios paquetes extiendan de estos). Como en este momento no tenemos otro paquete, este debe extender de "struts-default", que es el paquete base para la las aplicaciones de Struts 2. Como nombre de nuestro paquete colocaremos "demo-struts":


<package name="demo-struts" extends="struts-default">
</package>


Como podemos ver, no hemos indicado ningún namespace en nuestro paquete. Esto quiere decir que estamos usando el namespace por default (o sea ""). Cuando trabajamos con namespaces podemos usar principalmente 3 tipos: el namespace por default (como en este caso), el namespace raíz ("/") en cual en la URL de petición luce exactamente igual al namespace defult, o un namespace propio. Si invocamos un Action usando un namespace distinto al default (como en el ejemplo anterior "adminsitracion/registro.action") y Struts 2 no puede encontrar ningún Action que coincida con nuestra petición, en ese namespace, lo buscará en el namespace default. O sea, en el ejemplo anterior, si no encuentra el Action "registro" en el namespace "administracion", irá a buscarlo al namespace default.

Algo importante que hay que decir sobre los namespaces, es que estos NO se comportar como nombres de directorios. Esto quiere decir que si hacemos una petición a un Action en el namespace "administracion/usuarios", digamos: "adminsitracion/usuarios/registro.action" y Struts 2 no encuentra el Action "registro" en ese namespace, irá a buscarlo al namespace por default, y no a un namespace "adminsitracion".

Ahora mapearemos nuestro primer Action. Para esto usamos el elemento "<action>". Todas las acciones deben tener un nombre que debe ser único dentro de un namespace. Las acciones deben estar asociadas con una clase que será la que ejecute la acción adecuada. Si no indicamos una clase, Struts 2 usará una clase por default. Esta clase por default lo que hace es regresar siempre un resultado exitoso o "SUCCESS" de su ejecución. En este caso llamaremos a nuestra acción "index":


<action name="index">
</action>


Finalmente, debemos indicar el resultado de la ejecución de esta acción, con el elemento "<result>". Una acción puede tener varios resultados, dependiendo de la ejecución de la misma. Cada uno de estos resultados debe tener un nombre al que se hará referencia dentro de la acción para definir qué vista regresar al usuario. Se puede tener, por ejemplo, un resultado para el caso de una acción exitosa, otro para una situación de error, otra para cuando el usuario no ha ingresado todos los datos requeridos en un formulario, otra para cuando el usuario que ingresa es un administrador, otro para cuando es un usuario anónimo, etc.

Definimos que en el caso de una ejecución exitosa (success) se debe enviar al usuario a la página "index.jsp", de la siguiente forma (noten que la ruta de la página debe indicarse a partir de la raíz del directorio web):


<result name="success">/index.jsp</result>


El nombre por default de un result es "success", así que podemos dejar la línea anterior de la siguiente manera:


<result>/index.jsp</result>
Normalmente tratamos de usar los nombres de los result que se definen dentro de la interface Action ("success", "error", "input", etc.), para poder hacer uso de estas constantes cuando nos referimos a ellos, pero en realidad podemos darles el nombre que queramos (como "exito"o "fallo") y regresar estos valores en el método "execute".

Esto es todo, por lo que nuestro archivo de configuración queda de la siguiente forma:


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
    "http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>
    <constant name="struts.devMode" value="true" />
    <constant name="struts.configuration.xml.reload" value="true" />
  
    <package name="demo-struts" extends="struts-default">
        <action name="index">
            <result>/index.jsp</result>
        </action>
    </package>
</struts>


Ejecutamos la aplicación y entramos a la URL:

http://localhost:8080/struts2/index.action


Noten que estamos haciendo referencia al Action "index" mediante su nombre y agregando al final de este el postfijo "action". Struts 2 tiene configurados 2 postfijos que se usan por default para las acciones: "action" y "", por lo que también podemos hacer la petición a la siguiente URL:


http://localhost:8080/struts2/index


Si todo está bien configurado deberemos ver una pantalla como la siguiente:



Con este ejemplo pudimos ver algunos de los detalles de configuración de Struts 2, sin embargo la salida es muy sencilla. Hagamos algo un poco más interesante: haremos que el mensaje de la página sea obtenido desde un Action.

Primero crearemos un nuevo paquete. Hacemos clic derecho en el nodo "Source Packages", en el menú que aparece seleccionamos la opción "New -> Java Package...":



El nombre de mi paquete será "com.javatutoriales.struts2.configuracion.actions". Dentro de este paquete crearemos una nueva clase. Hacemos clic derecho en el paquete que acabamos de crear, en el menú que aparece seleccionamos la opción "New -> Java Class...":



El nombre de la clase será "SaludoAction". En esta clase colocaremos un atributo de tipo String que se llame mensaje. También proporcionaremos un getter para este atributo:


public class SaludoAction
{
    private String mensaje;

    public String getMensaje()
    {
        return mensaje;
    }
}


Ahora crearemos el método de negocio que realiza el proceso propio de la acción. Para esto Struts 2 siempre invoca un método llamado "excecute" (aunque hay forma de modificar esto en el archivo de configuración). Este método debe regresar un String, que indica el nombre de result que debe ser mostrado al usuario.

En este caso lo único que hará nuestro método "execute" es establecer el valor del mensaje:


public String execute()
{
    mensaje = "Bienvenido al mundo de Struts 2";
}


E indicaremos que se debe regresar el result cuyo nombre sea "success":


public String execute()
{
    mensaje = "Bienvenido al mundo de Struts 2";
        
    return "success";
}


Si se dan cuenta, estamos regresando el nombre del result usando una cadena literal ("success") esto parece sencillo en un principio, pero puede ser fuente de errores y dolores de cabeza si cometemos un error y escribimos, por ejemplo "succes", o "sucess". Como habíamos dicho antes, para simplificar esto (entre otras cosas que veremos más adelante) Struts proporciona una clase base de la que podemos extender. Esta clase es "ActionSupport", la cual implementa la interface "Action" que define las siguientes constantes para regresar los nombres de los results correspondientes:

  • ERROR
  • INPUT
  • LOGIN
  • NONE
  • SUCCESS

Modificamos nuestra clase para que quede de la siguiente forma:


public class SaludoAction extends ActionSupport
{
    private String mensaje;
    
    @Override
    public String execute()
    {
        mensaje = "Bienvenido al mundo de Struts 2";
        
        return SUCCESS;
    }
    
    public String getMensaje()
    {
        return mensaje;
    }
}


Ahora debemos modificar el archivo de configuración. En este caso agregaremos un Action el cual responderá al nombre "saludo", y la clase que se encargará de ejecutar la lógica de esta acción será "com.javatutoriales.struts2.configuracion.actions.SaludoAction". En caso de un resultado exitoso (el único que se tiene) se mostrará al usuario la página "saludo.jsp", de esta forma:


<action name="saludo" class="com.javatutoriales.struts2.configuracion.actions.SaludoAction">
    <result>/saludo.jsp</result>
</action>


Crearemos la página "saludo.jsp" en el directorio raíz de las páginas web. Hacemos clic derecho en el nodo "Web Pages". En el menú que se abre seleccionamos la opción "New –> JSP…":



Damos "saludo" como nombre de la página y presionamos el botón "Finish".

Dentro de la página usaremos las etiquetas de Struts para mostrar el valor correspondiente a la propiedad del objeto. Indicamos que usaremos la biblioteca de etiquetas (taglib) "/struts-tags". Por convención usaremos el prefijo "s" para hacer referencia a esta biblioteca:


<%@taglib uri="/struts-tags" prefix="s" %>


Dentro de esta biblioteca se encuentran contenidas todas las etiquetas de Struts 2. Tiene etiquetas control de flujo (if, iterator), de datos (a, bean, i18n), de formulario (checkbox, form, label, submit), para el manejo de componentes ajax (autocompleter, tree). En el sitio de struts pueden enontrar todas las etiquetas con las que cuenta Struts 2.

Usaremos una etiqueta de datos: "property". Dentro de esta etiqueta debemos indicar el nombre de la propiedad que queremos mostrar, en este caso "mensaje", usando su atributo "value":


<s:property value="mensaje" />


Nuestra página debe quedar de la siguiente forma:


<?xml version="1.0" encoding="UTF-8"?>
<%@taglib uri="/struts-tags" prefix="s" %>
<%@page pageEncoding="UTF-8" contentType="text/html;charset=UTF-8" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
    "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="es" >
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <title>JSP Page</title>
    </head>
    <body>
        <h1><s:property value="mensaje" /></h1>
    </body>
</html>


Ahora, cuando entremos a la siguiente URL:


http://localhost:8080/struts2/saludo.action


Debemos ver el mensaje que establecimos en el Action:



Aumentaremos aún más el grado de complejidad del ejemplo y haremos que la página nos muestre un mensaje saludando al usuario, este proporcionará su nombre, junto con su número de la suerte, a través de un formulario.

Creamos una nueva página con el formulario, a esta página la llamaremos "datos.jsp", y la colocaremos en el directorio raíz de las páginas web.

En esta página importaremos la biblioteca de etiquetas de Struts, como lo habíamos hecho anteriormente:


<%@taglib uri="/struts-tags" prefix="s" %>


Ahora crearemos un formulario con dos campos, uno para el nombre del usuario y otro para su número de la suerte. Para esto usaremos la etiqueta "form", la cual nos permite definir un formulario, y dentro de estas colocaremos los campos del mismo. En este formulario debemos colocar un atributo, "action", que indica el Action al que se deben enviar los datos para ser procesados, en este caso la acción se llamará "saludoUsuario", y lo crearemos en un momento:


<s:form action="saludoUsuario">
</s:form>


Dentro de estas etiquetas colocaremos tres componentes, dos campos de entrada de texto (texfields) y un botón para enviar la información (submit). Struts 2 proporciona etiquetas para cada componente que podemos colocar en un formulario. Además proporciona distintos temas que pueden aplicarse a los sitios o a componentes específicos (normal, xhtml, xhtml_css, y simple). Hablaremos más de esto un poco más adelante. Gracias al tema por default (xhtml) podemos colocar en las etiquetas información con respecto al formulario, como por ejemplo las etiquetas (labels) de los componentes, o indicar si estos datos son requeridos, como atributos de las etiquetas de Struts:


<s:form action="saludoUsuario">
    <s:textfield label="Nombre" name="nombre" />
    <s:textfield label="Número de la suerte" name="numero" />
    <s:submit value="Enviar" />
</s:form>


Podemos ver que en los campos de texto (los textfield) hemos colocado, en los atributos "name" correspondientes, los nombres "nombre" y "numero". Es con estos nombres que se hará referencia a las propiedades (o al menos a los setters) del Action que se encargará de procesar este formulario.

La página debe haber quedado de la siguiente forma:


<?xml version="1.0" encoding="UTF-8"?>
<%@page pageEncoding="ISO-8859-1" contentType="text/html;charset=UTF-8" %>
<%@taglib uri="/struts-tags" prefix="s" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
    "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="es" >
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
            <title>JSP Page</title>
    </head>
    <body>

        <s:form action="saludoUsuario">
            <s:textfield label="Nombre" name="nombre" />
            <s:textfield label="Número de la suerte" name="numero" />
            <s:submit value="Enviar" />
        </s:form>

    </body>
</html>


Ahora crearemos el Action que se encargará de procesar estos datos para saludar al usuario. Creamos una nueva clase Java, en el paquete "com.javatutoriales.struts2.configuracion.actions", llamada "SaludoUsuarioAction" y hacemos que extienda de "ActionSupport":


public class SaludoUsuarioAction extends ActionSupport
{

}


A esta clase le agregamos dos atributos, un String llamado "nombre" que contendrá el nombre del usuario, y un int llamado "numero" que contendrá el número de la suerte del usuario. También agregamos sus setters corresponientes:


public class SaludoUsuarioAction extends ActionSupport
{
    private String nombre;
    private int numero;

    public void setNombre(String nombre)
    {
        this.nombre = nombre;
    }

    public void setNumero(int numero)
    {
        this.numero = numero;
    }
}


Ahora agregamos un String, llamado "mensaje", que contendrá el mensaje que se regresará al usuario. Colocamos también un getter para esta propiedad:


private String mensaje;

public String getMensaje()
{
    return mensaje;
}


Lo siguiente que haremos es sobre-escribir el método "execute" estableciendo el mensaje del usuario:


@Override
public String execute() throws Exception
{
    mensaje = "Bienvenido " + nombre + " al munddo de Struts 2. Tu número de la suerte de hoy es " + numero;
        
    return SUCCESS;
}


La clase "SaludoUsuarioAction" completa queda de la siguiente forma:


public class SaludoUsuarioAction extends ActionSupport
{
    private String nombre;
    private int numero;
    private String mensaje;
    
    @Override
    public String execute() throws Exception
    {
        mensaje = "Bienvenido " + nombre + " al munddo de Struts 2. Tu número de la suerte de hoy es " + numero;
        
        return SUCCESS;
    }
    
    public String getMensaje()
    {
        return mensaje;
    }
    
    public void setNombre(String nombre)
    {
        this.nombre = nombre;
    }
    
    public void setNumero(int numero)
    {
        this.numero = numero;
    }
}


¿Por qué hemos colocado setters de unas propiedades y getters de otras? Bien, esto es porque algunas de las propiedades del action ("nombre" y "mensaje") serán establecidas por los interceptores. Estos valores son recibidos a través del formulario que creamos anteriormente. En el caso del atributo "mensaje" este será leído por el framework para presentar los datos en una página JSP.

En otras palabras, debemos proporcionar setters para las propiedades que serán establecidas por el framework, y getters para las propiedades que serán leídas.

Ahora haremos la página que mostrará el saludo al usuario. Agregamos una página llamada "saludoUsuario.jsp". En esta página indicaremos que haremos uso de la biblioteca de etiquetas de Struts 2, como lo hemos hecho anteriormente. También usaremos la etiqueta "property" para mostrar el mensaje que generamos en el action:

<%@taglib uri="/struts-tags" prefix="s" %>
y

<s:property value="mensaje" />


El último paso es configurar este Action en el archivo "struts.xml". Aquí indicaremos un Action llamado "saludoUsuario" que será procesado por la clase "SaludoUsuarioAction". También indicaremos un result que enviará al usuario a la página "saludoUsuario.jsp":


<action name="saludoUsuario" class="com.javatutoriales.struts2.configuracion.actions.SaludoUsuarioAction">
    <result>/saludoUsuario.jsp</result>
</action>


Esto es todo. Ahora iniciamos nuestra aplicación y entramos a la siguiente dirección:


http://localhost:8080/struts2/datos.jsp


Deberemos ver un formulario como el siguiente:



Como podemos ver, las etiquetas de Struts han generado un formulario y lo han acomodado para que se vea bonito :D. Si revisamos el código fuente de la página podremos ver que dentro del formulario hay una tabla para acomodar los componentes:


<form id="saludoUsuario" name="saludoUsuario" action="/struts2/saludoUsuario.action" method="post">
    <table class="wwFormTable">
        <tr>
            <td class="tdLabel">
                <label for="saludoUsuario_nombre" class="label">Nombre:</label>
            </td>
            <td>
                <input type="text" name="nombre" value="" id="saludoUsuario_nombre"/>
            </td>
        </tr>

        <tr>
            <td class="tdLabel">
                <label for="saludoUsuario_numero" class="label">Número de la suerte:</label>
            </td>
            <td>
                <input type="text" name="numero" value="" id="saludoUsuario_numero"/>
            </td>
        </tr>

        <tr>
            <td colspan="2">
                <div align="right"><input type="submit" id="saludoUsuario_0" value="Enviar"/></div>
            </td>
        </tr>

    </table>
</form>


Aunque el formulario se ve muy bien de esta forma, el uso de tablas para el acomodo de los componentes se considera una mala práctica, veremos cómo hacerlo de forma correcta en algún otro tutorial ^_^.

Ahora, cuando llenemos los datos del formulario y hagamos clic en el botón enviar el Action procesará nuestros datos y nos enviará la respuesta correspondiente, por lo que deberemos ver la siguiente pantalla:



Como podemos ver, los datos fueron establecidos correctamente y el mensaje mostrado es correcto. También podemos ver que en este caso ocurre algo interesante, el atributo "numero" que establecimos como tipo "int" fue tratado como eso, como un número, no como un String, como ocurre normalmente en los Servlets. Esto es debido a que los interceptores se encargan de transformar los datos que reciba al tipo indicado. Lo mismo ocurre con valores booleanos, arreglos, fechas, archivos, etc. Sin que tengamos que hacer la conversión manual.

Ahora que veremos cómo configurar nuestra aplicación usando anotaciones.


Configuración de Struts 2 Usando Anotaciones

Cuando usamos anotaciones para realizar la configuración de una aplicación con Struts 2, necesitamos hacer uso de un plugin, el plugin "convention" (el cual se encuentra en el jar "struts2-convention-plugin-2.2.3" que agregamos para la biblioteca de "Struts2Anotaciones"). Este plugin proporciona la característica de poder crear una aplicación Struts, siguiendo una serie de convenciones de nombres tanto para los results, los Actions, en fin, para todo lo relacionado con Struts. Esto sin necesidad de crear ningún archivo de configuración ni usar ninguna anotación.

Veremos el uso de este plugin en un tutorial exclusivo, un poco más adelante, por ahora debemos crear un nuevo proyecto web en el NetBeans siguiendo los pasos anteriores, hasta antes de llegar al título que dice "Configuración de Struts 2 Usando un Archivo XML" ^_^. O sea, debemos tener creado un proyecto web, configuramos el filtro "struts2" en el archivo "web.xml" y agregamos, en vez de la biblioteca "Stuts2" que usamos para configurar usando archivos XML, la biblioteca "Struts2Anotaciones".

Una vez llegado a este punto, creamos un paquete en nuestra aplicación, en mi caso será "com.javatutoriales.struts2.configuracion.acciones", y será en este paquete en el que coloquemos todas las acciones que creemos en el tutorial.

Dentro de este paquete crearemos una clase llamada "SaludoAction", esta clase puede extender de "ActionSupport", aunque como vimos no es necesario. Yo si extenderé de de "ActionSupport":


public class SaludoAction extends ActionSupport
{
}


A esta clase le agregaremos un atributo, de tipo String, llamado "mensaje", con su getter correspondiente:


public class SaludoAction extends ActionSupport
{
    private String mensaje;

    public String getMensaje()
    {
        return mensaje;
    }
}


Lo siguiente es sobre-escribir el método "execute" para que genere un mensaje de saludo al usuario:


@Override
public String execute()
{
    mensaje = "Hola Mundo de Struts 2";

    return SUCCESS;
}


La clase "SaludoAction" hasta el momento se ve de esta forma:


public class SaludoAction extends ActionSupport
{
    private String mensaje;

    @Override
    public String execute()
    {
        mensaje = "Hola Mundo de Struts 2";

        return SUCCESS;
    }

    public String getMensaje()
    {
        return mensaje;
    }
}


Ahora configuraremos este Action. Para eso tenemos dos formas de hacerlo. Ambas son casi iguales, con diferencias muy sutiles. En la primera debemos colocar las anotaciones a nivel de la clase. Las anotaciones son muy claras, y representan los elementos importantes del archivo XML.

Lo primero que debemos hacer es indicar el namespace para la acción, usando la anotación "@Namespace":


@Namespace(value="/")
public class SaludoAction extends ActionSupport


Ahora debemos usar la anotación "@Action" para indicar el nombre de esta acción, usando su atributo "value":


@Namespace(value="/")
@Action(value="saludo")
public class SaludoAction extends ActionSupport


Lo último que debemos hacer es configurar los results para este Action. Esto lo hacemos usando el atributo "results" de la anotación "@Action" y la anotación "@Result". Dentro de esta última colocaremos el nombre del result, en su atributo "name", y la ubicación de la página que mostrará el resultado apropiado, usando su atributo "location":


@Namespace(value="/")
@Action(value="saludo", results={@Result(name="success", location="/saludo.jsp")})
public class SaludoAction extends ActionSupport


Esto es todo para la primera forma de configuración usando anotaciones. La segunda forma es muy parecida, solo que esta vez colocamos las anotaciones a nivel del método "execute". Si lo hacemos así no podemos usar la anotación "@Namespace", pero podemos agregar el namespace directamente en el atributo "value" de la anotación "@Action":


@Override
@Action(value="/saludo", results={@Result(name="success", location="/saludo.jsp")})
public String execute()
{
    mensaje = "Hola Mundo de Struts 2";

    return SUCCESS;
}


En lo personal prefiero la primera forma así que será esta la que usaremos en los tutoriales :D. La clase "SaludoAction" anotada queda de la siguiente forma:


@Namespace(value="/")
@Action(value="saludo", results=@Result(name="success", location="/saludo.jsp"))
public class SaludoAction extends ActionSupport
{
    private String mensaje;

    @Override
    public String execute()
    {
        mensaje = "Hola Mundo de Struts 2";

        return "success";
    }

    public String getMensaje()
    {
        return mensaje;
    }
}


Ahora podemos ejecutar nuestra aplicación y entrar a la siguiente dirección:


http://localhost:8080/strutsanotaciones/saludo.action


Con lo que deberemos ver la siguiente pantalla:



Esto indica que la configuración ha funcionado correctamente.

Como podemos ver, no es necesario dar ninguna indicación al filtro de Struts 2 de en dónde se encuentran las clases anotadas. Esto en realidad es un poco engañoso, ya que si, por ejemplo, cambiamos el nombre del paquete de "struts2" a alguna otra cosa, como "aplicacion", al volver a ejecutar nuestra aplicación obtendremos el siguiente error:



Esto ocurre porque el plugin convention usa, como habíamos dicho, una convención de nombres para saber en dónde se encuentra cierto contenido. En el caso de los Actions, el plugin busca todas las clases Action que implementen la interface "Action" o cuyos nombres terminen con la palabra "Action" y que se encuentren en paquetes específicos. Estos paquetes son encontrados por el plugin convention usando una cierta metodología de búsqueda. Primero, el plugin busca los paquetes cuyos nombres sean "struts", "struts2", "action" o "actions". Cualquier paquete que coincida con estos nombres es considerado el paquete raíz de los Action por el plugin convention. Después, el plugin busca todas las clases en este paquete, y en sus sub-paquetes y determina si las clases implementan la interface "Action" o su nombre termina con "Action".

Debido a que en este caso, hemos cambiado el nombre del paquete que el plugin usaba para saber en dónde localizar los Actions, ya no puede encontrarlos. ¿Qué podemos hacer en este caso? Afortunadamente para esto existe un mecanismo que nos permite indicarle al plugin convention dónde puede encontrar los Actions de nuestra aplicación. Para este fin debemos agregar una constante: "struts.convention.action.packages", y su valor será una lista de los paquetes en los que se encuentran los Actions.

Ya que en esta ocasión no tenemos un archivo de configuración, debemos colocar esta constante como un parámetro del filtro de "struts2", en el archivo "web.xml".

Debido a que en mi caso el paquete en el que se encuentran las clases es "com.javatutoriales.aplicacion.configuracion.acciones", mi configuración del filtro queda de la siguiente forma:


<filter>
    <filter-name>struts2</filter-name>
    <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
    <init-param>
        <param-name>struts.convention.action.packages</param-name>
        <param-value>com.javatutoriales.aplicacion.configuracion.acciones</param-value>
    </init-param>
</filter>


El resto de la configuración queda como la teníamos.

Ahora al ejecutar la aplicación, deberemos ver nuevamente la siguiente salida:



La cual nos muestra que todo funciona correctamente ^_^.

El ejemplo del formulario trabaja exactamente de la misma forma que en el caso del archivo de mapeo, así que no lo explicaré, pero lo colocaré en los archivos de descarga al final del tutorial.

Esto es todo lo que respecta a este tutorial. En los siguientes veremos un poco más de las opciones que nos proporciona Struts 2 para trabajo con formularios, veremos cómo crear nuestros propios interceptores, y las opciones que nos ofrece el lenguaje OGNL.

Como siempre no olviden dejar sus dudas, comentarios y sugerencias.

Saludos y gracias ^_^

Descarga los archivos de este tutorial desde aquí:


Entradas Relacionadas: