4.5 caso de estudio: diseño e implementación de las capas ... · n los ejemplos acceden a los...

Post on 27-Oct-2018

216 Views

Category:

Documents

0 Downloads

Preview:

Click to see full reader

TRANSCRIPT

4.5 Caso de estudio: diseño e implementación de las capas controlador y vista de MiniBank con JSTL

y Jakarta Struts. Patrones usados

Introducción (1)

Introducción (2)

n En este apartado estudiaremos el diseño e implementación de las capas controlador y vista de MiniBank, fijándonos en unos cuantos problemas representativosn Acceso a objetos javax.sql.DataSourcen Un ejemplo de aplicación de un caso particular del patrón

Composite Viewn Dotar de una misma estructura a un conjunto de páginas

n Un ejemplo de una acción que realiza una operación que no visualiza resultadosn Transferencia bancaria

n Un ejemplo de una acción que realiza una operación y visualiza el resultado de la operaciónn Búsqueda de cuentas bancarias por identificador de cuenta (resultado

en una página) o de usuario (resultado en varias páginas)

n Algo más sobre internacionalizaciónn Formateo de balances y fechas según el idioma y paísn Selección de idioma (Gallego, Español e Inglés) y país (España, USA)

Introducción (y 3)

n Orden de exposiciónn Arquitectura global del controlador y la vista

n Estructura de paquetesn PlainMiniBank.war

n web.xml

n Extensiones a es.udc.fbellas.j2ee.util.struts

n Plantillas (caso particular del patrón Composite View)n Transferencia bancarian Búsqueda de cuentasn Selección de idioma y paísn Otros aspectos

Estructura de paqueteses.udc.fbellas.j2ee.util

es.udc.fbellas.j2ee.minibank

http

controller

actions

model

view

actionforms

messages

frontcontroller

applicationobjects

requestobjects

es.udc.fbellas.j2ee.minibank.http.view.actionforms

DefaultActionForm

(from action)

AddWithdrawForm

CreateAccountForm

FindAccountOperationsByDateForm

FindAccountsForm RemoveAccountForm

SelectLocaleForm

TransferForm

es.udc.fbellas.j2ee.minibank.http.controller.actions

DefaultAction

(from action)

AddWithdrawAction

CreateAccountAction

EditLocaleAction

FindAccountOperationsByDateAction

FindAccountsAction

RemoveAccountAction

SelectLocaleAction

TransferAction

jar tvf PlainMiniBank.war (1)DefaultFooter.jsp

DefaultHeader.jsp

...

AccountOperationsByDateContent.jsp

SelectLocaleContent.jspWEB-INF/Struts/struts-html.tld

WEB-INF/Struts/struts-template.tld

WEB-INF/Struts/plain-struts-config.xml

WEB-INF/StdTagLibs/fmt.tld

WEB-INF/StdTagLibs/c.tld

WEB-INF/Database/MySQLCreateTables.sqlWEB-INF/Database/PostgreSQLCreateTables.sql

WEB-INF/lib/standard.jar

WEB-INF/lib/jstl.jar

WEB-INF/lib/jaxen-full.jar

WEB-INF/lib/saxpath.jar

WEB-INF/lib/struts.jarWEB-INF/lib/commons-*.jar

WEB-INF/lib/<<Driver JDBC>>

WEB-INF/lib/StandardUtil.jar

WEB-INF/lib/WebUtil.jar

jar tvf PlainMiniBank.war (y 2)WEB-INF/classes/es/udc/fbellas/j2ee/minibank/model/accountoperation/

dao/SQLAccountOperationDAO.class

WEB-INF/classes/es/udc/fbellas/j2ee/minibank/model/accountoperation/

dao/AbstractSQLAccountOperationDAO.class

...WEB-INF/classes/es/udc/fbellas/j2ee/minibank/http/controller/actions/

EditLocaleAction.class

WEB-INF/classes/es/udc/fbellas/j2ee/minibank/http/controller/

frontcontroller/FrontController.class

WEB-INF/classes/es/udc/fbellas/j2ee/minibank/http/view/messages/

Messages.propertiesWEB-INF/classes/es/udc/fbellas/j2ee/minibank/http/view/messages/

Messages_es.properties

WEB-INF/classes/es/udc/fbellas/j2ee/minibank/http/view/messages/

Messages_gl.properties

WEB-INF/web.xml

web.xml

...

<resource-ref>

<res-ref-name>jdbc/J2EE-ExamplesDS</res-ref-name>

<res-type>javax.sql.DataSource</res-type>

<res-auth>Container</res-auth></resource-ref>

...

<env-entry>

<env-entry-name>AccountFacadeDelegateFactory/delegateClassName</env-entry-name>

<env-entry-value>es.udc.fbellas.j2ee.minibank.model.accountfacade.

plain.PlainAccountFacadeDelegate</env-entry-value>

<env-entry-type>java.lang.String</env-entry-type>

</env-entry>

...

resource-ref

n Como se explicó en el tema 2, dentro de un contenedor de aplicaciones web o de EJBs, es posible definir objetos DataSource y localizarlos por JNDIn Normalmente implementan pool de conexionesn Requiere configuración en el contenedor (driver, URL,

nombre, contraseña, parámetros específicos al pool, etc.)n Ej.: En Tomcat se definen en conf/server.xml

n Se acceden vía JNDI en el contexto java:comp y se recomienda declararlos bajo el subcontexto jdbcn El DataSource del ejemplo se accedería con el nombre

JNDI java:comp/jdbc/J2EE-ExamplesDS

n Los ejemplos acceden a los objetos DataSource vía es.udc.fbellas.j2ee.util.sql.DataSourceLocator

