sábado, 7 de abril de 2012

Struts 2 - Parte 5: Tipos de Results

Cada vez que un Action termina su ejecución, se muestra un resultado al usuario. Estos resultados pueden ser de muchos tipos y tener muchos significados. El tipo más común de resultado es mostrar al usuario una nueva página web cierta información, pero ¿si quisiéramos hacer otra cosa? ¿Qué ocurre si queremos regresar un archivo como texto plano? ¿Y cuando queremos regresar un archivo binario? ¿Y su necesitamos redirigir la petición a otro Action o enviar al usuario a otra página?

Los casos anteriores también son muy comunes cuando desarrollamos una aplicación web.

Struts 2 ofrece una gran variedad de tipos de Results para manejar estas y otras situaciones y que aprenderemos cómo usar en este tutorial.

Primero lo primero, aunque los hemos estado usando a lo largo de esta serie de tutoriales, nunca hemos definido de una forma clara qué es un Result. Cuando el método "execute" de un Action termina de ejecutarse este siempre regresa un objeto de tipo String. El valor de ese String se usa para seleccionar un elemento de tipo "<result>", si trabajamos con archivos de configuración en XML, o "@Result", si trabajamos con anotaciones. Dentro del elemento se indica qué tipo de Result se quiere regresar al usuario (enviar un recurso, redirigirlo a otro Action, un archivo de texto plano, etc.), el recurso que se quiere regresar, algunos parámetros adicionales que requiera el Result y, lo más importante, el nombre que tendrá este Result, en donde este nombre es precisamente lo que se indica con la cadena que es regresada de la ejecución del método "execute".

Cada result tiene un nombre que debe ser único para un Action y es en base a este nombre que Struts 2 sabrá qué es lo que debe mostrarle al usuario. La interface "Action", que es implementada por la clase "ActionSupport", que hemos estado utilizando como base para nuestros Actions, proporciona un conjunto de constantes que contienen los nombres de los results más comunes para que podamos usarlos dentro de nuestros Actions. El identificador de la constante es el mismo que el valor que contiene (usado las respectivas convenciones) para que sea fácil identificar cuándo debemos usar cada uno:


String SUCCESS = "success";
String NONE    = "none";
String ERROR   = "error";
String INPUT   = "input";
String LOGIN   = "login";


Claro que no es obligatorio que regresemos alguna de estas contantes en el método "execute", podemos usar cualquier cadena que queramos, siempre y cuando exista un "<result>" con ese nombre para nuestro Action. Por ejemplo en caso de algún valor incorrecto introducido por el usuario podríamos regresar la cadena "fail" ^^; o en caso de que dependiendo del perfil que tenga el usuario que ingresa a la aplicación podríamos redirigirlo a una página apropiada para ese perfil regresando el nombre del perfil, por ejemplo "operador", "administrador", "auditor", etc.

Hasta ahora solo hemos utilizado los valores "SUCCESS" e "INPUT" (cuando trabajamos con formularios) en nuestros results, pero el valor de cada una de las constantes anteriores representa una situación que puede ocurrir durante la ejecución de nuestro Action:

  • "success" - Indica que todo ha salido correctamente (validaciones, y el proceso de lógica en general) durante la ejecución del método, por lo que el usuario puede ver la salida esperada.
  • "error" - Indica que alguna cosa dentro del método ha salido mal. Puede ser que algún cálculo no pueda realizarse, o que algún servicio externo que estemos utilizando no esté disponible en ese momento o haya lanzado alguna excepción, o cualquier otra cosa que se les ocurra que pueda salir mal.
  • "input" - Indica que algún campo proporcionado en un formulario es incorrecto. Puede ser que el valor no sea del tipo esperado, o no cumpla con el formato o rango requerido, o cosas más sofisticadas como que un valor esté repetido (digamos que alguien está tratando de usar un nombre de usuario que ya está siendo usado por otra persona).
  • "login" - Indica que el recurso al que el usuario está intentado acceder solo está disponible para usuarios registrados del sitio y por lo tanto debe loguearse primero. Para poder usar este result de forma correcta debemos crear un result global en la configuración del sitio. Veremos cómo hacer esto al final del tutorial.
  • "none" - Este es un result de un tipo especial ya que le indica a Struts 2 que no debe enviar al usuario a ningún lugar (o en términos formales que el procesamiento del resultado sea cancelado). Esto es usado cuando el Action maneja el proceso de regresar el resultado al usuario usando, por ejemplo, de forma directa los objetos "HttpServletResponse" o "ServletOutputStream".


Como podrán imaginar un Action puede tener más de un result y estos pueden ser de distintos tipos, uno para cada situación que necesite manejar (siempre y cuando cada uno tenga un nombre único para ese Action).

Struts 2 maneja por default 11 tipos de results, aunque los plugins (como por ejemplo el de jasperreports) pueden definir sus propios tipos. Los results que por default maneja Struts 2 son:

  • "dispatcher" - Este es el result más usado y el default. Envía como resultado una nueva vista, usalmente una jsp.
  • "redirect" - Le indica al navegador que debe redirigirse a una nueva página, que puede estar en nuestra misma aplicación o en algún sitio externo, y por lo tanto este creará una nueva petición para ese recurso.
  • "redirectAction" - Redirige la petición a otro Action de nuestra aplicación. En este caso se crea una nueva petición hacia el nuevo Action.
  • "chain" - Al terminarse la ejecución del Action se invoca otro Action. En este caso se usa la misma petición para el segundo Action, el cual se ejecuta de forma completa, con todo su stack de interceptores y sus results (podemos crear cadenas de cuantos Actions queramos).
  • "stream" - Permite enviar un archivo binario de vuelta al usuario.
  • "plaintext" - Envía el contenido del recurso que indiquemos como un texto plano. Típicamente se usa cuando necesitamos mostrar una JSP o HTML sin procesar
  • "httpheader" - Permite establecer el valor de la cabecera HTTP de código de estatus que se regresará al cliente. Así por ejemplo podemos usarlo para enviar un error al cliente (estatus 500), un recurso no encontrado (404), un recurso al que no tiene acceso (401), o por el que requiere pagar (402).
  • "xslt" - Se usa cuando el resultado generará un XML será procesado por una hoja de transformación de estilos para generar la vista que se mostrará al cliente.
  • "freemarker" - Para integración con FreeMarker
  • "velocity" - Para integración con Velocity
  • "tiles" - Para integración con Tiles


En este tutorial veremos cómo funcionan los primeros 7 tipos de results (desde "dispatcher" hasta "httpheader").

Suficiente teoría, comencemos con la práctica. Comencemos creando un nuevo proyecto web en NetBeans, 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á "Struts2Results". 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. 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. Presionamos el botón "Finish", con lo que veremos aparecer la página "index.jsp" en nuestro editor:



Una vez creado nuestro proyecto debemos agregar la biblioteca "Struts2" que creamos en el primer tutorial de la serie. 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.

A continuación configuramos el filtro "struts2" en el deployment descriptor. Abrimos el archivo "web.xml" y colocamos el siguiente contenido, como se explicó en el primer tutorial de la serie:


<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 crearemos un nuevo archivo llamado "struts.xml" en el paquete default de código fuente de nuestra aplicación:



Este archivo contendrá la configuración de Struts 2 de nuestra aplicación. Colocaremos la configuración inicial, con un par de contantes para indicar que nos encontramos en modo de desarrollo y que cualquier cambio que hagamos en el archivo de configuración debe recargarse de inmediato, y un paquete inicial:


<?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="struts-results" extends="struts-default">
        <action name="index">
            <result>/index.jsp</result>
        </action>
    </package>
</struts>


Creamos un nuevo paquete en el nodo "Source Packages" llamado "com.javatutoriales.results.actions" que será donde colocaremos las clases Action de nuestra aplicación.

Ya con la configuración inicial comencemos viendo el primer tipo de result:


Result "dispatcher"

Lo primero que haremos será crear un directorio llamado "dispatcher" en el nodo de "Web Pages" del proyecto:



En este nuevo directorio crearemos una nueva JSP llamada "index.jsp" que contendrá un sencillo formulario con solo dos campos, un campo de texto para ingresar nuestro nombre, y un combo-box, lista desplegable, select o cómo quiere que llamen, para seleccionar su lenguaje de programación favorito ;). Este formulario será procesado por un Action llamado "dispatcher". No olviden que debemos indicar que haremos uso de las etiquetas de Struts 2 con el taglib correspondiente:


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


El formulario queda de la siguiente forma:


