jueves, 14 de diciembre de 2006

Tomcat Native Library

La documentación del Tomcat Native dice que se puede encontrar esa biblioteca en el directorio $TOMCAT_HOME/bin.. pero cada vez que inicio el tomcat me aparece la advertencia "se encontró la versión 1.1.3, considere actualizarlo a una versión superior al 1.1.4"... pero debería venir eso con el tomcat!.. je, encontré un servidor mirror con esos archivos:

http://tomcat.heanet.ie/


Aunque ahora que lo pienso... creo que debí examinar su sección "browse download area" de la sección "Tomcat connectors downloads".

¡AH! no olvidar bajar el APR (Apache Portable Runtime) y compilarlo antes de compilar el Tomcat Native Library. Asegurarse que sea la última versión. Si viene el APR de un RPM (como en CentOS o Fedora), desinstalarlo antes.

domingo, 26 de noviembre de 2006

String to XML

Necesitaba una función que me permitiera convertir una cadena en un objeto para manipular XML. Lo que hacía era crear un archivo .xml, le colocaba la cadena, lo cerraba y lo abría después con el objeto Document. Pero aquí tengo otra función mejor



String xmlString = request.getParameter("PARAM1"); //obtengo la cadena
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); //un factory
DocumentBuilder builder = factory.newDocumentBuilder(); //el documento
Document document = builder.parse(new InputSource(new StringReader(xmlString))); //aqui le paso al Document

lunes, 20 de noviembre de 2006

SCJP 5 - Genericos

Estudiando para dar mi examen de SCJP 5 encontré algo que llaman "genéricos". Recordarás que ahora las colecciones ahora pueden ser con tipo:

ArrayList perros=new ArrayList();
perros.add(new Perro("Fido"));


se ve bonito, y luego bueno de esto es que el
compilador no permitirá algo como esto:

perros.add(new Integer(1));


Así, también se cumple la herencia:

List
perros=new ArrayList ();

Pero esto no se puede hacer:

import java.util.*;


class Animal{}
class Perro extends Animal{}


public class Test {
public static void main(String[] args){
List<
Animal > animales=new ArrayList< Perro >();
}

}



¿Por qué? ¡si Perro ES-UN animal! Pues bien, esto corrige un problema que tienen los arreglos comunes:



import java.util.*;
class Animal{}
class Perro extends Animal{}
class Gato extends Animal{}
public class Test {
public static void main(String[] args){

Perro[] perros=new Perro[]{new Perro()}; //arreglo de perros

agregar(perros);
}

private static void agregar(Animal[] animales) { //es permisible xq Perro ES-UN Animal

animales[0]=new Gato(); //OUCH!! esto no debería ser

}

}



Compila, y cuando se ejecuta manda una excepción (ArrayStoreException). La cuestión es que las colecciones con tipo asegura que los valores que se les pasa no tengan errores de este tipo al momento de la compilación. Por ello, debe asegurar que sean del mismo tipo-de-la-colección. Entonces, esto no compilaría, porque las colecciones se aseguran que tengan el mismo tipo.

//...
public static void main(String[] args){
List perros=new ArrayList();
perros.add(new Perro());

agregar(perros); //no se puede aplicar tipos de colecciones diferentes.

}

private static void agregar(List animales) {
animales.add(new Gato());
}
//...


Pero, claro está,
no vamos a crear tantos métodos cómo subclases tengamos . Aquí entran los "genéricos". Se puede declarar parámetros de métodos que sean de tipo (subtipos o supertipos) de una clase en especial. Entonces, para que nuestro método compile aceptando cualquier colección, debería ser así:

//..
public static void main(String[] args){
List perros=new ArrayList();
perros.add(new Perro());
agregar(perros);

List gatos=new ArrayList();
agregar(gatos); //tambien puedo llamar al mismo método


}

private static void agregar(List animales) { //usando genéricos

//animales.add(new Gato()); //pero esta línea no va a compilar
for(Animal animal:animales) {
//pero puedo manejar cada elemento por igual
}
}
//..


Ajá, con esto aseguro que solo se permitan manejar todos por igual. Pero, si deseo agregar elementos a la colección? Java no va a permitir cualquier cosa que se agregue. Para ello se puede utilizar la palabra clave
super en lugar de extends . Aquí un código más completo para entender cómo funciona:

import java.util.*;
abstract class Animal{ //la clase Animal, tambien puede ser interface
public abstract void dejarseRevisar();
}
class Perro extends Animal{ //La implementacion en Perro
public void dejarseRevisar() {
System.out.println("El Perro se deja revisar");
}}
class Gato extends Animal{ //La implementacion en Gato
public void dejarseRevisar() {
System.out.println("El Gato se deja revisar");
}}
class Poodle extends Perro{ //La implementacion en Poodle que ES-UN Perro
public void dejarseRevisar() {
System.out.println("El Poodle se deja revisar");
}}
public class Test {
public static void main(String[] args){

Veterinario veterinario=new Veterinario(); //tenemos un Veterninario


List perros=new ArrayList(); //creamos nuestra lista de perros
perros.add(new Perro()); //agregamos uno

veterinario.revisar(perros); //lo mandamos a revisar, es tratado como Animal


List gatos=new ArrayList(); //creamos nuestra lista de gatos
gatos.add(new Gato());

veterinario.revisar(gatos); //lo mandamos a revisar, es tratado como un Animal


List poodles=new ArrayList(); //creamos nuestra lista de poodles
poodles.add(new Poodle());

veterinario.revisar(poodles); //.... idem...


List animales=new ArrayList(); //creamos una lista de animales

veterinario.agregarPerros(animales); //y lo manejamos solo para perros a pesar que es una coleccion de animales

}

}
class Veterinario{

void revisar(List animales){ //todos son tratados como animales

for(Animal animal:animales)
animal.dejarseRevisar();
}

void agregarPerros(List perros){

perros.add(new Perro());
perros.add(new Poodle());

//perros.add(new Gato()); //no lo permite, porque lo vamos a manejar como colección de Perros


}
}


Bacán, no? (je). además, esto fue un ejemplo para los argumentos, pero también funciona en variables (con ejemplos de los que no pueden ser compilablres):

List list = new ArrayList(); //compila
List aList = new ArrayList(); //compila

List foo = new ArrayList(); //no compila
List cList = new ArrayList(); //no compila porque Integer no ES-UN Perro

List bList = new ArrayList(); //compila

List dList = new ArrayList(); //no compila porque Perro no es una super-clase de Animal


Ahora, dirás "yo también quiero tener mi clase que permita genéricos". Pues es sencillo. Pongamos el escenario: imaginemos que tengamos una lógica para manejar los alquileres de lo que fuere:

import java.util.*;
class Alquilar{
private List listaAlquiler;
private int maxAlquiler;
public Alquilar(int max, List lista){
maxAlquiler=max;
listaAlquiler=lista;
}
public Object alquilar(){
//algo que maneje la disponibilidad de alquiler
return listaAlquiler.get(0);
}
public void regresa(Object o){ //regresa el elemento alquilado
listaAlquiler.add(o);
}
}


Ahora, queremos implementar esta lógica para alquilar autos:

class Auto{}
class AlquilarAuto extends Alquilar{
public AlquilarAuto(int m,List l){
super(m,l);
}

public Object alquilar() {
return super.alquilar();
}

public void regresa(Object o) {
if (o instanceof Auto)
super.regresa(o);
else
System.err.println("Aqui no se devuelve");
}
}

Pero, si queremos usar la misma lógica para alquilar casas, uartos, computadoras? ¿tendriamos que extender siempre la misma la clase para hacer solo llamadas al
super ? Bastaría con tener una misma lógica, y dependiendo de cómo se instancie, debería de comportarse por igual, no? además no deberí haber esa validación de instanceof en el método regresa().

class
AlquilarGenerico { //de algun T ipo
private List listaAlquiler; //manejará el mismo tipo

private int maxAlquiler;

public AlquilarGenerico(int max,
List lista){ //recibe el mismo tipo con que fue instanciado
maxAlquiler=max;
listaAlquiler=lista;
}

public
T alquilar(){ //retorna el mismo Tipo
//algo que maneje la disponibilidad de alquiler
return listaAlquiler.get(0);
}

public void regresa(
T o ){ //recibe del mismo tipo que fue instanciado
listaAlquiler.add(o);
}
}



Podría haber sido cualquier letra en lugar de T, pero por convencion, se usa la letra T para referirse a los Tipos. Entonces, ya se podría usar algo así:

//...
class Auto{}
class Casa{}


public class Test2 {
public static void main(String[] args){
List autos=new ArrayList();
autos.add(new Auto());
AlquilarGenerico alquilarAuto=new AlquilarGenerico(100,autos);
Auto a1=alquilarAuto.alquilar();
alquilarAuto.regresa(a1);

List casas=new ArrayList();
casas.add(new Casa());
AlquilarGenerico alquilarCasa=new AlquilarGenerico(100,casas);
Casa c1=alquilarCasa.alquilar();
alquilarCasa.regresa(c1);

}

}

Ahora, no solo depende de cómo fue instanciado, sino también depende de cómo se llame al método. Es decir, así:


class CreateAnArrayList {

public
void makeArrayList(T t) {

List list = new ArrayList();
list.add(t); //un método nada útil, pero se conoce como influye el
}
}