env-entry (1)

n Como se explicó en el tema 2, se pretende que la configuración de aplicaciones J2EE esté disponible en servidores de información accesibles vía JNDI, y no en ficheros planosn Especialmente importante en clusters

n Salvo para configuraciones muy complejas, los parámetros de configuración que necesiten las clases que conforman una aplicación web deben estar especificados en web.xml mediante tags env-entry

env-entry (2)

n Cuando el servidor de aplicaciones web arranca, pone disponible vía JNDI la información de configuración especificada por los tags env-entryn La información residirá bajo el contexto java:comp/envn La implementación del servidor de aplicaciones web puede

usar cualquier tipo de servicio accesible vía JNDI para mantener la información (LDAP, uno propietario, etc.)

n Ejemplo de acceso a un parámetro de configuraciónContext context = new javax.naming.InitialContext();

String generatorClassName =

(String) context.lookup("java:comp/env/" +

" AccountFacadeDelegateFactory/delegateClassName");

env-entry (3)

n Como vimos en el tema 2, para no depender de la fuente de la que se leen los parámetros de configuración, todas las clases acceden a los parámetros mediante

ConfigurationParametersManager

<<stat ic>> - parameters : Map

- ConfigurationParametersManager()<<static>> + getParameter(name : String) : String

(en es.udc.fbellas.j2ee.util.configuration)

n Permite leer los parámetros de configuración del fichero ConfigurationParameters.properties o vía JNDI

n ConfigurationParameters.properties sólo debería usarse en tests de unidad desde la línea de comandos

env-entry (y 4)

n ConfigurationParametersManagern Incluye bloque static para tratar de leer ConfigurationParameters.properties

n Si lo puede leer, inserta los parámetros en el mapa parameters

n Si no lo puede leer, asume configuración vía JNDIn getParameter comprueba si el parámetro está en el mapa

n En caso afirmativo, lo devuelven En caso negativo, lo recupera vía JNDI (prefijándole java:comp/env/ al nombre pasado como parámetro) y lo deja cacheado en el mapa parameters

es.udc.fbellas.j2ee.util.struts.action.DefaultActionForm: Motivación (1)

n java.util.Localen Un objeto Locale representa una asociación idioma y país

(y opcionalmente una variante), que se puede usar paran Imprimir correctamente fechas, números, monedas, etc. según

el Locale seleccionadon 19/4/02 en Español/España es 4/19/02 en Inglés/USAn Ej.: 12.345,67 en Español/España es 12,345.67 en Inglés/USA

n Aceptar entrada de datos sensible al idioma y paísn 12.345,67 ó 12345,67 en Español/España son correctos (e

iguales) como doubles

n Seleccionar el fichero de mensajes en las aplicaciones que usan ficheros de mensajes

n Struts mantiene una instancia de Locale en la sesiónn Atributo org.apache.struts.Globals.LOCALE_KEYn Cada usuario conectado a la aplicación web puede tener un Locale distinto

es.udc.fbellas.j2ee.util.struts.action.DefaultActionForm: Motivación (y 2)

n Típicamente es necesario acceder al Locale del usuario en los ActionForms y en las accionesn org.apache.struts.action.Action dispone del

método getLocale(HttpServletRequest)n org.apache.struts.action.ActionForm no dispone

de este método

n es.udc.fbellas.j2ee.util.action.DefaultActionFormn Por comodidad, define el método getLocale(HttpServletRequest)

n Todos los ActionForms derivarán de DefaultActionForm

es.udc.fbellas.j2ee.util.struts.action.DefaultActionForm

public abstract class DefaultActionForm extends ActionForm {

protected Locale getLocale(HttpServletRequest request) {

HttpSession session = request.getSession(false);

if (session == null) {return Locale.getDefault();

}

Locale locale = (Locale) session.getAttribute(Globals.LOCALE_KEY);

if (locale == null) {return Locale.getDefault();

} else {return locale;

}

}

}

Plantillas (1)

n En MiniBank, todas las páginas JSP tienen un mismo layout (disposición)

Título

Cabecera

Lista de enlaces Contenido

Pie de página

Plantillas (2)

n La cabecera, la lista de enlaces y el pie de página son iguales en todas las páginas, pero podrían ser distintos

n En principio, todas las páginas JSP podrían tener esta estructuran Títulon Tabla (ancho = 100%) con una fila y una columna, que hace

un <%@ include file=“DefaultHeader.jsp”>

n Tabla (ancho = 100%) con una fila y dos columnasn Columna 1: ancho = 17%, alto=470, <%@ includefile=“DefaultSidebar.jsp”>

n Columna 2: ancho = 83%, alto=470, contenido propio

n Tabla (ancho = 100%) con una fila y una columna, que hace un <%@ include file=“DefaultFooter.jsp”>

Plantillas (3)

n Probleman Una vez en producción, se decide cambiar el layout

Título

Cabecera

Lista de enlaces

Contenido

Pie de página

n Ahora la segunda tabla tiene dos filas y una columnan ¡ Necesitamos cambiar todas las páginas JSP !

Plantillas (y 4)

n ¿ Cuál es la causa del problema ?n Se ha hecho copy-n-paste del layout en cada página JSP

n ¿ Cuál es la solución ?n Tags JSP que permitan

n Definir una plantilla (template), es decir, un layout parametrizable

n Construir instancias de la plantilla

n Caso particular del patrón Composite View (Core J2EE Patterns)

n Struts proporciona un sencillo conjunto de tags que permite definir e instanciar plantillas

n En una aplicación grande, normalmente habrá varias plantillas

DefaultTemplate.jsp (1)<%-- Struts tag libraries. --%>

