HBean
HBean (HTML Bean) | ||
---|---|---|
Información general | ||
Autor | Daniel del Río | |
Desarrollador | Ricard Forner | |
Licencia | GNU General Public License | |
Información técnica | ||
Programado en | Java | |
Enlaces | ||
Introducción
editarEl 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.
- «Proyecto HBean Opensource». Código abierto.
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:
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
editarA 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
editarPara 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
editarEl 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 Descriptor | XML Prototipo |
---|---|
boolean 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
editarUn 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
editarLos 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
Clase | Tipo | Descripción |
---|---|---|
com.it.hbean.editor.BooleanEditor | boolean, Boolean | *Editor por defecto si no especifica |
com.it.hbean.editor.DateEditor | java.util.Date | *Editor por defecto si no especifica |
com.it.hbean.editor.FileEditor | String | Editor por subir ficheros |
com.it.hbean.editor.FloatEditor | float, Float | *Editor por defecto si no especifica |
com.it.hbean.editor.ImageEditor | String | Editor por subir ficheros de imagen |
com.it.hbean.editor.IntegerEditor | int, Integer | *Editor por defecto si no especifica |
com.it.hbean.editor.IntegerSelectEditor | int, Integer | Selector listas |
com.it.hbean.editor.StringCheckboxEditor | String | Editor de opciones múltiples (casillas de verificación) |
com.it.hbean.editor.StringEditor | String | *Editor por defecto si no especifica |
com.it.hbean.editor.StringRadioEditor | String | Editor de opciones múltiples (botones de radio) |
com.it.hbean.editor.StringSelectEditor | String | Editor de opciones múltiples (tipo selector) |
com.it.hbean.editor.TextAreaEditor | String | Editor zona de texto libre |
Nuevos editores | ||
com.it.hbean.editor.BankCCCEditor | int, int, int, int | Editor de cuenta bancaria CCC (20 dígitos) |
com.it.hbean.editor.NifEditor | int, String | Editor de Identificación fiscal (NIF) |
Validación JavaScript
editarEl código de validación puede generarse automáticamente mediante el objeto JavascriptCoder.
JavascriptCoder coder = new JavascriptCoder();
// añadir todos los atributos definidios en el iterador del hbean
coder.addValidation(hbean.getAttributeIterator(iteratorName));
// añadir función javascript
out.println(coder.getValidationJS("nombreFuncionValidacion"));
El código javascript se genera a partir de las definiciones de validación definidas en el prototipo:
<attribute name="Horas" size='4' length='1'>
:
<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>
Dónde js-name es el nombre de la función de javascript y las entradas js-param son cada uno de los argumentos que se pasan a la función. Cómo primer argumento se añade automáticamente el id del componente HTML en el formulario.
Podemos utilizar alguna de las funciones de javascript disponibles en el fichero hbean.js:
- checkNoEmpty(name, errorMsg)
- checkPassword(name, errorMsg)
- checkFloat(name, decimalChar, groupingChar, errorMsg)
- checkInteger(name, errorMsg)
- checkPositiveFloat(name, decimalChar, groupingChar, errorMsg)
- checkBetween(name, minimus, maximus, errorMsg)
- checkInteger(name, errorMsg)
- checkPositive(name, errorMsg)
- checkDate(name, errorMsg)
- checkLengthMandatory(name, len, errorMsg)
- checkLength(name, len, errorMsg)
- checkLetraNIF(name, errorMsg)
- checkEmail(name, errorMsg)
Etiquetas y Variables
editarLas etiquetas sirven para insertar texto en tiempo de ejecución, esto puede ser útil para mostrar texto según el usuario actual, el idioma seleccionado o cualquier otra condición. Una etiqueta se define añadiéndola entre los caracteres #{ y } . El ejemplo MultiIdioma.jsp utiliza etiquetas para mostar texto en diferentes idiomas y variables para cambiar la foto de la mascota seleccionada con el texto adecuado.
El prototipo Persona.hbean utiliza una etiqueta para el nombre que será sustituida por "Nombre", "Nom" o "Name" según el idioma seleccionado:
<attribute name="Nombre">
<display-name>#{Persona.Nombre}</display-name>
</attribute>
:
Para resolver la etiqueta se utiliza un objeto que implemente la interfaz com.it.hbean.LabelResolver establecido en el objeto com.it.hbean.Configuration del contenedor. El contenedor busca entre el texto las etiquetas y las sustituye llamando al método de la interfaz LabelResolver.
public String resolve(HttpServletRequest request, HBeanAttribute attribute, String label);
Internamente es el objeto com.it.hbean.Compiler quién sustituye las etiquetas. Podemos llamar directamente a sus métodos para sustituir cualquier otro texto que queramos incluir.
En el ejemplo se utiliza el objeto com.it.hbean.test.Diccionario como LabelResolver y se establece a la configuración en la jsp index.jsp. El objeto Diccionario accede a tres ficheros de propiedades: data.es, data.ct y data.en según el idioma sea castellano, catalán o inglés.
De la misma forma que utilizamos etiquetas para sustituir texto en tiempo de ejecución utilizamos variables para referenciar valores de atributos que se desconocen hasta el momento de ejecución. Una variable se define añadiéndola entre los caracteres ${ y }. Las variables que podemos utilizar están definidas como constantes en el mismo objeto com.it.hbean.HBeanAttribute.
public static String VAR_ID = "Id";
public static String VAR_DISPLAY_NAME = "DisplayName";
public static String VAR_DESCRIPTION = "Description";
public static String VAR_NAME = "Name";
public static String VAR_VALUE = "Value";
El ejemplo MultiIdioma.jsp utiliza las variables ${Nombre.Id} y ${Mascota.Id} que referencian el elemento html del atributo nombre y los botones de radio del atributo mascota. Cuando la selección de mascota cambia se muestra la foto de la mascota y el texto "La mascota de [Nombre]"
<attribute name="Mascota">
<display-name>#{Persona.Mascota}</display-name>
<editor class="com.it.hbean.editor.StringRadioEditor">
<value-set>
<value name="perro">#{Persona.Perro}</value>
<value name="gato">#{Persona.Gato}</value>
<value name="pajaro">#{Persona.Pajaro}</value>
</value-set>
<html-attribute-set>
<html-attribute name="class">radio</html-attribute>
<html-attribute name="onclick">mostrarMascota(
"#{Persona.TextoMascota}", "${Nombre.Id}", "${Mascota.Id}")
</html-attribute>
</html-attribute-set>
</editor>
Arquitectura
editarEn el diagrama adjunto podemos apreciar la arquitectura del sistema de HBeans.
Enlaces externos
editar- «Proyecto HBean Opensource». Código abierto.
- Sourceforge. «Download».
- RFKSolutions. «HBean, versión de desarrollo de beans HTML gestionables.».
- Wikimedia Commons alberga contenido multimedia sobre HBean.