19 de diciembre de 2011

Struts 2 - Parte 4: Scopes de Objetos Web

Cuando estamos desarrollando una aplicación web debemos almacenar información que será procesada de distinta manera. Dependiendo de cuál sea el propósito de esta información querremos que tenga un tiempo de vida más corto o más largo, alguna información deberá permanecer disponible durante todo el momento que viva nuestra aplicación, mientras que otra solo nos interesará que viva durante una petición. Además habrá información que pertenecerá a cada usuario que acceda a la aplicación y que deberá estar disponible sólo para el usuario correspondiente.

Estos tiempos de vida son llamados scopes, y en las aplicaciones web tenemos un cierto número de ellos. Es importante conocer estos scopes y ver qué tipo de información es conveniente colocar en cada uno de ellos. A la información que colocamos en los distintos scopes les llamamos atributos.

También algunas veces es necesario tener un acceso directamente a los objetos del API de Servlets, como el "HttpServletRequest", o el "ServletContext", o a los parámetros de la petición,

Struts 2 nos proporciona una forma simple y elegante, además de diversa, para manejar todas estas cosas y en este tutorial aprenderemos estas maneras ^_^.

Las aplicaciones web con Java tienen básicamente tres scopes o tiempos de vida:

  • Application: Es el scope más largo ya que abarca el tiempo de vida completo de la apicación; esto es, los datos vivirán mientras la aplicación esté activa.
  • Session: Este scope nos permite tener datos que vivirán a lo largo de múltiples peticiones HTTP para un mismo usuario, mientras el usuario esté dentro de la aplicación. Cada usuario verá únicamente sus datos y no habrá forma de que vea los de los demás.
  • Request: Este es el scope más pequeño, los datos asociados con la petición únicamente estarán disponibles mientras se realiza dicha petición.


Existen algunos otros scopes, pero estos son los más y importantes, además no todos los frameworks proporcionan acceso a los demás scopes.

La información o atributos que se puede colocar dentro de estos scopes son pares nombre valor, en donde el nombre debe ser una cadena y el valor puede ser cualquier objeto que nosotros queramos.

Struts 2 nos proporciona tres formas para colocar y leer los atributos que se encuentren en estos scopes:

  • Implementación de interfaces Aware
  • Uso del objeto "ActionContext"
  • Uso del objeto "ServletActionContext"


Las tres son igual de sencillas y nos permiten obtener más o menos los mismos resultados.

Además podemos usar dos de los métodos anteriores, las interfaces Aware y el objeto "ServletActionContext", para obtener acceso directo a los objetos "HttpServletRequest" y "ServletContext"

Lo primero que haremos es crear un nuevo proyecto web en NetBeans. Vamos al menú "File -> New Project...". En la ventana que aparece seleccionamos la categoría "Java Web" y en el tipo de proyecto "Web Application". Presionamos el botón "Next >" y le damos un nombre y una ubicación a nuestro proyecto; presionamos nuevamente el botón "Next >" y en este punto se nos preguntará el servidor que queremos usar. En nuestro caso usaremos el servidor "Tomcat 7.0", con la versión 5 de JEE y presionamos el botón "Finish".

Una vez que tengamos nuestro proyecto debemos recordar agregar la biblioteca "Struts2" (o "Struts2Anotaciones" si van a hacer uso de anotaciones, como es mi caso ^_^), que creamos en el primer tutorial de la serie de Struts 2. Hacemos clic derecho sobre el nodo "Libraries" del proyecto. En el menú que aparece seleccionamos la opción "Add Library...". En la ventana que aparece seleccionamos la biblioteca "Struts2" o "Struts2Anotaciones" y presionamos "Add Library". Con esto ya tendremos los jars de Struts 2 en nuestro proyecto.

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


Comenzaremos viendo cómo poder agregar y leer datos en los scopes anteriores, y cómo mostrarlos en nuestras JSPs.


Manejo de Scopes

Manejo de Scopes usando interfaces Aware

