Sunteți pe pagina 1din 102

Tema 9: Caso de Estudio de

Implementación de la Capa Web con


Tapestry
Objetivos
n Aprender más sobre Tapestry usando la capa Web de MiniBank
como caso de estudio
n Implementación del layout de una aplicación Web
n Integración con Spring
n Componentes Select, Loop y PageLink
n Evento validate en formularios
n Internacionalización, Application Module Builder y evento
prepareForRender
n Tratamiento de excepciones “unchecked” no esperadas
n Ilustrar algunos aspectos relativos a Spring e Hibernate en el
contexto de una aplicación Web
n [Recordatorio] Uso del DataSource proporcionado por el servidor
de aplicaciones
n Habilitación del patrón Open Session in View
n Minimización, compresión y caché

9-2
Estructura de src/main (1)
pojo-minibank
src/main

java
es.udc.pojo.minibank

model
web
components
pages

accounts
preferences
search

services
util
(Continúa en la siguiente transparencia)

9-3
Estructura de src/main (y 2)

pojo-minibank
src/main

resources

es/udc/pojo/minibank/web
components
pages

accounts
preferences
search
org/apache/tapestry5
corelib
webapp
pages

WEB-INF
9-4
Implementación del layout de una aplicación Web (1)

n En MiniBank todas las páginas tienen el mismo “layout”


(estructura, disposición)

9-5
Implementación del layout de una aplicación Web (2)

n Concretamente, todas las páginas tienen el siguiente layout

Título – MiniBank (tag title)

Menú MiniBank Cuentas Buscar Preferencias


[Título]

Contenido

Pie de página

9-6
Implementación del layout de una aplicación Web (3)

n Además, en MiniBank
n El menú y el pie de página son iguales en todas las páginas
n El título y el contenido son específicos en cada página
n Con un enfoque básico, la plantilla de cada página
tendría que contener
n El markup que define el layout => ¡el mismo en todas las
páginas!
n El markup de las áreas comunes => ¡el mismo en todas
las páginas!
n El markup de las áreas específicas
n Este enfoque básico conduce a una solución difícil de
mantener

9-7
Implementación del layout de una aplicación Web (4)

n Para hacer frente a este problema, en MiniBank (y en


MiniPortal) se ha adoptado la solución aconsejada en
la documentación de Tapestry
n Desarrollar un componente que genere el layout y el
contenido de las áreas comunes => componente
Layout
n 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
n Los componentes representan elementos reusables
en la interfaz gráfica
n Los componentes PageLink, Form, TextField, Label e
If estudiados en el tema 8 son ejemplos de componentes
n Es posible desarrollar componentes a medida

9-8
Implementación del layout de una aplicación Web (5)

n Un componente en Tapestry consta de una clase, y


opcionalmente, una plantilla
n La clase y la plantilla de un componente tienen que estar en
el paquete components, hermano de pages
n En el código fuente de MiniBank
n La clase (Layout) está en
src/main/java/es/udc/pojo/minibank/web/componen
ts
n La plantilla (Layout.tml) y su catálogo de mensajes
(Layout.properties) están en
src/main/resources/es/udc/pojo/minibank/web/com
ponents
n 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)

9-9
Implementación del layout de una aplicación Web (6)

n El componente Layout nos permitirá escribir la plantilla


correspondiente a una página especificando sólo el markup de las
áreas específicas
n El título
n El markup correspondiente al área de contenido
n Ejemplo: Index.tml

<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd"
t:type="Layout" t:title="title" showTitleInBody="false">

<p class="text-center">${message:welcome}</p>

</html>
n Como se verá en las siguientes transparencias, el parámetro title se ha
definido con prefijo por defecto message
n Ejemplo: Index.properties

title=MiniBank welcome page


welcome=Welcome to MiniBank!

9 - 10
Implementación del layout de una aplicación Web: Layout.java (7)

package es.udc.pojo.minibank.web.components;

import org.apache.tapestry5.annotations.Import;

import org.apache.tapestry5.annotations.Parameter;
import org.apache.tapestry5.annotations.Property;

