En Mi Local Funciona

Technical thoughts, stories and ideas

Portlets 3.0 y Liferay 7 ¿Complementarios o competidores?

Publicado por Antonio Javier Ortega Pérez el

PortalesLiferayPortlets 3.0

Liferay es un portal implementado en Java que permite una gran adaptabilidad gracias a ser un Portlet Container. El hecho de implementar esta especificación es lo que le da a Liferay (al igual que a otros portales) el poder desarrollar nuevas funcionalidades de forma estándar. Dada la importancia que tiene la especificación de portlets dentro del mundo Liferay en este post se analizarán las características de la nueva versión de la especificación de portlets (Portlets 3.0 JSR 362), su implantación en el mercado y su (posible) aplicación a Liferay 7 o futuro Liferay 8.

Historia de la especificación de Portlets

Portlet 1.0

La especificación de portlets es bastante antigua, la versión final de Portlet 1.0 JSR 168 apareció en octubre de 2003. Esta especificación establecía las bases del comportamiento de Portlets, lo cual cabe destacar que sigue totalmente vigente en las versiones 2 y 3. Los puntos más destacables de esta especificación eran:

  • Ejecución en dos fases (action y render) para la implementación del Model View Controller.
  • Portlet Modes (View, Edit Help). Permite crea visualizaciones y acciones diferentes en función del modo de ejecución.
  • Window State. Indican la cantidad de espacio que ocupará el portlet.
  • Modelo de datos. Permite al portlet guardar información de diferente manera, como render parameters, en sesión mediante PortletSession o bien como PortletPreferences.

Portlet 2.0

La especificación de portlets 2.0 apareció en junio de 2008 y fue pensada para cubrir carencias que tenía la versión inicial y cubrir necesidades adaptadas a la época. Entre las características más destacables están:

  • IPC Inter Portlet Communication. En la especificación hay dos formas de hacer IPC, mediante public render parameters y mediante eventos.
  • Capacidades AJAX a través de ResourceServingPortlet / ResourceRequest / RsourceResponse
  • Implementación de Portlet Filters y Portlet Listeners.

Portlet 3.0

La especificación de portlets 3.0 se empezó a definir en febrero de 2013 y la versión final se publicó en abril de 2017. Respecto a estas fechas cabe destacar dos hechos:

  1. Se trabajó en la especificación durante 4 años.
  2. Respecto a la especificación 2.0 tardó casi 9 años en aparecer.

Los puntos más destacables (que se desarrollaran con más detalle más adelante) son:

  • Poder definir recursos globales.
  • Integración / alineamiento con CDI 1.2.
  • Integración / alineamiento con Servlet 3.1.

Novedades de la expecificación 3.0

Portlet 3.0 introduce varias adaptaciones a nivel de modelo mediante RenderState y HeaderPhase.

La característica más destacada de Portlets 3.0 es el alineamiento con Java EE7, principalmente con CDI 1.2 y Sevlets 3.1, aún que también con WebSockets y JSF 2.2. Otro puntos destacables son mayor flexibilida en el us

RenderState

Se trata de una nueva interface. Típicamente el estado de un portlet se ha definido mediante portletMode, rednderParameters y windowState, ahora, en la especificación de portlet 3.0 se crea este interfaz que explicita el estado. De esta interfaz hereda MutableRenderState que añade los métodos ‘setters’. Las diferentes interfaces o clases que utilicen (pero no modifiquen) el estado, heredaran directa o indirectamente de RenderState, mientras que aquellas que puedan modificar el estado heredaran o implementaran de MutableRenderState.

HeaderPhase

  • Es una fase que se ejecuta antes de que se devuelva todo el conjunto de la página.
  • El contenido generado en esta fase se añadirá a la cabecera de la página.
  • Se pueden modificar las cabeceras HTTP.
  • El portlet puede declarar dependencias de recursos y el portal ser quien gestione estas dependencias.

