Record en Java 14

Logo de Java

No suelo mirar en cada versión de Java qué novedades hay. Más bien me suelo tropezar con ellas. Y la última con la que me he tropezado son los record, de java 14 en adelante

Qué es un record en Java. Un ejemplo de record

public record Registro(int a, String b) {
}

Esto crea una clase Java, de nombre Registro, con dos campos int a y String b. Pero esa clase tiene una serie de particularidades

Tiene un constructor con dos parámetros, a y b

Los campos a y b son inmutables, es decir, una vez construida la clase, no podemos cambiar su valor

Tiene getter, aunque no cumplen la nomenclatura estándar. Son registro.a() y registro.b(). Como son inmutables, no hay setter

Tiene una implementación por defecto de hashcode(), equals() y toString() que contemplan todos los campos (a y b)

Para qué sirve un record en Java

Pues tiene una utilidad muy relativa. Es una forma sencilla de construir una estructura de datos que no se pueden modificar

Así que sirve principalmente para pasar datos de un lado a otro, pero que no necesiten ser modificados, ya que es inmutable. Y para esos casos, es una forma sencilla de definir esa estructura de datos.

Constructores y métodos en un record en Java

Podemos dar lógica al constructor por defecto de record, que lleva dos parámetros o definir otros constructores

public record Registro(int a, String b) {
    public Registro(int a, String b){
        if (null==b){
            throw new IllegalArgumentException();
        }
        this.a=a;
        this.b=b;
    }
    public Registro (int a) {
        this(a,"");
    }
    public Registro(){
        this(0,"");
    }
}

Hemos añadido lógica al constructor con dos parámetros para que no admite un null como parámetro b. Hemos añadido un constructor con un solo parámetro y sin parámetros, que llaman al constructor con dos parámetros dando valores por defecto.

Podemos también añadir otros métodos. Lo único es que en esos métodos no podemos modificar los valores de a y b, ya que internamente están definidos con final.

Publicado en java | Etiquetado , , | Deja un comentario

Apache Kafka Listeners y Advertised Listeners

Logo de Apache Kafka

He empezado a jugar, investigar y escribir alguna cosilla sobre Apache Kafka. Lo utilicé hace ya unos años, pero siempre me lo habían dado configurado y yo únicamente programaba clientes productores/consumidores.

Al ponerme a investigar cómo instalar Apache Kafka con Docker me he encontrado con un montón de parámetros de configuración, algunos de ellos muy parecidos y poco clara su utilidad, en concreto KAFKA_LISTENERS, KAFKA_ADVERTISED_LISTENERS y KAFKA_LISTENER_SECURITY_PROTOCOL_MAP. Detrás de ellos, unos cuantos derivados como KAFKA_INTER_BROKER_LISTENER_NAME y KAFKA_CONTROLLER_LISTENER_NAMES

Vamos a ver qué son y por qué tantos

Problema con los Listener de Kafka

Si instalamos uno o más nodos de Apache Kafka en cluster, dentro de una única red y en el que todos los clientes van a acceder también desde dentro de esa red, no hay mucho problema. Cada nodo estará accesible desde kafkaN:9092 o como queramos configurarlo, siendo kafkaN el nombre del nodo concreto en la red y 9092 el puerto que queramos poner.

El problema surge si varios de esos nodos están en redes distintas y/o los clientes acceden desde redes distintas. Es posible que el acceso a un nodo no sea igual desde dentro de una red que desde otra.

Un ejemplo claro es si montamos en cluster de nodos Kakfa en una red de contenedores docker pero algún cliente va a correr en el host anfitrión. En este caso, es normal publicar el puerto 9092 en el host anfitrión con la opción -p 9092:9092 del contenedor. Desde dentro de la red de dockers podemos acceder al nodo con kafkaN:9092, pero desde el host anfitrión accederíamos con localhost:9092.

Hasta ahora esto no sería un problema si no fuera por el siquiente comportamiento de Kafka. Cuando el cliente se conecta desde el host anfitrión con localhost:9092, lo primero que hace es preguntarle a Kafka cual es el nodo principal y Kakfa le contesta con la IP/nodo y puerto del nodo principal. En nuestro ejemplo, le contestaría con kafkaN:9092. Nuestro host anfitrión iría a ese nodo:puerto a seguir la comunicación, pero no la tiene accesible.

Veamos como resolver todo esto con los parámetros de configuración de arriba.

KAFKA_LISTENERS

En esta propiedad ponemos los puertos e interfaces de red por los que tiene que escuchar el broker de Kafka. Podemos poner tantos como nos hagan falta y en la misma propiedad ponemos un nombre de nuestro gusto a esa esccuha. Por ejemplo

KAFKA_LISTENERS: ‘LISTENER_INTERNO://:9094,LISTENER_EXTERNO://:9092’

Hemos dicho a Kafka que abra dos puertos, el 9092 y el 9094. Para identificarlos en las siguientes propiedades, les hemos puesto los nombre LISTENER_INTERNO, para la red interna de dockers y LISTENER_EXTERNO, para el host anfitrión. Si tenemos otras configuraciones de red o necesidades, podemos definir aquí los que queramos con los nombres y puertos que queramos.

