struts2

Maven en Eclipse: m2e

Una de las cosas que menos me gusta a la hora de empezar un proyecto nuevo es tener que comenzar desde cero a montar todas la infraestructura para poder comenzar. A pesar de que Eclipse tiene diversas plantillas, éstas son muy genéricas y hay que complementarlas con librerías y servidores, lo que nos puede llevar un buen rato hasta que comenzamos a poder empezar a ser productivos. Por ejemplo si queremos un proyecto que lleve struts2 con acceso JPA a base de datos, tenemos que ir a proyecto java web y complementar las librerías.

El punto ideal de comienzo de un proyecto sería disponer de un “Hola Mundo” básico sobre el cual empezar a programar, saltándonos la configuración previa. Afortunadamente en el mundo Java existe una herramienta llamada Maven que facilita el ciclo de vida de una aplicación, desde su creación (aspecto del que va este post) hasta el testing o el deploy. En este post solamente vamos a ver cómo aprovechar una pequeña parte de su potencial para facilitarlos el inicio de un proyecto.

Maven es una herramienta de Apache que puede ser descargada en http://maven.apache.org/ y que se puede encontrar una introducción a su uso en http://maven.apache.org/users/index.html (recomiendo el apartado http://maven.apache.org/guides/getting-started/maven-in-five-minutes.html para un rápido vistazo práctico a su potencial). Su funcionamiento para la creación está basado en los “Archetypes” que son una especie de plantillas que utilizaremos para crear nuestro proyectos y que contiene una descripción de las librerías y otras dependencias, así como unos ficheros básicos que contiene el “hola mundo” que necesitamos para comenzar.

Mediante Maven podemos “instanciar” un archetype de nuestro gusto de los múltiples que hay por Internet, como por ejemplo de http://search.maven.org/ o de http://mvnrepository.com/ . Hay cientos de ellos asi que es complicado no encontrar uno que se adapte a nuestras necesidades. Maven descarga el archetype y crea en el directorio que le indiquemos una implementación del proyecto. Además se descarga las bibliotecas .jar en un lugar común en nuestro ordenador y establece las dependencias para que simplemente ejecutemos.

Como se trata de una herramienta básica que muchos otros programas utilizan y además es susceptible de automatizar (sobre todo por las otras etapas del ciclo de vida en las que es de gran ayuda), no tiene una interfaz gráfica. Esto tiene sus ventajas e inconvenientes. En este post vamos a ver cómo la podemos integrar dentro de Maven para que los usuarios que están más acostumbrados a usar eclipse, puedan usar las ventajas de esta herramienta. Para ello existe el plugin m2e para Eclipse, así como su integración en WTP (Web Tools Platform) de Eclipse. Este plugin ya incorpora una edición embebida de Maven para que no tengamos que hacer nada

Pasos para ponerlo en práctica:

1. Instalar los plugins de eclipse m2e y m2e-wtp, que integra maven con el WTP (Web Tools Platform) de eclipse:

  • Ir a Help->Install new Software e introducir la siguiente URL: http://download.eclipse.org/m2e-wtp/releases/juno/ (o la distribución de Eclipse que estemos usando – Kepler, Galileo…-)
  • Elegir el plugin m2e y el m2e-wtp normal (no el SDK, aunque podemos instalarlo si queremos)
  • Reiniciar Eclipse para finalizar la instalación

