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 (<, >, 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”