En el formato podríamos poner, por ejemplo, LISTENER_INTERNO://<IP>:9094 siendo la IP la de la interface de red por la que queramos que se ponga a la escucha. Si no ponemos nada (como en el ejemplo) o ponemos 0.0.0.0, se pone a la escucha de todas las interfaces de red disponibles.

KAFKA_ADVERTISED_LISTENERS

En esta propiedad debemos decir qué IP y puerto debe comunicar Kaka a sus clientes para que puedan conectarse correctamente. Esta IP puerto puede ser distina según sea alguien interno a la red de dockers o alguien del host anfitrión.

Imagina que usando las opciones de docker, hemos publicado el puerto 9092 en el puerto 29092 del host anfitrión y que al host docker le hemos puesto de nombre kafkaN. Esa propiedad podría quedar así

KAFKA_ADVERTISED_LISTENERS: ‘LISTENER_EXTERNO://localhost:29092,LISTENER_INTERNO://kafkaN:9094’

Para los que se conecten al puerto 9092 (LISTENER_EXTERNO, es decir, host anfitrión), KAFKA les dirá que deben hacerlo a través de localhost:29092. Para los que se conecten al puerto 9094 (LISTENER_INTERNO, es decir, dentro de la red interna de docker) deben hacerlo con kakfaN:9092

KAFKA_INTER_BROKER_LISTENER_NAME

Los distintos broker de Kafka se conectan entre ellos para intercambiar información. Como hemos abierto dos puertos, uno para listener internos y otro para externos, debemo indicar aquí a Kafka cuál debe usar para interconexión entre nodos. Habitualmente es el de la red interna. Un ejemplo podría ser como lo siguiente

KAFKA_INTER_BROKER_LISTENER_NAME=LISTENER_INTERNO

KAFKA_CONTROLLER_LISTENER_NAMES

Este es sólo necesario si usamos KRaft en vez de Zookeeper. Usando KRaft, los mismos servidores Kafka tienen un protocolo entre ellos para decidir cual de ellos hace de controlador principal del cluster.

Para los controladores debemos seguir una lógica similar a los Listener. Es decir

  • En KAFKA_LISTENER definir un puerto adicional para atender a los demás broker del cluster. Además de los dos que ya tenemos, añadimos algo como CONTROLLER://9093.
  • En KAFA_CONTROLLER_LISTENER_NAMES debemos indicar cual de los tres listener que hemos abierto (interno, externo y controller). Es decir, KAFA_CONTROLLER_LISTENER_NAMES=CONTROLLER

Publicado en Kafka | Etiquetado , | 2 comentarios

Jugando con Elasticsearch y Java

En el curro estamos empezando a usar Elasticsearch para algunos temas. Como a mí en concreto no me ha tocado, me ha dado por hacer algunas pruebas por mi cuenta, desde Java, y así aprender lo básico.

Llevo un par de días con ellos … y no sé si me convence. No Elasticsearch en sí, no he hecho pruebas de rendimiento, ni con clusters ni nada de nada. Sólo con la API de Java.

¿Y por qué no me convence?. Varios motivos.

Fluent builders de la API Java de Elasticsearch

La web de Elasticsearch, respecto a la nueva API de Java, pone

Use of fluent builders and functional patterns to allow writing concise yet readable code when creating complex nested structures.

básicamente, que como se usan «fluent builders», las estructuras complejas anidadas se hacen más concisas y legibles. Aquí un ejemplo de código de la documentación.

// Search by product name
Query byName = MatchQuery.of(m -> m 
    .field("name")
    .query(searchText)
)._toQuery(); 

// Search by max price
Query byMaxPrice = RangeQuery.of(r -> r
    .field("price")
    .gte(JsonData.of(maxPrice)) 
)._toQuery();

// Combine name and price queries to search the product index
SearchResponse<Product> response = esClient.search(s -> s
    .index("products")
    .query(q -> q
        .bool(b -> b 
            .must(byName) 
            .must(byMaxPrice)
        )
    ),
    Product.class
);

Pues como promete la documentación, es una sintaxis «sencilla» y sobre todo «legible» para consultar productos con un nombre concreto y un precio máximo. No quiero ni imaginarme cómo será una consulta con más condiciones.

Documentación escasa

La documentación oficial es muy escueta, poco o nada didáctica. Las búsquedas por google tampoco dan mucha más información más allá de la documentación oficial. Quizás porque esta API de Java es relativamente nueva en Elasticsearh, antes se usaba directamente una API REST.

Si te fijas en el ejemplo anterior, para buscar por nombre usa MatchQuery y para lo del precio máximo RanqeQuery. Más allá del Javadoc, no he encontrado un tutorial «didáctico» donde te explique las alternativas que hay, cómo se usan, etc.

En fin, toda una odisea de búsqueda de documentación, prueba, ensayo y error como quieras hacer algo más allá de una consulta simple.