Struts 2 proporciona un conjunto de interfaces llamadas interfaces Aware (bueno, creo que solo yo las llamo así ^_^) las cuales permite que nuestros Actions reciban cierta información, al momento de inicializarlos. La mayoría de estas interfaces proporcionan un Map con los pares nombre valor de los atributos de cada uno de los scopes.

Struts 2 contiene las siguientes interfaces Aware para obtener y leer atributos de los scopes:

  • ApplicationAware
  • SessionAware
  • RequestAware


Además de un par de interfaces para recibir el "HttpServletRequest" y el "ServletContext", pero hablaremos de ellas en su momento.

El uso de estas interfaces es muy intuitivo, creamos un Action que implemente la interface que nos interese. Cada una de estas interfaces proporciona un método setter que permite inyectar el mapa con los atributos del scope correspondiente.

Los métodos de estas interfaces se muestran a continuación:


public interface RequestAware
{
    public void setRequest(Map<String, Object> map);
}

public interface SessionAware
{
    public void setSession(Map<String, Object> map);
}

public interface ApplicationAware
{
    public void setApplication(Map<String, Object> map);
}


La forma de los tres métodos es prácticamente la misma: no regresan nada y reciben como único parámetro un Map donde el nombre del atributo es un String y el valor es un Object.

Como la forma de trabajo con estas tres interfaces es también el mismo, hagamos un ejemplo de todo en uno.

Lo primero que haremos es crear una nueva JSP llamada "datosScopesInterfaces.jsp":



Esta página contendrá un formulario que nos permitirá establecer el valor de algunos datos que colocaremos posteriormente en cada uno de los scopes. Como usaremos las etiquetas de Struts 2 para generar este formulario, lo primero que debemos hacer es indicar, con la directiva "taglib", que usaremos la bibliotecas de etiquetas de Struts 2:


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


Ahora crearemos un formulario con unos cuantos campos de texto, para colocar los valores que enviaremos a nuestro Action para ser procesados. Cada uno de estos valores tendrá un nombre que indica en cuál scope quedará. Este formulario se enviará a un Action llamado "scopesInterfaces" que crearemos un poco más adelante:


<s:form action="scopesInterfaces">
    <s:textfield name="datoSesion" label="Sesion" />
    <s:textfield name="datoRequest" label="Request" />
    <s:textfield name="datosAplicacion" label="Aplicacion" />
    <s:submit />
</s:form>


Ahora crearemos un nuevo paquete, llamado "com.javatutoriales.actions", para colocar nuestros Actions. Dentro de este paquete crearemos una clase llamada "ScopesInterfacesAction", esta clase deberá extender de "ActionSupport":


public class ScopesInterfacesAction extends ActionSupport
{
}


Agregaremos un atributo de tipo String, con sus correspondientes setters, para cada uno de los campos del formulario:


public class ScopesInterfacesAction extends ActionSupport
{
    private String datoSesion;
    private String datoRequest;
    private String datosAplicacion;

    public void setDatoRequest(String datoRequest)
    {
        this.datoRequest = datoRequest;
    }

    public void setDatoSesion(String datoSesion)
    {
        this.datoSesion = datoSesion;
    }

    public void setDatosAplicacion(String datosAplicacion)
    {
        this.datosAplicacion = datosAplicacion;
    }
}


Sólo hemos colocado los setters porque estableceremos estos valores en los scopes correspondientes y, por lo tanto, los leeremos posteriormente de una manera diferente ^_^.

Ahora el tema que nos interesa, las interfaces. Como toda buena interface de Java, para implementar las interfaces Aware lo primero que debemos hacer es declarar que nuestra clase implementará estas interfaces; implementaremos las tres interfaces de una sola vez:


public class ScopesInterfacesAction extends ActionSupport implements RequestAware, SessionAware, ApplicationAware
{
}


Como mencioné antes: estas interfaces tienen solo un método cada una (el método que mencioné anteriormente). Normalmente colocamos un atributo, de tipo "Map<String, Object>" para almacenar el argumento que es establecido por el framework y poder usarlo posteriormente en el método "execute" de nuestro Action; después todo lo que hay que hacer es implementar cada uno de los métodos de estas interfaces de la siguiente forma:


