martes, 21 de junio de 2011

Struts 2 - Parte 2: OGNL

OGNL es el acrónimo de Object Graph Navigation Language, un lenguaje de expresiones muy poderoso que nos permite leer valores de objetos Java. Este lenguaje nos permite leer valores y ejecutar métodos (que regresen algún valor) para mostrar los valores o resultados de los mismos en nuestras páginas JSP creadas usando las etiquetas de Struts. Además proporciona una conversión automática de tipos que permite convertir datos desde texto HTTP a objetos Java.

En este tutorial aprenderemos a usar este sencillo pero poderoso lenguaje dentro de nuestras aplicaciones, así como los objetos implícitos que tiene y cómo acceder a ellos. Además veremos cómo obtener valores de constantes, variables, y elementos enumerados, que se encuentran en nuestras clases.

Struts no funciona exactamente usando una versión estándar de OGNL, usa una versión propia a la que agrega ciertas características interesantes.

OGNL usa un contexto estándar de nombres para evaluar las expresiones, esto quiere decir que dependiendo de qué tan "profundo" esté nuestro objeto en el grafo, podremos hacer referencia a él de distintas formas. El objeto de más alto nivel en OGNL es un Map, al cual llamamos "mapa de contexto" (context map) o simplemente "contexto".

OGNL maneja siempre un objeto raíz dentro del contexto. Este objeto raíz es el objeto default al que se hacen las llamadas, a menos que se indique lo contrario. Cuando usamos una expresión, las propiedades del objeto raíz pueden ser referenciadas sin ninguna marca especial, esto quiere decir que si nuestro objeto tiene una propiedad llamada "nombre", hacemos referencia a él simplemente con la expresión "nombre". Las referencias a otros objetos son marcadas con un signo de número (#).

Para entender esto veamos un ejemplo de cómo funciona esto en el OGNL estándar. Supongamos que hay dos objetos en el mapa de contexto de OGNL: "foo" y "bar", y que el objeto "foo" es el objeto raíz. El siguiente código muestra cómo resuelve OGNL los valores pedidos:


#foo.blah   //regresa foo.getBlah()
#bar.blah   //regresa  bar.getBlah()
blah        //regresa foo.getBlah(), porque foo es la raíz


Esto quiere decir que OGNL permite que haya varios objetos en el contexto, pero solo podemos acceder a los miembros del objeto raíz directamente. También es importante mencionar aquí que cuando hacemos referencia a una propiedad como "blah", OGNL buscará un método "getBlah()", que regrese algún valor y no reciba parámetros, para obtener el valor que mostrará.

Cuando queremos invocar métodos usamos el nombre del método junto con paréntesis, como en una invocación normal de Java. En el caso anterior pudimos haber hecho algo como lo siguiente:


#foo.getBlah()
#bar.getBlah()


En el OGNL estándar solo se tiene una raíz, sin embargo en el OGNL de Struts 2 se tiene un "ValueStack", el cual permite simular la existencia de varias raíces. Todos los objetos que pongamos en el "ValueStack" se comportarán como la raíz del mapa de contexto.

En el caso de Struts 2, el framework establece el contexto como un objeto de tipo "ActionContext", que es el contexto en el cual se ejecuta un Action (cada contexto es básicamente un contenedor de objetos que un Action necesita para su ejecución, como los objetos "session", "parameters", "locale", etc.), y el "ValueStack" como el objeto raíz. El "ValueStack" es un conjunto de muchos objetos, pero para OGNL este aparenta ser solo uno.

Debido al "ValueStack", al que algunas veces llamamos solo "stack", en vez de que nuestras expresiones tengan que obtener el objeto que queremos del stack y después obtener las propiedades de él (como en el ejemplo de #bar.blah), el OGNL de Struts 2 tiene un "PropertyAccessor" especial que buscará automáticamente en todos los objetos del stack (de arriba a abajo) hasta que encuentre un objeto con la propiedad que estamos buscando.

Siempre que Struts 2 ejecuta uno de nuestros Actions, los coloca en la cima del stack, es por eso que en el tutorial anterior hacíamos referencia a la propiedad llamada "mensaje", del Action correspondiente, solamente indicando el nombre de la propiedad. Struts 2 busca en el stack un objeto que tenga un getter para esa propiedad, en este caso nuestro Action.

Veamos otro ejemplo. Supongamos que el stack contiene dos objetos: "Animal" y "Persona". Ambos objetos tienen una propiedad "nombre", "Animal" tiene una propiedad "raza", y "Persona" tiene una propiedad "salario". "Animal" está en la cima del stack, y "Persona" está debajo de él. Si hacemos llamadas simples, como las mostradas a continuación, OGNL resuelve los valores de la siguiente forma:


raza     //llama a animal.getRaza()
salario  //llama a persona.getSalario()
nombre   //llama a animal.getNombre(), porque animal está en la cima del stack


En el ejemplo anterior, se regresó el valor del "nombre" del Animal, que está en la cima del stack. Normalmente este es el comportamiento deseado, pero algunas veces nos interesa recuperar el valor de la propiedad de un objeto que se encuentra más abajo en el stack. Para hacer esto, Struts 2 agrega soporte para índices en el ValueStack. Todo lo que debemos hacer es:


[0].nombre  //llama a animal.getNombre()
[1].nombre  //llama a persona.getNombre()


Con esto le indicamos a OGNL a partir de cuál índice queremos que inicie la búsqueda (digamos que cortamos el stack a partir del índice que le indicamos).

Suficiente teoría :D. Comencemos a ver lo anterior en código. Crearemos un nuevo proyecto web en NetBeans. Para esto 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":



Con esto aparecerá en nuestro editor una página "index.jsp".

Ahora agregamos la librería "Struts2Anotaciones" que creamos en el tutorial anterior. 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 "Struts2Anotaciones" y presionamos "Add Library". Con esto ya tendremos los jars de Struts 2 en nuestro proyecto:



Finalmente abrimos el archivo "web.xml" y agregamos la configuración del filtro de Struts 2, de la misma forma que lo hicimos en el tutorial anterior:


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


Ya tenemos todo listo para comenzar. Lo primero que haremos en este ejemplo es crear un paquete que contendrá nuestras clases "Animal" y "Persona". En mi caso el paquete se llamará "com.javatutoriales.struts2.ognl.modelo". En el agregamos dos clases: "Animal" y "Persona".

La clase "Animal" tendrá, como habíamos dicho, dos atributos de tipo String: "nombre" y "raza", con sus correspondientes setters y getters:


public class Animal
{
    private String nombre;
    private String raza;

    public String getNombre()
    {
        return nombre;
    }

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

    public String getRaza()
    {
        return raza;
    }

    public void setRaza(String raza)
    {
        this.raza = raza;
    }
}


La clase "Persona" también contendrá dos atributos de tipo String: "nombre" y "salario", con sus correspondientes setters y getters:


public class Persona
{
    private String nombre;
    private String salario;

    public String getNombre()
    {
        return nombre;
    }

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

    public String getSalario()
    {
        return salario;
    }

    public void setSalario(String salario)
    {
        this.salario = salario;
    }
}


Ahora crearemos el Action que se encargará de poner una instancia de cada una de estas clases en el "ValueStack". Creamos una nueva clase llamada "StackAction" y hacemos que esta extienda de "ActionSupport":


public class StackAction extends ActionSupport
{
}


Agregamos las anotaciones correspondientes, y que explicamos en el tutorial anterior. Haremos que este Action responda al nombre de "stack" y que nos envíe a una página llamada"/stack.jsp":


@Namespace(value="/")
@Action(value="stack", results={@Result(location="/stack.jsp")})
public class StackAction extends ActionSupport
{
}


Lo siguiente que haremos es sobre-escribir el método "execute" del Action para obtener una referencia al ValueStack. La forma de hacer esto último es a través de un método estático de la clase "ActionContext":


@Override
public String execute() throws Exception
{
    ValueStack stack = ActionContext.getContext().getValueStack();
}


Una vez que tenemos esta referencia solo nos resta crear una instancia de cada una de nuestras clases, establecer los valores de sus parámetros, y agregarlos al ValueStack usando su método "push":


@Override
public String execute() throws Exception
{
    ValueStack stack = ActionContext.getContext().getValueStack();

    Animal animal = new Animal();
    animal.setNombre("nombre del animal");
    animal.setRaza("perro labrador");

    Persona persona = new Persona();
    persona.setNombre("nombre de la persona");
    persona.setSalario("realmente poco");


    stack.push(persona);
    stack.push(animal);

    return SUCCESS;
}


Nota: Por lo regular no agregamos objetos al stack de forma manual como lo estamos haciendo para este ejemplo, dejamos que sea Struts quien agregue los objetos necesarios de forma automática. Esto solo lo hacemos en casos en los que no queda otra opción... como en este ejemplo ^_^!

Agregamos primero la referencia de la Persona y luego la del Animal porque, como en toda buena pila, el último elemento que se agregue al ValueStack será el que quede en su cima.

Ahora creamos la página "stack.jsp" en el directorio raíz de las páginas web. En esta página indicamos que se usará la biblioteca de etiquetas de Struts 2:


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


Usando la etiqueta "<s:property>" mostraremos los valores de "raza", "salario", y "nombre". Al colocarlos de esta forma, Struts buscará estos valores en todos los objetos que estén en el ValueStack:


<ul>
    <li><strong>Raza: </strong> <s:property value="raza" /></li>
    <li><strong>Salario: </strong> <s:property value="salario" /></li>
    <li><strong>Nombre: </strong> <s:property value="nombre" /></li>
</ul>


Ya está todo listo para correr el ejemplo. Ejecutamos nuestra aplicación, y entramos a la siguiente dirección:


http://localhost:8080/ognl/stack.action


Deberemos ver una pantalla como la siguiente:



Como podemos ver, la teoría es correcta ^_^. Al buscar la propiedad "raza", la encuentra en el objeto tipo "Animal", mostrando el valor de la misma; cuando busca la propiedad salario la encuentra en el objeto de tipo "Persona"; y al buscar la propiedad "nombre", que tienen ambos objetos, muestra el valor del objeto que se encuentra en la cima del stack, o sea el de "Animal".

Ahora hagamos una segunda prueba haciendo uso de los índices del ValueStack. Agreguemos las instrucciones que habíamos visto anteriormente: ("[0].nombre" y "[1].nombre") usando la etiqueta "s:property":


<ul>
    <li><strong>Animal:  </strong><s:property value="[0].nombre" /></li>
    <li><strong>Persona: </strong><s:property value="[1].nombre" /></li>
</ul>


Si volvemos a ejecutar nuestra aplicación veremos la siguiente salida:



Como podemos ver, efectivamente, el uso de índices permite que seleccionemos valores de objetos que se encuentran a una profundidad mayor en el ValueStack.

Solo para recordar. Cuando Struts 2 ejecuta un Action como consecuencia de una petición, este action es colocado en la cima del ValueStack, es por esto que podemos acceder a sus atributos haciendo una llamada directa al nombre del mismo, como lo hicimos en el tutorial anterior.

Hagamos nuevamente una prueba para explicar un poco más en detalle lo que ocurre en ese caso. Primero crearemos una clase llamada "SaludoAction", la cual será igual a la del tutorial anterior:


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

    @Override
    public String execute() throws Exception
    {
        mensaje = "Hola Mundo!!";

        return SUCCESS;
    }

    public String getMensaje()
    {
        return mensaje;
    }
}


En este caso nuestro Action tiene un atributo "mensaje" que en el método "execute" será establecido con un valor. También se proporciona un getter para este atributo.

Crearemos una página JSP llamada "saludo.jsp", en la raíz de las páginas web de la aplicación. En esta página mostraremos el valor del atributo "mensaje" usando la biblioteca de etiquetas de Struts 2:


<s:property value="mensaje" />


Ejecutamos la aplicación, y al acceder a la siguiente dirección:


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


Debemos ver la siguiente página:



Una vez más, solo para que quede claro. Cuando realizamos una petición para el Action, el DispatcherFilter ejecutará el método "execute" del Action, después colocará este Action en la cima del ValueStack con lo cual lo tendremos disponible.

Cuando hacemos la petición para el atributo "mensaje", usando la etiqueta "s:property", Struts busca en el ValueStack, de arriba a abajo, un objeto que tenga un método "getMensaje()". Como el primer objeto que encuentra con el método "getMensaje()" es "SaludoAction" (de hecho es el primer objeto en el que busca) ejecuta este método mostrando el valor correspondiente mostrando la pantalla que vimos anteriormente.

Además del ValueStack, Struts 2 coloca otros objetos en el ActionContext, incluyendo Maps que representan los contextos de "application", "session", y "request". Una representación de esto puede verse en la siguiente imagen:



Recuerden que para hacer referencia a los objetos del contexto que NO son la raíz debemos preceder el nombre del objeto con el símbolo de gato (#).

Hablaré brevemente de estos objetos:

  • "application" representa el ApplicationContext, o sea, el contexto completo de la aplicación.
  • "session" representa el HTTPSession, o sea, la sesión del usuario.
  • "request" representa el ServletRequest, o sea, la petición que se está sirviendo.
  • "parameters" representa los parámetros que son enviados en la petición, ya sea por GET o por POST.
  • "attr" representa los atributos de los objetos implícitos. Cuando preguntamos por un atributo, usando "attr", este busca el atributo en los siguientes scopes: page, request, session, application. Si lo encuentra, regresa el valor del atributo y no continúa con la búsqueda, sino regresa un valor nulo.

Veamos un ejemplo de esto. No entraré en muchos detalles sobre el manejo de la sesión ya que eso lo dejaré para el próximo tutorial.

Creamos una nueva clase Java llamada "DatosAction" que extienda de "ActionSupport". Colocaremos algunas anotaciones para la configuración de Struts 2:


@Namespace(value = "/")
@Action(value = "datos", results ={@Result(location = "/datos.jsp")})
public class DatosAction extends ActionSupport
{
}


Sobre-escribimos el método "execute" de esta clase para obtener el ActionContext. Una vez teniendo este contexto podemos obtener la sesión y podemos agregar objetos a ella. En este caso agregaré una cadena solo para poder obtenerla después:


@Override
public String execute() throws Exception
{
   ActionContext.getContext().getSession().put("datoSesion", "dato en la sesion");

   return SUCCESS;
}


Ahora creamos una JSP llamada "datos.jsp" en el directorio raíz de las páginas web. En esta página indicaremos que usaremos la biblioteca de etiquetas de Struts 2; y obtendremos, usando la etiqueta "<s:property>", el valor del atributo "datoSesion" que colocamos en la sesión del usuario.

Como el objeto "session" no es la raíz del mapa de contexto, es necesario hacer referencia a él de la siguiente forma:


#session


Y podemos obtener cualquiera de sus atributos mediante el nombre del atributo. Por lo que la etiqueta queda de la siguiente forma:


<s:property value="#session.datoSesion" />


También podemos hacerlo de esta otra forma:


<s:property value="#session['datoSesion']" />


Cuando ejecutemos la aplicación debemos ver la siguiente salida:



Ahora, dijimos que "parameters" representa los parámetros que se reciben, ya sea por GET o por POST.

Colocaremos una etiqueta para obtener el valor de un parámetro llamado dato:


<s:property value="#parameters.dato" />


Y accedemos a la siguiente URL:


http://localhost:8080/ognl/datos.action?dato=Alex


En donde estamos pasando, por GET, un parámetro llamado "dato" igualado al valor "Alex". Cuando entremos en la dirección anterior veremos una pantalla como la siguiente:



Si ahora, usamos el objeto "attr" para buscar el valor del atributo "datoSesion", de la siguiente forma:


<s:property value="#attr.datoSesion" />


Obtendremos la siguiente pantalla:



Con OGNL no solo es posible acceder a los objetos dentro del ActionContext, sino prácticamente a cualquier objeto java que sea visible. Para ilustrar esto veamos otro ejemplo.


Obteniendo Valores de Constantes y Variables con OGNL

Primero crearemos una nueva clase Java que contendrá una serie de atributos, métodos, y constantes. La clase se llamará "Constantes" (aunque también contendrá valores variables). Esta clase tendrá una variable de instancia llamada "atributo" (con su correspondiente getter), una constante llamada "valor", y una enumeración llamada "Datos" que tendrá un método "getDato()":


public class Constantes
{
    private String atributo = "atributo de instancia";
    public final static String valor = "variable estatica";

    private static enum Datos {PRIMERO, SEGUNDO, TERCERO; public String getDato(){ return "dato";} };

    public String getAtributo()
    {
        return atributo;
    }
}


Sé que el atributo "valor" no sigue la convención de las constantes de Java, pero colocarla de esta forma ayudará a hacer las explicaciones más sencillas ^_^.

Ahora, para que las cosas sean un poco más interesantes, agregaremos unos cuantos métodos.

Primero agregaremos un método de instancia que regrese una cadena y no reciba parámetros; y una versión sobrecargada de este método que reciba una cadena:


public String metodoDeInstancia()
{
    return "metodo de instancia";
}
    
public String metodoDeInstancia(String mensaje)
{
    return mensaje;
}


Agregaremos también las versiones estáticas de estos métodos:


public static String metodoEstatico()
{
    return "metodo estatico";
}
    
public static String metodoEstatico(String mensaje)
{
    return mensaje;
}


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


public class Constantes
{
    private String atributo = "atributo de instancia";
    public final static String valor = "variable estatica";

    private static enum Datos {PRIMERO, SEGUNDO, TERCERO; public String getDato(){ return "dato";} };
    
    public String metodoDeInstancia()
    {
        return "metodo de instancia";
    }
    
    public String metodoDeInstancia(String mensaje)
    {
        return mensaje;
    }
    
    public static String metodoEstatico()
    {
        return "metodo estatico";
    }
    
    public static String metodoEstatico(String mensaje)
    {
        return mensaje;
    }
    
    public String getAtributo()
    {
        return atributo;
    }
}


Como podemos ver, esta clase no tiene nada de especial: ninguna anotación, no usa ninguna librería, ni nada por el estilo.

Ahora creamos una página llamada "constantes.jsp", en el directorio raíz de las páginas web.

Para crear un objeto nuevo con OGNL podemos usar el operador "new" y el "fully qualified class name". Por ejemplo, para crear un nuevo objeto de la clase "Constantes" haríamos lo siguiente:


<s:property value="new com.javatutoriales.struts2.ognl.Constantes()" />


Teniendo esta instancia podemos obtener los valores de los atributos del objeto e invocar sus métodos, como lo haríamos en un objeto normal. Por ejemplo, para obtener el valor del atributo "atributo" se puede hacer de la siguiente forma:


<s:property value="new com.javatutoriales.struts2.ognl.Constantes().atributo" />


Con esto, si entramos a la siguiente dirección:


http://localhost:8080/ognl/constantes.jsp


Obtenemos la siguiente pantalla:



Para invocar un método de instancia se hace de la misma forma (no olviden colocar los paréntesis al final):


<s:property value="new com.javatutoriales.struts2.ognl.Constantes().metodoDeInstancia()" />


Con esto, obtenemos la siguiente pantalla:



Podemos invocar los miembros estáticas exactamente de la misma forma:


<s:property value="new com.javatutoriales.struts2.ognl.Constantes().metodoEstatico()" />
<s:property value="new com.javatutoriales.struts2.ognl.Constantes().valor" />


Con esto obtenemos la siguiente pantalla:



El valor de la constante "valor" no se muestra, porque recuerden que cuando llamamos a los atributos de esta forma:


<s:property value="new com.javatutoriales.struts2.ognl.Constantes().valor" />


OGNL busca un método llamado "getValor()" en la clase "Constantes", y este método no existe.

Ahora bien, una de las características de los miembros estáticos es que no es necesario tener una instancia de la clase en la que existe el miembro para poder llamar a estos miembros, pero aquí estamos creando una nueva instancia de las clases para llamarlos. También es posible hacer estas llamadas sin una instancia de la clase. En este caso debemos usar una notación especial de OGNL.

En esta notación, debemos indicar el nombre completo de la clase que contiene al miembro estático, precedida por una arroba ("@"). También se debe indicar el miembro que se quiere llamar precedido por una arroba.

Por ejemplo, para usar la constante "valor", la etiqueta debe estar colocada de esta forma:


<s:property value="@com.javatutoriales.struts2.ognl.Constantes@valor" />


Con lo que obtenemos:



Y para invocar el método estático, de esta forma:


<s:property value="@com.javatutoriales.struts2.ognl.Constantes@metodoEstatico()" />


Con esta etiqueta obtenemos la siguiente salida:



Bueno, en este caso parece que también ha habido un problema al invocar el método estático ^_^. En realidad lo que ocurre es que por default Struts 2 impide la invocación de métodos estáticos desde OGNL.

¿Entonces qué podemos hacer? Pues Struts proporciona una forma para habilitar esta opción. Podemos colocar el valor de la constante "struts.ognl.allowStaticMethodAccess" en "true".

En el tutorial anterior hablamos un poco de la constantes (muy poco, casi nada ^_^!), y declaramos un par de ellas en el archivo "struts.xml". Sin embargo, ahora no tenemos un archivo "struts.xml" ya que estamos usando anotaciones. Entonces ¿qué podemos hacer? (parece que salimos de un problema para entrar en otro, ¿no les ha pasado?).

Las constantes en Struts 2 pueden ser declaradas en tres lugares:

  • El archivo de configuración "struts.xml".
  • El archivo "struts.properties" (del que aún no hemos hablado).
  • Como un parámetro de inicio en el deployment descriptor (el archivo "web.xml").

Como de los tres archivos anteriores solo tenemos el archivo "web.xml" será aquí en donde agregamos esta constante. Para esto debemos modificar la declaración del filtro de Struts 2, dejándola de la siguiente forma:


<filter>
    <filter-name>struts2</filter-name>
    <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
    <init-param>
        <param-name>struts.ognl.allowStaticMethodAccess</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>


Ahora sí, si ejecutamos nuevamente el ejemplo debemos ver la siguiente salida:



Podemos ver que en este caso, se pudo obtener el valor de la variable estática de forma directa.

Para invocar las versiones de los métodos que reciben un parámetro basta con pasar el parámetro que esperan recibir:


<s:property value="@com.javatutoriales.struts2.ognl.Constantes@metodoEstatico('Hola Amigos Estaticos')" />
<s:property value="new com.javatutoriales.struts2.ognl.Constantes().metodoDeInstancia('Hola Amigos')" />


Inclusive podemos pasar como parámetro uno de los atributos obtenidos anteriormente:


<li><s:property value="new com.javatutoriales.struts2.ognl.Constantes().metodoDeInstancia(@com.javatutoriales.struts2.ognl.Constantes@valor)" /></li>


Con estos cambios obtenemos la siguiente pantalla:



Otra parte interesante del uso de OGNL viene cuando queremos obtener los valores de las constantes de una enumeración. Si recuerdan, nuestra clase "Constantes" tiene una enumeración llamada "Datos". Como cada uno de los valores de una enumeración es una constante (o sea una variable publica, estática, y final) obtener sus valores es igual a obtener cualquier otro valor de una variable estática. O sea que tenemos que usar la sintaxis de la doble arroba.

Ahora, el secreto aquí viene dado en el sentido de que las enumeraciones son en realidad un tipo especial de clase Java. Como aquí la enumeración está dentro de otra clase, la primera se convierte en una clase interna de la segunda. El fully qualified class name de una clase interna es el nombre de la primer clase, seguida de un signo de dólar ($) seguido del nombre de la clase interna. Por lo tanto, el fully qualified class name de la enumeración "Datos" es "com.javatutoriales.struts2.ognl.Constantes$Datos". Teniendo el nombre de esta enumeración, podemos obtener los valores de sus constes de la siguiente forma:


<s:property value="@com.javatutoriales.struts2.ognl.Constantes$Datos@PRIMERO" />
<s:property value="@com.javatutoriales.struts2.ognl.Constantes$Datos@SEGUNDO" />


E igual que en los casos anteriores, teniendo estos valores podemos invocar cualquier método que exista dentro de la enumeración, como en este caso el método "getDato()" que invocamos de esta forma:


<s:property value="@com.javatutoriales.struts2.ognl.Constantes$Datos@TERCERO.dato" />


Que también podría ser de esta forma:


<s:property value="@com.javatutoriales.struts2.ognl.Constantes$Datos@TERCERO.getDato()" />


En cualquiera de los dos casos, la salida obtenida es la siguiente:



Por último, veremos que en OGNL también se pueden declarar y acceder valores de tipos indexados, en particular de arreglos, listas, y mapas.


Obtenido Valores de Tipos Indexados con OGNL

Comencemos viendo los arreglos, que son los elementos más "complicados" de definir.

Cuando queremos definir un arreglo de objetos o de primitivos, debemos usar la misma sintaxis que se usa para definir los arreglos anónimos. Por ejemplo, para definir un arreglo con los enteros del 1 al 5 lo hacemos de la siguiente forma:


new int[] {1, 2, 3, 4, 5}


Colocando esto en la etiqueta "<s:property>", de esta forma:


<s:property value="new int[]{1, 2, 3, 4, 5}" />


Obtenemos la siguiente salida:



Podemos colocar estos mismos elementos dentro de una etiqueta "<s:select>", que nos ayuda a crear listas desplegables (o combo boxes, o como quiera que gusten llamarle):


<s:select name="valores" list="new int[]{1, 2, 3, 4, 5}" />


Obteniendo la siguiente salida:



Para crear un arreglo con todos sus valores inicializados a null, podemos hacerlo de la siguiente forma:


new int[5]


Si por alguna razón no podemos (o no queremos) hacer uso de arreglos, sino de algún tipo de colección, como una lista, podemos también construir una "al vuelo" usando la siguiente sintaxis:


{'uno', 'dos', 'tres', 'cuatro', 'cinco'}


Con esto obtenemos la siguiente salida:



De la misma forma que con los arreglos, si colocamos esto dentro de una etiqueta "<s:select>" obtenemos la siguiente salida:



Finalmente, para crear un Map, podemos usar la siguiente sintaxis:


#{'uno':'1', 'dos':'2', 'tres':'3', 'cuatro':'4', 'cinco':'5'}


Donde el primer elemento es la llave, y el segundo es el valor. Con esto obtenemos la siguiente salida:



Igual que en los ejemplos anteriores, si colocamos esto en una etiqueta "<s:select>", obtenemos la siguiente pantalla:



Si vemos el código fuente del elemento generado podremos observar que la llave es usada como el atributo "value" de cada opción, y el valor se usa como la etiqueta que será mostrada:


<select name="mapas" id="mapas">
    <option value="uno">1</option>
    <option value="dos">2</option>
    <option value="tres">3</option>
    <option value="cuatro">4</option>
    <option value="cinco">5</option>
</select>


Si por alguna razón necesitamos alguna implementación particular de un Map, podemos indicarlo de la siguiente forma:


#@java.util.TreeMap@{'uno':'1', 'dos':'2', 'tres':'3', 'cuatro':'4', 'cinco':'5'}


Por ejemplo, colocando los siguientes elementos:


<s:property value="#@java.util.LinkedHashMap@{'uno':'1', 'dos':'2', 'tres':'3', 'cuatro':'4', 'cinco':'5'}" />            
<s:property value="#@java.util.TreeMap@{'uno':'1', 'dos':'2', 'tres':'3', 'cuatro':'4', 'cinco':'5'}" />             
<s:property value="#@java.util.HashMap@{'uno':'1', 'dos':'2', 'tres':'3', 'cuatro':'4', 'cinco':'5'}" />


Obtenemos la siguiente salida:



Podemos determinar si en una colección existe un elemento podemos usar los operadores "in" y "not in":


<s:if test="'foo' in {'foo','bar'}">
   muahahaha
</s:if>
<s:else>
   boo
</s:else>

<s:if test="'foo' not in {'foo','bar'}">
   muahahaha
</s:if>
<s:else>
   boo
</s:else>


Si necesitamos realizar la selección de un subconjunto de una colección (que en términos formales se llama proyección), OGNL proporciona un conjunto de comodines para eso:

  • "?" – Para obtener todos los elementos que coinciden con la lógica de selección.
  • "^" – Para seleccionar solo el primer elemento que coincida con la lógica de selección.
  • "$" - Para seleccionar solo el último elemento que coincida con la lógica de selección.

Por ejemplo, si tuviéramos una clase llamada "Persona" con un atributo "genero", y un objeto con una colección de Personas llamada "familiares". Si solo queremos obtener las Personas con genero "masculino", podemos hacerlo de la siguiente forma:


persona.familiares.{? #this.genero = ‘masculino’}


Queda al lector hacer un ejemplo de esto ^_^.

Como nota final, hay que decir que algunas veces (en muy raras ocasiones en realidad) OGNL entiende las instrucciones que le damos como cadenas y no como expresiones. Para estos casos existe una forma de decirle que interprete lo que ve como una expresión, y esto es envolviendo dicha expresión entre los caracteres "%{" y "}".

Por ejemplo, para indicarle que la siguiente línea debe ser tomada como una expresión:


<s:property value="constantes.atributo" />


Lo indicamos de la siguiente forma:


<s:property value="%{constantes.atributo}" />


Como pudimos ver a lo largo de los ejemplos, esto casi nunca es necesario, pero si alguna vez, por alguna razón una de las expresiones que están creando no funciona, pueden resolverlo de esta forma ^_^.

Esto es todo en este tutorial. Como pudimos ver OGNL es un lenguaje de expresiones bastante completo (y eso que no hablamos de los operadores que tiene) con el que podemos obtener (y establecer) prácticamente cualquier valor de cualquier clase que necesitemos en nuestros desarrollos.

Espero que el tutorial les sea de utilidad y, como siempre, no olviden dejar sus dudas, comentarios y sugerencias.

Saludos y gracias ^_^

Descarga los archivos de este tutorial desde aquí:


Entradas Relacionadas:

18 comentarios:

  1. Bien por el tutorial... Gracias por explicar tan claramente.

    ResponderEliminar
  2. Muchas gracias por el tutorial. La verdad me ha servido de mucho. Sigue así por favor.

    ResponderEliminar
  3. Este es el tutorial que mayor información muestra y que además muestra varios ejemplos de cada subtema, felicidades.

    ResponderEliminar
  4. Estoy empezando con Struts 2 y es de los mejores tutoriales que he visto por el momento. Gracias de verdad.

    ResponderEliminar
  5. Fantástico tutorial, muchísimas gracias!

    ResponderEliminar
  6. hola: al final haces un comentario
    "Esto es todo en este tutorial. Como pudimos ver OGNL es un lenguaje de expresiones bastante completo (y eso que no hablamos de los operadores que tiene) con el que podemos obtener (y establecer) prácticamente cualquier valor de cualquier clase que necesitemos en nuestros desarrollos."
    Como "establecer" donde puedo profundizar el tema.
    gracias muy buen el tutorial.

    ResponderEliminar
    Respuestas
    1. http://commons.apache.org/proper/commons-ognl/language-guide.html

      Eliminar
  7. Gracias, excelente tutorial eres lo maximo :D

    ResponderEliminar
  8. Estoy empezando a seguir tus tutoriales de Struts 2, excelente aporte !!!

    ResponderEliminar
  9. Este comentario ha sido eliminado por el autor.

    ResponderEliminar
  10. Hola, muchas gracias por los tutoriales. Son muy ilustrativos y por fin me están adentrando en Struts, del que tanto se habla en el mundo de Java.

    Quiero comentar algunas cosas:

    "Cuando realizamos una petición para el Action, el DispatcherFilter ejecutará el método "execute" del Action, después colocará este Action en la cima del ValueStack con lo cual lo tendremos disponible."

    Según he experimentado, el Action se pone en la cima del ValueStack antes de ejecutar "execute". Se comprueba fácilmente aplilando el Animal y la Persona dentro de Execute, de manera que quedan: (el primero )Animal, Persona y Action (el último).

    Luego:

    <s:property value="@com.javatutoriales.struts2.ognl.Constantes@metodoEstatico()"

    No funciona correctamente si no se establece en el struts.xml:

    <constant name="struts.ognl.allowStaticMethodAccess" value="true"

    Saludos!

    ResponderEliminar
    Respuestas
    1. Vale, ya vi que se menciona más abajo. Traté de darle la solución antes de seguir leyendo... Me pasa a menudo con eso de probar y probar a la vez que voy leyendo :P

      Eliminar
    2. Hola PosTi,

      No entiendo a qué te refieres con "apilando dentro del Execute". ¿Cómo "apilas" estos elementos y cuál es su relación en el ValueStack? El ValueStack (hasta donde sé) es parte de OGNL y por lo tanto sólo existe en las páginas JSP que hacen uso de él. Por eso siempre primero se ejecuta el método "execute" y luego se pone en el ValueStack (o sea, primero se ejecuta la lógica y luego se muestra la vista). Aunque en realidad no estoy seguro del orden, pero este se comprueba fácilmente viendo el código fuente del DispatcherFilter ;)

      Saludos y gracias

      Saludos

      Eliminar
  11. Well It Was Very Goog Information ForJava Learners I Was Reaaly happy See This Areticle.We Are Also Providing All Java Online Training Courses.
    Our Java Online Training Institute Is one Of The Best Training Institute In The World

    ResponderEliminar
  12. Hola Álex:

    Gracias por el tutorial, me está sirviendo de mucho para aprender Struts 2.

    He instalado la última versión de Struts 2 (v.2.3.20) para ir haciendo los ejercicios y me he dado cuenta de que no me funcionaban los del apartado "Obteniendo Valores de Constantes y Variables con OGNL". El servidor Tomcat arrojaba la siguiente advertencia a la hora de instanciar la clase en la página JSP:

    [WARNING] Target class [nuestra clase] or declaring class of member type [public nuestra clase()] are excluded!

    Según he visto en la documentación de Apache, en estas últimas versiones de Struts la instanciación de clases externas no está permitida por defecto ya que supone un riesgo de seguridad. Puede forzarse de todas formas mediante el archivo struts.xml.

    Además de eso, el acceso a miembros estáticos será deshabilitado en futuras versiones, también por razones de seguridad.

    Un saludo y gracias de nuevo
    Néstor

    ResponderEliminar
  13. Saludos,

    estuve probando el siguiente código, y tiene un pequeño error:

    persona.familiares.{? #this.genero = ‘masculino’}

    Este código establece el valor de genero a 'masculino' de cada persona en la colección de familiares. El código correcto sería colocando doble igual ==, y quedaría así:

    persona.familiares.{? #this.genero == ‘masculino’}

    Saludos cordiales!!!

    ResponderEliminar
  14. Hola Álex, he estado buscando la forma de forzar en el fichero struts.xml, lo que comenta Tyler Durden, y no he encontrado la manera de forzar a que me incluya la clase. En el log obtengo

    abr 28, 2016 10:53:09 AM com.opensymphony.xwork2.ognl.SecurityMemberAccess warn
    ADVERTENCIA: Target class [class com.struts2.ognl.modelo.Constantes] is excluded!

    Alguien sabe como puedo evitar esto, el tío google no me ayuda nada...
    :(

    Muchas gracias por la web!!!

    ResponderEliminar