<s:form action="dispatcher">
    <s:textfield name="nombre" label="Nombre" />
    <s:select name="lenguaje" label="Lenguaje de Programacion" list="{'Java', 'PHP', '.Net'}" />
    <s:submit value="Enviar" />
</s:form>


Ahora crearemos, en nuestro paquete "actions" una nueva clase Java que extenderá de "ActionSupport", el nombre de esta clase será "DispatcherResultAction":


public class DispatcherResultAction extends ActionSupport
{
    
}


En esta clase colocaremos un par de atributos de tipo String, con sus setters y getters, para recibir los parámetros del formulario que acabamos de crear:

    
    private String nombre;
    private String lenguaje;

    public String getLenguaje()
    {
        return lenguaje;
    }

    public void setLenguaje(String lenguaje)
    {
        this.lenguaje = lenguaje;
    }

    public String getNombre()
    {
        return nombre;
    }

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


Ahora sobre-escribiremos el método "execute" para realizar una simple validación: en el caso de que el lenguaje de programación seleccionado por el usuario no sea Java, vamos a enviar al usuario a una página de error ^_^:


@Override
public String execute() throws Exception
{
    if(!"Java".equals(lenguaje))
    {
        return ERROR;
    }
        
    return SUCCESS;
}


Como podemos ver en el código anterior, estamos haciendo uso de la constantes definidas en el interface "Action", la cual es implementada por la clase "ActionSupport" para hacer referencia a los nombres de los results.

Ahora crearemos dos nuevas JSPs en el directorio "dispatcher" una para mostrar el resultado en caso de que todo haya salido bien y una para mostrar el mensaje de error. La primer JSP que crearemos se llamará "exito.jsp" y como pueden imaginarse es la que mostrará los resultados en caso de que todo sea correcto.

En esta página primero indicamos que haremos uso de las etiquetas de Struts 2, con el mismo taglib que usamos hace un momento, además colocaremos como único contenido el siguiente mensaje:


Bienvenido <s:property value="nombre" /> al mundo de la programación Java.


Donde estamos usando la etiqueta "<s:property>" para mostrar el nombre del usuario que ha quedado almacenado en la propiedad "nombre" de nuestro Action, y que a su vez se encuentra en el "ValueStack" (pueden encontrar una explicación más clara en el segundo tutorial de la serie).

Ahora vamos a la página "error.jsp", en ella también indicaremos que haremos uso de la biblioteca de etiquetas de Struts 2, y colocaremos el siguiente mensaje:


Lo siento <s:property value="name" />, este es un sitio exclusivo para programadores Java, pide perdón y retírate u_u


Así de directo ^_^

Ahora que tenemos nuestras dos páginas vamos a configurar ambas en los results para nuestro Action, lo haremos con archivos de mapeo en XML, para los que trabajen con anotaciones, las opciones son las mismas.

En el archivo "struts.xml" agregamos un elemento "<action>" que tendrá como nombre "dispatcher" y que esté implementado por la clase "com.javatutoriales.results.actions. DispatcherResultAction":


<action name="dispatcher" class="com.javatutoriales.results.actions.DispatcherResultAction">
</action>


Dentro de este elemento "<action>" definiéremos dos elementos "result" de tipo "dispatcher", uno para cada el resultado "success" y otro para el "error":


<result name="success" type="dispatcher">    
</result>
<result name="error" type="dispatcher">    
</result>


Cada uno de los tipos de results pueden recibir varios parámetros. Estos parámetros son indicados usando el elemento "<param>", para cuál parámetro queremos establecer se usa el atributo "name" de este elemento. El valor del parámetro se coloca como contenido de este elemento. Por ejemplo para establecer un parámetro llamado, por ejemplo, "tiempo" a "15" segundos lo haríamos de la siguiente forma:


<param name="tiempo">15</param>


En el caso de los results de tipo "dispatcher" pueden recibir dos parámetros: "location" y "parse". "location" indica dónde se encuentra el recurso que se enviará al usuario cuando se termine la ejecución del Action (usualmente una JSP). No se preocupen por el otro atributo porque casi no se usa (aún la explicación de lo que hace es un poco complicada ^_^).

Así que indicaremos, usando el parámetro "location" cuáles son las JSPs que deben regresar en cada caso:


<result name="success" type="dispatcher">
    <param name="location">/dispatcher/exito.jsp</param>
</result>
            
<result name="error" type="dispatcher">    
    <param name="location">/dispatcher/error.jsp</param>
</result>


Si ahora ejecutamos nuestra aplicación y entramos en la siguiente dirección:


http://localhost:8080/results/dispatcher/index.jsp


Debemos ver el siguiente formulario:



Al colocar nuestro nombre, seleccionar "Java" como nuestro lenguaje de programación y enviar nuestro formulario, veremos la siguiente salida:



Si seleccionamos otro lenguaje de programación veremos la siguiente salida:



Como podemos ver, la validación se realiza de forma correcta y nos envía al result adecuado en cada uno de los casos que seleccionamos.

Ahora regresemos a la configuración de los results para explicar algunas cosas interesantes.

En el primer tutorial de la serie dijimos que una de las ventajas que nos da Struts 2 es que nos proporciona muchos valores por default que son bastante útiles. El caso de los Actions es un ejemplo perfecto de los valores por default. Si regresamos a la definición básica de los results en XML veremos que tenemos lo siguiente:


<result name="success" type="dispatcher">
    <param name="location">/dispatcher/exito.jsp</param>
</result>
            
<result name="error" type="dispatcher">    
    <param name="location">/dispatcher/error.jsp</param>
</result>


Podemos ver que en realidad la configuración es muy corta y muy sencilla; pero imaginen que en vez de dos results, necesitáramos 10, y lo mismo para cada uno de los Actions de nuestra aplicación... ya no suena tan bien ¿cierto?

Bueno, el enunciado anterior puede ser un poco exagerado, pero sirve para ilustrar el deseo de querer simplificar un poco las cosas.

Lo primero que hay que saber es que cada uno de los tipos de results tienen siempre un parámetro por default. Si solo vamos a utilizar un parámetro en nuestro result, y ese parámetro es el parámetro por default entonces podemos omitir el elemento "<param>" y colocar directamente el valor dentro del elemento "<result>". En el caso del result de tipo "dispatcher" el parámetro por default es "location", por lo que podemos simplificar los dos elementos anteriores de la siguiente forma:


<result name="success" type="dispatcher">/dispatcher/exito.jsp</result>
<result name="error" type="dispatcher">/dispatcher/error.jsp</result>


Podemos ver que con esto la configuración ya ha quedado mucho más sencilla.

Otro valor por default útil que tenemos es el caso del atributo "type" del elemento "<result>". Por default el tipo de result es "dispatcher", por lo que en las definiciones anteriores podemos omitir este atributo. Esto es útil ya que en nuestras aplicaciones, en la mayoría de los casos nuestros results serán de tipo "dispatcher". Por lo tanto las definiciones anteriores quedan de la siguiente forma:


<result name="success">/dispatcher/exito.jsp</result>
<result name="error">/dispatcher/error.jsp</result>


Podemos ver que cada vez queda más corta la declaración de nuestros results ^_^.

Para terminar eliminaremos un valor más. También el atributo "name" tiene un valor por default: "success". Esto quiere decir que si vamos a definir el result para el caso "success", podemos poner un result sin nombre (solo uno por Action). Si nuestro result tiene otro nombre, si debemos colocarlo de forma explícita. Con este nuevo cambio la definición anterior queda de la siguiente forma:


<result>/dispatcher/exito.jsp</result>
<result name="error">/dispatcher/error.jsp</result>


Como podemos ver, gracias a los valores por default proporcionados por Struts 2, pasamos de esto:


<result name="success" type="dispatcher">
    <param name="location">/dispatcher/exito.jsp</param>
</result>
            
<result name="error" type="dispatcher">    
    <param name="location">/dispatcher/error.jsp</param>
</result>


A esto:


<result>/dispatcher/exito.jsp</result>
<result name="error">/dispatcher/error.jsp</result>


Con lo que no solo nos ahorramos unas cuantas líneas de código, sino que las definiciones son más fáciles de leer.

La definición del Action queda de la siguiente forma:


<action name="dispatcher" class="com.javatutoriales.results.actions.DispatcherResultAction">
    <result>/dispatcher/exito.jsp</result>
    <result name="error">/dispatcher/error.jsp</result>
</action>