@Import(library = {"tapestry5/bootstrap/js/collapse.js",
"tapestry5/bootstrap/js/dropdown.js"},
stylesheet="tapestry5/bootstrap/css/bootstrap-theme.css")
public class Layout {

@Property
@Parameter(required = true, defaultPrefix = "message")
private String title;

@Parameter(defaultPrefix = "literal")
private Boolean showTitleInBody;

9 - 11
Implementación del layout de una aplicación Web: Layout.java (8)

public boolean getShowTitleInBody() {

if (showTitleInBody == null) {
return true;
} else {
return showTitleInBody;
}

9 - 12
Implementación del layout de una aplicación Web: Layout.java (9)

n Los parámetros de un componente corresponden a los atributos


anotados con @Parameter
n @Parameter sólo se puede usar sobre atributos (no sobre

métodos)
n required: indica si el parámetro es obligatorio u opcional (false
por defecto)
n defaultPrefix: indica el prefijo por defecto
n En el ejemplo, el parámetro showTitleInBody permite
especificar si debe imprimir el título de la página
(parámetro title) al principio del área de contenido
n @Import
n Permite importar JavaScript y CSS
n La inclusión de los correspondientes ficheros ocurre en la fase de
renderización

9 - 13
Implementación del layout de una aplicación Web: Layout.java (10)

n @Import (cont)
n En el ejemplo se está importando JavaScript y CSS de Bootstrap
n collapse.js: requerido para que el menú (.navbar-collapse) se
“encoja” cuando el tamaño del viewport se estrecha

n dropdown.js: requerido por el componente .dropdown

n El tema Bootstrap (bootstrap-theme.css)

9 - 14
Implementación del layout de una aplicación Web: Layout.tml (11)

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd"
xmlns:p="tapestry:parameter">
<head>
<meta charset="utf-8" />
<title>${title} - MiniBank</title>
...
</head>

<body>

<div class="navbar navbar-inverse navbar-fixed-top" role="navigation">


...
</div>

9 - 15
Implementación del layout de una aplicación Web: Layout.tml (12)

<div class="container">

<t:if test="showTitleInBody">
<h3 class="text-center">${title}</h3>
<br/>
</t:if>

<t:body/>

<hr/>

<footer>
<p class="text-center">${message:footer}</p>
</footer>

</div>

</body>

</html>

9 - 16
Implementación del layout de una aplicación Web: Layout.tml (13)

n La plantilla define el layout


n También proporciona contenido para las áreas
comunes
n Menú (.navbar)
n Pie de página (footer)
n Partes variables
n Contenido del tag HTML title
n Opcionalmente el título del contenido
n Contenido
n El elemento <t:body/> es un elemento especial (no es un
componente)
n Tapestry reemplaza <t:body/> por el markup contenido en
el cuerpo del componente

9 - 17
Implementación del layout de una aplicación Web: Layout.properties (y
14)

footer=MiniBank - Area of Telematics Engineering - University of A


Coruña
menu-accounts=Accounts
menu-accounts-addWithdraw=Add/Withdraw
menu-accounts-create=Create
menu-accounts-remove=Remove
menu-accounts-transfer=Transfer
menu-preferences=Preferences
menu-preferences-selectLanguage=Select language
menu-search=Search
menu-search-findAccounts=Find accounts
menu-search-findAccountOperations=Find account operations

9 - 18
Integración con Spring (1)
n Tapestry proporciona una librería de integración con
Spring que permite inyectar beans de Spring en la capa
Web
n web.xml
<?xml version="1.0" encoding="UTF-8"?>

<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"


xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

<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>

9 - 19
Integración con Spring (2)
n web.xml (cont)
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:/pojo-minibank-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>
</web-app>

9 - 20
Integración con Spring (3)
n web.xml (cont)
n Con respecto a la configuración estudiada en el tema 8
n Se ha hecho un cambio específico a Tapestry: reemplazar el
filtro estándar (TapestryFilter) por el filtro
TapestrySpringFilter
n Extiende a TapestryFilter, añadiendo la posibilidad de
inyectar servicios Spring en la capa Web
n Se ha hecho un cambio relativo al uso de Spring con cualquier
framework Web: se ha definido el parámetro
contextConfigLocation
n Especifica la ubicación del fichero de configuración de Spring
(valor por defecto: /WEB-INF/applicationContext.xml)

9 - 21
Integración con Spring (y 4)
n @org.apache.tapestry5.ioc.annotations.Inject
n Permite inyectar un bean de Spring, servicios de Tapestry y
algunos objetos especiales
n Generalmente se aplica sobre un atributo de una clase
página/componente
n 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;

n 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;

9 - 22
Componentes Select, Loop y PageLink (1)

n 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

9 - 23
Componentes Select, Loop y PageLink: FindAccounts.tml (2)

<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd"
t:type="Layout" t:title="title">

<form t:type="Form" class="form-horizontal" t:id="findAccountsForm">

<t:errors/>

<div class="form-group">
<t:label for="id" class="col-md-offset-3 col-md-2"/>
<div class="col-md-3">
<input t:type="TextField" t:id="id"
t:validate="required, min=0" size="16" maxlength="16"/>
</div>
</div>

<div class="form-group">
<t:label for="accountSearchType" class="col-md-offset-3 col-md-2"/>
<div class="col-md-3">
<t:select t:id="accountSearchType" validate="required"/>
</div>
</div>

9 - 24
Componentes Select, Loop y PageLink: FindAccounts.tml (3)

<div class="form-group">
<div class="col-md-offset-5 col-md-1">
<button type="submit" class="btn btn-
primary">${message:button-find}</button>
</div>
</div>

</form>

</html>

9 - 25
Componentes Select, Loop y PageLink: FindAccounts.tml (4)

n Componente Select
n Genera el elemento select de HTML y sus opciones
(elementos option)
n 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)
n En este ejemplo, t:id está haciendo referencia a la propiedad
accountSearchType de la clase página (FindAccounts)
n La propiedad debe tener los métodos get y set para acceder

al valor de la propiedad y establecerlo


n 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
}

9 - 26
Componentes Select, Loop y PageLink: FindAccounts.tml (5)

n Componente Select (cont)


n Para cada opción generada
n El valor lógico (el que se envía por HTTP) es el valor del enumerado
(e.g. ACC_ID)
n 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
n La forma natural de especificar los valores visuales de las opciones
explícitamente consiste en definirlos en el catálogo de mensajes
n 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
n 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>
n Más adelante se ilustra una alternativa para generar las opciones
basada en el uso del parámetro model

9 - 27
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;

9 - 28
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;
}

n Cuando la búsqueda se hace por identificador de cuenta,


FindAccounts redirige el navegador a AccountDetails
n Cuando la búsqueda se hace por identificador de usuario,
FindAccounts redirige el navegador a UserAccounts

9 - 29
Componentes Select, Loop y PageLink: UserAccounts.tml (8)

<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd"
xmlns:p="tapestry:parameter"
t:type="Layout" t:title="title">

<t:if test="accounts">

<!-- Print user accounts. -->

<table class="table table-striped table-hover">

<thead>
<tr>
<th>${message:accountId-label}</th>
<th>${message:balance-label}</th>
</tr>
</thead>

9 - 30
Componentes Select, Loop y PageLink: UserAccounts.tml (9)
<tbody>
<tr t:type="Loop" t:source="accounts" t:value="account">
<td>
<a href="#" t:type="PageLink"
t:page="search/accountdetails"
t:context="account.accountId">
${account.accountId}
</a>
</td>
<td><t:output value="account.balance" format="format"/></td>
</tr>
El componente Output se
</tbody>
explica más adelante como
parte del soporte de
</table> internacionalización que
ofrece Tapestry

9 - 31
Componentes Select, Loop y PageLink: UserAccounts.tml (10)
<!-- "Previous" and "Next" links. -->
<ul class="pager">

<t:if test="previousLinkContext">
<li>
<a href="#" t:type="PageLink" t:page="search/useraccounts"
t:context="previousLinkContext">&larr;
${message:link-previous}</a>
</li>
</t:if>

<li>&nbsp;</li>

<t:if test="nextLinkContext">
<li>
<a href="#" t:type="PageLink" t:page="search/useraccounts"
t:context="nextLinkContext">${message:link-next}
&rarr;</a>
</li>
</t:if>

</ul>

9 - 32
Componentes Select, Loop y PageLink: UserAccounts.tml (11)

<p:else>
<h4 class="alert alert-danger text-center">${message:noAccounts}</h4>
</p:else>

</t:if>

</html>

9 - 33
Componentes Select, Loop y PageLink: UserAccounts.tml (12)

n La plantilla muestra las cuentas contenidas en la


propiedad accounts (de tipo List) de la clase
página (UserAccounts)
n Componente Loop
n Itera sobre una colección de elementos
n En cada iteración genera el markup que contiene su cuerpo
n 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

9 - 34
Componentes Select, Loop y PageLink: UserAccounts.tml (13)

n Componente Loop (cont)


n Parámetros
n source: [prefijo por defecto: prop] especifica la colección de
elementos sobre la que hay que iterar
n value: [prefijo por defecto: prop] elemento actual de la
colección
n En UserAccount.tml, source hace referencia a la
propiedad accounts y value a la propiedad account
n En cada iteración, el componente Loop, 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
n En consecuencia, en cada iteración, la propiedad account
contiene el valor de la cuenta actual

9 - 35
Componentes Select, Loop y PageLink: UserAccounts.tml (14)

n Parámetro context del componente PageLink


n Permite establecer el contexto de activación en el enlace
generado
n Prefijo por defecto: prop
n Ejemplo 1
<a href="#" t:type="PageLink" t:page="search/accountdetails"
context="account.accountId"> ... </a>
n context establece como contexto de activación el
identificador de la cuenta
n Ejemplo 2
<a href="#" t:type="PageLink" t:page="search/useraccounts"
context="previousLinkContext"> ... </a>
n En este caso el contexto de activación necesita dos datos:
userId y startIndex
n context hace referencia a la propiedad
previousLinkContext de UserAccounts, que devuelve un
array con estos dos datos (ésta es la forma de pasar un
contexto de activación que esté formado por más de un dato)
9 - 36
Componentes Select, Loop y PageLink: UserAccounts.java (15)

public class UserAccounts {

private final static int ACCOUNTS_PER_PAGE = 10;


private Long userId;
private int startIndex = 0;
private Block<Account> accountBlock;
private Account account;

@Inject
private AccountService accountService;

@Inject
private Locale locale;

public Long getUserId() {


return userId;
}

public void setUserId(Long userId) {


this.userId = userId;
}

9 - 37
Componentes Select, Loop y PageLink: UserAccounts. java (16)
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;
}
}
9 - 38
Componentes Select, Loop y PageLink: UserAccounts. java (17)
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);
}

9 - 39
Componentes Select, Loop y PageLink: UserAccounts. java (18)

n onPassivate
n Cuando el contexto de activación está formado por más de
un dato, tiene que devolver un Object[]
n onActivate
n 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)
n Componente Grid
n Alternativamente, para mostrar las cuentas, se podría haber
usado el componente Grid
n Permite mostrar un conjunto de items en una tabla
n Soporta una versión avanzada del patrón Page-by-Page
iterator, que incluye enlaces a cada página

9 - 40
Componentes Select, Loop y PageLink (y 19)

n Componente Grid (cont)


n Este componente se ha utilizado para implementar la búsqueda de
operaciones bancarias
n Ver AccountOperationsByDate.{tml, java} en el módulo
pojo-minibank de pojo-examples

,00

n Implementa la lógica de presentación de datos y navegación


n Usa estilos CSS para que se pueda adaptar al look-n-feel de la
aplicación

9 - 41
Evento validate en formularios (1)

n Para entender la necesidad de procesar el evento validate en


formularios, se mostrará la implementación completa del caso
de uso “Eliminar una cuenta” RemoveAccount

Si existe
Si no existe

SuccessfulOperation

RemoveAccount
9 - 42
Evento validate en formularios: RemoveAccount.tml (2)

<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd"
t:type="Layout" t:title="title">

<form t:type="Form" class="form-horizontal" t:id="removeAccountForm”>


<t:errors/>
<div class="form-group">
<t:label for="accountId" class="col-md-offset-3 col-md-2"/>
<div class="col-md-3">
<input t:type="TextField" t:id="accountId"
t:validate="required, min=0" size="16" maxlength="16" />
</div>
</div>
<div class="form-group">
<div class="col-md-offset-5 col-md-1">
<button type="submit" class="btn btn-
primary”>${message:remove}</button>
</div>
</div>
</form>

</html>
9 - 43
Evento validate en formularios: RemoveAccount.tml (3)

n El parámetro t:validate de un componente correspondiente a un


campo de entrada de un formulario sólo sirve para especificar
comprobaciones
n Relativas a ese campo
n Que no necesiten usar un servicio de la capa modelo
n 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 validate
n Cuando llega una petición HTTP correspondiente al botón “Submit” de
un formulario
n Se ejecutan los validadores de los campos (parámetro t:validate en los
campos de entrada)
n Como se dijo en el tema 8, incluso las validaciones que se pueden ejecutar en el
navegador (JavaScript), se vuelven a ejecutar en el lado servidor por seguridad
n El formulario genera el evento validate
n Si hay errores => genera el evento failure; en otro caso => genera el
evento success
n Por defecto, los eventos success y failure redirigen el navegador a la
misma página

9 - 44
Evento validate en formularios: RemoveAccount.java (4)

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;

9 - 45
Evento validate en formularios: RemoveAccount.java (5)

void onValidateFromRemoveAccountForm () {

if (!removeAccountForm.isValid()) {
return;
}

try {
accountService.removeAccount(accountId);
} catch (InstanceNotFoundException e) {
removeAccountForm.recordError(accountIdTextField,
messages.format("error-accountNotFound", accountId));
}

Object onSuccess() {
return SuccessfulOperation.class;
}

9 - 46
Evento validate en formularios: RemoveAccount.java (6)

n @org.apache.tapestry5.annotations.Compo
nent
n Permite definir un componente dentro de otro o de una
página
n id: especifica el identificador del componente (por defecto,
su valor es el nombre del atributo anotado)
n En RemoveAccount, @Component se está utilizando para
definir un formulario (Form) y un campo de entrada
(TextField) dentro de la página
n En ejemplos anteriores esto no fue necesario: los
componentes que necesitaba una página se especificaban en
la propia plantilla
n 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

9 - 47
Evento validate en formularios: RemoveAccount.java (7)

n Evento validate en formularios


n Procesado con el método onValidateFromRemoveAccountForm
n Es preciso añadir el sufijo From<<id formulario>> porque existen
otros componentes en la página que también generan el evento
validate (e.g. TextField)
n 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)
n Si no se produjeron errores, se realizan las validaciones adicionales
necesarias
n 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
n Alternativamente, se podría haber intentado recuperar la cuenta a
partir de su identificador en onValidateFromRemoveAccountForm,
y si existe, borrarla en el método onSuccess

9 - 48
Evento validate en formularios: RemoveAccount.java (8)

n Evento validate en formularios (cont)


n Los errores encontrados se añaden al formulario a través del método
recordError, que recibe
n El campo de entrada al que se quiere asociar el error
n El mensaje de error
n Dado que los mensajes están externalizados en ficheros, para insertar el
mensaje de error se necesita acceder al catálogo de mensajes
n org.apache.tapestry5.ioc.Messages: servicio ofrecido por Tapestry
para acceder al catálogo de mensajes
n Se inyecta en la página con la anotación @Inject (como cualquier otro
servicio)
n 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
n En este caso, se desea que el mensaje contenga el identificador de la cuenta
que no existe
n En app.properties
n error-accountNotFound=%s: account not found
n Notación (inspirada en printf de C): ver JavaDoc de java.util.Formatter

9 - 49
Evento validate en formularios (y 9)

n NOTA: 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
n Obsérvese cómo se ha implementado el método onSuccess en
RemoveAccount
n Dado que RemoveAccount no le tiene que comunicar ningún dato a la
página SuccessfulOperation
n No es necesario inyectar esta página en RemoveAccount
n onSuccess puede devolver SuccessfulOperation.class
n SuccessfulOperation simplemente imprime un mensaje que indica
que la operación se realizó correctamente
n SuccessfulOperation.tml
<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd"
t:type="Layout" t:title="title" showTitleInBody="false">
<h4 class="alert alert-success text-
center">${message:successfulOperation}</h4>
</html>

9 - 50
Internacionalización, Application Module Builder y evento
prepareForRender (1)

n En Java, la internacionalización gira en torno al


concepto de locale
n “Un objeto java.util.Locale representa una región
geográfica, política o cultural” [JavaDoc de la API de Java
SE]
n Una instancia de Locale contiene un código ISO de idioma,
y opcionalmente un código de país y una variante
n Códigos de idiomas: http://www.loc.gov/standards/iso639-
2/php/English_list.php
n Códigos de países:
http://www.iso.ch/iso/country_codes/iso_3166_code_lists/engli
sh_country_names_and_code_elements.htm
n Códigos de variantes: no estandarizados

9 - 51
Internacionalización, Application Module Builder y evento
prepareForRender (2)

n En el tema 8, se estudió un primer aspecto relativo a


la internacionalización: externalizar los mensajes de
aplicación en ficheros .properties (catálogos de
mensajes)
n Cuando se desean soportar “n” idiomas es necesario
n Indicarle a Tapestry la lista de locales soportados
n Tener una versión de cada catálogo de mensajes para cada
locale soportado
n Especificación de locales soportados
n 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
n Para ello es necesario implementar la clase Application
Module Builder
9 - 52
Internacionalización, Application Module Builder y evento
prepareForRender (3)

n Clase Application Module Builder


n 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
n Esta clase tiene que
n Estar definida en el paquete services, hermano de pages y
components
n 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
n En el caso de MiniBank, dado que el filtro se llama app, la clase es
AppModule

9 - 53
Internacionalización, Application Module Builder y evento
prepareForRender (4)

n 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");
configuration.add(
SymbolConstants.JAVASCRIPT_INFRASTRUCTURE_PROVIDER,
"jquery");

}
9 - 54
Internacionalización, Application Module Builder y evento
prepareForRender (5)

n Clase Application Module Builder – versión 1 (cont)


n Los métodos de esta clase siguen convenciones de nombrado
n Para modificar la configuración (“contribuir” en terminología de
Tapestry) de un servicio es necesario implementar el método
contribute<NombreDeServicio>
n 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)
n En el ejemplo, se están añadiendo los parámetros
tapestry.supported-locales y tapestry.javascript-
infrastructure-provider (usando las respectivas constantes)
a la configuración del servicio ApplicationDefaults
n tapestry.javascript-infrastructure-provider
n Tapestry puede usar varios frameworks JavaScript para manipular el
árbol DOM en el navegador, gestionar eventos JavaScript y hacer
peticiones AJAX
n Posibles valores: jquery y prototype (valor por defecto por
compatibilidad con versiones anteriores de Tapestry, pero posiblemente
Tapestry deje de soportarlo en un futuro)

