Intercomunicación entre portlets.

1
23415

Intercomunicación entre portlets.

1. Introducción

La intercomunicación entre portlets es una funcionalidad que permite compartir o enviar información entre varios portlets desplegados en cualquier lugar del portal. Antiguamente con la versión 1 de la especificación JSR-168 la comunicación únicamente se realizaba a través de la Session del Portlet o utilizando la implementación que ofrecía el Gestor de Portales que se estuviera manejando. El uso de la solución propietaria impedía que dicho portlet pudiera ser exportado al resto de gestores de portales del mercado. Con la aparición de la versión 2 de la especificación JSR 286 se resolvió este problema. En esta versión se han definido dos nuevos métodos para intercambiar información entre los portlets desplegados en un portal: por eventos o por parámetros públicos de renderización.
Con este tutorial se mostrará el uso de las tres formas a través de un pequeño ejemplo. Se utilizará Liferay como gestor de portales y se tomará como punto de partida el tutorial «Utilización de arquetipos y plugins de LifeRay para Maven.» donde se explican los pasos para crear un portlet en LifeRay.

Este tutorial está relacionado con una charla de autentia que encontrarás aquí

2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Mac Book Pro (Core 2 Duo 2,8 Ghz, 4 GB RAM, 500 GB)
  • Sistema Operativo: Snow Leopard
  • Navegador: Mozilla Firefox 3.6.3
  • Maven: 2.2.1
  • Liferay:5.2.3 + SDK.

3. Ejemplo práctico:

Para ver el funcionamiento de cada método de comunicación se realizará un ejemplo muy sencillo. Se crearán dos proyectos maven, el primero llamado «localizador-portlet» que se encargará de obtener una dirección a través de un formulario, y el segundo llamado «visualizador-portlet» mostrará un mapa con la localización de esa dirección. En cada uno de ellos se harán tres versiones del mismo portlet, implementando cada tipo de comunicación.

Lo primero de todo es crear los dos proyectos utilizando el arquetipo «liferay-portlet-archetype» tal como se indica en el tutorial al que se ha hecho referencia anteriormente.

mvn -e archetype:create -DgroupId=com.adictos.portlet -DartifactId=localizador-portlet -DarchetypeGroupId=com.liferay.maven.archetypes -DarchetypeArtifactId=liferay-portlet-archetype -DarchetypeVersion=6.0.2-SNAPSHOT
mvn -e archetype:create -DgroupId=com.adictos.portlet -DartifactId=visualizador-portlet -DarchetypeGroupId=com.liferay.maven.archetypes -DarchetypeArtifactId=liferay-portlet-archetype -DarchetypeVersion=6.0.2-SNAPSHOT

3.1 Intercomunicación entre Portlets utilizando la sesión.

La intercomunicación utilizando la sesión fue el primer método que se utilizó para este propósito. Su uso es bastante sencillo, se recupera la sesión del portlet y se establece el atributo que se desea compartir, indicando que dicho atributo va a ser visible por el resto de la aplicación (APPLICATION_SCOPE).

Empezamos con el portlet «localizador-portlet». Su función es presentar un formulario para que el usuario introduzca una dirección. Cuando éste lo envíe, la dirección se almacenará en la sesión para que el portlet «visualizador-portlet» lo recupere y pinte el mapa con la localización.

Creamos una clase llamada LocalizadorSessionPortlet que extienda de GenericPortlet.

package com.adictos.portlet.localizador;

import java.io.IOException;

import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.GenericPortlet;
import javax.portlet.PortletException;
import javax.portlet.PortletRequestDispatcher;
import javax.portlet.PortletSession;
import javax.portlet.ProcessAction;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;

public class LocalizadorSessionPortlet extends GenericPortlet{

	protected String viewJSP;
	protected String keyAddressSession;
	public static final String KEY_JSP_ADDRESS = "address";
	
	@Override
	public void init() throws PortletException {
		//Se recupera la jsp encargada de pintar el portlet
		viewJSP = getInitParameter("view-jsp");
		//Se recupera la clave utilizada para compartir la dirección entre los portlets
		keyAddressSession = getInitParameter("key-address-session");
		
	}

	//Método que se ejecuta cuando se envia el formulario
	@ProcessAction(name="localizar")
	public void processLocalizador(ActionRequest request, ActionResponse response) {
		//Se recupera la seion del portlet
		PortletSession session = request.getPortletSession();
		//Se añade a la sesion la direccion en el ambito de la aplicacion
		session.setAttribute(keyAddressSession, request.getParameter(getDefaultNamespace()+ KEY_JSP_ADDRESS),PortletSession.APPLICATION_SCOPE);
	}
	