Si volvemos a ejecutar nuestra aplicación debemos ver exactamente la misma salida. Podemos ver con esto que los valores por default proporcionados en todos los elementos de Struts 2 pueden ahorrarnos muchas líneas de código. En cada uno de los results les diré cuál es el parámetro por default.

Una última cosa importante que hay que aprender desde el inicio acerca de los results es que algunos parámetros (por lo regular los más importantes) se pueden establecer de forma dinámica usando una expresión en OGNL.

Vemos un ejemplo para ver cómo podemos indicar cuál será la ubicación a la que debe enviarse al usuario en caso de un error. Para esto debemos indicar en el archivo "struts.xml" que este parámetro será recibido usando una expresión, un parámetro llamado "ubicacion":


<result name="error">${ubicacion}</result>


Ahora modificaremos un poco nuestra clase "DispatcherResultAction". Primero tendremos que agregar un getter para una propiedad llamada "ubicacion". Struts 2 usará este getter cuando busque el valor para la expresión "${ubicacion}". Este getter debe regresar un String que indicará, en este caso, la ubicación a la que se enviará al usuario. El valor que regrese el método puede ser una cadena estática o generada de forma dinámica. En este caso regresaremos la ubicación de manera estática:


public String getUbicacion()
{
    return "/dispatcher/error.jsp";
}


Al ejecutar nuevamente nuestra aplicación y seleccionemos un lenguaje que no sea Java, deberemos volver a ver nuestra página de error normal.

Casi todos los parámetros de los results puedes ser establecidos de forma dinámica usando expresiones (con excepción de los que indican si los parámetros pueden aceptar expresiones ^^, los parámetros llamados "parse"), esto nos da una gran ventaja para que nuestra aplicación sea realmente dinámica.

Ahora veamos el siguiente tipo de result:


Result "redirect"

Este result le indica al navegador que debe dirigirse hacia otro recurso, de nuestra aplicación o algún lugar externo a nuestro sitio. Esto termina con la ejecución de la petición actual del cliente. Muy útil cuando queremos enviar al usuario a otro sitio como Google o algún otro servicio de nuestra empresa.


Este result tiene tres parámetros:
  • location (default): indica la ubicación a donde irá el resultado (puede ser una página dentro de nuestro sitio o afuera de él).
  • parse: indica si el parámetro "location" (el anterior) puede ser obtenido a través de una expresión en OGNL , logrando que la ubicación se pueda establecer de forma dinámica. Por default es verdadero.
  • anchor: se puede especificar un ancla para el resultado (un ancla es un lugar en una página html al que se le ha dado un nombre especial, único en el documento y que nos permite enviar al navegador del usuario a ese punto específico). Esta parámetro es opcional. Sobre este parámetro, aunque en la documentación oficial no lo dice, parece que sólo funciona cuando redireccionamos a otra parte de nuestro mismo sitio.


Para ver un ejemplo de este result en acción primero agregaremos un directorio llamado "redirect" en el nodo "Web Pages":



Dentro de este directorio crearemos una nueva JSP llamada "index". Como en este caso lo único que interesa es llamar al Action que regresará el result de tipo "redirect", que crearemos en un momento. Lo único que colocaremos en esta página será una liga, usando la etiqueta "<s:a>" para invocar directamente un Action llamado "redirect":


<s:a action="redirect">Redireccionar</s:a>


Ahora crearemos, en el paquete "actions" una nueva clase llamada "RedirectResultAction" que extienda de "ActionSupport":


public class RedirectResultAction extends ActionSupport
{    
}


Como en este caso no recibimos parámetros o algo por el estilo, lo único que necesitaremos hacer es sobre-escribir el método "excute" del Action para que regrese el valor "SUCCESS":


@Override
public String execute() throws Exception
{
    return SUCCESS;
}


Lo dejaremos solamente así ya que no nos interesa, en este caso, realizar ningún tipo de procesamiento para la entrada.

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


public class RedirectResultAction extends ActionSupport
{
    @Override
    public String execute() throws Exception
    {
        return SUCCESS;
    }   
}


Ahora, en el archivo "struts.xml" agregaremos la siguiente definición para nuestro Action:


<action name="redirect" class="com.javatutoriales.results.actions.RedirectResultAction">            
</action>


Agregaremos un único result, que será de tipo "redirect". Como el parámetro por default para este result es "location" (que indica a dónde será redirigido el usuario), podemos indicarlo como valor del elemento "<result>"; en este caso redirigiremos al usuario a la primer página que creamos, el "index.jsp" del directorio "dispatcher":


<result type="redirect">/dispatcher/index.jsp</result>


Recuerden que cuando no colocamos un nombre al result por default el result se llama "success". La configuración de nuestro Action queda de la siguiente forma:


<action name="redirect" class="com.javatutoriales.results.actions.RedirectResultAction">   
    <result type="redirect">/dispatcher/index.jsp</result>
</action>


Nuestro ejemplo ya está listo, así que ahora ejecutamos nuestra aplicación y entramos en la siguiente dirección:


http://localhost:8080/results/redirect/index.jsp


Deberemos ver la siguiente página:

br />
En donde lo único que hay es la liga que nos enviará al Action "redirec". Al hacer clic en esa liga seremos enviados al Action correspondiente, el cual nos enviará a su vez a la página "/dispatcher/index.jsp", por lo que terminaremos viendo el primer formulario:



Ahora veremos cómo redireccionar a una página que no se encuentre en nuestro sitio. Para esto crearemos una nueva clase, en el paquete "actions", llamada "RedirectExternoResultAction" que, como podrán imaginarse, extenderá de "ActionSupport":


public class RedirectExternoResultAction extends ActionSupport
{
}


Como esta clase tampoco hará nada especial, solo sobre-escribiremos su método "execute" para que regresa un valor de "success", usando la constante apropiada:


public class RedirectExternoResultAction extends ActionSupport
{
    @Override
    public String execute() throws Exception
    {
        return SUCCESS;
    }
}


Ahora iremos al archivo de configuración "struts.xml" y declaramos un nuevo elemento "<action>", cuyo nombre sea "redirect-externo" y la clase que lo implemente sea nuestra clase "RedirectExternoResultAction":


<action name="redirect-externo" class="com.javatutoriales.results.actions.RedirectExternoResultAction">            
</action>


A continuación agregaremos un elemento "<result>", de tipo "redirect", en cuyo valor colocaremos cualquier página de un sitio externo, yo elegiré una al azar que será "http://www.javatutoriales.com/2011/10/struts-2-parte-3-trabajo-con.html", así que el result quedará definido de la siguiente forma:


<result type="redirect">http://www.javatutoriales.com/2011/10/struts-2-parte-3-trabajo-con.html</result>


Y el mapeo de nuestro Action de la siguiente forma:


<action name="redirect-externo" class="com.javatutoriales.results.actions.RedirectExternoResultAction">
    <result type="redirect">http://www.javatutoriales.com/2011/10/struts-2-parte-3-trabajo-con.html</result>
</action>


Agregaremos una liga a este Action en la página "index.jsp" del directorio "redirect" para poder invocar a este Action:


<s:a action="redirect-externo">Redireccionar Externo</s:a>


Ahora ejecutamos la aplicación y entramos en la siguiente dirección:


http://localhost:8080/results/redirect/index.jsp


En la que veremos los dos enlaces a los Actions de redirección:



Hacemos clic en el enlace que acabamos de agregar, y en ese momento deberemos ser re-dirigidos a la página que hayamos indicado:



Si están viendo la página que indicaron en el elemento "<result>" quiere decir que todo ha funcionado correctamente ^_^.

Ahora veremos cómo funciona el siguiente tipo de result: "redirectAction"


Result "redirectAction"

Este tipo de result lo podemos ver como una forma especializada del result anterior, en donde el navegador del usuario es redirigido y ejecuta el Action que le especifiquemos. Nos permite especificar qué parámetros queremos enviar a este Action, desde el archivo de configuración, los cuales pueden ser parámetros estáticos o dinámicos (en realidad podemos hacer esto con todos los results, pero este es el que me parece más claro para mostrar esta característica).

Este result tiene tres parámetros:

  • actionName (default): El nombre del Action al que seremos redirigidos.
  • namespace: El namespace al que pertenece el Action. Si no lo indicamos, se usa por default el namespace actual.
  • supressEmptyParameters: Evita que los parámetros que estén vacios sean enviados al siguiente Action. Su valor por default es "false", o sea que los parámetros vacios si serán enviados.


Hagamos un ejemplo. Primero creamos un nuevo directorio, en las páginas web del proyecto, llamado "redirectAction":



