75
Tema 3: Especificación de portlets Java

Tema 3: Especificación de portlets Java - tic.udc.esfbellas/teaching/tp-2007-2008/Tema3.pdf · Implementa los casos de uso del portal ... En cualquier caso, la especificación de

Embed Size (px)

Citation preview

Tema 3: Especificación de portlets Java

El contenedor de portlets (1)

Al igual que los servlets, los portlets se ejecutan dentro de un contenedor

Es una extensión de un contenedor de servlets

De hecho, un servidor de portales Java suele ser una extensión de un servidor de aplicaciones Java

Pero un portlet no es un tipo especial de servlet

Debe implementar la especificación “Servlets 2.3”

Soporta “aplicaciones portlet” Extensión de aplicaciones Web J2EE (ficheros .war)

Adicionalmente cada aplicación portlet contiene Uno o más portlets

Un descriptor de sus portlets (WEB-INF/portlet.xml)

Dado que el contenedor sólo está obligado a soportar la especificación “Servlets 2.3”, para lograr máxima portabilidad la aplicación portlet debería usar JSP 1.2 (JSP 2.0 requiere “Servlets 2.4”)

No soporta lenguaje de expresiones

JSTL 1.0 (JSTL 1.1 depende de JSP 2.0) Implementa lenguaje de expresiones (para sus tags)

El contenedor de portlets (2)

Típicamente el contenedor de portlets es un componente del portal

Contenedor de portlets

Aplicación Web del portal

Servidor de portales

Apl. portlet

Apl. portlet

[...]

La especificación de portlets Java

Estandariza el API que ofrece el contenedor a los portlets

El diseño del API tiene cierto parecido con el API de servlets

El contenedor de portlets (y 3)

Aplicación Web del portal

Implementa los casos de uso del portal (registro y autenticación de usuarios, selección de portlets y layout en las páginas, creación y destrucción de páginas, agregación de las respuestas de los portlets en la página actual, etc.)

Interactúa con el contenedor de portlets mediante un API específica

Arquitectónicamente, en algunos servidores de portales la separación entre la “aplicación Web del portal” y el contenedor de portlets puede no ser tan clara

En cualquier caso, la especificación de portlets Java estandariza el API del que disponen los portlets

Visión global del API de portlets (1)

<<interface>>javax.portlet.Portlet

+ init(portletConfig : PortletConfig) : void+ destroy() : void+ processAction(request : ActionRequest, response : ActionResponse) : void+ render(request : RenderRequest, response : RenderResponse) : void

javax.portlet.GenericPortlet

+ init(portletConfig : PortletConfig) : void+ destroy() : void+ processAction(request : ActionRequest, response : ActionResponse) : void+ render(request : RenderRequest, response : RenderResponse)# doDispatch(request : RenderRequest, response : RenderResponse) : void# doView(request : RenderRequest, response : RenderResponse) : void# doEdit(request : RenderRequest, response : RenderResponse) : void# doHelp(request : RenderRequest, response : RenderResponse) : void

Visión global del API de portlets (2)

Interfaz

Todo portlet tiene que implementar javax.portlet.Portlet

Normalmente se implementan extendiendo de javax.portlet.GenericPortlet

Ciclo de vida

Número de instancias de la clase portlet (similar a un Servlet)

Entorno no distribuido: una (por cada definición de portlet)

Entorno distribuido (<distributable/> en web.xml): una

(por cada definición de portlet) por cada máquina virtual

Cuando el contenedor crea una instancia del portlet init

Cuando el contenedor decide destruirla destroy

Visión global del API de portlets (3)

Modos PortletMode.VIEW (view), PortletMode.EDIT (edit)

y PortletMode.HELP (help)

El vendedor del portal puede definir modos a medida

Estados de ventana WindowState.NORMAL (normal), WindowState.MAXIMIZED (maximized) y WindowState.MINIMIZED (minimized)

El vendedor del portal puede definir estados de ventana a medida

Visión global del API de portlets (4)

Dos tipos de peticiones

Petición de acción (“action request”)

Peticiones que modifican el estado del portlet o causan una redirección

No son idempotentes

Se procesan redefiniendo processAction

Petición de renderización (“render request”)

Peticiones para solicitar el markup del portlet

Son idempotentes

El contenedor invoca a render

GenericPortlet lo implementa como un método plantilla, que entre otras cosas, invoca a doDispatch, quien a su vez delega en doView, doEdit o doHelp, dependiendo del modo

seleccionado

El método doDispatch proporcionado por GenericPortlet no

invoca a ningún método de renderización cuando windowState=minimized

Visión global del API de portlets (5)

<<interface>>javax.portlet.PortletRequest

+ getParameter(name : String) : String+ getParameterValues(name : String) : String[]+ getPortletMode() : PortletMode+ getWindowState() : WindowState+ getPortletSession(boolean create) : PortletSession+ getPreferences() : PortletPreferences+ getAttribute(name : String) : String+ setAttribute(name : String, o : Object) : void+ removeAttribute(name : String) : void

<<interface>>javax.portlet.ActionRequest

+ getPortletInputStream() : java.io.InputStream+ getReader() : java.io.BufferedReader

<<interface>>javax.portlet.RenderRequest

PortletPreferences

representa las preferencias de personalización de una instancia del portlet (un mapa que asocia entradas <nombre, valor (String o String[])>).

Visión global del API de portlets (6)<<interface>>

javax.portlet.PortletResponse

<<interface>>javax.portlet.ActionResponse

