Introducción
El desarrollo de aplicaciones web con formularios presenta algunas problemáticas comunes:
Una arquitectura tradicional basada en Servlets y páginas JSPs requiere un número de clases y páginas JSPs proporcional al número de formularios. Cuando el número de formularios es elevado también lo es la cantidad de código a desarrollar y mantener. Se necesita utilizar una gran variedad de elementos html que además deben mantener una coherencia de funcionalidad y estilo en todos los formularios. Mantener la coherencia requiere un esfuerzo notable y difícil de comprobar, la técnica de copiar y pegar acentúa más este problema, cualquier error o cambio se propaga por todos los formularios. Se produce un fuerte acoplamiento entre el objeto que contiene los datos y el código que visualiza el formulario. Cambiar el tamaño de un 'input' del formulario requiere que el programador modifique el código para cada una de las jsps o servlets dónde se muestra el formulario. En el caso de utilizar servlets además es necesario volver a compilar y reiniciar el servidor de aplicaciones, con los inconvenientes que conlleva. Incrustar y programar el código javascript de validación de cada componente HTML del formulario es costoso, difícil de depurar y adolece de estar continuamente en prueba y error. Se requiere ejecutar código javascript por ejemplo para validar que el usuario ha entrado un número positivo, o comprobar que el número de caracteres entrados para el NIF del formulario es 9. Todos los formularios deben implementar algún tipo de estrategia para mostrar los textos en diferentes idiomas, no tener un control centralizado sino diversificado por múltiples páginas dificulta la implementación. La recogida de datos del formulario requiere una programación pesada y repetitiva para establecer cada atributo del objeto con el valor del formulario relacionado.
Los HBeans (acrónimo de HTML Beans) son beans orientados a crear y gestionar formularios y sus objetos relacionados de manera homogénea, sus características básicas: Generación automática de formularios HTML. Generación automática de código de validación Javascript. Gestión automática de recogida y establecimiento de valores del formulario. Sustitución de etiquetas. Lenguaje declarativo en XML. Bajo acoplamiento entre contenido y vista. Promueve la división de tareas. Son extensibles. Permite crear nuevos elementos HTML y funciones Javascript. Los prototipos pueden cambiar "en caliente" sin tener que volver a compilar ni reiniciar el servidor.
El siguiente diagrama muestra los componentes con los que se construye un HBean:
Diagrama 1
El bean es el bean de negocio que se gestiona en el formulario. Contiene todos los getXXX() y setXXX() para cada atributo del objeto.
public class Tarea {
private String descripcion; private int horas;
...
public String getDescripcion() { return descripcion;
public void setDescripcion(String descripcion) { this.descripcion = descripcion;
public void setHoras(int horas) { this.horas = horas;
public int getHoras() { return horas;
...
El prototipo define mediante xml las propiedades de cada atributo del bean en el formulario, si es editable o no, tamaño, descripción, editor, etc.
<prototype target="com.it.hbean.test.Tarea" descriptor="com.it.hbean.test.Tarea"> <attribute name="Descripcion" size='30' enabled='true' length='255'> <display-name>Descripción</display-name> </attribute>
<attribute name="Horas" size='4' enabled='true' length='1'> <display-name>Horas</display-name> <editor class="com.it.hbean.editor.IntegerEditor" /> <js-validation> <js-function> <js-name>checkBetween</js-name> <js-param>0</js-param> <js-param>8</js-param> <js-param>Introduzca un número entre 0 y 8</js-param> </js-function> </js-validation> </attribute> </prototype>
El descriptor es un objeto que implementa la interfaz HBeanDescriptor y tiene asociado el bean de negocio. Podemos tener cualquier número de prototipos y descriptores apuntando a un mismo bean de negocio.
public Class hbeanGetTargetClass();
/** * Obtiene el bean destino */ public Object hbeanGetTarget();
/** * Obtiene la primary key que identifica de forma única el bean. */ public String hbeanGetPrimaryKey();
El descriptor también permite especificar en tiempo de ejecución algunas propiedades en lugar de obtenerlas del prototipo. Por ejemplo, el siguiente método define el valor de la propiedad enabled:
public boolean hbeanIsDescripcionEnabled() { if (..) return true; else return false;
... Estos métodos deben seguir la nomenclatura: hbeanXXXAtributoXXX, el mismo método para el atributo Horas se definiría como hbeanIsHorasEnabled.
Guía
A continuación se describen los diferentes actores que conforman el uso y aplicación de los HBeans. Prototipos Descriptor HBean Editores Validación JavaScript Etiquetas y Variables
Prototipos
Para obtener un hbean se pide al contenedor un hbean según un prototipo y descriptor.
HBeanDescriptor descriptor = new Tarea(); HBean hbean = HBeanContainer.getInstance().getHBean(request, descriptor, "Tarea.hbean");
Dónde Tarea.hbean es el nombre del prototipo. El contenedor se configura previamente con un objeto PrototypeLoader que se encarga de buscar y acceder al prototipo. Podemos utilizar el cargador de prototipos por defecto que nos permite especificar un directorio dónde buscar los prototipos.
// especificamos el directorio de nuestro repositorio de prototipos String fname = getServletConfig().getServletContext().getRealPath("WEB-INF/repository"); PrototypeLoader loader = new DefaultPrototypeLoader(new File(fname)); com.it.hbean.Configuration configuration = new com.it.hbean.Configuration(null, loader, null); HBeanContainer.setConfiguration(configuration);
Este cargador por defecto resuelve los nombres de los prototipos siguiendo la misma lógica que la definición de paquetes Java, el prototipo intranet.Tarea hace referencia al fichero WEB-INF/repository/intranet/Tarea.hbean (la extensión .hbean se debe omitir).
El contenedor instancia un nuevo HBean clonando el prototipo y añadiendo las propiedades definidas en el descriptor.
Los prototipos se crean bajo demanda y se guardan en el contenedor junto con información sobre el descriptor. Después de que el prototipo ha sido creado si modificamos el prototipo solo tenemos que vaciar la caché del contenedor para que los cambios tengan efecto, podemos hacerlo por código o directamente desde el navegador:
HBeanContainer.getInstancia().reset();
O desde el navegador
http://localhost:8080/.../miContexto/manager?action=reset Para borrar los prototipos guardados.
http://localhost:8080/.../miContexto/manager?action=list Para listar los prototipos instalados.
Descriptor
El descriptor tiene asociado un único bean de negocio.
Aunque puede existir de forma separada, la forma más fácil es definir los métodos del descriptor en la misma clase del Bean. En los ejemplos la clase Tarea se define como:
public class Tarea implements HBeanDescriptor {
private String primaryKey;
/** * Obtiene la primary key que identifica de forma única el bean. */ public String hbeanGetPrimaryKey() { return primaryKey == null ? getId() : primaryKey;
public void hbeanSetPrimaryKey(String pk) { this.primaryKey = pk;
/** * Obtiene la clase del bean. */ public Class hbeanGetTargetClass() { return Tarea.class;
/** * Obtiene el bean {@link HBean.getTarget()} */ public Object hbeanGetTarget() { return this;
/** * Todos los proyectos. */ public Value[] hbeanGetIdProyectoValues() { // ... se obtendría de base de datos return new Value[] { new Value("-1", "----"), new Value("0", "Merit"), new Value("1", "R.C.E espanyol"), new Value("2", "Aigües de Barcelona"), new Value("3", "Hotusa Hoteles"), new Value("4", "Restel")
El método hbeanGetPrimaryKey debe identificar al hbean de forma única dentro del formulario, normalmente coincidirá con el identificador del objeto en base de datos pero no tiene porque ser así, es suficiente que dentro del formulario no exista ningún otro objeto de su misma clase con su misma clave.
Puesto que el ejemplo no trabaja con bases de datos se ha añadido el método hbeanSetPrimaryKey de forma que se pueda establecer desde la JSP.
Además de implementar los métodos de la interfaz HBeanDescriptor un descriptor puede implementar métodos de propiedades no definidas en el prototipo, por ejemplo el método public Value[] hbeanGetIdProyectoValues() { // ... se obtendría de base de datos return new Value[] { new Value("-1", "----"), new Value("0", "Merit"), new Value("1", "R.C.E espanyol"), new Value("2", "Aigües de Barcelona"), new Value("3", "Hotusa Hoteles"), new Value("4", "Restel")
equivale a la entrada <value-set> del XML del prototipo. Si no lo definimos en el prototipo entonces el contenedor mira si existe el método hbeanGetXXXValues para ese atributo. Estos métodos deben seguir la nomenclatura: hbeanXXXPPP dónde XXX es "Get" o "Is" más el nombre del atributo, y PPP es el nombre de la propiedad.
La correspondencia entre los métodos del descriptor y el XML del prototipo son:
Método DescriptorXML Prototipoboolean hbeanIsNombreEnabled()<attribute name='Nombre' ... enabled='yes'>boolean hbeanGetHorasValues()<value-set>.. <value name='0'>0<value> .. <value-set>boolean hbeanGetHorasJavascripts()<js-validation>.. <js-function> .. <js-validation>
HBean
Un hbean se obtiene del contenedor a partir de un descriptor y un prototipo:
HBeanDescriptor descriptor = ... HBean hbean = HBeanContainer.getInstance().getHBean(request, descriptor, prototypeName);
Una vez obtenido el hbean podemos acceder a sus atributos y generar el html del formulario, o modificar cualquiera de sus propiedades.
// obtener el atributo con nombre "Descripcion" HBeanAttribute att = hbean.getAttribute("Descripcion");
// html del editor del atributo out.println(att.getEditor().getHtml());
Para guardar los datos que el usuario pueda introducir en el formulario html creamos un objeto Setter:
// Guardar el atributo 'Descripcion' del bean del descriptor con el valor del formulario Setter setter = new Setter(request); setter.set(prototypeName, prototypeName, new String[] { "Descripcion" }, descriptor);
También podemos especificar más de un atributo especificando un iterador. Un iterador se define en el prototipo mediante los tags.
<iterator name="lista"> <iterator-attribute name="IdProyecto" /> <iterator-attribute name="Descripcion" /> <iterator-attribute name="Horas" /> </iterator>
Utilizando el nombre del iterador (en este caso lista):
Setter setter = new Setter(request); setter.setAll(prototypeName, "lista", descriptor);
Editores
Los editores corresponden con los componentes HTML que permiten una interacción con el usuario: listas, casillas de verifación, botones de radio, etc ... En cada atributo del prototipo se define el editor a utilizar, si no se especifica el contenedor buscará un editor que se ajuste al tipo de valor utilizado por el método "get" del bean para ese atributo.
Atributo horas en Bean:
public int getHoras(); public void setHoras(int horas);
XML prototipo:
<attribute name="Horas" size='4' length='1'> <display-name>Horas</display-name> <editor class="com.it.hbean.editor.IntegerEditor"> </editor> </attribute>
puesto que IntegerEditor es el editor por defecto para atributos que retornan enteros no haría falta establecerlo, tendríamos el mismo resultado con:
<attribute name="Horas" size='4' length='1'> <display-name>Horas</display-name> </attribute>
Los editores disponibles en la versión 1.0 son