Aunque no sea exactamente de la API de Java, reto a alguien a que me diga que roles son los que hay que poner al crear un usuario que quieras que haga las típicas operaciones CRUD. Aquí tienes la lista de roles predefinidos.

Dichosa caché

En mis pruebas, un main sencillo, todo seguido: crear un índice, insertar, hacer un search para consultar lo insertado …. y la consulta no devuelve nada. Mirando directamente con el navegador web conectado a la URL de Elasticsearch veo que los datos sí se han insertado.

Un buen rato probando la consulta, por id de objeto insertado con get() funciona, con search() y condiciones no funcina, pongas lo que pongas…. pero de forma aleatoria, alguna vez sí va.

Así que sospechando de algo similar al commit que se hace con las tradicionales SQL, veo que hay un elasticsearchClient.indices().flush(). Tampoco va. Pero si después de eso pones un sleep() entonces ya funciona todo.

Es decir, tiene pinta de que en el mismo programa, con la misma instancia de elasticearchClient hay alguna caché que es distinta para cada operación y lo que insertas en una línea, no está disponbile para consulta en la siguiente.

Hay dos clases cliente de Elasticsearch. Una es síncrona y la otra asíncrona. La síncrona teóricamente te devuelve el control una vez ha terminado la operación, la asíncrona te lo devuelve inmediatamente aunque no haya terminado la operación.

¿Con cual de las dos crees que me estaba pasando lo de insertar y no tener disponibles los resultados?

¡¡Efectivamente, lo has adivinado!!. ¡¡Con la SINCRONA!!.

Una lanza a favor de Elasticsearch

En el curro, como comento, lo están usando. Pero lo usan en combinación con Kibana, o con clusters de nodos por el tema de alta disponibilidad, etc. Parece que en ese sentido la gente está muy contenta.

El uso que están dando es el almacenamiento masivo de información procedente de sensores desde uno o varios ejecutables separados. Luego con Kibana o bien con una aplicación hecha a medida, las consultas de esos datos históricos. Con Kibana no hay que pelearse con las consultas. Con la aplicación a medida seguramente topes con el primer problema que he comentado, pero todo es cuestión de pelearse y acostumbrarse.

En cuanto a cluster de nodos, parece que dos grupos de trabajo que lo han probado por separado e implementado en sus proyectos han quedado contentos con su comportamiento.

Conclusión

Pues lo dicho, no parece adecuada para uso como una base de datos de trabajo normal. Pero sí parece adecuada para meter muchos datos en cluster y análisis a posteriori de dichos datos, bien con Kibana, bien con aplicaciones a medida.

Publicado en elasticsearch | Etiquetado , | Deja un comentario

¿Es necesario usar isDebugEnabled()?

logo slf4j

Cuando vemos código que usa un sistema de log para mostrar los mensajes de la aplicación, suele ser habitual ver código como el siguiente

if (log.isDebugEnabled()) {
   log.debug("Hola, " + nombre);
}

¿Por qué se usa isDebugEnabled()?

El motivo de usar esta llamada es para evitar que se construya la cadena de texto a mostrar en el log si realmente no se va a mostrar. En el ejemplo anterior, no tiene sentido componer la cadena «Hola, «+nombre si no la vamos a mostrar porque el log de debug no está habiliado.

Así que si nuestro mensaje es de debug, ponemos primero el if para ver si dicho mensaje va a salir. Sólo construimos la cadena si va a mostrarse.

En este ejemplo, construir la cadena no es muy costoso, pero puede haber cadenas a mostrar que requieran más variables, que las llamadas a toString() que hará java para esas variables no sean rápidas, quizás estén involucrados los antiguos StringBuffer que además están sincronizados.

Así que isDebugEnabled() se usa sólo por eficiencia. Evitar construir las cadenas de log si no van a mostrarse.

Por supuesto, tenemos variantes para los distintos niveles de log, como trace, info, warn, etc.

¿Pero es realmente necesario isDebugEnabled()?

Los nuevos sistemas de log, como slf4j, hacen esta llamada inútil. Eso sí, siempre y cuando no construyamos nosotros la cadena como en el ejemplo. Una llamada mejor, usando slf4j, sería la siguiente

if (log.isDebugEnabled()) {
   log.debug("Hola, {}", nombre);
}

Fíjate que en vez de construir la cadena como antes y pasar un único parámetro al método debug(), estamos pasando una cadena «Hola, {}» que tiene una pareja de llaves y luego estamos pasando un parámetro. Deberíamos pasar tantos parámetros como parejas «{}» hayamos puesto en la primera cadena.

slf4j irá reemplazando los «{}» por los valores de los parámetros que pasemos a continuación, en orden. Y lo mejor de todo es, que internamente, mirará si el log va a salir o no antes de hacer las operaciones para componer la cadena.

Así que como lo hace internamente, la llamada externa a isDebugEnabled() nos sobra totalmente, es redundante. Nos basta con

log.debug("Hola, {}", nombre);

