Tema 3: Especificación de portlets Java - tic.udc.esfbellas/teaching/tp-2007-2008/Tema3.pdf ·...
Transcript of Tema 3: Especificación de portlets Java - tic.udc.esfbellas/teaching/tp-2007-2008/Tema3.pdf ·...
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”
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 (<, >, 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