(En la página oficial de los plugins hay un excelente video de poco más de un minuto http://www.eclipse.org/m2e-wtp/)

2. Para crear un repositorio:

  • Crear un proyecto nuevo (File->New->Other o Ctrl+N)
  • En la caja de elección del wizard, escribir Maven para localizar los proyectos de Maven
  • Elegir “Maven Project”
  • Usar el default workspace de Eclipse (para que cree el proyecto en nuestro workspace, que suele ser lo más habitual).
  • En la ventana de elección del archetype, pinchar en Configure, puesto que no están los que buscamos (necesitamos localizar un Archetype de Struts2).
  • En la ventana de configuración de Archetypes, pulsar sobre “Add Remote Catalog” y añadir un nuevo repositorio con esta URL: http://repo1.maven.org/maven2/archetype-catalog.xml
  • Elegir como fuente el nuevo catálogo (yo lo he llamado “Maven General” pero puedes llamarlo como quieras)
  • Pinchar sobre struts2-archetype-blank (que es un archetype de un proyecto struts2 en blanco. Puedes elegir el que quieras).
  • En la siguiente ventana, completar los datos del proyecto que amos a crear: Group ID para el grupo al que pertenece el proyecto; Artifact ID para designar el proyecto y Package para nombrar el paquete base que usaremos.
  • Pulsamos en finalizar para acabar con el wizard de creación.

Ahora esperamos unos instantes para que Maven descargue todas las librerías y dependencias que están declaradas en el archetype, y para que cree los ficheros básicos (algunas acciones de Struts2, JSP, los xml de configuración de la aplicación Web…)

Al final tendremos un proyecto basado en el Archetype elegido. Podremos ver que:

  • El proyecto se ha creado en nuestro workspace y que ya tiene una estructura completa creada, con las dependencias de struts2 necesarias, además de una aplicación de ejemplo con acciones básicas y un struts.xml
  • Las dependencias (los .jar) ahora se encuentran en nuestro ordenador en nuestro directorio personal de nuestro sistema operativo /.m2/repository
  • Podemos tratar el proyecto como si de un proyecto web de Eclipse se tratara. Esto es posible gracias al plugin m2e-wtp.

Desafortunadamente puede surgir una serie de problemas derivados del uso del plugin m2e para Eclipse, que podremos resolver con ayuda de StackOverflow en su mayor parte. Aquí van alguno de los problemas que he experimentado, aunque todos ellos se han podido resolver con no mucho trabajo:

  • El problema persistente del fichero Missing artifact com.sun:tools:jar:1.5.0:system pom.xml que es un fichero que lleva el JRE pero que no es capaz de encontrar. Esta biblioteca (tools.jar) forma parte de la instalación del JDK pero no del JRE. Si tenemos el JRE instalado y eclipse está corriendo con la máquina virtual del JRE y no del JDK puede sucedernos este error, puesto que no es capac de encontrar la biblioteca tools.jar. Se soluciona de esta forma:
    •  editando el fichero eclipse.ini e incluyendo justo antes de de -vmargs la sentencia: vm “C:\Program Files\Java\jdk1.6.0_37\bin\javaw.exe”. (o donde se tenga el JDK instalado.
    •  usando el JDK como entorno de ejecución del proyecto (y no el JRE).
    •  por último haciendo un clean del proyecto seguido de un build para que coja los cambios.
  •  Descarga incorrecta de bibliotecas. Es algo que en Maven en modo comando es fácil de detectar puesto que avisa de que el hash del fichero no coincide con el proporcionado, pero en Eclipse debemos esperar a un build del proyecto. De este modo recibiremos errores de clases no encontradas o similar. La razón es que el fichero .jar está corrupto y no puede extraerse la información. La solución:
    • Ir al repositorio de bibliotecas que hay en nuestro perfil de usuario (en mi caso: C:\Users\mysticalpotato\.m2\repository) y localizar el .jar. Se renombrar o elimina y se actualiza el proyecto con Maven (botón auxiliar sobre el proyecto, Maven->Update Project) para que se vuelva a bajar todas las bibliotecas que no están. Si hay suerte se bajarán las correctas, sino habrá que insistir

La madre de todas las listas de modos de acceder a una variable en Struts2

No, no la he hecho yo, la he sacado de:

http://stackoverflow.com/questions/4560169/checking-request-parameter-value-in-struts2-tag
<%@taglib prefix="s" uri="/struts-tags"%>
<!-- values from action where letters = "abcd" -->
<s:property value="letters"/><br/> <!-- Displays: abcd -->
<s:property value="letters.equals('abcd')"/><br/> <!-- Displays: true -->
<s:property value="'abcd'.compareTo('abcd')"/><br/> <!-- Displays: 0 -->
<s:property value="'abcd'.compareTo('abcd') == 0"/><br/> <!-- Displays: true -->
<s:property value="'abcd'.equals('abcd')"/><br/> <!-- Displays: true -->
<s:property value="'abcd'.equals(letters)"/><br/> <!-- Displays: true -->
<br/>
<!-- RUN with ?test=a&test2=abc appended to the url -->
<!-- Time for the numbers from action where number = 1-->
<s:property value="number"/><br/><!-- Displays: 1 -->
<s:property value="number.toString()"/><br/><!-- Displays: 1 -->
<!-- OGNL strings in certain cases must be double quoted -->
<s:property value='"1".equals(number.toString())'/><br/><!-- Displays: true -->
<!-- As we can see single quotes does automatic type conversion to Character which is then evaluates false-->
<s:property value="'1'.equals(number.toString())"/><br/><!-- Displays: false -->
<!-- here a string is compared to an integer which is false-->
<s:property value='"1".equals(number)'/><br/><!-- Displays: false -->
<br/><!-- Request Variables -->
<s:property value="#parameters['test']"/><br/><!-- Displays: a -->
<!-- a is single quoted so automatic type conversion probably converted it to a Character, which is not equal to string "a" -->
<s:property value="'a'.equals(#parameters['test'])"/><br/><!-- Displays: false -->
<!-- both are strings so equality works as expected -->
<s:property value='#parameters["test"]'/><br/><!-- Displays: a -->
<s:property value='"a".equals(#parameters["test"])'/><br/><!-- Displays: false because #parameters["test"] is a string[] and calling toString on string[] does not work -->
<!-- #parameters['test2'] now is 'abc' automatic type conversion of OGNL swill convert 'abc' to a string and so both are equal -->
<s:property value='#parameters["test2"]'/><br/>
<!-- Double quotes must be a string -->
<s:property value='"abc".compareTo(#parameters["test2"]) == 0'/><br/><!-- Displays: true -->
<!-- Single quote so automatic type conversion... string of chars is converted to String -->
<s:property value="'abc'.compareTo(#parameters['test2']) == 0"/><br/><!-- Displays: true -->
<!-- Out of curiosity I'd like to know if '1' is an Integer or a Byte -->
<s:property value="'1'.toString()"/><br/><!-- Answer: Neither it prints "class java.lang.Character" -->
<!-- 'a' is a Character however equals calls toString() -->
<!-- But the request object (and session too) is not <string, string> but <string, string[]> -->
1: <s:property value='"1".class'/><br/> <!-- class java.lang.String -->
2: <s:property value='#parameters["test"].class'/><br/> <!-- Array of string: class [Ljava.lang.String; -->
3: <s:property value='#parameters["test"][0].class'/><br/> <!-- This is what we need: class java.lang.String -->
<s:property value='#parameters["test"][0].equals("a")'/><br/> <!-- Now this works -->
<s:property value="#parameters['test'][0].equals('a'.toString())"/><br/> <!-- this is another way, leaving off the .toString results in false -->

Recorrido de vectores y bucle condicional con tags de Struts2

Cuando se utiliza Struts2 para la parte de pintado de datos con ficheros JSP se puede hacer del modo Java (metiendo snippets de código Java entre las etiquetas JSP), que es poco recomendable, o utilizar los tags de Struts2 para JSP, que es una opción más recomendable.

Desgraciadamente el uso de los tags de Struts2 requiere conocer un poco la sintáxis que utilizan y cómo acceder a los valores con sintaxis OGNL en cada momento.

En este ejemplo tendremos dos ArrayList diferentes que queremos pintar alternativamente, decidiendo en cada momento si tenemos que pintar uno u otro. Es decir, la concatenación de los bucles es lo que vamos a pintar, pero intercalados, aunque manteniendo el orden. Es decir:

Vector1: 1; 2 ; 3 ;4
Vector 2: a; b; c; d
Resultado: 1;2;a;b;c;3;d;4 (u otras combinaciones)

Básicamente el algoritmo consiste en:

  • Tomar la longitud del vector1 y del vector 2
  • Hacer un bucle que tenga como iteraciones la suma de ambos vectores.
  • Por cada iteración decidir qué elemento se pinta, si el que toque del vector 1 o el del vector 2.
  • Si se pinta el vector 1, pintar el valor al que apunta su contador e incrementar 1
  • Si se pinta el vector 2, pintar el valor al que apunta su contador e incrementar 1

Para ello vamos a crear las siguientes variables utilizando el tag “s:set” un contador para el vector 1 y otro contador para el vector 2; también guardaremos la longitud total del bucle, es decir, la suma de la longitud de ambos vectores.

Para el bucle usaremos el tag s:iterator , que irá desde el valor 1 hasta la suma de los vectores.

Dentro del bucle simplemente tendremos una condición que nos indicará si debemos pintar valores del vector 1 o del 2. La condición es lo de menos. En nuestro caso los contenidos del vector comprueba un valor que define el orden en el que se tiene que pintar los elementos del vector 1 en el total: si el elemento n-ésimo tiene el orden coincidente con el bucle lo pinta, de otro modo pinta el vector de datos.

Lo más importante es ver el acceso a las variables definidas en s:set, indicando a través del attr. Y cómo se puede indicar que el puntero de un vector puede estar basado en el valor de una variable.
Los pasos son los siguientes:

1. Definimos las variables que contienen los índices de los vectores. Los inicializamos a 0, como no podría ser de otro modo:

<s:set var ="contador_vector1" value="%{0}" scope="page" />
<s:set var ="contador_vector2" value="%{0}" scope="page" />

2. Calculamos el recorrido que tiene que hacer el bucle como suma de las longitudes. Hay que saber también la longitud del vector 1 para no salir de los límites en la comparación.

<s:set var="elementos_totales" value="vector1.size+vector2.size" scope = "page"></s:set>
<s:set var="elementos_totales_vector1" value ="syllabus.bloquesList.size" scope = "page" />

3. Comenzamos el bucle. El bucle va desde el elemento 1 al número de elementos totales. Ojo, comenzamos en el valor 1 porque vamos a contar el orden de aparición de elementos para la condición de pintar uno u otro. Se podría haber comenzado en el valor 0

<s:iterator begin="1" end="%{#attr.elementos_totales}">

Lo más destacable es el acceso a la variable creada mediante el tag set. Se hace usando la sintaxis: %{#attr.nombre_variable}

6. Condición en el bucle para saber si tenemos que pintar en la iteración los valores de un vector o los valores de otro:

<s:if test="(#attr.contador_vector1<#attr.elementos_totales_vector1)&&(vector1[#attr.contador_vector1].orden == top)">

Nótese que el valor con el que se hace la comparación usando el tag if es el valor top, que contiene el valor del contador del bucle en ese momento.
También es destacable la forma de acceder al vector utilizando la sintaxis vector[#attr.contador]

7. Empujamos al tope de la pila OGNL el valor del vector mediante el tag push

<s:push value="vector1[#attr.contador_vector1]">

8. Se acceden a las propiedades del valor empujado normalmente:

<li>Valor:<s:property value="pk1" /></li>

9. Se incrementa el puntero del vector que hemos pintado. Es muy importante dejar espacios entre el signo de suma y los valores para que el parser interprete una suma de enteros y no una cadena referida a una variable.

<s:set var="contador_vector1" value="%{1 + #attr.contador_vector1}"/>

Se procede de la misma manera para el otro vector en el caso de que la sentencia if de Struts2 de como resultado false y vaya por el camino del s:else.

En definitiva mediante este ejemplo se han visto algunos detalles intersantes de cómo hacer con tags de Struts2 las clásicas operaciones de recorrido de vectores usando bucles con contador e índices para acceder.

Código fuente completo:

<s:set var ="contador_vector1" value="%{0}" scope="page" />
<s:set var ="contador_vector2" value="%{0}" scope="page" />

<s:set var="elementos_totales" value="vector1.size+vector2.size" scope = "page"></s:set>
<s:set var="elementos_totales_vector1" value ="syllabus.bloquesList.size" scope = "page" />

<s:iterator begin="1" end="%{#attr.elementos_totales}">
     <s:if test="(#attr.contador_vector1<#attr.elementos_totales_vector1)&&(vector1[#attr.contador_vector1].orden == top)">
          <s:push value="vector1[#attr.contador_vector1]">
               <li>Valor:<s:property value="pk1" /></li>
          </s:push>
          <s:set var="contador_vector1" value="%{1 + #attr.contador_vector1}"/>
      </s:if>
      <s:else>
           <s:push value="vector2[#attr.contador_vector2]">
                <li>Valor:<s:property value="pk1" /></li>
           </s:push>
           <s:set var="contador_vector2" value="%{1 + #attr.contador_vector2}"/>
      </s:else>
 <s:iterator>

Struts2: cuando la vista es un stream (JSON, plain text…) y no un JSP

Hay ocasiones en las que el MVC de Struts2 no tiene por qué tienes como vista una página JSP o HTML al uso, sino que queremos escribir directamente en la salida de datos a través de un stream. Tal es el caso de una acción que responde con un código (un ID por ejemplo) a una llamada AJAX, que lee la respuesta en Javascript para confirmar que la operación se ha llevado a cabo.

Para ello tenemos que definir un tipo especial de resultado en el fichero struts.xml, de tipo “stream”. Dentro, podemos configurar el formato de la cabecera HTTP que indica el contentType, así como el nombre del inputStream que se usará para escribir los resultados. Lo que escribamos en este stream será lo que devuelva como vista el servidor.

<action name="MiAccion" method="create" class="com.wordpress.mysticalpotato.acciones.MiAccion">
<result type="stream" name="success">
<param name="contentType">plain/text</param>
<param name="inputName">inputStream</param>
</result>
</action>

El siguiente paso es editar la acción (controlador) de struts2. Como siempre, tendremos un fichero con un método llamado “create” (no es necesario, pero la acción de ejemplo tiene otros métodos que responden a diferentes patrones de URL, de ahí que se haya indicado) que tiene como salida un String.


public class MiAccion extends ActionSupport

{

private InputStream inputStream;//Salida para escribir directamente

public String create()

{

String miRespuesta = "666";

inputStream = new StringBufferInputStream(miRespuesta );

return SUCCESS

}

public InputStream getInputStream()

{

return inputStream;

}

Simplemente con este código, le indicaremos, que en la salida SUCCESS (viene del atributo del ActionSupport) tiene que tomarla del inputStream. Nótese cómo el inputStream es un atributo de la clase entera. Además, hay un getter para este atributo para exponer al framework dicho stream.

La salida será “666” en una comunicación HTTP con content-type: plain/text que puede ser leída perfectamente desde Javascript como respuesta a una llamda AJAX

Acceder a una variable de Struts2 dentro de un JSP

Struts2 tiene su forma particular de pasar valores desde el controlador a la vista, no usando los clásicos ámbitos como request, session o application, sino que utilizar una pila de valores a la que se acceder mediante el lenguaje OGNL.

Gracias a las librerías de tags de Struts2 es sencillo acceder a estos valores para hacer operaciones rutinarias, pero a veces queremos, dentro de una página JSP tocar los objetos dentro de los snippets para hacer alguna operación más compleja. Como no están en los “scopes” clásicos, sino en la pila de valores, es algo complicado. Utilizando una combinación de tags de Struts2 y JSP se puede hacer:

En este ejemplo iteramos mediante un tag de Struts2 sobre una lista que obtenemos de un objeto que está en la pila llamado “syllabus” y que tiene un método llamado getCvList() que devuelve un objeto de tipo List de objetos de la clase “entities.Cv”

<s:iterator value="syllabus.cvList" >
   <s:set name="cv" scope="page"/>
   <jsp:useBean id="cv" class="entities.Cv" scope="page"/>
   <%
      cv.getNombre().equals.... (o el código que sea..)
   %>
</s:iterator>

El tag de Struts2 s:set tomará el valor de la cima de la pila, que en este caso es el objeto n-ésimo de la iteración y lo asignará a la variable “cv”. A su vez, mediante el tag de JSP useBean, lo podremos “bajar” al nivel de código JSP para poder usarlo en el snippet mediante código Java.

JSONP. Salvando la limitación de dominio de XHR cuando se hace Cross-Scripting (XSS)

Existe una variación de JSON llamada JSONP (P de padding – con relleno-) que permite salvar uno de los problemas que existe en las llamadas AJAX a través del objeto XMLHttpRequest. Esta limitación consiste en que desde la página web no podemos escribir un Javascript que llame a otro dominio. Esto es así para evitar problemas de seguridad.

Por ejemplo, en uno de los post anteriores en el que se probaba la utilidad de JSON con Jquery se hacía una llamada asíncrona desde JQuery a un servlet en el mismo servidor que respondía con datos en formato JSON. Parte del código de la llamada era

$.post("../jqueryJSON.action", {parametroJSON:$.toJSON(person)} ,

El parámetro de la URL de llamada es ../jqueryJSON.action, que es una dirección relativa y está en el propio servidor, que en este caso es localhost.
Esta vez colocaré la dirección de forma explícita (haz acto de fe que es la buena):

<pre>$.post("http://localhost:8084/TwitterPrueba1/jqueryJSON.action", {parametroJSON:$.toJSON(person)}</pre>

La página sigue haciendo peticiones sin problemas porque su URL es: http://localhost:8084/TwitterPrueba1/jqueryExamples/jqueryJSON.html

Ahora imaginemos que el servidor está en otra URL. Como estoy trabajando en local, voy a hacer un truco, que es cambiar localhost por 127.0.0.1. Todos sabemos que son equivalentes, pero a nivel de dominio son diferentes. Entonces tengo ahora

$.post("http://localhost:8084/TwitterPrueba1/jqueryJSON.action", {parametroJSON:$.toJSON(person)}

Recargo la página y si utilizo una consola de Javascript como firebug de ffox o la de Chrome, tengo el siguiente error al hacer la petición.

XMLHttpRequest cannot load http://127.0.0.1:8084/TwitterPrueba1/jqueryJSON.action/jqueryJSON.action. Origin http://localhost:8084 is not allowed by Access-Control-Allow-Origin.

Como se puede comprobar, no es posible hacer una llamada asíncrona a una url fuera del dominio

Por aclarar, se está usando Jquery, que enmascara todo el proceso de uso del objeto XMLHttpRequest, pero internamente se usa.

¿Para qué queremos cargar información de otros servidores? Muchas de las compañías de servicios de Internet publican sus API a través de este formato (Twitter,Flicker, Google, Yahoo…), de modo que podemos utilizar la potencia de sus servicios fácilmente.

Para poder hacer llamadas a otros servidores y cargar la información de otros servicios, se puede utilizar JSONP, que realmente lo que hace es, en vez de hacer una llamada con el objeto XMLHttpRequest (XHR), imita la inserción de un script en nuestra página, que contendrá básicamente la información de vuelta enformato JSON y la llamada a un función de Javascript que hemos creado.

Por tanto lo que tiene que devolver el servidor es algo del tipo

funcion(Respuesta_formato_JSON)

Este texto como respuesta se meterá dentro de un tag script, de modo que se podrá ejecutar sin llamar al objeto XHR. Por tanto en el cliente, se incluye algo parecido a esto:

<script src="URL_que_devuelve_JSONP"></scrip>

Esa URL como digo devolverá texto en formato JSONP que no es otra cosa que JSON “envuelto” entre paréntesis como parámetro de una función.

Entonces, si vamos más allá y vemos cómo se comporta el escript con la URL, quedará algo así:

<script>funcion(datos_JSONP)</script>

Este código será ejecutado normalmente por el navegador como cualquier otro Javascript

¿Cómo se especifica el nombre de la función que “envuelve” el JSON de respuesta?

Sencillamente se añadirá un parámetro más a la llamada de la URL, por ejemplo “callback”. En este parámetro se le indicará al servlet cómo se llama la función del cliente.

Por usar un servicio conocido que emplee JSONP, el buscador Yahoo provee los resultados en este formato:

http://search.yahooapis.com/ImageSearchService/V1/imageSearch?appid=YahooDemo&output=json&query=pepe&callback=MIFUNCION

Como se puede comprobar, devuelve los respuesta a la búsqueda en Yahoo “pepe” en formato JSON, incluyendo además la función MIFUNCION envolviendo toda la información.

Por lo tanto, en el lado del servidor se debe preparar para devolver la respuesta incluyendo esta función, cuyo nombre proporciona el cliente en Javascript a través de un parámetro.

Modificando el ejemplo de Struts2 en el que se usa Gson, simplemente sería añadir un nuevo parámetro cuyo valor se inyecta, siempre que tengamos el setter preparado:

private String callback;   //Nuevo parámetro

public void setCallback(String callback) {
	this.callback = callback;
}

El otro cambio que hay que hacer es la salida, envolviendo la cadena de salida en formato JSON con el nombre de la función que ha indicado el cliente:

salidaFormatoJSON = callback + "(" + salidaFormatoJSON + ")";

El resultado final de la acción de Struts2 que sirve el código en formato JSONP usando Gson es:

imports...

public class jqueryJSONP extends ActionSupport implements
        ServletRequestAware, ServletResponseAware {

    private HttpServletRequest request;
    private HttpServletResponse response;

    private String parametroJSON;
    private String callback;   //Nuevo parámetro

    public jqueryJSONP() {
    }

    public String execute() throws Exception {

        System.out.println("Parámetro de llamada:" + parametroJSON);

        Gson gson = new Gson();
        ServletOutputStream sos= response.getOutputStream();

        Persona persona=gson.fromJson(parametroJSON, Persona.class);
        System.out.println("Persona Recibida. Nombre:" + persona.nombre + ";Edad:" + persona.edad);

        //1. Conversión de un ArrayList de Objetos a JSON
        ArrayList prueba1Array = new ArrayList();
        for (int i = 0; i &lt; persona.edad; i++) {
            prueba1Array.add(new Persona("Nombre_" + i, ((int)(Math.random()*100))));
        }
        String salidaFormatoJSON = gson.toJson(prueba1Array);

        salidaFormatoJSON = callback + "(" + salidaFormatoJSON + ")";
        sos.print(salidaFormatoJSON);
        System.out.println("Salida:" + salidaFormatoJSON);

        return null;

    }

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

    public void setServletResponse(HttpServletResponse hsr) {
        this.response = hsr;
    }

    public String getParametroJSON() {
        return parametroJSON;
    }

    public void setParametroJSON(String parametroJSON) {
        this.parametroJSON = parametroJSON;
    }

    public void setCallback(String callback) {
        this.callback = callback;
    }

    //Inner class
    private static class Persona {

        String nombre;
        int edad;

        //Necesario para el método fromJson
        public Persona() {
        }

        public Persona(String n, int e) {
            nombre = n;
            edad = e;
        }
    }
}

De este modo hemos creado una acción en Struts2 que puede ser llamada desde cualquier dominio usando JSONP. Es importante tener en cuenta que cuando publicamos algo con JSONP es accesible por cualquier cliente, de modo que debe ser información NO SENSIBLE.

Ahora toca el trabajo en el lado del cliente. Vamos a utilizar el ejemplo modificado que se empleó en el post de explicación de Json desde Jquery un poco modificado.

La llamada al servidor deberá ser de tipo GET, ya que no es una llamadd XHR, sino que es una llamada para hacer un include. Más información en http://www.markhneedham.com/blog/2009/08/27/jquery-post-jsonp-and-cross-domain-requests/

Por tanto vamos utilizar la función $.get de jQuery para recoger la información en formato JSONP con el añadido de la llamada a la función. El procedimiento es muy parecido, sólo que en esta ocasión se construirá la URL incluyendo los parámetros, tal y como se hace con Get

En este caso el código Javascript envía al servidor un objeto persona, con nombre y edad. El servidor devolverá otros objetos persona, tantos como edad tenga el individuo indicado. Por ejemplo 10 años, devolverá como respuesta en JSONP un array con 10 individuos.

Los parámetros en la llamada de la función $.getJSON (o $.get, ya que sólo evita poner el tipo “json” al final), debe constuirse en la URL, ya que de otro modo no funciona.

Esto es incorrecto

$.getJSON("http://localhost:8084/TwitterPrueba1/jqueryJSONP.action", {parametroJSON:$.toJSON(person),callback:"?"} ,

La URL debe construirse de forma explícita, concatenando cadenas:

$.getJSON("http://localhost:8084/TwitterPrueba1/jqueryJSONP.action?parametroJSON="+ $.toJSON(person)+"&callback=?" ,

Si se han seguido las indicaciones anteriores, se esperará que el parámetro callback tenga el nombre de la función de vuelta que tiene que incluir el servidor en la respuesta JSONP. La explicación de que sea una interrogación “?” es que JQuery sustituye la interrogación por un valor determinado por él, y será la referencia a la función de resultado que tiene $getJSON.

A continuación se muestra un ejemplo de salida del servidor en formato JSONP. Obsérverse el nombre elegido por jQuery para la función de callback = jsonp1290095678230

jsonp1290095678230([{"nombre":"Nombre_0","edad":31},{"nombre":"Nombre_1","edad":52},{"nombre":"Nombre_2","edad":82},{"nombre":"Nombre_3","edad":19},{"nombre":"Nombre_4","edad":14},{"nombre":"Nombre_5","edad":33},{"nombre":"Nombre_6","edad":86},{"nombre":"Nombre_7","edad":70},{"nombre":"Nombre_8","edad":56},{"nombre":"Nombre_9","edad":37}])

El resto del proceso no difiere de una llamada JSON normal y corriente. Se ejecutará la función de respuesta, que es otro parámetro de getJSON. En este caso esta función rellena, para mostrar un ejemplo, un select con opciones basadas en las “personas” obtenidas, borrando las que hubiera con anterioridad.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
    <head>
        <title></title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <script language="javascript" src="jquery/jquery.js"></script>
        <script language="javascript" src="jquery/jquery.json-2.2.js"></script>

        <script language="javascript">
            $(document).ready(function() {

                //Petición AJAX, que envía dos parámetros y recibe la respuesta con JSON
                $("#envioPeticion").click(function(e){
                    e.preventDefault();

                    //Crea el objeto que se va a enviar
                    person = new persona($("#nombre").val(),$("#edad").val());

                    $.getJSON("http://localhost:8084/TwitterPrueba1/jqueryJSONP.action?parametroJSON="+ $.toJSON(person)+"&amp;callback=?" ,
                    function(response){

                        console.log("no hay response");

                        var i = 0;
                        var textoSalida = "";

                        console.log(response);
                        

                        $("#selector> option").remove();


                        for (i=0;i<response.length;i++)
                        {
                            textoSalida = textoSalida + response[i].nombre + " - " + response[i].edad;
                            textoSalida = textoSalida + "<br/>";
                            
                            //Añade además al selector los valores
                            $('<option/>').attr("value",response[i].edad).text(response[i].nombre).appendTo("#selector");
                        }
                        //$("#vueltaServidor").html(textoSalida);    //Pega lo recibido en el div
                    });
                });

             

                //Objeto persona
                function persona (nombre, edad)
                {
                    this.nombre = nombre;
                    this.edad = parseInt(edad);
                }

            });
        </script>
    </head>
    <body>

        <h1>Prueba de JSONP</h1>


        <form>
            <input type ="text" id ="nombre" value ="Luis" />
            <input type ="text" id ="edad" value ="10"/>
            <a href="#" id="envioPeticion">Envía la petición</a>
        </form>

        <hr />
        <h3>Esta es la vuelta del servidor</h3>
        <p>Se recibe un objeto JSON y se dibuja en la pantalla</p>

        <form>
            <select id="selector">
                <option value="pepe">pepe</option>
                <option value="luis">luis</option>
            </select>
        </form>

        <div id ="vueltaServidor" style=""></div>


    </body>
</html>

Mediante la utilización de JSONP se podrá acceder a la información que proveen otros servidores, no solamente el servidor en el que se encuentra la página. Existen multitud de servicios que proveen de datos mediante este sistema, como por ejemplo Yahoo o Twitter.

Algunos enlaces útiles:

Envío de parámetros por Post y lectura de respuesta JSON con JQuery

En este ejemplo se muestra cómo se puede utilizar JQuery para realizar una llamada sencilla de tipo AJAX a una acción de Struts2, que recoge los parámetros de la llamada y devuelve esos mismos parámetros en formato JSON, haciendo una lectura posterior con Javascript.

Por lo tanto hay 2 partes en la arquitectura: un servidor con Struts2 que recibe la petición y un cliente en Javascript que reliza la petición y recibe la respuesta, ayudado de las librerías de JQuery.

En primer lugar se crea una página Web con un formulario, que será el que se utilice para la introducción de la información que se va a enviar al servidor

<form>
	<label for="entrada">Pon lo que quieras:</label>
	<a href="#" id="envioPeticion">Envía la petición</a>
</form>

Como se puede ver, se usará un enlace para desencadenar la acción. Este enlace tiene como id “envioPeticion”.

La cabecera de la página Web, tendrá el correspondiente código de Javascript, usando la librería JQuery

$(document).ready(function() {
	//Petición AJAX, que envía dos parámetros y recibe la respuesta con JSON
	$("#envioPeticion").click(function(e){
		e.preventDefault();
		$.post("../jqueryRate.action", {param1:$("#entrada").val(),param2:$("#entrada").val()+"-dos"} ,
                   function(response){
                        var i = 0;
                        var textoSalida = "";

                        for (i=0;i&lt;response.length;i++)
                        {
                            textoSalida = textoSalida + response[i].nombre + &quot; - &quot; + response[i].edad;
                            textoSalida = textoSalida + &quot;<br />";
                        }
                        alert(textoSalida);
                        $("#vueltaServidor").html(textoSalida);    //Pega lo recibido en el div
                    }, "json");
	});
});

La clave está en la instrucción post de JQuery, que envía los parámetros indicados a la URL, en este caso jqueryRate.action. Existen dos parámetros, el primero de ellos es el texto que el usuario ha introducido y el segundo es el mismo pero añadiendo la cadena “-dos” para utilizar de ejemplo. Obsérvese el formato de los parámetros. Esta información pasará por POST a la petición de la URL indicada.

$.post("../jqueryRate.action", {param1:$("#entrada").val(),param2:$("#entrada").val()+"-dos"} ,

El código del lado del servidor es sencillo: está preparado para recibir la petición por POST con Struts2. Para flexibilizar el ejemplo no se esperan dos atributos en los que se cargan los valores de los parámetros, ya que auque figuran con nombres “param1” y “param2” la acción está preparada para cualquier parámetro, de ahí que se lea directamente del objeto Request. De otro modo, si los parámetros son constantes, se podrían haber hecho sendos atributos en la clase con sus getters y setters para que el interceptor inyectara los valores.

El objetivo de la acción es recibir estos dos parámetros en formato estándar para un pos y devolverlos en formato JSON, sin hacer uso de ninguna librería para su transformación (esto lo dejamos para otro post).

La acción implementa ServletRequestAware, ServletResponseAware para tener acceso a los objetos request y response y poder recibir los valores y escribir en la salida. No tiene vista, puesto que necesitamos escribir como respuesta una cadena de texto en formato JSON, no una página JSP, Velocity o HTML. Esa cadena de texto será la que reciba el cliente de Javascript.

import com.opensymphony.xwork2.ActionSupport;
import java.util.Enumeration;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts2.interceptor.ServletRequestAware;
import org.apache.struts2.interceptor.ServletResponseAware;

public class jqueryRate extends ActionSupport implements
        ServletRequestAware, ServletResponseAware {

    private HttpServletRequest request;
    private HttpServletResponse response;
    private ServletOutputStream sos;

    public jqueryRate() {
    }

    public String execute() throws Exception {

        sos = response.getOutputStream();

        Enumeration enumeration = request.getParameterNames();
        String parametro;

        StringBuilder salida = new StringBuilder();

         //Escritura en formato JSON del array
        int contador = 0;
        while (enumeration.hasMoreElements()) {
            contador++;
            parametro = enumeration.nextElement();
            salida.append("{\"Parametro\":\"" + parametro + "\",\"Valor\":\"" + request.getParameter(parametro).toString() + "\"},");
         }
        //Quita la coma final
        salida.deleteCharAt(salida.length() - 1);

        if (contador &gt; 1) {
            salida.insert(0, "[");
            salida.append("]");
        }

        //Imprime el resultado
        sos.print(salida.toString());

        return null;    //Devuelve NULL para no tener vista

    }

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

    public void setServletResponse(HttpServletResponse hsr) {
        this.response = hsr;
    }
}

La salida de esta acción no tendrá una vista como todas las acciones (de ahí que el return del execute sea null), sino que escribe un texto plano de vuelta que estará en formato JSON. En este caso se trata de los parámetros de la llamada, cualesquiera que sea su número. En este caso son dos:

[{"Parametro":"param2","Valor":"pepe-dos"},{"Parametro":"param1","Valor":"pepe"}]

Esta cadena de texto es leída como parámetro de la función de post de JQuery.

Existen dos modos de tratar la respueta de JSON

  • Mediante la función eval se convierte este objeto en formato JSON a objetos de Javascript. MEdiante su manipulación con el lenguaje Javascript y JQuery se pintan en una capa cuyo id es “vueltaServidor”
  • O mediante el último parámetro del $.post, es decir “json”, de modo que ya se encargan JQuery de dar en “response” un objeto de Javascript, fruto del parsing que ha hecho a la cadena de respuesta. Este método es más seguro y eficiente, por lo que se recomienda su uso.
  •   function(response){
    
    	alert(response.length);
    	var i = 0;
    	var textoSalida = "";
    
    	for (i=0;i&lt;response.length;i++)
    	{
    		textoSalida = textoSalida + response[i].Parametro + &quot; - &quot; + response[i].Valor;
    		textoSalida = textoSalida + &quot;<br />";
    	}
    	alert(textoSalida);
    	$("#vueltaServidor").html(textoSalida);    //Pega lo recibido en el div
    }, "json");
    

    Capa en la que se pinta la cadena de texto textoSalida:

    <div id = "vueltaServidor"></div>

    Resumiendo: hemos hecho una llamada a una acción de Struts2 mediante JQuery y su función POST de AJAX, adjuntando mediante POST dos parámetros estándar (no JSON), uno escrito en una caja de texto y otro derivado de éste por fines didácticos. La acción ha recuperado estos parámetros y los ha devuelto en formato JSON, que mediante eval de Javascript hemos transformado en un array de objetos y se han pintado en una capa DIV. Todo ello haciendo uso de la funcionalidad de llamadas asíncronas de AJAX.

Struts2 Acción para escribir directamente en el response

Puede resultar interesante escribir una acción de Struts2 que no tenga ninguna vista como resultado, y escriba directamente en el outputstream del response.

Un ejemplo puede ser una acción que es invocada a través de AJAX y solamente quiere devolver un texto plano sin ningún tipo de formato.

El problema es que en el archivo struts.xml, para cada acción se deberá especificar un resultado. Nuestra acción, al no tener vista no debería tener. Podemos poner uno inventado.

<action name="jqueryRate">	
<result>nosecarganunca.jsp</result>
</action>

Dentro de nuestra acción, para poder tener acceso a los objeto Response y Request, se implementarán sendas interfaces, que posibilitan la inyección de las dependencias de estos objetos por parte del framework:

public class jqueryRate extends ActionSupport implements
ServletRequestAware, ServletResponseAware {
private HttpServletRequest request;       //atributo en la acción para el request
private HttpServletResponse response;

Obviamente se implementan los métodos requeridos por estas interfaces, haciendo la asignación a los dos atributos.

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

Para escribir en el response directamente se debe tomar su stream:

ServletOutputStream sos;
sos = response.getOutputStream();
sos.println("Esta es la respuesta directamente en el response");

Y finalmente el toque final para saltar el requerimiento del resultado en forma de vista: devolver null como resultado del execute

return null;

Struts2: Redirección a una acción cuya ejecución fue interceptada

En ocasiones puede ser interesante modificar el flujo de la ejecución de las acciones por parte de un interceptor para luego devolver el control a la acción que fue invocada originalmente.

Un caso paradigmático es el de Login: cuando se quiere acceder a una acción cuyo acceso está restringido a los usuarios por una pantalla de login. El esquema general de funcionamiento sería:

  1. Llamada a la acción original, llamémosla BorrarUsuarioAction
  2. Invocación de un Interceptor que captura el flujo de ejecución antes de que llege a la acción. El nombre será: LoginInterceptor.
  3. LoginInterceptor comprueba si existe el usuario en la sesión.
    1. Si existe deja continuar el flujo de ejecución hacia BorrarUsuarioAction y termina la ejecución.
    2. Si no existe, redirige el flujo a una nueva página llamada login.jsp que a su vez llama a la acción LoginAction.java
  4. LoginAction valida el usuario y redirige a una página, que en este caso debería ser la acción BorrarUsuarioAction, recuperando así el flujo original de llamadas.

El problema está en cómo sabe LoginAction que tiene que redirigir, en el caso de que el nombre y clave sean correctos a la acción cuya llamada originó todo.

La solución consiste en que en LoginInterceptor se almacenará a nivel de sesión el nombre de la acción que se invocó originalmente.

Map session = actionInvocation.getInvocationContext().getSession();
if (session.get(LoginAction.CLAVESESION) == null) {    //Comprueba si hay usuario
         String actionName = actionInvocation.getProxy().getNamespace() + "/" + actionInvocation.getInvocationContext().getName();
        if (actionName.startsWith("/")) {
            actionName = actionName.substring(1);   //Elimina el primer carácter, que es la /
        }
	session.put("lastAction", actionName);    //Almacena en la sesión el nombre de la acción</code>
	return "login";    //Devuelve como resultado el literal login, que en struts.xml redirigirá a Login.jsp
}
else {
return actionInvocation.invoke();   //Si hay usuario
}

Ya tenemos almacenado el nombre de la acción original en la sesión. Ahora, en la configuración struts.xml, cuando haya tenido éxito la acción Login en la validación del usuario, deberá redirigir a la acción que está almacenada en la sesión. Para ello se utiliza la redirección de acción y el parámetro para fijar el destino, que se lee de la sesión.


       <action name = "Login" class="actions.LoginAction">
            <result type="redirect-action">
                <param name="actionName">${#session['lastAction']}</param>
            </result>
            <result name = "error">/Login.jsp</result>
        </action>