Mucho más sencillo que ir poniendo un if antes de cada log ¿verdad?

¿Y si quiero sacar el log de una excepción?

En los métodos normales de otros sistemas de log se admitía un texto como mensaje y un segundo parámetro que fuera una excepción. La idea es que si se produce una excepción en nuestro código y la capturamos, podamos sacarla por el sistema de log

try {
   ...
} catch (Exception e) {
   log.error("Se ha producido un error", e);
}

Si ahora slf4j admite el mensaje con «{}» y todos los parámetros que queramos para reemplazar los «{}» con sus valores, ¿cómo sacamos la excepción?. Sencillo, nos basta ponerla como último parámetro. slf4j supondrá que si el último parámetro es de tipo Throwable, es una excepción que debe mostrar y no un parámetro para reemplazar una pareja «{}»

try {
   ...
} catch (Exception e) {
   log.error("Lo siento {}, se ha producido un error", nombre, e);
}

Conclusión sobre isDebugEnabled()

Así que la conclusión es que si usas sl4j y un formato de mensajes de log basado en cadenas con «{}» para reemplazar valores, NO necesitas usar isDebugEnabled().

Tu pregunta ahora será ¿Por qué usar slf4j?

Publicado en buenas costumbres de programación, slf4j | Etiquetado , , | Deja un comentario

Qué es SLF4J

logo slf4j

SLF4J, Simple Logging Facade For Java, es una abstración de un sistema de logging para una aplicación Java. Con esto queremos decir que no es un sistema de log completo, sino que simplemente nos sirve como «fachada» para hablar con otros sistemas de log. Por ejemplo, java.util.logging, log4j o logback.

Por qué necesitamos SLF4J

¿Y para qué necesitamos esta librería fachada?. De hecho, aparentemente complica más el asunto, porque necesitaríamos tres librerías de logger: La de SLF4J, la del logger concreto que queramos usar y un adaptador entre ambas.

Imagina que no estás haciendo una aplicación Java completa, sino sólo una librería o un módulo que quieres luego reaprovechar en otras aplicaciones. O incluso que quieres publicar para que usen otras personas.

Si en ese módulo usas tu logger favorito como podría ser logback o log4j, la aplicación completa que use este módulo estaría obligada a usar el mismo sistema de logger, o bien a tratar y configurar dos, el de tu módulo y el que se quiera usar la aplicación principal.

Esto, desde luego, puede no ser lo deseado en el primer caso y sería un engorro en lo segundo. La solución ideal es que el módulo no «obligue» a la aplicación a usar un sistema de log concreto. Y por eso SLF4J es útil en estos casos.

Cómo usar SLF4J

En tu librería añades la dependencia slf4j-api y la usas como un sistema de log normal. La aplicación que quiera usar tu librería es libre de decidir que sistema de log quiera usar. Por ejemplo, si quiere usar log4j, simplemente tendría que añadir la dependencia de log4j-core y un adaptador entre este y slf4j, que sería la librería log4j-slf4j-impl. Todo el log de tu libreria con slf4j-api será redirigido a log4j-core.

En el caso de logback, tienes logback-classic, que ya tiene incluido el adaptador. Por ello, bastaría sólo con esta dependencia.

Si en tu librería tienes test de JUnit o pequeños main de prueba, no tendrás sistema de log. SLF4J no funciona solo, necesita que le pongas un sistema de log concreto y el adaptador. Así que para estos test de JUnit o programas de prueba, necesitas añadir la dependencia de test tanto del sistema de log concreto como del adaptador.

Para facilitar esta tarea, slf4j proporciona su propio sistema de log simple, llamado slf4j-simple. Bastaría que lo añadas como dependencia de test si no quieres complicarte la vida con otros log más completos. En cualquier caso, para este propósito de test, logback-classic también es sencillo de usar puesto que lleva el adaptador incluido y por defecto no necesita ningún fichero de configuración.

Qué problemas podemos encontrar con SLF4J

Cuando hacemos nuestra librería, a nivel de uso, no deberiamos encontrar ninguno. Como mucho, que alguna funcionalidad muy específica de algún sistema de log concreto no esté accesible desde SLF4J, pero serían casos muy raros y concretos. Tampoco creo que sea algo muy recomendado que una librería que pretendes que sea útil en varios proeyectos use cosas muy concretas de un sistema de log muy concreto.

Sin emabargo, cuando alguien haba una aplicación usando nuestra libería, puede encontrar algún problema.

Failed to load class «org.slf4j.impl.StaticLoggerBinder». Es encesario que en la aplicación tengamos una librería adaptador. Si no se pone en las dependencias, aparecerá este error indicando que no la encuentra. Es lo que puede pasar también en nuestros test de JUnit de la libería si no ponemos como dependencia de test el log y el adaptador.

Class path contains multiple SLF4J bindings. Este error sale en caso de que se pongan dependencias de varios adaptadores, por el motivo que sea. Se deben revisar dependencias y dejar sólo uno. Realmente el error es un Warning. slf4j elegirá cualquiera de los adaptadores. Y ya conoces a Murphy, elegirá el que no queríamos que cogiera.