+ sendRedirect(location : String) : void+ setPortletMode(portletMode : PortletMode) : void+ setWindowState(windowState : WindowState) : void+ setRenderParameter(key : String, value : String) : void+ setRenderParameter(key : String, values : String[]) : void+ setRenderParameters(parameters : java.util.Map) : void

<<interface>>javax.portlet.RenderResponse

+ createActionURL() : ActionURL+ createRenderURL() : ActionURL+ setContentType(type : String) : void+ setTitle(title : String) : void+ getPortletOutputStream() : java.io.OutputStream+ getWriter() : java.io.PrintWriter

createActionURL y createRenderURL permiten

crear URLs que provocan peticiones de acción y peticiones de renderización.

El API de portlets proporciona librería de tags JSP para facilitar la creación de URLs desde páginas JSP

Visión global del API de portlets (7)

Los métodos de renderización son los únicos que pueden generar markup mediante RenderResponse

Una petición de acción sobre un portlet siempre va seguida de una petición de renderización sobre ese portlet, y sobre el resto de portlets de esa página cuyo contenido no esté cacheado (para regenerar la página)

Una petición de renderización sobre un portlet provoca una petición de renderización sobre el resto de portlets de esa página cuyo contenido no esté cacheado (para regenerar la página)

Los métodos de renderización pueden delegar la generación de markup en una página JSP (lo más normal) o un servlet de la aplicación portlet

Visión global del API de portlets (8)

Procesamiento de peticiones de renderización

Portlet 1Contenedor

1: Interacción (renderización) sobre el portlet 1

1.1: render

[respuesta no en caché]

1.2: render

[respuesta no en caché]

1.N: render

Portlet 2 Portlet N

...

...

Portal

El usuario está actuando sobre una página que contiene los portlets 1, 2, ... N

Visión global del API de portlets (9)

Procesamiento de peticiones de acción

Portlet 1Contenedor

1: Interacción (acción) sobre el portlet 1

1.1: processAction

1.2: render

[respuesta no en caché]

1.3: render

[respuesta no en caché]

1.N+1: render

Portlet 2 Portlet N

...

...

Portal

El usuario está actuando sobre una página que contiene los portlets 1, 2, ... N

Visión global del API de portlets (10)

¿Por qué el API de portlets necesita distinguir entre peticiones (métodos) de renderización y acción? NOTA: Asumiremos que las respuestas de los portlets no están cacheadas (y aunque

no fuese así, tendríamos que suponerlo porque en cualquier momento puede vencer el tiempo máximo en caché)

Supongamos que todas las peticiones pudiesen generar markup, es decir, que todas las peticiones fuesen de renderización

El usuario realiza una interacción sobre el portlet 1 que conlleva una operación no idempotente (e.g. añadir una cantidad de dinero a una cuenta en BD)

El portal invoca el método de renderización sobre el portlet 1 y sobre el resto de portlets de la página (para regenerarla)

A continuación, el usuario realiza una interacción sobre el portlet 2en la misma página

El portal invoca el método de renderización sobre el portlet 2 y sobre el resto de portlets de la página (para regenerarla)

¡La operación no idempotente sobre el portlet 1 se vuelve a ejecutar!

Visión global del API de portlets (11)

Distinguiendo entre peticiones de renderización y acción no existe ese problema NOTA: Asumiremos que las respuestas de los portlets no están cacheadas (y aunque

no fuese así, tendríamos que suponerlo porque en cualquier momento puede vencer el tiempo máximo en caché)

El usuario realiza una interacción sobre el portlet 1 que conlleva una operación no idempotente (e.g. añadir una cantidad de dinero a una cuenta en BD)

El portal invoca primero el método de acción (que realiza la operación no idempotente) sobre el portlet 1, y después el método de renderización (que devuelve una respuesta visual que muestra su estado) sobre el propio portlet 1 y el resto de portlets de la misma página (para regenerarla)

A continuación, el usuario realiza una interacción sobre el portlet 2en la misma página

El portal invoca el método de acción sobre el portlet 2 si la interacción causó una petición de acción, y en cualquier caso, invoca el método de renderización sobre todos los portlets de la página (para regenerarla) La operación no idempotente sobre el portlet 1 no se vuelve a

ejecutar (sólo la de renderización, que muestra estado)

Visión global del API de portlets (12)

URLs

RenderResponse permite crear URLs que causan peticiones de acción (createActionURL) o renderización (createRenderURL)

El API de portlets proporciona librería de tags JSP para facilitar la creación de URLs desde páginas JSP

El desarrollador

No especifica la ruta de la URL

Sólo puede especificar: parámetros, cambio de estado de ventana, cambio de modo y si la URL debe llevar asociada una conexión segura

El portal genera la URL real (la que ve el navegador)

Apunta al portal

Lleva codificada la información especifica por el desarrollador

La URL asociada al campo action de un formulario debe

ser de acción, aunque conceptualmente la petición sea de renderización

El contenedor puede ignorar los parámetros del formulario (de hecho, así ocurre en eXo Portal 1.0.x/1.1.x)

Página 31, apartado PLT.7.1, “Java Portlet Specification 1.0”

Visión global del API de portlets (13)

Parámetros en peticiones Cuando el usuario realiza una petición de acción/renderización

sobre un portlet => el contenedor debe invocar processAction/render sobre ese portlet con los parámetros asociados a la petición

En principio, la petición de renderización que sigue a una petición de acción sobre un portlet no debe propagar los parámetros de la petición de acción Si el portlet quiere establecer parámetros (durante el procesamiento de

la petición de acción) para la petición de renderización, tiene que hacerlo mediante los métodos ActionResponse.setRenderParameter(-s)