Dentro de este directorio creamos una página llamada "index.jsp". Por el momento dejaremos esta página tal como está y comenzaremos a crear nuestro Action.

Agregamos una nueva clase llamada "RedirectActionAction" dentro del paquete "actions". Esta clase debe exender de "ActionSupport":


public class RedirectActionAction extends ActionSupport
{
}


Al igual que en el ejemplo anterior, esta clase será muy simple: no tendrá atributos y sólo sobre-escribiremos su método "execute" para regresar el valor de la constante "SUCCESS":


public class RedirectActionAction extends ActionSupport
{
    @Override
    public String execute() throws Exception
    {
        return SUCCESS;
    }
}


Ahora que nuestro Action está listo, vamos al archivo de configuración "struts.xml" y hacemos su declaración:


<action name="redirect-action" class="com.javatutoriales.results.actions.RedirectActionAction">            
</action>


Lo siguiente es declarar el result al que iremos en el caso de la respuesta "success". El result será del tipo "redirectAction" y nos redirigirá al Action llamado "dispatcher" (el primero que creamos en el tutorial), recordemos que el parámetro por default de este result es el nombre del Action:


<result type="redirectAction">dispatcher</result>


La declaración de este Action queda de la siguiente forma:


<action name="redirect-action" class="com.javatutoriales.results.actions.RedirectActionAction">
    <result type="redirectAction">dispatcher</result>
</action>


Regresemos a la página "index.jsp" que creamos hace unos momentos, y agreguemos un enlace para el Action "redirect-action" (no olviden indicar que haremos uso de las etiquetas de Struts 2 con la directiva taglib correspondiente):


<s:a action="redirect-action">Redireccionar</s:a>


Ahora ejecutamos nuestra aplicación y entramos a la siguiente dirección:


http://localhost:8080/results/redirectAction/index.jsp


En donde únicamente debemos ver la liga que colocamos hace un momento. Al hacer clic sobre ella seremos redirigidos al Action llamado "dispatcher":



Recordemos que el Action anterior recibe dos parámetros, el nombre del usuario que envía el formulario y el lenguaje de programación que este usa. Por el resultado anterior podemos ver que no se están recibiendo ninguno de los dos parámetros. Ahora resolveremos esto pasando ambos parámetros, uno de forma estática y el otro de forma dinámica.

Como ahora pasaremos más parámetros al result, ya no podemos seguir indicando el nombre del Action como el parámetro por default, y debemos hacerlo de forma explícita:


<result type="redirectAction">
    <param name="actionName">dispatcher</param>
</result>


Para pasar parámetros a los Actions, los definimos dentro del result que los invoca, y es tan simple como definirlos, usando su nombre, dentro del elemento "<param>"; el valor del parámetro lo colocaremos dentro de este elemento. Por ejemplo, para definir el parámetro "nombre", con un valor de "Alex", lo hacemos de la siguiente forma:


<param name="nombre">Alex</param>


El parámetro anterior está definido de forma estática, pero también podemos definirlos de forma dinámica. Los parámetros dinámicos necesitan tener dos partes, una declaración en el result, y un método getter en el Action del cual se obtendrá el valor. Definamos el parámetro "lenguaje".

Primero declararemos en el result un nuevo parámetro cuyo nombre será lenguaje:


<param name="lenguaje"></param>


Queremos que el valor de este parámetro sea establecido de forma dinámica, por lo que lo debemos indicar, entre los elementos "${" y "}", qué atributo del Action que se debe llamar para obtener este valor. El valor se obtendrá del getter de este atributo, que debe seguir la convención de nombres de los JavaBeans; o sea que si queremos obtener el valor de un atributo llamado "lenguajeProgramacion", nuestro Action debe tener un método "public String getLenguajeProgramacion()". Por lo tanto, la definición de parámetro queda de la siguiente forma:


<param name="lenguaje">${lenguajeProgramacion}</param>


También debemos modificar la clase "RedirectActionAction", agregándole el getter necesario. En este caso el regresaremos un valor constante, pero este parámetro podría ser obtenido de forma dinámica, conectándose a una base de datos, de la sesión del usuario, o de alguna otra forma. Regresaremos el valor "Java" como nuestro lenguaje de programación favorito ^_^:


public String getLenguajeProgramacion()
{
    return "Java";
}


Ahora, cuando volvamos a ejecutar nuestra aplicación debemos ver la siguiente salida:



Esto nos indica que el ejemplo ha funcionado correctamente ^_^. Ahora veremos cómo funciona el siguiente tipo de result:


Result "chain"

Este result permite que se invoque otro Action completo, incluyendo todos sus interceptores y su result.

Aunque puede sonar como el result anterior, no lo es, ya que en el caso anterior el result le indicaba al navegador que debía redirigirse hacia otro Action, en este caso todos los Actions de la cadena se ejecutan en el servidor, y este nos regresa el resultado del último Action. Por la explicación anterior podemos entender además que es posible colocar más de un Action en la cadena de invocación (para esto el segundo Action debe regresar a un result que también sea de tipo "chain").

Este Action tiene cuatro parámetros:

  • actionName (default) - El nombre del Action que será encadenado.
  • namespace - Indica en cuál namespace se encuentra el Action
  • method - Si queremos invocar un método en el Action que no sea el método "execute" lo indicamos con este parámetro.
  • skipActions - Una lista, separada por comas, de los nombres de los Actions que pueden ser encadenados a este.


Comencemos con el ejemplo, en el cual encadenaremos todos los Actions que hemos creado hasta el momento ^_^.

Primero crearemos un nuevo directorio, en la parte de "Web Pages" del proyecto, llamado "chain", dentro de este creamos una nueva página llamada "index.jsp". En esta página nuevamente sólo pondremos una liga que nos enviará al Action que definiremos en un momento y que iniciará con la cadena de llamadas. El nombre de este Action será "chain", así que colocamos la liga de la siguiente forma (recuerden indicar con la directiva taglib correspondiente que haremos uso de las etiquetas de Struts 2):


<s:a action="chain">Encadenar</s:a><br />


Como en esta ocasión haremos uso de varios Actions "vacios" no crearemos ninguna clase, esto ayudará para mostrar cómo se va llamando uno después de otro... y para aprovechar a mostrar otra cosa interesante de Struts 2.

Vamos al archivo de configuración "struts.xml" y declaramos un nuevo action, llamado "chain", sólo que en esta ocasión no declararemos ninguna clase para este action, de la siguiente forma:


<action name="chain">
</action>


Cuando no colocamos un valor en el atributo "class" del elemento "<action>" este por default usa la clase "ActionSupport" (aunque podemos cambiar esto en los archivos de configuración de Struts 2), de la cual han extendido todos nuestros Actions hasta el momento. ActionSupport tiene un método "execute" que regresa el valor de "success", y un método "input" que regresa el valor de "input". Eso quiere decir que el elemento "<action>" que acabamos de declarar, siempre irá a su result llamado "success".

Ahora colocaremos el result "success", el cual recuerden que es el nombre por default de los results, indicando que este será de tipo "chain" y que al terminar ejecutará un action llamado "chain2":


<action name="chain">
    <result type="chain">chain2</result>
</action>


Declararemos otro action llamado "chain2", que será muy parecido al anterior, y en su result, también de tipo "chain", encadenará la llamada a un action llamado "chain3":


<action name="chain2">
    <result type="chain">chain3</result>
</action>


Para terminar, declaramos un action llamado "chain3", que será muy parecido a los dos anteriores, con la diferencia de que este nos enviará ahora a un Action que si realiza una acción, yo encadenaré el Action "redirect-externo" pero pueden usar cualquiera que ustedes prefieran:


<action name="chain3">
   <result type="chain">redirect-externo</result>
</action>


La invocación de los Actions será más o menos la siguiente:



  1. 1- Primero el action "chain" ejecuta su método "execute" que regresa "succes". Como este result es de tipo "chain", se manda llamar el siguiente action de la cadena, que en este caso es "chain2".
  2. 2- El action "chain2" ejecuta su método "execute", este regresa el valor "success", el cual es un result de tipo "chain", por lo que se ejecuta el siguiente action de la cadena: "chain3".
  3. 3- "chain3" ejecuta su método "execute" el cual regresa el valor "success", este envía a un result de tipo "chain" el cual hará una llamada al action llamado "redirect-externo".
  4. 4- el action llamado "redirect-externo" ejecuta su método "execute", al terminar de ejecutarse este regresa el valor "success", el cual nos envía a un result de tipo "redirect" que nos envía a la página de www.javatutoriales.com
  5. 5- Nuestro navegador nos muestra la entrada correspondiente del blog ^_^.