@HeaderMethod(portletNames="*")
public void header(HeaderRequest req, HeaderResponse resp) throws IOException {  
  String contextRoot = req.getContextPath();
  StringBuilder txt = new StringBuilder(128);
  txt.append("<link href='").append(contextRoot);
  txt.append("/resources/css/infobox.css' rel='stylesheet' type='text/css'>");
  resp.addDependency("infobox", "org.apache.pluto", "0.3.0", txt.toString());
}

El uso de la HeaderPhase permite que dos portlets diferentes puedan utilizar el mismo recurso: Supongamos una imagen o fichero js que es utilizado por dos portlets diferentes (dos WARs / JARs totalmente diferentes). Actualmente lo que ocurriría es que, siendo exactamente el mismo recurso, este se incluiría dos veces en la página, haciendo referencia a donde se encuentra almacenado. Mediante HeaderPhase el recuso solo se serviría una vez al navegador.

Portlet Hub

Esta es una funcionalidad muy deseada y novedosa dado que es la primera vez que una especificación Java incluye la definición de un componente Javascript. Es un gestor de porlets en el lado cliente implementado en Javascript que facilita la implementación de portales siguiendo la filosofía SPA y el uso de AJAX. Este gestor permite hacer operaciones típicas como establecer render parameters, ejecutar portlet actions, generar URL, etc. siguiendo una filosofía AJAX y evitando refrescos de página. Los portlets interactúan con el PortletHub con Javascript en el lado cliente, y el portlet hub es quien se encarga de la comunicación con el portal. Los portlets se pueden registrar en el portlet hub para recibir notificaciones, cambios de estado, etc.

Las principal API del portlet hub es:

  • Promise(PortletInit) register( < Portlet ID > ). Registra un portlet en el portlet hub y devuelve un objeto que da acceso al resto de funciones del PortletHub.
  • ListenerID addEventListener( < listener type > , < listener method > ). Registrar eventListeners para notificar cambios de estado.
  • Promise(url) createResourceUrl( < resource parameters > , < cacheability > , < resource ID > ). Crear resourceUrl’s.
  • setPortletState( < new portlet state > ). Establecer public y private render parameters.
  • action( < action parameters > , < form element > ). Ejecutar acciones.
// Register portlet with Portlet Hub. Add listener for onStateChange event.
portlet.register(pid).then(function (pi) {  
   console.log("CSP Color Selection Portlet: registered: " + pid);
   hub = pi;
   currState = hub.newState();
   hub.addEventListener("portlet.onStateChange", update);
});

Secuencia de registro:

Secuencia de acción:

Integración con CDI

Se añaden anotaciones para la definición e instanciación de los portlets. Se habilita escaneo del classpath en busca de anotaciones de definición (@PortletApplication, @PortletConfiguration, @PortletFilter) o ficheros portlet-fragment.xml (similar a web-fragment.xml), lo que permite menos (o nula) configuración a través de ficheros, o bien, que esta configuración no esté tan centralizada en los típicos descriptores.

@PortletApplication(
   runtimeOptions = {
      @RuntimeOption(name = "javax.portlet.escapeXml", values = { "true" }),
      @RuntimeOption(name = "javax.portlet.actionScopedRequestAttributes", values = { "true" })
   },
   customPortletModes={
      @CustomPortletMode(
         name = "custom1",
         portalManaged=false
      ),
      @CustomPortletMode(
         name = "custom2",
         portalManaged=true
      )
   },
   customWindowStates = {
      @CustomWindowState(name = "custom1")   
   },
   publicParams = {
      @PublicRenderParameterDefinition(
         identifier = "tr0_public", 
         qname = @PortletQName(
            localPart = "tr0_public", 
            namespaceURI = ""
         )
      ) 
   },
   events = {
      @EventDefinition(
         qname = @PortletQName(
         localPart = "AnnotationPortletApplicationConfigTests_SPEC1_28_EventConfigurationTr0", 
         namespaceURI = "http://www.apache.org/portals/pluto/portlet-tck_3.0"), 
      payloadType = java.lang.String.class
      ), @EventDefinition(
         qname = @PortletQName(
         localPart = "AnnotationPortletApplicationConfigTests_SPEC1_28_EventConfigurationTr1", 
         namespaceURI = ""), 
      payloadType = java.lang.String.class
      )
   }
)
@PortletConfiguration(portletName = "AnnotationPortletApplicationConfigTests_SPEC1_28_EventConfiguration")

Los métodos relacionados con portlets podrán estar ubicados en varias clases mediante el uso de anotaciones @RenderMethod, @ActionMethod o @ResourceMethod.

Integración con CDI mediante las anotaciones típicas, como es @Inject, y un nuevo conjunto de anotaciones que indican el ámbito de los beans dentro del contexto de portlet: @ApplicationScoped, @PortletRequestScoped, @RenderStateScoped, @PortletSessionScoped, etc.

Una funcionalidad muy práctica es que se podrán inyectar beans típicamente utilizados como son Request, Response, PortletConfig, parámetros, etc. Estas inyecciones se pueden realizar tanto en código de portlet como en JSP mediante EL.

Asynchronous processing

Esta funcionalidad es la contrapartida en el mundo de portlets de la funcionalidad Asynchronous Servlets en el mundo de servlets (@WebServlet(asyncSupported = true, value = "/AsyncServlet")). La idea es que para peticiones costosas que requieran un cierto coste de recursos delegar la ejecución en un thread / worker independiente del pool del servidor, avisar al servidor de que se va a utilizar este worker, y liberar lo antes posible el thread de servidor:

@ServeResourceMethod(portletNames = "BeanPortletDemo", 
  asyncSupported = true,
  resourceID="getChatHistory")
public void getChatHistory(ResourceRequest req, ResourceResponse resp)  
        throws IOException, PortletException {
    boolean refresh = new Boolean(req.getResourceParameters().
         getValue("refresh"));
    PortletAsyncContext portletAsyncContext = req.startPortletAsync();
    portletAsyncContext.setTimeout(60000);
    portletAsyncContext.addListener(listener);
    runner.init(portletAsyncContext, refresh);
    portletAsyncContext.start(runner);
}

Multipart

  • Se añade soporte a la gestión de multipart. Ahora, el standard de portlets añade una dependencia respecto javax.servlet.http.Part para no depender de otros frameworks como commons-fileupload.
  • Posibilidad de obtener facilment el ‘User agent’ en cualquier Request
  • Posibilidad de establecer directamente los códigos de estado http para ResourceRequest.

¿Quién implementa Portlet 3.0?

Después de todo lo explicado hasta el momento, queda claro que Portlet 3.0 / JSR 362 es una especificación, pero … ¿Quién la implementa? A fecha de escritura de este post solo se encuentra implementado en Apache Pluto 3.0 que es la implementación de referencia y la única en pasar el TCK (bueno, claro, sólo hay una).

Apache Pluto 3.0.0 se libera el 18 de enero de 2017 curiosamente antes de la ‘Final realease’ de Portlet 3.0, incluso antes del Proposed Final Draft (23 de enero de 2017). Desde entonces hasta la fecha de escritura de este post no ha habido ninguna evolución, ni siquiera en cuanto a minor versión (no ha aparecido una versión 3.0.1)

Por otra parte, cabe destacar que Apache Pluto es un Portlet Container, no un portal. Un portal en sí utiliza el portlet container como núcleo, pero incluye funcionalidades que lo hacen usable de base (como un conjunto de portlets, gestión de la seguridad, temas, un entorno de desarrollo amigable, etc.). Apache Pluto es el portlet container utilizado en Jetspeed, el cual, si se considera un portal ‘real’, sin embargo, si nos fijamos en las versiones de Jetspeed se puede observar como la última versión es la 2.3.1 (de mayo de 2016), utiliza Pluto 2 como portlet container, y, evidentemente, implementa solo Portlet 2.0 (dadas las fechas de lanzamiento). Pero lo más curioso es que, al fijarse en la sección de ‘roadmap’ de Jetspeed no hay nada previsto a futuro, entonces, no está previsto el lanzamiento de digamos un Jetspeed 3 que utilice Pluto 3 como portlet container, por tanto, se puede afirmar que, a fecha de escritura de este post, Pluto es la prueba de concepto y la implementación de referencia de Portlet 3.0, pero que realmente no es usable en un entorno “real”.