9 - 55
Internacionalización, Application Module Builder y evento
prepareForRender (6)

n Especificación de idiomas soportados


n El valor del parámetro tapestry.supported-locales es un
conjunto de locales (separados por comas, ¡sin espacios en
blanco!)
n Los locales siguen el formato idioma[_pais[_variante]]
n Ejemplos: es (Español), en (Inglés), en_GB (Inglés del Reino Unido),
etc.
n Catálogos de mensajes
n Para cada catálogo de mensajes, una versión para cada idioma
n Fichero[_idioma[_pais[_variante]]].properties
n En MiniBank, por cada fichero .properties existen las
siguientes versiones
n fichero_es.properties: mensajes en Español
n fichero_gl.properties: mensajes en Gallego
n fichero.properties: mensajes en Inglés

9 - 56
Internacionalización, Application Module Builder y evento
prepareForRender (7)

n Selección del locale


n 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
n ¿Cómo hace Tapestry para decidir qué versión del catálogo de
mensajes seleccionar cada vez que procesa una petición HTTP?
n Por defecto, Tapestry realiza la selección automáticamente en base a
la cabecera Accept-Language de la petición HTTP, que incluye
una lista priorizada de locales preferidos por el navegador
n Todos los navegadores permiten
que el usuario especifique esta
lista
n Ejemplo: Firefox -> [Preferencias
| Herramientas -> Opciones] ->
Contenido -> Idiomas

9 - 57
Internacionalización, Application Module Builder y evento
prepareForRender (8)

n Selección del locale (cont)


n Para determinar el locale, Tapestry compara la lista de
locales preferidos que llega en la cabecera Accept-
Language con la lista de locales soportados
(tapestry.supported-locales), y elige, de entre los
soportados, el que “mejor concuerde” con la lista de
preferidos
n 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
n Si no hay ninguno que concuerde, elige el primero de los
locales soportados
n Ejemplo: en el caso de MiniBank, el primer locale especificado
en el parámetro tapestry.supported-locales es en

9 - 58
Internacionalización, Application Module Builder y evento
prepareForRender (9)

n Selección del catálogo de mensajes


n Cuando hay que recuperar un mensaje del catálogo de
mensajes, Tapestry elige el fichero en base al locale
seleccionado
n fichero_locale.properties si existe;
fichero.properties en otro caso
n Formateo de datos en función del locale
n En MiniBank, cuando se imprimen números reales y fechas,
se formatean en base al locale seleccionado
n 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
n Ejemplo 2: 23 de Diciembre de 2008 se formatea como
23/12/08 con locale es y 12/23/08 con locale en

9 - 59
Internacionalización, Application Module Builder y evento
prepareForRender (10)

n Formateo de datos en función del locale: caso de uso


“Buscar las cuentas de un usuario”

locale es locale en

9 - 60
Internacionalización, Application Module Builder y evento
prepareForRender (11)

n Formateo de datos en función del locale: caso de uso


“Buscar las cuentas de un usuario” (cont)
n En UserAccounts.tml (transparencias 30-32)
n El saldo (número real) se imprime con el componente Output
n <t:output value="account.balance" format="format"/>
n Componente Output
n Permite formatear datos
n value: [prefijo por defecto: prop] valor a formatear
n format: [prefijo por defecto: prop] objeto de tipo
java.text.Format
n El paquete java.text de Java SE proporciona clases e interfaces
para tratar datos: números, fechas, etc.
n Format es una clase abstracta que actúa como clase base de los
formateadores de datos

9 - 61
Internacionalización, Application Module Builder y evento
prepareForRender (12)

n Formateo de datos en función del locale: caso de uso “Buscar


las cuentas de un usuario” (cont)
n En UserAccounts.java (transparencias 37-39)
@Inject
private Locale locale;

public Format getFormat() {


return NumberFormat.getInstance(locale);
}
n Es posible utilizar la anotación @Inject para inyectar una
instancia del locale seleccionado por Tapestry para la petición HTTP
actual
n java.text.NumberFormat es una clase abstracta que extiende
a java.text.Format
n 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

9 - 62
Internacionalización, Application Module Builder y evento
prepareForRender (13)

n Entrada de datos en función del locale


n En MiniBank, se permite introducir números reales y fechas en función del
locale seleccionado
n 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)
n Ejemplo 2: 23/12/08 con locale es y 12/23/08 con locale en se interpretan
como 23 de Diciembre de 2008
n NOTA: Tapestry 5.1 mejora el soporte de entrada de datos numéricos para
tener en cuenta la internacionalización. Sin embargo, en los ejemplos se ha
decidido no usar este soporte dado que no funciona bien en todos los casos
n Entrada de datos en función del locale: caso de uso “Crear una cuenta
bancaria”

locale es locale en

9 - 63
Internacionalización, Application Module Builder y evento
prepareForRender (14)

n Entrada de datos en función del locale: caso de uso “Crear una


cuenta bancaria” – CreateAccount.java (cont)
@Property
private Long userId;

@Property
private String balance;

private BigDecimal balanceAsBigDecimal;

@Component
private Form createAccountForm;

@Component(id="balance")
private TextField balanceTextField;

@Inject
private Locale locale;

@Inject
private Messages messages;
9 - 64
Internacionalización, Application Module Builder y evento
prepareForRender (15)

n Entrada de datos en función del locale: caso de uso “Crear una


cuenta bancaria” – CreateAccount.java (cont)
@Inject
private AccountService accountService;

@InjectPage
private AccountCreated accountCreated;

void onValidateFromCreateAccountForm() {

if (!createAccountForm.isValid()) {
return;
}

balanceAsBigDecimal = BigDecimalUtils.parse(balance, locale);

if (balanceAsBigDecimal == null) {
createAccountForm.recordError(balanceTextField,
messages.format("error-incorrectNumberFormat",
balance));
return;
} 9 - 65
Internacionalización, Application Module Builder y evento prepareForRender (16)

n Entrada de datos en función del locale: caso de uso “Crear una


cuenta bancaria” – CreateAccount.java (cont)

Account account =
new Account(userId, balanceAsBigDecimal);

try {
accountService.createAccount(account);
accountCreated.setAccountId(account.getAccountId());

} catch (MaxBalanceExceededException e) {
createAccountForm.recordError(messages.format(
"error-maxBalanceExceededForNewAccount"));
} catch (InsufficientAmountException e) {
createAccountForm.recordError(messages.format(
"error-insufficientBalanceForNewAccount",
BigDecimalUtils.getFormat(locale,
2).format(e.getMinValue())));
}

}
9 - 66
Internacionalización, Application Module Builder y evento
prepareForRender (17)

n Entrada de datos en función del locale: caso de uso “Crear una


cuenta bancaria” – CreateAccount.java (cont)
n La propiedad balance se ha modelado como un String, y no
como un BigDecimal, dado que es necesario interpretar el
significado de los caracteres “.” y “,” en función del locale
seleccionado
n En un BigDecimal/double/float el único carácter admisible es el
“.”, que actúa como separador de decimales
n BigDecimalUtils: forma parte del código de MiniBank y ofrece
dos métodos públicos:
n BigDecimal parse(String value, Locale locale): parsea
value de acuerdo al locale pasado como parámetro y lo devuelve
como un BigDecimal
n Format getFormat(Locale locale, int fractionDigits):
devuelve una instancia de java.text.Format preparada para
formatear un BigDecimal con dos decimales

9 - 67
Internacionalización, Application Module Builder y evento
prepareForRender (18)

n Formateo y parsing de fechas


n Similar al código estudiado para números reales, usando
java.text.DateFormat en lugar de
java.text.NumberFormat
n Se ejemplifica en el caso de uso “Buscar las operaciones que se
hicieron sobre una cuenta entre dos fechas” (véase
AccountOperationsByDate y
FindAccountOperationsByDate)

9 - 68
Internacionalización, Application Module Builder y evento
prepareForRender (19)

n Internacionalización/redefinición de los mensajes generados por


Tapestry
n Dentro del fichero JAR correspondiente a la librería tapestry-
core se encuentran los ficheros
org/apache/tapestry5/core[_xxx].properties
n Contienen los mensajes que imprimen algunos componentes de
Tapestry (e.g. Errors y Grid), así como los mensajes
correspondientes a los errores de validación
n Los mensajes presentes en core.properties están
internacionalizados para varios idiomas
n Pero no están internacionalizados para Gallego
n Y la versión para Español (core_es.properties) contiene
algunos mensajes que no encajan bien en Español/España
n required=Tiene que ingresar un valor para %s.

9 - 69
Internacionalización, Application Module Builder y evento
prepareForRender (20)

n Internacionalización/redefinición de los mensajes generados por


Tapestry (cont)
n En MiniBank, se han incluido versiones para Gallego
(core_gl.properties) y Español (core_es.properties)
n Están en en src/main/resources/org/apache/tapestry5

n No se han incluido todas las claves presentes en core.properties,

sino sólo las que requiere MiniBank


n En consecuencia, los catálogos de mensajes en el WAR generado estarán
en WEB-INF/classes/org/apache/tapestry5
n A la hora de elegir los catálogos de mensajes, tienen preferencia los de
la aplicación frente a los incluidos en tapestry-core, porque en el
CLASSPATH de la aplicación Web tiene preferencia WEB-INF/classes
sobre los JARs presentes en WEB/lib (entre ellos, tapestry-core-
<version>.jar)

9 - 70
Internacionalización, Application Module Builder y evento
prepareForRender (21)

core-default-error-banner

core-goto-page

,00

9 - 71
Internacionalización, Application Module Builder y evento
prepareForRender (22)

min-integer

9 - 72
Internacionalización, Application Module Builder y evento
prepareForRender (23)

n Caso de uso “Selección de idioma”


n 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

9 - 73
Internacionalización, Application Module Builder y evento
prepareForRender (24)

n Caso de uso “Selección de idioma” (cont)


n Requisitos
n Cuando el usuario accede al formulario de selección de idioma,
el campo “Idioma” tiene que mostrar el idioma correspondiente
al idioma actualmente seleccionado
n La lista de idiomas en el desplegable asociado al campo
“Idioma” tiene que estar ordenada alfabéticamente según el
idioma actualmente seleccionado
n 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)

9 - 74
Internacionalización, Application Module Builder y evento
prepareForRender: SelectLanguage.tml (25)

<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd"
t:type="Layout" t:title="title">

<form t:type="Form" class="form-horizontal" t:id="selectLanguageForm">

<div class="form-group">
<t:label for="language" class="col-md-offset-3 col-md-2"/>
<div class="col-md-3">
<t:select t:id="language" model="languages" validate="required"/>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-5 col-md-1">
<button type="submit" class="btn btn-primary">${message:button-
apply}</button>
</div>
</div>

</form>

</html>

9 - 75
Internacionalización, Application Module Builder y evento
prepareForRender: SelectLanguage.tml (26)

n Componente Select
n Anteriormente se describió un uso del componente Select basado
en especificar en t:id el nombre de una propiedad enumerada
n En principio, se podría haber definido un enumerado al estilo
n public enum LanguageType {en, es, gl}
n Y proporcionar los valores visuales en
SelectLanguage[{_es,_gl}].properties
n Aunque en el caso de MiniBank sería una opción perfectamente
asumible, se ha decidido 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
n Esta solución es imprescindible si la lista de elementos del desplegable
es extensa
n 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)

9 - 76
Internacionalización, Application Module Builder y evento
prepareForRender: SelectLanguage.tml (27)

n Componente Select (cont)