En cada uno de los casos se ejecutarán todos los actions, del lado del servidor, y cuando se llegué a un result que no sea de tipo "chain" este se ejecutará de forma normal, en este caso será redirigir el navegador a la página de Java Tutoriales :).

Ejecutamos nuestra aplicación y entramos a la siguiente dirección:


http://localhost:8080/results/chain/index.jsp


Veremos el enlace que creaos hace un momento. Al hacer clic sobre él, se ejecutarán nuestros cuatro Actions, y posteriormente veremos la página correspondiente del blog.

Como podemos imaginarnos, este tipo de result es muy útil cuando necesitamos ejecutar dos o más Actions juntos, y el primero no necesita un resultado visual (como un proceso de login y una carga de permisos del usuario).

Ahora que sabemos cómo funciona este result, pasemos al siguiente:


Result "stream"

Ya conocemos un poco acerca de este result, ya que lo vimos en el tutorial número 4, cuando trabajamos con formularios. Por lo cual ahora solo hablaremos de los pequeños detalles.

Este result nos permite regresar un flujo de bytes al navegador del usuario para que este los interprete de alguna manera. Por ejemplo, podemos enviar un flujo de bytes que representen un archivo PDF, y si el navegador del cliente tiene el plugin adecuado lo podrá visualizar. Por lo regular, si el navegador no sabe como mostrar la información que le estamos enviando, le preguntará al usuario si quiere guardar el archivo o abrirlo con alguna aplicación particular.

Este result tiene los siguientes parámetros

  • contentType: El mime-type (el tipo del contenido) que es enviado al usuario. Por default su valor es "text/plain".
  • contentLength: El número de bytes contenidos en el flujo que enviaremos al usuario. Como vimos en el tutorial anterior, este es usado para que el navegador pueda mostrar al usuario, de forma correcta, una barra de progreso de la descarga.
  • contentDisposition: Este parámetro sirve para establecer el valor de la cabecera "content-disposition", que el navegador usa para establecer el nombre del archivo que recibirá. El valor por default es "inline".
  • inputName: El nombre del atributo de nuestro Action que contiene el flujo de bytes. El atributo debe ser de tipo "InputStream". Por default se busca un atributo de nombre "inputStream".
  • bufferSize: El tamaño del buffer que se usa para copiar los valores de la entrada al flujo de salida del navegador. Por default es "1024".
  • allowCaching: Indica si el navegador debe o no mantener en caché los datos que le hemos enviado. Esto quiere decir que solamente descargará el archivo una vez, y posteriormente lo regresará del caché. Esto es útil si el contenido que le enviaremos siempre será el mismo (digamos una imagen estática); pero si el contenido que le enviaremos cambiará de manera constante debemos indicarle que no queremos que lo guarde en caché, de esta forma siempre lo descargará del servidor. Su valor por default es "true".
  • contentCharSet: El juego de caracteres que se usan en el contenido (este parámetro es útil si regresamos texto plano en algún formato como XML).


Como podemos ver, este result no tienen ningún parámetro por default, por lo que siempre tendremos que usar el elemento "<param>" para establecer sus valores.

Haremos un pequeño ejemplo en el que regresaremos al usuario una imagen de forma dinámica. Lo primero que necesitamos es una imagen, yo usaré la siguiente:



Colocaremos la imagen anterior en cualquier ubicación de nuestro disco duro, por facilidad yo la pondré en "C:\imagenes":



Ahora que tenemos la imagen, creamos un nuevo directorio, en la zona de páginas web de nuestro proyecto, llamado "stream". Dentro creamos una página llamada "index.jsp". Por el momento dejaremos la página como está.

Creamos en el paquete "actions" una nueva clase llamada "StreamResultAction", esta clase debe extender de "ActionSupport":


public class StreamResultAction extends ActionSupport
{
}


Esta clase se encargará de crear un InputStream para leer la imagen, indicar el tamaño de la misma, y decir cuál será el nombre de esta imagen. Lo primero que debemos hacer es colocar unos cuantos atributos para mantener los valores de los datos anteriores. Colocamos en esta clase tres atributos, con sus correspondientes getters, de la siguiente forma:


    private int bytesArchivo;
    private InputStream streamImagen;
    private String nombreImagen;

    public int getBytesArchivo()
    {
        return bytesArchivo;
    }

    public String getNombreImagen()
    {
        return nombreImagen;
    }

    public InputStream getStreamImagen()
    {
        return streamImagen;
    }


Los getters son necesarios ya que es por medio de estos que el result puede leer los valores del Action.

Lo siguiente que debemos hacer es sobre-escribir el método "execute" para poder establecer el valor de los atributos anteriores. Dentro del método lo primero que haremos es crear un nuevo objeto de tipo "File" que apunte al archivo de la imagen, de esta manera podremos obtener el tamaño de la misma con el método "length" y su nombre usando el método "getName()":


@Override
public String execute() throws Exception
{
    File archivoImagen = new File("/imagenes/imagen.png");
    bytesArchivo = (int)archivoImagen.length();
    nombreImagen = archivoImagen.getName();
        
    return SUCCESS;
}


Usando el File anterior, creamos un InputStream, un objeto de tipo "FileInputStream" que será el flujo de bytes de la imagen:


streamImagen = new FileInputStream(archivoImagen);


Y esto es todo lo que debemos hacer. La clase "StreamResultAction" completa queda de la siguiente forma:


public class StreamResultAction extends ActionSupport
{
    private int bytesArchivo;
    private InputStream streamImagen;
    private String nombreImagen;

    @Override
    public String execute() throws Exception
    {
        File archivoImagen = new File("/imagenes/imagen.png");
        bytesArchivo = (int)archivoImagen.length();
        nombreImagen = archivoImagen.getName();
        
        streamImagen = new FileInputStream(archivoImagen);
        
        return SUCCESS;
    }
    
    public int getBytesArchivo()
    {
        return bytesArchivo;
    }

    public String getNombreImagen()
    {
        return nombreImagen;
    }

    public InputStream getStreamImagen()
    {
        return streamImagen;
    }
}


Lo siguiente que debemos hacer es declarar este Action en el archivo "struts.xml", el nombre que le daremos será "stream":


<action name="stream" class="com.javatutoriales.results.actions.StreamResultAction">
</action>


Ahora declararemos el result para el caso "success", este result será de tipo "stream":


<result type="stream">
</result>


Lo siguiente es declarar cada uno de los parámetros necesarios. En este ejemplo, algunos parámetros, como el "contentType" serán estáticos y otros como el "contentLength" serán dinámicos:


<result type="stream">
    <param name="contentType">image/png</param>
    <param name="contentLength">${bytesArchivo}</param>
    <param name="inputName">streamImagen</param>
    <param name="contentDisposition">attachment;filename="${nombreImagen}"</param>
</result>


La declaración del Action queda de la siguiente forma:


<action name="stream" class="com.javatutoriales.results.actions.StreamResultAction">
    <result type="stream">
        <param name="contentType">image/png</param>
        <param name="contentLength">${bytesArchivo}</param>
        <param name="inputName">streamImagen</param>
        <param name="contentDisposition">attachment;filename="${nombreImagen}"</param>
    </result>
</action>


Regresemos a la página index.jsp que creamos hace un momento. En esta colaremos una etiqueta "<img />" indicando que la imagen será devuelta por nuestro Action, para eso sólo debemos declarar en su atributo "src" con el valor del nombre de nuestro Action, o sea de la siguiente forma:


<img src="stream.action" alt="imagen" />


Ahora podemos ejecutar nuestra aplicación. Entramos en la siguiente dirección:


http://localhost:8080/results/stream/index.jsp


Y con eso deberemos ver la página con la imagen que cargamos:



Podemos ver con esto que el ejemplo ha funcionado de forma correcta, o sea el flujo de bytes se envío al navegador, indicando el tipo de contenido del mismo flujo, y este lo interpretó de forma correcta para mostrarlo al usuario.

Veamos ahora el penúltimo tipo de result de este tutorial:


Result "plaintext"

Este tipo de result envía el contenido de un archivo como texto plano al navegador. Con este result podemos, por ejemplo enviar el contenido de una JSP, sin que esta sea procesada, o cualquier otro archivo que pueda ser abierto con un editor de texto plano.

Este result tiene dos parámetros:

  • location (default): la ubicación del archivo que será mostrada como texto plano. Esta ubicación será relativa a la raíz del proyecto web.
  • chatSet: El conjunto de caracteres con el que será mostrado el archivo.