A parte, también resulta curioso ver que en el ‘Expert group’ de definición de Portlet 3.0 (JSR 362) se encuentra gente de Oracle, IBM, Liferay, Vaadin y otros, pero que, sin embargo, ni Liferay 7 ni IBM Websphere Portal 9 han implementado esta especificación.

¿Cómo cuadra todo Liferay?

Ahora que sabemos las fechas finales de las diferentes especificaciones de portlets vamos a poner encima de la mesa las fechas de aparición de las últimas versiones de Liferay:

  • 6.0 – 04/03/2010
  • 6.1 – 10/01/2012
  • 6.2 – 12/12/2013
  • 7.0 – 17/05/2016

De la lista anterior se denota que la versión definitiva de Liferay 7 salió bastante antes que la versión definitiva de Portlets 3.0, pero claro, a parte del detalle de la “fecha fin”, en Liferay 7 se empezó a trabajar varios años antes, de hecho, la primera “milestone” (7.0.0 M1) es del 20 de agosto de 2014.

Con toda la información anterior, en mi opinión, ha ocurrido lo siguiente:

La especificación 3.0 de Portlets tardó mucho tiempo en completarse (¡9 años!), hecho que aplicado a la informática se convierte en una eternidad. En ese lapso de tiempo los fabricantes, y en especial Liferay, han querido adaptarse más rápidamente a las tendencias del momento, y, avanzaron por su cuenta, pero, aplicando la misma filosofía que la especificación (AJAX, SPA, inyección de dependencias, etc.) dado que las necesidades observadas eran las mismas. Este hecho es algo que en el mundo Java ya ha ocurrido en varias ocasiones, como por ejemplo con JPA e Hibernate, CDI y Spring, etc.

Dado todo lo anterior, Liferay avanzó en implementar nuevas funcionalidades que cubrieran las necesidades del momento pero que éstas no alterasen la especificación original de Portlet 2.0. Por ejemplo, hay muchas funcionalidades descritas en la especificación 3.0 de Portlets que tienen su contrapartida (o muy parecida) en Liferay 7:

Pero en contrapartida, Liferay no ha implementado conceptos de la especificación 3.0, como PartialAction, HeaderPhase, RenderState / MutableRenderState, etc. que son conceptos muy ligados a la especificación y que de haberlos implementado por su cuenta fácilmente podría haber tenido algún efecto colateral y no ser completamente compatible con la especificación 2,0 de portlets.

¿Qué pasará a futuro?

Siendo esta, desde mi punto de vista, la situación actual, la pregunta es ¿Qué pasará a futuro? Habiendo pasado un año de la publicación final de la especificación 3.0 de portlets solo existe Pluto 3.0 como implementación, y éste último, no se ha consolidado en una versión funcional de JetSpeed 3. Ningún fabricante ha implementado la especificación, y en especial ni IBM ni Liferay, líderes del Expert Group de la JSR 362. Liferay avanzó por su cuenta en implementar funcionalidades necesarias al momento, pero presentes en Portlet 3.0, entonces, a futuro ¿se puede esperar que Liferay implemente la especificación 3.0?

En mi opinión creo que lo acabarán implementando dado que hay conceptos básicos interesantes, pero tendrán un cierto grado de colisión en las funcionalidades ‘redundadas’. Si más bien es una pregunta que ya se ha realizado en alguna DevCon, las funcionalidades presentes en Liferay 7 ¿Compiten o son complementarias respecto a Portlet 3.0? Desde el punto de vista de Liferay se afirma que son complementarias, pero en mi opinión, en cierta medida compiten y entran en conflicto, con lo cual, implementar toda la especificación 3.0 tendrá un coste añadido, y creo que se realizará mediante una capa de compatibilidad (de forma similar a como hace con los desarrollos SDK en Liferay 7) para cubrir la especificación, pero, que igualmente se promoverá el utilizar los mecanismos que Liferay inicialmente desarrolló, dado que serán los más afines.

Espero que os haya gustado el post. Puedes dejarnos vuestros comentarios y opiniones a través de los comentarios del blog o mediante nuestra cuenta de Twitter.