Siempre que el portal invoca una petición de renderización sobre un portlet (e.g. porque su respuesta no estaba cacheada) como consecuencia de una petición de acción/renderización dirigida a otro portlet de la misma página, el portal debe usar los mismos parámetros que en la anterior invocación de renderización De esta manera, cada vez que se realiza una interacción sobre un

portlet, la página generada por el portal mostrará el resto de portlets en el estado anterior

En algunos servidores, los parámetros no se conservan

Visión global del API de portlets (14)

Parámetros en peticiones (cont)

Cada portlet sólo ve los parámetros de la petición dirigida hacia él

Las URLs asociadas a los botones de cambio de modo y estado de ventana causan peticiones de renderización, y el portal debe conservar los parámetros de renderización asociados a cada portlet de la página

En algunos servidores, los parámetros no se conservan

Visión global del API de portlets (15)

Delegación de generación de markup en una página JSP/Servlet desde un método de renderización Los atributos que se añadan a RenderRequest estarán

disponibles como atributos en ServletRequest

Ejemplo

renderRequest.setAttribute("result", result);

renderResponse.setContentType("text/html");

PortletRequestDispatcher rd =

getPortletContext().getRequestDispatcher("/view.jsp");

rd.include(renderRequest, renderResponse);

Aparentemente (página 67, PLT.16.3.3, “Java Portlet Specification 1.0”) los parámetros de RenderRequestestarán disponibles en ServletRequest

Pero no parece ser así en algunos servidores de portales (e.g. eXo Portal 1.0.x/1.1.x)

Visión global del API de portlets (y 16)

Sesiones

Al igual que las aplicaciones Web, las aplicaciones portlet disponen del concepto de sesión (PortletSession)

Métodos PortletRequest.getPortletSession

PortletSession.setAttribute(name, value, scope)

scope: PortletSession.APPLICATION_SCOPE (atributo

disponible para cualquier portlet de la aplicación portlet en la misma sesión) o PortletSession.PORTLET_SCOPE (atributo disponible a

las peticiones dirigidas a esa instancia del portlet en la misma sesión)

PortletSession.getAttribute(name, scope)

Los atributos de la sesión del portlet se guardan en atributos de la sesión javax.servlet.http.HttpSession

Los atributos con scope=PortletSession.APPLICATION_SCOPE,

se guardan con el mismo nombre

Los atributos con scope=PortletSession.PORTLET_SCOPE, se

guardan con nombre codificado

Ejemplo StockQuotePortlet (1)windowState=normal, mode=view

Clic en “Search”Clic en “My quotes”

Clic en “Search”

Ejemplo StockQuotePortlet (2)windowState=normal, mode=view

Clic en “Search”

Introducir “SUNW” y clic en “Search”

Ejemplo StockQuotePortlet (3)windowState=normal, mode=edit

Clic en “Add”

Introducir “MSFT” y clic en “Add”

Clic

en “

Rem

ove”

de “

MSFT”

Ejemplo StockQuotePortlet (4)windowState=normal, mode=help

Ejemplo StockQuotePortlet (5)windowState=maximized, mode=view, página “My Quotes”

Ejemplo StockQuotePortlet (y 6)windowState=maximized, mode=view, página “Search”

Diseño MVC

<<interface>>StockQuoteFacade

+ findStockQuote(stockSymbol : String) : StockQuote+ findStockQuotes(stockSymbols : String[]) :

List<StockQuote>+ findExtendedStockQuote(stockSymbol : String) :

ExtendedStockQuote+ findExtendedStockQuotes(stockSymbols : String[],

maxHeadlines : int) : List<ExtendedStockQuote>

StockQuote

- symbol : String- last : double- change : double- time : String

+ get’s

StockQuotePortlet

javax.portlet.GenericPortlet

myStockQuotes.jsp

searchStockQuotes.jsp

Modelo

Controlador

Vista

ExtendedStockQuote

+ get’s

- stockNewsList : List<StockNews>

StockNews

- headline : String- date : String- time : String- source : String- url : String

+ get’s

0..n

edit.jsp

help.jsp

commonHeader.jspviewHeader.jsp

StockQuoteFacade

Interfaz de la fachada de la capa modelo

Dos implementaciones MockStockQuoteFacade

Implementación ficticia (“mock object”)

XigniteStockQuoteFacade

Invoca servicios Web SOAP mediante JAX-RPC (Axis)