Veamos el ejemplo correspondiente. Lo primero que haremos es crear un nuevo directorio, en la sección de páginas web, llamado "plaintext". Dentro de este directorio crearemos dos páginas, la primera será el típico "index.jsp" el cual dejaremos por el momento, y la otra será una página llamada "pagina.html" (así es, html).

Esta página tendrá un simple mensaje que dirá: "soy tan sólo una página HTML". La página (en html5) es la siguiente:


<!DOCTYPE html> 
<html> 
  <head>
    <title>Página HTML - http://javatutoriales.com/</title>
    <meta charset="UTF-8" />
  </head>
  <body>
      Soy tan sólo una <strong>página HTML</strong>.
  </body>
</html>


Si ejecutamos la aplicación y entramos a la siguiente dirección:


http://localhost:8080/results/plaintext/pagina.html


Debemos ver el siguiente contenido:



Con eso comprobamos que el navegador pueda ejecutar correctamente esta página.

Ahora crearemos en el paquete "actions" una nueva clase llamada "PlainTextResultAction", la cual extenderá de "ActionSupport" y lo único que tendrá será un método "execute" sobrecargado que regresará el valor de la constante "SUCCESS":


public class PlainTextResultAction extends ActionSupport
{
    @Override
    public String execute() throws Exception
    {
        return SUCCESS;
    }
}


A continuación pasemos a la declaración de este Action en el archivo "struts.xml". El nombre de este Action será "plaintext":


<action name="plaintext" class="com.javatutoriales.results.actions.PlainTextResultAction">
            
</action>


Declaramos el result para este Action. El tipo del result será "plainText". Como en el juego de caracteres de nuestra página (UTF-8) es distinta a la del proyecto (ISO-8859-1), debemos indicar el valor del mismo en el parámetro "charSet". Además colocaremos el valor del parámetro "location" como la página HTML que creamos hace un momento (recuerden que este valor debe ser relativo a la raíz de las páginas web):


<action name="plaintext" class="com.javatutoriales.results.actions.PlainTextResultAction">
    <result type="plainText">
        <param name="location">/plaintext/pagina.html</param>
        <param name="charSet">UTF-8</param>
    </result>
</action>


Ahora que tenemos esta declaración, lo último que haremos es regresar a la página "index.jsp" que creamos hace un momento y colocaremos un enlace al Action que acabamos de declarar, de esta forma (recuerden indicar que haremos uso de las etiquetas de Struts 2 con el taglib correspondiente):


<s:a action="plaintext">Ver página</s:a>


Entramos a la siguiente dirección:


http://localhost:8080/results/plaintext/index.jsp


Con lo que deberemos ver el enlace que acabamos de declarar. Al hacer clic sobre el nos enviará al Action correspondiente, con lo cual deberemos ver en el contenido de la página HTML que creamos (en texto plano, sin ser procesada por el navegador):



Si revisan el código fuente de la página verán que es exactamente el mismo que creamos, lo que ocurre es que este result cambia el content-type para que pueda ser mostrado como texto.

Ahora veremos el último result de este tutorial:


Result "httpheader"

Este result permite establecer el valor de las cabeceras de la respuesta que se le envía al cliente. Con este result podemos, por ejemplo enviar estatus de error al cliente, indicando errores o falta de permisos para ejecutar ciertas acciones.

Este result puede recibir los siguientes parámetros:

  • status: El estatus de la respuesta http.
  • parse: Indica si los parámetros "headers" deben ser parseados como expresiones OGNL (por default es true)
  • headers: Los valores de las cabeceras que queramos establecer. Estos se establecer colocando el nombre de la cabecera que queremos establecer precedida por un punto, como por ejemplo "headers.a", "headers.b", etc.
  • error: El código de error HTTP que será enviado como respuesta.
  • errorMessage: El mensaje de error que será mostrado en la respuesta.
La mayor utilidad de este tipo result es, como habrán imaginado por los parámetros, es para enviar estatus de error al usuario (ya que los mensajes de éxito muestran el contenido correspondiente del sitio).

Para los que tienen dudas de cuáles son los estatus que se pueden tener, pueden consultar la siguiente entrada de Wikipedia:


http://en.wikipedia.org/wiki/List_of_HTTP_status_codes


También esta es la lista de los campos de las cabeceras que se pueden establecer para las peticiones y respuestas, en Wikipedia:


http://en.wikipedia.org/wiki/List_of_HTTP_header_fields


Como una nota adicional, cada uno de los códigos de estatus debe ser usado con alguna o algunas cabeceras adicionales.

Veamos algunos ejemplos del uso de este result. Lo primero que hacemos es crear un nuevo directorio, en la sección de páginas web, llamado "headers". Dentro de este directorio crearemos una página llamada "index.jsp" en la que colocaremos las ligas de los actions que vayamos creando, por lo que indicaremos que haremos uso de las etiquetas de Struts 2 con la directiva taglib correspondiente:


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


En este caso no crearemos ninguna clase Action, ya que solamente necesitamos su declaración, con su correspondiente "<result>" dentro del archivo "struts.xml".