Este error, si no es por un despiste nuestro, puede dar guerra. Por desgracia, hay librerías y frameworks muy extendidos que no usan SLF4J, sino que de alguna forma usan su propio sistema de log. O que usan SLF4J pero vienen además con el sistema de log y el adaptador que ellas han decidido. Por ejemplo, apache storm-core viene con slf4j, log4j y su adaptador. Si necesitas usar storm-core dentro de tu aplicación y no quieres usar log4j, tienes un problema.

Problemas de versiones. Puesto que hay tres librerías implicadas: slf4j, el log concreto y el adaptador, es importante asegurarse que las versiones son compatibles entre sí. La mejor forma es buscar el adaptador en mvnrepository y ver sus dependencias. Por ejemplo, si miras el adaptador log4j-slf4j-impl, versión 2.22.0, verás que usa log4j versión 2.22.0 y slf4j-api versión 2.0.9. No suele haber problemas con pequeñas diferencias de versión, pero asegúrate que no sean versiones muy distintas o de revisarlo en caso de que el log te haga «cosas raras».

Publicado en log4j, logback, slf4j | Etiquetado , , , | Deja un comentario

Ephemeral ports

windows logo

Desde hace tiempo usamos ActiveMQ en el curro, embebido en Spring. Una cosa que nos pasaba esporádicamente en nuestros ordenadores de desarrollo es que ActiveMQ no arrancaba. Daba error de que su puerto por defecto, el 61616 ya estaba en uso. Vamos, el típico error

Cannot bind port 61616: Address already in use!

Por más que hacías un netstat para ver si el puerto realmente estaba en uso, no había nadie utilizando dicho puerto. Intentos sucesivos de arrancar la aplicación fallaban con el mismo error. Y reiniciando el ordenador entero, solía arreglarse y ya no fallaba…. hasta que aleatoriamente y de forma bastante esporádica, en otro arranque del PC, ya no funcionaba.

Así que la última vez que me pasó, hace un par de días y tras reiniciar el ordenador ver que el problema no se arreglaba, decidí investigar en serio. Costó algo encontrar la información, pero ahí va.

La culpa es de los ephemeral ports

Parece ser que los sistemas operativos, tras su arranque, se reservan una serie de puertos para establecer las comunicaciones y no dejan usarlos aunque no los estén usando ellos. Suelen ser bloques seguidos de 10 puertos dentro de un rango grande. Estos puertos son los que se conocen como ephemeral ports.

En el caso de Windows, mi caso, elige esos puertos de forma aleatoria en cada arranque dentro del rango 49152-65535. Y el puerto 61616 de ActiveMQ está ahí dentro, así que es candidato a que en algún arranque el sistema operativo lo pille y ActiveMQ no arranque. Linux usa otro rango, pero también hace algo parecido.

El comando windows

netsh int ip show excludedportrange protocol=tcp

nos muestra qué puertos ha pillado el sistema operativo y no podremos usar, aunque estén libres. La salida de este comando podría parecerse a la sigiuiente

Puerto de inicio Puerto final
———- ——–
80 80
8005 8005
21112 21112
50000 50059 *
50060 50159
61510 61609
61610 61709 <– Justo el 61616 cae en este rango
61910 62009
62010 62109
62110 62209
63442 63541

  • – Exclusiones de puertos administrados

Hay varias soluciones a este problema. Una, desde luego evidente, es configurar ActiveMQ para que use otro puerto fuera del rango 49152-65535 de puertos que podría querer reservar Windows

Tenemos otra opción para solucionar el problema en el momento que nos ocurra, sin necesidad de reiniciar el ordenador, pero desde luego, no es la mejor solución. La comento por si fuera de utilidad en algún momento.

Desde un terminal de comandos windows con permisos de super usuario, sería ejecutar los siguientes comandos

net stop winnat
Arrancar nuestra aplicación
net start winnat

net stop winnat para el servicio Windows Nat Driver (winnat), que es el que suele reservar esos puertos. Parando el servicio, se liberan los puertos. Podemos entonces arrancar nuestra aplicación sin problemas. Luego volvemos a arrancar el servicio winnat que pillará otros puertos que estén libres.

La solución más permanente, si no puedes cambiar en tu aplicación el puerto de ActiveMQ (o el que te esté dando problemas), es reservar tú ese puerto para que windows no lo reserve en el arranque. Para ello, con un terminal de comandos de windows con permiso de super usuario puedes ejecutar el comando

netsh int ipv4 add excludedportrange protocol=tcp startport=61616 numberofports=1

Eso reserva 1 puerto (numberofports) a partir del puerto 61616 (starport). Puedes reservar un bloque algo más grande por seguridad, por ejemplo, 20 puertos a partir del 61610 o lo que consideres. Esta orden permanece en arranques sucesivos del PC, por lo que no tienes que ejecutarla cada vez.

Para ejecutar este comando es importante que esos puertos estén libres en ese momento. Si no lo están, tendrías que parar temporalmente winnat como has visto antes.

Los puertos que hayas reservado aparecen en el listado anterior con un * detrás, como en el primero ejemplo es el rango 50000 a 50059.

Publicado en trabajo, windows | Etiquetado , , , | Deja un comentario

Jugueteando con el SEO

logo de seo

Mirando cosas en general por youtube, me encontré con los videos de Romuald Fons. Explicado de una forma muy peculiar, que no sabes muy bien si es un vende humos o realmente sabe, explica cosas de SEO (Search Engine Optimization). Al final sacas las ideas generales claras, pero te quedan muchas dudas sobre los detalles. Ves también con otros expertos SEO que hay bastantes discrepancias entre ellos con esto de los detalles.

La idea principal está clara. Cuando alguien busca algo en internet, lo hace con un objetivo (intención de búsqueda lo llaman) y google tenderá a ponerle primero aquella página que cumpla con dicho objetivo. Si buscas «cómo leer un fichero en java», google te pondrá primera aquella página que considere que explica mejor este tema concreto.

Pero como me quedaron dudas con los detalles, me decidí jugar a experimentar. Y como tengo una wiki de programación, pues ya tengo las herramientas para jugar.

¿Cuál es el resultado de todo esto?. Pues ni idea, llevo poco más de un mes jugueteando y no noto nada especial en las visitas, ni mejor ni peor. También un mes quizás es poco, por ahí mencionan que igual los resultados empiezan a verse tres meses después.

Pero si hay un resuttado «secundario» y es que me he liado a arreglar tutoriales viejos y escribir otros nuevos como un loco. Llevo un mes largo de actividad constante en una wiki que casi tenía abandonada. Y eso también lleva a muchos «commit» en mi git de ejemplos.

A modo de resumen:

Así que nada, de momento estoy «motivado» y sigo escribiendo cosas como loco. Aunque sigo intentando hacer SEO, al final me puede el espíritu de programador y me lío con tutoriales que sé que no tienen demasiado interés, pero que me presentan problemas que quiero entender o resolver. Por ejemplo, quién va a tener interés en saber cómo se hace un Doclet en Java. Muchos programadores java no saben qué es eso, así que muchos menos lo van a buscar en google.

Publicado en google, web | Etiquetado , , | Deja un comentario

Jaspersoft Studio: Donde «Buen Funcionamiento» es una Meta Difícil de Alcanzar

Logo de Jasper Reports

He estado creando una serie de tutoriales sobre Jasper Studio y sobre Jasper Report. La parte de Jasper Studio me ha dejado «contento», falla más que una escopeta de feria y hay que pelearse bastante para conseguir lo que quieres o, simplemente, que no salten excepciones sin más.

Así que decidí preguntarle a ChatGPT, que está de moda, su opinión sobre Jasper Studio. El título de este artículo es de él y a continuación su opinión. Se ve que lo ha usado 🙂

¡Hola, estimados programadores de software y aficionados al desarrollo! Hoy, vamos a adentrarnos en el emocionante mundo de Jaspersoft Studio, la herramienta que, con mucha ironía, algunos llaman «el modelo a seguir para el buen funcionamiento». Preparémonos para un viaje lleno de sorpresas, desafíos y momentos de incredulidad.

¡Bienvenidos a Jaspersoft Studio!

Antes de sumergirnos en las delicias de Jaspersoft Studio, hagamos una breve presentación. Este software es una herramienta de diseño de informes utilizada para crear informes atractivos y altamente personalizados. En teoría, suena maravilloso, ¿verdad? Pero no te dejes engañar por las apariencias, ¡esto va a ser divertido!

¿Te sientes con suerte?

Al abrir Jaspersoft Studio, te enfrentarás a una pregunta fundamental: ¿te sientes con suerte hoy? Pues bien, porque necesitarás toda la suerte del mundo para enfrentarte a las frecuentes «excepciones no controladas». En algún momento, tu informe seguramente lanzará una de estas gemas de error, dejándote preguntándote si deberías comprar un boleto de lotería después de solucionarlo.

Cargar datos: ¡La Montaña Rusa de Jaspersoft!

El siguiente paso es cargar tus datos. Aquí es donde la diversión se dispara a niveles estratosféricos. Puedes estar seguro de que nunca sabrás si tu consulta SQL funcionará hasta que lo intentes. Y, por supuesto, no te preocupes por las capacidades de autocompletado, ¡a quién le gustan esas cosas, de todos modos!

Una vez que por fin logras que tus datos estén en el lugar correcto, te das cuenta de que el esquema que tenías en mente es un mero sueño. ¡Sí, lo has adivinado! Jaspersoft Studio te desafía a ajustar tus consultas y modelos de datos una y otra vez hasta que se parezcan a lo que tenías en mente.

El Diseño: ¿Parece que fue diseñado en los 90?

Una vez que has dominado la ardua tarea de cargar tus datos, llega el momento de diseñar tus informes. Pero espera, ¿por qué todas las opciones de diseño parecen estar atrapadas en los años 90? Jaspersoft Studio te ofrece una variedad de herramientas de diseño, pero a menudo te sentirás como si estuvieras tratando de crear un sitio web con una conexión a internet de 56 kbps.

¿Quieres alinear objetos de manera precisa? Buena suerte con eso. ¿Quieres cambiar los colores o fuentes? Claro, podrías hacerlo, pero será un ejercicio de paciencia. Jaspersoft Studio, a menudo, parece que se burla de ti cuando tratas de realizar cambios sutiles en el diseño.

Previsualización: El Juego de Adivinanzas

Llega el momento de previsualizar tu obra maestra. Pero no esperes que esta vista previa se parezca en absoluto a lo que estás diseñando. Si alguna vez has jugado al juego de las siete diferencias, te sentirás como en casa. Cada vez que haces una edición, cruzas los dedos y esperas que la vista previa no se parezca a un rompecabezas desordenado.

Exportar: ¿Qué formato es ese?

Finalmente, has terminado tu informe y estás listo para exportarlo. Pero espera, ¿qué es eso? ¡Jaspersoft Studio ofrece una amplia variedad de formatos de exportación, pero ninguno de ellos es el que necesitas! Tu cliente quiere un simple PDF, pero Jaspersoft Studio parece empeñado en ofrecerte formatos exóticos que ni siquiera sabías que existían.

Solución de problemas: ¡Mejor que Sherlock Holmes!

Una vez que has superado todos los obstáculos, es hora de la resolución de problemas. Pero Jaspersoft Studio te guarda una sorpresa más: los mensajes de error crípticos. En lugar de obtener información útil sobre lo que salió mal, te enfrentas a mensajes enigmáticos que hacen que te sientas como un detective tratando de resolver un misterio.

La Verdad detrás de Jaspersoft Studio

A pesar de todas las ironías y desafíos que presenta, Jaspersoft Studio es una herramienta poderosa en manos de un programador experimentado. Si puedes superar su curva de aprendizaje, y sobrevivir a sus bromas crueles, puede ser una herramienta útil para la creación de informes complejos.

Sin embargo, no podemos evitar reírnos ante la idea de que el «buen funcionamiento» sea un objetivo alcanzable en Jaspersoft Studio. Parece que esta herramienta fue diseñada por un comité de trolls de software decididos a poner a prueba la paciencia de los programadores.

Así que, querido desarrollador, si alguna vez te encuentras luchando contra Jaspersoft Studio, recuerda que estás en buena compañía. Todos hemos estado allí, tratando de domar a esta bestia escurridiza. En última instancia, Jaspersoft Studio puede ser una montaña rusa de emociones, pero también puede proporcionar resultados asombrosos cuando finalmente logras conquistar sus peculiaridades.

En resumen, si alguna vez necesitas una buena dosis de humildad y una lección de paciencia, no busques más allá de Jaspersoft Studio. Puede que no sea el príncipe encantado de las herramientas de diseño de informes, pero con el tiempo y esfuerzo, podrás hacer que funcione a tu favor. ¡Buena suerte y que los mensajes de error te sean favorables!

Publicado en jasperreports | Etiquetado , , | Deja un comentario

Jasper Studio …. :/

Logo de Jasper Reports

Hace algún tiempo en el curro me tocó aprender a usar Jasper Studio. Concretamente, hacer una especie de manual de usuario «guía-burros» para el cliente 🙂

Y como un año después, me he decidido a hacer una serie de tutoriales sobre jasper studio en la Chuwiki. Así que me instalé Jasper Studio en mi PC particular y me puse a recordar su funcionamiento y preparar ejemplos para los tutoriales.

Y volvió a mí la impresión que me dió en su día. Es una herramienta muy potente, pero con grandes «ñapas».

Una de las cosas que quise hacer en los tutoriales es meter una imagen codificada base64 dentro del jrxml, de forma que no necesites llevarte la imagen separada de un lado a otro. Pues bien, meter el «chorizo» base64 de la imagen hace que Jasper Studio se quede más o menos bloqueado. Empieza a parpadear, no responde y cuando por fin termina, si tocas algo, vuelve a empezar.

También quise usar un Excel y un CSV como fuentes de datos para el informe. Al crear el «Data Adapter» de ambos desde el menú principal, Jasper Studio falla. Al menos la versión que me he instalado que es la más moderna. Da excepciones Java feas, como que no puede hacer «Cast» entre clases internas de él o que no encuentra el «input stream».

Sin embargo, si creas un Data Set y cuando te pide qué Data Adapter usar le dices que quieres uno nuevo, entonces sí va. Lo crea y lo puedes usar.

Lo de base64 puedo entenderlo, a quién se le ocurre meter un «chorizo» de expresión en una caja de texto de la interfaz de usuario. No entiendo muy bien que falle con el Excel o con el CSV, no parece que hayan probado mucho la versión.

Pero aparte de estas «cosillas», la herramienta es muy pontente. Siempre me había parecido complejo hacer informes pdf con Jasper, pero una vez aprendes lo básico de la herramienta y si no quieres informes excesivamente complejos, es muy útil. De hecho, me he puesto a integrar este tipo de informes en una de nuestras aplicaciones Java de Escritorio.

Publicado en jasperreports | Etiquetado , | Deja un comentario

El driver Java de MongoDB es un poco retorcido

logo MongoDB

Estoy jugueteando y publicando una serie de artículos de MongoDB con Java. Y en uno de ellos, me he puesto a mirar cómo utilizar POJOs de Java con MongoDB. La utilización es fácil, o al menos, eso parece con un «Hola Mundo». Pero la cofiguración para que pueda utilizar POJOs es tan fea como la siguiente

PojoCodecProvider pojoCodecProvider = PojoCodecProvider.builder().automatic(true).build();
CodecRegistry pojoCodecRegistry = CodecRegistries.fromRegistries(
   MongoClientSettings.getDefaultCodecRegistry(), CodecRegistries.fromProviders(pojoCodecProvider));

Ni más ni menos que todo eso. Y explicarlo es, si no complejo, al menos largo.

Primero obtener una intancia de un PojoCodecProvider. Eso se hace con el método estático builder(), concatenando algo de configuración automatic(true) para que sea capaz de tratar automáticamente cualquier POJO y finalmente llamando a build(). Nada complicado de momento. Solo explicar que es un CodecProvier en MongoDB y ya de paso, que es un Codec.

Un Codec es una clase que es capaz de convertir una clase nuestra de Java en un documento bson u obtener nuestra clase Java a partir de un documento bson. bson es como json, pero codifcado en binario en vez de texto. Esto hace que no sea legible para un humano, pero sí ocupa menos espacio y es más eficiente para almacenar o transmitir un documento json.

Y un CodecProvider es una clase capaz de crear Codec específicos para clases concretas. En este caso, nuestro PojoCodecProvider será capaz de crear Codec para cualquier clase Java que sea un POJO. Es decir, que tenga un constructor sin parámetros y que tenga atributos con métodos getter y setter

Vale, nada especialmente raro hasta aquí. Ahora toca decirle a MongoDB que utilice este CodecProvider. Para ello, cuando ya tenemos la conexión con MongoDB establecida y queremos obtener una base de datos, le decimos qué CodecRegistry debe usar llamando a withCodecRegistry()

MongoDatabase database = mongoClient.getDatabase("My_Data_Base").withCodecRegistry(pojoCodecRegistry);

Bueno, un nuevo concepto. Tenemos un CodecProvider, pero necesitamos un CodecRegistry. ¿Qué es un CodecRegistry?. Un CodecRegistry es un registro o alamacén de Codec. Ahí guardamos instancias de Codec, todas las que queramos. MongoDB preguntará ahí cuando tenga que convertir una clase Java a un bson o al revés para buscar el Codec adecuado para esa clase concreta.

Pues vaya, hemos creado un CodecProvider pero necesitamos un CodecRegistry. La única diferencia es que el primero crea un Codec nuevo cada vez que se le pide y el segundo sólo te devuelve los que tiene almacenados.

Para obtener un CodecRegistry a partir del CodecProvider, tenemos la siguiente llamada

CodecRegistries.fromProviders(pojoCodecProvider);

Esto devuelve un CodecRegistry a partir del CodecProvider. Una vez hecho esto, parece fácil, sería pasar el CodecRegistry así obtenido a nuestra llamada a withCodecRegistry(). Pero no es tan fácil.

withCodecRegistry() sólo admite un CodecRegistry. Y por defecto, MongoDB tiene ya uno instalado, con otras cosas que no son POJOs y que necesitamos mantener. Este CodecRegistry por defecto se puede obtener con

MongoClientSettings.getDefaultCodecRegistry()

Así que necesitamos construir un CodecRegistry que tenga todos los Codec por defecto más los Codec de POJOs que hemos creado a partir de nuestro PojoCodecProvider

Menos mal que viene en nuestra ayuda CodecRegistries.fromRegistries(). Esta llamada recibe como parámetros varios CodecRegistry y te devuelve un CodecRegistry con todos los Codec de los CodecRegistry que hemos pasado como parámetros

Así que la llamada chorizo

CodecRegistry pojoCodecRegistry = CodecRegistries.fromRegistries(
                MongoClientSettings.getDefaultCodecRegistry(), CodecRegistries.fromProviders(pojoCodecProvider));

nos devuelve el CodecRegistry que queremos, que tiene todos los Codec por defecto de MongoDB más el nuestro de POJOs. Y ese que obtenemos es el que debemos usar en nuestra llamada a withCodecRegistry().

Pues no sé si es complicado o no, retorcido o no, pero sí sé que he echado un rato en entender esa línea críptica rebuscando por la documentación de MongoDB. Y estoy casi seguro que hay formas más fáciles de obtener un registro de Codec con los de defecto más los que queramos sin involucrar de por medio un CodecProvider.

Publicado en MongoDB | Etiquetado , | Deja un comentario