n En consecuencia, se ha decidido
n Modelar la propiedad language (valor del parámetro t:id) como un
String
n Usar el parámetro model
n Parámetro model
n Prefijo por defecto: prop
n Entre otros usos, permite definir explícitamente la lista de
elementos a desplegar
n Ejemplo 1: valores lógicos iguales a valores visuales
<t:select t:id="language" model="literal:Español, Gallego, Inglés"/>
n Ejemplo 2: valores lógicos diferentes de valores visuales
<t:select t:id="language"
model="literal:es=Español, gl=Gallego, en=Inglés"/>
n 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

9 - 77
Internacionalización, Application Module Builder y evento
prepareForRender: SelectLanguage.java (28)

public class SelectLanguage {

@Property
private String language;

@Inject
private Locale locale;

@Inject
private PersistentLocale persistentLocale;

void onPrepareForRender() {
language = locale.getLanguage();
}

public String getLanguages() {


return SupportedLanguages.getOptions(locale.getLanguage());
}

9 - 78
Internacionalización, Application Module Builder y evento
prepareForRender: SelectLanguage.java (29)

Object onSuccess() {
persistentLocale.set(new Locale(language));
return Index.class;
}

n Evento prepareForRender
n Un formulario antes de renderizarse, emite el evento
prepareForRender
n En SelectLanguage, se captura este evento
(onPrepareForRender) para dar valor automáticamente al
atributo language
n Se establece el código del idioma correspondiente al locale actual
n Este ejemplo ilustra una solución a un problema muy frecuente:
mostrar un formulario con los valores actualmente seleccionados
n En MiniPortal, ocurre lo mismo en la página UpdateProfile
n 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
9 - 79
Internacionalización, Application Module Builder y evento
prepareForRender: SelectLanguage.java (30)

n org.apache.tapestry5.services.PersistentLocale
n Servicio proporcionado por Tapestry
n Permite establecer el locale mediante el método set
n Una vez invocado el método set, Tapestry codifica el identificador
del locale seleccionado en las URLs
n Formato: http://.../<nombre-aplicación>/<locale>/...
n Ejemplo: http://localhost:9090/pojo-minibank/en/findaccounts

9 - 80
Internacionalización, Application Module Builder y evento
prepareForRender: SupportedLanguages (31)

n SupportedLanguages
n La clase SelectLanguage implementa el método
getLanguages apoyándose en la clase
SupportedLanguages

public String getLanguages() {


return SupportedLanguages.getOptions(locale.getLanguage());
}

n El método getOptions(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

9 - 81
Internacionalización, Application Module Builder y evento
prepareForRender: SupportedLanguages (32)

public class SupportedLanguages {

private static Map<String, String> options;


private static String codes = "";

private SupportedLanguages() {}

public static void initialize() {

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";

9 - 82
Internacionalización, Application Module Builder y evento
prepareForRender: SupportedLanguages (33)

public static String getCodes() {


return codes;
}

public static String getOptions(String languageCode) {

String languages = options.get(languageCode);

if (languages != null) {
return languages;
} else {
return options.get("en");
}

9 - 83
Internacionalización, Application Module Builder y evento
prepareForRender: SupportedLanguages (34)

n SupportedLanguages (cont)
n Y en AppModule (versión 2)
n Versión actual en el código de MiniBank

public class AppModule {

public static void contributeApplicationDefaults(


MappedConfiguration<String, String> configuration) {

SupportedLanguages.initialize();
configuration.add(SymbolConstants.SUPPORTED_LOCALES,
SupportedLanguages.getCodes());
configuration.add(
SymbolConstants.JAVASCRIPT_INFRASTRUCTURE_PROVIDER,
"jquery");

9 - 84
Internacionalización, Application Module Builder y evento
prepareForRender: SupportedLanguages (35)

n SupportedLanguages (cont)
n Actualmente, la clase SupportedLanguages contiene la
lista de idiomas para cada locale soportado
n En un escenario más realista, los códigos de los idiomas y
sus nombres respectivos seguramente se leerían de la BD
n En este caso, el método
SupportedLanguages::initialize crearía
dinámicamente el mapa options en función de los valores
leídos de la BD
n options actuaría como caché de sólo lectura
n Para leer los valores de la BD, sería preciso invocar un servicio
del modelo (e.g. LanguageService)
n Es posible inyectar servicios como parámetros en el método
contributeApplicationDefaults de la clase Application
Module Builder (AppModule en el ejemplo)

9 - 85
Internacionalización, Application Module Builder y evento
prepareForRender: AppModule (y 36)

n Escenario más realista (AppModule – versión 3)


public class AppModule {

public static void contributeApplicationDefaults(


MappedConfiguration<String, String> configuration,
LanguageService languageService) {

SupportedLanguages.initialize(languageService);
configuration.add(SymbolConstants.SUPPORTED_LOCALES,
SupportedLanguages.getCodes());
configuration.add(
SymbolConstants.JAVASCRIPT_INFRASTRUCTURE_PROVIDER,
"jquery");

9 - 86
Tratamiento de excepciones “unchecked” no esperadas (1)

n ¿Qué ocurre cuando se produce una excepción de


tipo “unchecked” no esperada (hijas de Runtime o
Error)?
n 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
n 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
n Cuando Tapestry captura una excepción de este tipo, pasa el
control a la página ExceptionReport
n Paquete: org.apache.tapestry5.corelib.pages
n Como cualquier página dispone de una clase
(ExceptionReport) y una plantilla
(ExceptionReport.tml)
n No dispone de catálogo de mensajes

9 - 87
Tratamiento de excepciones “unchecked” no esperadas (2)

n ExceptionReport.tml consulta la propiedad


productionMode para decidir el nivel de información a
mostrar
n La propiedad productionMode es boolena y corresponde al
parámetro de configuración tapestry.production-mode
del servicio ApplicationDefaults
n true [valor por defecto]: la aplicación está en modo producción

n false: la aplicación está en modo desarrollo

n 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

9 - 88
Tratamiento de excepciones “unchecked” no esperadas (3)

n 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");

n 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)
n 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>
9 - 89
Tratamiento de excepciones “unchecked” no esperadas (4)

Modo desarrollo
Modo
producción

9 - 90
Tratamiento de excepciones “unchecked” no esperadas (5)

n En MiniBank se ha creado una versión de


ExceptionReport.tml que
n Es una copia de la versión original incluida en el JAR de
Tapestry, pero a diferencia de ella
n [Cualquier modo] Muestra “MiniBank: Error interno” como
contenido del tag title (internacionalizado)
n [Modo producción] Un aviso de error interno
(internacionalizado) en el cuerpo de la página
n [Modo desarrollo] Muestra la misma información detallada que
la versión original
n Código
n ExceptionReport.tml y
ExceptionReport*.properties en
src/main/resources/org/apache/tapestry5/core
lib/pages

9 - 91
Tratamiento de excepciones “unchecked” no esperadas (y 6)

Modo desarrollo
Modo
producción

9 - 92
[Recordatorio] Uso del DataSource proporcionado por el servidor de aplicaciones

n Como se comentó en el tema 4, Spring ofrece soporte para acceder al


DataSource proporcionado por el servidor de aplicaciones
n Habitualmente estos DataSources implementan la estrategia de pool de
conexiones
n En src/main/resources/pojo-minibank-spring-config.xml

...

<bean id="dataSource"
class="org.springframework.jndi.JndiObjectFactoryBean"
p:jndiName="${dataSource.jndiName}"
p:resourceRef="true" />

<bean id="sessionFactory"
class=
"org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"
p:dataSource-ref="dataSource"
p:configLocation="classpath:/pojo-minibank-hibernate-config.xml"/>

...
9 - 93
Habilitación del patrón Open Session in View (1)

n Spring ofrece un filtro,


OpenSessionInViewFilter, que permite usar el
patrón Open Sesion in View
n Para cada petición HTTP que intercepta
n Abre una sesión de Hibernate
n Dejar fluir la petición
n Cierra la sesión de Hibernate

9 - 94
Habilitación del patrón Open Session in View: web.xml (2)

...
<filter>
<filter-name>openSessionInView</filter-name>
<filter-class>
org.springframework.orm.hibernate5.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>

9 - 95
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>

...

9 - 96
Habilitación del patrón Open Session in View: web.xml (4)

n OpenSessionInViewFilter
n Los filtros pueden tener parámetros de configuración (tags
init-param)
n Entre otros, dispone del parámetro
sessionFactoryBeanName
n Indica el nombre del bean SessionFactory declarado en el
fichero de configuración de Spring
n 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)
n Encadenamiento de filtros
n El orden en el que aparecen los tags filter-mapping en
web.xml determina el orden en el que se aplicarán los
filtros

9 - 97
Habilitación del patrón Open Session in View: web.xml (5)

n Encadenamiento de filtros (cont)


n En el ejemplo, para cualquier petición HTTP, primero se
aplicará OpenSessionInViewFilter y posteriormente
TapestrySpringFilter
n Procesamiento de una petición HTTP
n [OpenSessionInViewFilter] Abre la sesión
n [OpenSessionInViewFilter] Dejar fluir la petición
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)
n [OpenSessionInViewFilter] Cierra la sesión
n Obsérvese que este orden de encadenamiento (y no
el contrario) es obligatorio para que funcione el
patrón Open Session in View

9 - 98
Habilitación del patrón Open Session in View: web.xml (6)

n Problema: con el mapping del ejemplo, todas las peticiones


HTTP pasan por OpenSessionInViewFilter
n <url-pattern>/*</url-pattern>
n Como parte de cada petición HTTP se abre una sesión de Hibernate
n La sesión de Hibernate consume una conexión del DataSource
n 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)
n Solución 1
n Especificar que sólo pasen por OpenSessionInViewFilter 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)
n Inconveniente: hay que especificar tantos mappings (filter-
mapping) como peticiones tengan que pasar por
OpenSessionInViewFilter o hacer que todas sigan un patrón
común en el nombre de sus URLs (e.g. mismo prefijo o sufijo)

9 - 99
Habilitación del patrón Open Session in View: web.xml (7)

n Solución 2
n 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
n En src/main/resources/pojo-minibank-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:/pojo-minibank-hibernate-config.xml"/>

9 - 100
Habilitación del patrón Open Session in View: web.xml (y 8)

n Solución 2 (cont)
n 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
n pojo-minibank y pojo-miniportal
n El código actual no requiere que el patrón Open Session in
View esté habilitado
n pojo-mini{bank,portal}-spring-config.xml y
web.xml incluyen secciones comentadas para facilitar la
habilitación de este patrón en caso necesario
n pojo-advhibernatetut
n pojo-advhibernatetut-spring-config.xml y
web.xml habilitan el patrón Open Session in View

9 - 101
Minimización, compresión y caché
n Minimización
n MiniBank y MiniPortal incluyen la librería tapestry-webresources entre
sus dependencias
n Cuando se incluye esta librería y el valor de tapestry.production-
mode es true, Tapestry minimiza los recursos CSS y JavaScript
n Compresión
n Tapestry comprime las respuestas automáticamente mediante GZIP si el
cliente lo solicita (que es lo habitual) y tiene sentido
n No realiza compresión si la respuesta es muy pequeña o si el recurso solicitado ya
estaba comprimido (e.g. una imagen PNG)
n Caché de recursos estáticos
n Los recursos estáticos (CSS, JavaScript, imágenes, etc.) se deben
referenciar en las plantillas con el prefijo asset o context
n <img src="${context:images/tapestry_banner.gif}"
alt="Banner"/>
n En las respuestas, Tapestry indicará que son cachebles
n Más información
n http://tapestry.apache.org/assets.html
n http://tapestry.apache.org/response-compression.html

9 - 102

S-ar putea să vă placă și