Accede a tus EJB3 de Sesión

Quiero explicar cómo acceder de distintas maneras a un sEJB ejecutándose en un servidor de aplicaciones.

Además quiero hablar sobre las interfaces de los sEJB y cierta ventaja de salirse un poco del camino establecido por los tutoriales que se encuentran en la web dada las grandes posibilidades de implementación y configuración de los sEJB y sus interfaces.

¿Interfaces remotas o locales o las dos?

Depende, pero es mejor preguntarse ¿quién debe poder acceder a mi sEJB?

Como regla general, prefiero que cada EJB sea sólo utilizado como remoto o como local, no ambos. y así limitarlos según sea necesario.

La principal diferencia entre una interfaz local y una remota, es la manera en que el servidor de aplicaciones sirve el sEJB. Con las interfaces remotas, los objetos son enviados como valores y con las locales, se envían los objetos como referencias. Para conocer esto, el contenedor depende de la llamada que utilicemos: local o remote

Ahora, podemos definir las dos interfaces que implementen métodos distintos según nuestras necesidades, como evitar que los clientes remotos accedan a ciertos métodos que deseamos utilizar en local.

Un pequeña práctica que me gusta implementar es la de no utilizar la notación @remote o @local en las interfaces. En su lugar, prefiero utilizarlas directamente en el EJB que las implementa. Así podremos tener implementaciones de estas interfaces independientes del EJB. Dado que no podemos tener la certeza de que cualquier servicio proporcionado por estas interfaces vaya a ser un EJB prefiero mantener las interfaces por separado.

Seguimos necesitando indicar en alguna parte que estas interfaces son la local y remota de nuestro sEJB. Para lograr esto utilizamos la misma notación, pero ésta vez en el sEJB directamente:



@Stateless
@Local(MyTest.class)
@Remote(MyTestRemote.class)
public class MyTestBean implements MyTest, MyTestRemote{
//...
}

Ya habiendo definido el sEJB y sus interfaces, vamos a utilizarlas desde un cliente remoto y desde otro EJB incluido dentro del mismo EAR.

Lo más importante, es conocer la URI donde vamos a encontrar al EJB. Dado que lo hemos incluido dentro del EAR MyEA podremos acceder a las dos interfaces con los siguientes nombres:

  • /MyEA/MyTestBean/remote

  • /MyEA/MyTestBean/local



El cliente remoto tendrá que hacer algo como lo siguiente, para poder obtener una instancia del EJB desplegado en un servidor JBoss:



Properties properties = new Properties();
properties.put("java.naming.factory.initial","org.jnp.interfaces.NamingContextFactory");
properties.put("java.naming.factory.url.pkgs","org.jboss.naming:org.jnp.interfaces");
properties.put("java.naming.provider.url","localhost:1099");

Context context = new InitialContext(properties);
MyTestRemote remote = (MyTestRemote)context.lookup("MyEA/MyTestBean/remote");
//...


Para un EJB o POJO que está desplegado junto a MyTestBean en el EAR, debemos hacer algo parecido:



Context context = new InitialContext();
MyTest bean = (MyTest)context.lookup("MyEA/MyTestBean/local");
//...


Como veréis, no hace falta indicar las propiedades del contexto inicial ya que el servidor lo inicializará por nosotros.

Es importante no caer en el error de instanciar directamente un sEJB desde alguna de nuestras clases, ya que las inyección realizadas por el contenedor no se van a realizar. Por ejemplo, las unidades de persistencia no funcionarán y obtendremos un NPE poco explicativo.

Los Stored Proceedures son tus amigos