El primer result que crearemos será para el caso que el usuario busque un recurso (una página, una imagen, un archivo, etc.) que haya sido movido de nuestro servidor a algún otro lugar. Si revisamos la lista de códigos de estatus, vemos que existe uno para este caso (vaya coincidencia ^^!), el código "301 - Moved Permanently". Si buscamos información sobre este código, veremos que, según Wikipedia (http://en.wikipedia.org/wiki/HTTP_301) este código debe de usarse junto con la cabecera "location".

Vamos al archivo "struts.xml" y declaramos un nuevo action llamado "httpstatus301", el cual no será implementado por ninguna clase:


<action name="httpstatus301">
</action>


A continuación declaremos un "<result>" para el caso "success", el tipo de este result será "httpheader":


<action name="httpstatus301">
    <result type="httpheader">
    </result>
</action>


Ahora viene lo interesante; "301" no es un código de error, sino que indica que la petición se realizó correctamente pero el recurso se movió a otra ubicación, la cual por cierto conocemos y queremos que el usuario sea redirigido de forma automática a esa nueva ubicación. Como 301 no es un código de error, lo indicamos usado el parámetro "status":


<result type="httpheader">
    <param name="status">301</param>
</result>


Y como vimos hace un momento, cuando usamos el estatus 301, también debemos indicar la cabecera "Location" con la nueva ubicación del recurso. Supongamos que en este caso la ubicación nueva del recurso que estamos buscando es: "http://www.javatutoriales.com/", por lo que lo indicamos de la siguiente forma:


<result type="httpheader">
    <param name="status">301</param>
    <param name="headers.Location">http://www.javatutoriales.com/</param>
</result> 


La declaración completa de nuestro action queda de la siguiente forma:


<action name="httpstatus301">
    <result type="httpheader">
        <param name="status">301</param>
        <param name="headers.Location">http://www.javatutoriales.com/</param>
    </result>
</action>


Ahora regresaremos a la página "index.jsp" y agregaremos un enlace al action que acabamos de declarar, de la siguiente forma:


<s:a action="httpstatus301">301 - Moved Permanently</s:a>


Ejecutamos nuestra aplicación y entramos a la siguiente dirección:


http://localhost:8080/results/headers/index.jsp


Con lo que debemos ver el enlace que creamos hace un momento. Cuando hagamos clic en el debemos ser redirigidos a otra página:



¿Qué fue lo que pasó? Para responder a esta pregunta haremos uso de una extensión de firefox llamada "live http headers" que nos permite analizar los encabezados de las peticiones y las respuestas que realizamos.



Podemos ver que en primer lugar (paso 1) el navegador hace una petición a la página "http://localhost:8080/results/headers/index.jsp" (la parte que dice "jsessionid" es la cookie por la cual se manejan las peticiones en los servidores java), el servidor envía la respuesta (paso 2) con el código "301 Movido permanentemente" y envía además la cabecera "Location" indicando que el recurso ahora se encuentra en "http://www.javatutoriales.com". El navegador hace una nueva petición a esta ubicación (paso 3) y se continúa con el proceso normal.

Podemos ver que la redirección ha funcionado correctamente.

Veamos ahora un ejemplo de un código de error. En Http existen dos tipos de errores: los errores generados por el cliente (cuando, por ejemplo, la petición no se entiende o cuando excede de un cierto tamaño) y los errores generados por el servidor (cuando ocurre algún error inesperado que no permite que la petición se procese).

Los errores de cliente tienen un código a partir del 400 y los del servidor a partir del 500. Veamos primero un error generado por el cliente.

El status 400 indica que la petición no puede ser completada debido a que la sintaxis en la que se recibió no es correcta.

Declaramos un nuevo elemento "<action>" en el archivo "struts.xml" que se llamará "httpstatus400" y no será implementado por ninguna clase:


<action name="httpstatus400">
</action>


Dentro de este action declaramos el result para el caso "success", que será del tipo "httpheader".


<result type="httpheader">
</result>


Como en este caso "400" si se trata de un error, debemos indicarlo usando el parámetro "error" en vez de "status" como la vez anterior:


<result type="httpheader">
    <param name="error">400</param>
</result>


Para terminar, cuando indicamos un error, en vez de proporcionar una cabecera se indica un mensaje de error para que el usuario tenga una idea de lo que ha salido mal, esto lo hacemos con el parámetro "errorMessage":


<result type="httpheader">
    <param name="error">400</param>
    <param name="errorMessage">Los datos proporcionados no pueden ser entendidos, tal vez ha escrito algo mal o su navegador no puede colocarlos en la sintaxis adecuada, favor de utilizar otro navegador.</param>
</result>


La declaración completa del action queda de la siguiente forma:


<action name="httpstatus400">
    <result type="httpheader">
        <param name="error">400</param>
        <param name="errorMessage">Los datos proporcionados no pueden ser entendidos, tal vez ha escrito algo mal o su navegador no puede colocarlos en la sintaxis adecuada, favor de utilizar otro navegador.</param>
    </result>
</action>


Ahora regresamos a la página "index.jsp" y agregamos un enlace más, para este nuevo Action:


<s:a action="httpstatus400">404 - Bad Request</s:a>


Ejecutamos nuesta aplicación y entramos en la siguinte dirección:


http://localhost:8080/results/headers/index.jsp


En donde deberemos ver enlace que acabamos de crear. Al hacer clic sobre él, deberemos ver la siguiente salida:



En donde podemos ver que el navegador nos muestra una ventana de error, con el mensaje que hemos indicado. Así el usuario sabrá que ha hecho algo mal y así podrá corregirlo.

Veamos el último ejemplo, en este crearemos otra respuesta de error, sólo que ahora será un error del servidor, en el cual usaremos el statis "501" para indicar que la funcionalidad que está solicitada no ha sido implementada aún.

En el archivo "struts.xml" declaramos nuevamente un elemento "<action>" que se llamará "httpstatus501" y no será implementado por ninguna clase:


<action name="httpstatus501">
</action>


Lo siguiente que haremos es declarar un elemento "<result>" para el caso "success" que será de tipo "httpheader":


<result type="httpheader">
</result>


Como el estatus "501" se trata de un error, lo indicaremos usando el parámetro "error" y daremos un mensaje con la descripción del error usando el parámetro "errorMessage":


<result type="httpheader">
    <param name="error">501</param>
    <param name="errorMessage">Estimado usuario, la funcionalidad que está tratando de usar aún no ha sido implementada. Le pedimos tenga paciencia y regrese más tarde ^^!</param>
</result>


La declaración completa del Action queda de la siguiente forma:


<action name="httpstatus501">
    <result type="httpheader">
        <param name="error">501</param>
        <param name="errorMessage">Estimado usuario, la funcionalidad que está tratando de usar aún no ha sido implementada. Le pedimos tenga paciencia y regrese más tarde ^^!</param>
    </result>
</action>


Ahora regresamos nuevamente a la página "index.jsp" y agregamos un enlace al action que acabamos de declarar:


<s:a action="httpstatus501">501 - Not Implemented</s:a>


Ejecutamos nuevamente nuestra aplicación y entramos en la siguiente dirección:


http://localhost:8080/results/headers/index.jsp


Hacemos clic en el enlace que acabamos de colocar y deberemos ver el siguiente error:



Lo cual nos indica que el ejemplo ha funcionado correctamente ^_^.

Para terminar con este tutorial, veremos una funcionalidad que nos permite ejecutar un proceso después de que el Action se ha ejecutado, pero antes de regresar el result:


PreResultListener

Como dijimos hace un momento: este mecanismo permite realizar algún procesamiento después de que se ha ejecutado nuestro Action (o interceptor, pero eso lo veremos en el siguiente tutorial) y antes de que se ejecute el result. Esto nos permite que modifiquemos algunas cosas, como por ejemplo, podemos cambiar el result al que seremos dirigidos (en vez de ir al result "input" iremos a "success") o modificar los parámetros del Action antes de que el result se ejecute.

Struts 2 proporciona la interface "com.opensymphony.xwork2.interceptor.PreResultListener" para este fin. Esta interface puede ser agregada tanto a un Action como a un Interceptor, aunque la única forma de agregar este PreResultListener es en código dentro del método "execute", esto quiere decir que no hay alguna forma de hacerlo en configuración por lo que, si queremos quitar este listener deberemos ir a nuestro código, eliminarlo y volver a compilarlo.

Veamos cómo funciona este listener con un ejemplo. Primero creamos un nuevo directorio llamado "preresult" en la sección de páginas web. Dentro de esta crearemos tres páginas, la primera se llamara "index.jsp", la segunda "exito.jsp", y la tercera "error.jsp". En la primer página colocaremos un formulario que será muy parecido al del primer ejemplo que vimos. En el formulario preguntaremos el nombre del usuario y su lenguaje de programación favorito, si el lenguaje es "Java" lo enviaremos a la página "exito.jsp", en caso contrario lo enviaremos a la página "error.jsp". El formulario queda de la siguiente forma:


<s:form action="presult">
    <s:textfield name="nombre" label="Nombre" />
    <s:select name="lenguaje" label="Lenguaje de Programacion" list="{'Java', 'PHP', '.Net'}" />
    <s:submit value="Enviar" />
</s:form>


No olviden indicar que haremos uso de las etiquetas de Struts 2 usando la directiva "taglib" correspondiente.

Antes de ver el Action crearemos el contenido de las páginas "exito.jsp" y "error.jsp". El contenido de "exito.jsp" será el siguiente:


Pasa por favor mi estimado <s:property value="nombre" />, has entrado al mundo de la programación Java.


Si, sólo el contenido anterior. Y el de "error.jsp" será el siguiente:


Perdón <s:property value="nombre" />, este es un sitio exclusivo para programadores Java, pero siéntate al fondo a ver si aprendes algo...


Ahora crearemos un nuevo Action, en el paquete "actions", llamado "PreResultListenerAction" que extenderá de "ActionSupport":


public class PreResultListenerAction extends ActionSupport
{    
}


A esta clase le colocaremos dos atributos de tipo String: "nombre" y "lenguaje", con sus correspondientes getters y setters, estos atributos contendrán los valores que son recibidos a través del formulario:


    private String nombre;
    private String lenguaje;

    public String getLenguaje()
    {
        return lenguaje;
    }

    public void setLenguaje(String lenguaje)
    {
        this.lenguaje = lenguaje;
    }

    public String getNombre()
    {
        return nombre;
    }

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


Ahora sobre-escribiremos el método "execute" para, en base al lenguaje seleccionado, decidir a cuál página enviar al usuario. Si el lenguaje es "Java" lo enviaremos el result "SUCCESS", en caso contrario lo enviaremos al result "ERROR":


    @Override
    public String execute() throws Exception
    {
        if (!"Java".equals(lenguaje))
        {
            return ERROR;
        }

        return SUCCESS;
    }


Ahora que tenemos todos los elementos, vamos al archivo "struts.xml" y declaramos un nuevo elemento "<action>" llamado "presult" que será implementado por la clase que acabamos de crear:


<action name="presult" class="com.javatutoriales.results.actions.PreResultListenerAction">            
</action>


Agregaremos dos results, uno llamado "succes" que nos enviará a la página "/preresult/exito.jsp", y otro llamado "error" que nos enviara a la página "/preresult/error.jsp":


<action name="presult" class="com.javatutoriales.results.actions.PreResultListenerAction">
    <result>/preresult/exito.jsp</result>
    <result name="error">/preresult/error.jsp</result>
</action>


Como ya somos expertos en results, ¿de qué tipo son los dos results anteriores ;)?

Ejecutamos nuestra aplicación y entramos a la siguiente dirección:


http://localhost:8080/results/preresult/index.jsp


Con lo que debemos ver el formulario que creamos hace un momento:



Colocamos nuestro nombre, y si en el lenguaje de programación colocamos "Java" debemos ser enviados a la página "exito.jsp":



De lo contrario iremos a la página "error.jsp":