private Map<String, Object> sesion;
private Map<String, Object> application;
private Map<String, Object> request;

public void setRequest(Map<String, Object> map)
{
    this.request = map;
}

public void setApplication(Map<String, Object> map)
{
    this.application = map;
}

public void setSession(Map<String, Object> map)
{
    this.sesion = map;
}


En el método "execute" lo único que haremos es agregar el valor recibido desde el formulario al scope correspondiente, usando el "Map" apropiado:


@Override
public String execute() throws Exception
{
    application.put("datoAplicacion", datosAplicacion);
    sesion.put("datoSesion", datoSesion);
    request.put("datoRequest", datoRequest);
        
    return SUCCESS;
}


Así de simple ^^.

Para terminar este Action debemos agregar las anotaciones que vimos en el primer tutorial de la serie, y que a estas alturas ya conocemos de memoria. El nombre de nuestro Action será "scopesInterfaces" y el resultado será enviado a la página "resultado.jsp":


@Namespace(value = "")
@Action(value = "scopesInterfaces", results ={@Result(name = "success", location = "/resultado.jsp")})
public class ScopesInterfacesAction extends ActionSupport implements RequestAware, SessionAware, ApplicationAware
{
}


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


@Namespace(value = "")
@Action(value = "scopesInterfaces", results ={@Result(name = "success", location = "/resultado.jsp")})
public class ScopesInterfacesAction extends ActionSupport implements RequestAware, SessionAware, ApplicationAware
{
    private String datoSesion;
    private String datoRequest;
    private String datosAplicacion;
    private Map<String, Object> sesion;
    private Map<String, Object> application;
    private Map<String, Object> request;

    @Override
    public String execute() throws Exception
    {
        application.put("datoAplicacion", datosAplicacion);
        sesion.put("datoSesion", datoSesion);
        request.put("datoRequest", datoRequest);

        return SUCCESS;
    }

    public void setRequest(Map<String, Object> map)
    {
        this.request = map;
    }

    public void setApplication(Map<String, Object> map)
    {
        this.application = map;
    }

    public void setSession(Map<String, Object> map)
    {
        this.sesion = map;
    }

    public void setDatoRequest(String datoRequest)
    {
        this.datoRequest = datoRequest;
    }

    public void setDatoSesion(String datoSesion)
    {
        this.datoSesion = datoSesion;
    }

    public void setDatosAplicacion(String datosAplicacion)
    {
        this.datosAplicacion = datosAplicacion;
    }
}


Ahora crearemos la página "resultado.jsp", en la raíz de las páginas web, para comprobar que los datos se han establecido correctamente. En esta página primero indicaremos que haremos uso de la biblioteca de etiquetas de Struts 2, como lo hicimos anteriormente. Además usaremos la etiqueta "<s:property>" para mostrar el valor en cada uno de los scopes. Para obtener el valor usaremos OGNL con el cual, como recordarán del segundo tutorial de la serie, tiene una sintaxis especial para los objetos que se encuentran en los scopes anteriores. Si no recuerdan la sintaxis pueden regresar a ver el tutorial correspondiente... o continuar leyendo este.

Recuerden que Struts 2 coloca en el "ActionContext" algunos objetos extra además del objeto raíz, para acceder a estos objetos necesitamos usar un operador especial, el operador "#", junto con el nombre del objeto al que queremos acceder. En este caso Struts 2 coloca 3 objetos especiales que hacen referencia cada uno a uno a uno de los distintos scopes:

  • application
  • session
  • request


Y como habíamos visto, es posible obtener el valor de los atributos colocados en estos scopes usando el operador punto (".") o colocando el nombre del parámetro entre corchetes ("[" y "]").

En este caso, para mostrar el valor almacenado en el request debemos hacer:


<s:property value="#request.datoRequest" />


Para el resto de los parámetros se hace algo similar. Al final la página queda de la siguiente forma:


