Download Tema 3: Especificación de portlets Java

Document related concepts
no text concepts found
Transcript
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
„
„
„
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
Apl.
portlet
Aplicación
Web del
portal
Contenedor
de portlets
[...]
Apl.
portlet
Servidor de portales
„
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)
„
„
„
Cuando el contenedor crea una instancia del portlet
„
„
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
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
PortletPreferences
representa las preferencias de
personalización de una instancia
del portlet (un mapa que asocia
entradas <nombre, valor
(String o String[])>).
+
+
+
+
+
+
+
+
+
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
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
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
<<interface>>
javax.portlet.RenderResponse
+
+
+
+
+
+
createActionURL() : ActionURL
createRenderURL() : ActionURL
setContentType(type : String) : void
setTitle(title : String) : void
getPortletOutputStream() : java.io.OutputStream
getWriter() : java.io.PrintWriter
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
Portal
Contenedor
1: Interacción
(renderización)
sobre el portlet 1
El usuario está
actuando sobre
una página que
contiene los
portlets 1, 2, ...
N
Portlet 1
1.1: render
[respuesta no en caché]
1.2: render
...
[respuesta no en caché]
1.N: render
Portlet 2
...
Portlet N
Visión global del API de portlets (9)
„
Procesamiento de peticiones de acción
Portal
Contenedor
Portlet 1
1: Interacción
(acción) sobre
el portlet 1
1.1: processAction
El usuario está
actuando sobre
una página que
contiene los
portlets 1, 2, ...
N
1.2: render
[respuesta no en caché]
1.3: render
...
[respuesta no en caché]
1.N+1: render
Portlet 2
...
Portlet 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 2
en 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 2
en 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
„
„
„
El portal genera la URL real (la que ve el navegador)
„
„
„
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
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
„
„
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
„
„
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)
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
Cada portlet sólo ve los parámetros de la petición dirigida hacia él
Visión global del API de portlets (14)
„
Parámetros en peticiones (cont)
„
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
„
„
„
En JBoss Portal 2.5/2.6 no se conservan
En eXo Portal 1.0.x/1.1.x y Liferay Portal 4.1.2 sólo se conservan
cuando se cambia de estado de ventana
(En Pluto 1.0.1 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 RenderRequest
estará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)
„
„
PortletSession.setAttribute(name, value, scope)
„
„
„
Métodos PortletRequest.getPortletSession
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 “My quotes”
Clic en “Search”
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 “Remove” de “MSFT”
Clic en “Add”
Introducir “MSFT” y clic en “Add”
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
Modelo
StockNews
StockQuote
<<interface>>
StockQuoteFacade
+ findStockQuote(stockSymbol : String) : StockQuote
+ findStockQuotes(stockSymbols : String[]) :
List<StockQuote>
+ findExtendedStockQuote(stockSymbol : String) :
ExtendedStockQuote
+ findExtendedStockQuotes(stockSymbols : String[],
maxHeadlines : int) : List<ExtendedStockQuote>
-
symbol : String
last : double
change : double
time : String
+ get’s
-
headline : String
date : String
time : String
source : String
url : String
+ get’s
ExtendedStockQuote
- stockNewsList : List<StockNews>
+ get’s
Controlador
javax.portlet.GenericPortlet
StockQuotePortlet
Vista
viewHeader.jsp
myStockQuotes.jsp
searchStockQuotes.jsp
commonHeader.jsp
edit.jsp
help.jsp
0..n
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)
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/
InternalErrorException.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/portletapp_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>StockQuote Portlet</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>
„
„
<expiration-cache>
„
„
„
Un portlet puede declarar “n” parámetros de inicialización
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 un 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
„
„
WEBINF/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
„
web.xml.standard
„
„
web.xml.exo
„
„
Añadido al ejemplo para facilitar el deployment en un
contenedor que no requiera hacer nada especial para
instalar el portlet (cp web.xml.standard web.xml)
Añadido al ejemplo para facilitar el deployment en eXo (cp
web.xml.exo web.xml)
jboss-portlet.xml, 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
Selección mode=view
Petición de renderización (doView)
Parámetros: {}*
Clic en “My quotes”
Petición de renderización (doView)
Parámetros: {}
Clic en “Search”
Clic en “Search”
Petición de renderización (doView)
Parámetros: {command=“ShowSearchStockQuotePage”}
(1) Petición de acción (processAction)
Parámetros: {command(hidden)=“SearchStockQuote”,
stockSymbol=“”}
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
Selección mode=edit
Clic en “Remove” de “MSFT”
Petición de renderización (doEdit)
Parámetros: {}*
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”
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=help
Petició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 stockQuoteClassName =
getInitParameter(STOCK_QUOTE_FACADE_CLASS_INIT_PARAM);
if (stockQuoteClassName == 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(stockQuoteClassName);
} 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,
java.io.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 {
try {
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");
} catch (InternalErrorException e) {
throw new PortletException(e);
}
}
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 {
try {
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");
} catch (InternalErrorException e) {
throw new PortletException(e);
}
}
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
„
„
renderURL
„
„
Genera una URL que provoca una petición de acción
param
„
„
Genera una URL que provoca una petición de renderización
actionURL
„
„
Define las variables renderRequest, renderResponse y
portletConfig
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
„
Las versiones más recientes de los frameworks Web
más famosos incluyen soporte para desarrollo de
portlets
„
„
„
„
„
Struts 2.0 (http://struts.apache.org) / WebWork 2.x
(http://www.opensymphony.com/webwork)
Tapestry 4.0 (http://jakarta.apache.org/tapestry)
Spring 2.0 (http://www.springframework.org)
NOTA: Shale (http://shale.apache.org) parece que incluirá
soporte para portlets en un futuro
Sólo asumen el API Java estándar de Portlets
„
„
Se pueden usar en cualquier servidor de portales Java
Han necesitado refactorización, dado que el API de portlets
distingue entre peticiones de renderización y acción
Arquitectura de un servidor de portales Java basado en estándares (1)
Apl.
portlet
Navegador
Aplicación
Web del
portal
[...]
Contenedor
de portlet
Java
Apl.
portlet
Internet/Intranet
Otros
portales
Portlet
consum.
WSRP
Productor
WSRP
Servidor de portales
Productor
WSRP
Arquitectura de un servidor de portales Java basado en estándares (y 2)
„
Productor WSRP (dentro del servidor de portales)
„
„
Proporciona una implementación de los interfaces de
WSRP, para que otros consumidores remotos puedan
acceder a los portlets locales Java
Portlet consumidor WSRP
„
Actúa como un proxy de un productor WSRP, permitiendo que el
portal pueda consumir portlets WSRP remotos como si fuesen
portlets locales Java
Proyectos Jakarta Pluto y Apache WSRP4J
„
Para facilitar la adopción de estándares, Apache ha
arrancado dos proyectos
„
Jakarta Pluto
„
„
„
„
Apache WSRP4J
„
„
„
„
http://portals.apache.org/pluto
Implementación de referencia de un contenedor de portlets
Java
También incluye un componente “aplicación Web del portal”
muy sencillo (para pruebas)
http://portals.apache.org/wsrp4j
Funciona sobre Jakarta Pluto, y proporciona los componentes
“productor WSRP” y “portlet consumidor WSRP”
Actualmente “en fase de incubación”
Algunos servidores de portales están integrando
estos proyectos (e.g. Jakarta Jetspeed 2)
Especificación de Portlets Java 2.0
„
En fase de desarrollo
„
„
„
„
„
„
http://www.jcp.org/en/jsr/detail?id=286
Mejora la comunicación entre portlets
Mejora el soporte para AJAX
Mejora el soporte para frameworks Web
Añade soporte para filtros
El contenedor debe soportar al menos la
especificación “Servlets 2.4”