En todos los lugares en los que he trabajado, la figura del DBA es bastante habitual, y es normal, hace falta al menos una persona para la labor, que además tenga la experiencia y los nervios para trabajar con datos reales sin destrozar el negocio en pocos segundos. Los pobres tienen que convivir con usuarios, clientes y desarrolladores, y de vez en cuando, con la dirección. Todos pidiendo informes extraños y miles de cosas que además, tienen que implementar con algo tan doloroso como es SQL (para mi, al menos).
Gracias a herramientas como Hibernate, y muchas otras, los programadores podemos olvidarnos de SQL y concentrarnos en nuestro lenguaje. Pero no siempre una capa de persistencia es la solución.
Muchas veces, nos encontramos con que la información está en distintos lugares (queues, BB.DD., sistemas de ficheros) pero el caso más común, es conectarte a distintas BB.DD. para obtener ingentes cantidades de información o verificaciones sobre datos y es aquí donde no vamos a utilizar estas herramientas, ya que realmente no vamos a realizar ninguna tarea de persistencia, sólo vamos a utilizar la R de CRUD.
¿Hasta dónde debemos llegar con estas búsquedas? En mi opinión, cualquier búsqueda más complicada que un SELECT x FROM y WHERE a = b debe ser un procedimiento almacenado.
Los stored procedures o sp nos permiten obtener un nuevo nivel de abstracción entre el código y los datos. Son una caja negra con la que siempre debemos cumplir y la que siempre debe cumplir con sus especificaciones. Es decir, si al sp_test yo le doy dos enteros y espero que me devuelva una cadena, siempre debe ser así. Internamente, el sp puede cambiar, pero siempre que se ciña al contrato, el código (probablemente, desplegado en producción o distribuido a miles de empleados) no tiene por que cambiar.
Además, es mucho más fácil buscar y modificar un sp ya que estos están en un mismo sitio. Probablemente, los cree o modifique un DBA que conoce SQL en lugar de un programador con dudosa experiencia en SQL. Y el código queda mucho más legible y limpio, ya que no hace falta interpretar una query gigante.
Ejemplo gráfico:
Esta es una query perfecta para nuestro ejemplo: es bastante larga, tiene una finalidad bien definida y los parámetros son siempre los mismos:


private final static String QUERY_ENTRIES_NEEDING_HARVEST =
"SELECT entries.id, entries.permalink, age(now(), harvested_at) " +
"FROM watched_pages " +
"INNER JOIN entries ON watched_pages.entry_id = entries.id " +
"WHERE feed_id = 0 AND (harvested_at IS NULL OR " +
"(age(now(), harvested_at) > interval '24:00:00' AND has_microformats = 't'))";


Esto puede convertirse en la pesadilla de algún programador, ya que buscar fallos en código SQL desde otro lenguaje es realmente doloroso: no se tiene syntax highlighting, verificación de datos (¿la tabla existe o no?¿está bien la llamada a la tabla o era X.dbo.Y?). Si además, el próximo programador, no tiene ni idea de SQL, ya es un largo tiempo que tendrá que utilizar para aprender qué demonios es INNER JOIN con los consiguientes problemas que esto acarrea. Si el nombre de alguna tabla cambia, habrá que realizar un nuevo despliegue o distribución de la aplicación corregida.
La solución habría sido darle esta query o un "contrato" a tu DBA especificando tus necesidades, lo que puedes proveer y lo que deseas recibir. Una query así, para un programador, puede resultar bastante complicada de entender, pero para un DBA, con sus herramientas específicas y con sus amplios conocimientos, esto es como ver un "Hello World". Probablemente, el DBA nos devolverá un correo para decirnos que ya podemos utilizar el sp sp_entries_needing_harvest(). Y nuestro código será algo como esto:


//...
CallableStatement cstmt = cx.prepareCall("{ ? = call sp_query_entries_needing_harvest()}");
cstmt.registerOutParameter(1, Types.VARCHAR);
//...


De ésta manera, además de abstraer un poco más nuestro código de los datos, estamos delegando tareas y responsabilidades en quien debe ser: el DBA. Si algo falla en esa sp el marrón será del él/ella :)

Dia del CapsLock o BloqMayus

Es que además tiene un nombre gracioso, pero ¿cuál es su utilidad?

Para cualquier persona estudiada, la tecla sobra, de hecho, la OLPC no trae la tecla maldita.

Veamos un pequeño resumen del uso de la tecla innombrable:

1- Quienes no hayan tenido la oportunidad de toparse con un hoygan, leed este articulo de la frikipedia.




DIZESE DE UNA ESTRAÑAS CRIATURAS CUYA HARMA MAS MORTAL ES EL HIDIOMA DEL MISMO NOMBRE CON CASI TODAS LA LETRAS MAYUSCULAS QUE SON MAS VONITAS Y UNA ECTRAÑA HORTOGRAFIA. SU AVITAT NATURAL SON LOS CHAS Y LOS FOROS DEL INTERNETE.

