Download 4.3 Caso de Estudio: Diseño e Implementación de la Capa
Document related concepts
no text concepts found
Transcript
4.3 Caso de Estudio: Diseño e Implementación de la Capa Web de MiniBank Objetivos Aprender más sobre Tapestry usando la capa Web de MiniBank como caso de estudio Implementación del layout de una aplicación Web Integración con Spring Componentes Select, Loop y PageLink Evento validateForm Internacionalización, Application Module Builder, servicios y evento prepareForRender Tratamiento de excepciones “unchecked” no esperadas Ilustrar algunos aspectos relativos a Spring e Hibernate en el contexto de una aplicación Web [Recordatorio] Uso del DataSource proporcionado por el servidor de aplicaciones Habilitación del patrón Open Session in View Estructura de src/main (1) pojo-minibank src/main java es.udc.pojo.minibank model web components pages services util (Continúa en la siguiente transparencia) Estructura de src/main (y 2) pojo-minibank src/main resources es/udc/pojo/minibank/web components pages org/apache/tapestry5 corelib components pages webapp internal css WEB-INF Implementación del layout de una aplicación Web (1) En MiniBank todas las páginas tienen el mismo “layout” (estructura, disposición) Implementación del layout de una aplicación Web (2) Concretamente, todas las páginas tienen el siguiente layout Título (tag title) Cabecera Lista de enlaces Contenido Pie de página NOTA: el posicionamiento se hace desde la hoja de estilos CSS src/main/webapp/css/styles.css en la distribución fuente y css/styles.css en el fichero WAR Implementación del layout de una aplicación Web (3) Además, en MiniBank Con un enfoque básico, la plantilla de cada página tendría que contener La cabecera, la lista de enlaces y el pie de página son iguales en todas las páginas El título y el contenido son específicos en cada página El markup que define el layout => ¡el mismo en todas las páginas! El markup de las áreas comunes => ¡el mismo en todas las páginas! El markup de las áreas específicas Este enfoque básico conduce a una solución difícil de mantener Implementación del layout de una aplicación Web (4) Para hacer frente a este problema, en MiniBank (y en MiniPortal) se ha adoptado la solución aconsejada en la documentación de Tapestry Desarrollar un componente que genere el layout y el contenido de las áreas comunes => componente Layout En una aplicación más compleja, en la que hubiese varios grupos de páginas, cada uno con un layout distinto, sería preciso desarrollar un componente para cada tipo de layout Los componentes representan elementos reusables en la interfaz gráfica Los componentes PageLink, Form, TextField, Label e If estudiados en el apartado 4.2 son ejemplos de componentes Es posible desarrollar componentes a medida Implementación del layout de una aplicación Web (5) Un componente en Tapestry consta de una clase, y opcionalmente, una plantilla La clase y la plantilla de un componente tienen que estar en el paquete components, hermano de pages En el código fuente de MiniBank La clase (Layout) está en src/main/java/es/udc/pojo/ minibank/web/components La plantilla (Layout.tml) y su catálogo de mensajes (Layout.properties) están en src/main/resources/ es/udc/pojo/minibank/web/components Recordatorio: durante la fase de procesamiento de recursos de Maven (process-resources) la plantilla y el catálogo de mensajes (src/main/resources) se copian al mismo directorio en el que se genera el fichero .class (target/ classes) Implementación del layout de una aplicación Web (6) El componente Layout nos permitirá escribir la plantilla correspondiente a una página especificando sólo el markup de las áreas específicas El contenido del tag title de HTML El markup correspondiente al área de contenido Ejemplo: Index.tml <html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd" t:type="Layout" t:pageTitle="title"> <p class="welcome"> ${message:welcome} </p> </html> Como se verá en las siguientes transparencias, el parámetro pageTitle se ha definido con prefijo por defecto message Ejemplo: Index.properties title=MiniBank welcome page welcome=Welcome to MiniBank! Implementación del layout de una aplicación Web: Layout.java (7) package es.udc.pojo.minibank.web.components; import org.apache.tapestry5.annotations.Parameter; import org.apache.tapestry5.annotations.Property; public class Layout { @SuppressWarnings("unused") @Property @Parameter(required = true, defaultPrefix = "message") private String pageTitle; } Implementación del layout de una aplicación Web: Layout.java (8) Los parámetros de un componente corresponden a los atributos anotados con @Parameter @Parameter sólo se puede usar sobre atributos (no sobre métodos) required: indica si el parámetro es obligatorio u opcional defaultPrefix: indica el prefijo por defecto NOTA: en la clase Layout también se ha anotado el parámetro pageTitle con @Property, dado que desde la plantilla (Layout.tml) se utiliza la expansión ${pageTitle} (siguiente transparencia), lo que requiere que la clase tenga explícitamente el método getPageTitle o se genere dinámicamente (@Property) Implementación del layout de una aplicación Web: Layout.tml (9) <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd"> <head> <title>${pageTitle}</title> <link rel="StyleSheet" href="${asset:context:/css/styles.css}" type="text/css" media="all"/> </head> <body> <!-- Window. --> <div id="window"> <!-- Body header. --> <div id="header"> ${message:header} </div> Implementación del layout de una aplicación Web: Layout.tml (10) <!-- Page body. --> <div id="pageBody"> <!-- Sidebar. --> <div id="sidebar"> <ul> <li> <a href="#" t:type="PageLink" t:page="Index"> ${message:sidebar-home} </a> </li> ... <li> <a href="#" t:type="PageLink" t:page="SelectLanguage"> ${message:sidebar-selectLanguage} </a> </li> </ul> </div> Implementación del layout de una aplicación Web: Layout.tml (11) <!-- Content. --> <div id="content"> <t:body/> </div> <!-- End of "pageBody" id. --> </div> <!-- Body footer. --> <div id="footer"> ${message:footer} </div> <!-- End of "window" id. --> </div> <!-- End of HTML body. --> </body> </html> Implementación del layout de una aplicación Web: Layout.tml (12) La plantilla define el layout en términos de estilos CSS Proporciona contenido para las áreas comunes Cabecera (header) Lista de enlaces (sidebar) Pié de página (footer) Partes variables Contenido del tag HTML title Valor del parámetro/propiedad pageTitle (${pageTitle}) Contenido (content) El elemento <t:body/> es un elemento especial (no es un componente) Tapestry reemplaza <t:body/> por el markup contenido en el cuerpo del componente Implementación del layout de una aplicación Web: Layout.tml (13) En Layout.tml se ha utilizado la expansión $ {asset:context:/css/styles.css} Prefijo asset: indica que se hace referencia a un recurso accesible por el navegador (a estos recursos se les denomina “assets” en la terminología de Tapestry) La ruta al recurso se indica con la notación context:ruta context indica que la ruta es de tipo path relativo a contexto, es decir, la ruta empieza por /, como las URLs de tipo path absoluto, pero sin incluir el nombre de la aplicación En tiempo de ejecución Tapestry genera la ruta /nombreaplicación/css/styles.css Implementación del layout de una aplicación Web: Layout.properties (y 14) footer=Area of Telematics Engineering - University of A Coruña header=MiniBank: A Sample POJO Web Application with Tapestry+Spring +Hibernate sidebar-addWithdraw=Add/Withdraw sidebar-create=Create sidebar-findAccounts=Find accounts sidebar-findAccountOperationsByDate=Find account operations by date sidebar-home=Home sidebar-remove=Remove account sidebar-selectLanguage=Select language sidebar-transfer=Transfer Integración con Spring (1) Tapestry proporciona una librería de integración con Spring que permite inyectar beans de Spring en la capa Web web.xml <?xml version="1.0" encoding="UTF-8"?> <!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>POJO-Examples MiniBank</display-name> <context-param> <param-name>tapestry.app-package</param-name> <param-value>es.udc.pojo.minibank.web</param-value> </context-param> Integración con Spring (2) web.xml (cont) <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring-config.xml</param-value> </context-param> ... <filter> <filter-name>app</filter-name> <filter-class> org.apache.tapestry5.spring.TapestrySpringFilter </filter-class> </filter> <filter-mapping> <filter-name>app</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener> </web-app> Integración con Spring (3) web.xml (cont) Con respecto a la configuración estudiada en el apartado 4.2 Se ha hecho un cambio específico a Tapestry: reemplazar el filtro estándar (TapestryFilter) por el filtro TapestrySpringFilter Extiende a TapestryFilter, añadiendo la posibilidad de inyectar servicios Spring en la capa Web Se han hecho dos cambios relativos al uso de Spring con cualquier framework Web Se ha definido el parámetro contextConfigLocation: especifica la ubicación (path relativo a contexto) del fichero de configuración de Spring (valor por defecto: /WEB-INF/ applicationContext.xml) Se ha definido el listener ContextLoaderListener: parsea el fichero de configuración de Spring y crea el contenedor de objetos NOTA: Un listener es una clase a la que el contenedor de servlets puede notificar determinados eventos Integración con Spring (y 4) @org.apache.tapestry5.ioc.annotations.Inject Permite inyectar un bean de Spring, servicios de Tapestry y algunos objetos especiales Generalmente se aplica sobre un atributo de una clase página/ componente Por defecto, tiene una semántica similar a la anotación @Autowired de Spring, es decir, se inyecta un bean que tenga el mismo tipo que el atributo anotado @Inject private AccountService accountService; Si hay más de un bean con el mismo tipo es preciso usar a mayores la anotación @org.apache.tapestry5.annotations.Service @Inject @Service("mockAccountService") private AccountService accountService; Componentes Select, Loop y PageLink (1) Para estudiar los componentes Select y Loop, y conocer algo más sobre el componente PageLink, se mostrará la implementación completa del caso de uso “Buscar las cuentas de un usuario” FindAccounts UserAccounts Componentes Select, Loop y PageLink: FindAccounts.tml (2) <html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd" t:type="Layout" t:pageTitle="title"> <form t:type="Form" t:id="findAccountsForm"> <t:errors/> <div class="field"> <t:label for="id"/> <span class="entry"> <input t:type="TextField" t:id="id" t:validate="required, min=0" size="16" maxlength="16"/> </span> </div> <div class="field"> <t:label for="accountSearchType"/> <span class="entry"> <t:select t:id="accountSearchType" validate="required"/> </span> </div> Componentes Select, Loop y PageLink: FindAccounts.tml (3) <div class="button"> <input type="submit" value="${message:button-find}"/> </div> </form> </html> Componentes Select, Loop y PageLink: FindAccounts.tml (4) Componente Select Genera el elemento select de HTML y sus opciones (elementos option) Los parámetros t:id y validate son idénticos a los de otros componentes correspondientes a campos de entrada de un formulario (e.g. TextField) En este ejemplo, t:id está haciendo referencia a la propiedad accountSearchType de la clase página (FindAccounts) La propiedad debe tener los métodos get y set para acceder al valor de la propiedad y establecerlo Si la propiedad es de tipo enumerado, como es el caso de accountSearchType, el componente Select genera una opción para cada valor del tipo enumerado public enum AccountSearchType { ACC_ID, USR_ID } Componentes Select, Loop y PageLink: FindAccounts.tml (5) Componente Select (cont) Para cada opción generada para cada valor del tipo enumerado El valor lógico (el que se envía por HTTP) es valor del enumerado (e.g. ACC_ID) El valor visual (el que muestra el navegador) puede generarlo el componente “automáticamente” dividiendo el valor en palabras (e.g. ACC_ID se mostraría como “Acc Id”) o se le puede especificar un valor explícitamente La forma natural de especificar los valores visuales de las opciones explícitamente consiste en definirlos en el catálogo de mensajes En MiniBank, en FindAccounts_es.properties (versión española del catálogo de mensajes) AccountSearchType.ACC_ID=Identificador de cuenta AccountSearchType.USR_ID=Identificador de usuario Código HTML generado <select id="accountSearchType" name="accountSearchType"> <option value="ACC_ID">Identificador de cuenta</option> <option value="USR_ID">Identificador de usuario</option> </select> Más adelante se ilustra una alternativa para generar las opciones basada en el uso del parámetro model Componentes Select, Loop y PageLink: FindAccounts.java (6) public class FindAccounts { public enum AccountSearchType { ACC_ID, USR_ID } @Property private Long id; @Property private AccountSearchType accountSearchType; @InjectPage private AccountDetails accountDetails; @InjectPage private UserAccounts userAccounts; Componentes Select, Loop y PageLink: FindAccounts.java (7) Object onSuccess() { if (accountSearchType==AccountSearchType.ACC_ID) { accountDetails.setAccountId(id); return accountDetails; } else { userAccounts.setUserId(id); return userAccounts; } } } Cuando la búsqueda se hace por identificador de cuenta, FindAccounts redirige el navegador a AccountDetails Cuando la búsqueda se hace por identificador de usuario, FindAccounts redirige el navegador a UserAccounts Componentes Select, Loop y PageLink: UserAccounts.tml (8) <html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd" t:type="Layout" t:pageTitle="title"> <t:if test="accounts.empty"> <p>${message:noAccounts}.</p> <t:parameter name="else"> <!-- Print user accounts. --> <table class="userAccounts"> <tr> <th>${message:accountId-label}</th> <th>${message:balance-label}</th> </tr> Componentes Select, Loop y PageLink: UserAccounts.tml (9) <tr t:type="Loop" t:source="accounts" t:value="account"> <td> <a href="#" t:type="PageLink" t:page="AccountDetails" t:context="account.accountId"> ${account.accountId} </a> </td> <td><t:output value="account.balance" format="format"/></td> </tr> El componente Output se </table> explica más adelante como parte del soporte de <!-- "Previous" and "Next" links. --> internacionalización que ofrece Tapestry <div class="previousNextLinks”> <span class="previousLink”> <t:if test="previousLinkContext”> <a href="#" t:type="PageLink" t:page="UserAccounts" t:context="previousLinkContext"> ${message:link-previous} </a> </t:if> </span> Componentes Select, Loop y PageLink: UserAccounts.tml (10) <span class="nextLink"> <t:if test="nextLinkContext"> <a href="#" t:type="PageLink" t:page="UserAccounts" t:context="nextLinkContext"> ${message:link-next} </a> </t:if> </span> </div> </t:parameter> </t:if> </html> Componentes Select, Loop y PageLink: UserAccounts.tml (11) La plantilla muestra las cuentas contenidas en la propiedad accounts (de tipo List) de la clase página (UserAccounts) <t:if test="accounts.empty"> Comprueba si la lista de cuentas es vacía List no tiene el método getEmpty, sino el método isEmpty En el caso de las propiedades booleanas, las convenciones de JavaBeans establecen que el método de lectura puede llamarse isPropiedad en vez de getPropiedad Componentes Select, Loop y PageLink: UserAccounts.tml (12) Componente Loop Itera sobre una colección de elementos En cada iteración genera el markup que contiene su cuerpo Cuando se utiliza embebido en otro elemento (e.g. en la plantilla se utiliza dentro del elemento HTML tr), el elemento también se genera en cada iteración Parámetros source: [prefijo por defecto: prop] especifica la colección de elementos sobre la que hay que iterar value: [prefijo por defecto: prop] elemento actual de la colección En UserAccount.tml, source hace referencia a la propiedad accounts y value a la propiedad account En cada iteración, el componente Select, antes de renderizar el markup, invoca a setAccount sobre la instancia de la clase página UserAccounts que está sirviendo esta petición, pasándole el elemento de la iteración actual En consecuencia, en cada iteración, la propiedad account contiene el valor de la cuenta actual Componentes Select, Loop y PageLink: UserAccounts.tml (13) Parámetro context del componente PageLink Permite establecer el contexto de activación en el enlace generado Prefijo por defecto: prop Ejemplo 1 <a href="#" t:type="PageLink" t:page="AccountDetails" context="account.accountId"> ... </a> context establece como contexto de activación el identificador de la cuenta Ejemplo 2 <a href="#" t:type="PageLink" t:page="UserAccounts" context="previousLinkContext"> ... </a> En este caso el contexto de activación necesita dos datos: userId y startIndex context hace referencia a la propiedad previousLinkContext de UserAccounts, que devuelve un array con estos dos datos (esta es la forma de pasar un contexto de activación que esté formado por más de un dato) Componentes Select, Loop y PageLink: UserAccounts.java (14) public class UserAccounts { private private private private private final static int ACCOUNTS_PER_PAGE = 10; Long userId; int startIndex = 0; AccountBlock accountBlock; Account account; @Inject private AccountService accountService; @Inject private Locale locale; public Long getUserId() { return userId; } public void setUserId(Long userId) { this.userId = userId; } Componentes Select, Loop y PageLink: UserAccounts. java (15) public List<Account> getAccounts() { return accountBlock.getAccounts(); } public Account getAccount() { return account; } public void setAccount(Account account) { this.account = account; } public Format getFormat() { return NumberFormat.getInstance(locale); } public Object[] getPreviousLinkContext() { if (startIndex-ACCOUNTS_PER_PAGE >= 0) { return new Object[] {userId, startIndex-ACCOUNTS_PER_PAGE}; } else { return null; } } Componentes Select, Loop y PageLink: UserAccounts. java (16) public Object[] getNextLinkContext() { if (accountBlock.getExistMoreAccounts()) { return new Object[] {userId, startIndex+ACCOUNTS_PER_PAGE}; } else { return null; } } Object[] onPassivate() { return new Object[] {userId, startIndex}; } void onActivate(Long userId, int startIndex) { this.userId = userId; this.startIndex = startIndex; accountBlock = accountService.findAccountsByUserId(userId, startIndex, ACCOUNTS_PER_PAGE); } } Componentes Select, Loop y PageLink: UserAccounts. java (17) onPassivate onActivate Cuando el contexto de activación está formado por más de un dato, tiene que devolver un Object[] Cuando el contexto de activación está formado por más de un dato, puede recibir un Object[], o alternativamente, recibir tantos parámetros como datos hay en el contexto de activación (Tapestry hace automáticamente el cast al tipo del parámetro) Componente Grid Alternativamente, para mostrar las cuentas, se podría haber usado el componente Grid Permite mostrar un conjunto de items en una tabla Soporta una versión avanzada del patrón Page-by-Page iterator, que incluye enlaces a cada página Componentes Select, Loop y PageLink (y 18) Componente Grid (cont) Este componente se ha utilizado para implementar la búsqueda de operaciones bancarias Ventaja: implementa la lógica de presentación de datos y navegación Ver AccountOperationsByDate.{tml, java} en el módulo pojo-minibank de pojo-examples Usa estilos CSS para que se pueda adaptar al look-n-feel de la aplicación Desventaja: puede ser difícil de adaptar si su comportamiento por defecto no es el que se desea Evento validateForm (1) Para entender la necesidad de procesar el evento validateForm, se mostrará la implementación completa del caso de uso “Eliminar una cuenta” RemoveAccount Si existe Si no existe SuccessfulOperation RemoveAccount Evento validateForm (2) Este caso de uso, al igual que los casos de uso añadir/ retirar/transferir, redirige el navegador a la página SuccessfulOperation si la operación se pudo realizar correctamente SuccessfulOperation simplemente imprime un mensaje que indica que la operación se realizó correctamente SuccessfulOperation.tml <html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd" t:type="Layout" t:pageTitle="title"> <p>${message:successfulOperation}.</p> </html> Evento validateForm: RemoveAccount.tml (3) <html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd" t:type="Layout" t:pageTitle="title"> <form t:type="Form" t:id="removeAccountForm"> <t:errors/> <div class="field"> <t:label for="accountId"/> <span class="entry"> <input t:type="TextField" t:id="accountId" t:validate="required, min=0" size="16" maxlength="16" /> </span> </div> <div class="button"> <input type="submit" value="${message:remove}"/> </div> </form> </html> Evento validateForm: RemoveAccount.tml (4) El parámetro t:validate de un componente correspondiente a un campo de entrada de un formulario sólo sirve para especificar comprobaciones Relativas a ese campo Que no necesiten usar un servicio de la capa modelo Cuando es necesario validar varios campos de entrada conjuntamente o se necesita acceder a la capa modelo para verificar los valores de entrada, una opción consiste en redefinir el evento validateForm Cuando llega una petición HTTP correspondiente al botón “Submit” de un formulario Se ejecutan los validadores de los campos (parámetro t:validate en los campos de entrada) Algunos (e.g. required) se ejecutan en el lado cliente (JavaScript) El formulario genera el evento validateForm Si hay errores => genera el evento failure; en otro caso => genera el evento success Por defecto, los eventos success y failure redirigen el navegador a la misma página Evento validateForm: RemoveAccount.java (5) public class RemoveAccount { @Property private Long accountId; @Component private Form removeAccountForm; @Component(id="accountId") private TextField accountIdTextField; @Inject private Messages messages; @Inject private AccountService accountService; @InjectPage private SuccessfulOperation successfulOperation; Evento validateForm: RemoveAccount.java (6) void onValidateForm() { if (!removeAccountForm.isValid()) { return; } try { accountService.removeAccount(accountId); } catch (InstanceNotFoundException e) { removeAccountForm.recordError(accountIdTextField, messages.format("error-accountNotFound", accountId)); } } Object onSuccess() { return successfulOperation; } } Evento validateForm: RemoveAccount.java (7) @org.apache.tapestry5.annotations.Compo nent Permite definir un componente dentro de otro o de una página id: especifica el identificador del componente (por defecto, su valor es el nombre del atributo anotado) En RemoveAccount, @Component se está utilizando para definir un formulario (Form) y un campo de entrada (TextField) dentro de la página En ejemplos anteriores esto no fue necesario: los componentes que necesitaba una página se especificaban en la propia plantilla Sin embargo, cuando desde el código de la clase página se desea manipular un componente, es necesario definirlo en la propia clase de la página Evento validateForm: RemoveAccount.java (8) Evento validateForm Procesado con el método onValidateForm Lo primero que hay que hacer es comprobar si los validadores de los campos de entrada detectaron algún error (método isValid del componente Form) Si no se produjeron errores, se realizan las validaciones adicionales necesarias En este caso, se intenta eliminar la cuenta a partir de su identificador y si se produce InstanceNotFoundException, se concluye que el identificador no es correcto Alternativamente, se podría haber intentado recuperar la cuenta a partir de su identificador en onValidateForm, y si existe, borrarla en el método onSuccess Evento validateForm: RemoveAccount.java (y 9) Evento validateForm (cont) Los errores encontrados se añaden al formulario a través del método recordError, que recibe El campo de entrada al que se quiere asociar el error El mensaje de error Dado que los mensajes están externalizados en ficheros, para insertar el mensaje de error se necesita acceder al catálogo de mensajes org.apache.tapestry5.ioc.Messages: servicio ofrecido por Tapestry para acceder al catálogo de mensajes Se inyecta en la página con la anotación @Inject (como cualquier otro servicio) El método format de Messages recibe la clave de un mensaje y una lista variable de argumentos que se insertarán en el mensaje En este caso, se desea que el mensaje contenga el identificador de la cuenta que no existe En app.properties error-accountNotFound=%s: account not found Notación (inspirada en printf de C): ver JavaDoc de java.util.Formatter Internacionalización, Application Module Builder, servicios y evento prepareForRender (1) En Java, la internacionalización gira en torno al concepto de locale “Un objeto java.util.Locale representa una región geográfica, política o cultural” [JavaDoc de la API de Java SE] Una instancia de Locale contiene un código ISO de idioma, y opcionalmente un código de país y una variante Códigos de idiomas: http://www.loc.gov/standards/iso639-2/ php/English_list.php Códigos de países: http://www.iso.ch/iso/country_codes/ iso_3166_code_lists/ english_country_names_and_code_elements.htm Códigos de variantes: no estandarizados Internacionalización, Application Module Builder, servicios y evento prepareForRender (2) En el apartado 4.2, se estudió un primer aspecto relativo a la internacionalización: externalizar los mensajes de aplicación en ficheros .properties (catálogos de mensajes) Cuando se desean soportar “n” idiomas es necesario Indicarle a Tapestry la lista de locales soportados Tener una versión de cada catálogo de mensajes para cada locale soportado Especificación de locales soportados La manera usual de especificar la lista de locales soportados en Tapestry es mediante la propiedad tapestry.supported-locales de la configuración del servicio ApplicationDefaults Para ello es necesario implementar la clase Application Module Builder Internacionalización, Application Module Builder, servicios y evento prepareForRender (3) Clase Application Module Builder Una aplicación que desee modificar la configuración de los servicios de Tapestry, o incluso, añadir nuevos servicios, necesita definir la clase Application Module Builder Esta clase tiene que Estar definida en el paquete services, hermano de pages y components Llamarse <filter-name>Module, siendo filter-name el nombre del filtro de Tapestry especificado en web.xml con el tag filter-name, con la primera letra en mayúscula En el caso de MiniBank, dado que el filtro se llama app, la clase es AppModule Internacionalización, Application Module Builder, servicios y evento prepareForRender (4) Clase Application Module Builder – versión 1 package es.udc.pojo.minibank.web.services; import org.apache.tapestry5.SymbolConstants; import org.apache.tapestry5.ioc.MappedConfiguration; public class AppModule { public static void contributeApplicationDefaults( MappedConfiguration<String, String> configuration) { configuration.add(SymbolConstants.SUPPORTED_LOCALES, "en,es,gl"); } } Internacionalización, Application Module Builder, servicios y evento prepareForRender (5) Clase Application Module Builder – versión 1 (cont) Los métodos de esta clase siguen convenciones de nombrado Para modificar la configuración (“contribuir” en terminología de Tapestry) de un servicio es necesario implementar el método contribute<NombreDeServicio> Es estático y recibe un objeto MappedConfiguration que representa la configuración del servicio, y puede recibir parámetros adicionales (se ilustra más adelante) En el ejemplo, se está añadiendo a la configuración del servicio ApplicationDefaults, el parámetro tapestry.supported-locales (a través de la constante SymbolConstants.SUPPORTED_LOCALES) Internacionalización, Application Module Builder, servicios y evento prepareForRender (6) Especificación de idiomas soportados El valor del parámetro tapestry.supported-locales es un conjunto de locales (separados por comas, ¡sin espacios en blanco!) Los locales siguen el formato idioma[_pais[_variante]] Ejemplos: es (Español), en (Inglés), en_GB (Inglés del Reino Unido), etc. Catálogos de mensajes Para cada catálogo de mensajes, una versión para cada idioma Fichero[_idioma[_pais[_variante]]].properties En MiniBank, por cada fichero .properties existen las siguientes versiones fichero_es.properties: mensajes en Español fichero_gl.properties: mensajes en Gallego fichero.properties: mensajes en Inglés Internacionalización, Application Module Builder, servicios y evento prepareForRender (7) Selección del locale Una aplicación Web, a diferencia de una aplicación de escritorio, es usada concurrentemente por múltiples usuarios, quizás cada uno deseando usar un locale distinto ¿Cómo hace Tapestry para decidir qué versión del catálogo de mensajes seleccionar cada vez que procesa una petición HTTP? Por defecto, Tapestry realiza la selección automáticamente en base a la cabecera de la petición HTTP, que incluye una lista priorizada de locales preferidos por el navegador Todos los navegadores permiten que el usuario especifique esta lista Ejemplo: Firefox 3 -> Preferencias -> Contenido -> Idiomas Internacionalización, Application Module Builder, servicios y evento prepareForRender (8) Selección del locale (cont) Para determinar el locale, Tapestry compara la lista de locales preferidos que llega en la cabecera HTTP con la lista de locales soportados (tapestry.supported-locales), y elige, de entre los soportados, el que “mejor concuerde” con la lista de preferidos Ejemplo: si la lista de locales preferidos del navegador está formada sólo por es_ES (Español de España), en el caso de MiniBank, Tapestry elegiría el locale es Si no hay ninguno que concuerde, elige el primero de los locales soportados Ejemplo: en el caso de MiniBank, el primer locale especificado en el parámetro tapestry.supported-locales es en Internacionalización, Application Module Builder, servicios y evento prepareForRender (9) Selección del catálogo de mensajes Cuando hay que recuperar un mensaje del catálogo de mensajes, Tapestry elige el fichero en base al locale seleccionado fichero_locale.properties si existe; fichero.properties en otro caso Formateo de datos en función del locale En MiniBank, cuando se imprimen números reales y fechas, se formatean en base al locale seleccionado Ejemplo 1: 12345.67 (punto como separador de decimales) se formatea como 12.345,67 con locale es y como 12,345.67 con locale en Ejemplo 2: 23 de Diciembre de 2008 se formatea como 23/12/08 con locale es y 12/23/08 con locale en Internacionalización, Application Module Builder, servicios y evento prepareForRender (10) Formateo de datos en función del locale: caso de uso “Buscar las cuentas de un usuario” locale es locale en Internacionalización, Application Module Builder, servicios y evento prepareForRender (11) Formateo de datos en función del locale: caso de uso “Buscar las cuentas de un usuario” (cont) En UserAccounts.tml El saldo (número real) se imprime con el componente Output <t:output value="account.balance" format="format"/> Componente Output Permite formatear datos value: [prefijo por defecto: prop] valor a formatear format: [prefijo por defecto: prop] objeto de tipo java.text.Format El paquete java.text de Java SE proporciona clases e interfaces para tratar datos: números, fechas, etc. Format es una clase abstracta que actúa como clase base de los formateadores de datos Internacionalización, Application Module Builder, servicios y evento prepareForRender (12) Formateo de datos en función del locale: caso de uso “Buscar las cuentas de un usuario” (cont) En UserAccounts.java @Inject private Locale locale; public Format getFormat() { return NumberFormat.getInstance(locale); } Es posible utilizar la anotación @Inject para inyectar una instancia del locale seleccionado por Tapestry para la petición HTTP actual java.text.NumberFormat es una clase abstracta que extiende a java.text.Format El método estático getInstace(Locale) es un método factoría que devuelve una instancia de una clase que extiende a NumberFormat apropiada para tratar números en el locale pasado como parámetro Internacionalización, Application Module Builder, servicios y evento prepareForRender (13) Entrada de datos en función del locale En MiniBank, se permite introducir números reales y fechas en función del locale seleccionado Ejemplo 1: 12.345,67 con locale es y como 12,345.67 con locale en se interpretan como 12345.67 (punto como separador de decimales) Ejemplo 2: 23/12/08 con locale es y 12/23/08 con locale en se interpretan como 23 de Diciembre de 2008 Entrada de datos en función del locale: caso de uso “Crear una cuenta bancaria” locale es locale en Internacionalización, Application Module Builder, servicios y evento prepareForRender (14) Entrada de datos en función del locale: caso de uso “Crear una cuenta bancaria” – CreateAccount.java (cont) // ... @Property private String balance; private double balanceAsDouble; @Component private Form createAccountForm; @Component(id="balance") private TextField balanceTextField; @Inject private Locale locale; @Inject private Messages messages; Internacionalización, Application Module Builder, servicios y evento prepareForRender (15) Entrada de datos en función del locale: caso de uso “Crear una cuenta bancaria” – CreateAccount.java (cont) void onValidateForm() { if (!createAccountForm.isValid()) { return; } NumberFormat numberFormatter = NumberFormat.getInstance(locale); ParsePosition position = new ParsePosition(0); Number number = numberFormatter.parse(balance, position); if (position.getIndex() != balance.length()) { createAccountForm.recordError(balanceTextField, messages.format("error-incorrectNumberFormat", balance)); } else { balanceAsDouble = number.doubleValue(); } } // ... Internacionalización, Application Module Builder, servicios y evento prepareForRender (16) Entrada de datos en función del locale: caso de uso “Crear una cuenta bancaria” – CreateAccount.java (cont) La propiedad balance se ha modelado como un String, y no como un double, dado que es necesario interpretar el significado de los caracteres “.” y “,” en función del locale seleccionado En un double el único caracter admisible es el “.”, que actúa como separador de decimales En CreateAccount.tml se utiliza t:validate="required, min=0" sobre la propiedad balance El método parse de NumberFormat (definido en Format) permite parsear un String (que se supone que contiene un número) desde una posición (java.text.ParsePosition) Interpreta correctamente el significado de los caracteres “.” y “,” Después de invocarlo, ParsePosition indica el índice en el que ha terminado el proceso de parsing Si el índice no es igual al tamaño de la cadena parseada, quiere decir que no toda la cadena se pudo parsear como un número Internacionalización, Application Module Builder, servicios y evento prepareForRender (17) Formateo y parsing de fechas Similar al código estudiado para números reales, usando java.text.DateFormat en lugar de java.text.NumberFormat Se ejemplifica en el caso de uso “Buscar las operaciones que se hicieron sobre una cuenta entre dos fechas” (véase AccountOperationsByDate y FindAccountOperationsByDate) Internacionalización, Application Module Builder, servicios y evento prepareForRender (18) Internacionalización/redefinición de los mensajes generados por Tapestry El fichero JAR correspondiente a la librería tapestry-core incluye las clases correspondientes al framework, los componentes nativos y algunas páginas especiales En el caso de los componentes/páginas, además de los ficheros .class, el fichero JAR incluye los ficheros plantilla y los catálogos de mensajes de los componentes/páginas que lo necesitan El fichero JAR también incluye el catálogo de mensajes de los validadores Los catálogos de mensajes están internacionalizados para algunos idiomas En una situación real puede ser necesario añadir/modificar los catálogos de mensajes Soportar algún idioma adicional Proporcionar unos mensajes más adecuados Internacionalización, Application Module Builder, servicios y evento prepareForRender (19) Internacionalización/redefinición de los mensajes generados por Tapestry – Componente Errors Cuando hay errores, antes de imprimirlos, este componente genera el mensaje que aparece en la cabecera de la lista de errores locale es locale en Internacionalización, Application Module Builder, servicios y evento prepareForRender (20) Internacionalización/redefinición de los mensajes generados por Tapestry – Componente Errors (cont) Dado que el nombre completo del componente es org.apache.tapestry5.corelib.components.Errors, dentro del fichero JAR sus catálogos de mensajes (Errors*.properties) están en org/apache/tapestry5/corelib/components En MiniBank, se han incluido los catálogos para los tres locales que soporta en src/main/resources/org/apache/tapestry5/ corelib/components NOTA: en principio, en este caso como en otros, no sería necesario proporcionar los catálogos de mensajes que ya vienen dentro del JAR; sin embargo, podría ser necesario proporcionarlos si se quieren adaptar los mensajes La única clave que se ha definido es default-banner, que es la única que utiliza el componente Errors Ejemplo: Errors_es.properties default-banner=Debe corregir los siguientes errores antes de continuar. Internacionalización, Application Module Builder, servicios y evento prepareForRender (21) Internacionalización/redefinición de los mensajes generados por Tapestry – Componente Errors (cont) En consecuencia, los catálogos de mensajes en el WAR generado estarán en WEB-INF/classes/org/apache/tapestry5/ corelib/components A la hora de elegir los catálogos de mensajes, tienen preferencia los de la aplicación frente a los incluidos en las librerías Se ha procedido de manera análoga para el componente GridPager (que es usado por el componte Grid) Internacionalización/redefinición de los mensajes generados por Tapestry – Validadores Dentro del fichero JAR, los catálogos de mensajes de los validadores están en org/apache/tapestry5/internal/ ValidationMessages.properties En consecuencia, en MiniBank, se han incluido los catálogos para los tres locales que soporta en src/main/resources/org/apache/ tapestry5/internal Sólo se han definido las claves de los mensajes de error requeridos por MiniBank Internacionalización, Application Module Builder, servicios y evento prepareForRender (22) Caso de uso “Selección de idioma” Finalmente, MiniBank ilustra cómo implementar un caso de uso para permitir que el usuario cambie explícitamente el idioma desde la interfaz de la propia aplicación Web Internacionalización, Application Module Builder, servicios y evento prepareForRender (23) Caso de uso “Selección de idioma” (cont) Requisitos Cuando el usuario accede al formulario de selección de idioma, el campo “Idioma” tiene que mostrar el idioma correspondiente al idioma actualmente seleccionado La lista de idiomas en el desplegable asociado al campo “Idioma” tiene que estar ordenada alfabéticamente según el idioma actualmente seleccionado Tras pulsar en el botón “Aplicar”, la aplicación tiene que aplicar el correspondiente locale en todas las peticiones HTTP que haga ese navegador (con preferencia sobre la lista de idiomas que envíe el navegador en la cabecera HTTP) Internacionalización, Application Module Builder, servicios y evento prepareForRender: SelectLanguage.tml (24) <html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd" t:type="Layout" t:pageTitle="title"> <form t:type="Form" t:id="selectLanguageForm"> <div class="field"> <t:label for="language"/> <span class="entry"> <t:select t:id="language" model="languages" validate="required"/> </span> </div> <div class="button"> <input type="submit" value="${message:button-apply}"/> </div> </form> </html> Internacionalización, Application Module Builder, servicios y evento prepareForRender: SelectLanguage.tml (25) Componente Select Anteriormente se describió un uso del componente Select basado en especificar en t:id el nombre de una propiedad enumerada En principio, se podría haber definido un enumerado al estilo Y proporcionar los valores visuales en SelectLanguage[{_es,_gl}].properties Aunque en el caso de MiniBank sería una opción perfectamente asumible, se ha decido complicar la solución para que la lista de idiomas del desplegable se muestre ordenada según el idioma actualmente seleccionado, tal y como ilustran las pantallas anteriores public enum LanguageType {en, es, gl} Esta solución es imprescindible si la lista de elementos del desplegable es extensa La opción de usar un enumerado tampoco es válida cuando la lista de elementos a mostrar es dinámica (e.g. se leen de una BD) Internacionalización, Application Module Builder, servicios y evento prepareForRender: SelectLanguage.tml (26) Componente Select (cont) En consecuencia, se ha decido Modelar la propiedad language (valor del parámetro t:id) como un String Usar el parámetro model Parámetro model Prefijo por defecto: prop Entre otros usos, permite definir explícitamente la lista de elementos a desplegar Ejemplo 1: valores lógicos iguales a valores visuales <t:select t:id="language" model="literal:Español, Gallego, Inglés"/> Ejemplo 2: valores lógicos diferentes de valores visuales <t:select t:id="language" model="literal:es=Español, gl=Gallego, en=Inglés"/> En SelectLanguage.tml el valor de model es el nombre de una propiedad (languages) que devuelve la lista de elementos a desplegar en función del locale actual Internacionalización, Application Module Builder, servicios y evento prepareForRender: SelectLanguage.java (27) public class SelectLanguage { @Property private String language; @Inject private Locale locale; @Inject private PersistentLocale persistentLocale; @Inject private SupportedLanguages supportedLanguages; void onPrepareForRender() { language = locale.getLanguage(); } public String getLanguages() { return supportedLanguages.getOptions(locale.getLanguage()); } Internacionalización, Application Module Builder, servicios y evento prepareForRender: SelectLanguage.java (28) Object onSuccess() { persistentLocale.set(new Locale(language)); return Index.class; } } Evento prepareForRender Un formulario antes de renderizarse, emite el evento prepareForRender En SelectLanguage, se captura este evento (onPrepareForRender) para dar valor automáticamente al atributo language Se establece el código del idioma correspondiente al locale actual Este ejemplo ilustra una solución a un problema muy frecuente: mostrar un formulario con los valores actualmente seleccionados En MiniPortal, ocurre lo mismo en la página UpdateProfile En esta página también se captura este evento: se invoca al caso de uso que devuelve el perfil del usuario y se inicializan los atributos correspondientes a los campos del formulario Internacionalización, Application Module Builder, servicios y evento prepareForRender: SelectLanguage.java (29) org.apache.tapestry5.services.PesistentLocale Servicio proporcionado por Tapestry Permite guardar el locale en una cookie Nombre de la cookie: org.apache.tapestry5.locale Tiene un tiempo de vida de una semana Tiene preferencia sobre la lista de idiomas preferidos que llega en la cabecera de las peticiones HTTP Internacionalización, Application Module Builder, servicios y evento prepareForRender (30) Servicios definidos por el desarrollador La clase SelectLanguage implementa el método getLanguages apoyándose en la clase SupportedLanguages public String getLanguages() { return supportedLanguages.getOptions(locale.getLanguage()); } El método getLanguages(Locale) de SupportedLanguages devuelve la lista de idiomas (ordenada) en el locale pasado como parámetro y con el formato esperado por el parámetro model del componente Select Internacionalización, Application Module Builder, servicios y evento prepareForRender: SupportedLanguages (31) public class SupportedLanguages { private final Map<String, String> options; private String codes = ""; public SupportedLanguages() { String options_en = "en=English, gl=Galician, es=Spanish"; String options_es = "es=Español, gl=Gallego, en=Inglés"; String options_gl = "es=Español, gl=Galego, en=Inglés"; options = new HashMap<String, String>(); options.put("en", options_en); options.put("es", options_es); options.put("gl", options_gl); codes = "en,es,gl"; } Internacionalización, Application Module Builder, servicios y evento prepareForRender: SupportedLanguages (32) public String getCodes() { return codes; } public String getOptions(String languageCode) { String languages = options.get(languageCode); if (languages != null) { return languages; } else { return options.get("en"); } } Internacionalización, Application Module Builder, servicios y evento prepareForRender: SupportedLanguages (33) Servicios definidos por el desarrollador (cont) Actualmente, la clase SupportedLanguages contiene la lista de idiomas para cada locale soportado Esta clase se podría haber definido como un Singleton o como una clase utilidad (atributos y métodos estáticos) Sin embargo, se ha decido complicar la situación, dado que en un escenario más realista, los códigos de los idiomas y sus nombres respectivos seguramente se leerían de la BD En este caso, el mapa options se crearía en el constructor dinámicamente en función de los valores leídos de la BD options actuaría como caché de sólo lectura Para leer los valores de la BD, sería preciso invocar un servicio del modelo Internacionalización, Application Module Builder, servicios y evento prepareForRender: SupportedLanguages (34) Servicios definidos por el desarrollador (cont) La inyección de servicios con @Inject sólo se puede usar en las clases de las páginas y los componentes En consecuencia, definir SupportedLanguages como un singleton o una clase utilidad nos obligaría a construir un contenedor de objetos (WebApplicationContext, como se hace en el servlet DemoServlet de pojoadvhibernatetut) explícitamente con la API de Spring para resolver el problema planteado Una solución más sencilla consiste en definir SupportedLanguages como un servicio de Tapestry Internacionalización, Application Module Builder, servicios y evento prepareForRender: SupportedLanguages (35) Servicios definidos por el desarrollador (cont) Un servicio puede recibir en su constructor otros servicios Misma semántica que @Inject, pero sin usar esta anotación Ejemplo: para resolver el problema planteado, SupportedLanguages podría recibir en su constructor un bean de Spring (servicio del modelo) que le permitiese recuperar los nombres de los idiomas y sus códigos respectivos public SupportedLanguages(LanguageService languageService) { // ... } Los servicios se declaran en la clase Application Module Builder (AppModule, en el caso de MiniBank) Internacionalización, Application Module Builder, servicios y evento prepareForRender: AppModule (36) package es.udc.pojo.minibank.web.services; import import import import public org.apache.tapestry5.SymbolConstants; org.apache.tapestry5.ioc.MappedConfiguration; org.apache.tapestry5.ioc.ServiceBinder; org.apache.tapestry5.ioc.annotations.InjectService; class AppModule { public static void bind(ServiceBinder binder) { binder.bind(SupportedLanguages.class). withId("SupportedLanguages"); } public static void contributeApplicationDefaults( MappedConfiguration<String, String> configuration, @InjectService("SupportedLanguages") SupportedLanguages supportedLanguages) { configuration.add(SymbolConstants.SUPPORTED_LOCALES, supportedLanguages.getCodes()); } } Internacionalización, Application Module Builder, servicios y evento prepareForRender: AppModule (37) Clase Application Module Builder – versión 2 Método bind Para declarar servicios de Tapestry Es estático y recibe un objeto ServiceBinder que permite declarar el servicio El método bind de la clase ServiceBinder recibe la clase de implementación del servicio Por defecto, los servicios son singletons También es posible implementar un servicio con una clase de implementación y una interfaz; en este caso, existe otra variante de bind que recibe la clase de implementación y la interfaz El método bind de la clase ServiceBinder devuelve un objeto ServiceBindingOptions El método withId permite asignar un identificador al servicio Cuando el servicio implementa una interfaz, el identificador por defecto es el nombre simple de la interfaz Internacionalización, Application Module Builder, servicios y evento prepareForRender: AppModule (y 38) Clase Application Module Builder – versión 2 (cont) Método contributeApplicationDefaults Como se comentó anteriormente, además del objeto MappedConfiguration, es posible pasar otro tipo de objetos En particular, se pueden pasar servicios (de Tapestry o beans de Spring), que tienen que anotarse con @InjectService @InjectService dispone del elemento value, cuyo valor es el identificador del servicio En el ejemplo, se utiliza @InjectService para poder tener acceso al servicio SupportedLanguages y poder así declarar los locales soportados Tratamiento de excepciones “unchecked” no esperadas (1) ¿Qué ocurre cuando se produce una excepción de tipo “unchecked” no esperada (hijas de Runtime o Error)? Este tipo de excepciones representan errores graves (e.g. BD caída) y pueden producirse tanto en la capa modelo como en la capa Web A menos que el código las capture explícitamente (en general, esto no se suele hacer), este tipo excepciones fluyen hacia arriba y finalmente Tapestry las captura Cuando Tapestry captura una excepción de este tipo, pasa el control a la página ExceptionReport Paquete: org.apache.tapestry5.corelib.pages Como cualquier página dispone de una clase (ExceptionReport) y una plantilla (ExceptionReport.tml) No dispone de catálogo de mensajes Tratamiento de excepciones “unchecked” no esperadas (2) ExceptionReport.tml consulta la propiedad productionMode para decidir el nivel de información a mostrar La propiedad productionMode es boolena y corresponde al parámetro de configuración tapestry.production-mode del servicio ApplicationDefaults true [valor por defecto]: la aplicación está en modo producción false: la aplicación está en modo desarrollo En modo desarrollo, la página ExceptionReport.tml muestra gran cantidad de información sobre la excepción producida (muy útil para depuración), mientras que en modo producción sólo muestra el mensaje de la excepción Tratamiento de excepciones “unchecked” no esperadas (3) Como cualquier otro parámetro de configuración de un servicio, el valor de tapestry.production-mode (también disponible mediante la constante SymbolConstants.PRODUCTION_MODE) se puede especificar en la implementación del método contribute<NombreDeServicio> (contributeApplicationDefaults en este caso) de la clase Application Module Builder configuration.add(SymbolConstants.PRODUCTION_MODE, "false"); Algunos parámetros de configuración, entre ellos tapestry.production-mode, también se pueden especificar en web.xml o como propiedades de la máquina virtual (opción -D) En MiniBank, se ha elegido la opción de especificarlo en web.xml (para que su valor se pueda cambiar fácilmente cuando la aplicación entra en producción) <context-param> <param-name>tapestry.production-mode</param-name> <param-value>false</param-value> </context-param> Tratamiento de excepciones “unchecked” no esperadas (4) Modo desarrollo Modo producción Tratamiento de excepciones “unchecked” no esperadas (5) En MiniBank se ha creado una versión de ExceptionReport.tml que Es copia de la versión original incluida en el JAR de Tapestrty, pero a diferencia de ella [Cualquier modo] Muestra “MiniBank: Error interno.” como contenido del tag title y como mensaje inicial en la página El mensaje está internacionalizado [Modo producción] No muestra más información [Modo desarrollo] Muestra la misma información detallada que la versión original Código ExceptionReport.tml y ExceptionReport*.properties en src/main/ resources/org/apache/tapestry5/corelib/pages Tratamiento de excepciones “unchecked” no esperadas (6) Modo desarrollo Modo producción Tratamiento de excepciones “unchecked” no esperadas: ExceptionReport.tml (y 7) <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http:// www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xml:space="default" xmlns:t="http://tapestry.apache.org/schema/ tapestry_5_0_0.xsd"> <head> <title>${message:title}</title> </head> <body> <h1 class="t-exception-report">${message:title}.</h1> <t:if test="productionMode"> <!-<p>${rootException.message}</p> --> <t:parameter name="else"> ... </t:parameter> </t:if> </body> </html> [Recordatorio] Uso del DataSource proporcionado por el servidor de aplicaciones Como se comentó en el apartado 3.4, Spring ofrece soporte para acceder al DataSource proporcionado por el servidor de aplicaciones Habitualmente estos DataSources implementan la estrategia de pool de conexiones En src/main/webapp/WEB-INF/spring-config.xml ... <bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean" p:jndiName="jdbc/pojo-examples-ds" p:resourceRef="true" /> <bean id="sessionFactory" class= "org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean" p:dataSource-ref="dataSource" p:configLocation="classpath:hibernate.cfg.xml"/> ... Habilitación del patrón Open Session in View (1) Spring ofrece un filtro, OpenSessionInViewFilter, que permite usar el patrón Open Sesion in View Para cada petición HTTP que intercepta Abre una sesión de Hibernate Dejar fluir la petición Cierra la sesión de Hibernate Habilitación del patrón Open Session in View: web.xml (2) ... <filter> <filter-name>openSessionInView</filter-name> <filter-class> org.springframework.orm.hibernate3.support.OpenSessionInViewFilter </filter-class> <init-param> <param-name>sessionFactoryBeanName</param-name> <param-value>sessionFactory</param-value> </init-param> </filter> <filter> <filter-name>app</filter-name> <filter-class> org.apache.tapestry5.spring.TapestrySpringFilter </filter-class> </filter> Habilitación del patrón Open Session in View: web.xml (3) <filter-mapping> <filter-name>openSessionInView</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>app</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> ... Habilitación del patrón Open Session in View: web.xml (4) OpenSessionInViewFilter Los filtros pueden tener parámetros de configuración (tags init-param) Entre otros, dispone del parámetro sessionFactoryBeanName Indica el nombre del bean SessionFactory declarado en el fichero de configuración de Spring Por defecto asume el nombre sessionFactory (por tanto, en este caso no sería necesario especificarlo, dado que coincide con el nombre que se le dio en el fichero de configuración de Spring) Encadenamiento de filtros El orden en el que aparecen los tags filter-mapping en web.xml determina el orden en el que se aplicarán los filtros Habilitación del patrón Open Session in View: web.xml (5) Encadenamiento de filtros (cont) En el ejemplo, para cualquier petición HTTP, primero se aplicará OpenSessionInViewFilter y posteriormente TapestrySpringFilter Procesamiento de una petición HTTP [OpenSessionInView] Abre la sesión [OpenSessionInView] Dejar fluir la petición [TapestrySpringFilter] Procesa la petición (la clase página invoca a un método de un servicio del modelo y la plantilla atraviesa relaciones entre entidades) [OpenSessionInView] Cierra la sesión Obsérvese que este orden de encadenamiento (y no el contrario) es obligatorio para que funcione el patrón Open Session in View Habilitación del patrón Open Session in View: web.xml (6) Problema: con el mapping del ejemplo, todas las peticiones HTTP pasan por OpenSessionInView <url-pattern>/*</url-pattern> Como parte de cada petición HTTP se abre una sesión de Hibernate La sesión de Hibernate consume una conexión del DataSource Para las peticiones HTTP que no tengan que acceder a la capa modelo, consumir una conexión del DataSource es innecesario y costoso (se consume una conexión cuando no es necesario) Solución 1 Especificar que sólo pasen por OpenSessionInView las peticiones que realmente lo requieran (es decir, aquellas que necesiten navegar por relaciones después de invocar un método de un servicio de la capa modelo) Inconveniente: hay que especificar tantos mappings (filtermapping) como peticiones tengan que pasar por OpenSessionInView o hacer que todas sigan un patrón común en el nombre de sus URLs (e.g. mismo prefijo o sufijo) Habilitación del patrón Open Session in View: web.xml (7) Solución 2 Emplear un proxy (LazyConnectionDataSourceProxy) del DataSource real que retrase la petición de una conexión al DataSource real hasta que se intente lanzar una consulta En src/main/webapp/WEB-INF/spring-config.xml <bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean" p:jndiName="jdbc/pojo-examples-ds" p:resourceRef="true" /> <bean id="dataSourceProxy" class="org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy" p:targetDataSource-ref="dataSource"/> <bean id="sessionFactory" class= "org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean" p:dataSource-ref="dataSourceProxy" p:configLocation="classpath:hibernate.cfg.xml"/> Habilitación del patrón Open Session in View: web.xml (y 8) Solución 2 (cont) pojo-minibank y pojo-miniportal De esta forma, aunque cada petición HTTP abre una sesión de Hibernate, sólo aquellas que acceden a la capa modelo terminan consumiendo una conexión del pool El código actual no requiere que el patrón Open Session in View esté habilitado spring-config.xml y web.xml incluyen secciones comentadas para facilitar la habilitación de este patrón en caso necesario pojo-advhibernatetut spring-config.xml y web.xml habilitan el patrón Open Session in View