Pareciera que la el método es de tipo y a la vez es un void. Pues escrito así dice que todo lo que está con será el tipo genérico. Esto se podría llamar así:

CreateAnArrayList cal=new CreateAnArrayList();
Auto a2=new Auto();
cal.makeArrayList(a2);



también podriamos restringirlo para que solo permita números

class CreateAnArrayList {

public
void makeArrayList(T t) {

List list = new ArrayList();
list.add(t);
}
}

y el bloque anterior ya no compilaría, porque a2 es un Auto y no es Number.


Ahora, también serviría como constructor de una clase.

class Radio {

public Radio(T t) { }

}

domingo, 1 de octubre de 2006

Vergüenza de programador

Para el escale utilicé al principio Hibernate, pero me fue pesado aprender HQL, y los reportes que emitia se cortaban. Así que pasé a iBatis Mapper y me fue más fácil, flexible y rápido.. incluso usé iBatis DAO por su idependencia de implementación DAO.

Pero desde que lo puse, no recuerdo qué mes, el procesador se satura con el java... llegaba a ocupar casi el 100% del procesador... y mandaba error de Java Heap Memory (no indicaba la línea)

Continuamente reiniciaba el tomcat, modifiqué el pool de conexiones, aligeré código de consultas... y nada. Incluso puse en un cron para que se reinicié el tomcat cada 2 horas..... y nada.

Encontré en la web que el kernel de linux que usaba (2.4) provocaba ese error con java.. así que cambié a CentOS 4.4

Y nada.

Prácticament al día tenía que reiniciarlo manualmente 6 veces.

Supuse que el diseño de la página estaba mal, xq hacia llamadas recursivas para mostrar el menú de opciones.. así que con mi compañero nos pusimos a pasar todas las páginas a tiles. Cambié la biblioteca de ajax (usaba ajaxtaga y lo modifiqué para usar DWR), y cuando estaba haciendo las pruebas (demasiadas) con DWR.. volvió el mismo problema.

"Afortunadamente" tenía el log al detalle, y el error ocurría cuando hacía una consulta... y antes de hacer una consulta le pedía la conexión al iBatis DAO... y cada vez que hacía esta llamada, volvía abrir el archivo dao.xm y procesaba el stream para obtener el daoManager... siempre.

Así que cambié esa línea: colocar el daoManager como static y que se cargué sus valores en el static{} de la clase. Es decir, solo una vez.

Ya van más de 24 horas contínuas y no ha vuelto a dispararse el procesador.

Así que creo que tendrá para muchas horas más... y días.

Solo fue una línea mal programada que dió un dolor de cabeza por varias semanas.

jueves, 31 de agosto de 2006

Leyes del programador

Hace tiempo encontré esto, me parece muy bueno.
  1. Todo programa que empieza bien, acaba mal.
  2. Todo programa que empieza mal, acaba peor.
  3. Siempre trabaja en equipo al hacer algún programa, esto da oportunidad de echarle la culpa a otro.
  4. Si el Debuggear es el proceso de remover Bugs, entonces el programar debe ser el proceso de ponerlos
  5. Por cada Bug que elimines en un programa, aparecen tres más.
  6. Programar es como el sexo, un error y hay que soportarlo para el resto de la vida.
  7. Las dos frases más repetidas y más falsas en la programación: "Esta ocasión si correrá bien" y "Acabo de encontrar el último Bug".
  8. Todos los programadores en esencia son optimistas, hasta que terminan su programa.
  9. Cada programa interesante tiene cuando menos una variable, un procedimiento, un loop.....y de hecho un bug.
  10. Las computadoras siempre tienen una excusa, los programadores no.
  11. No importando cuanto tiempo hayas probado la versión final de un programa, los bugs aparecerán en la presentación al público. (Ley de Microsoft)
  12. Todo programa tiene cuando menos dos propósitos: Uno para lo cual fué escrito y otro para el que no fué.
  13. La utilidad de un software es inversamente proporcional a la cantidad de usuarios.
  14. Es imposible hacer un programa 100% contra tontos, ya que estos son muy ingeniosos.
  15. La peor parte de la programación es la documentación.
  16. "100% compatible" es una expresión 50% falsa.
  17. Cualquier programa, cuando funciona, ya es obsoleto.
  18. Cualquier programa cuesta más y dura cada vez más que se ejecuta.
  19. Si un programa es útil, deberá ser modificado.
  20. Si un programa no es útil, deberá ser documentado.
  21. Cualquier programa tiende a expandirse hasta llenar toda la memoria disponible.
  22. El valor de un programa es inversamente proporcional al peso de sus "outputs".
  23. La complejidad de un programa crece hasta que excede la capacidad del programador que debe mantenerlo.

jueves, 1 de junio de 2006

iBatis Datamapper (sql maps) parte 1

El framework iBatis DataMapper (también conocido como SQL
Maps) permite reducir significativamente la codificación en
java para una aplicación que maneja base de datos relacional.
Quizás la primera impresión que uno tenga es que sea
igual a Hibernate (que es un mapeador de objetos con tablas
relacionales – ORM). Ibatis es diferente. Como veremos en este
tutor, iBatis mapea las consultas SQL y permite interactuarlas con
JavaBeans tanto como parámetros de entrada y como salidas.


Manos a la obra


Para comenzar, obtendremos el framework de la página de
ASF: http://ibatis.apache.org/
El archivo que habremos bajado (iBATIS_DBL-2.1.7.XX.zip) contendrá
tres archivos .jar. En este tutor solo usaremos ibatis-common-2.jar y
ibatis-sqlmap-2.jar. No necesita de algún otro .jar, al menos
para este capítulo.


Definiendo la estructura de los datos


Crearemos nuestro JavaBean Categoria con la
siguiente estructura.


package com.jugperu.tutores.ibatis.beans;

public class Categoria {
private int id; // podemos utilizar tipos de datos nativos
private String nombre;
/*... poner sus respectivos métodos
setXX() y getXX() para que tenga el
patrón JavaBean (value object)*/
}


También necesitamos crear nuestra base de datos. Podemos usar
cualquier motor; naturalmente necesitaremos su respectivo driver.


Crearemos nuestra tabla CATEGORIA, la cual tendrá la
siguiente estructura:




















Nombre



Tipo



CAT_ID



Autonumérico (clave principal)



CAT_NOMBRE



Varchar(30)



Como se puede ver, hemos puesto nombres de campos distintos a las
propiedades de nuestro JavaBean. Veremos como en iBatis podremos
asociar cada columna con su respectiva propiedad en los objetos.


Configuración de la conexión a la base de datos
desde nuestra aplicación


Crearemos el archivo database.properties dentro del paquete
com.jugperu.tutores.ibatis.resources el cual tendrá los
siguientes valores:


#el driver de nuestra base de datos
jdbc.driver=org.hsqldb.jdbcDriver
#el url para nuestra conexion.
jdbc.url=jdbc:hsqldb:file:data/productos.hsqldb
#el usuario
jdbc.username=sa
#y la contraseña
jdbc.password=


Crearemos el archivo SqlMapConfig.xml que tendrá el
siguiente contenido:


<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMapConfig
PUBLIC "-//iBATIS.com//DTD SQL Map Config 2.0//EN"
"http://www.ibatis.com/dtd/sql-map-config-2.dtd">
<sqlMapConfig>
<properties resource="com/jugperu/tutores/ibatis/resources/database.properties"/>
<transactionManager type="JDBC">
<dataSource type="SIMPLE">
<property name="JDBC.Driver" value="${jdbc.driver}"/>
<property name="JDBC.ConnectionURL" value="${jdbc.url}"/>
<property name="JDBC.Username" value="${jdbc.username}"/>
<property name="JDBC.Password" value="${jdbc.password}"/>
</dataSource>
</transactionManager>
<sqlMap resource="com/jugperu/tutores/ibatis/resources/Almacen.xml"/>
</sqlMapConfig>


El atributo “resource” del elemento “properties”
debe apuntar a la ubicación del archivo .properties que
acabamos de crear. Debe estar en posición relativa a nuestras
clases. Si deseamos utilizar una ubicación que esté
fuera del alcance de la aplicación, utilizaremos el atributo
“url”.


  <properties url="file://c:/proyecto/database.properties"/>


Vemos que las propiedades de las conexión (elementos <property
/>) utiliza variables como ${jdbc.driver}. Esto es porque
está utilizando los valores nuestro archivo
database.properties. El archivo SqlMapConfig.xml solo
puede utilizar un archivo .properties para estos casos.


El elemento <sqlMap/> apunta a un archivo
Almacen.xml. Este archivo lo describiremos a continuación.
Al igual que el elemento <properties /> se puede
especificar un url para apuntar a un recurso que se encuentre fuera
del alcance de la aplicación.


Los demás elementos de este .xml lo detallaremos en el
siguiente capítulo.


Definiendo el mapa de consultas.


Crearemos el archivo Almacen.xml dentro del paquete
com.jugperu.tutores.ibatis.resources el cual tendrá el
siguiente contenido


<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMap
PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN"
"http://www.ibatis.com/dtd/sql-map-2.dtd">
<sqlMap namespace="almacen">
<typeAlias type="com.jugperu.tutores.ibatis.beans.Categoria" alias="categoria"/>
<insert id="insertCategoria" parameterClass="categoria">
insert into CATEGORIA
(cat_nombre)
values (#nombre#)
</insert>
<select id="getCategoriaPorId" resultClass="categoria" parameterClass="int">
select cat_id as id, cat_nombre as nombre
from categoria
where cat_id=#value#
</select>
</sqlMap>


Detallaremos el contenido de este .xml:


El elemento <typeAlias /> nos permitirá
describir un alias para evitar escribir un nombre clase largo. En
este caso, en vez de escribir
"com.jugperu.tutores.ibatis.beans.Categoria"
usaremos “categoria”


Vemos que tiene elementos que se pueden asociar rápidamente
a las sentencias SQL. Todos estos elementos tienen el atributo “id”.
Este nos permitirá identificar a cada uno de ellos.
Detallaremos la estructura de cada uno:


  <insert id="insertCategoria" parameterClass="categoria">
insert into CATEGORIA
(cat_nombre)
values (#nombre#)
</insert>


Este elemento recibe como parámetro un objeto cuyo tipo está
definido en el atributo “resultClass”. Como el nombre
completo de nuestra clase es bien largo, hemos definido un alias con
el elemento <typeAlias />. Por tanto, el tipo del
parámetro es com.jugperu.tutores.ibatis.beans.Categoria.


El comando INSERT debe guardar relación con la sintaxis del
motor que estamos usando. Los valores encerrados en signos numerales
(#) hacen referencia a las propiedades del objeto recibido como
parámetro.


El elemento <select> recibe como parámetro un
valor numérico “int” y devuelve devuelve un objeto
de tipo “categoria”. El parámetro “int”
es un alias predefinido de la clase java.lang.Integer. Por
tanto, al invocar a esta sentencia SQL pasaremos como parámetro
un objeto Integer.


  <select id="getCategoriaPorId" resultClass="categoria" parameterClass="int">
select cat_id as id, cat_nombre as nombre
from categoria
where cat_id=#value#
</select>


En este elemento tiene una sentencia SQL donde los campos que son
seleccionados tienen un alias. Es decir, el campo CAT_ID tiene como
alias “id”, y CAT_NOMBRE tiene como alias “nombre”.
De esta manera iBatis colocará cada campo de la tabla
resultante y los colocará en sus respectivas propiedades del
objeto a devolver.


Programa de prueba


La mejor manera de hacer un programa de prueba es utilizando un
TestCase en JUnit. Crearemos nuestro TestCase
llamándolo IbatisMapsTestCase y tendrá el siguiente
método setUp().


        Reader reader = Resources.getResourceAsReader("SqlMapConfig.xml");
// el .xml para la conexión
sqlMap = SqlMapClientBuilder.buildSqlMapClient(reader);
//construye el manejador de llamadas al iBatis


Crearemos nuestro test para registrar objetos Categorias.


    public void testInsertarCategorias() throws SQLException {
try {
sqlMap.startTransaction();
Categoria c1 = new Categoria();
Categoria c2 = new Categoria();
Categoria c3 = new Categoria();
c1.setNombre("memorias");
c2.setNombre("placas");
c3.setNombre("procesadores");

sqlMap.insert("insertCategoria", c1);
sqlMap.insert("insertCategoria", c2);
sqlMap.insert("insertCategoria", c3);

sqlMap.commitTransaction();
} catch (SQLException ex) {
ex.printStackTrace();
assertTrue(false);
} finally {
sqlMap.endTransaction();

}
}


También haremos un test para obtener un objeto.


    public void testGetCategoriaPorId() throws SQLException {
try {
sqlMap.startTransaction();

Categoria c1 = (Categoria) sqlMap.queryForObject(
"getCategoriaPorId", Integer.valueOf(2));
assertNotNull(c1);
mostrar(c1);

} catch (SQLException ex) {
ex.printStackTrace();
assertTrue(false);
} finally {
sqlMap.endTransaction();
}
}

private static void mostrar(Categoria cat) {
System.out.println(cat.getId() + "\t" + cat.getNombre());
}


Claves autogeneradas


El método “insert” de sqlMap devuelve un objeto
que contiene el valor de la nueva clave generada (si aplica). Pero en
nuestro ejemplo el valor que devuelve es null, a pesar que
hemos declarado que la clave primaria es autonumérica . iBatis
nunca sabrá cual es el valor a menos que pongamos en la
sentencia sql qué valor tiene que devolver.


Editemos Almacen.xml en el
elemento <insert > de tal manera que luzca de la siguiente
manera.


...
<insert id="insertCategoria" parameterClass="categoria">
insert into CATEGORIA
(cat_nombre)
values (#nombre#)
<selectKey resultClass="int">
select distinct identity() as id
from categoria
</selectKey>
</insert>
...


Esta es la sentencia para HSQLDB, que es el motor usado en este
ejemplo. Para MySQL el comando SQL que está dentro de
<selectKey> debería ser:


...
<selectKey resultClass="int">
select last_insert_id() as id
</selectKey>
...


y para SQL Server:


...
<selectKey resultClass="int">
select @@IDENTITY as ID
</selectKey>
...


Manejando resultados


Definiendo mapa de resultados


Hemos visto que para asociar todos los campos de una consulta a
las propiedades del JavaBean, cada uno de estos debe tener un alías.
Lo cual nos puede ser un gran problema, ya que si se tratase de
varios campos, tendríamos que poner varios “as”
por cada uno. Y si fueran varias sentencias SQL, también nos
puede resulta problemático hacer una modificación.


iBatis nos permite crear un mapa de resultado. Allí
podremos definir la asociación que utilizaremos entre
propiedades del objeto y campos de la consulta.


Para ello agregaremos el siguiente elemento en Almacen.xml


  <resultMap id="res-categoria" class="categoria">
<result property="id" column="cat_id"/>
<result property="nombre" column="cat_nombre"/>
</resultMap>


Cada vez que utilicemos como respuesta “res-categoria”,
iBatis se encargará de crear una instancia de “categoria”
y colocará en cada propiedad los valores de cada columna que
se utilice.


Agregaremos una consulta nueva:


  <select id="getAllCategoria" resultMap="res-categoria">
select *
from categoria
</select>


Como se puede ver, ya no es necesario colocar un alias por cada
campo.


Y crearemos un nuevo test para probarlo:


            List lista = sqlMap.queryForList("getAllCategoria", null);
for (Iterator iter = lista.iterator(); iter.hasNext(); ) {
Categoria cat = (Categoria) iter.next();
mostrar(cat);
}


Resultados sin JavaBeans


No necesariamente utilizaremos un JavaBean para obtener un
resultado. Podemos utilizar un java.util.HashMap para obtener
todos los campos de una consulta. Cada key será el
nombre del campo, y su respectivo value será el valor
asociado.


  <resultMap id="res-map-categoria" class="java.util.HashMap">
<result property="id" column="cat_id"/>
<result property="nombre" column="cat_nombre"/>
</resultMap>
...
<select id="getMapCategoriaPorId" resultMap="res-map-categoria">
select *
from categoria
where cat_id=#value#
</select>


El objeto devuelto por queryForObject() será de clase
java.util.HashMap.


            HashMap map = (HashMap) sqlMap.queryForObject(
"getMapCategoriaPorId", Integer.valueOf(1));


Por tanto, podríamos construir una consulta con diversos
campos sin preocuparnos por la estructura que va a devolver.


También podemos obtener solo un campo en una lista.


  <resultMap id="res-categoria-nombre" class="java.lang.String">
<result property="nombre" column="cat_nombre"/>
</resultMap>
...
<select id="getNombresCategoria" resultMap="res-categoria-nombre">
select *
from categoria
</select>


Y cada objeto de la lista obtenida de queryForList() será
un java.lang.String.


            List lista = sqlMap.queryForList("getNombresCategoria", null);


Naturalmente, podríamos crear una consulta que tenga como
resultMap un objeto HashMap e invocarlo con
queryForList() . El resultado será una lista donde cada
elemento será un hashmap.


Manejando parámetros


iBatis también permite definir los parámetros que
van a recibir las consultas. Su estructura es muy similar a los
resultMaps.


  <parameterMap id="par-categoria" class="categoria">
<parameter property="id"/>
<parameter property="nombre"/>
</parameterMap>
//......
<update id="updateCategoria" parameterMap="par-categoria">
update categoria
set cat_nombre=#nombre#
where cat_id=#id#
</update>


También podemos prescindir de un JavaBean para enviar
parámetros. Para ello utilizaremos un java.util.Map.
Cada key se accederá como si fuera una propiedad de un
JavaBean.


  <update id="updateMapCategoria" parameterClass="java.util.Map">
update categoria
set cat_nombre=#nombre#
where cat_id=#id#
</update>


Asociaciones


Uno de los principales problemas del manejo de base de datos
relacionales utilizando objetos es la asociación entre tablas.


Crearemos una tabla PRODUCTO con la siguiente estructura:




























Campo



Tipo



PR_ID



Autonumérico (clave principal)



PR_NOMBRE



Varchar(30)



PR_PRECIO



Decimal



PR_STOCK



Numérico entero



PR_CATEGORIA



Numérico no nulo (clave foránea de CATEGORIA)



Crearemos el JavaBean con sus respectivas propiedades:


package com.jugperu.tutores.ibatis.beans;

public class Producto {
private int id;
private String nombre;
private int stock;
private double precio;
private Categoria categoria;
//.....
}


Agregando un registro


Crearemos un <insert > para manejar la inserción
de objetos a la tabla.


  <typeAlias type="com.jugperu.tutores.ibatis.beans.Producto" alias="producto"/>
//...
<insert id="insertProducto" parameterClass="producto">
insert into producto
(pr_nombre,pr_stock,pr_precio,pr_categoria)
values (#nombre#,#stock#,#precio#,#categoria.id#)
</insert>


Note cómo se accede a una propiedad de Producto
que es de clase Categoria. En caso que la propiedad
categoria fuera nulo, iBatis toma toda la expresión
(categoria.id) como nulo.


Probamos insertar un objeto Producto:


            Categoria cat = (Categoria) sqlMap.queryForObject(
"getCategoriaPorId", Integer.valueOf(1));
Producto p1 = new Producto();
p1.setNombre("Kingston");
p1.setPrecio(300.50);
p1.setStock(5);
p1.setCategoria(cat);

sqlMap.insert("insertProducto", p1);


Es necesario recalcar que iBatis no agrega automáticamente los
objetos asociados que no existan en la base de datos. Es decir, si se
crea un objeto Categoria y se asocia a un nuevo objeto de
Producto, al hacer el insert() solo se guardarán
los valores del objeto Producto y no los del objeto Categoria.


Obteniendo un registro


Para obtener un registro mapeado en
objeto, al <resultMap> se agregará un atributo
más:


  <resultMap id="res-producto" class="producto">
<result property="id" column="pr_id"/>
<result property="nombre" column="pr_nombre"/>
<result property="stock" column="pr_stock"/>
<result property="precio" column="pr_precio"/>
<result property="categoria" column="pr_categoria" select="getCategoriaPorId"/>
</resultMap>


Como se ve, para la propiedad categoria se tomará el
campo pr_categoria y se buscará su valor del select
getCategoriaPorId. Al hacer esto, se invocará a dicho
select y se le pasará como parámetro el valor de
pr_categoria obteniendo el objeto correspondiente.


El select para obtener objetos de
Producto será muy simple:


  <select id="getProductoPorId" resultMap="res-producto">
select *
from producto
where pr_id=#value#
</select>


Y la llamada desde java será la misma que se ha estado
manejando:


            Producto p = (Producto) sqlMap.queryForObject("getProductoPorId",
Integer.valueOf(1));
mostrar(p); //muestra cada campo del objeto “p”
//.....

private static void mostrar(Producto p) {
System.out.println(p.getId() + "\t" + p.getNombre() + "\t" +
p.getPrecio() + "\t" + p.getStock() + "(" +
p.getCategoria().getNombre() + ")");
}


Pero esta solución tiene una deficiencia: para obtener un
producto, iBatis hará dos consultas: uno para el producto y
otro para la categoría. Si fueran varios productos, hará
una consulta por los productos y N consultas por cada producto para
obtener su categoria.


Esto se puede solucionar haciendo un join modificando el
<resultMap >.


  <resultMap id="res-producto-opt" class="producto">
<result property="id" column="pr_id"/>
<result property="nombre" column="pr_nombre"/>
<result property="stock" column="pr_stock"/>
<result property="precio" column="pr_precio"/>
<result property="categoria.id" column="pr_categoria"/>
<result property="categoria.nombre" column="cat_nombre"/>
</resultMap>
....
<select id="getAllProductoOpt" resultMap="res-producto-opt">
select *
from producto p, categoria c
where p.pr_categoria=c.cat_id
</select>


Conclusiones


Con iBatis se puede mapear las consultas que necesitamos para
nuestro proyecto. La sintaxis que se utilice para manejar los
registros de la base de datos está fuertemente aislada en la
lógica de negocio. Esto nos permite tener un código
limpio de sentencias SQL. Si es necesario hacer alguna modificación
en SQL, bastará con editar el XML y no una clase en java
evitando la compilación de esta.

lunes, 1 de mayo de 2006

AJAX

¿Qué es AJAX?

Imaginemos que estamos haciendo un formulario web de registro de
clientes que tiene cuarenta campos. Tres de esos campos son combos
para “departamento”, “provincia” y
“distrito”. Al seleccionar un “departamento”,
el combo “provincia” se debe actualizar con el contenido
correspondiente. De igual manera, al seleccionar una “provincia”:
sus distritos correspondientes deberán aparecer en su
respectivo combo. Para implementarlo tenemos dos maneras:



  • Colocar en el evento “onchange” de los combos un
    submit() para que se envíe el formulario actual al
    servidor, y éste devuelva el mismo formulario (sin perder los
    valores de los demás campos) sólo para que actualice
    las opciones de los combos afectados. Como se puede predecir, se
    estaría desperdiciando el ancho de banda enviando todo un
    formulario sólo para cambiar uno o dos campos. Eso, sin
    contar que la programación de los submit() en el
    lado del servidor debe estar contemplada para saber que lo que hizo
    fue cambiar un combo o se presionó en el botón
    “guardar”

  • Cargar en javascript todos los
    departamentos (26 opciones, considerando a Callao), todas las
    provincias (195) y todos los distritos (1680), programar en
    javascript, y actualizar los combos según convenga. En este
    caso, se estaría enviando todas las opciones a elegir al
    cliente sólo para que seleccione tres de todos ellos.


¿Por qué no combinamos ambas formas?


AJAX es una técnica en desarrollo web que consiste en la
mezcla de tecnologías existentes:



  • XML / XSLT para el envío de datos en XML del servidor
    al cliente (aunque también puede ser texto plano).

  • DOM + Javascript para la manipulación de los datos
    enviados del servidor.

  • HTML / XHTML + CSS que
    involucra a la presentación en lado del cliente.


La técnica consiste en pedir al servidor sólo lo
necesario para que nos devuelva tan sólo lo necesario. Si bien
el formulario – o cualquier página – presentada
por el servidor se hizo una vez, pueden haber partes de esa misma
página que se actualicen después y en cualquier
momento; de ahí el nombre AJAX: Asynchronous Javascript And
XML (Javascript y XML asíncronos).


Por ejemplo, cuando se va a redactar un correo electrónico
desde Yahoo! Mail, se puede escribir parte de la dirección,
nombre o alias del destinatario e irá apareciendo un listado
de los posibles nombres tomados de la lista de contactos.




A continuación, se mostrará cómo implementar
AJAX en nuestras aplicaciones Web. Primero, de una manera nativa.
Después, utilizando Ajax-Tags, una potente biblioteca de tags
disponible en http://ajaxtags.sourceforge.net/.


Conociendo AJAX


Nuestro ejemplo consiste en listar nombres de tipos de producto en
un select. Al seleccionar uno de estos, se mostrarán
sus productos asociados en otro select.


Primero, debemos describir la forma cómo se van a recibir
los productos de un determinado tipo. Podemos utilizar el formato
text/plain, pero, a todas luces, el más recomendable es el XML
ya que podemos enviar en un sóolo archivo varios datos
estructurados.


Entonces, haremos un Servlet que devolverá un XML con los
productos de un determinado tipo enviado por parámetro. Por lo
tanto, utilizaremos el esquema MVC para recuperar la información
de una base de datos. Pero, para evitarnos problemas en la
configuración y llenado de información de la base de
datos, utilizaremos listas bajo el patrón DAO.


Paso 1: Preparando el DAO


Existirán dos clases que serán los beans de Tipo
y de Producto.


package com.jugperu.tutores.ajax.beans;

public class Tipo {
private String id;
private String tipo;
public Tipo(String id, String nombre) {
this.id=id;
this.tipo =nombre;
}

public String getId() {
return id;
}

public String getTipo() {

return tipo;
}

public void setId(String id) {
this.id = id;
}

public void setTipo(String tipo) {

this.tipo = tipo;
}
}
package com.jugperu.tutores.ajax.beans;

public class Producto {
private String id;

private String producto;
private Tipo tipo;
public Producto(String id, String nombre,Tipo tipo) {
this.id=id;
this.producto =nombre;
this.tipo=tipo;
}
public String getId() {
return id;
}

public String getProducto() {

return producto;
}

public Tipo getTipo() {
return tipo;
}

public void setId(String id) {
this.id = id;
}

public void setProducto(String producto) {

this.producto = producto;
}

public void setTipo(Tipo tipo) {
this.tipo = tipo;
}
}


Nuestra interfaz DAO.


package com.jugperu.tutores.ajax.dao;

import java.util.List;
import com.jugperu.tutores.ajax.beans.Producto;

public interface MercadoDAO {
public List<Producto> productosPorTipo(String tipo);

}


y la implementación para manejo de listas. En el constructor,
prepararemos la data para nuestro ejemplo.


package com.jugperu.tutores.ajax.dao;

import java.util.ArrayList;
import java.util.List;

import com.jugperu.tutores.ajax.beans.Producto;
import com.jugperu.tutores.ajax.beans.Tipo;
import java.util.*;

public class ListasMercadoDAO implements MercadoDAO {
private static List<Tipo> tipos = new ArrayList();
private static List<Producto> productos = new ArrayList();

static {
Tipo frutas, verduras, carnes;
tipos.add(frutas = new Tipo("frt", "Frutas"));
productos.add(new Producto("mzn", "Manzanas", frutas));
productos.add(new Producto("per", "Peras", frutas));
productos.add(new Producto("uva", "Uvas", frutas));

tipos.add(verduras = new Tipo("ver", "Verduras"));
productos.add(new Producto("lec", "Lechuga", verduras));
productos.add(new Producto("pap", "Papas", verduras));
productos.add(new Producto("esp", "Espinaca", verduras));

tipos.add(carnes = new Tipo("crn", "Carnes"));
productos.add(new Producto("bis", "Bisket", carnes));
productos.add(new Producto("lom", "Lomo", carnes));
productos.add(new Producto("pol", "Pollo", carnes));

}

public List<Producto> productosPorTipo(String tipo) {
List lista = new ArrayList();
for (Iterator<Producto> iter = productos.iterator(); iter.hasNext(); ) {
Producto pro = iter.next();
if (pro.getTipo().getId().equals(tipo)) {
lista.add(pro);
}

}
return lista;
}

}


Y ahora nuestro DAOFactory.


package com.jugperu.tutores.ajax.dao;

public class DAOFactory {
public static final int LISTAS = 1;

public static MercadoDAO crearMercado(int tipoRDBMS) {
switch (tipoRDBMS) {
case LISTAS:
return new ListasMercadoDAO();
}
return null;
}
}


Este DAO nos permitirá, a parte de tener una buena costumbre
de tener todo en orden, reutilizarlo para otros ejemplos.


Paso 2: Creando el Servlet que devuelve XML


Nuestro servlet, simplemente, obtendrá el parámetro
“tipo”, lo buscará en la 'base de datos',
preparará el XML y lo devolverá al cliente. Si no
existiese, o no hubiera enviado algún parámetro, el
servlet no devolverá nada.


package com.jugperu.tutores.ajax.servlets;

import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
import com.jugperu.tutores.ajax.dao.MercadoDAO;
import com.jugperu.tutores.ajax.dao.DAOFactory;
import com.jugperu.tutores.ajax.beans.Producto;

public class ProductoPorTipoServlet extends HttpServlet {
private static final String CONTENT_TYPE = "text/xml";

public void doGet(HttpServletRequest request, HttpServletResponse response) throws
ServletException, IOException {
response.setContentType(CONTENT_TYPE);
MercadoDAO dao = DAOFactory.crearMercado(DAOFactory.LISTAS);
String tipo = request.getParameter("tipo");
List<Producto> prods = null;
if (tipo != null) {
prods = dao.productosPorTipo(tipo);
if (prods.size() > 0) {
System.out.println("obteniendo productos de "+tipo);
PrintWriter out = response.getWriter();
out.println("<?xml version=\"1.0\"?>");
out.println("<productos>");
for (Iterator<Producto> iter = prods.iterator(); iter.hasNext(); ) {
Producto prod = iter.next();
out.println("<producto id=\"" + prod.getId() + "\" >" +
prod.getProducto() + "</producto>");

}
out.println("</productos>");
return;

}
}
System.out.println("no se obtuvo nada de "+tipo);
response.setStatus(response.SC_NO_CONTENT);
}

}


Agregamos la definición de este servlet en nuestra aplicación
web, es decir, editamos el archivo web.xml y colocamos las siguientes
líneas.


  <servlet>
<servlet-name>ProductoPorTipo</servlet-name>
<servlet-class>com.jugperu.tutores.ajax.servlets.ProductoPorTipoServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ProductoPorTipo</servlet-name>
<url-pattern>/servlets/ajax/productoportipo</url-pattern>
</servlet-mapping>


Ahora, ejecutamos nuestra aplicación, llamados al servlet y le
damos un parámetro, por ejemplo “tipo=frt”.




Paso 3: Haciendo que un cliente acceda al XML
utilizando Javascript


El cliente será un html que muestre dos select: uno con los
tipos, y otro de los productos pero no tendrá valores. Lo
fuerte de este cliente es el javascript que obtendrá el XML y
preparará los valores obtenidos para agregar opciones al
select de productos.


<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=ISO-8859-1" lang="es">
<title>combo</title>
<script type="text/javascript">
function mostrarProductos(){
var tipo=document.getElementById("tipo");
//esta será nuestra petición
var url='<%=request.getContextPath()%>/servlets/ajax/productoportipo?tipo='+tipo.value;
//obtenemos el objeto para manejar peticiones en XML...
if (window.XMLHttpRequest) req = new XMLHttpRequest();
//.. tambien puede estar como ActiveX
else if (window.ActiveXObject) req = new ActiveXObject("Microsoft.XMLHTTP");
//asociamos un metodo alevento que se ejecuta a medida que vaya leyendo el XML
req.onreadystatechange = processRequest;
//pedimos el xml
req.open("GET", url, true);
//no enviamos nada y termina
req.send(null);
}
function processRequest(){ //este metodo se ejecuta a medida que va cargando el XML.
/** los estados pueden ser
0: no se ha iniciado
1: esta cargando
2: ya ha cargado
3: esta intercambiando con el cliente
4: ha terminado
*/
if (req.readyState==4){
/*y cuando haya terminado procesa el mensaje
(200: el requerimiento ha sido satisfactorio)*/
if (req.status==200) parseMessages();
//sino, hubo un problema al obtener el XML
else alert("no se pudo obtener los datos de la marca");
}
}
function parseMessages(){
//obtenemos el XML
var response=req.responseXML.documentElement;
//obtenemos de nuestro HTML la zona donde escribiremos el resultado
var cboProducto=document.getElementById("producto");
cboProducto.disabled=false;
var productos=response.getElementsByTagName("producto");
cboProducto.options.length=productos.length;
for(i=0;i<productos.length;i++){
var prod=productos[i];
cboProducto.options[i].value=prod.getAttribute("id");
cboProducto.options[i].innerHTML=prod.firstChild.data;
}
}
</script>
</head>
<body>
<h1>Ajax Combo</h1>
<form action="."> Tipo:
<select name="tipo" id="tipo" onchange="mostrarProductos()">
<option value=" ">Elija un tipo</option>
<option value="frt">Frutas</option>
<option value="ver">Verduras</option>
<option value="crn">Carnes</option>
</select>
Producto:
<select name="producto" id="producto" disabled="disabled">
<option value=" ">Elija un producto</option>
</select>
</form>
</body>
</html>


Ahora, abrimos esta página y vemos los resultados al
seleccionar un tipo de producto.




AJAX-TAGS


Hasta el momento no hemos hecho algo extraordinario en Java con lo
que respecta a AJAX. Este ejemplo fue para entender la forma cómo
interactúa el javascript del cliente con el servidor
utilizando XML. Podemos predecir que programar todos los javascript
para nuestros formularios pueda resultar un suplicio.
Afortunadamente, unos programadores han elaborado unos taglibs
que nos permitirá crear aplicaciones web utilizando AJAX en
poco tiempo.



AJAX Tag Library permite:



  • Crear autocompletes en campos text.

  • Llenar opciones de un select basado en la selección
    de otro campo.

  • Mostrar globos de algún texto seleccionado en la
    página web.

  • Refrescar campos de formulario, y

  • Alternar imágenes
    dependiendo del estado de un campo (como encendido/apagado)


Podemos encontrar esta biblioteca en
http://ajaxtags.sourceforge.net/


Realizaremos el mismo ejemplo anterior, pero utilizando AjaxTags.
Por tanto, reutilizaremos el DAO y cambiaremos el Servlet que genera
el XML y el cliente.


Paso 2a: Creando el Servlet que devuelve XML


AjaxTags recibe XML que tengan el siguiente formato.


<?xml version="1.0" encoding="UTF-8"?>
<ajax-response>
<response>
<item>
<name>Registro 1</name>
<value>1</value>
</item>
<item>
<name>Registro 2</name>
<value>2</value>
</item>
<item>
<name>Registro 3</name>
<value>3</value>
</item>
</response>
</ajax-response>


Podemos hacer que nuestro Servlet cree el XML en ese mismo formato, o
podemos utilizar una clase de AjaxTags
(org.ajaxtags.helpers.AjaxXmlBuilder) que arma el XML
dependiendo del parámetro que reciba. También, provee
una clase abstracta Servlet (org.ajaxtags.servlets.BaseAjaxServlet)
que tiene todo el código necesario para devolver un XML al
cliente, sólo nos toca armar la lista de elementos del XML.


package com.jugperu.tutores.ajax.servlets;

import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.jugperu.tutores.ajax.dao.DAOFactory;
import com.jugperu.tutores.ajax.dao.MercadoDAO;
import org.ajaxtags.helpers.AjaxXmlBuilder;
import org.ajaxtags.servlets.BaseAjaxServlet;

public class AjaxTagsProductoPorTipoServlet extends BaseAjaxServlet {

public String getXmlContent(HttpServletRequest request,
HttpServletResponse response) throws
Exception {
MercadoDAO dao = null;
dao = DAOFactory.crearMercado(DAOFactory.LISTAS);

String tipo = request.getParameter("tipo");

List lista = dao.productosPorTipo(tipo);

return new AjaxXmlBuilder().addItems(lista, "producto", "id").toString();

}
}


También existe la clase org.ajaxtags.servlets.BaseAjaxAction
que es similar a BaseAjaxServlet sólo que está
orientado para Struts.


Ahora, configuraremos nuestra aplicación web para que
contemple este servlet.


  <servlet>
<servlet-name>AjaxTagsProductoPorTipo</servlet-name>
<servlet-class>com.jugperu.tutores.ajax.servlets.AjaxTagsProductoPorTipoServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>AjaxTagsProductoPorTipo</servlet-name>
<url-pattern>/servlets/ajax/ajaxtags-productoportipo</url-pattern>
</servlet-mapping>


Ejecutamos el servlet y le damos el mismo parámetro para ver
el resultado en nuestro navegador.




Paso 3a: Haciendo que un cliente acceda al XML
utilizando AjaxTags


Ahora, nuestro jsp. En este caso, se llamará a dos
javascript que vienen incluidos en el ajaxtags.


<%@taglib uri="http://ajaxtags.org/tags/ajax" prefix="ajax"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<c:set var="contextPath"><%=request.getContextPath() %></c:set>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=ISO-8859-1" lang="es">
<script type="text/javascript" src="<c:out value='${contextPath}'/>/js/prototype-1.3.1.js"></script>
<script type="text/javascript" src="<c:out value='${contextPath}'/>/js/ajaxtags-1.1.5.js"></script>
<title>ajaxtags-combo</title>
</head>
<body>
<h1>AjaxTags Combo</h1>
<form action="."> Tipo:
<select name="tipo" id="tipo">
<option value=" ">Elija un tipo</option>
<option value="frt">Frutas</option>
<option value="ver">Verduras</option>
<option value="crn">Carnes</option>
</select>

Producto:
<select name="producto" id="producto">
<option value=" ">Elija un producto</option>
</select>
</form>
<ajax:select baseUrl="${contextPath}/servlets/ajax/ajaxtags-productoportipo"
parameters="tipo={tipo}" source="tipo"
target="producto"/>
</body>
</html>


Ejecutamos ahora el jsp y veremos los resultados:





Autocomplete


También se puede hacer un input text que tenga
autocomplete. Para ello, se debe implementar un método
en ListasMercadoDAO que se encargue de devolver una lista
con todos los productos cuyos nombres comiencen con determinada letra
o letras.


    public List<Producto> productosPorNombre(String nombre) {
if (nombre.trim().equals("")) {
return null;
}
List lista = new ArrayList();
for (Iterator<Producto> iter = productos.iterator(); iter.hasNext(); ) {
Producto pro = iter.next();
if (pro.getProducto().toLowerCase().startsWith(nombre.toLowerCase())) {
lista.add(pro);
}

}
return lista;
}


Debería existir, naturalmente, la declaración de este
método en la clase interfaz MercadoDAO.


También, se debe crear un servlet que reciba el
parámetro del cliente, le pida al DAO los productos y que
devuelva el XML.


package com.jugperu.tutores.ajax.servlets;

import java.util.*;

import javax.servlet.http.*;

import com.jugperu.tutores.ajax.beans.*;
import com.jugperu.tutores.ajax.dao.*;
import org.ajaxtags.helpers.*;
import org.ajaxtags.servlets.*;

public class AjaxTagsProductoPorNombre extends BaseAjaxServlet {

public String getXmlContent(HttpServletRequest request,
HttpServletResponse response) throws
Exception {
MercadoDAO dao = null;
dao = DAOFactory.crearMercado(DAOFactory.LISTAS);

String producto = request.getParameter("producto");

List<Producto> lista = dao.productosPorNombre(producto);
System.out.println("se encontraron "+lista.size()+" que cumplen con '"+producto+"'" );

return new AjaxXmlBuilder().addItems(lista, "producto", "tipo.tipo").
toString();

}
}


Declaramos el servlet en web.xml


  <servlet>
<servlet-name>AjaxTagsProductosPorNombre</servlet-name>
<servlet-class>com.jugperu.tutores.ajax.servlets.AjaxTagsProductoPorNombre</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>AjaxTagsProductosPorNombre</servlet-name>
<url-pattern>/servlets/ajax/ajaxtags-productoPorNombre</url-pattern>
</servlet-mapping>


Y creamos el jsp que se presentará al usuario.


<%@taglib uri="http://ajaxtags.org/tags/ajax" prefix="ajax"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<c:set var="contextPath"><%=request.getContextPath() %></c:set>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<link href="css/ajaxtags-sample.css" rel="stylesheet" rev="stylesheet">
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" lang="es">
<script type="text/javascript" src="<c:out value='${contextPath}'/>/js/prototype-1.3.1.js"></script>
<script type="text/javascript" src="<c:out value='${contextPath}'/>/js/ajaxtags-1.1.5.js"></script>
<title>ajaxtags-autocomplete</title>
</head>
<body>
<h1>AjaxTags Autocomplete</h1>
<form action="."> Producto:
<input id="producto" type="text" name="producto" class="autocomplete" />
<br />
Tipo
<input id="tipo" name="tipo" type="text" />
<ajax:autocomplete baseUrl="${contextPath}/servlets/ajax/ajaxtags-productoPorNombre"
className="autocomplete" parameters="producto={producto}" source="producto" target="tipo"/>
</form>
</body>
</html>


El resultado es el siguiente:




El efecto visual de mostrar una lista de elementos en el input
text
se logra utilizando CSS y javascript. AjaxTags viene
con un archivo .css de ejemplo, que contiene algunas definiciones de
clases útiles. En este ejemplo, hemos usado la clase style
“autocomplete”. El javascript se encarga de escribir en
el HTML un div que tiene la particularidad de mostrarse flotando
sobre el html (justamente, debajo del input text) y crea la
lista de elementos enviados por el servlet.


El código que generó el javascript es como este.


<div style="overflow: visible; top: 102px; left: 71px; min-width: 153px; z-index: 20;"
id="ajaxAutocompletePopup" class="autocomplete">
<ul>
<li id="Frutas">Peras</li>
<li class="selected" id="Verduras">Papas</li>
<li id="Carnes">Pollo</li>
</ul>
</div>

Conclusiones


Recordemos que AJAX no es una tecnología, es una técnica
que reúne tecnologías existentes. Si bien es cierto que
en estas últimas semanas han aparecido IDEs que soportan AJAX
(como JDeveloper y netbeans 5) no significa que con sólo estos
productos podamos hacer lo que acabamos de ver.


Al incorporar AJAX, podremos hacer que nuestras páginas
sean más dinámicas sin sacrificar ancho de banda,
dándole al usuario una sensación de “respuesta
inmediata” en cada interacción que realiza.


Bibliografía


miércoles, 1 de marzo de 2006

JUnit

Seamos sinceros: nuestras aplicaciones las hacemos usando la metodología ODBC (Ojo De Buen Cubero) que consiste en ir escribiendo todo el código de un tirón calculando "al ojo" que funciona bien... luego ejecutamos toda la aplicación para ver si funciona bien... y si hay un problema, corregimos esa parte y volvemos a ejecutar toda la aplicación " otra vez " para ver si esa pequeña parte ha sido solucionada... y así sucesivamente hasta que ya no haya más problemas... por el momento.

JUnit es un framework que permite, a nosotros los programadores y desarrolladores, realizar pruebas a partes de una aplicación (una rutina, una clase, un algoritmo, etc) antes de la etapa de construcción.

Nuestro IDE favorito (JBuilder, netBeans, eclipse, JDeveloper, etc) permite crear módulos Test. Si aún no lo has utilizado, este sería un buen momento para conocer más tu IDE.

Manos a la obra

Imaginemos que estamos haciendo una aplicación que requiere manejar una lista en forma de pila: el primero en entrar es el último en salir. Para ello tenemos que crear una clase "Pila" que tendrá esta funcionalidad.

Nuestra clase tendrá los métodos poner() para agregar un nuevo objeto, sacar() para obtener el ultimo objeto agregado y tamanio() para conocer el tamaño de la pila en cualquier momento. El método poner() devolverá, además, el tamaño de pila después de agregar un elemento.

Antes de crear nuestra clase, primero pensemos cómo vamos a instanciarla y qué métodos va a tener.

        Pila pila1=new Pila();

pila1.poner("Marisol");
pila1.poner(new Integer(23));

Integer num=(Integer)pila1.sacar();

System.out.println("el tamanio de la pila es "+pila1.tamanio());

Con esto tendremos una idea más clara de cómo será nuestra clase.

Paso 1: creando un TestCase

Si usamos eclipse, al presionar Ctrl+N seleccionamos JUnit JUnit Test Case como se ve en la siguiente pantalla:

Si usamos netBeans, al presionar Ctrl + N seleccionamos JUnit Empty Test como se ven la siguiente pantalla:


También podemos crear un Test Case en jdeveloper. Para ello debemos bajar de la página de oracle la extensión necesaria. Una vez instalado podemos crear un Test Case como se ven la siguiente ventana

Si usamos JBuilder, entraremos al menú File New, y seleccionamos Test Test Case como se ve en la siguiente pantalla



Cualquiera fuera nuestro IDE, crearemos la clase con el nombre PilaTestCase ubicado en el paquete com.jugperu.tutores.junit.

Si nuestro IDE no tiene incorporado este módulo, podemos descargarlo de http://junit.sourceforge.net/ y ejecutarlo directamente.

Ahora, escribiremos el método "testApilar()" donde se harán las llamadas a nuestra clase que aún no hemos creado. Será como nuestro escenario donde se ejecutará nuestra clase. package com.jugperu.tutores.junit;

import junit.framework.*;

public class PilaTestCase extends TestCase {
public PilaTestCase(String name) {
super(name);
}


public void testApilar() {
Pila pila1 = new Pila();
pila1.poner("Marisol");
pila1.poner("Santiago");
pila1.poner("Diego");
pila1.poner(new Integer(23));

assertEquals(pila1.tamanio(),4);

}

}

La línea...

    assertEquals(pila1.tamanio(),4);

... es parte de nuestro test. Consiste en evaluar un resultado obtenido en un determinado momento y que debe coincidir con lo que esperamos. En este caso, despues de apilar 4 objetos, el tamaño de nuestra pila debe ser, naturalmente, 4. "Pero mi algoritmo de la pila va a estar perfecto cuando lo haga... ¿para qué tengo que hacer un test? " podremos decir.. pero ¿cuántas veces hemos codificado algoritmos "perfectos" que al final comenzaba a fallar por algo y "no sale lo que deberia salir" ?

Paso 2: Implementado nuestra clase

Los IDE actuales poseen una característica muy útil llamada "refactor" que permite reducir el tiempo en programar: puede crear sentencias que faltan, cambio de nombres, indentación, cambio de paquete de una clase, etc. Utilizaremos esta característica para crear parte del código de la clase Pila con las sentencias que hemos declarado. El resultado sería el sigueinte:

package com.jugperu.tutores.junit;

public class Pila {
public void poner(Object o) {
}

public int tamanio() {
return 0;
}
}

Ahora, completaremos el código que falta para que funcione nuestra clase.

package com.jugperu.tutores.junit;

import java.util.*;

public class Pila {
private List lista = new ArrayList();

public int poner(Object o) {
lista.add(0, o);
return tamanio();
}

public int tamanio() {
return lista.size();
}
}

Paso 3: probando nuestra clase

Ahora ejecutaremos el TestCase para ver si nuestro algoritmo funcionó. Al ejecutar JUnit siempre evaluará todos los métodos cuyos nombres empiecen con "test". Si uno de ellos falló, seguirá con el siguiente test.

La manera cómo va a ejecutar el TestCase varia dependiendo del IDE que estemos usando. Pero escencialmente da el mismo resultado.

Resultado en Eclipse

Resultado en JDeveloper o JUnit autónomo

Resultado en JBuilder

Resultado en netBeans

Al parecer todo va bien.

Paso 4: Agregando más funcionalidad a nuestra clase

Ahora, incorporaremos la funcionalidad de sacar los objetos de nuestra pila, pero al hacer eso nos aseguramos que los objetos estén en el orden correcto. Agregaremos el siguiente test a nuestro TestCase.

 public void testDesapilar() {
Pila pila1 = new Pila();

pila1.poner("Marisol");
pila1.poner("Santiago");
pila1.poner("Diego");
pila1.poner(new Integer(23));

assertEquals( ( (Integer) pila1.sacar()).intValue(), 23);
assertEquals(pila1.sacar(), "Diego");
assertEquals(pila1.sacar(), "Santiago");
assertEquals(pila1.sacar(), "Marisol");

assertEquals(pila1.tamanio(), 0);

}

Ahora, agregaremos el método que nos falta en nuestra clase Pila.

  public Object sacar() {
return lista.remove(0);
}

Ejecutamos el TestCase y evaluará los dos test.

Cada test lo ejecuta de forma independiente. Esto es, si la variable "pila1" la hubieramos declarado como propiedad de la clase TestCase y hubieramos asumido que al ejecutar el TestCase primero ejecuta "testApilar()" haciendo que el objeto "pila1" ya tenga valores para "testDesapilar()" hubieramos cometido un error. El JUnit carga todo el TestCase por cada test que tenga que hacer.

Si queremos iniciar valores para todos los test que queramos hacer, debemos sobreescribir el método "setUp()". Este método se ejecuta antes de cada test de nuestro TestCase. También existe el método "tearDown()" que se ejecuta al terminar cada test.

Modifiquemos nuestro TestCase para ver estos métodos.

package com.jugperu.tutores.junit;

import junit.framework.TestCase;

public class PilaTestCase extends TestCase {
private Pila pila1;
public PilaTestCase(String name) {
super(name);
}

public void testApilar() {
assertEquals(pila1.tamanio(), 4);
}

public void testDesapilar() {

assertEquals(((Integer) pila1.sacar()).intValue(), 23);
assertEquals(pila1.sacar(), "Diego");
assertEquals(pila1.sacar(), "Santiago");
assertEquals(pila1.sacar(), "Marisol");

assertEquals(pila1.tamanio(), 0);

}

protected void setUp() throws Exception {
System.out.println("cargando valores");
pila1 = new Pila();
pila1.poner("Marisol");
pila1.poner("Santiago");
pila1.poner("Diego");
pila1.poner(new Integer(23));

}

protected void tearDown() throws Exception {
System.out.println("termino este test");
}

}

Ahora bien, supongamos que por alguna razón se saca un objeto más de la pila cuando esta ya está vacia. No sabemos lo que va a pasar, así que mejor lo probamos acá antes de ponerlo en la aplicación final.

        ........
assertEquals(((Integer) pila1.sacar()).intValue(), 23);
assertEquals(pila1.sacar(), "Diego");
assertEquals(pila1.sacar(), "Santiago");
assertEquals(pila1.sacar(), "Marisol");

Object o = pila1.sacar();
......

Ejecutamos el Test y nos dará la excepción "IndexOutOfBoundsException". "Sí, ya lo sabía".. pero es preferible asegurarse.

¿Qué hacemos en este caso? Podriamos modificar nuestra clase Pila para que al tratar de sacar un objeto cuando ya no hay más por sacar, que devuelva null. Entonces, nos aseguraremos que va a devolver un null.

Modificamos nuestro método testDesapilar()

    public void testDesapilar() {

assertEquals(((Integer) pila1.sacar()).intValue(), 23);
assertEquals(pila1.sacar(), "Diego");
assertEquals(pila1.sacar(), "Santiago");
assertEquals(pila1.sacar(), "Marisol");

assertNull(pila1.sacar());

assertEquals(pila1.tamanio(), 0);

}

Y modificamos nuestro método sacar()
    public Object sacar() {
if (tamanio() == 0)
return null;
return lista.remove(0);
}

Ejecutamos nuestro TestCase y veremos los resultados.

Agrupando varios TestCase en un Test Suite

Tambien podemos crear un conjunto de Test Case y ejecutarlos a la vez, indicando qué test deseamos evaluar. Para ello crearemos una clase Test Suite el cual tendrá el siguiente código.

package com.jugperu.tutores.junit;

import junit.framework.*;

public class AppTestSuite {


public static Test suite() {
TestSuite suite = new TestSuite();
suite.addTestSuite(PilaTestCase.class);
return suite;
}
}

También podemos ejecutar solamente un test de un TestCase de la siguiente manera:

       suite.addTest(new PilaTestCase("testDesapilar"));

Ahora que ya sabemos que funciona bien, lo podemos utilizar en nuestra aplicación.

Conclusiones

Es importante realizar siempre tests a cada parte de nuestra aplicación. Hay que evitar probar solo una parte dentro de toda la aplicación en ejecución. Sería como esperar a que todo el automóvil esté construido para ver si está bien el alternador.

El todo es mayor que la suma de sus partes, y si nos aseguramos que sus partes están bien, el todo será lo que hemos esperado.

También hay variantes de JUnit que se utilizan para web, para swing, para Struts, etc.

Si necesitamos hacer un cambio, no olvidemos pasarlo por un test para asegurarnos que funcionará correctamente.

miércoles, 1 de febrero de 2006

Log4j: Un framework para mostrar logs de aplicaciones

Introducción


¿Quién no ha utilizado System.out.println() para saber qué está haciendo en ese momento el programa? A veces ponemos mensajes como “estoy en 'calcularMonto'”, quizás mostrando la hora actual, o quizás el tiempo que pasó entre un proceso y otro, o mostrando el valor que tiene una variable. El problema sucede cuando el programa tiene que estar en producción y estos mensajes pueden resultar incómodos. Se torna una tarea tediosa buscar estas instrucciones para “comentarlas”. Y si hay error después ¿dónde está? ¡voy a tener que editar y recompilar el código para ver lo que pasa!. Afortunadamente programadores como nosotros han tenido el mismo problema y han desarrollado un framework que permite administrar estos mensajes – en inglés el término utilizado es logging – de tal manera que nuestros programas no tengan que pasar por el quirófano cada vez que esté “enfermo”. Este framework es el muy utilizado Log4j.
Log4j permite registrar eventos de nuestro programa sin necesidad de modificar el código a cada instante. Estos eventos (o logs) pueden ser mostrados en pantalla, guardados en un archivo, enviados por correo electrónico, etc. Por ejemplo, cuando nuestra aplicación web se levantó correctamente deseamos que se guarde en un log la hora y fecha con un mensaje “success”, pero cuando nuestra misma aplicación que atiende a más quinientos mil usuarios al día tenga un error crítico queremos que nos avise por correo electrónico a nuestro celular. Todo esto puede ser posible gracias a Log4j.

Manos a la obra: Log4j en acción


Desarrollaremos un ejemplo y a medida que lo hagamos iremos explicando cada parte del Log4j.

Paso 1: Obteniendo, instalando y configurando


Naturalmente para poder utilizar Log4j debemos obtenerlo de algún lado. Este framework se encuentra en http://logging.apache.org/. Descomprimimos su contenido en un directorio1. Al descomprimir se habrá creado un subdirectorio con un nombre como logging-log4j-1.2.12. Este subdirectorio tiene otros subdirectorios que incluye la documentación, ejemplos, el código fuente y las bibliotecas necesarias. Dentro del subdirectorio “dist/lib” hay un archivo .jar que no pesa más de 400KB. Este es un nuestro framework que usaremos en nuestros programas.
Para usarlo recomiendo crear una biblioteca2 (Library) en nuestro IDE para así poderlo reutilizar en otros proyectos. Solo bastaría con agregar a nuestra biblioteca el archivo .jar del framework.
Recordemos que al crear un proyecto con nuestro IDE favorito, este crea una estructura de directorios especial. Generalmente crea el directorio src donde estarán todos los .java, un directorio class para nuestras .class, y un directorio build donde está la aplicación lista para ejecutar. Esto varía dependiendo de nuestro IDE.

Paso 2: Haciendo nuestro ejemplo


Crearemos la clase demolog4j.Main (es decir, la clase Main dentro del paquete demolog4j)
package demolog4j;

import org.apache.log4j.Logger;

public class Main {
  static final Logger log = Logger.getLogger(Main.class);

  public static void main(String[] args) {
    log.info("iniciando aplicación");

    for (int i = 10; i >= 0; i--) {
      try {
        double n = 100 / i * 1.0;
        log.debug("el valor de n=" + n);
    }
      catch (Exception ex) {
      log.error(ex);
    }
  }
}
}

Es un ejemplo bastante forzado para mostrar tres de los cinco niveles de mensajes: info, debug y error. El nivel más detallado es el debug, que nos será de mucha utilidad al momento de depuración. Esto sirve, por ejemplo, para mostrar el nuevo valor de una variable. No debería estar activado cuando se encuentre el producción.
El siguiente nivel es info. Este nivel es para informarnos de cosas que ya se hizo algo concreto, tal como haberse conectado a la base de datos, el inicio satisfactorio de un servlet o un thread, etc. Es similar al modo “verbose” de las aplicaciones.
El siguiente nivel es el warn. Este es para advertir de eventos que no necesariamente pueda ser un error. Por ejemplo el que no haya definido un archivo de configuración pero se puede cargar valores por omisión,
El nivel error está relacionado para informar errores.
Y un nivel mucho más crítico es el fatal. Este es para informar de eventos que no tienen solución. Por ejemplo cuando una aplicación está a punto de cerrarse a causa de un error.
Para que nuestro ejemplo funcione crearemos un archivo llamado log4j.properties que debe estar en la raíz de los código fuente. Es decir, en el directorio src de nuestro proyecto. Debe encontrarse aquí ya que cada vez que el IDE construya el proyecto se copiará al directorio junto con las .classes.
El archivo log4j.properties deberá tener el siguiente contenido:
log4j.rootLogger=DEBUG, A1

log4j.appender.A1=org.apache.log4j.ConsoleAppender

log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
Y ejecutamos el programa. Lo que mostrará es algo similar a esto:
0    [main] INFO  demolog4j.Main  - iniciando aplicación
15   [main] DEBUG demolog4j.Main  - el valor de n=10.0
15   [main] DEBUG demolog4j.Main  - el valor de n=11.0
15   [main] DEBUG demolog4j.Main  - el valor de n=12.0
15   [main] DEBUG demolog4j.Main  - el valor de n=14.0
15   [main] DEBUG demolog4j.Main  - el valor de n=16.0
15   [main] DEBUG demolog4j.Main  - el valor de n=20.0
15   [main] DEBUG demolog4j.Main  - el valor de n=25.0
15   [main] DEBUG demolog4j.Main  - el valor de n=33.0
15   [main] DEBUG demolog4j.Main  - el valor de n=50.0
15   [main] DEBUG demolog4j.Main  - el valor de n=100.0
15   [main] ERROR demolog4j.Main  - java.lang.ArithmeticException: / by zero

Puedes editar la primera línea del log4.properties cambiando el texto “debug” por alguno de los demás niveles: info, warn, error o fatal para ver los resultados.

Estructura de Log4j.


Log4j tiene tres componentes principales: loggers, appenders y layouts. Estos tres tipos de componentes trabajan juntos para permitir a los desarrolladores registrar mensajes según el tipo y nivel de estos, y controlarlos en tiempo de ejecución para mostrarlos de acuerdo a un formato que le especifiquemos.

Loggers

La principal ventaja del Log4j comparado con el tradicional System.out.println() es su capacidad para habilitar o deshabilitar los registradores de mensajes sin afectar a los demás. Un registrador de mensajes (Logger) son entidades autónomas con configuración independiente, de tal manera que uno tenga cierta funcionalidad que otro no lo tenga. Los nombres de los loggers son sensibles a mayúsculas.

Appenders

La habilidad para activar o desactivar cualquier logger es solo una parte de Log4j. El framework permite registrar los eventos en varios destinos. Para Log4j una salida (pantalla, archivo, etc) es un appender. Actualmente, existe appenders para consola (como hemos visto), archivos, componentes visuales, servidores de sockets, JMS, Registro de Eventos de NT, y demonios de registro Unix.

Layouts

El layout es el responsable de formatear los mensajes de acuerdo a criterio del programador. Existen dos tipos de Layout: SimpleLayout, que básicamente muestra el nivel del mensaje y el mensaje en sí, y el PatternLayout que consiste en formatear la salida.

Paso 3: Personalizando los mensajes


Veremos un ejemplo más avanzado para ilustrar estos conceptos. Modifiquemos el archivo .properties para que tenga el siguiente contenido:

log4j.rootLogger=debug, stdout, R

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout

log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n

log4j.appender.R=org.apache.log4j.RollingFileAppender
log4j.appender.R.File=example.log
log4j.appender.R.Append=false

log4j.appender.R.MaxFileSize=100KB
log4j.appender.R.MaxBackupIndex=1

log4j.appender.R.layout=org.apache.log4j.PatternLayout
log4j.appender.R.layout.ConversionPattern=%p %t %c - %m%n

Destallaremos cada línea de esta configuración

log4j.rootLogger=debug, stdout, R
Aquí estamos definiendo que nuestro Logger serán stdout y R. Ambos serán para atender el nivel debug.

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
En esta línea el appender stdout será de tipo ConsoleAppender. Esta clase se encargará de mostrar los mensajes a la pantalla.

log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
La clase “PatternLayout” nos permite configurar nuestro appender por una cadena. En el API del framework podemos ver más detalle de estas clases.

log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n
Aquí ya estamos definiendo nuestro formato el cual consiste en mostrar en un espacio de 5 caracteres (%5) el nivel del mensaje (p). Luego sigue de un espacio. Entre llaves mostrará el hilo que generó el mensaje (%t). Luego de un espacio, entre paréntesis el nombre del archivo fuente (%F) y después de dos puntos muestra la línea dentro del código fuente (%L). Después sigue un guión y luego muestra el mensaje que se ha mandado desde el programa. Finaliza con un salto de línea.
Tenemos otro appender que es el R. Este es de clase RollingFileAppender. Esta clase permite agregar a un archivo y si este llega a un límite especificado es renombrado y el registro seguirá en un archivo nuevo. Esto es muy útil si queremos mantener todos los registros de eventos partidos en archivos de un tamaño manejable.
log4j.appender.R=org.apache.log4j.RollingFileAppender
log4j.appender.R.File=example.log
log4j.appender.R.Append=false
Aquí estamos diciendo que el archivo que creará será “example.log”y que sobreescriba los mensajes cada vez que se ejecute la aplicación.

log4j.appender.R.MaxFileSize=100KB
Que tendrá un tamaño máximo de 100KB

log4j.appender.R.MaxBackupIndex=2
Y cuantos backups deberá crear antes de que el más antiguo deba borrarse.

log4j.appender.R.layout=org.apache.log4j.PatternLayout
log4j.appender.R.layout.ConversionPattern=%p %t %c - %m%n
Aquí estamos definiendo el formato de salida que tendrá: la prioridad del evento (%p) seguido del nombre del thread que generó el evento (%t), el nombre completo de la clase incluyendo el paquete (%c) seguido de un guión y luego mensaje. Termina con una línea nueva.
Por último, agregaremos estas líneas a la configuración para enviar un email cuando suceda un error.
log4j.rootLogger=debug, stdout, R,email

....
log4j.appender.email.Threshold=error
log4j.appender.email=org.apache.log4j.net.SMTPAppender
log4j.appender.email.SMTPHost=smtp.provider.com
log4j.appender.email.From=events@jugperu.com
log4j.appender.email.To=user@jugperu.com
log4j.appender.email.Subject=Mensaje de Log4j!
log4j.appender.email.layout=org.apache.log4j.PatternLayout
log4j.appender.email.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n

Para que este funcione deberemos de agregar la biblioteca de JavaMail.

Conclusiones


Cómo hemos visto, el cambio de la manera de registrar los eventos fue solo en el archivo .properties y no en nuestro programa. Muchas aplicaciones hechas en Java que utilizamos – como el Tomcat – utilizan este framework de registro de mensajes. Puedes personalizar sus mensajes de acuerdo a tus exigencias. Piensa qué tan útil puede ser para tus proyectos.

Tips


1 Recomiendo que todas las bibliotecas en general se guarden en un directorio llamado “lib”
2 Comúnmente cometemos el error de decir 'librería' para referirnos a 'library'. La traducción correcta de 'library' es biblioteca. 'librería' significa “tienda de libros”.