Request: <s:property value="#request.datoRequest" /><br />
Sesión: <s:property value="#session.datoSesion" /><br />
Aplicacion: <s:property value="#application.datoAplicacion" /><br />


Cuando ejecutemos la aplicación y entremos a la siguiente dirección:


http://localhost:8080/scopes/datosScopesInterfaces.jsp


Debemos ver el formulario que creamos anteriormente. Si colocamos los siguientes datos:



Y presionamos el botón de enviar, veremos la siguiente salida:



Ahora, ¿cómo podemos comprobar que cada dato está efectivamente en los scopes indicados? Bueno para esto hay varias formas. Primero, para comprobar el atributo del request podemos entrar directamente a la página del resultado:


http://localhost:8080/scopes/resultado.jsp


Con esto estamos creando una nueva petición, por lo que el atributo que teníamos anteriormente debería desaparecer:



Para el dato de la sesión, podemos esperar a que termine nuestra sesión de forma automática (después de 30 minutos), podemos usar otro navegador para entrar a la misma dirección, o simplemente podemos terminar las sesiones activas de nuestro navegador. Con esto los atributos de la sesión deben desaparecer:



Finalmente, y como podemos ver por la imagen anterior del explorer, los atributos del scope aplicación permanecerán hasta que detengamos nuestro servidor (con lo cual, por cierto, ya no podremos entrar al sitio ^_^).

Como podemos ver, esta forma de establecer (y leer) atributos de los distintos scopes es bastante sencilla pero, como dijimos al inicio del tutorial: no es la única forma. Ahora veremos la segunda forma de hacer esto: usado el objeto "ActionContext"


Manejo de Atributos de Scope usando ActionContext

La segunda forma que tenemos de manejar los atributos de los scopes es a través de un objeto especial de Struts 2 llamado "ActionContext". Este objeto es el contexto en al cual el Action se ejecuta. Cada contexto es básicamente un contenedor para objetos que un Action necesita para su ejecución, como la sesión, sus parámetros, etc.

Dentro de los objetos que se encuentran dentro del "ActionContext" están justamente los mapas que contienen los atributos de sesion y aplicación (así es, no se pueden establecer los atributos de request usando este objeto), que son los que nos interesan para esta pare del tutorial.

Obtener una referencia al "ActionContext" es en realidad muy sencillo ya que, aunque este objeto no es un singleton como tal, lo obtenemos como si fuera uno; veremos esto dentro de un momento, primero crearemos una nueva página que contendrá un formulario como el anterior.

Creamos una nueva página llamada "datosScopesActionContext.jsp". Esta página contendrá un formulario parecido al que creamos anteriormente, con la diferencia de que los datos de este serán procesados por un Action distinto:


<s:form action="scopesActionContext">
    <s:textfield name="datoSesion" label="Sesion" />
    <s:textfield name="datosAplicacion" label="Aplicacion" />
    <s:submit />
</s:form>


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


public class ScopesActionContextAction extends ActionSupport
{
}


Esta clase tendrá dos atributos de tipo String, uno para el dato que irá en sesión y otro para el dato que irá en el scope aplicación, con sus correspondientes setters:


public class ScopesActionContextAction extends ActionSupport
{
    private String datoSesion;
    private String datosAplicacion;

    public void setDatoSesion(String datoSesion)
    {
        this.datoSesion = datoSesion;
    }

    public void setDatosAplicacion(String datosAplicacion)
    {
        this.datosAplicacion = datosAplicacion;
    }
}


Lo último que esta clase necesita es sobre-escribir su método "execute" para, haciendo uso del "ActionContext", establecer los valores en los scopes adecuados.

Para obtener una instancia del "ActionContext" se usa el método estático "getContext()" que nos regresa la instancia de "ActionContext" adecuada para el Action que estamos ejecutando:


ActionContext contexto = ActionContext.getContext();


Y una vez teniendo una referencia al "ApplicationContext" lo único que debemos hacer es usar el método "getApplication()" para obtener un mapa con los atributos del scope application y "getSession()" para obtener un mapa con los atributos del scope session:


Map<String, Object> application = contexto.getApplication();
Map<String, Object> sesion = contexto.getSession();