<%@ taglib prefix="fmt" uri="http://java.sun.com/jstl/fmt" %><%@ taglib prefix="template" uri="/struts-template.tld" %><%@ taglib prefix="html" uri="/struts-html.tld" %>

<%-- Start of HTML. --%>

<html:html locale="true">

<fmt:setLocalevalue='${sessionScope["org.apache.struts.action.LOCALE"]}'scope="session"/>

<%-- HTML header. --%>

<head><title><template:get name="title"/>

</title><meta http-equiv="Content-Type“

content="text/html; charset=iso-8859-1"></head>

DefaultTemplate.jsp (2)<%-- Start of HTML body. --%>

<body text="#000000" bgcolor="#ffffff" link="#000ee0" vlink="#551a8b"

alink="#000ee0">

<%-- Body header. --%>

<table width="100%" border="0">

<tr>

<td>

<template:get name="header"/></td>

</tr>

</table>

<hr>

DefaultTemplate.jsp (3)<%-- Sidebar and body content. --%>

<table width="100%" border="0">

<tr valign="top">

<td width="17%" height="470" bgcolor="#CCFFCC"> <template:get name="sidebar"/>

</td>

<td width="83%" height="470">

<table width="100%" border="0">

<tr valign="middle">

<td height="470"><template:get name="content"/>

</td>

</tr>

</table>

</td>

</tr></table>

<hr>

DefaultTemplate.jsp (y 4)<%-- Body footer. --%>

<table width="100%" border="0">

<tr>

<td><template:get name="footer"/>

</td>

</tr>

</table>

<%-- End of HTML body. --%>

</body>

<%-- End of HTML. --%>

</html:html>

Welcome.jsp<%@ taglib prefix="fmt" uri="http://java.sun.com/jstl/fmt" %>

<%@ taglib prefix="template" uri="/struts-template.tld" %>

<template:insert template="DefaultTemplate.jsp">

<template:put name="title" direct="true"><fmt:message key="Welcome.title"/>

</template:put>

<template:put name="header" content="DefaultHeader.jsp" />

<template:put name="sidebar" content="DefaultSidebar.jsp" />

<template:put name="content" direct="true">

<div align="center"><font face="Arial, Helvetica, sans-serif" size="+3"

color="#000099">

<fmt:message key="Welcome.welcome"/></font>

</div>

</template:put>

<template:put name="footer" content="DefaultFooter.jsp" />

</template:insert>

Comentarios (1)

n DefaultTemplate.jsp define y parametriza la plantilla mediante tags template:get

n Welcome.jsp usa el tag template:insert para instanciaruna página JSP (en tiempo de ejecución) a partir de la plantilla, especificando el contenido de cada una de sus partes (tag template:put)

n Es importante observar que los tags de HTML: html, head y body sólo aparecen en la plantillan Si apareciesen en el resto de páginas, las páginas resultantes

serían incorrectas en HTMLn <html:html locale="true">

n Aparte de generar el correspondiente tag HTML, inserta el Localeen la sesión (si no lo estaba ya, y creando la sesión si es preciso) en base a la cabecera Accept-Language de la petición HTTPn Los navegadores se pueden configurar para que envíen en las

peticiones HTTP una lista de idiomas (en orden de preferencia)

Comentarios (y 2)

n fmt:setLocalen Permite fijar un Localen En el código se usa para fijar el Locale que usa JSTL al

mismo valor que usa Strutsn Véase el código fuente de DefaultTemplate.jsp en J2EE-

Examples para más información

n Este truco dejará de ser necesario cuando Struts esté integrado con JSTL

n Tiles (Struts 1.1)n Versión mejorada de la librería “template”n Permite definir “pantallas” en XML (con herencia)n Evita la típica necesidad de escribir “dos páginas JSP por

cada pantalla” (ver más adelante)

Demo transferencia (1)

Demo transferencia (y 2)

Implementación transferencia

n Formularion Transfer.jsp: instancia la plantillan TransferContent.jsp: sección content

n Convenio seguido en los ejemplos para formularios y páginas de visualización de resultadosn XXX.jsp (instancia plantilla) y XXXContent.jsp (sección content),

excepto que la sección content sea muy pequeña (ej.: Welcome.jsp)

n ActionFormn es.udc.fbellas.j2ee.minibank.http.view.actionforms

.TransferForm

n Acciónn es.udc.fbellas.j2ee.minibank.http.controller.actio

ns.TransferAction

n Usa la fachada del modelo para realizar la transferencian Si error => forward a Transfer.jspn En otro caso => sendRedirect a SuccessfulOperation.jsp

Transfer.jsp<%@ taglib prefix="fmt" uri="http://java.sun.com/jstl/fmt" %>

<%@ taglib prefix="template" uri="/struts-template.tld" %>

<template:insert template="DefaultTemplate.jsp">

<template:put name="title" direct="true"><fmt:message key="Transfer.title"/>

</template:put>

<template:put name="header" content="DefaultHeader.jsp" />

<template:put name="sidebar" content="DefaultSidebar.jsp" />

<template:put name="content" content="TransferContent.jsp"/>

<template:put name="footer" content="DefaultFooter.jsp" /></template:insert>

TransferContent.jsp (1)<%@ taglib prefix="fmt" uri="http://java.sun.com/jstl/fmt" %><%@ taglib prefix="html" uri="/struts-html.tld" %>

<div align="center"><html:errors property="org.apache.struts.action.GLOBAL_ERROR"/></div>

<p><p>

<html:form action="Transfer.do" focus="sourceAccountIdentifier">

<table border="0" width="100%" align="center" cellspacing="12">

<tr><th align="right" width="50%">

<fmt:messagekey="TransferContent.sourceAccountIdentifier"/>