2- Ciertos especímenes, que en lugar de cambiar la resolución de pantalla o el tamaño de las fuentes, deciden que todo queda mejor en mayúsculas.

Pero la verdadera connotación de la tecla, lo que cualquiera con dos dedos de frente y cierto tiempo "utilizando Internet" entiende, es la de un grito.

Veamos, aunque parezca algo absurdo, es difícil dar un "tono" a una conversación escrita, ya sea vía IM, email o en chats. Para intentar subsanar esto, el ser humano, siempre tan creativo, utiliza los emoticons para expresar emociones, símbolos para expresar entonación y las mayúsculas, para gritar.
Ejemplos:
:) -> Feliz
_idiota_ -> énfasis
IDIOTA -> grio

"Pero, muchas veces estoy escribiendo y no me doy cuenta de que tengo la tecla activada y no voy a volver a escribir todo"



Querido amigo, querida amiga, levanta la cabeza del teclado y apúntate a unas clases de taquigrafía. Por otra parte, aquí, como siempre, os voy a dar la solución a todos vuestros problemas: eliminar la tecla. No me refiero a sacarla de cuajo del teclado, me refiero a darle una función más acorde con una tecla de tan importante localización. Que si fuera un solar, estaría al lado de la Cibeles o en medio de la Pza. de Cataluña :)

Como eliminar la tecla en Ubuntu


Podemos asignarle muchas tareas a esta tecla, la más común es la de emular el comportamiento de la tecla Ctrl (¿algún usuario de emacs por allí?) pero aquí somos un poco más puritanos (además de usuarios de vi) por lo que haremos algo útil con esta tecla que será abrir una consola.

1. Abrimos el programa gconf-editor ya sea con la combinación Alt+F2 o desde la consola.
2. Navegamos el árbol de la izquierda hasta llegar a apps/metacity/global_keybindings
3. Modificamos la clave run_command_terminal añadiendo Caps_lock



Y eso es todo, un feliz día del BloqMayus y recuerda: AQUI NO SE GRITA.

Yahoo's Web 3.0 y OSS

Para aquellos quienes no lo sepan aún, cada vez que utilizamos algún sitio como Facebook, Digg, Meneame, Google Maps, Google Docs, etcétera, estamos en el mundo llamado Web 2.0. Sitios (o aplicaciones) que permiten al usuario, tener una verdadera interactividad con la aplicación y con otros usuarios de la misma. ¿Cuál es el punto de inflexión que inicio todo esto? Según Wikipedia, el termino fue acuñado en 2004 por Tim O'Reilly. Sitios como AdSense y Flickr, eran una nueva especie, en la nueva fauna de la web.

Cuatro años después, los primeros pasos hacia la llamada Web 3.0 se están dando, y ayer, uno de los grandes jugadores de la escena como lo es Yahoo! con más de 250 MM de usuarios y siendo su sitio "www.yahoo.com" el más visitado y "mail.yahoo.com" muy cerca, lo que ellos esperan sea el futuro con Y!OS 1.0 enmarcado en una política Open Source y la "socialización" de la web.



Cómo esto repercute en la comunidad de desarrolladores y en los distintos proyectos, queda por ver, con la esperanza de que gracias a estas iniciativas, el movimiento Open Source tenga verdadera posibilidad de competir bajos las mismas bases, contra las viejas creencias, como se ha demostrado poco a poco por proyectos de gran repercusión.

¿Será el año del Web 3.0 el año de Linux en el escritorio? ¿Cuándo, finalmente, millones de usuarios decidan que no necesitan pagar onerosas sumas de dinero por realizar las mismas tareas, de la misma forma que hace 20 años? (MS Office fue lanzado en 1989)

¿Qué necesidad tendrá el usuario de a pie, de tener un Sistema Operativo completo, cuando lo único que necesitará será un buen navegador y un WebOS que integre todas las aplicaciones en un sólo sitio?

A muchos les podrá parecer esto una barbaridad, ya que las políticas de privacidad y de manejo de datos de muchas multinacionales como MS, Yahoo! y Google, dejan mucho que desear, pero la facilidad y conveniencia de estos cambios, harán replantear muchas estrategias a mucha gente.

