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