Con esto podemos ver que todo está configurado y funcionado de forma correcta.... ya sé lo que se están preguntado: "¿.... y dónde está el PreResultListener? :S". Bueno, esto sólo era para ver el comportamiento normal de la aplicación. Ahora agregaremos el listener:

Cada Action, como habíamos visto en el tutorial anterior, tiene una forma de obtener el contexto en el cual se ejecuta (el "ActionContext"). Este contexto nos permite obtener un objeto de tipo "ActionInvocation" que representa... bueno la invocación del Action ^^!. Con este objeto podemos, entre otras cosas, agregar un "PreResultListener" a la invocación de este Action.

La obtención de estos dos objetos debe hacerse dentro del método "execute":


@Override
public String execute() throws Exception
{
    ActionInvocation actionInvocation = ActionContext.getContext().getActionInvocation();
}


"PreResultListener" es una interface que tiene sólo un método:


void beforeResult(ActionInvocation invocation, String resultCode);


El cual será invocado cuando la ejecución del Action termine, pero antes de que se comience con el procesamiento de result.

Normalmente esta interface se implementa de dos formas: como una clase anidada dentro del Action, o como una clase interna anónima dentro del método "execute", al momento de agregar el listener. La desición de cuál de las dos usar es, como siempre, una cuestión de diseño y dependerá más de cuántas cosas haremos dentro de método "beforeResult". Si haremos muchas cosas lo mejor podría ser crear una clase anidada que se encargue de realizar todas las tareas, si se harán pocas (como es nuestro caso) lo mejor es usar una clase anónima.

La clase "ActionInvocation" tiene un método llamado "addResultListener" que es el que usamos para agregar nuestro listener:


actionInvocation.addPreResultListener(new PreResultListener()
{
    public void beforeResult(ActionInvocation ai, String resultCode)
    {            
    }
});


Dentro del método "beforeResult" colocaremos la lógica que se ejecutará antes de comenzar con el procesamiento del result. En nuestro caso haremos un cambio sencillo, no importa qué datos introduzca el usuario, siempre lo regresaremos al result "success".

El método "beforeResult" recibe el nombre del result al que actualmente irá la petición, a través del parámetro "resultCode", de esta forma sabremos a dónde se supone que debe ir el usuario. En nuestro caso no importa a cuál result vaya a ser enviado originalmente, de todas formas lo reenviaremos al result "success". Para cambiar el result al que lo enviaremos establecemos usamos el método "setResultCode" del objeto "ActionInvocation" que recibimos como parámetro; a este método le pasamos como argumento el nombre del nuevo result, en nuestro caso usaremos la constante "SUCCESS", de la siguiente forma:


public void beforeResult(ActionInvocation ai, String resultCode)
{
    ai.setResultCode(SUCCESS);
}


Probemos que efectivamente seamos enviados siempre a la página de éxito; ejecutamos nuestra aplicación y entramos a la siguiente dirección:


http://localhost:8080/results/preresult/index.jsp


Colocamos los datos en nuestro formulario, y no importa qué lenguaje de programación seleccionemos, siempre seremos enviados a la página del resultado de éxito:



Hagamos un cambio más. Al momento de saludar al usuario podremos hacerlo de una forma aún más amable. En vez de saludarlo sólo por su nombre, lo saludaremos diciéndole "colega", para lo cual modificaremos el nombre del usuario, agregando siempre la palabra "colega" antes del nombre que nos proporciona:


public void beforeResult(ActionInvocation ai, String resultCode)
{
    ai.setResultCode(SUCCESS);
    nombre = " colega " + nombre;
}


Y listo, ahora podemos volver a ejecutar nuestra aplicación, entrar a la siguiente dirección y proporcionar nuestros datos:


http://localhost:8080/results/preresult/index.jsp


Al enviar nuestros datos, debemos ser saludados como nos lo merecemos ^_^:



Como vimos, usando un "PreResultListener" es muy fácil modificar el comportamiento de nuestros results sin tener que hacer modificaciones en la definición de los mismos.

Ahora veamos un último y pequeño tema extra acerca de los results:


Results Globales

Hasta ahora, todos los results que hemos visto son declarados dentro de un Action, pero ¿qué pasa si necesitamos que más de un Action tenga acceso al mismo result? Imaginemos el caso de una página para errores globales cuando algo dentro de nuestra aplicación sale mal, y no tenemos idea de por qué (aunque sé que esto nunca pasa ^^).

El caso más común es cuando necesitamos una página de "login" a la cual enviar a un usuario si está tratando de ejecutar un Action que sólo pueden usar los usuarios registrados en el sitio y este no ha iniciado una sesión.

Los results globales funcionan de la misma forma que el resto de los que hemos visto, y declararlos es igual de sencillo que el resto, sólo que en este caso no están dentro de un elemento "<action>", sino dentro del elemento "<global-results>" que debe estar siempre dentro de un paquete. Para declarar varios resultados globales lo hacemos de la siguiente forma:


<global-results>
    <result name="login">/login.jsp</result>
    <result name="error">/error.jsp</result>
</global-results>


Una pregunta que deben estarse haciendo en este momento es: ¿y qué pasa si tengo declarado un result con el nombre de "error" en mi "<action>" y otro con el mismo nombre en los results globales? Buena pregunta; en ese caso el result declarado dentro del action tiene mayor precedencia. Cuando indicamos que debemos ir a un result (digamos "login"), Struts 2 siempre lo buscará dentro de la declaración del action en ejecución, si no lo encuentra irá a buscarlo a los resultados globales, y en caso de que este tampoco sea encontrado ahí, arrojará un error.

Esto es todo lo que respecta a este tutorial, espero que les sea de utilidad. No olviden dejar sus dudas comentarios y sugerencias en la sección de comentarios o al correo programadorjavablog@gmail.com (también recuerden hacerse fans de JavaTutoriales en Facebook, Google+, y Twitter ^_^).

Saludos y gracias.

Descarga los archivos de este tutorial desde aquí:



Entradas Relacionadas:

10 comentarios:

  1. Excelente tutorial de struts 2, muchas gracias los he seguido todos, te felicito y estare esperando el otro, ojala sea algo con base de datos, un CRUD.

    ResponderEliminar
  2. Muy buen tutorial de introducción a Struts2 gracias!

    ResponderEliminar
  3. Hola una consulta, tengo formulario en Struts 2.0, lo que tengo son tres botones que estan etiquetados como IF , AND , OR ; donde al presionar el boton, me debe redirigir el valor IF , AND , OR a un textarea (osea que aparesca ese valor en el textarea):




    Pueden ayudarme, tambien quiero que al seleccionar un combo con valores y seleccionar un boton Agregar me muestre el valor interno del combo en el textarea

    ResponderEliminar
  4. Segun encontre ejemplos en Struts 1.0 era algo asi:

    Conceptos:


    --- Seleccione ---




    < name="button" type="button" class="botones" value="Agregar" onclick="agregaConcepto()">

    Y el evento era asi:

    function agregaConcepto(){
    f = document.forms[0];
    if (f.concepto.value!='')
    insertAtCursor(f.formula,' c'+f.concepto.value);
    else alert("Debe seleccionar un Concepto");
    }

    ResponderEliminar
  5. Estimado:

    muy buenos los tutoriales. Me queda una duda con el redirectAction, segui las configuraciones que indicas en el struct.xml pero el redirect me lleva a un action que no existe (/results/redirectAction/redirect-action.action) soy nuevo en struct 2 y sigo sin identificar el problema.

    Asi tengo configurado el action:


    dispatcher


    Saludos y gracias por los tutoriales estan muy buenos

    ResponderEliminar
  6. Excelente tutorial, un poco mas del tema:

    www.javeritos.blogspot.com
    Programación java

    ResponderEliminar
  7. alguien me pude auxiliar tengo problemas en el redirectAction me sale que no hay definido result para success lo cual (obviamente ahi esta)

    ResponderEliminar
  8. Al parecer hay un problema con el nombre RedirectActionAction.java y el compilador o el ejecutor tienen problema al distinguir este nombre por que tiene dos Action embebidos. Simplemente cambien el nombre a RedirectAccionAction.java y en el struts.xml hagan lo mismo
    reinicien el servidor y estará solucionado.

    Saludos.

    ResponderEliminar
  9. Se puede realizar lo siguiente con Annotation, (En el archivo "struts.xml" declaramos nuevamente un elemento "" que se llamará "httpstatus501" y no será implementado por ninguna clase)

    muchas gracias

    ResponderEliminar