Por mi parte, llevo mucho tiempo utilizando las herramientas de Yahoo! y de Google, por lo que sí, en mi caso, vería con buenos ojos la idea de un Web OS con todas estas aplicaciones integradas bajo una misma plataforma open source que además, me permita desarrollar soluciones que yo vea convenientes para mis necesidades y mantener un contacto constante con quienes me interesa.

Por ejemplo, ahora mismo, ésta entrada no será automáticamente publicada en mi Facebook. Las fotos que agrego al Facebook, no se integran con las que tengo en Flickr o Google Photos, y mi calendario de Google, no añade mis citas a Gmail o Yahoo! Mail ni a Facebook. Es esa integración social lo que revolucionará el futuro, todas nuestras herramientas interactuando entre sí, para que nosotros interactuemos mejor.

¿Podría Yahoo! con estas nuevas tecnologías, dar vida y concretar el futuro Web 3.0 y convertirse en un serio competidor de Google?

Queda por ver. Yo mientras tanto, me voy mirando YAP y YQL, no vaya a ser que me pille el tren.

NOTA: Si algún día se me permite, eliminare los "captchas" de los sitios web en los que a pesar de estar loggeado con mi usuario, siguen dandome por culo... gracias blogger.com!

WUBI (Impresiones) Instalar Ubuntu desde Windows

Hace poco comence en un nuevo curro y como todo, me he tenido que amoldar a la situación de la empresa, sus políticas y sobretodo a su "Windows network". La verdad es que los comerciales de M$ han hecho un buen trabajo aquí. Lastima.

En fin, lo intente, de verdad, lo intente. Hasta me dije: al menos podré sincronizar la PDA con el Outlook y utilizar el portal con el IE. Incluso instale Firefox, OpenOffice, Putty y cualquier cosa que pudiese acercarme a mi mundo... pero no pude continuar con Windows. Quiźas hace 11 años era algo filosófico e idealista, esmero de un joven pseudo-informático por utilizar algo "distinto" a todos lo demás. Pero hoy es mi herramienta de trabajo, no puedo utilizar otro tipo de sistema, ya que este lo conozco, lo conozco demasiado.

Mi primer intento fue con una máquina virtual, pero fue infructífero, el VirtualBox no cumplió con mis expectativas como aplicación para Windows, ya que al revés (Windows sobre Linux), sí que funciona de maravilla.

Esta entrada es sobre WUBI y mis impresiones al respecto y por qué fue una solución como venida del cielo.

Si te encuentras en esta situación, WUBI es para ti:
1. No puedes o no quieres desinstalar Windows.
2. Quieres compartir tus ficheros, tanto en Windows como en Linux.
3. Quieres algo rápido.

Básicamente, WUBI es un programa para Windows que instala Linux. Esta es la ventana de configuración y es lo único -sí, lo único!- que debéis configurar:



Cuando pulséis OK aquí, empezará a instalarse. Dos cosas a recalcar. Como veréis en la captura, no hay nada acerca de donde va a buscar los ficheros de Ubuntu. Esto es porque los descarga automáticamente de Internet. Si por alguna razón, ya habéis descargado la imagen ISO de Ubuntu Desktop 8.04, sólo tendréis que copiarla al mismo directorio que contiene el fichero de WUBI (wubi.exe)

Dado que sí tenía la imagen, la instalación fue bastante rápida. Así que esta ventana apareció al cabo de 10 minutos:



Y después de 15 minutos ya tenía mi Ubuntu instalado... por fin! Mi suplicio había acabado.



Todo funciona correctamente, los ficheros de Windows están en /host/ y la instalación de Windows sigue allí, sin tocarse, ni las particiones han cambiado, es increible lo que han hecho con WUBI.

He leído que dado que no es una instalación nativa, se pierde rendimiento. Mentira! Va excelente, tengo activados los efectos 3D, el electricsheep y todo va genial. Mi día a día es (todo en la misma máquina) de desarrollo sobre Java, por lo que al mismo tiempo tengo Eclipse, JBoss (completito), MySQL, Firefox, Evolution, 40 terminales y todo va de lujo sobre 2GB de RAM y un procesador normal (sí, ni siquiera es una dual core).

Bueno, ahora sólo me queda acostumbrarme a este ultimo vestigio de que aquí alguna vez, trabajé con Windows:

De persistencia, enums y varargs en Java

A pesar del tiempo que ha pasado desde la salida de la versión 1.5 de Java, muchas personas siguen sin comprender ni utilizar los enum y los varargs, quizás por falta de interés al no encontrarles ninguna utilidad. Espero haber demostrado la utilidad de los primeros con mis entradas anteriores al respecto y, más aun con la siguiente entrada, donde utilizare enum en su forma más básica y además explicare la utilidad de los varargs.

A este respecto, vamos a centrarnos en entities o entidades EJB. Vamos a crear una entidad con persistencia, la cual hereda una serie de métodos para validar, antes de persistir estas entidades, que están definidas correctamente. Es obvio, que este tipo de verificaciones debería hacerlas una capa superior como un DAO, pero cuando queremos definir un EJB específico para tareas de persistencia, este tipo de verificaciones no está de más ya que no sabemos cómo va a ser utilizado el EJB una vez lo hayamos desplegado. Por otra parte, veremos cómo hacer que el EJB se comporte de distintas maneras, según lo deseemos.

Vamos a definir una entidad llamada "MonoUser" la cual contiene los datos de un usuario de la aplicación:



package es.monocaffe.persistence

...

@Entity
public class MonoUser extends ValidEntity implements Serializable{
@Id
private int id;

private String name;
private String lastName;
private String address;
private String phone;
private String email;
private String idDoc;

@Transient
protected Validation validation;

//Implementamos dos contructores
public MonoUser(){
super();
//Definimos un nivel de validación por defecto
this.validation = Validation.ESTRICT;
}

//Este constructor, lo utilizaremos para especificar el nivel de validación
public MonoUser(Validation validation){
super();
this.validation = validation;

//Implementación de getters/setters
...

@Override
public boolean isValid() throws IncorrectFormatException{
validateMonoUser(this);
return true;
}

}


Como veis, es una entidad sencilla, que simplemente, hereda de ValidEntity una clase abstracta con el método abstracto isValid el cual debemos implementar en nuestras entidades. Además, hemos utilizado el enum Validation para controlar el nivel de validación, con esto controlamos el comportamiento de nuestras entities. Será esto lo que utilizaremos para cambiar cuánto y qué deseamos validar de los datos de MonoUser.La clase ValidEntity es algo así:


package es.monocaffe.persistence

...

public abstract class ValidEntity{
public abstract boolean isValid() throws IncorrectFormatException;

//Ahora definimos los métodos que realizarán la validación

public static void validateMonoUser(MonoUser user, Validation validation) throws IncorrectFormatException{
switch(validation){
// Algo a lo que nunca me acostumbrare es que lo siguiente, no compila:
// case Validation.ESTRICT:
// case Validation.NONE:
// case Validation.LAZY:
// Esto nos tirará una excepción de como ésta:
// The enum constant Validation.ESTRICT reference cannot be qualified in a case label
// Vamos a hacerlo bien :)

case ESTRICT:
validateIdDoc(user.getIdDoc);
validateStrings(new String[] {user.getName(), user.getLastName(), user.getAddress()});
validatePhone(user.getPhone());
validateEmail(user.getEmail());

case LAZY:
validateEmail(user.getEmail());
case NONE:
validateUserId(user.getId());
break;
default:
throw new java.lang.IllegalArgumentException();
}
}

public static enum Validation{
ESTRICT, LAZY, NONE;
}

}


Es bastante gráfico el ejemplo de utilización de los enum para definir estados y opciones más concretas. Podríamos haber definido la validación utilizando boolean o int pero preguntaros ¿cuál de los siguientes ejemplos es más fácil de leer y entender?:


MonoUser myuser = new MonoUser(1);

MonoUser myuser = new MonoUser(true);

MonoUser myuser = new MonoUser(Validation.ESTRICT);


Creo que queda demostrada cual es la intención aquí, y la utilidad de los enum. Continuemos con validateStrings.

Esto



public boolean validateString(String a){ ...

public boolean validateString(String a, String b){ ...

public boolean validateString(String a, String b, String c){ ...

public boolean validateString(String a, String b, String c, String d){ ...

public boolean validateString(String a, String b, String c, String d, String e){ ...



Es cosa del pasado. Antiguo, viejo, y como alguien haga esto, deberían ponerle una etiqueta en la frente con @Deprecated :)

Entran los varargs en acción, para poder definir un método que reciba argumentos de manera dinámica, sin tener que repetirnos 30 veces para 30 parámetros distintos. Ojo, que esto también lo podemos utilizar para factories aunque puede ser un poco peligroso. Este es el ejemplo de validateStrings con varargs:


public static void validateStrings(String... strings) throws IncorrectFormatException{
for(String string : strings){
// Haz lo que debas con cada String
}
}


Como veis, sólo hace falta un parámetro para el método, así que podemos meter allí, cuantos String queramos de la siguiente manera:


// La forma limpia
String[] toverify = {user.getName(), user.getLastName(), user.getAddress()};
validateStrings(toverify);

// O como hemos visto más arriba
validateStrings(new String[] {user.getName(), user.getLastName(), user.getAddress()});


Para este caso concreto, quiero verificar que los datos que voy persistir, no sean inválidos y que la BB.DD. se pegue una hostia por esto. Así que supongamos que hemos definido los campos name, lastname y address como varchar(50). Tenemos que evitar, que si alguno de los datos contenidos en una nueva entidad, contienen más de 50 caracteres, podamos atraparlos y tomar medidas al respecto.

Nota: Este problema, lo tendremos al implementar la persistencia utilizando un modelo bottom-up o lo que es lo mismo, el esquema de la BB.DD. ya está definido y no podemos cambiarlo a complacencia. Por lo que al intentar enviar un String demasiado grande para un campo, tendremos problemas.

Por último, si deseamos comentar cada elemento de un enum habrá que añadirle los comentarios típicos de javadoc a cada uno.


/**
* ENUM para determinar distintos tipos de
* validaciones
*/
public static enum Validation{
/**
* Validación Estricta, se validarán todos los campos
*/
ESTRICT,

/**
* Validación amigable, solo validará los campos que no pueden ser 'null'
*/
LAZY,

/**
* Sin validación, sólo valida que el ID este definido en caso de ser necesario.
*/
NONE;
}


Como siempre, espero que os sea de utilidad para mejorar vuestras aplicaciones.

Nota2: El tema de los factories con varargs es peligros, ya que no podemos controlar el orden con el que recibimos los datos, así que podemos terminar creando un MonoUser con la dirección en el campo name.

Iconografía de MonoCaffe

Hoy domingo me ha dado por hacer los iconos que voy a usar en el proyecto.

Lo más importante es mantener una gráfica constante, llamativa y explicativa, por lo que cada icono debería ser representativo del estado de cada cliente, sesiones y errores.

Hasta ahora ésta es la iconografía (palabra que creo me he inventado :)

Icono azul de estado "Conectado"



Icono rojo para errores



Icono gris de estado "Desconectado"



Icono gris para "Salir"



Icono amarillo para "Warnings", sugerencias.



Icono para la ayuda


Icono verde de estado "Conectado y en sesión"


Finalmente, esta es la fuente SVG para editar en Inkscape

SyntaxHighlight en Blogger

Despues de las anteriores entradas y del futuro de este blog, me he dado cuenta de la GRAN limitación de Blogger a la hora de mostrar código con colores y bien formateado. Buscando por Internet encontré la siguiente entrada que explica cómo añadir fácilmente ésta herramienta.

Por lo tanto, esta es una entrada informativa y de prueba, ya que tengo mucha curiosidad por ver esto funcionar.


package es.monocaffe.test;

import java.util.*;

/**
* Javadoc comment
*/
public class Test{
/*
* Long Comment
*/
public static void main (String [] args){
//Short comment
System.out.println("Hello SyntaxHighlight!");
}
}

Excepciones versatiles + Enums + Propiedades

Siguiendo la dinámica propuesta por el sistema de MonoLogger, le ha llegado la hora a las excepciones.
Además de utilizar otro fichero de propiedades donde vamos a definir un código de error y un mensaje, vamos a darle un buen uso a los ENUM de Java en lugar de una clase estática llena de mensajes y códigos de error (puaf puaf puag).

Lo primero que haremos será crear nuestro fichero de propiedades en es.monocaffe.exceptions con lo siguiente:

404=Service Not Found.
405=Computer Not Found.
406=Duplicated User, please use another code.
407=Duplicated Computer, please use another code.
408=Duplicated Value
409=User Not Found.

Para cada uno de estos errores crearemos un nueva excepción, menos para los valores duplicados donde crearemos una excepción general DuplicatedValueException o algo por el estilo. Pensando en objetos, un error de usuario duplicado es una excepción de valores duplicados, por lo tanto, definiremos sólo ésta excepción y cambiaremos sólo el código y el mensaje de error.

Un pequeño gráfico para que os hagais una idea:


¿Qué buscamos? Pues crear un grupo de clases bien definidas para manejar las excepciones de nuestra aplicación que sean facilmente modificables sin la necesidad de tocar mucho código o mejor, nada de código.

Lo más interesante aquí, es el uso que le doy al ENUM MCExceptions. Aquí la definición:


Como podeis ver, a cada elemento de la enumeración le asignamos un valor, el cual corresponde
al código de error que manejamos en el fichero de propiedades. Además, incluimos el mensaje, cargandolo desde el objeto ExceptionsMessages, quien devuelve el mensaje que tenemos en el fichero, utilizando el código de error. Esta clase ExceptionMessages es igual a la clase que definimos para las trazas en la entrada anterior, pero olvidandonos de lo referente a las trazas y centrandonos en la carga desde el fichero de propiedades.

Lo siguiente será crear las Excepciones en si. Como buena práctica, es siempre aconsejable que vayamos a definir unas excepciones que funcionen de forma diferente, crear una clase descendiente de Exception y a partir de ésta, definir nuestras propias excepciones.

Por otra parte, no hay que abusar de esto, y utilizar en lo posible, las excepciones propias de las librerias que estemos utilizando. Por ejemplo, no deseamos crear una excepción para referencias a Objetos nulos (NullPointerException) o excepciones como SintaxisJDBCIncorrecta. Recordar que el uso de las excepciones estandar, tiene la ventaja de la familiaridad que estas presentan para los desarrolladores, quienes reconoceran más rapidamente una NPException o una IOException a alguna otra que nosotros hayamos creado.

Vamos a crear la excepción MonoCaffeGeneralException:


Como veis, aquí empezamos a utilizar nuestro enum: MDExceptions. Este nos va a proveer con el código de error y el mensaje de error el cual mandamos a Exception y que luego podremos utilizar en nuestro código.

A continuación definiremos las excepciones especificas a cada caso que deseemos controlar. El ejemplo más útil para esto, es la DuplicatedValuesException la cual utilizaremos para distintos códigos de error, en lugar de definir una excepción para cada tipo de valor que pueda ser duplicado: User, PC, Client, etc. Vamos a contruir esa excepción:


Cada contructor tiene una aplicación específica, dejando gran libertad al usuario de esta excepción a hacer casí lo que quiera. En el primer caso el programador no tiene ganas de pensar en mensajes ni código de error, sólo en lanzar la excepción y continuar, entonces puede utilizar el primer contructor que generá una excepción de valores duplicados generica y con los mensajes que tenemos definidos.

La segunda excepción permite específicar un mensaje de error. Este contructor es opcional y deberiamos forzar a que cada nuevo error deba ser definido
en el fichero de propiedades y en el enum, antes de poder continuar, pero me siento condescendiente.

El tercero es el que más vamos a utilizar siempre y cuando tengamos definido en el enum el error que deseamos mostrar. Este contructor, tomara la mensaje
asociado al código de error y lo utilizará para lanzar la excepción.

Otra excepciones más específicas como UserNotFoundException, sólo deberia definir el primer tipo de contructor, para mantener un mensaje y código de error
constante a lo largo de la aplicación y sin hacernos pensar cada vez que lo utilicemos. Sólo con escribir:

throw new UserNotFoundException();

será suficiente.

Tambien podriamos seguir el mismo diseño utilizado para los valores duplicados y crear una excepción ObjectNotFound.

Para utilizar en nuestro código estas excepciones hariamos algo como:


throw new DuplicateValueException(); //El valor es duplicado
//...
throw new DuplicateValueException(MCExceptions.DUP_USER); //El usuario está duplicado
//...
throw new DuplicateValueException("Ese DNI/NIF/CIF ya existe"); //Una excepción poco habitual

Eso es todo, espero que os sea de utilidad.

Trazas bien hechas

Una parte muy importante de cualquier aplicación es la de realizar trazas o logs. ¿Dónde estaríamos sin estas?
Para MonoCaffe he desarrollado hoy esta solución que viene en gran medida a cierta experiencia en este tema.
En primer lugar, hay que seleccionar una tecnologia para esto, y yo escogí Log4j de la Fundacion Apache por su facilidad y por los buenos resultados que me ha dado.
Muchos tutoriales y artículos en Internet se enfrascan en explicar la configuración de log4j que es un tema tratado hasta la saciedad y bastante sencillo en cuanto se entiende cada una de las partes.
Es en su uso donde dejan mucho que desear, y a lo que viene ésta entrada, mostrar cómo hacer bien un sistema de trazas el cual se puede utilizar con cualquier librería que utilicemos.
La idea es sencilla, un "singleton" será quien se encargue de realizar las trazas.
En muchos tutoriales os encontrareis con esto:

static Logger logger = Logger.getLogger(MyClass.class);
...
logger.debug("Mensaje de debug");

Lo cual está bien y funciona, pero lo hace difícil de mantener y propenso a errores, por ejemplo, olvidarnos de ponerlo. Otro ejemplo es si cambiamos la librería, en lugar de utilizar, tendríamos que cambiar los imports de TODAS las clases de la aplicación!

La solución a esto sería definir una clase especifica para esto que llamaremos MonoLogger.

package es.monocaffe.logging;
import org.apache.log4j;
public class MonoLogger(){
private final static Logger logger = Logger.getLogger("MonoCaffe");
}

Lo siguiente será añadir los métodos que escriban al Logger:

public static void writeError(Object message){
logger.error(message);
}

Lo mismo para "WARN", "DEBUG" e "INFO". Si paramos aquí, ya tendremos a MonoLogger listo para recibir nuestras trazas:

MonoLogger.writeError("Esto es un error");


Pero vayamos más allá y vamos a añadir un fichero de propiedades a todo esto, donde almacenaremos los mensajes que deseemos enviar al usuario o a otros programadores y una sorpresa especial. Gracias a java.util.PropertyResourceBundle podemos crear un fichero monologs.properties y definir una serie de mensajes utilizando un formato de clave=valor, como si de un Hash se tratase (al fin y al cabo, los objetos Properties, son hijos de de Hashmap).
Creamos el fichero dentro del mismo paquete o directorio que MonoLogger y añadimos lo siguiente:

#Esto es un comentario

client_not_found=No se ha encontrado el cliente que busca.
format_error=Error en el formato de entrada
null_assert=¿Pero qué haces?¿null?
this_is_an_error=Esto es un error
debug=true

Lo siguiente que necesitamos es "cargar" la información del fichero properties en la aplicación:

private static final PropertyResourceBundle monomessages = PropertyResourceBundle.getBundle("monologs.properties");

Y añadir un par de métodos que servirán para obtener los mensajes que están en las propiedades.
¿Qué error estamos cometiendo aquí? Más abajo la solución.

public static String getMessage(String key){
return monomessages.getString(key);
}

De esta manera, si queremos escribir un error como el anterior:

MonoLogger.writeError(MonoLogger.getMessage("this_is_an_error"));

Con esta práctica podremos cambiar todo el lenguaje de la aplicación, cambiando ficheros properties y sin la necesidad de compilar el código.
Finalmente, y lo que más me gusta de todo esto, es la posibilidad de activar/desactivar las trazas de debugging sin compilar y en un solo sitio con la propiedad debug que hemos metido en el fichero de propiedades:

private static boolean debug = doDebugging();
...
public static boolean doDebugging(){
String ret = getMessage("debug");
return Boolean.valueOf(ret);
}

De esta manera, podremos añadir libremente en nuestro código lineas como:

MonoLogger.writeDebug("Esto es un mensaje de Dios");

Y solo aparecerán cuando la propiedad debug sea true.
El error anterior está en que, si el fichero no existe, nos lanzará una excepción como una casa, por lo que es mejor meter esa lógica dentro de un método estático, capturar las excepción y devolver un PropertyResourceBundle.