	@Override
	public void doView(RenderRequest request, RenderResponse response) throws IOException, PortletException {
		include(viewJSP, request, response);
	}

	protected void include(
			String path, RenderRequest renderRequest,
			RenderResponse renderResponse)
		throws IOException, PortletException {

		PortletRequestDispatcher portletRequestDispatcher =
			getPortletContext().getRequestDispatcher(path);

		if (portletRequestDispatcher == null) {
			throw new IOException(path + " is not a valid include");
		}
		else {
			portletRequestDispatcher.include(renderRequest, renderResponse);
		}
	}

}

Creamos la jsp view.jsp que será común a todos los portlet que se vayan a crear:

<%@ taglib uri="http://java.sun.com/portlet" prefix="portlet" %>
<%@page import="com.adictos.portlet.localizador.LocalizadorSessionPortlet"%>


<%@page import="javax.portlet.Portlet"%>
<%@page import="javax.portlet.ActionRequest"%>



Se añade la definición del portlet al descriptor portlet.xml:

	
...
	
		localizador-portlet
		localizador-portlet
		com.adictos.portlet.localizador.LocalizadorSessionPortlet
		
			view-jsp
			/view.jsp
		
		
			key-address-session
			com.adictos.portlet.localizador.key.address
		
		0
		
			text/html
		
		
			localizador-portlet
			localizador-portlet
			localizador-portlet
		
		
			administrator
		
		
			guest
		
		
			power-user
		
		
			user
		
	
...


A continuación se añade la configuración específica del portlet para que funcione correctamente en liferay. En el descriptor liferay-portlet.xml se definen algunos parámetros específicos para el gestor de portales. Debemos hacer hincapié en el atributo private-session-attributes que hay que deshabilitar para que la compartición de atributos a través de la sesión funcione.


...
	
		localizador-portlet
		/icon.png
		true
		
		false
		/css/portlet.css
		/js/javascript.js
	
...

Y por último se añade al fichero liferay-display.xml la asociación del portlet con la categoría a la que pertenece. En nuestro caso a la categoría «Sample».


	
	...
		
	...
	


Ahora se tiene que desarrollar el portlet «visualizador-portlet», su función es recoger la dirección que haya depositado en la sesión «localizador-portlet» y utilizando el API de Google Maps mostrar el mapa con la localización.

Creamos la clase VisualizadorSessionPorlet:

package com.adictos.portlet.visualizador;

import java.io.IOException;

import javax.portlet.GenericPortlet;
import javax.portlet.PortletException;
import javax.portlet.PortletRequestDispatcher;
import javax.portlet.PortletSession;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;

public class VisualizadorSessionPortlet extends GenericPortlet {

	protected String viewJSP;
	protected String keyAddressSession;
	public static final String KEJ_JSP_ADDRESS = "address";
	
	@Override
	public void init() throws PortletException {
		//Se recupera la jsp encargada de pintar el portlet
		viewJSP = getInitParameter("view-jsp");
		//Se recupera la clave utilizada para compartir la dirección entre los portlets
		keyAddressSession = getInitParameter("key-address-session");
	}

	@Override
	protected void doView(RenderRequest request, RenderResponse response)
			throws PortletException, IOException {
		//Se recupera la seion del portlet
		PortletSession session = request.getPortletSession(false);
		//Se recupera la dirección que haya establecido el otro portlet en  la sesion
		String address = (String) session.getAttribute(keyAddressSession, PortletSession.APPLICATION_SCOPE);
		if (address != null) {
			//Se establece la dirección en la request donde lo leerá la jsp
			request.setAttribute(KEJ_JSP_ADDRESS, address);
		}
		
		include(viewJSP, request, response);
	}

	@Override
	protected void doHeaders(RenderRequest request, RenderResponse response) {
		super.doHeaders(request, response);
	}

	protected void include(
			String path, RenderRequest renderRequest,
			RenderResponse renderResponse)
		throws IOException, PortletException {

		PortletRequestDispatcher portletRequestDispatcher =
			getPortletContext().getRequestDispatcher(path);

		if (portletRequestDispatcher == null) {
			throw new IOException(path + " is not a valid include");
		}
		else {
			portletRequestDispatcher.include(renderRequest, renderResponse);
		}
	}

}

Creamos la jsp view.jsp común a todas las versiones del portlet:

<%@ taglib uri="http://java.sun.com/portlet" prefix="portlet" %>
<%@page import="com.adictos.portlet.visualizador.VisualizadorSessionPortlet"%>