El resto es solo colocar los atributos que recibidos del formulario en los mapas correspondientes:


application.put("datoAplicacion", datosAplicacion);
sesion.put("datoSesion", datoSesion);


El método "execute" queda de la siguiente forma:


@Override
public String execute() throws Exception
{
    ActionContext contexto = ActionContext.getContext();
        
    Map<String, Object> application = contexto.getApplication();
    Map<String, Object> sesion = contexto.getSession();
        
    application.put("datoAplicacion", datosAplicacion);
    sesion.put("datoSesion", datoSesion);

    return SUCCESS;
}


Lo último que falta es anotar nuestro Action para indicar que responderá al nombre de "scopesActionContext" y que regresará a la página "/resultado.jsp" (la cual creamos para el ejemplo anterior así que ahora la reutilizaremos):


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


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


@Namespace(value = "/")
@Action(value = "scopesActionContext", results ={@Result(name = "success", location = "/resultado.jsp")})
public class ScopesActionContextAction extends ActionSupport
{
    private String datoSesion;
    private String datosAplicacion;

    @Override
    public String execute() throws Exception
    {
        ActionContext contexto = ActionContext.getContext();
        
        Map<String, Object> application = contexto.getApplication();
        Map<String, Object> sesion = contexto.getSession();
        
        application.put("datoAplicacion", datosAplicacion);
        sesion.put("datoSesion", datoSesion);

        return SUCCESS;
    }

    public void setDatoSesion(String datoSesion)
    {
        this.datoSesion = datoSesion;
    }

    public void setDatosAplicacion(String datosAplicacion)
    {
        this.datosAplicacion = datosAplicacion;
    }
}


Ahora que ya tenemos todo listo ejecutamos la aplicación y al entrar a la siguiente dirección:


http://localhost:8080/scopes/datosScopesActionContext.jsp


Debemos ver el siguiente formulario, muy parecido al anterior:



Cuando ingresemos algunos valores y demos clic en el botón de enviar, veremos un resultado como el siguiente:



El dato del request queda vacio ya que no lo hemos establecido en esta ocasión.

Podemos ver que esta segunda forma de establecer los datos es también muy simple de utilizar y da buenos resultados.

Veremos la tercer y última forma de manejar los atributos de los scopes request, session y application. Debo decir que esta tercer forma no es recomendable ya que rompe con el esquema de trabajo de Struts 2, el cual oculta detalles de la especificación de Servlets, pero eso lo veremos en un momento.


Manejo de Atributos de Scope usando ServletActionContext

Con esta última forma tendremos acceso directo a los objetos "HttpServletRequest" y "ServletContext", de la especificación de Servlets; haciendo uso de otro objeto especial de Struts 2: "ServletActionContext".

Este objeto especial "ServletActionContext" contiene una serie de métodos estáticos que nos dan acceso a otros objetos de utilidad (entre ellos "ActionContext"). Los métodos que en este momento nos interesan son: "getRequest", con el que podemos obtener el objeto "HttpServletRequest" (desde el cual tenemos además acceso a la sesión) asociado con la petición que el Action está sirviendo, y "getServletContext", que nos regresa el objeto "ServletContext" que representa el contexto de la aplicación web.

El uso de estos dos métodos es muy directo y lo veremos en un momento, pero primero crearemos una nueva página JSP que contendrá el formulario, como los anteriores, que nos permitirá introducir los datos que se agregarán como atributos de cada uno de los scopes. Esta página se llamará "datosScopesServletActionContext.jsp" y contendrá un formulario como el primero que creamos con la diferencia de los datos de este formulario serán procesados por otro Action:


<s:form action="scopesServletActionContext">
    <s:textfield name="datoSesion" label="Sesion" />
    <s:textfield name="datoRequest" label="Request" />
    <s:textfield name="datosAplicacion" label="Aplicacion" />
    <s:submit />
</s:form>


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


public class ScopesServletActionContextAction extends ActionSupport
{
}


