Buscar
Social
Ofertas laborales ES
sábado
jun012002

Introduccrión a los motores de plantillas

Motores de plantillas (Templates engines)




¿Que son las plantillas?.




Si hay algo "de moda" en Java, en la programación en general, y en la programación para la web en particular, es el famoso patrón MVC (Model View Controller). Este patrón trata de separar los datos de su presentación, por decirlo en terminos web, de separar el código del programador del código del diseñador web. Las plantillas son una aproximación más para resolver este problema, como lo puede ser XMLC (http://xmlc.enhydra.org/).



Las plantillas, o más exactamente los motores de plantillas (templates engines) leen un fichero de texto, que contiene la presentación ya preparada en HTML (o meta información, por ejemplo XML, o lo que sea, siempre que sea texto), e inserta en él la información dinámica que le ordena el "controlador" (la C de MVC), la parte que une la vista con la información.



Veamos un par de ejemplos para ver las posibilidades de las plantillas, que no acaban, ni mucho menos, en la web. La sintaxis a utilizar depende del motor de plantillas utilizado, pero todos son muy similares, e incluso hay herramientas de conversión entre ellos. Los motores de plantillas suelen tener un pequeño lenguaje de script que permite generar código dinámico, como listas o cierto comportamiento condicional. Este lenguaje de script es absolutamente mínimo, lo justo para posibilitar ese comportamiento dinámico.



<html>
<body>
Hola ${nombre}
</body>
</html>


Esta claro que este ejemplo, una pequeña variación del famoso "Hola Mundo", es "vergonzosamente" simple, pero tampoco es plan de asustar con el primero. Bién, al procesar este fichero, el motor de plantillas lo recorrerá, parseará (si es que existe esa palabra en castellano) y sustituirá esa "etiqueta clave" ${nombre} por el texto que le hallamos indicado, el nombre del visitante, por ejemplo, de forma que tengamos una presentación personalizada.



Como hemos dicho, las plantillas se pueden utilizar para mucho más que para generar HTML, así pues en este ejemplo más complicado veremos como generar XML.



<?xml version="1.0"?>
<link>
<titulo>${link.titulo}</titulo>
<descripcion>${link.descripcion}</descripcion>
<url>${link.url}</url>
</link>


Como vemos, las plantillas no se refieren sólo a elementos simples de datos, si no que también pueden procesar objetos para mostrar sus miembros, de la forma ${objeto.campo}, aunque como hemos dicho, la sintaxis exacta depende del motor de plantillas empleado.



Este tercer ejemplo, y último de esta pequeña introducción a las posibilidades que nos ofrecen las plantillas, es un poco más complicado. Por medio de plantillas podríamos generar un informe sobre el pedido de un cliente, para enviarselo al mismo cliente por email o para enviarselo a nuestro sección de envios.



Nombre: ${cliente.nombre} Fecha: {fecha}
Número de cliente: ${cliente.codigo}
Dirección: ${cliente.direccion}

<list pedido as elemento>
${elemento.cantidad} ${elemento.descripcion} ${elemento.precio}
</list>
Total a pagar: ${total}



Como podeis observar, la sintaxis de este pequeño lenguaje de script es muy sencilla, en ocasiones "de aspecto xml", por lo que no es difícil de aprender.



También os podeis imaginar lo sencillo que es hacer cambios en el resultado generado por nuestra aplicación sin tener que tocar el código Java de nuestra aplicación, y aún más, sin tener que hacerlo nosotros. Si nuestro jefe quiere que aparezca una cabecera extra en el listado del tercer ejemplos, o una firma, lo puede hacer el solito, editando la plantilla.






Plantillas vs JSP.



Si has leido lo anterior, y crees que las plantillas pueden ser interesantes para ti, en la programación web, pero aún no estas del todo seguro en que puede mejorar esto tu desarrollo con JSP, en esta sección intentaremos describir los ventajas/desventajas de uno u otro estilo.



El patrón MVC se empeña en separar el código HTML del código Java. Como habreis visto en los ejemplos anteriores, la separación entre ambos es "absoluta" en las plantillas, mientras que en las JSP se puede incorporar toda la cantidad de código Java que quieras.



Es cierto que con buenos hábitos de programación, y usando herramientas como Java Beans y taglibs la cosa puede resultar más sencilla, pero en todo caso tienes un problemas



  • o bien haces que los programadores Java tengan que manipular el código HTML.

  • o bien haces que los programadores HTML tengan que aprender, como poco, los conceptos básicos de las JSP.



Ninguna de las dos variantes es factible, o recomendable, creo yo. Los programadores Java no son, por lo general, demasiado amigos del HTML, y es posible que les falten conocimientos, y que la página tan perfecta que les han entregado los programadores HTML para que introduzcan la lógica dinámica acabe, por decirlo de una forma sencilla, "no tan perfecta". Por el otro lado, si obligas a los programadores HTML a aprender los conceptos de las JSP y taglib, aunque no son complicados, no es del todo seguro que los usen eficientemente, o al menos pasará un tiempo antes de que lo hagan. Esto llevará a la típica situación en las que un equipo se queja del otro porque estan "destrozando" su trabajo.



El lenguaje de script que incorporan las plantillas es muy reducido y simple, y en algunos motores de plantillas, como Freemarker, este lenguaje tiene aspecto de XML, con etiquetas de inicio y final, por lo que los programadores HTML lo pueden aprender en media hora de estudio. Puedes comprobar su sencillez mostrando los ejemplos anteriores a un programador HTML y preguntándole que cree él que hace ese código, o incluso tú mismo puedes darte cuenta de la sencillez de este código, puesto que estoy seguro de que has entendido a simple vista lo que hacen los ejemplos aún siendo la primera vez que oyes algo sobre las plantillas.



Por último, esta clara separación entre HTML y Java hace que sea posible el trabajo en paralelo de ambos equipos, siempre que se haya definido al principio ciertos aspectos, como cual será la información a mostrar y bajo que nombres.



Antes hemos mencionado la posibilidad de usar taglibs o librerías de etiquetas. Las librerías de etiquetas se introdujeron con la especificación 1.1 de JSP para intentar "sacar" un poco de código Java del HTML, ya que esto violaba la "ley de separación" del MVC. Este intento no fue muy acertado, aunque algo mejoró la situación. Estas librerias de etiquetas encapsulan datos y cierta lógica (en ocasiones incluso aún cierto código HTML, una aberración) para ofrecérsela a la JSP por medio de una interface que define la especificación. El mayor problema de esta aproximación, a parte de la dificultad intrinseca de crear una libreria (programación, configuración y despliegue, si no podeis mirar la cantidad de proyectos al respecto que hay, bastante escasos), esta en que todo esta idea se basa en la "reflexión" (reflection), que debido a su forma de trabajo (buscar dentro de una clase los métodos con la signatura dada) no es el modo más eficiente de trabajar. Y por supuesto, ni hablar de la posibilidad de que alguién meta código HTML en la libreria, solo con que alguién cambie (o quiera cambiar) la apariencia de la página se presenta la necesidad de recompilar la libreria.



Sin embargo las JSP tienen la ventaja de contar con una gran industria detrás de ellas. Esto no las hace mejores, pero por motivos de marketing, un programador java que se acerca a la web empezará con JSPs, y tardará en enterarse de sus problemas, o en oir hablar de otras posibilidades (plantillas, XMLC), si es que algún día llega a hacerlo. Por supuestoque a esto se une la "decisión" de los jefes, que en muchas ocasiones se deciden por tecnologías que no conocen, sin preguntarse si son buenas o hay alternativas posibles, solo porque lo ha leido/oido en algún sitio.





Diferentes motores de plantillas.



Principalmente existen tres motores de plantillas disponibles en internet, todos open source. Webmacro (http://www.webmacro.org), Velocity (http://jakarta.apache.org/velocity), y Freemarker (http://freemarker.sourceforge.net). Webmacro y Velocity son muy similares (Velocity empezó basándose en Webmacro), por lo que esta especie de comparación se basará en Velocity y Freemarker,




  1. La primera gran diferencia esta en el modo de trabajar de ambos motores. Velocity lo hace a través de reflection, mientras que Freemarker lo hace a través de una serie de interfaces definidas. Esto es bueno y es malo para las dos. Es malo para Velocity, porque si hemos dicho que la reflexión aplicada a las taglibs es ineficiente, es de suponer que aplicada a las plantillas no será la mejor solución del mundo, aunque sea la más sencilla para el programador usuario del motor de plantillas. Pero en el caso de Freemarker, aunque el trabajo en base a interfaces sea más eficiente, el hecho de tener que tener que implementar unas interfaces determinadas (principalmente tres, para listas, mapas y valores simples), nos obliga a implemtar esas interfaces en los objetos de nuestro modelo de negocio, o a desarrollar clases envoltorio (wrappers) para envolverlos. Freemarker nos ofrece ya algunas soluciones "listas para su uso", y se esta desarrollando un concepto para soportar la reflexión de forma más óptima (en realidad ya tienen una aproximación a ello funcionando, pero trabajan en su mejora), por lo que este trabajo en base a interfaces no deberia ser problema, más bien pasa a ser, por decirlo de alguna manera, opcional.

  2. Velocity esta más documentado, o al menos hay más documentación al respecto. Aunque el soporte de ambas comunidades es igual de bueno, Velocity como subprojecto de jakarta tiene más colaboradores en todos los sentidos. Freemarker trata de hacerse camino, aunque aún se esta regenerando de cierta dejadez anterior.

  3. Fremarker tiene un lenguaje de script "de formato XML", por lo que puede hacer que sea más apatecible para diseñadores web que Velocity, con un formato mucho más del estilo del programador. En Freemarker un bucle se hará con:

    <list lista as elemento>
    ...
    </list>


    y en Velocity:



    #foreach( $elemento in $lista )
    ...
    #end



  4. El futuro de ambos se podría considerar "incierto" por diversos motivos. La comunidad de Velocity es mucho mayor, lo que puede hacer presuponer que su futuro es más halagüeño, pero al mismo tiempo tiene aspecto de ser más monolítico, y sigue arrastrando aspectos (arrastrados desde el comienzo del proyecto) que la limitan a avanzar para no romper la compatibilidad. Sin embargo, la más limitada comunidad de Freemarker hace que sea más ágil y factible su renovaciópn/adaptación a los nuevos tiempo, como la reciente reescritura de la versión 2.0 (que aún asi es casi totalmetne compatible con la versión 1.x).



Otros aspectos, además de los referentes a sus diferencias con las JSP ya comentados, los hacen similares, como son la existencia de frameworks para su utilización desde Servlets, el soporte para XML (aunque este es un poco desigual en favor de Freemarker), soporte numérico en las plantillas (Velocity soporta enteros, Freemarker reales y enteros), y su "lucha" contra las JSP y otras tecnologías similares por hacerse un hueco en el desarrollo web.





Conclusión.



Los motores de plantillas tienen el handicap de ser algo "desconocido". Existe el problema de que mucha gente usa JSP "porque no conoce otra cosa", y porque creen que es lo mejor porque hay más libros sobre el tema. Quién sabe, quizás para alguna aplicación sean adecuadas. En este artículo he intentado mostraros lo que las plantillas pueden hacer, espero haberlo conseguido, y espero que les deis "una oportunidad", aunque tengais que esperar al siguiente artículo para aprender como usarlas (al menos si no sois curiosos y empezais vosotros mismos).



Cerraré contando, como anécdota, que en mi vida profesional ya he "convertido" dos equipos de trabajo basados en JSP a hacerlo en las plantillas, y ya estoy deseando que llegue el siguiente. Algo tendrán las plantillas si convencen cuando se las conoce, ¿no?.















Alberto Molpeceres es ahora mismo desarrollador de aplicaciones en ámbito cliente/servidor para la empresa T-Systems - debis Systemhaus en Munich (Alemania).

Cuando no está trabajando o "metiendo caña" al resto de los integrantes
de javaHispano intenta pasear con su novia, buscar la desaparecida
lógica del idioma alemán o intenta olvidar la pesadilla que es buscar piso en Munich.



Para cualquier duda o tirón de orejas, e-mail a:
al_ARROBA_javahispano.com




sábado
jun012002

Ant (5): creacrión de tareas propias


Creación de tareas en Ant







Descripción




Como ya hemos visto en otros artículos, Ant ofrece una gran cantidad de tareas predefinidas para realizar distintas acciones. Sin embargo, es posible que en algún momento necesitemos realizar algunas acciones concretas y no haya una acción que se ajuste a nuestras necesidades.



Para solucionar este problema, Ant proporciona al usuario la posibilidad de crear tareas personalizadas escritas en Java.



En este artículo se explica paso a paso la creación de distintos tipos de tareas para Ant.




Creando una tarea sencilla




Vamos a crear una tarea muy sencilla que funciona de manera similar a echo. Se llamará Prueba1 y tendrá un atributo cadena de tipo String.



En un fichero build.xml, se usaría de la siguiente forma:




<target ...>
<Prueba1 cadena="unaCadena"/>
...
</target>



Cada tarea se implementa en (al menos) una clase Java. Todas las tareas extienden org.apache.tools.ant.Task.




import org.apache.tools.ant.*;

public class Tarea1 extends Task {
...
}



Es importante destacar que el nombre de la tarea (Prueba1) y el nombre de la clase que la implementa (Tarea1) no tienen por qué coincidir.




Por cada atributo de la tarea, definiremos un atributo en la clase:




protected String cadena;



En este caso, ambos nombres sí deben coincidir.




Además, definiremos un método de tipo setter, que será invocado por Ant para darle valor al atributo Java:




public void setCadena (String cadena) {
this.cadena = cadena;
}



Existen algunas restricciones en cuanto al nombre del método: debe empezar por set, la siguiente letra debe ser mayúscula y las demás minúsculas.




Por último, definimos un método execute, de la siguiente manera:




public void execute () throws BuildException {
...
}



En el cuerpo del método pondremos todo aquello que queremos que la tarea haga.




Aquí está el ejemplo completo listo para ser compilado.




A continuación compilamos la tarea. El paquete org.apache.tools.ant se encuentra en el fichero $ANT_HOME/lib/ant.jar.




javac -classpath $ANT_HOME/lib/ant.jar Tarea1.java



Modificando y utilizando el fichero build.xml




Sólo queda añadir al principio del project, una linea taskdef para relacionar el nombre de la tarea con el nombre completo (incluyendo los paquetes, si los hay) de la clase que la implementa:




<project ...>
<taskdef name="Prueba1" classname="Tarea1"/>
...
</project ...>



Aquí está el primer build.xml.




Para poder utilizar la nueva tarea, Ant debe ser capaz de encontrar la clase que la implementa. En la documentación oficial se recomienda ampliar la variable de entorno CLASSPATH (en este caso, con el directorio donde está situada Tarea1.class). Así, la nueva tarea podría utilizarse con:




ant -buildfile build1.xml



Alternativamente, es posible invocar a Ant sin tener que modificar el classpath actual. Suponiendo que Tarea1.class esté en el directorio actual, podríamos usar algo como:




java -classpath $ANT_HOME/lib/ant.jar:. org.apache.tools.ant.Main -buildfile build1.xml



Ésta es la salida obtenida:




Buildfile: build1.xml

Prueba1:
[Prueba1]
[Prueba1] Tarea1 ...
[Prueba1] cadena = unaCadena
[Prueba1] Fin de Tarea1
[Prueba1]
[Prueba1]
[Prueba1] Tarea1 ...
[Prueba1] cadena = otraCadena
[Prueba1] Fin de Tarea1
[Prueba1]

BUILD SUCCESSFUL

Total time: 0 seconds



Por último, también es posible añadir nuestra tarea al conjunto de tareas predefinidas de Ant, de modo que no sea necesario indicar el elemento taskdef. Para ello es necesario disponer del código fuente de Ant y añadir al fichero src/main/org/apache/tools/ant/taskdefs/defaults.properties una linea indicando el nombre de la tarea, tal cual ha de ser usado y el nombre completo de la clase que la implementa. Por ejemplo:




unatarea=com.javahispano.tareas.unatarea



Por supuesto, es necesario que esta tarea sea accesible por Ant, bien modificando la variable de entorno CLASSPATH, bien invocando org.apache.tools.ant.Main con el parámetro classpath adecuado.




La clase org.apache.tools.ant.Task




La clase Task es la clase base de todas las tareas. En ella se definen varios atributos y métodos que conviene conocer. Entre otras cosas, es posible conocer atributos del proyecto (nombre, tareas predefinidas que puede utilizar, propiedades, etc.), de los objetivos del proyecto (cuáles son, qué tareas contienen, etc.) y de la tarea que se está ejecutando. Por ejemplo:



  • Obtener el nombre del proyecto: getProject().getName();
  • Obtener el nombre del objetivo al que pertenece la tarea: getTarget().getName();
  • Obtener el nombre de la tarea: getTaskName();



Aunque la mejor referencia es el API, aquí hay un ejemplo de cómo usar alguno de ellos. Aquí está el fichero build.




A modo de curiosidad, las propiedades definidas en el fichero build no se incluyen en las obtenidas con el Project.getUserProperties sino con Project.getProperties.




Más tipos de datos




En el primer ejemplo sólo hemos usado atributos del tipo String, aunque Ant admite además tipos primitivos, la clase File, la clase Class y cualquier otra clase que tenga un constructor (público) con un único argumento de tipo String (por ejemplo, StringBuffer).




Los argumentos de tipo File permiten indicar un fichero o un directorio (mediante una ruta absoluta o relativa). Por ejemplo, el atributo todir de la tarea Copy, internamente es de tipo File.



Cuando en una tarea se declara un atributo de tipo File, se instancia un objeto File con el nombre de fichero indicado en el atributo correspondiente. Se tiene la garantía de que cuando se ejecuta el método execute, los métodos set ya se han ejecutado y los atributos de tipo File ya han sido instanciados. El siguiente es un ejemplo de uso de este tipo de atributos en una tarea de un fichero build:




<target ...>
<unaTarea archivo="build.xml"/>
...
</target>



En la clase correspondiente, tendríamos algo como:




protected File archivo;

public void setArchivo (File archivo) {
this.archivo = archivo;
}

public void execute () throws BuildException {
System.out.println("Ruta absoluta: " + archivo.getAbsolutePath());
}



Así, el valor indicado en el atributo ("build.xml") será utilizado en una invocación al método setArchivo. Antes de ejecutar el método execute, se habrá realizado una instanciación equivalente a:




new File(archivo)



de forma que archivo está correctamente inicializado con el valor indicado en la tarea del fichero build.




También es posible definir argumentos cuyo atributo correspondiente en la clase que implementa la tarea pertenezca a una clase que tiene un constructor que recibe como único parámetro un objeto String.




Básicamente, se tienen las mismas garantías que en el caso anterior, de manera que cuando se ejecuta el método execute, el atributo correspondiente ya ha sido instanciado con el valor dado en el argumento de la tarea.




Por ejemplo:




<target ...>
<unaTarea buffer="cadena"/>
...
</target>



y en la clase correspondiente:




protected StringBuffer buffer;

public void setBuffer (StringBuffer buffer) {
this.buffer = buffer;
}

public void execute throws BuildException () {
buffer.append(" cadena2");
System.out.println("StringBuffer:" + buffer.toString());
}



Sabemos que cuando se ejecute el método execute, el atributo buffer de la clase estará inicializado como si se hubiera ejecutado la siguiente instanciación:




new Stringbuffer(buffer)



La ejecución de esta tarea mostraría por pantalla la cadena "cadena cadena2".




Aquí hay un ejemplo de uso de los atributos de tipo File y StringBuffer. Aquí está el correspondiente fichero build.




Tareas que admiten texto




Algunas tareas, como echo, admiten un cuerpo no vacío, en el que hay texto:




<target ...>
<echo>
Primera linea del cuerpo
Una linea más
Otra linea
</echo>
</target>



La forma de implementar este tipo de tareas se basa en definir un método con el siguiente perfil:




public void addText (String)



No existen otras restricciones en cuanto a atributos necesarios o implementación del método anterior. Dentro del cuerpo de este método, podemos realizar cualquier tipo de acción, aunque lo más indicado es guardar el texto recibido en algún atributo, procesarlo de alguna manera si es necesario y utilizarlo posteriormente durante la ejecución del método execute.




Aquí hay un ejemplo de uso de tareas con texto interno. Aquí está el correspondiente fichero build.




Usando tipos enumerados




Algunas tareas tienen atributos enumerados, esto es, atributos que deben contener un valor de entre un conjunto de ellos. Por ejemplo, el atributo access de la tarea Javadoc es un atributo de tipo enumerado (puede tener los valores public, protected, package o private):




<target ...>
<javadoc
...
access="public"
/>
</target>



Es posible comprobar estos atributos de dos formas distintas. La primera es la forma obvia: comparar el valor recibido como argumento del método setter contra un conjunto de posibles valores:




protected String cadena;

public void setCadena (String enumerado) {
// Comprobar los posibles valores
if (enumerado.equals("valor1") || enumerado.equals("valor2") || ...)
this.cadena = enumerado;
else
throw new Exception ("Valor no permitido: " + enumerado);

}



Sin embargo, existe una forma más cómoda de proceder. Consiste en definir una tarea (por ejemplo interna) que extienda org.apache.tools.ant.types.EnumeratedAttribute, como:




public static class MiTipoEnumerado extends EnumeratedAttributes {
public String [] getValues () {
return new String [] {"valor1", "valor2", "valor3"};
}
}



El método getValues es abstracto en la clase EnumeratedAttributes. En él se declaran los posibles valores. La clase EnumeratedAttributes tiene otro método, setValue, declarado como final, donde se comprueba la pertenencia del valor indicado en el fichero build al conjunto de valores indicado en el método getValues. De esta forma, se nos garantiza que el valor recibido en el método setEnumerado es un valor correcto. En el caso de que el valor indicado no sea correcto, desde el método setValue de EnumeratedAttributes se lanzará una excepción señalando que el valor indicado no es válido.



Es importante que la clase se declare con los modificadores public y static. De otra forma, se obtendrá un error en tiempo de ejecución (concretamente, una excepción de tipo java.lang.InstantiationException).




El argumento del método setter a usar será de este tipo:




public void setCadena (MiTipoEnumerado conjunto) {
// Obtener el valor indicado en el fichero build
this.cadena = conjunto.getValue();
}



Esta forma de proceder presenta la ventaja de que el control del valor utilizado se reduce únicamente a declarar el conjunto de valores permitidos. El resto corre a cargo de la clase EnumeratedAttributes.



Según el caso, declarar la clase como interna puede ser suficiente. Por ejemplo, cuando el conjunto de valores es muy concreto y específico. En otros casos, puede ser útil definirla como pública, en un fichero aparte e incluso en un paquete aparte.




Aquí hay un ejemplo de uso con el correspondiente fichero build. La ejecución del único objetivo que hay produce un error de ejecución debido a que el valor "valor4" no pertenece al conjunto de valores válidos declarado en la clase interna MiTipoEnumerado.




Tareas con elementos anidados



La mayoría de tareas predefinidas aceptan elementos anidados. Por ejemplo, la tarea javac admite un parámetro src para indicar los ficheros fuente.



Para manejar estos elementos hay que implementar métodos createXXX o addXXX. Por la falta de documentación oficial en la distribución actual (1.4.1) y su complejidad, el uso de este tipo de elementos será descrito en un próximo artículo.




El ciclo de vida de una tarea




Cada tarea indicada en un fichero build sigue el siguiente ciclo de vida:





  1. La tarea es instanciada invocando el constructor sin argumentos, cuando Ant parsea el fichero build.xml. Todas las tareas indicadas con taskdef son instanciadas, incluso si posteriormente no llegan a ser utilizadas.
  2. La tarea obtiene referencias al projecto al que pertenece y a su location (posición dentro del fichero build) mediante los atributos project y location heredados de la clase Task.
  3. Si el usuario indicó un atributo id en la instanciación de la tarea (en el fichero build), en el objeto proyecto se guarda (en tiempo de parseo) una referencia a esta tarea, mediante su método addReference(String, Object). Es posible obtener estas referencias mediante el método getReferences del objeto Project.
  4. La tarea obtiene una referencia al objetivo al que pertenece mediante el atributo target heredado de la clase Task.
  5. El método init es invocado en tiempo de parseo.
  6. En tiempo de parseo, todos los elementos hijos de la tarea (en el fichero build, los elementos XML anidados) son creados mediante los métodos createXXX o instanciados y añadidos a esta tarea mediante los métodos addXXX.
  7. En tiempo de ejecución, los métodos setXXX son ejecutados para dar valor a los atributos de la tarea.
  8. En tiempo de ejecución, el método addText es ejecutado, para recuperar el texto contenido en el interior del elemento XML que representa a la tarea.
  9. En tiempo de ejecución, los atributos de los elementos hijos son establecidos mediante sus correspondientes métodos setXXX.
  10. El método execute es ejecutado al menos una vez.



En cuanto al último punto, es importante destacar que el método execute puede invocarse varias veces. En el siguiente ejemplo, el método execute de la tarea t es ejecutado exactamente 2 veces:




<?xml version="1.0" encoding="iso-8859-1"?>
<project name="variasveces" default="tres">
<target name="uno">
<echo message="uno"/>
</target>

<target name="dos" depends="uno">
<echo message="dos"/>
</target>

<target name="tres" depends="dos">
<echo message="tres"/>
</target>
</project>



Integración de Ant con editores y entornos externos.




Durante la ejecución de Ant, se generan eventos que pueden ser dirigidos a otras aplicaciones. jEdit es un ejemplo de editor que puede trabajar estrechamente con Ant.



En un próximo artículo incluiremos información adicional sobre esta característica de Ant.




Puedes bajarte todo el código y los ficheros build de éste artículo en un solo fichero de aquí




Conclusión



Ant es una potente herramienta de desarrollo que proporciona una gran cantidad de tareas estándar para realizar multitud de acciones comunes. Su flexibilidad va más allá, al ofrecer un mecanismo sencillo pero potente de ampliación del juego básico de tareas.



En este artículo se han cubierto los primeros pasos para poder desarrollar nuestras propias tareas de Ant, a través de varios ejemplos sencillos que el lector sabrá ampliar y personalizar para adaptarlos a sus necesidades.

















Emili Miedes es Ingeniero en Informática y trabaja de momento
para el Instituto Tecnológico de Informática de la Universidad
Politécnica de Valencia desarrollando en Java.


Desarrollador opensource en sus ratos libres (buscar por emiedes en Sourceforge) anda buscando colaboración para acabar todo lo que empieza.




Para cualquier duda o tirón de orejas, e-mail a:
emiedes_ARROBA_iti.upv.es




sábado
jun012002

Creacrión de pools de objetos


Construye un pool de objetos en Java



Aumenta el rendimiento, eficiencia y velicidad de tus aplicaciones.




Autor: THOMAS E. DAVIS
Traductor: VICENTE REIG RINCÓN DE ARELLANO

Artículo origianl: http://www.javaworld.com/javaworld/jw-06-1998/jw-06-object-pool_p.html






Resumen




Un pool de objetos es una estructura de datos que permite compartir los objetos que almacena entre varias
aplicaciones o entre los diferentes módulos que las componen. En este caso particular, hablamos de objetos en Java.
De esta manera, conseguimos eliminar el tiempo de carga, pues no necesitamos instanciar o construir esos objetos,
y a la vez reducimos el trabajo del recolector de basura (garbage collector). En este artículo se implementa dicho
pool de objetos y a partir de ahí, un pool de conexiones a una base de datos.



En este artículo, se tiene en cuenta
que el lector está familiarizado con la Herencia en Java y conocimientos básicos de la API de conectividad con bases
de datos JDBC: Java DataBase Connectivity. (1.664 palabras).








Cuando nos sumergimos en el mundo de las aplicaciones
multiusuario en tiempo real, es conveniente implementar una estructura de datos que incremente drásticamente el
rendimiento de nuestro programa. Por ejemplo, suponiendo que el tiempo medio que nuestra aplicación invierte en
transacciones básicas (como construir objetos) sea de unos 250 milisegundos, utilizando un pool de objetos
conseguiremos reducir ese tiempo hasta los 30 milisegundos.



La idea básica de un pool de objetos es muy similar a
cómo se trabaja en una biblioteca: si quieres leer un libro, lo tomas prestado, lo lees y finalmente lo devuelves.
Obviamente, es una operación mucho más económica que comprarlo y eso lo notan nuestros bolsillos. De la misma forma,
para un proceso es más económico (con respecto a memoria y velocidad) tomar prestado un objeto, trabajar con él y
finalmente devolverlo a su lugar de origen. En otras palabras, supongamos que nuestra aplicación necesita un objeto,
y en vez de instanciarlo lo obtiene de una estructura que contiene ya esos objetos construidos: el pool de objetos.
Hay que tener en cuenta que existen unas pequeñas diferencias entre nuestro pool y la biblioteca. Es decir,
si necesitamos un libro en concreto, pero todas las copias están cogidas, tendremos que esperar a que alguien
devuelva una copia para poder obtener la nuestra. Está claro que nuestra aplicación no puede estar esperando
tranquilamente a que otras devuelvan el objeto que necesita, por lo que el pool creará copias nuevas cuando sea necesario.
Para no saturar nuestro pool de objetos no utilizados habrá que buscar alguna forma de poder limpiarlo periódicamente.




El pool implementado en este artículo es lo suficientemente genérico como para gestionar el almacenamiento,
el seguimiento
y los tiempos de expiración de los objetos almacenados. Por el contrario, la construcción, validación y destrucción
de los mismos se conseguirán empleando la Herencia (subclassing). Es decir, si queremos un pool de conexiones a
una base de datos, partiremos del comportamiento base del pool de objetos, heredando atributos y métodos de la
clase que lo define, y añadiendo el resto de la funcionalidad en la clase derivada resultante.



Ahora que tenemos
claros los conceptos básicos, pasemos a la práctica. A continuación puedes apreciar la implementación del pool de
objetos. Parte del código señalado por los puntos suspensivos (...) se verá más adelante.



import java.util.Hashtable;
import java.util.Enumeration;

public abstract class PoolObjetos{
private long tiempoExpiracion;
private Hashtable bloqueados, nobloqueados;

PoolObjetos(){...}

abstract Object crear();
abstract boolean validar( Object o );
abstract void expirar( Object o );
synchronized Object checkOut() {...}
synchronized void checkIn( Object o ){...}
}



El almacenamiento interno de los objetos se gestionará con dos tablas hash, una para los objetos bloqueados
y otra para los no bloqueados. Los mismos objetos serán las claves de las entradas de la tabla hash y la última
vez que se utilizó (en milisegundos), el valor asociado a cada clave. De esta manera, si un objeto supera el tiempo
de expiración, podremos eliminarlo y liberar memoria. A título de curiosidad, el pool de objetos debería permitir a
las clases que hereden de él establecer el tamaño inicial de las tablas hash, su factor de carga y el
tiempo de expiración. Estos parámetros se han preestablecido en el constructor para no complicar más este ejemplo.



PoolObjetos(){
tiempoExpiracion = 30000; // 30 segundos
bloqueados = new Hashtable();
nobloqueados = new Hashtable();
}



Lo primero que hace el método checkOut() es comprobar si hay objetos en la tabla de bloqueados.
De ser así, itera en la búsqueda de uno de ellos que sea válido. Ese proceso de validación depende de dos cosas.
Primera, el pool comprueba que el tiempo en que se utilizó ese objeto por última vez no supera el tiempo de expiración,
la clase que marca esa última vez es la derivada de nuestra PoolObjetos.
Segunda, el pool llama el método abstracto validate() , definido en la subclase, que comprueba alguna propiedad
específica del propio objeto para poder reutilizarlo. Si esta validación falla, se libera el objeto y
continua el bucle al siguiente objeto en la tabla.



Cuando encontramos un objeto válido, lo movemos a la
tabla de bloqueados y lo entregamos al proceso o aplicación que lo ha llamado. Si la tabla de no bloqueados
está vacía, o no contiene objetos válidos, tendremos que instanciar nuevo objeto.



synchronized Object checkOut() {
long ahora = System.currentTimeMillis();
Object o;
if( nobloqueados.size() > 0 ){
Enumeration e = nobloqueados.keys();
while( e.hasMoreElements() ){
o = e.nextElement();
if( ( ahora - ( ( Long ) nobloqueados.get( o ) ).longValue() ) > tiempoExpiracion ){
// el objeto ha expirado
nobloqueados.remove( o );
expirar( o ); o = null;
}
else{
if( validar( o ) ){
nobloqueados.remove( o );
bloqueados.put( o, new Long( ahora ) );
return( o );
}
else{
// no es un objeto válido
nobloqueados.remove( o );
expirar( o );
o = null;
}
}
}
}
// no hay objetos disponibles, por lo que creamos uno nuevo
o = crear();
bloqueados.put( o, new Long( ahora ) );
return( o );
}



Ya pasó lo peor, de aquí en adelante todo se vuelve cuesta abajo. El método checkIn() simplemente mueve el objeto,
dado por parámetros, desde la tabla de bloqueados ha la de los no bloqueados.



synchronized void checkIn( Object o ){
bloqueados.remove( o );
nobloqueados.put( o, new Long( System.currentTimeMillis() ) );
}



Los tres métodos restantes son abstractos, por lo que tendremos que implementarlos en la subclase.
Una aplicación interesante y frecuente del pool de objetos es la de implementar un pool de conexiones
a una base de datos. A continuación, la clase encargada de todo esto: JDBCConnectionPool.



import java.sql.*;

public class JDBCConnectionPool extends PoolObjetos {

private String servidor, usuario, contraseña, bd;

public JDBCConnectionPool(String driver,
String servidor,
String usuario,
String contraseña, String bd ){
...
}

Object crear() {...}
boolean validar( Object o ) { ... }
void expirar( Object o ) {...}
public Connection getConnection() { ... }
public void returnConnection( Connection c ) { ... }

}


El pool de conexiones necesitará que nuestra aplicación le especifique el driver de nuestra base de datos, el servidor,
nombre de usuario, contraseña y base de datos a través del constructor. Esto último son conceptos específicos de la API JDBC
(Java DataBase Connectivity).




public JDBCConnectionPool(String driver,
String servidor,
String usuario,
String contraseña,
String bd )
{
try {
Class.forName( driver ).newInstance();
}
catch( Exception e ){
e.printStackTrace();
}
this.servidor = servidor;
this.usuario = usuario;
this.contraseña = contraseña;
this.bd = bd;
}


Ahora procederemos a implementar los métodos abstractos definidos en la clase PoolObjetos:
crear(), expirar() y validar().



Como ya vimos en el checkOut() , la clase PoolObjetos invocará el método crear()
desde la subclase, cuando necesite instanciar un nuevo objeto.
Para nuestro pool de conexiones, todo lo que tenemos que hacer es crear un nuevo objeto de tipo
Connection y devolverlo. De nuevo, para no complicar este artículo, simplemente se capturan las posibles
excepciones y no hacemos nada al respecto.



Object crear(){
try {
return(DriverManager.getConnection( jdbc:mysql://" +
servidor + ":3306/" + bd +
"?user="+usuario+";password="+contraseña ) );
}
catch( SQLException e ) {
e.printStackTrace();
return( null );
}
}


Antes de que el pool de objetos libere un objeto expirado o inválido al recolector de basura,
se lo pasa al método expirar() de la subclase por si hay que hacer algún tipo de limpieza en el último
momento. (esto es muy parecido al método finalize() del recolector de basura).
En el caso del JDBCConnectionPool , lo único que tenemos que hacer es cerrar esa conexión.




void expirar( Object o ){
try {
( ( Connection ) o ).close();
}
catch( SQLException e ){
e.printStackTrace();
}
}



Y finalmente, necesitamos implementar el método validar() que el pool de objetos llama para asegurarse
de que un objeto todavía es válido para usarse. También éste es el lugar par realizar alguna reinicialización .
Para nuestra clase JDBCConnectionPool simplemente comprobamos que la conexión a la base de datos continúa abierta.



boolean validar( Object o) {
try {
return( ! ( ( Connection ) o ).isClosed() );
}
catch( SQLException e ){
e.printStackTrace();
return( false );
}
}



Con los siguientes métodos, el pool de conexiones permitirá a nuestra aplicación tomar o devolver conexiones.



public Connection getConnection() {
return( ( Connection ) super.checkOut() );
}

public void returnConnection( Connection c ) {
super.checkIn( c );
}



Por último, solo queda comentar que este diseño un par de defectos. Probablemente, el peor sea la posibilidad
de crear un pool de objetos tan grande que habrá elementos que nunca se utilicen, por lo que ya no se aplica
la definición de comportamiento de un pool. Por ejemplo, en el caso de que un montón de procesos requieran un
objeto del pool simultáneamente, se construirán todas las intancias necesarias. Por lo que, si todos los
procesos devuelven los objetos a nuestro pool y el checkOut() nunca se vuelve a llamar,
no se eliminará ninguno de los objetos. Ésta es una situación poco común, pero puede llegarse a dar con
procesos que tengan un tiempo de espera o idle time. Podemos solventar este problema con un
thread o hilo especializado en la limpieza del pool. Pero el autor se reserva este tema para otro
artículo siguiente, cubriendo también la gestión de errores y la propagación de excepciones para hacer un pool más robusto.




Podeis descargar el código de ejemplo de este artículo de aquí








Sobre el Autor: Thomas E. Davis es un Programador Certificado por Sun.
Actualmente vive en el sur de Florida, pero debido al exceso de trabajo no puede salir mucho de casa.















Vicente Reig Rincón de Arellano, es un estudiante de 2º de Ingeniería
Informática Superior residente en Madrid.






Para cualquier duda o comentario: vitxo_ARROBA_ya.com



viernes
may312002

Nueva versión de James

El equipo de James, el servidor de correo de la fundación Apache, ha hecho pública una nueva versión del servidor, la 2.0a3, que se ha centrado en darle más estabilidad y en corregir bugs.




James tiene soporte para SMTP, POP3, IMAP, mails y usuarios almacenados en un SGBD, administración remota, etc, y ahora trabajan en el soporte de LDAP para gestión de usuarios y en incluir un servidor NNTP.




Como última característica interesante esta la existencia de un API de Mailets, que permite escribir aplicaciones de proceso de emails de forma relativamente sencilla.




Es posible que esto no sirva de mucho para los programadores, pero puede que si a algún administrador de sistemas en busca de un interesante servidor de correo empresarial open source, y como poco nos sirve para ver todo lo que se puede hacer con Java.
jueves
may302002

Ejecutables Java sin JRE con SWT+Excelsior JET

Uno de los puntos débiles que muchos desarrolladores le han puesto siempre a Java es la imposibilidad de crear ficheros nativos.

Son muchas las discusiones que ha habido y que todavía hay sobre si realmente esto es necesario tal como está actualmente el mundo de la informática y no voy a entrar en ellas.


La noticia es que Excelsior JET 2.5 ahora soporta SWT (Standard Widget Toolkit).


Para los que no lo conozcáis, SWT es un conjunto de componentes gráficos que aprovechan la funcionalidad de los componentes nativos de la plataforma donde se encuentren. Su funcionamiento es intermedio entre Swing y AWT. SWT siempre intentará utilizar los componentes nativos pero en el caso de que un determinado componente no se encuentre en una plataforma determinada entonces utilizará una implementación ligera en Java.


SWT ha sido desarrollado por IBM y se creó inicialmente para la plataforma eclipse y podéis ver más sobre el aquí.


La consecuencia de esto es que si unimos SWT + Excelsior JET podremos crear aplicaciones gráficas que no necesiten un JRE.


¿Todavía no os lo creéis? Bajaros el ejecutable de prueba de la web de Excelsior y comentar que os parece. A mi personalmente me ha dejado impresionado. El tiempo de carga es mínimo.


¿Hora de cambiarse a SWT?