XigniteQuotes (http://www.xignite.com/xQuotes.asmx)

XigniteNews (http://www.xignite.com/xNews.asmx)

Sólo disponible en la versión 1.0 del ejemplo (http://www.tic.udc.es/~fbellas/teaching/tp-2006-2007)

jar tvf StockQuotePortlet.war (1)

commonHeader.jsp

edit.jsp

help.jsp

myStockQuotes.jsp

searchStockQuotes.jsp

viewHeader.jsp

WEB-INF/portlet.xml

WEB-INF/web.xml

WEB-INF/<<Otros ficheros>>

WEB-INF/lib/<<Librerías>>

WEB-INF/classes/es/udc/fbellas/portlets/stockquote/controller/

StockQuotePortlet.class

WEB-INF/classes/es/udc/fbellas/portlets/stockquote/model/facade/

ExtendedStockQuote.class

WEB-INF/classes/es/udc/fbellas/portlets/stockquote/model/facade/

MockStockQuoteFacade.class

WEB-INF/classes/es/udc/fbellas/portlets/stockquote/model/facade/

StockNews.class

WEB-INF/classes/es/udc/fbellas/portlets/stockquote/model/facade/

StockQuote.class

WEB-INF/classes/es/udc/fbellas/portlets/stockquote/model/facade/

StockQuoteFacade.class

jar tvf StockQuotePortlet.war (y 2)

WEB-INF/classes/es/udc/fbellas/portlets/stockquote/model/facade/

XigniteStockQuoteFacade.class

WEB-INF/classes/es/udc/fbellas/portlets/stockquote/view/messages/

Messages.properties

WEB-INF/classes/es/udc/fbellas/portlets/stockquote/view/messages/

Messages_es.properties

WEB-INF/classes/es/udc/fbellas/portlets/stockquote/view/messages/

Messages_gl.properties

WEB-INF/web.xml

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app

PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"

"http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>

<display-name>StockQuotePortlet</display-name>

<description>It contains a portlet allowing to monitor stock

quotes</description>

<!-- Disabled by default, since some versions of JBoss Portal

server do not support clustering.

<distributable/>

-->

</web-app>

Comentarios

Especifica detalles relativos a los recursos de la aplicación portlet que no son portlets (e.g. servlets y páginas JSP)

Debe tener al menos <description>

<display-name>

<security-role>

El ejemplo no lo utiliza

WEB-INF/portlet.xml (1)<?xml version="1.0" encoding="UTF-8"?>

<portlet-app

xmlns="http://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd"

version="1.0"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd http://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd">

<portlet>

<description>A portlet allowing to monitor stock quotes</description>

<portlet-name>StockQuotePortlet</portlet-name>

<display-name>StockQuotePortlet</display-name>

<portlet-class>es.udc.fbellas.portlets.stockquote.controller.

StockQuotePortlet</portlet-class>

<init-param>

<name>stockQuoteFacadeClass</name>

<value>es.udc.fbellas.portlets.stockquote.model.facade.

MockStockQuoteFacade</value>

<!--

<value>es.udc.fbellas.portlets.stockquote.model.facade.

XigniteStockQuoteFacade</value>

-->

</init-param>

WEB-INF/portlet.xml (2)

<expiration-cache>1200</expiration-cache>

<supports>

<mime-type>text/html</mime-type>

<portlet-mode>view</portlet-mode>

<portlet-mode>edit</portlet-mode>

<portlet-mode>help</portlet-mode>

</supports>

<supported-locale>en</supported-locale>

<!-- JBoss Portal does not like "gl" locale in

<supported-locale> tag. However, messsages will be

printed in galician if this language is specified as the

preferred language in the navigator.

<supported-locale>gl</supported-locale>

-->

<supported-locale>es</supported-locale>

<resource-bundle>es.udc.fbellas.portlets.stockquote.view.

messages.Messages</resource-bundle>

WEB-INF/portlet.xml (y 3)

<!-- Needed for some portal servers (e.g. eXo and Pluto),

despite these values are defined in the resource

bundle. -->

<portlet-info>

<title>Stock Quotes</title>

<short-title>Stock Quotes</short-title>

<keywords>stock, quotes</keywords>

</portlet-info>

<portlet-preferences>

<preference>

<name>stockSymbols</name>

<value>ORCL</value>

<value>IBM</value>

</preference>

</portlet-preferences>

</portlet>

</portlet-app>

Comentarios (1)

Especifica todos los portlet (<portlet>) que

componen la aplicación Web

<init-param>

Un portlet puede declarar “n” parámetros de inicialización

<expiration-cache>

Número de segundos que el contenedor cacheará el markup del portlet

Cuando una petición va dirigida directamente a un portlet (es decir, cuando el usuario realiza una interacción sobre él), su contenido cacheado siempre se descarta

<supports>

Por cada tipo de markup soportado se utiliza un tag <supports>, que especifica los modos soportados por el

portlet para ese markup

Comentarios (2)

Internacionalización <supported-locale>

Permite declarar los Locales soportados por el portlet

<resource-bundle>

Necesario si se quiere internacionalizar la información que aparece en <portlet-info> (más adelante)

Especifica el recurso (fichero .properties o clase) que

contiene los mensajes

Tiene que contener las claves javax.portlet.title, javax.portlet.short-title y javax.portlet.keywords

Recursos de mensajes WEB-

INF/classes/es/udc/fbellas/portlets/stockquote/

view/messages/{Messages, Messages_es,

Messages_gl}.properties

Contienen los mensajes de la aplicación (los propios y los javax.portlet.*)

Comentarios (y 3)

portlet-info

Información básica del portlet

<title>: título que aparece por defecto (GenericPortlet.getTitle) en la ventana del portlet

La información puede estar internacionalizada, y en ese caso se utilizan las claves javax.portlet.title, javax.portlet.short-title y javax.portlet.keywords del recurso especificado con <resource-bundle>

portlet-preferences

Especifica (si se desea) las preferencias iniciales que tendrá la instancia recién creada de un portlet

Resto de ficheros en WEB-INF

portlet-instances.xml y stockquoteportlet-object.xml

Ficheros que requiere JBoss Portal (además de web.xml)

WEB-INF/lib

Jakarta TagLibs 1.0.6

Para alcanzar máxima portabilidad, las páginas JSP utilizan JSTL 1.0

Jakarta TagLibs 1.1.x+ implementa JSTL 1.1+, que no incluye el lenguaje de expresiones (lo hace el contenedor de JSP 2.0+)

Jars de Axis Requeridos por XigniteStockQuoteFacade

Peticiones de acción y renderización (1)mode=view

Clic en “Search”Petición de renderización (doView)Parámetros: {command=“ShowSearchStockQuotePage”}

Clic en “My quotes”Petición de renderización (doView)Parámetros: {}

Clic en “Search”(1) Petición de acción (processAction)

Parámetros: {command(hidden)=“SearchStockQuote”,stockSymbol=“”}

Selección mode=viewPetición de renderización (doView)Parámetros: {}*

Peticiones de acción y renderización (2)mode=view

(2) Petición de renderización (doView)Parámetros: {command=“ShowSearchStockQuotePage”,

viewStockSymbolError=“ErrorMessages.mandatoryField”}

Introducir “SUNW” y clic en “Search”(1) Petición de acción (processAction)

Parámetros: {command(hidden)=“SearchStockQuote”,stockSymbol=“SUNW”}

(2) Petición de renderización (doView)Parámetros: {command=“SearchStockQuote”,

stockSymbol=“SUNW”}

Peticiones de acción y renderización (3)mode=edit

Clic en “Add”(1) Petición de acción (processAction)

Parámetros: {command(hidden)=“AddStockSymbol”,stockSymbol=“”}

(2) Petición de renderización (doEdit)Parámetros: {editStockSymbolError=“ErrorMessages.mandatoryField”}

Introducir “MSFT” y clic en “Add”

Clic

en “

Rem

ove”

de “

MSFT”

Selección mode=editPetición de renderización (doEdit)Parámetros: {}*

Peticiones de acción y renderización (4)mode=edit

Introducir “MSFT” y clic en “Add”(1) Petición de acción (processAction)

Parámetros: {command(hidden)=“AddStockSymbol”,stockSymbol=“MSFT”}

(2) Petición de renderización (doEdit)Parámetros: {}

Clic en “Remove” de “MSFT”(1) Petición de acción (processAction)

Parámetros: {command=“RemoveStockSymbol”,stockSymbol=“MSFT”}

(2) Petición de renderización (doEdit)Parámetros: {}

Peticiones de acción y renderización (y 5)mode=help

Selección mode=helpPetición de renderización (doHelp)Parámetros: {}*

NOTA: * Por sencillez, cuando se cambia de modo, en las figuras anteriores se ha

puesto una lista de parámetros vacía. Pero si el portal implementa de manera estándar los enlaces de cambio de modo y estado de ventana, los parámetros de renderización deben conservarse. Más adelante se vuelve a incidir en este aspecto.

es.udc.fbellas.portlets.stockquote.controller.StockQuotePortlet (1)

public class StockQuotePortlet extends GenericPortlet {

private final static String STOCK_QUOTE_FACADE_CLASS_INIT_PARAM =

"stockQuoteFacadeClass";

private final static int MY_STOCK_QUOTES_MAX_HEADLINES = 5;

private Class<StockQuoteFacade> stockQuoteFacadeClass;

public void init() throws PortletException {

String stockQuoteFacadeClassName =

getInitParameter(STOCK_QUOTE_FACADE_CLASS_INIT_PARAM);

if (stockQuoteFacadeClassName == null) {

throw new PortletException(

STOCK_QUOTE_FACADE_CLASS_INIT_PARAM +

" init-param not defined in WEB-INF/portlet.xml");

}

try {

stockQuoteFacadeClass = (Class<StockQuoteFacade>)

Class.forName(stockQuoteFacadeClassName);

} catch (Exception e) {

throw new PortletException(e);

}

}

es.udc.fbellas.portlets.stockquote.controller.StockQuotePortlet (2)

private StockQuoteFacade createStockQuoteFacadeInstance()

throws PortletException {

try {

return stockQuoteFacadeClass.newInstance();

} catch (Exception e) {

throw new PortletException(e);

}

}

public void processAction (ActionRequest request,

ActionResponse response) throws PortletException, IOException {

String command = request.getParameter("command");

if (command.equals("AddStockSymbol")) {

addStockSymbol(request, response);

} else if (command.equals("RemoveStockSymbol")) {

removeStockSymbol(request);

} else if (command.equals("SearchStockQuote")) {

fireSearchStockQuote(request, response);

} else {

throw new PortletException("unexpected action command: " +

command);

}

}

es.udc.fbellas.portlets.stockquote.controller.StockQuotePortlet (3)

public void doView (RenderRequest request, RenderResponse response)

throws PortletException, IOException {

String command = request.getParameter("command");

if (command == null) {

showMyStockQuotes(request, response);

} else if (command.equals("ShowSearchStockQuotePage")) {

showSearchStockQuotePage(request, response);

} else if (command.equals("SearchStockQuote")) {

searchStockQuote(request, response);

} else { // Not useful in this case, but it would be necessary if

// "edit" and "help" modes would also use the "command"

// parameter. In such a case, a click on the "view mode"

// button from "edit" or "help" modes could carry a

// "command" parameter (render parameters should be

// preserved) with an unexpected value for the "view"

// mode.

showMyStockQuotes(request, response);

}

}

es.udc.fbellas.portlets.stockquote.controller.StockQuotePortlet (4)

public void doEdit (RenderRequest request, RenderResponse response)

throws PortletException, IOException {

PortletPreferences prefs = request.getPreferences();

String[] stockSymbols = prefs.getValues("stockSymbols",

new String[0]);

request.setAttribute("editStockSymbolError",

request.getParameter("editStockSymbolError"));

request.setAttribute("stockSymbols", stockSymbols);

includeHTMLResponse(request, response, "/edit.jsp");

}

public void doHelp (RenderRequest request, RenderResponse response)

throws PortletException, IOException {

includeHTMLResponse(request, response, "/help.jsp");

}

es.udc.fbellas.portlets.stockquote.controller.StockQuotePortlet (5)

private void addStockSymbol(ActionRequest request,

ActionResponse response)

throws PortletException, IOException {

String stockSymbol = request.getParameter("stockSymbol");

if ( (stockSymbol == null) ||

(stockSymbol.trim().length() == 0) ) {

response.setRenderParameter("editStockSymbolError",

"ErrorMessages.mandatoryField");

} else {

PortletPreferences prefs = request.getPreferences();

String[] currentStockSymbols =

prefs.getValues("stockSymbols", new String[0]);

String[] newStockSymbols = addToArray(currentStockSymbols,

stockSymbol.trim().toUpperCase());

prefs.setValues("stockSymbols", newStockSymbols);

prefs.store();

}

}

es.udc.fbellas.portlets.stockquote.controller.StockQuotePortlet (6)

private void removeStockSymbol(ActionRequest request)

throws PortletException, IOException {

String stockSymbol = request.getParameter("stockSymbol");

PortletPreferences prefs = request.getPreferences();

String[] currentStockSymbols = prefs.getValues("stockSymbols",

new String[0]);

String[] newStockSymbols = removeFromArray(currentStockSymbols,

stockSymbol);

prefs.setValues("stockSymbols", newStockSymbols);

prefs.store();

}

es.udc.fbellas.portlets.stockquote.controller.StockQuotePortlet (7)

private void fireSearchStockQuote(ActionRequest request,

ActionResponse response) throws PortletException, IOException {

String stockSymbol = request.getParameter("stockSymbol");

if ( (stockSymbol == null) ||

(stockSymbol.trim().length() == 0) ) {

response.setRenderParameter("command",

"ShowSearchStockQuotePage");

response.setRenderParameter("viewStockSymbolError",

"ErrorMessages.mandatoryField");

} else {

response.setRenderParameter("command", "SearchStockQuote");

response.setRenderParameter("stockSymbol", stockSymbol);

}

}

es.udc.fbellas.portlets.stockquote.controller.StockQuotePortlet (8)

private void showMyStockQuotes(RenderRequest request,

RenderResponse response) throws PortletException, IOException {

PortletPreferences prefs = request.getPreferences();

String[] stockSymbols = prefs.getValues("stockSymbols",

new String[0]);

List<? extends StockQuote> stockQuoteList;

if (request.getWindowState().equals(WindowState.MAXIMIZED)) {

stockQuoteList = createStockQuoteFacadeInstance().

findExtendedStockQuotes(stockSymbols,

MY_STOCK_QUOTES_MAX_HEADLINES);

request.setAttribute("windowState", WindowState.MAXIMIZED);

} else { // NORMAL

stockQuoteList = createStockQuoteFacadeInstance().

findStockQuotes(stockSymbols);

}

request.setAttribute("stockQuoteMap",

createStockQuoteMap(stockSymbols, stockQuoteList));

includeHTMLResponse(request, response, "/myStockQuotes.jsp");

}

es.udc.fbellas.portlets.stockquote.controller.StockQuotePortlet (9)

private void showSearchStockQuotePage(RenderRequest request,

RenderResponse response) throws PortletException, IOException {

request.setAttribute("viewStockSymbolError",

request.getParameter("viewStockSymbolError"));

includeHTMLResponse(request, response,

"/searchStockQuotes.jsp");

}

es.udc.fbellas.portlets.stockquote.controller.StockQuotePortlet (10)

private void searchStockQuote(RenderRequest request,

RenderResponse response) throws PortletException, IOException {

String stockSymbol =

request.getParameter("stockSymbol").trim().toUpperCase();

StockQuote stockQuote;

if (request.getWindowState().equals(WindowState.MAXIMIZED)) {

stockQuote = createStockQuoteFacadeInstance().

findExtendedStockQuote(stockSymbol);

request.setAttribute("windowState", WindowState.MAXIMIZED);

} else { // NORMAL

stockQuote = createStockQuoteFacadeInstance().

findStockQuote(stockSymbol);

}

request.setAttribute("stockSymbol", stockSymbol);

request.setAttribute("stockQuote", stockQuote);

request.setAttribute("showSearchResult", "***ANY_VALUE***");

includeHTMLResponse(request, response, "/searchStockQuotes.jsp");

}

es.udc.fbellas.portlets.stockquote.controller.StockQuotePortlet (y 11)

private void includeHTMLResponse(RenderRequest request,

RenderResponse response, String path) throws PortletException,

IOException {

response.setContentType("text/html");

PortletRequestDispatcher rd =

getPortletContext().getRequestDispatcher(path);

rd.include(request, response);

}

<<Métodos privados "addToArray", "removeFromArray" y

"createStockQuoteMap">>

}

Comentarios (1)

Parámetros viewStockSymbolError y editStockSymbolError

viewStockSymbolError: especifica el mensaje de error asociado al campo stockSymbol en el formulario de

búsqueda

editStockSymbolError: especifica el mensaje de error asociado al campo stockSymbol en el formulario de

edición

Dado que el portal debería conservar los parámetros de renderización cuando el usuario cambia de modo, si en vez de estos dos parámetros usásemos uno sólo, por ejemplo, stockSymbolError, podría ocurrir la siguiente situación

Ver siguientes transparencias

Comentarios (2)

Clic en “Search”(1) Petición de acción (processAction)

Parámetros: {command(hidden)=“SearchStockQuote”,stockSymbol=“”}

(2) Petición de renderización (doView)Parámetros: {command=“ShowSearchStockQuotePage”,

stockSymbolError=“ErrorMessages.mandatoryField”}

Clic en “Edit”

Comentarios (y 3)

Clic en “Edit”Petición de renderización (doEdit)Parámetros: {command=“ShowSearchStockQuotePage”,

stockSymbolError=“ErrorMessages.mandatoryField}

Si el portal implementa de manera estándar los enlaces de cambio de modo y estado de ventana, los parámetros de renderización deben conservarse. Por tanto, cuando el usuario cambia del estado anterior al modo de edición, la petición de renderización arrastra los parámetros actuales de renderización (“command” y “stockSymbolError”), y como en “doEdit” también se tiene en cuenta la presencia del parámetro “stockSymbolError” para el campo “stockSymbol” del formulario de edición, éste se mostrará con un mensaje de error en dicho campo (cuando el usuario todavía no hace hecho ninguna interacción en el modo de edición).

commonHeader.jsp

<%@ taglib prefix="fmt" uri="http://java.sun.com/jstl/fmt" %>

<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>

<%@ taglib prefix="portlet" uri="http://java.sun.com/portlet" %>

<fmt:setBundle

basename="es.udc.fbellas.portlets.stockquote.view.messages.Messages"/>

viewHeader.jsp (1)

<div align="center">

[

<c:choose>

<c:when test='${page == "myStockQuotesPage"}'>

<span class="portlet-font-dim">

<fmt:message key="viewHeader.myStockQuotes"/>

</span>

</c:when>

<c:otherwise>

<portlet:renderURL var="myQuotesPageURL"/>

<a href="<c:out value='${myQuotesPageURL}' escapeXml='false'/>">

<fmt:message key="viewHeader.myStockQuotes"/>

</a>

</c:otherwise>

</c:choose>

|

viewHeader.jsp (y 2)

<c:choose>

<c:when test='${page == "searchStockQuotesPage"}'>

<span class="portlet-font-dim">

<fmt:message key="viewHeader.search"/>

</span>

</c:when>

<c:otherwise>

<portlet:renderURL var="searchPageURL">

<portlet:param name="command" value="ShowSearchStockQuotePage"/>

</portlet:renderURL>

<a href="<c:out value='${searchPageURL}' escapeXml='false'/>">

<fmt:message key="viewHeader.search"/>

</a>

</c:otherwise>

</c:choose>

]

</div>

<br>

myStockQuotes.jsp (1)<%-- Header --%>

<%@ include file="commonHeader.jsp" %>

<c:set var="page" value="myStockQuotesPage"/>

<%@ include file="viewHeader.jsp" %>

<%-- My stock quotes --%>

<c:choose>

<%-- No stock quotes --%>

<c:when test="${empty stockQuoteMap}">

<div class="portlet-msg-alert">

<fmt:message key="myStockQuotes.noStockSymbolsSelected"/>

<div>

</c:when>

<c:otherwise>

<%-- Print stock quotes --%>

<table width="100%">

<%-- Table header --%>

<tr class="portlet-section-header">

<th><fmt:message key="StockQuote.symbol"/></th>

<th><fmt:message key="StockQuote.last"/></th>

<th><fmt:message key="StockQuote.change"/></th>

<th><fmt:message key="StockQuote.time"/></th>

</tr>

myStockQuotes.jsp (2)<%-- Stock quotes --%>

<c:forEach var="entry" items="${stockQuoteMap}"

varStatus="status">

<c:choose>

<c:when test="${status.index % 2 == 0}">

<tr class="portlet-section-body">

</c:when>

<c:otherwise>

<tr class="portlet-section-alternate">

</c:otherwise>

</c:choose>

<c:choose>

<c:when test="${empty entry.value}">

<%-- No information about this stock symbol. Probably

the stock symbol does not exist. --%>

<td class="portlet-msg-alert">

<c:out value="${entry.key}"/>

</td>

<td></td><td></td><td></td>

</c:when>

<c:otherwise>

<td><c:out value="${entry.value.symbol}"/></td>

<td><fmt:formatNumber value="${entry.value.last}"/></td>

<td><fmt:formatNumber value="${entry.value.change}"/></td>

<td><c:out value="${entry.value.time}"/></td>

</c:otherwise>

</c:choose>

myStockQuotes.jsp (3)</tr>

</c:forEach>

</table>

<%-- Print stock news --%>

<c:if test="${windowState == 'maximized'}">

<br>

<c:forEach var="entry" items="${stockQuoteMap}">

<c:if test="${!empty entry.value}">

<%-- We only show stock news for correct stock

symbols. --%>

<div class="portlet-section-header">

<c:out value="${entry.value.symbol}"/>

</div>

<div class="portlet-section-body">

<c:choose>

<c:when test="${empty entry.value.stockNewsList}">

<%-- The stock symbol exists, but there are no news

available at this moment. --%>

<fmt:message key="common.noNewsAvailable"/>

</c:when>

myStockQuotes.jsp (y 4)<c:otherwise>

<ul>

<c:forEach var="stockNews" items="${entry.value.stockNewsList}">

<li>

<a href="<c:out value='${stockNews.url}'

escapeXml='false'/>">

<c:out value="${stockNews.headline}"/>

</a>

[<c:out value="${stockNews.source}"/>]

<c:out value="${stockNews.time}"/> -

<c:out value="${stockNews.date}"/>

</li>

</c:forEach>

</ul>

</c:otherwise>

</c:choose>

</div>

<br>

</c:if>

</c:forEach>

</c:if>

</c:otherwise>

</c:choose>

searchStockQuotes.jsp (1)<%-- Header --%>

<%@ include file="commonHeader.jsp" %>

<c:set var="page" value="searchStockQuotesPage"/>

<%@ include file="viewHeader.jsp" %>

<%-- Search form --%>

<portlet:actionURL var="searchStockQuoteURL"/>

<form action="<c:out value='${searchStockQuoteURL}' escapeXml='false'/>"

method="post">

<input type="hidden" name="command" value="SearchStockQuote">

<label for="stockSymbol" class="portlet-form-field-label">

<fmt:message key="StockQuote.symbol"/>

</label>

<input type="text" name="stockSymbol"

value="<c:out value='${stockSymbol}'/>"

class="portlet-form-input-field" size="5" maxlength="5">

<c:if test="${!empty viewStockSymbolError}">

<span class="portlet-msg-error">

<fmt:message key="${viewStockSymbolError}"/>

</span>

</c:if>

<input type="submit" class="portlet-form-button"

value="<fmt:message key='searchStockQuotes.search'/>">

</form>

searchStockQuotes.jsp (y 2)

<br>

<%-- Search result --%>

<c:if test="${!empty showSearchResult}">

<< Se hizo una búsqueda => imprimir resultado... >>

</c:if >

edit.jsp (1)

<%@ include file="commonHeader.jsp" %>

<%-- Print stock symbols (with remove link) --%>

<c:if test="${!empty stockSymbols}">

<table width="100%">

<c:forEach var="stockSymbol" items="${stockSymbols}"

varStatus="status">

<portlet:actionURL var="removeStockSymbolURL">

<portlet:param name="command" value="RemoveStockSymbol"/>

<portlet:param name="stockSymbol"

value='<%=(String) pageContext.findAttribute("stockSymbol")%>'/>

</portlet:actionURL>

RECORDATORIO: JSP 1.2 no proporciona lenguaje de expresiones. Las expresiones sólo están disponibles en JSTL 1.0.

edit.jsp (2)<c:choose>

<c:when test="${status.index % 2== 0}">

<tr class="portlet-section-body">

</c:when>

<c:otherwise>

<tr class="portlet-section-alternate">

</c:otherwise>

</c:choose>

<td>

<c:out value="${stockSymbol}"/>

</td>

<td>

<a href="<c:out value='${removeStockSymbolURL}'

escapeXml='false'/>">

<fmt:message key="edit.remove"/>

</a>

</td>

</tr>

</c:forEach>

</table>

</c:if>

edit.jsp (y 3)

<%-- Print form for adding stock symbols --%>

<portlet:actionURL var="addStockSymbolURL"/>

<form action="<c:out value='${addStockSymbolURL}' escapeXml='false'/>"

method="post">

<input type="hidden" name="command" value="AddStockSymbol">

<label for="stockSymbol" class="portlet-form-field-label">

<fmt:message key="StockQuote.symbol"/>

</label>

<input type="text" name="stockSymbol"

class="portlet-form-input-field" size="5" maxlength="5">

<c:if test="${!empty editStockSymbolError}">

<span class="portlet-msg-error">

<fmt:message key="${editStockSymbolError}"/>

</span>

</c:if>

<input type="submit" class="portlet-form-button"

value="<fmt:message key='edit.add'/>">

</form>

Comentarios (1)

Como parte del API de portlets, se proporciona una librería de tags JSP defineObjects

Define las variables renderRequest, renderResponse y portletConfig

renderURL

Genera una URL que provoca una petición de renderización

actionURL

Genera una URL que provoca una petición de acción

param

Para añadir parámetros a renderURL y actionURL

namespace

Devuelve un nombre único para el portlet actual

Útil para evitar conflictos de nombres, por ejemplo, en funciones JavaScript, entre portlets de una misma página

Ejemplo: <a href="javascript:<portlet:namespace/>doFoo()">Foo</a>

Comentarios (y 2)

Estilos CSS estándar

Para lograr un look-n-feel consistente entre los portlets de una misma página y para independizarlo de un portal particular, la especificación de Portlets Java utiliza los estilos definidos en WSRP

Excepto los portlet-table-* (“parecen” redundantes, dado que los portlet-section-* “parecen” suficientes)

PLT.C, “CSS Style Definitions”, “Java Portlet Specification 1.0”

Impresión de URLs

En el ejemplo, las URLs se imprimen especificando escapeXml="false" en <c:out>

De esta manera los caracteres <, >, &, ’ y ” no se escapan

Podrían formar parte de la URL, y en consecuencia, queremos que no se sustituyan por sus entidades equivalentes (&lt;, &gt;, etc.)

Frameworks Java para desarrollo de portlets (1)

Programar directamente con el API de portlets es tan tedioso como programar directamente con el API de servlets

Algunos servidores de portales y/o librerías incluyen un portlet que permite encapsular a una aplicación Web Java

La aplicación Web Java tiene que estar instalada en el servidor de portales Java

Ejemplo:

Apache Portals Bridges: http://portals.apache.org/bridges

Puente para encapsular una aplicación Web Java como un portlet

Internamente usa el API estándar de portlets

Puede usarse en cualquier servidor de portales estándar Java

Soporta varios frameworks Web (e.g. Struts 1.x, JSF, etc.)

Frameworks Java para desarrollo de portlets (y 2)

Las versiones más recientes de los frameworks Web más famosos incluyen soporte nativo para el desarrollo de portlets

Struts 2.x (http://struts.apache.org)

Tapestry (http://jakarta.apache.org/tapestry)

Spring 2.x (http://www.springframework.org)

Internamente usan el API estándar de portlets

Pueden usarse en cualquier servidor de portales estándar Java