Nov 28

La maldición de las herramientas

web services internet Es bastante habitual que la gente que empieza a aprender java (o cualquier otro lenguaje de programación) coja el IDE correspondiente (eclipse, netbeans o el que sea)  y se ponga a aprender. Esos novatos van programando y con el tiempo van cogiendo ciertos conocimientos y experiencia en java. Pero desgraciadamente, el IDE les hace no aprender ciertas cosas básicas. No es raro encontrarse gente que lleva programando algún tiempo pero que sería incapaz de compilar o ejecutar un programa java desde línea de comandos, usando los comandos java, el compilador javac, o generar su jar con el comando jar.

Y esto no solo pasa con las cosas básicas ni sólo con los novatos. Cuanto más complejo sea el tema y más nos resuelva un IDE o una herramienta/framework cualquiera, menos cosas aprendemos de ese tema y más dependemos del IDE/herramienta/framework. Cuento mi caso de hace un par de días.

Llevo ya unos días trabajando con Web Services con jax-ws. Cuando empecé con ello, no me leí la documentación completa (soy un impaciente) y en seguida me puse a buscar ejemplos de aquí y de allí para ir haciendo mi propio código. Leí en la documentación que para hacer un Web Services bastaba con ponerle unas anotaciones a la clase (@WebService, @WebMethod, etc), compilarla de forma normal, pasarle la herramienta wsgen que viene con jax-ws y listo. Pues bien, eso hice, montando todo desde el principio con maven y plugins de maven. Y desde luego, pasando ese wsgen automáticamente en la fase posterior al compilado.

Hace un par de días me decidí a hacer un tutorial sobre la aprendido y quise hacerlo más de forma manual, dependiendo lo menos posible de herramientas (eclipse, maven, etc). Así que me puse a ello … y empezaron las dudas y a ponerse de manifiesto las grandes lagunas en mi conocimiento.

El problema desencadenante de todo es que hice todos los pasos para el tutorial sin usar la herramienta wsgen y el web service me funciona. Una clase con las anotaciones correspondientes, un main() que arranca un EndPoint, no se pasa el wsgen y funciona, arrancado desde línea de comandos.

Bueno, según la documentación, después de pasar el wsgen se hace el war para desplegarlo en Tomcat o similar. Será entonces que si usas EndPoint no necesitas pasar wsgen, quizás EndPoint hace todo eso que hace wsgen de forma automática. Pero lo grave de todo es que sí, se supone que hay que pasar wsgen, pero realmente no sé qué hace wsgen (sí lo sé, genera unos fuentes java que no sé exactamente para qué sirven).

Así que nada, seguiré de forma manual, intentaré montar el "hola mundo" en un tomcat y veré si ahí es necesario o no el wsgen… Y luego a pelearse con la parte del cliente, que aunque también la se hacer con herramientas, tampoco entiendo el fondo de todo.

Nov 24

Winpcap, jpcap y java

 En uno de los proyectos (java) tenía necesidad de tocar uno de los campos que hay en las cabeceras IP de los mensajes, en concreto, el campo de tipo de servicio, que de alguna forma establece prioridades para el mensaje.

Mirando la API de java, veo que la clase Socket tiene un método setTrafficClass() que de alguna manera sirve para cambiar este campo, pero fíjate tú que cosas, hago mis pruebas, arranco un sniffer (Wireshark gratuito) y de todos los bits de ese campo sólo consigo cambiar el segundo. Todos los demás bits siempre se captura con ceros. No es de extrañar, la misma API de java indica que ese método puede o no funcionar dependiendo del "underlying network implementation", así que empiezo a buscar librerías que me faciliten esto.

Al final, el conjunto de librerías a usar es Winpcap + jpcap. La primera es un instalable windows que nos proporciona unas librerías de base (dll) para programar sockets a bajo nivel. La segunda es una librería java que nos facilita las llamadas a la librería Winpcap. Con ambas juntas, podemos programar sockets a bajo nivel desde java con comodidad. Eso sí, hay JNI entre medias aunque no lo veamos al programar.