Creamos el fichero javascript.js que se usa para localizar la dirección en Google Maps (idéntico en todos los ejemplos).

Event.observe(window,"load", initGMaps);


var map;

function onLoadGeo (point) {
	map.setCenter(point,16);
	map.clearOverlays() ;
	map.addOverlay(new GMarker(point));
}						


function initGMaps() {
	if (GBrowserIsCompatible()) {
		map = new GMap2($('google_maps'));
		map.setCenter(new GLatLng(39.856778,-4.024476),4);
		map.setMapType(G_NORMAL_MAP);
		var geo = new GClientGeocoder();
		
		geo.getLatLng($('address').firstChild.nodeValue,onLoadGeo);

	}
}

Y por último se añade la configuración del portlet en los xml. Remarcar las etiquetas del fichero liferay-portlet.xml footer-portal-javascript y footer-portlet-javascript pues son el método que pone a disposición liferay para añadir recursos javascript con el fin de ser usados en el portlet.

Fichero portlet.xml

	
...
	
		visualizador-portlet
		visualizador-portlet
		com.adictos.portlet.visualizador.VisualizadorSessionPortlet
		
			view-jsp
			/view.jsp
		
		
			key-address-session
			com.adictos.portlet.localizador.key.address
		
		0
		
			text/html
		
		
			visualizador-portlet
			visualizador-portlet
			visualizador-portlet
		
		
			administrator
		
		
			guest
		
		
			power-user
		
		
			user
		
	
...


Fichero liferay-portlet.xml:


...
	
		visualizador-portlet
		/icon.png
		true
		false
		/css/portlet.css
		
		http://maps.google.com/maps?file=api&v=2&key=ABQIAAAAAMeZHKeOqK-v_PHwvq2QJRTFY3xLQcZvsmZbINvV2jie0CQhfRR4qwlngIcWd5-KcKJs4bAE5zw9Dw
		/js/prototype.js
		/js/javascript.js
	
...


Fichero liferay-display.xml:


	
		...
		
		...
	


Después se despliegan los portlets en liferay; para ello se ejecuta el comando en cada uno de los proyectos maven generados:

mvn -e clean package liferay:deploy

Finalmente se accede al portal como administrador, se añaden los portlets a una página y se realiza una prueba enviando el formulario con una dirección.

3.2 Intercomunicación entre Portlets utilizando parámetros públicos.

La intercomunicación utilizando parámetros públicos es un método disponible a partir de la versión 2 (JSR 286); para poder utilizarlo únicamente se debe registrar el parámetro en el fichero portlet.xml con el atributo «supported-public-render-parameter». En ese momento, cuando en la url de invocación se encuentre dicho parámetro, aquellos portlets que hayan indicado su uso podrán acceder a su valor de la misma forma que se hace con cualquier otro parámetro.

En este ejemplo y en el siguiente sólo se van a incluir aquellos recursos relevantes para el mismo, ya que la información de los ficheros liferay-display.xml, liferay-portlet.xml es prácticamente idéntica a excepción del nombre del portlet.

Para comenzar crea la clase LocalizadorParameterPortlet:

package com.adictos.portlet.localizador;

import java.io.IOException;

import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.GenericPortlet;
import javax.portlet.PortletException;
import javax.portlet.PortletRequestDispatcher;
import javax.portlet.ProcessAction;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;

public class LocalizadorParameterPortlet extends GenericPortlet {

	protected String viewJSP;
	protected String keyAddressSession;
	public static final String KEY_JSP_ADDRESS = "address";
	
	@Override
	public void init() throws PortletException {
		//Se recupera la jsp encargada de pintar el portlet
		viewJSP = getInitParameter("view-jsp");
		
	}
	//Se define el nombre del accion que ejecutara este metodo
	@ProcessAction(name="localizar")
	public void processLocalizador(ActionRequest request, ActionResponse response) {
				
		//Se recupera la direccion enviada desde el formulario
		String address = request.getParameter(getDefaultNamespace()+ KEY_JSP_ADDRESS);
		if (address != null) {
			//Establecemos el parametro render publico
			response.setRenderParameter("address", address);
		}
		
		
	}
	
	@Override
	public void doView(RenderRequest request, RenderResponse response) throws IOException, PortletException {
		include(viewJSP, request, response);
	}

	protected void include(
			String path, RenderRequest renderRequest,
			RenderResponse renderResponse)
		throws IOException, PortletException {

		PortletRequestDispatcher portletRequestDispatcher =
			getPortletContext().getRequestDispatcher(path);

		if (portletRequestDispatcher == null) {
			throw new IOException(path + " is not a valid include");
		}
		else {
			portletRequestDispatcher.include(renderRequest, renderResponse);
		}
	}

}