Esta clase tendrá tres atributos de tipo String, con sus correspondientes setters, uno para cada valor que recibimos del formulario:


public class ScopesServletActionContextAction extends ActionSupport
{
    private String datoSesion;
    private String datoRequest;
    private String datosAplicacion;

    public void setDatoRequest(String datoRequest)
    {
        this.datoRequest = datoRequest;
    }

    public void setDatoSesion(String datoSesion)
    {
        this.datoSesion = datoSesion;
    }

    public void setDatosAplicacion(String datosAplicacion)
    {
        this.datosAplicacion = datosAplicacion;
    }
}


Ahora sobre-escribiremos el método "execute" para, haciendo uso de los métodos estáticos de la clase "ServletActionContext", obtener referencias al "HttpServletRequest" y al "ServletContext". Como había dicho "ServletActionContext" nos proporciona métodos estáticos que nos regresan estas referencias de forma directa, por lo que lo único que tenemos que hacer es invocarlos:


HttpServletRequest request = ServletActionContext.getRequest();
ServletContext context = ServletActionContext.getServletContext();


La sesión (el objeto "HttpSession") se obtiene usando el método "getSession()" del objeto "request":


HttpSession sesion = request.getSession();


Y ya con estas referencias podemos obtener los atributos de los scopes correspondientes como lo hacemos cuando trabajamos directamente con Servlets: usando el método "setAttribute" de cada uno de los objetos:


request.setAttribute("datoRequest", datoRequest);
sesion.setAttribute("datoSesion", datoSesion);
context.setAttribute("datoAplicacion", datosAplicacion);


Y eso es todo lo que hay que hacer. El método "execute" queda de la siguiente forma:


@Override
public String execute() throws Exception
{
    HttpServletRequest request = ServletActionContext.getRequest();
    ServletContext context = ServletActionContext.getServletContext();
    HttpSession sesion = request.getSession();
        
    request.setAttribute("datoRequest", datoRequest);
    sesion.setAttribute("datoSesion", datoSesion);
    context.setAttribute("datoAplicacion", datosAplicacion);
        
    return SUCCESS;
}


Lo único que resta hacer es colocar las anotaciones para indicar que esta clase será un Action. Al igual que en los casos anteriores, reutilizaremos la página "resultados.jsp". Como ya conocemos estas anotaciones de memoria, solo mostraré como queda finalmente la clase "ScopesServletActionContextAction"


@Namespace(value = "")
@Action(value = "scopesServletActionContext", results ={@Result(name = "success", location = "/resultado.jsp")})
public class ScopesServletActionContextAction extends ActionSupport
{
    private String datoSesion;
    private String datoRequest;
    private String datosAplicacion;

    @Override
    public String execute() throws Exception
    {
        HttpServletRequest request = ServletActionContext.getRequest();
        ServletContext context =   ServletActionContext.getServletContext();
        HttpSession sesion = request.getSession();
        
        request.setAttribute("datoRequest", datoRequest);
        sesion.setAttribute("datoSesion", datoSesion);
        context.setAttribute("datoAplicacion", datosAplicacion);
        
        return SUCCESS;
    }

    
    
    public void setDatoRequest(String datoRequest)
    {
        this.datoRequest = datoRequest;
    }

    public void setDatoSesion(String datoSesion)
    {
        this.datoSesion = datoSesion;
    }

    public void setDatosAplicacion(String datosAplicacion)
    {
        this.datosAplicacion = datosAplicacion;
    }
}


Cuando ejecutemos nuestra aplicación y entremos a la siguiente dirección:


http://localhost:8080/scopes/datosScopesServletActionContext.jsp


Debemos ver el mismo formulario que en el primer ejemplo:



Al rellenar los campos con algunos datos y enviar el formulario debemos ver el siguiente resultado:



Como podemos ver esta tercer forma abarca en realidad los dos temas del tutorial, establecer valores en los scopes de la aplicación web y obtener los objetos "HttpServletRequest" y "ServletContext", así que a continuación veremos la segunda forma de obtener estos objetos:


Obtención de Objetos "HttpServletRequest" y "ServletContext"