La aplicación típica que muestra en los ejemplos de esta librería jpcap es la captura de paquetes de red, para hacerse uno su propio sniffer. También permite el envío de paquetes IP, dando valores a todos los campos de la cabecera IP a nuestro gusto. Tras pelearme con ella un par de días, todo parece funcionar bien. Sin embargo, en esos días encontré pegas que ¿cómo no?, pongo aquí. No son pegas de la librería, sino del entorno. Allá van:

  1. Posiblemente se necesitan permisos de administrador para ejecutar nuestro programa si anda manejando las tarjetas de red con la librería jpcap.
  2. En Windows XP hay una variable de registro que debe tener un valor concreto para que nuestro programa tenga permisos para modificar el campo traffic class: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters : "DisableUserTOSSetting"=dword:00000000
  3. Cuando se envía un paquete IP, en el ejemplo pone unas direcciones mac "aleatorias". Para que funcione bien es importante poner direcciones mac correctas. La de origen puede tener o no importancia, dependiendo de si por debajo se nos permite o no enviar cabeceras IP con una mac propia falsa. La de destino debe ser correcta o el mensaje nunca llegará. Se puede poner 255:255:255:255:255:255 si la mac de destino es desconocida.
  4. La mayoría de los routers/switch de la red están configurados por defecto para poner a cero los campos del traffic class de la cabecera IP, por lo que aunque cambiemos esos campos, es muy posible que a destino lleguen como ceros. No es que no funcione la librería, sino que hay que configurar bien los routers/switches.
  5. Así que el sniffer que usemos para ver si todo va como debe, debemos arrancarlo tanto en el PC que envía como en el que recibe. En el que envía podemos ver si hemos rellenado todos los campos como queremos. En el que recibe veremos si ningún router malvado lo ha modificado por el camino.

En fin, que he estado entretenido un par de días.

 

Oct 20

Jugando con Proguard

Candado de código ofuscado Estos días estoy jugando con Proguard, una herramienta que coge nuestro jar en java y realiza básicamente tres tareas: ofuscarlo, optimizarlo y eliminar sobrantes. Por supuesto, estas tareas son independientes y podemos realizar unas sí y otras no a nuestro gusto. Tiene plugin para maven, por lo que si usamos maven, podemos realizar cualquiera de estas tres tareas automáticamente al generar nuestro jar.

Ofuscado

La parte de ofuscado es sencilla, símplemente coge los paquetes, las clases y los métodos y les cambia el nombre por a, b, c, etc. Así, en vez de persona.setId(7), si descompilamos veríamos a.b(7). Es decir, se puede seguir descompilando pero el código es mucho menos claro.

Tiene opciones para decirle qué clases o métodos no debe ofuscar. Son candidatos claros a no ofuscar el método main() de la clase principal e incluso el nombre de esa clase, para que luego la máquina virtual java sepa qué debe ejecutar. Los métodos write() y read(9 de serialización, etc. También, en el caso de que estemos intentando ofuscar una librería, se deben no ofuscar las clases que se usen desde el programa principal.

Curiosamente, proguard es bastante inteligente y tiene en cuenta la relexión de java. Si ve que usamos cosas como Class.forname(), o Class.getDeclaredMethod()…., nos avisa con un error si intentamos ofuscar las clases a las que hace referencia esa reflexión.

Optimizado

Con esto no me he metido a fondo, porque no hay muchas posibilidades de ver qué es lo que hace. Entiendo que limpia nuestro código ineficiente, borrando variables locales no usadas o arreglando cualquier cosa que tenga que ver con la efectividad de nuestro código.

Eliminar sobrantes

Esta es la funcionalidad que no esperaba de la herramienta y que más me ha llamado la atención. Elimina de nuestro jar todas las clases que no usamos y borra todos los métodos que no se usan en el resto de clases. ¿Y cómo sabe si lo usamos o no?. Pues por la clase que le hemos dicho que contiene el main(). Empieza a tirar de ahí y borra todo lo que no se use. El resultado es que en muchas ocasiones obtendremos un jar mucho más pequeño si llevamos tiempo trabajando en el proyecto y somos reacios a borrar clases y métodos que no se usan "por si acaso me hacen falta más adelante".

Y ya lo máximo, al integralo en maven, el proceso queda totalmente automático, por lo que una vez configurado en nuestro pom.xml, podemos olvidarnos de que usamos esa herramienta y seguir con nuestros comandos de maven típico mvn package, mvn install, mvn deploy, etc. Eso sí, ojo al hacer el javadoc.

Sep 29

jax-ws y maven

 Siguiendo con los web services y yo, he estado probando herramientas como axis2 y jax-ws. Por supuesto, nada me ha funcionado a la primera y llevo casi dos días peleándome con esto para arrancar un "hola mundo"

Con axis2 el problema es que se me ocurrió hacerme mi propia clase de ejemplo, sin copiar la de los tutoriales. Al final descubro que no soporta enumerados de java y qué casualidad, a mí se me había ocurrido poner uno de los métodos con un parámetro enumerado. Una vez conseguido arrancar un servidor y hacerme un cliente, el servidor me da error cuando intento acceder al web service desde el ciente. Ante las pocas pistas que daba el error, decidí dejar axis-2 de momento y probar con jax-ws.

Con jax-ws me cree un proyecto maven y me puse a ello. Pues bien, las dependencias maven teóricas según la documentación son estas https://jax-ws.dev.java.net/guide/Using_JAX_WS_from_Maven.html . Creo el proyecto, arranco el servidor y todo aparentemente correcto. Accedo desde un navegador a http://localhost:8080/MiServicio?WDSL con el que teóricamente debería obtener el fichero WSDL del servicio… y el servidor da error. Buscando el error por google, me encuentro con esto http://forums.java.net/jive/message.jspa?messageID=222799 Parece que la librería sjsxp de la que depende según maven el jax-ws no es correcta y hay que coger la versión 1.0.1 en vez de la 1.0. Así que me toca "tunear" el pom.xml y hacer esta ñapa

 

<dependency>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-rt</artifactId>
<version>2.1.1</version>
<exclusions>
<exclusion>
<groupId>com.sun.xml.stream</groupId>
<artifactId>sjsxp</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.sun.xml.stream</groupId>
<artifactId>sjsxp</artifactId>
<version>1.0.1</version>
</dependency>
Dicho de otra forma, poner la dependencia de jaxws-rt, decirle que no me traiga su sjsxp y luego poner la dependencia del que yo quiero. Esto ha funcionado bien y ya tengo el servidor arrancado. Ahora toca pelearse con el cliente…. espero no tener demasiados problemas. Luego volveré con axis2.
 
Y aunque todavía tengo que probar más, de momento me gusta mucho más jax-ws que axis2. Los scripts de axis2 generan montones de clases que luego tampoco facilitan tanto la creación de un cliente. jax-ws también genera clases, pero parece que son menos y además quedan "ocultas", ya que genera los .class que luego se empaquetan en el war o jar.
 
Lo segundo que no me ha gustado de axis2 es que aparentemente no tiene posibilidad de hacer un main() que arranque un servidor web con los webservices, dependemos de un Tomcat ajeno con una webapp axis2 instalada sobre la que desplegamos nuestros web services. Bueno, sí tiene un servidor, pero según la documentación puede usarse sin garantías, ya que no está soportado. jax-ws permite hacerse un main y publicar el servicio de una forma tan fácil como esta
public static void main(String [] args) {
   Endpoint.publish("http//localhost:8080/MiServicio", new MiWebService());
   // Aqui puedes seguir haciendo cosas.
}
Bueno, no es nada grave si quieres publicar webservices en internet. Pero en mi caso, necesito que el servidor aparte de publicar los webservices, haga más cosas.

Aug 20

Los Web Services y yo

web services Sí, se que ando de vacaciones, pero por suerte para mí, soy de los que todavía les gusta su trabajo y, aunque no estoy trabajando, sí ando "revolviendo" en cosillas que me resultarán útiles más adelante. En concreto, en varios proyectos en los que es posible que participe se usan o se van a usar Web Services. A aprender me toca, sobre todo teniendo en cuenta que es posible que en uno de ellos sea yo el que tome la decisión de qué herramientas/tecnologías usar. Y en ello ando estos días, entre playa y terrazita, leyendo y jugueteando con los Web Services.

Aunque en el proyecto se nos aconseja el uso de .NET, yo soy más partidario de Java, básicamente por tres motivos:

  • Es el lenguaje que conocemos todos los desarrolladores del grupo. De usar .NET, tendríamos un tiempo de aprendizaje mayor: los Web Services y el lenguaje.
  • En Java casi todo es gratis, con el consiguiente ahorro de licencias, tanto de desarrollo como de los servidores.
  • .NET nos limitaría a servidores Windows, mientras que Java nos permitiría desplegar en cualquier servidor, Windows, linux, solaris, …. Sé que está el proyecto MONO, pero no veo la necesidad de meter más complejidad al asunto.

En cualquier caso, no dudo que con .NET se puedan hacer perfectamente Web Services igual que en java, posiblemente más fácilmente integrados con otras cosas de Microsoft, pero no veo ninguna ventaja clara en lo que es estrictamente el lenguaje de uno sobre otro.

El siguiente tema es si SOAP o REST. En principio, por lo que he ido leyendo, me inclino por REST. Aparentemente es más simple y parece ser que es la nueva tendencia. Posiblemente me decida por REST, pero le veo/tengo un par de pegas/dudas.

  • Al ser más nuevo, me da la impresión de que está menos soportado por los servidores/herramientas actuales. Muchas de las herramientas traen como coletilla "soporte para REST". Siempre es un riesgo meterse en algo reciente.
  • De la misma forma, me da la impresión de que toca codificar más. En SOAP creas tu WSDL y aunque no he buscado con detalle, me da la impresión de que hay miles de herramientas que te generan el código tanto de la parte cliente como de la parte servidor para el uso del Web Service definido por ese WSDL. En REST posiblemente haya equivalentes, pero al ser más nuevo, seguro que hay menos o están menos desarrollados. Insisto, no he mirado en serio, es sólo la impresión que me ha dado en una búsqueda superficial.

Otra gran duda es el tipo de librerías a usar para el Web Service. He visto herramientas como Restlet, (es con la que estoy jugando), pero da la impresión de ser algo muy simple para aprendizaje, no tengo muy claro si puede servir para un servidor en producción con fuertes requisitos de seguridad. También hay cosas como JAX-WS o como Jersey, pero el verlos debajo de GlassFish y sobre todo el primero, debajo de Java EE, me da la impresión de que sería matar moscas a cañonazos. Nuestro proyecto sólo tendría unos pocos Web Services y con datos no muy complejos, aunque sí bastante trasiego.

Finalmente, está el tema de elegir servidor, hay cosas como Spring Web Services, Apache Axis 2 o cosas más tradicionales como Jetty, Tomcat o servidores más "bestias" como Glassfish o JBoss. Por un lado, las ganas de aprender me tiran a Spring, pero el irme a algo conocido me tira por Tomcat. Como he comentado, Glassfish o JBoss me parecen excesivos para lo que pretendemos.

En fin, sigo investigando y haciendo pruebas, pero cualquier sugerencia de gente con experiencia por estos lares, es bienvenida.

Jun 30

Curiosidad en java

auto-boxing en javaDe este post del foro, sale un ejemplo curioso de java. Fíjate en el siguiente código

public static void main (String [] args) {
   Integer a = 100;
   Integer b = 100;

   System.out.println(a == b);
}

Pues bien, este ejemplo da como resultado "true". Si cambias los valores de a y b (pero manteniéndolos iguales), verás una cosa curiosa. Para valores entre -128 y 127, el resultado es "true". Para valores fuera de ese rango, el resultado es "false".

El motivo podemos encontrarlo en este post sobre Auto-Boxing del Weblog de Victor Ramirez. Teóricamente las instancias de Integer que se crean de forma más o menos automáticamente en esas asignaciones deberían ser distintas y el == debería dar siempre "false". Pero por eficiencia, el compilador reaprovecha instancias de estas clases, de forma que cuando se hace este tipo de conversión (de un tipo primitivo a un objeto Integer, Boolean, o lo que sea), se tiene que == da "true" en los siguientes casos:

  • Siempre en los Boolean y en los Byte
  • Entre -128 y 127 en los Short e Integer
  • Entre u0000 y u007F en los Character.

Una curiosidad que puede dar muchos quebraderos de cabeza a un programador novato que no tiene muy controlada la diferencia entre el método equals() y el ==.

Jun 21

Mis dudas con Spring para aplicaciones de escritorio

Logo spring frameworkTodos nuestros sistemas se parecen, unos llevan determinados módulos, otros no. Por eso, siempre he estado pensando la forma de hacerlos modulares de forma que sea fácil quitar o poner módulos de un sistema a otro, cada uno con su configuración. Cuando me puse a pensar en ello llegué a la conclusión de que sería buena idea hacer que cada módulo fuera un jar, independiente de los otros. Luego, en un sistema, es cuestión de instanciar y configurar aquellos módulos que nos interesen, trayendo sólo los jar necesarios.

Puesto que los módulos interaccionan entre ellos y cuando uno de ellos quiere hacer algo normalmente necesita datos de otro módulo, o a veces cuando un módulo genera algo necesita avisar a los otros para que sigan el proceso, también me puse a pensar en ello. Una especie de mecanismo de suscripción de eventos, de forma que en algún sitio cerca del main, donde se instancian y configuran módulos, también se hicieran suscripciones a determinados eventos de un módulo y se avisara de ellos a los otros módulos.

Afortunadamente, antes de ponerme a codificar algo como esto, se me ocurrió investigar por internet y me encontré con los contenedores de inversión de control y en concreto con Spring Framework. Aunque el framework está muy pensado para aplicaciones web, está bien diseñado y puedo bajarme y usar sólo el núcleo, lo que me permite a través de ficheros XML instanciar los distintos módulos, configurarlos y hacer que se vean unos a otros. También me proporciona un sistema de publicación y suscripción de eventos entre módulos. Justo lo que quería.

Así que como suelo hacer en estos casos, cogí a un grupo de gente afín a mis ideas peregrinas, les conté lo fácil y bien que podiamos usar Spring Framework en nuestras aplicaciones y lo usamos en uno de los proyectos que empezaban. La experiencia fue buena y todos quedamos bastante contentos en general, así que decidimos ir aplicándolo a más proyectos.

Pero al empezar a usarlo en más proyectos e involucrar a más gente, empecé también a encontrar más oposición y otros puntos de vista. Un compañero mío más crítico que yo con las innovaciones comentó que "Spring no es más que una forma "friky" de hacer news". No obstante, él también andaba buscando una forma de configurar con ficheros sus módulos, ya que se ve repitiendo código de configuración prácticamente igual de un proyecto a otro.

Y el otro día sucedió algo que me ha hecho empezar a dudar de la utilidad de Spring Framework, al menos usando sólo el núcleo y en aplicaciones de escritorio. Este compañero en uno de los proyectos en los que tiene cierta responsabilidad me dijo que se estaban instanciando en Spring dos módulos iguales con configuración distinta, pero que habían cambiado los requisitos y debíamos eliminar uno de ellos y cambiar el otro por un tercero distinto. Nos pusimos a ello.

Borrar uno de los módulos es fácil, sólo hay que ir borrando las referencias en los ficheros XML y en algunos sitios muy concretos del código. Cambiar el otro fue algo más complejo, buscarlo para reemplazarlo, cambiar nombre de clases, parámetros a inicializar, buscar referencias para ver si son compatibles, etc, etc. Una vez que creímos todo estaba hecho, arrancamos la aplicación y nos saltan un par de errores, nos habíamos equivocado en el paquete de una de las clases de un bean de Srping Framework. Más cambios, nueva prueba y nuevo fallo. Esta vez, a la tercera fue la vencida y todo funcionando. Pero mi compañero hizo el comentario que me metió la duda en el cuerpo: "Si en vez de hacer los news y la configuración en ficheros XML los hubieramos hecho en código, todo esto habría cantado en el mismo IDE. Prefiero ver los errores mientras escribo que verlos después arrancando la aplicación".

Y tiene razón, hacer news y configurar en XML tiene la pega de que errores que se verían inmediatamente mientras escribes en el IDE (un new de una clase que no existe por ejemplo, o la llamada a un set para configurar algo) no los vemos hasta que arrancamos la aplicación.

Hay un plugin para eclipse que permite ver si lo que escribes en el XML de Spring es correcto (las clases existen, los métodos existen, etc), pero no lo tenía instalado y lo de que hayan cogido Spring los de SpringSource, me da "mal rollo", parece que se quieren dedicar a cobrar por dar cursos y que este tipo de plugins o incluso las descargas están cada vez más escondidas previo registro.

Instalaré el plugin, miraré la posibilidad de hacer un test de JUnit que verifique que un XML de Spring es correcto y a ver qué pasa. De todas formas, empiezo a pensar que sólo para hacer news no merece la pena. Me queda el tema de los eventos al que sí sacamos partido….

 

Jun 19

Pros y contras de maven

 Llevamos ya varios años usando maven y nos hemos acostumbrado a él. Recordamos ahora cómo teníamos antes los proyectos y nos asombramos de la mejora conseguida. Sin embargo, no todo es bueno con maven, tiene sus pegas.

Ventajas de maven

Hay principalmente tres grandes cosas que hemos conseguido con maven y sin las que ahora no seríamos capaces de trabajar.

La primera es que ahora todos nuestros proyectos están organizados igual. Esto, por supuesto, puede conseguirse con disciplina y sin necesidad de maven, pero en grupos numerosos de desarrolladores es más fácil de conseguir si la herramienta te obliga a seguir una estructura o al menos, si te obliga a hacer un esfuerzo importante si te quieres salir de esa estructura. Se acabó el  hacer scripts de compilado, el preguntar a otro cuando cambias de proyecto dónde están las cosas, el dejar ficheros o iconos por cualquier lado. Ahora cualquiera puede pasar de un proyecto a otro y sabe manejarse por la estructura sin ningún problema.

La segunda gran ventaja son los jar. Al dejar todos los jar de los proyectos en un repositorio centralizado (usamos nexus) y usar versiones SNAPSHOT de maven, todos tenemos siempre disponibles los últimos jars. Antes era necesario que cada uno compilase los jar que  necesitara o se los pidiera a alguien que los tuviera, o que alguien se acordara de meterlos en el sistema de control de versiones. Eso ya no es necesario, maven/nexus se encarga de que todos tengamos siempre los últimos jars.

Y la tercera gran ventaja es la herramienta de integración continua (Hudson), que todas las noches saca los fuentes del sistema de control de versiones, los compila y pone los jars en nexus. Al ser hudson el encargado de meter los jar en nexus, estos siempre están actualizados y siempre está disponible la última versión para todo el mundo. Y al ser hudson el que compila en un servidor separado, eliminamos por un lado los típicos errores de código que sólo compila en el PC del desarrollador Fulanito porque inadvertidamente ha puesto un path suyo local en algún sitio, o se ha olvidado de meter algo en el sistema de control de versiones. Este Hudson nos sirve además para obtener de él las versiones de instalación tanto para el entorno de producción como para pruebas. Ya no dependemos de que alguien tenga todo lo necesario para generar estas versiones y de que ese alguien esté.

Pegas con maven

Pero no todo son ventajas. La gran y enorme pega de maven es su dependencia de que haya internet o al menos red, para acceder al servidor de Hudson. Los problemas con la red suelen ser relativamente frecuentes: se va el servidor de nexus por el motivo que sea, se cae algún proxy o router, etc, etc. En esos casos, se nos queda un poco parado el tema de compilado. Sí, maven tiene una ejecución off-line, pero no funciona todo lo bien que debiera. Si le falta algo, se empeña en ir a buscarlo a través de la red.

Y esta dependencia de la red se nos hace especialmente grave cuando vamos a instalaciones del cliente en los que no hay internet ni, por supuesto, acceso a nuestro nexus. Si queremos llevarnos un entorno de desarrollo para depurar, corregir algún bug y compilar en las instalaciones del cliente, nos obliga a llevarnos toda una copia de repositorios, o montar el proyecto de forma totalmente independiente de maven. Hay comandos de maven que ayudan a hacer toda esta copia, como dependency:go-offline, pero hay que acordarse de hacerlo.

Es bastante molesto también el tema de plugins de maven. A veces y sin saber el motivo, maven decide que debe actualizar plugins a versiones más modernas. En la mayoría de los casos esto no afecta demasiado, pero a veces si coincide con algún tipo de problema de red o el estar off-line, nos hace imposible compilar.

En fin, desde luego las ventajas compensan con creces los inconvenientes y ni se nos pasa por la cabeza dejar de usar maven, pero a veces algunos problemas misteriosos pueden tenerte de pelea con ellos toda una mañana.

May 07

Mirando OSGi

osgi allianceOSGI es una de esas cosas de las que quería enterarme de qué iba y aprovechando el manual de OSGi de Roberto Montero publicado en javahispano, me he puesto a ello.

El manual, para alguien como yo que tiene ciertos conocimientos de java y no le suenan a chino cosas como fichero de manifiesto, classloader o maven, pero que no tiene ni puñetera idea de qué es OSGi, está muy bien. Da una explicación desde el principio y mostrando un ejemplo "Hola Mundo" en diversas plataformas y con distintas herramientas (Equinox, Apache Félix, Eclipse, Pax Constructor, etc). Al final me queda más o menos claro qué es OSGi y sólo queda hacerme mis propios ejemplos y pruebas.

La idea básica de OSGi es que es una forma de desarrollar aplicaciones java a base de hacer módulos más o menos independientes, pero que pueden colaborar entre ellos. Estos módulos (llamados bundles) no son más que jars con algunas clases especiales propias de OSGi y un fichero de manifiesto con campos especiales, que entiende OSGi. Los bundles pueden colaborar entre ellos, indicando qué paquetes java exportan a otros módulos y qué paquetes java de otros módulos necesitan. Una plataforma que implemente OSGi (Equinox o Apache Félix entre otras) es capaz de cargar, arrancar o parar estos módulos en tiempo de ejecución, sin necesidad de tirar la aplicación y volver a arrancarla. También es capaz de saber qué módulos dependen de cuales para pararlos si les faltan dependencias o arrancarlos sólo cuando todas sus dependencias están cargadas.

Sin embargo hay un problema que tengo de hace mucho y que veo que OSGi no me va a solucionar. Cuando hay dos módulos (jars) que se quiere que colaboren, al final no queda más remedio que uno de ellos vea (necesite) al otro para compilar, ya que si un módulo usa otro, debe ver al menos alguna interface o tipo de dato. Esto hace que los módulos no sean realmente independientes y no puedas aprovechar un módulo para otro proyecto. Si en otro proyecto quiero llevarme el móudlo A y este tira de una interface de B, pues me tengo que llevar B también y éste, desgraciadamente, en una de sus clases que no necesito para nada, tira de una interface del módulo C y así sucesivamente. Para proyectos grandes con muchos programadores (expertos y novatos), lo normal es que todos los módulos acaben dependiendo de todos e incluso empiece a haber dependencias cruzadas. Sí, ya sé que "sólo" es cuestión de pensar antes que módulos debe haber, cómo dependen unos de otros y "sólo" hay que conseguir que todos los programadores respeten esa arquitectura.

Una posible solución, que había descartado, para este problema es que cada módulo fuera en realidad dos módulos. Uno pequeño, con sólo las interfaces y clase de datos (beans) que es lo que dependerían los demás módulos. Y otro módulo más grande con todo el resto del código. Lo descarté porque en un proyecto grande con 30 o 40 módulos, se nos duplica el número de módulos y de jars.

Sin embargo, en los ejemplos OSGi que he visto, parece que hace eso mismo. Si crea un módulo que es un servicio que van a ver otros módulos, crea dos módulos: uno con la interface del servicio y los bean de datos, el otro con la implementación. Los módulos que usen servicios de éste, sólo dependen del módulo pequeño con la interface.

En fin, a jugar un poco con OGSi y a repensar esto de partir los módulos en dos submódulos: lo visible al exterior y la implementación concreta.

Apr 28

WeakHashMap y una posible solución de diseño

WeakHashMapHace poco descubrí la clase WeakHashMap de java y encontré un ejemplo de su uso con una idea interesante que no se me había ocurrido y que puede ser una alternativa buena de diseño para resolver determinados problemas. Me "explayo":

Imagina que en tu empresa haces proyectos de software en los que siempre hay listados de personas, pero la información que se guarda de cada persona varía de un proyecto a otro. Por ejemplo, en todos los proyectos las personas tienen DNI, nombre, apellidos, fecha de nacimiento, sexo, etc. Pero en un proyecto que hacéis de un taller de coches la persona tiene además asociado el coche que tiene, con su marca, modelo, etc. En otro proyecto que hacéis de nóminas la persona tiene también su cargo en la empresa, sueldo base, complementos, antigüedad, etc. Y así con cualquier tipo de proyecto que se os ocurra relacionado con personas.

Como somos una empresa espabilada y vemos que siempre tenemos personas en los proyectos y unos datos que siempre son comunes, decidimos hacernos una librería en la que tenemos la clase Persona con los atributos comunes, paneles para editar personas, JTable de personas, daos de persistencia en bd, tablas en bd, etc.

Cuando llega un proyecto concreto debemos ampliar los datos de esa Persona que tenemos en la librería. Y se nos presentan varias opciones.

La que rápidamente piensa cualquiera que haya estudiado orientación a objetos (y quizás sin mucha experiencia), es heredar de Persona y añadir en la clase hija los atributos adicionales. Así, en el ejemplo de nuestro taller, tendríamos PersonaConCoche extends Persona y le añadimos todo lo relativo al coche. Esta solución se puede hacer y funciona, pero al implementarla vemos que no acaba de convencer. El código que hagamos adicional para el taller va a tratar con una Persona a la que hay que hacer cast a PersonaConCoche, debemos rellenar los datos de un mismo objeto en dos consultas de bd e insertarlo en dos veces, unos campos primero y otros después, posiblemente en el orden adecuado, etc, etc.

La siguiente opción que se nos ocurre es poner un método en nuestra clase Persona que sea setDatosAdicionales(datosAdicionales). ¿Pero qué tipo ponemos a esos datosAdicionales en nuestra librería general para que nos sirva en cualquier proyecto?. Pues o bien ponemos Object, o bien hacemos que nuestra clase Persona sea un genérico. La primera opción no es especialmente clara, porque nunca sabremos que hay dentro de datosAdicionales y si somos varios programadores, alguno puede despistarse y meter ahí cualquier cosa. Lo del genérico es un rollo, ya que obliga a casi todas las clases de nuestra librería general (paneles, daos de bd) a ser a su vez genéricos.

Y finalmente está la solución del ejemplo de WeakHashMap que había comentado y que, aunque no he probado, tiene muy buena pinta. Consiste básicamente en meter esos datos adicionales directamente en un HashMap. La clave sería nuestra clase Persona tal cual la tenemos en la librería común y el valor los datos adicionales. Podemos concretar el HashMap genérico para nuestros datos concretos HashMap<Persona, Coche> y así todo queda claro sin posibilidad de confusión. No necesitamos tocar nada de nuestra librería y podemos hacer nuestros paneles y daos de Coche por separado. Por supuesto, este WeakHashMap se guarda fuera de la clase Persona (si no, no hemos mejorado nada respecto a las soluciones anteriores) y hay que hacerlo accesible al que lo necesite.

La ventaja para este tipo de solución de un WeakHashMap frente a un HashMap normal es que el primero no se guarda referencias reales a los objetos que tiene almacenados (ni de Persona ni de Coche), por lo que si en nuestra lista de personas (que tenemos aparte) eliminamos una persona y perdemos todas las referencias a ella, el recolector de basura reclamará también la Persona y sus datos guardados en el WeakHashMap. Si lo hubiéramos metido en un HashMap normal, las referencias a Persona y Coche ahí dentro son reales, por lo que el recolector de basura no los libera. por lo que después de borrar la persona de nuestra lista de personas, debemos además acordarnos de hacer y hacer una llamada al hashmap.remove(persona). Ya sabes que un un grupo de uno o más programadores, siempre hay al menos un despistado que se olvida de estas cosas.

No veo el momento de aplicar una solución de este estilo en algún sitio, a ver si me encuentro con pegas prácticas.