</th><td align="left" width="50%">

<html:text property="sourceAccountIdentifier" size="16" maxlength="16"/>

<html:errors property="sourceAccountIdentifier"/></td>

</tr>

TransferContent.jsp (y 2)<tr>

<th align="right" width="50%"><fmt:message

key="TransferContent.destinationAccountIdentifier"/></th><td align="left" width="50%">

<html:text property="destinationAccountIdentifier“size="16" maxlength="16"/>

<html:errors property="destinationAccountIdentifier"/></td>

</tr>

<tr><th align="right" width="50%">

<fmt:message key="AccountAttributes.amount"/></th><td align="left" width="50%">

<html:text property="amount" size="16" maxlength="16"/><html:errors property="amount"/>

</td></tr>

...

es.udc.fbellas.j2ee.minibank.http.view.actionforms.TransferForm (1)

public class TransferForm extends DefaultActionForm {

private String sourceAccountIdentifier;

private Long sourceAccountIdentifierAsLong;

private String destinationAccountIdentifier;private Long destinationAccountIdentifierAsLong;

private String amount;

private double amountAsDouble;

public TransferForm() {

reset();}

public String getSourceAccountIdentifier() {

return sourceAccountIdentifier;

}

public Long getSourceAccountIdentifierAsLong() {

return sourceAccountIdentifierAsLong;

}

es.udc.fbellas.j2ee.minibank.http.view.actionforms.TransferForm (2)

public void setSourceAccountIdentifier(

String sourceAccountIdentifier) {

this.sourceAccountIdentifier = sourceAccountIdentifier.trim();

}

public String getDestinationAccountIdentifier() {

return destinationAccountIdentifier;

}

public Long getDestinationAccountIdentifierAsLong() {

return destinationAccountIdentifierAsLong;}

public void setDestinationAccountIdentifier(

String destinationAccountIdentifier) {

this.destinationAccountIdentifier =destinationAccountIdentifier.trim();

}

es.udc.fbellas.j2ee.minibank.http.view.actionforms.TransferForm (3)

public String getAmount() {

return amount;

}

public double getAmountAsDouble() {

return amountAsDouble;

}

public void setAmount(String amount) {

this.amount = amount.trim();}

public void reset(ActionMapping mapping,

HttpServletRequest request) {

reset();

}

public ActionErrors validate(ActionMapping mapping,

HttpServletRequest request) {

ActionErrors errors = new ActionErrors();

es.udc.fbellas.j2ee.minibank.http.view.actionforms.TransferForm (y 4)

sourceAccountIdentifierAsLong = new Long(PropertyValidator.validateLong(errors,

"sourceAccountIdentifier", sourceAccountIdentifier,true, 1, Long.MAX_VALUE));

destinationAccountIdentifierAsLong = new Long(PropertyValidator.validateLong(errors,

"destinationAccountIdentifier",destinationAccountIdentifier, true, 1, Long.MAX_VALUE));

amountAsDouble = PropertyValidator.validateDouble(errors,"amount", amount, true, 0.01, Double.MAX_VALUE,getLocale(request));

return errors;

}

private void reset() {sourceAccountIdentifier = null;sourceAccountIdentifierAsLong = null;destinationAccountIdentifier = null;destinationAccountIdentifierAsLong = null;amount = null;

}

}

Comentarios (1)

n Se ha usado String como tipo en los métodos getXXX/setXXX para las propiedades sourceAccountIdentifier, destinationAccountIdentifier y amountn En general, se debe hacer así cuando el usuario teclea la

entradan Si se hubiesen utilizado los tipos lógicos (Long, Long y double), y el usuario teclea un número en formato no apropiado para los correspondientes editores de propiedades (ej.: números erróneos, blancos al principio, blancos al final, signos separadores de miles y decimales, etc.), la invocación del método setXXX haría que la propiedad tomase valor 0

n Se han definido métodos getXXXAsYYY para facilitar la implementación de las acciones

Comentarios (y 2)

n Se permite usar los separadores de miles y decimales (según el idioma y país seleccionado) en el campo amountn En validate se usa PropertyValidator.validateDouble para el campo amount

n PropertyValidator.validateDouble usa java.text.NumberFormat para parsear el String

NumberFormat numberFormatter = NumberFormat.getNumberInstance(locale);

propertyValueAsDouble =

numberFormatter.parse(propertyValue).doubleValue();

es.udc.fbellas.j2ee.minibank.http.controller.actions.TransferAction (1)

public class TransferAction extends DefaultAction {

public ActionForward doExecute(ActionMapping mapping,ActionForm form, HttpServletRequest request,HttpServletResponse response)throws IOException, ServletException, InternalErrorException {

/* Get data. */TransferForm transferForm = (TransferForm) form;Long sourceAccountIdentifier =

transferForm.getSourceAccountIdentifierAsLong();Long destinationAccountIdentifier =

transferForm.getDestinationAccountIdentifierAsLong();double amount = transferForm.getAmountAsDouble();

/* Transfer. */ActionErrors errors = new ActionErrors();

try { AccountFacadeDelegateFactory.getDelegate().transfer(

sourceAccountIdentifier, destinationAccountIdentifier,amount);

es.udc.fbellas.j2ee.minibank.http.controller.actions.TransferAction (2)

} catch (InstanceNotFoundException e) {

Long key = (Long) e.getKey();

if (key.equals(sourceAccountIdentifier)) {errors.add("sourceAccountIdentifier",

new ActionError("ErrorMessages.account.notFound"));

} else {

errors.add("destinationAccountIdentifier",

new ActionError("ErrorMessages.account.notFound"));

}

} catch (InsufficientBalanceException e) {

NumberFormat numberFormatter =

NumberFormat.getNumberInstance(getLocale(request));

errors.add(ActionErrors.GLOBAL_ERROR, new ActionError("ErrorMessages.balance.insufficientBalanceInSource",

numberFormatter.format(e.getCurrentBalance())));

}

es.udc.fbellas.j2ee.minibank.http.controller.actions.TransferAction (y 3)

/* Return ActionForward. */

if (errors.isEmpty()) {

return mapping.findForward("Success");

} else {saveErrors(request, errors);

return new ActionForward(mapping.getInput());

}

}

}

Comentarios

n Cuando se detecta un error que no es específico a un campo del formulario, sino a la ejecución de la acción, se usa ActionErrors.GLOBAL_ERRORcomo nombre de propiedad en el ActionErrors

Demo búsqueda de cuentas (1)

Demo búsqueda de cuentas (2)

Demo búsqueda de cuentas (3)

Demo búsqueda de cuentas (y 4)

Implementación búsqueda de cuentas

n Formularion FindAccounts.jspn FindAccountsContent.jsp

n ActionFormn es.udc.fbellas.j2ee.minibank.http.view.actionforms.FindAccountsForm

n Acciónn es.udc.fbellas.j2ee.minibank.http.controller.actions.FindAccountsAction

n Usa la fachada del modelo para realizar una búsqueda por identificador de cuenta (una cuenta) o por identificador de usuario (una colección de cuentas)

n Si error => forward a FindAccounts.jspn En otro caso, deja el resultado en la request y hace un forward a AccountDetails.jsp o UserAccounts.jsp

FindAccountsContent.jsp (1)<%@ taglib prefix="fmt" uri="http://java.sun.com/jstl/fmt" %>

<%@ taglib prefix="html" uri="/struts-html.tld" %>

<html:form action="FindAccounts.do" focus="identifier">

<table border="0" width="100%" align="center" cellspacing="12">

<html:hidden property="startIndex" value="1"/>

<html:hidden property="count" value="10"/>

<tr><th align="right" width="50%">

<fmt:message key="FindAccountsContent.identifier"/>

</th>

<td align="left" width="50%">

<html:text property="identifier" size="16" maxlength="16"/>

<html:errors property="identifier"/></td>

</tr>

FindAccountsContent.jsp (y 2)<tr>

<th align="right" width="50%"><fmt:message key="FindAccountsContent.findBy"/>

</th><td align="left" width="50%">

<html:select property="identifierType"><html:option value="ACC_ID">

<fmt:messagekey="AccountAttributes.accountIdentifier"/>

</html:option><html:option value="USR_ID">

<fmt:messagekey="AccountAttributes.userIdentifier"/>

</html:option></html:select><html:errors property="identifierType"/>

</td></tr>

...

Comentarios

n html:hiddenn Genera un tag input de tipo hidden en HTML (no es

visible al usuario)n Ej.: <input type="hidden" name="startIndex" value="1">

n En el ejemplo se usan para dar valor a los parámetros startIndex y count, necesarios para realizar búsquedas de cuentas por identificador de usuarion La primera pantalla de resultados empezará en el índice 1 y

tendrá 10 resultados como máximo

n html:select y html:option generan los tags HTML select y option, con el valor actualmente seleccionado (property="identifierType")

es.udc.fbellas.j2ee.minibank.http.view.actionforms.FindAccountsForm

n startIndex y count no se han definido como Strings dado que el usuario no los tecleará

FindAccountsForm

- identifier : String

- identifierAsLong : Long

- identifierType : String

- startIndex : int

- count : int

+ FindAccountsForm()

+ getIdentifier() : String

+ getIdentifierAsLong() : Long

+ setIdentifier(identifier : String) : void

+ getIdentifierType() : String

+ setIdentifierType(identifierType : String) : void

+ getStartIndex() : int

+ setStartIndex(startIndex : int) : void

+ getCount() : int

+ setCount(count : int) : void

+ reset(mapping : ActionMapping, request : HttpServletRequest) : void

+ validate(mapping : ActionMapping, request : HttpServletRequest) : ActionErrors

- reset() : void

AccountDetailsContent.jsp (1)<%@ taglib prefix="fmt" uri="http://java.sun.com/jstl/fmt" %><%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %><%@ taglib prefix="html" uri="/struts-html.tld" %>

<table width="35%" border="1" align="center" cellspacing="6">

<tr><th align="left">

<fmt:message key="AccountAttributes.accountIdentifier"/></th><td align="left">

<c:out value="${requestScope.account.accountIdentifier}"/></td>

</tr>

<tr><th align="left">

<fmt:message key="AccountAttributes.userIdentifier"/></th><td align="left">

<c:out value="${requestScope.account.userIdentifier}"/></td>

</tr>

AccountDetailsContent.jsp (y 2)<tr>

<th align="left">

<fmt:message key="AccountAttributes.balance"/>

</th>

<td align="left"><fmt:formatNumber value="${requestScope.account.balance}"/>

</td>

</tr>

</table>

Comentarios

n AccountDetails.jsp(AccountDetailsContent.jsp) espera que en la request exista el objeto account, con atributosn accountIdentifier

n userIdentifier

n balance

n Internacionalizaciónn Queremos que balance se muestre con separadores de

miles y decimalesn fmt:formatNumber formatea un número de acuerdo al Locale seleccionado

UserAccountsContent.jsp (1)<%@ taglib prefix="fmt" uri="http://java.sun.com/jstl/fmt" %><%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %><%@ taglib prefix="html" uri="/struts-html.tld" %>

<c:choose>

<c:when test="${empty requestScope.accounts}"><div align="center"><fmt:message key="UserAccountsContent.noAccounts"/></div>

</c:when>

<%-- Print current account chunk. --%>

<c:otherwise>

<table width="50%" border="0" align="center" cellspacing="6"><tr>

<th align="center"><fmt:message key="AccountAttributes.accountIdentifier"/>

</th><th align="center">

<fmt:message key="AccountAttributes.balance"/></th>

</tr>

UserAccountsContent.jsp (2)<c:forEach var="account" items="${requestScope.accounts}"><tr>

<td align="center">

<c:url var="accountDetailsURL" value="FindAccounts.do"><c:param name="identifierType" value="ACC_ID"/><c:param name="identifier“

value="${account.accountIdentifier}"/></c:url>

<a href="<c:out value='${accountDetailsURL}'/>"><c:out value="${account.accountIdentifier}"/>

</a>

</td><td align="center">

<fmt:formatNumber value="${account.balance}"/></td>

</tr></c:forEach></table></c:otherwise></c:choose>

UserAccountsContent.jsp (y 3)<%-- "Previous" and "Next" links. --%><table width="70%" border="0" align="center" cellspacing="6"><tr>

<c:if test="${!empty requestScope.previous}">

<td align="left"><html:link href="FindAccounts.do" name="previous">

<fmt:message key="Links.previous"/></html:link>

</td>

</c:if>

<c:if test="${!empty requestScope.next}">

<td align="right"><html:link href="FindAccounts.do" name="next">

<fmt:message key="Links.next"/></html:link>

</td></c:if>

</tr></table>

Comentarios

n UserAccounts.jsp(UserAccountsContent.jsp) espera que en la request existan (opcionalmente) los objetosn accounts

n Collection de objetos con propiedades accountIdentifier y balance

n previousn Map con los parámetros que requiere el enlace previous

n nextn Map con los parámetros que requiere el enlace next

n NOTAn La generación del enlace para visualizar los detalles de una

cuenta ha quedado algo complejan Implementar un tag JSP a medida habría sido una

alternativa más elegante

es.udc.fbellas.j2ee.minibank.http.controller.actions.FindAccountsAction (1)

public class FindAccountsAction extends DefaultAction {

public ActionForward doExecute(ActionMapping mapping,ActionForm form, HttpServletRequest request,HttpServletResponse response)throws IOException, ServletException, InternalErrorException {

/* Get data. */FindAccountsForm findAccountsForm = (FindAccountsForm) form;String identifierType = findAccountsForm.getIdentifierType();Long identifier = findAccountsForm.getIdentifierAsLong();

/* Do action. */if (FindAccountsForm.ACCOUNT_IDENTIFIER.equals(identifierType)) {

return doFindAccountByAccountIdentifier(mapping, identifier,request);

} else {return doFindAccountsByUserIdentifier(mapping, identifier,

findAccountsForm.getStartIndex(),findAccountsForm.getCount(), request);

}

}

es.udc.fbellas.j2ee.minibank.http.controller.actions.FindAccountsAction (2)

private ActionForward doFindAccountByAccountIdentifier(ActionMapping mapping, Long accountIdentifier, HttpServletRequest request) throws InternalErrorException {

try {

AccountVO accountVO =AccountFacadeDelegateFactory.getDelegate().

findAccount(accountIdentifier); request.setAttribute("account", accountVO);

return mapping.findForward("FindAccountByAccountIdentifierSuccess");

} catch (InstanceNotFoundException e) {

ActionErrors errors = new ActionErrors(); errors.add("identifier",

new ActionError("ErrorMessages.account.notFound"));saveErrors(request, errors);

return new ActionForward(mapping.getInput());

}}

es.udc.fbellas.j2ee.minibank.http.controller.actions.FindAccountsAction (3)

private ActionForward doFindAccountsByUserIdentifier(ActionMapping mapping, Long userIdentifier, int startIndex,int count, HttpServletRequest request) throws InternalErrorException {

/* Find accounts by user identifier. */ Collection accountVOs =

AccountFacadeDelegateFactory.getDelegate().findAccountsByUserIdentifier(userIdentifier,

startIndex, count);

if (accountVOs.size() > 0) {request.setAttribute("accounts", accountVOs);

}

/* Generate parameters for previous and next links. */Map previousLinkParameters =

getPreviousLinkParameters(userIdentifier, startIndex,count);

if (previousLinkParameters != null) {request.setAttribute("previous", previousLinkParameters);

}

es.udc.fbellas.j2ee.minibank.http.controller.actions.FindAccountsAction (4)

Map nextLinkParameters = getNextLinkParameters(userIdentifier, startIndex, count, accountVOs.size());

if (nextLinkParameters != null) {request.setAttribute("next", nextLinkParameters);

}

/* Return ActionForward. */ return mapping.findForward(

"FindAccountsByUserIdentifierSuccess"); }

private Map getPreviousLinkParameters(Long userIdentifier,int startIndex, int count) {

Map linkAttributes = null;

if ( (startIndex-count) > 0 ) {linkAttributes = getCommonPreviousNextLinkParameters(

userIdentifier, startIndex, count);linkAttributes.put("startIndex",

new Integer(startIndex-count));}

return linkAttributes; }

es.udc.fbellas.j2ee.minibank.http.controller.actions.FindAccountsAction (5)

private Map getNextLinkParameters(Long userIdentifier,

int startIndex, int count, int currentChunkSize) {

Map linkAttributes = null;

if (currentChunkSize == count) {

linkAttributes = getCommonPreviousNextLinkParameters(

userIdentifier, startIndex, count);

linkAttributes.put("startIndex",

new Integer(startIndex+count));

}

return linkAttributes;

}

es.udc.fbellas.j2ee.minibank.http.controller.actions.FindAccountsAction (y 6)

private Map getCommonPreviousNextLinkParameters(

Long userIdentifier, int startIndex, int count) {

Map linkAttributes = new HashMap();

linkAttributes.put("identifierType",

FindAccountsForm.USER_IDENTIFIER);

linkAttributes.put("identifier", userIdentifier);

linkAttributes.put("count", new Integer(count));

return linkAttributes;

}

}

Demo selección de idioma y país (1)

Demo selección de idioma y país (y 2)

Implementación selección de idioma y país (1)

n Cuando se hace clic en “Select locale” debe aparecer el formulario de cambio de idioma y país, mostrando los valores anteriormente seleccionadosn Hasta ahora, los formularios estudiados mostraban las

entradas en blanco o con valores por defecto la primera vez que se accedía a ellos

n Esta nueva situación es muy frecuente cuando se permite modificar datos a través de un formulario (ej.: información de registro en una aplicación con usuarios registrados)

Implementación selección de idioma y país (y 2)

n “Select locale” es un enlace a EditLocale.don EditLocale.do

n Acción: EditLocaleAction

n ActionForm: SelectLocaleForm

n EditLocaleAction establece los valores anteriormente seleccionados (Locale actual) en el SelectLocaleForm y hace un forward a SelectLocale.jsp (formulario)

n La URL que invoca el formulario SelectLocale.jspes SelectLocale.don SelectLocale.do

n Acción: SelectLocaleAction

n ActionForm: SelectLocaleForm

n SelectLocaleAction cambia el Locale según los nuevos valores de SelectLocaleForm

SelectLocaleContent.jsp (1)<%@ taglib prefix="fmt" uri="http://java.sun.com/jstl/fmt" %>

<%@ taglib prefix="html" uri="/struts-html.tld" %>

<html:form action="SelectLocale.do">

<table border="0" width="100%" align="center" cellspacing="12">

<tr>

<th align="right" width="50%">

<fmt:message key="SelectLocaleContent.language"/>

</th><td align="left" width="50%">

<html:select property="language">

<html:options collection="languages" property="value"

labelProperty="label" />

</html:select>

<html:errors property="language"/></td>

</tr>

SelectLocaleContent.jsp (y 2)<tr>

<th align="right" width="50%">

<fmt:message key="SelectLocaleContent.country"/>

</th>

<td align="left" width="50%"><html:select property="country">

<html:options collection="countries" property="value"

labelProperty="label" />

</html:select>

<html:errors property="country"/>

</td></tr>

...

Comentarios

n Aparte de la instancia de SelectLocaleForm, SelectLocale.jsp(SelectLocaleContent.jsp) espera que en la request existan los objetos languages y countries en html:optionsn Cada uno es una Collection de objetos que disponen de

las propiedades value (valor lógico) y label (valor visual)n Se podrían haber usado “n” tags html:option, como en FindAccountsContent.jsp, pero las opciones no aparecerían ordenadas alfabéticamente en el idioma actual, lo que es necesario cuando pueden ser muchas

es.udc.fbellas.j2ee.minibank.http.controller.actions.EditLocaleAction (1)

public class EditLocaleAction extends DefaultAction {

public ActionForward doExecute(ActionMapping mapping,

ActionForm form, HttpServletRequest request,

HttpServletResponse response)throws IOException, ServletException, InternalErrorException {

/*

* Fill "form" (ActionForm) with the values of the user's

* current locale (this way the form (JSP page) presented to

* the user will display his/her current selection).*/

SelectLocaleForm selectLocaleForm = (SelectLocaleForm) form;

Locale locale = getLocale(request);

selectLocaleForm.setLanguage(locale.getLanguage());

selectLocaleForm.setCountry(locale.getCountry());

es.udc.fbellas.j2ee.minibank.http.controller.actions.EditLocaleAction (y 2)

/*

* Insert list of languages and countries (ordered by current

* language) as attributes in the "request".

*/

request.setAttribute("languages", Languages.getLanguages(locale.getLanguage()));

request.setAttribute("countries",

Countries.getCountries(locale.getLanguage()));

/* Return an "ActionForward" to the form page. */

return mapping.findForward("SelectLocaleForm");

}

}

Comentarios (1)

n Countries y Languages en es.udc.fbellas.j2ee.minibank.http.view.applicationobjects

n LabelValue en es.udc.fbellas.j2ee.util.struts.view

Languages

<<final, static>> - LANGUAGES : Map<<final, static>> - LANGUAGES_en : List<<final, static>> - LANGUAGES_es : List<<final, static>> - LANGUAGES_gl : List

- Languages()+ getLanguages(languageCode : String) : List

Countries

<<final, static>> - COUNTRIES : Map<<final, static>> - COUNTRIES_en : List<<final, static>> - COUNTRIES_es : List<<final, static>> - COUNTRIES_gl : List

- Countries()+ getCountries(languageCode : String) : List

LabelValue

- label : String- value : String

+ LabelValue(label : String, value : String)+ getLabel() : String+ setLabel(label : String) : void+ getValue() : String+ setValue(value : String) : void

(from view)

n

languageCode : String

n

languageCode : StringlanguageCode : String

languageCode : String

n

n

COUNTRIES

LANGUAGES

Comentarios (2)public class Languages {

private final static List LANGUAGES_en = Arrays.asList(new LabelValue[] {

new LabelValue("English", "en"),new LabelValue("Galician", "gl"),new LabelValue("Spanish", "es") });

private final static List LANGUAGES_es = ...private final static List LANGUAGES_gl = ...

static {LANGUAGES = new HashMap();LANGUAGES.put("en", LANGUAGES_en);LANGUAGES.put("es", LANGUAGES_es);LANGUAGES.put("gl", LANGUAGES_gl);

}

...

public final static List getLanguages(String languageCode) {return (List) LANGUAGES.get(languageCode);

}}

Comentarios (y 3)

n En una aplicación más realista, los códigos y nombres visuales en los distintos idiomas y países podrían leerse de la BD en el bloque staticn Languages y Countries actuarían como cachén Cada vez que se añadiese un nuevo idioma o país, habría

que recargar la aplicación n Cuando no suele haber cambios, esto no es un probleman En otro caso, es mejor no hacer caché (la implementación de

los métodos getLanguages y getCountries leería de BD)

es.udc.fbellas.j2ee.minibank.http.controller.actions.SelectLocaleAction

public class SelectLocaleAction extends DefaultAction {

public ActionForward doExecute(ActionMapping mapping,ActionForm form, HttpServletRequest request,HttpServletResponse response)throws IOException, ServletException, InternalErrorException {

/* Get data. */SelectLocaleForm selectLocaleForm = (SelectLocaleForm) form;String language = selectLocaleForm.getLanguage();String country = selectLocaleForm.getCountry();

/* Change locale. */Locale locale = new Locale(language, country);setLocale(request, locale);

/* Return ActionForward. */return mapping.findForward("Welcome");

}

}

Otros aspectos (1)

n FindAccountOperationsByDateContent.jspn Requiere desplegables para permitir seleccionar día, mes y

año de las fechas de inicio y fin

<html:select property="startDay">

<html:options name="dateRanges" property="days"/>

</html:select>

Otros aspectos (2)

n FindAccountOperationsByDateContent.jsp (cont)n Esta variante de html:options requiere el objeto dateRanges

con la propiedad days, que devuelve una lista (List) con los números correspondientes (se asume que valor visual = valor lógico)n es.udc.fbellas.j2ee.minibank.http.view.applicationobjets.DateRanges

n Dado que los valores visuales son iguales para todos los idiomas, dateRanges puede ser un objeto con scope = application

DateRanges

- days : List

- months : L is t

- years : List

+ DateRanges()

+ getDays() : L ist

+ getMonths() : List

+ getYears() : List

Otros aspectos (3)

n FindAccountOperationsByDateContent.jsp (cont)n ¿ Cómo creamos el objeto dateRanges ?

n Alternativa 1:<jsp:useBean id="dateRanges" scope="application"

class="es.udc.fbellas.j2ee.minibank.http.view.applicationobjects.DateRanges"/>

n Desventaja: hay que acordarse de usar este tag en todas las páginas que permitan especificar fechas

n Alternativa 2:n Especializar el Front Controller para que cree el objeto y lo registre

en el ámbito aplicaciónn Es la alternativa usada en el ejemplo

Otros aspectos (4)

n FindAccountOperationsByDateContent.jsp (cont)

public class FrontController extends ActionServlet {

public void init() throws ServletException {super.init();

registerApplicationObjects();

}

private void registerApplicationObjects() {

registerApplicationObject("dateRanges", new DateRanges());}

private void registerApplicationObject(String key, Object object) {

getServletConfig().getServletContext().setAttribute(key,

object);

}

}

Otros aspectos (5)

n Agrupación de páginas JSP, ficheros HTML, imágenes, etc.n En MiniBank, todos estos componentes cuelgan del

directorio raízn En una aplicación grande es normal agruparlos en

directoriosn Ej.: en un portal personalizable “My”, se podrían tener los

directorios: wizards, services, images, etc.

Otros aspectos (6)

n Transferencia bancarian Si la operación se realiza correctamente, se visualiza una

página de “Operación realizada con éxito”n ¿ Y si quisiésemos que esa página informase sobre el nuevo

balance de ambas cuentas ?n Opción 1: Desde TransferAction (controlador) invocamos

sobre la fachada del modelo a transfer (para realizar la acción propiamente dicha) y dos veces a findAccount (para recuperar el nuevo estado de ambas cuentas)

n Opción 2: Refactorizamos transfer para que devuelva el nuevo balance de ambas cuentas en un Custom Value Object o un mapa

n La opción 2 es la correctan Evita copy-n-paste en aplicaciones con varias vistasn Permite ejecutar código transaccionalmenten Es más eficiente en una arquitectura en 3 capasn Una operación de un Session Facade corresponde a un caso de

uso (y no a un “trozo de un caso de uso”)

Otros aspectos (7)

n Encontrar cuentas por identificador de usuarion Por cada cuenta se muestra su identificador y su balancen La operación invocada de la fachada del modelo, findAccountsByUserIdentifier, devuelve una colección de AccountVOsn Lo más puro sería que devolviese una colección de Custom

Value Objects o mapas con los atributos requeridos por la vistan Si no hay mucha diferencia entre lo que requiere la vista y el

Domain Value Object (AccountVO, en este caso), es razonable usar este último

Otros aspectos (y 8)

n Autenticaciónn Proporcionada por el contenedor (servidor de aplicaciones)

n Útil cuando es posible tener un conjunto fijo de usuarios/roles,creados al margen de la aplicación web (ej.: LDAP)

n En web.xml es posible definir roles (ej.: administrador, usuario, etc.) y especificar que a un conjunto de páginas sólo puedan acceder determinados roles

n En el servidor de aplicaciones se configuran las asociaciones <usuario, rol>

n En MiniBank se podría haber usado este mecanismo

n Proporcionada por la aplicaciónn Necesaria cuando la aplicación permite registrar usuarios (ej.:

MiniPortal)n El programador tiene que implementar la lógica de

autenticación

top related