Obtención de Objetos de Servlet usando interfaces Aware

En la primer parte del tutorial vimos cómo trabajan las interfaces Aware, y en el ejemplo anterior vimos cómo tener acceso directo a los objetos de la especificación de Servlets. Así que en este último ejemplo veremos una forma de obtener estos objetos, usando las interfaces Aware.

Para no perder la costumbre de este tutorial, lo primero que haremos es crear una nueva JSP llamada "datosServletsInterfaces.jsp" que contendrá el formulario que también ya debemos conocer de memoria ^_^, en donde, nuevamente, lo único que cambiará es el valor del atributo "action":


<s:form action="datosInterfaces">
    <s:textfield name="datoSesion" label="Sesion" />
    <s:textfield name="datoRequest" label="Request" />
    <s:textfield name="datosAplicacion" label="Aplicacion" />
    <s:submit />
</s:form>


Ahora crearemos una nueva clase llamada "ObjetosServletAction", en el paquete "actions". Esta clase deberá extender "ActionSupport":


public class ObjetosServletAction extends ActionSupport
{   
}


Struts 2 permite obtener una referencia al objeto "HttpServletRequest", que representa la petición actual que está siendo atendida por el Action, y al objeto "ServletContext", que representa el contexto de la aplicación web, implementando las interfaces "ServletRequestAware" y "ServletContextAware" respectivamente.

Ambas interfaces son igual de sencillas que sus contrapartes para los atributos de los scopes. La interface "ServletContextAware" luce de la siguiente forma:


interface ServletContextAware
{  
    public void setServletContext(ServletContext sc);
}


Y la interface "ServletRequestAware" se ve así:


interface ServletRequestAware
{
    public void setServletRequest(HttpServletRequest hsr);
}


Como podemos ver, ambas interfaces tienen tan solo un método que debemos implementar, y que reciben el objeto adecuado perteneciente a la especificación de Servlets (en vez de algún objeto propio de Struts 2 o algún objeto genérico, como estamos acostumbrados al trabajar con este framework), por lo que al implementar estos métodos ya tenemos una referencia directa a estos objetos sin tener que hacer ningún otro proceso, conversión, o invocación.

Junto con la implementación de estas interfaces proporcionaremos un par de atributos, uno de tipo "ServletContext" y uno de tipo "HttpServletRequest", para almacenar los valores recibidos con la implementación.

Así que lo primero que hacemos entonces es declarar que nuestra clase "ObjetosServletAction" implementará las interfaces "ServletContextAware" y "ServletRequestAware":


public class ObjetosServletAction extends ActionSupport implements ServletContextAware, ServletRequestAware
{
}


Ahora colocaremos los atributos (sin getters ni setters) para almacenar las referencias recibidas por los métodos de las interfaces anteriores:


private ServletContext application;
private HttpServletRequest request;


Lo siguiente es proporcionar la implementación de las interfaces, en los cuales almacenaremos las referencias recibidas por estas, en los atributos que hemos colocado en nuestra clase:


public void setServletContext(ServletContext sc)
{
    this.application = sc;
}

public void setServletRequest(HttpServletRequest hsr)
{
    this.request = hsr;
}


Ahora que ya tenemos las referencias que nos interesaban agregaremos los tres atributos, junto con sus setters, que nos permitirán recibir los parámetros provenientes del formulario:


private String datoSesion;
private String datoRequest;
private String datosAplicacion;

public void setDatoRequest(String datoRequest)
{
    this.datoRequest = datoRequest;
}

public void setDatoSesion(String datoSesion)
{
    this.datoSesion = datoSesion;
}

public void setDatosAplicacion(String datosAplicacion)
{
    this.datosAplicacion = datosAplicacion;
}


El siguiente paso es sobre-escribir el método "execute" para establecer los valores de los atributos en cada uno de los scopes. Primero obtendremos el objeto "HttpSession" usando el "HttpServletRequest", como en el ejemplo anterior, para posteriormente poder establecer los atributos en los scopes usando los objetos apropiados:


@Override
public String execute() throws Exception
{
    HttpSession sesion = request.getSession();
        
    application.setAttribute("datoAplicacion", datosAplicacion);
    sesion.setAttribute("datoSesion", datoSesion);
    request.setAttribute("datoRequest", datoRequest);
        
    return SUCCESS;
}


Como ven, la lógica del método es tan sencilla como en todo el tutorial.

El último paso es anotar esta clase para indicarle a Struts 2 que se trata de un Action. Una vez más reutilizaremos la página "resultado.jsp" para mostrar los resultados de la ejecución del Action:


@Namespace(value = "")
@Action(value = "datosInterfaces", results ={@Result(name = "success", location = "/resultado.jsp")})
public class ObjetosServletAction extends ActionSupport implements ServletRequestAware, ServletContextAware
{
}


Al final la clase "ObjetosServletAction" queda de la siguiente forma:


@Namespace(value = "")
@Action(value = "datosInterfaces", results ={@Result(name = "success", location = "/resultado.jsp")})
public class ObjetosServletAction extends ActionSupport implements ServletRequestAware, ServletContextAware
{
    private ServletContext application;
    private HttpServletRequest request;
    private String datoSesion;
    private String datoRequest;
    private String datosAplicacion;

    @Override
    public String execute() throws Exception
    {
        HttpSession sesion = request.getSession();
        
        application.setAttribute("datoAplicacion", datosAplicacion);
        sesion.setAttribute("datoSesion", datoSesion);
        request.setAttribute("datoRequest", datoRequest);
        
        return SUCCESS;
    }
    
    public void setServletContext(ServletContext sc)
    {
        this.application = sc;
    }

    public void setServletRequest(HttpServletRequest hsr)
    {
        this.request = hsr;
    }

    public void setDatoRequest(String datoRequest)
    {
        this.datoRequest = datoRequest;
    }

    public void setDatoSesion(String datoSesion)
    {
        this.datoSesion = datoSesion;
    }

    public void setDatosAplicacion(String datosAplicacion)
    {
        this.datosAplicacion = datosAplicacion;
    }
}


Cuando ejecutemos la aplicación y entremos a la siguiente dirección:


http://localhost:8080/scopes/datosServletsInterfaces.jsp


Debemos ver el formulario al que ahora ya debemos estar acostumbrados ^_^!:



Y, nuevamente, al ingresar algunos datos y presionar el botón enviar debemos ver la siguiente salida:



Como podemos ver, una vez más todo ha funcionado correctamente.

Algunos se preguntarán ¿para qué queremos tener acceso a estos objetos si Struts 2 se encarga de manejar todos los aspectos relacionados con Servlets por nosotros? Bueno me alegra que hagan esa pregunta ^_^.

En realidad esto tiene varios usos, los dos más utilizados que se me ocurren en este momento es el poder obtener la ubicación absoluta de un recurso (un archivo o algún otro elemento) que se encuentra en nuestra aplicación y que por lo tanto no sabemos en qué directorio del disco duro está. Para esto se necesita el método "getRealPath" del objeto "ServletContext" de la siguiente forma:


application.getRealPath("/recurso.ext");


El segundo y más común uso es para invalidar una sesión. Struts 2 no nos da directamente una manera para invalidar la sesión de un usuario de nuestra aplicación web, así que para esto es necesario tener objeto al objeto "HttpSession", y para esto al objeto "HttpServletRequest":


request.getSession().invalidate();


Así que, como podemos ver, algunas veces es útil tener acceso a los objetos de la específicación de Servlets cuando el framework que estamos usando no nos pueda, por la razón que sea, darnos todas las facilidades que necesitemos para hacer algunas cosas específicas.

Esto es todo para este tutorial, espero que les sea de utilidad. En el siguiente tutorial de la serie de Struts 2, veremos los tipos de results que vienen incluidos con Struts 2 y para que pueden servirnos.

No olviden dejar cualquier duda, comentario, o sugerencia que puedan tener.

Saludos.

Descarga los archivos de este tutorial desde aquí:



Entradas Relacionadas: