Icefaces, JBoss, Maven2 y
EJB3: Parte 5.
Vamos ya con la
quinta entrega de este tutorial.
Antes de empezar, os
dejo un enlace a los códigos fuente con lo hecho hasta ahora y
con lo que voy a enseñaros en este:
-
Fuentes de la
parte Web: (descargar) -
Fuentes de la
parte del Negocio: (descargar) -
Fichero pom.xml
del Padre: (descargar)
-
Objetivo.
Como
objetivo en este tutorial es crear, usando icefaces y apoyándonos
en el proyecto Modelo, una página con esta apariencia:
-
Se mostrará
una página, donde usando pestañas podremos seleccionar
ver los libros, los socios o las categorías existentes en la
base de datos. -
Los listados se
mostrarán paginados de 5 en 5. -
Permitirá
ordenar por los campos de los registros -
Existirá
un campo de texto que filtrará los listados por las letras
iniciales de los campos de la lista. Este filtrado se refrescará
automáticamente cada vez que introduzcamos o eliminemos un
carácter en el campo de búsqueda.
Bueno, pues eso es
básicamente (que no es poco) lo que vamos a hacer.
-
Cambios
hechos en el Modelo.Primeramente
os comento una serie de cambios que he tenido que hacer en las
clases del Modelo para adecuarlas a nuestros requisitos. Los cambios
han sido los siguientes (si habéis seguido el tutorial
completo):
-
He
sacado el fichero ‘orm.xml’ del directorio META-INF (para no
tenerlo en cuenta, ya que esto se usó a modo de ejemplo), es
decir, dejamos como modo de mapeo únicamente las anotaciones. -
He
modificado todas las entidades donde aparecían relaciones del
tipo ‘MANY-TO-MANY’ y ‘ONE-TO-MANY’ para que en lugar de
usar colecciones de tipo ‘Set’, usen colecciones de tipo ‘List’.
Esto es debido a que en el listado de libros, se usa un componente
‘dataTable’ anidado dentro de otro componente ‘dataTable’
que muestra los libros, para mostrar los autores de un libro (un
libro puede tener varios autores) y el atributo ‘value’ de este
componente no convierte automáticamente colecciones de tipo
Set, pero si de tipo List. Al final he decidido hacerlo en todas las
relaciones de este tipo para evitar futuros errores, aunque hubiese
bastado con hacerlo en la relación ‘MANY-TO-MANY’ de la
entidad Libro. -
Ha
sido necesario también, por el mismo motivo que antes,
modificar la forma de obtener los autores de un libro, indicando que
NO se obtengan los autores de manera ‘Lazy’ (por defecto) sino
‘Eager’, es decir siempre, porque en el ‘dataTable’ anidado,
cuando intenta obtener los autores de un libro, sino están
obtenidos previamente, intentará obtenerlos en ese momento
(‘Lazy’), sin embargo, la sesión del EntityManager ya
está cerrada y eso provoca un error. El siguiente código
muestra las dos modificaciones realizadas:
/**
*
Modificamos
la
forma
de
obtener
los
autores
del
libro,
* ya
que al
mostrarlos
en el
listado
de
libros,
es
necesario
*
obtenerlos
siempre
para
evitar
un
error.
*
@return
*/@ManyToMany(fetch=FetchType.EAGER)
@JoinTable(name=«Libro_Autor»,
joinColumns=
@JoinColumn(name=«libro»,referencedColumnName=«id»),
inverseJoinColumns=
@JoinColumn(name=«autor»,referencedColumnName=«id»)
)public
List<Autor>
getAutores() {return
autores;}
-
He
modificado la firma de algunos métodos del ‘Dao’, y por
ende también la implementación ‘DaoImpl’. También
he añadido una clase llamada ‘FilterParameter’ que
representa un parámetro de búsqueda en una consulta.
Os muestro un extracto de algunos métodos del ‘DaoImpl’
modificados
-
public
<T extends
TransferObject> List<T> findAllAndFilterLike(Class<T>
transferObjectClass, String sortColumn, boolean
ascending, boolean
or, FilterParameter … params) {
log.debug(«DaoImpl:findAllAndFilterLike»);
final
String entityName = transferObjectClass.getSimpleName();
StringBuffer sbQuery = new
StringBuffer(«from
«).append(entityName).append(»
e»);
final
Query query;
FilterParameter [] params2
= null;
boolean
first=true;
int
i = 0;
if(params!=null
&& params.length>0)
{params2
= new
FilterParameter[params.length];for(FilterParameter
fP:params) {
if(first)
{
sbQuery.append(» where
e.»).append(fP.getField()).append(»
like :»).append(«p»+i);
} else
{
sbQuery.append(or?» or»:»
and»).append(»
e.»).append(fP.getField()).append(»
like :»).append(«p»+i);
}
first=false;
FilterParameter fPAux = new
FilterParameter(«p»+i,fP.getValue()+«%»);
params2[i] = fPAux;
i++;
}}
if
(ascending)
sbQuery.append(» order by e.»
+ sortColumn + » asc»);else
sbQuery.append(» order by e.»
+ sortColumn + » desc»);query
= createQuery(sbQuery.toString(), params2);final
List<T> resultList = query.getResultList();if
(log.isTraceEnabled())
{
log.trace(resultList.size()
+ » «
+ entityName + » recovered
from database.»);}
return
resultList;
}private
Query createQuery(String query, FilterParameter … params) {
Query qQuery =
em.createQuery(query);
if
(params != null
&& params.length>0)
{for(FilterParameter
fP:params) {
qQuery.setParameter(fP.getField(), fP.getValue());}
}
return
qQuery;}
El
primer método permite realizar la búsqueda descrita en
el punto 1. de este tutorial. Recibe una entidad sobre la que buscar
(en realidad la tabla), el campo sobre el que ordenar y la forma de
ordenar el resultado, si la búsqueda es un ‘or’ o ‘and’
sobre los criterios de búsqueda recibidos.
-
Creamos los
ManagedBean.En
el proyecto Web, he creado también los ManagedBean sobre la
que se apoyarán los componentes visuales para ‘pintarse’.
He creado tres ManagedBean, uno para cada Entidad con la que vamos a
interactuar (Libros, Categorías y Socios).Os
muestro y comento BookCtrl (los otros dos son similares, podéis
refactorizar si queréis y sacar lo común a una clase
abstracta o similar):
-
package
com.autentia.tutoriales.icefaces.beans;import
java.util.List;import
javax.faces.component.UIData;import
javax.faces.event.ValueChangeEvent;import
javax.naming.NamingException;import
org.apache.commons.logging.Log;import
org.apache.commons.logging.LogFactory;import
com.autentia.tutoriales.modelo.entidades.Libro;import
com.autentia.tutoriales.modelo.services.Dao;import
com.autentia.tutoriales.modelo.services.DaoImpl;import
com.autentia.tutoriales.modelo.services.FilterParameter;import
com.autentia.tutoriales.modelo.util.EjbLocator;/**
*
Managed
Bean
para
los
libros.*
@author
fjmpaez*
*/public
class
BookCtrl {private
static
final
Log log
= LogFactory.getLog(BookCtrl.class);private
String sortColumn
= «titulo»;private
boolean
sortAscending
= true;private
UIData tabla;Dao
dao;private
String search=«»;public
BookCtrl() {log.debug(«Constructor:BookCtrl»);
try
{dao
= EjbLocator.lookupLocalBean(DaoImpl.class);}
catch
(NamingException e) {log.error
(«Error al iniciar el
controlador: «, e);}
}
public
List<Libro> getAll() {if(«».equals(search))
{return
dao.findAll(Libro.class,
getSortColumn(), isSortAscending());}
else
{return
dao.findAllAndFilterLike(Libro.class,
sortColumn,
sortAscending,true,
new
FilterParameter(«titulo»,search),
new
FilterParameter(«isbn»,search),new
FilterParameter(«categoria.nombre»,search));}
}
public
String getSortColumn() {return
sortColumn;}
public
void
setSortColumn(String sortColumn) {this.sortColumn
= sortColumn;}
public
boolean
isSortAscending() {return
sortAscending;}
public
void
setSortAscending(boolean
sortAscending) {this.sortAscending
= sortAscending;}
public
UIData getTabla() {return
tabla;}
public
void
setTabla(UIData tabla) {this.tabla
= tabla;}
public
String getSearch() {return
search;}
public
void
setSearch(String search) {this.search
= search;}
/**
* Este
método
asegura
que
tras
una
búsqueda,
la
tabla
vuelva
* a
mostrar
la
primera
página.
*
@param
event
*/public
void
changeSearch(ValueChangeEvent event) {tabla.setFirst(0);
}
}
-
En
‘sortColumn’ servirá para almacenar el campo por el que
se ordenan los listados y ‘sortAscending’ indicará si la
búsqueda es ascendente o descendente. -
En
dao almacenaremos una referencia al ‘Bean’ de sesión
‘DaoImpl’ que inicializamos en el constructor del ‘Bean’
apoyándonos en la clase ‘EjbLocator’ -
El
atributo ‘search’ lo usaremos para recuperar el texto
introducido en el campo de búsqueda -
El
atributo ‘tabla’ será una referencia al componente JSF
que muestra los listados. Lo usamos en el método
‘changeSearch(…)’ para que cada vez que se produzca un cambio
en el contenido del campo de búsqueda, la tabla vuelva a la
página inicial. Si no hacemos esto (o algo similar), cuando
estemos mostrando una página distinta a la inicial y hagamos
una búsqueda, provocará que si la búsqueda no
retorna los suficientes registros como para llegar a mostrar la
página en la que te encuentras, en la pantalla no aparecerá
nada sin saber porqué ocurre; por eso forzamos a volver a la
página inicial. -
El
método ‘getAll()’ retornará una lista con los
registros encontrados. Si ‘search’ está vacío
devuelve todos los registros de la tabla. Si no lo está,
pasamos los parámetros correspondientes al método del
‘dao’ que filtra.
A
continuación, debemos registrar los ‘Managed Beans’ en el
fichero ‘faces-config.xml’ (muestro sólo el ‘bookCtrl’):
-
…
<managed-bean>
<description>Bean
que maneja los libros
</description>
<managed-bean-name>bookCtrl</managed-bean-name>
<managed-bean-class>com.autentia.tutoriales.icefaces.beans.BookCtrl
</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>…
-
Creamos la
parte visual.
-
Modo
JSF: Sea procesada por javax.faces.webapp.FacesServlet -
Modo
icefaces: Sea procesada por
com.icesoft.faces.webapp.xmlhttp.PersistentFacesServlet
Nos
vamos a crear una página llamada ‘listBooks.jspx’ (jsp
sujeto a las restricciones de XML). Cuando integramos icefaces
podemos seleccionar si queremos que la página ‘corra’ en:
Dependiendo
del ‘mapping’ que usermos al invocarla (podéis mirar el
web.xml para comprobarlo). Si queremos usar componentes icefaces,
debemos usar ‘jspx’ en modo icefaces
(invocarlo con el mapping ‘*.iface)
-
Para
las pestañas: ‘<ice:panelTabSet>’ y
‘<ice:panelTab>’ -
Para
el texto de búsqueda: ‘<ice:inputText>’ . Este
componente realizará un envío parcial o ‘partial
submit’, que es el ‘truco del almendruco’ de icefaces. Es
decir, usando AJAX (de manera transparente gracias a Dios), icefaces
se encargará de enviar la nueva información
introducida en este campo al modelo de componentes en memoria, y
refrescando o repintando sólo aquellos componentes que se ven
afectados por esta nueva información si necesidad de
refrescar la página completa (Genial ¿ no ?) Por
defecto, el ‘partial submit’ sólo se realiza (si está
activado para el componente con el atributo partialSubmit=true)
cuando el campo pierde el foco (‘onblur’). Yo voy a modificar
esto para que se realice en el evento ‘onchange’ del campo de
búsqueda. -
Para
pintar los listados y su ordenación: ‘<ice:dataTable>’
junto con ‘<ice:commandSortHeader>’. Y para la paginación:
<ice:dataPaginator> -
Para
los estilos, usaremos los que ‘regala’ icefaces.En
definitiva, que no tenemos que hacer casi nada, sólo usar
correctamente los componentes y apoyarnos el los ‘managed beans’
para obtener datos del modelo..Aquí
va el código de parte de la página resaltando lo más
importante:
Para
conseguir nuestro objetivo, usaremos los componentes:
-
<f:view
xmlns:h=«http://java.sun.com/jsf/html»
xmlns:f=«http://java.sun.com/jsf/core»
xmlns:ice=«http://www.icesoft.com/icefaces/component»
xmlns:jsp=«http://java.sun.com/JSP/Page»>
<html>
<head>
<meta
http-equiv=«Content-Type»
content=«text/html;
charset=iso-8859-1»></meta><title>Panel
de control de la Biblioteca</title>
<ice:outputStyle
rel=‘stylesheet’
type=‘text/css’
href=‘./xmlhttp/css/xp/xp.css’/>
</head>
<body>
<ice:form
id=«formBiblioteca»>
<ice:panelTabSet><ice:panelTab
label=«LIBROS»><ice:panelGrid
border=«1»
columns=«2»><ice:outputLabel
for=«searchBook»>Campo
empieza por (excepto autor):</ice:outputLabel>
<ice:inputText
id=«searchBook»
partialSubmit=«false»
value=«#{bookCtrl.search}»
onkeyup=«iceSubmitPartial(form,
this, event); return false;»
valueChangeListener=«#{bookCtrl.changeSearch}»>
</ice:inputText></ice:panelGrid>
<ice:panelGrid>
<ice:dataTable
id=«tablaLibros»
var=«book»
value=«#{bookCtrl.all}»
rows=«5»
sortColumn=«#{bookCtrl.sortColumn}»
sortAscending=«#{bookCtrl.sortAscending}»
binding=«#{bookCtrl.tabla}»>
<ice:column
id=«tablaLibroscolumn1»><f:facet
name=«header»>
<ice:commandSortHeader
columnName=«titulo»
arrow=«true»><ice:outputText
value=«TITULO»></ice:outputText>
</ice:commandSortHeader></f:facet>
<ice:outputText
value=«#{book.titulo}»></ice:outputText>
</ice:column>
<ice:column
id=«tablaLibroscolumn2»><f:facet
name=«header»><ice:commandSortHeader
columnName=«isbn»
arrow=«true»><ice:outputText
value=«ISBN»></ice:outputText></ice:commandSortHeader>
</f:facet>
<ice:outputText
value=«#{book.isbn}»></ice:outputText>
</ice:column>
<ice:column
id=«tablaLibroscolumn3»><f:facet
name=«header»><ice:commandSortHeader
columnName=«categoria.nombre»
arrow=«true»><ice:outputText
value=«CATEGORIA»></ice:outputText></ice:commandSortHeader>
</f:facet>
<ice:outputText
value=«#{book.categoria.nombre}»></ice:outputText>
</ice:column>
<ice:column
id=«tablaLibroscolumn4»><f:facet
name=«header»>
<ice:outputText
value=«AUTOR/ES»></ice:outputText></f:facet>
<ice:dataTable
id=«tablaLibrostablaAutores»
var=«autor»
value=«#{book.autores}»
rows=«0»>
<ice:column
id=«tablaLibroscolumn11»><ice:outputText
value=«#{autor.nombre}»></ice:outputText>
</ice:column>
<ice:column
id=«tablaLibroscolumn12»><ice:outputText
value=«#{autor.apellidos}»></ice:outputText>
</ice:column>
</ice:dataTable>
</ice:column>
</ice:dataTable>
<ice:dataPaginator
id=«dataScroll_tablaLibros»
for=«tablaLibros»
paginator=«true»
fastStep=«3»
paginatorMaxPages=«4»>
<f:facet
name=«first»>
<ice:graphicImage
url=«./xmlhttp/css/xp/css-images/arrow-first.gif»
style=«border:none;»
title=«Primera
Page»/>
</f:facet>
<f:facet
name=«previous»>
<ice:graphicImage
url=«./xmlhttp/css/xp/css-images/arrow-previous.gif»
style=«border:none;»
title=«Anterior
Page»/>
</f:facet>
<f:facet
name=«next»>
<ice:graphicImage
url=«./xmlhttp/css/xp/css-images/arrow-next.gif»
style=«border:none;»
title=«Siguiente
Page»/>
</f:facet>
<f:facet
name=«last»>
<ice:graphicImage
url=«./xmlhttp/css/xp/css-images/arrow-last.gif»
style=«border:none;»
title=«Ultima
Page»/>
</f:facet>
</ice:dataPaginator></ice:panelGrid>
</ice:panelTab>
…
…
…
</ice:panelTabSet></ice:form>
</body>
</html></f:view>
dsdsd
-
Lo
primero que hemos de poner es ‘<f:view>’ indicando los
‘namespaces’ correctamente (diferente a jsp) -
Luego
incluimos uno de los estilos por defecto de icefaces con
‘<ice:outputStyle>’ -
A
continuación definimos las pestañas. -
Luego
incluimos los listados y recogemos los valores invocando al método
‘getAll’ del Bean, le decimos cuales son los atributos
relacionados con la ordenación, el número de elementos
por página (un valor 0 no pagina) -
Incluimos
el campo de búsqueda. Ponemos el ‘partialSubmit’ a false
(aunque es el valor por defecto para darle énfasis a la
explicación) porque lo que queremos es que se envíe la
información después de pulsar una tecla:
onkeyup=«iceSubmitPartial(form,
this, event);…’ .Además
registramos un ‘listener’ para cada vez que cambie el valor y se
haga una búsqueda, la tabla muestre la página 1. -
Luego
anidamos otra tabla para mostrar todos los autores de un libro, esta
vez sin paginación ni ordenación. -
Por
último usamos el componente de paginación indicándole
que tabla está paginando.
Lo
último que nos queda es modificar la página ‘index.jsp’
para que redireccione a esta nueva página:
-
<html>
<head>
</head>
<body>
<jsp:forward
page=«listBooks.iface»
/></body>
</html>
Usamos
el mapping ‘iface’ para asegurarnos que el servlet que recibe la
petición sea el de icefaces.
Si
ahora arrancamos el servidor e invocamos la aplicación:
http://localhost:8080/Web/
, el resultado obtenido debe coincidir ‘sospechosamente’ con lo
que mostrábamos al principio del tutorial. Os recomiendo que
insertéis más datos en la base de datos para probarlo
mejor.
En
la siguiente entrega intentaré finalizar con toda las
operaciones del CRUD (Create, Read, Update y Delete) de alguna de las
entidades, ya que hasta ahora sólo tenemos operaciones de
lectura.
Hasta
la próxima entrega.