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

editar

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:

 

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.

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

editar

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

editar

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

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

editar

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

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

editar

El 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

editar

Las 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

editar

En el diagrama adjunto podemos apreciar la arquitectura del sistema de HBeans.

 

Enlaces externos

editar