Se añade la definición del portlet al descriptor portlet.xml, donde se establece el parámetro público de renderización:


...
	
		localizador-parameter-portlet
		localizador-parameter-portlet
		com.adictos.portlet.localizador.LocalizadorParameterPortlet
		
			view-jsp
			/view.jsp
		
		0
		
			text/html
		
		
			localizador-parameter-portlet
			localizador-parameter-portlet
			localizador-parameter-portlet
		
		
			administrator
		
		
			guest
		
		
			power-user
		
		
			user
		
		address
	
	...
	
		address
		x:address
	
	...


Ahora creamos la versión del visualizador para que soporte parámetro públicos:

package com.adictos.portlet.visualizador;

import java.io.IOException;

import javax.portlet.GenericPortlet;
import javax.portlet.PortletException;
import javax.portlet.PortletRequestDispatcher;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;

public class VisualizadorParameterPortlet extends GenericPortlet {

	protected String viewJSP;
	public static final String KEJ_JSP_ADDRESS = "address";
	@Override
	public void init() throws PortletException {
		//Se recupera la jsp encargada de pintar el portlet
		viewJSP = getInitParameter("view-jsp");
	}

	@Override
	protected void doView(RenderRequest request, RenderResponse response)
			throws PortletException, IOException {
		//Se recupera el valor del paamero publico de renderizacion
		String address = request.getParameter("address");
		if (address != null) {
			//Se establece la direccion en la request donde lo leera la jsp
			request.setAttribute(KEJ_JSP_ADDRESS, address);
		}
		
		include(viewJSP, request, response);
	}

	@Override
	protected void doHeaders(RenderRequest request, RenderResponse response) {
		super.doHeaders(request, response);
	}

	protected void include(
			String path, RenderRequest renderRequest,
			RenderResponse renderResponse)
		throws IOException, PortletException {

		PortletRequestDispatcher portletRequestDispatcher =
			getPortletContext().getRequestDispatcher(path);

		if (portletRequestDispatcher == null) {
			throw new IOException(path + " is not a valid include");
		}
		else {
			portletRequestDispatcher.include(renderRequest, renderResponse);
		}
	}

}

Y su configuración en el fichero portelt.xml:


...
	
		visualizador-parameter-portlet
		visualizador-parameter-portlet
		com.adictos.portlet.visualizador.VisualizadorParameterPortlet
		
			view-jsp
			/view.jsp
		
		0
		
			text/html
		
		
			visualizador-parameter-portlet
			visualizador-parameter-portlet
			visualizador-parameter-portlet
		
		
			administrator
		
		
			guest
		
		
			power-user
		
		
			user
		
		address
	
...	
	
		address
		x:address
	
...


Si desplegáis de nuevo los portlets añadiéndolos a una página, veréis que el comportamiento funcional del portlet es idéntico que el ejemplo anterior pero en este caso utilizando parámetros públicos que son compartidos por todos aquellos portlets que lo hayan registrado en su configuración.

3.3 Intercomunicación entre Portlets utilizando eventos.

La intercomunicación usando eventos se asemeja a la gestión de eventos de SWING. Existen productores de eventos y subscriptores que serán notificados cuando se lance el mismo. Los eventos sólo podrán ser lanzados en la fase de Action de un portlet utilizando el método setEvent de la clase ActionResponse. Una vez que se haya publicado un evento, el contenedor, antes de invocar a la renderización de los portlets, notificará a todos aquellos que se hubieran subscrito al evento. La configuración de productores (supported-publishing-event) y subscriptores, como os podéis imaginar, debe encontrarse en el fichero portlet.xml. A continuación se muestra la versión del ejemplo para que funcione con eventos.

Primero se crea la clase LocalizadorEventPortlet:

package com.adictos.portlet.localizador;

import java.io.IOException;

import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.GenericPortlet;
import javax.portlet.PortletException;
import javax.portlet.PortletRequestDispatcher;
import javax.portlet.ProcessAction;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import javax.xml.namespace.QName;

public class LocalizadorEventPortlet extends GenericPortlet {

	protected String viewJSP;
	protected String keyAddressSession;
	public static final String KEY_JSP_ADDRESS = "address";
	
	@Override
	public void init() throws PortletException {
		//Se recupera la jsp encargada de pintar el portlet
		viewJSP = getInitParameter("view-jsp");
		
	}

	//Se define el nombre del accion que ejecutara este metodo
	@ProcessAction(name="localizar")
	public void processLocalizador(ActionRequest request, ActionResponse response) {
				
		//Se recupera la direccion
		String address = request.getParameter(getDefaultNamespace()+ KEY_JSP_ADDRESS);
		if (address != null) {
			//Notificamos que se ha producido un cambio en la direccion
			response.setEvent(new QName("https://adictosaltrabajo.com/events","addressChangeEvent"), address);
		}
		
		
	}
	
	@Override
	public void doView(RenderRequest request, RenderResponse response) throws IOException, PortletException {
		include(viewJSP, request, response);
	}

	protected void include(
			String path, RenderRequest renderRequest,
			RenderResponse renderResponse)
		throws IOException, PortletException {

		PortletRequestDispatcher portletRequestDispatcher =
			getPortletContext().getRequestDispatcher(path);

		if (portletRequestDispatcher == null) {
			throw new IOException(path + " is not a valid include");
		}
		else {
			portletRequestDispatcher.include(renderRequest, renderResponse);
		}
	}

}

Se define el portlet en el fichero portlet.xml con el evento:




...
	
		localizador-event-portlet
		localizador-event-portlet
		com.adictos.portlet.localizador.LocalizadorEventPortlet
		
			view-jsp
			/view.jsp
		
		0
		
			text/html
		
		
			localizador-event-portlet
			localizador-event-portlet
			localizador-event-portlet
		
		
			administrator
		
		
			guest
		
		
			power-user
		
		
			user
		
		
			e:addressChangeEvent
		
	
	
		e:addressChangeEvent
		java.lang.String
			
...


Ahora se crea la versión del visualizador: la clase VisualizadorEventPortlet.

package com.adictos.portlet.visualizador;

import java.io.IOException;

import javax.portlet.EventRequest;
import javax.portlet.EventResponse;
import javax.portlet.GenericPortlet;
import javax.portlet.PortletException;
import javax.portlet.PortletRequestDispatcher;
import javax.portlet.PortletSession;
import javax.portlet.ProcessEvent;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;

public class VisualizadorEventPortlet extends GenericPortlet {

	protected String viewJSP;
	public static final String KEY_JSP_ADDRESS = "address";
	@Override
	public void init() throws PortletException {
		viewJSP = getInitParameter("view-jsp");
	}

	@Override
	protected void doView(RenderRequest request, RenderResponse response)
			throws PortletException, IOException {
		//Se recupera de la sesion la direccion establecida por la ejecucion del evento
		PortletSession session = request.getPortletSession();
		String address = (String) session.getAttribute(KEY_JSP_ADDRESS);
		request.setAttribute(KEY_JSP_ADDRESS, address);
		include(viewJSP, request, response);
	}

	//Definimos el metodo que se ejecutara cuando se notifique el evento
	@ProcessEvent(qname="{https://adictosaltrabajo.com/events}addressChangeEvent")
	public void onChangeAddress (EventRequest request, EventResponse response)
			throws PortletException, IOException {
		//Se recibe el evento y se establece el valor en la sesion
		PortletSession session = request.getPortletSession();
		session.setAttribute(KEY_JSP_ADDRESS, request.getEvent().getValue());
	}


	protected void include(
			String path, RenderRequest renderRequest,
			RenderResponse renderResponse)
		throws IOException, PortletException {

		PortletRequestDispatcher portletRequestDispatcher =
			getPortletContext().getRequestDispatcher(path);

		if (portletRequestDispatcher == null) {
			throw new IOException(path + " is not a valid include");
		}
		else {
			portletRequestDispatcher.include(renderRequest, renderResponse);
		}
	}

}

Y la correspondiente configuración en el portlet.xml:




...	
	
		visualizador-event-portlet
		visualizador-event-portlet
		com.adictos.portlet.visualizador.VisualizadorEventPortlet
		
			view-jsp
			/view.jsp
		
		0
		
			text/html
		
		
			visualizador-event-portlet
			visualizador-event-portlet
			visualizador-event-portlet
		
		
			administrator
		
		
			guest
		
		
			power-user
		
		
			user
		
		
			e:addressChangeEvent
		
		
	
		e:addressChangeEvent
		java.lang.String
			
	
...


4. Conclusión

Como se ha podido ver, el procedimiento para intercomunicar portlets con la especificación JSR 286 es muy sencillo y sistemático. Únicamente se tendrá que optar por una de las tres opciones, teniendo en cuenta siempre la más adecuada para la situación que estemos desarrollando.

1 COMENTARIO

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad