Archivo

Entradas Etiquetadas ‘java’

Implementando Observers en Java

Jueves, 10 de febrero de 2011 Sin comentarios

Una aclaración antes de empezar: en Java existe un mecanismo para implementar Observers que requiere que la clase observada extienda Observable. Esto, en algunas situaciones puede ser una limitante ya que no es posible extender mas de una clase. Si por ejemplo, queremos observar una clase que ya extiende a otra, no seria posible incorporar este mecanismo con las herramientas built in de Java sin hacer cambios importantes. En esta situación, la mecanica que se explica a continuación puede ser de utilidad.

En muy pocas palabras, los patrones de diseño son soluciones conocidas para problemas conocidos, un esfuerzo por no reinventar la rueda cada vez que nos enfrentamos a un problema cotidiano en el mundo de la programación. Este patrón se utiliza cuando queremos que alguna entidad, se entere de que algo pasó en alguna otra parte de nuestro sistema. Por ejemplo, supongamos que tenemos una ventana con un cierto listado de datos, como la siguiente:

Carga de contenedores - Observa la actualización

Los datos que la tabla muestra pueden cambiar en función de acciones que se realizan en algún otro formulario de la aplicación que permita el ingreso o edición y el objetivo es que ésta tabla se actualice cuando esos datos cambian sin necesidad de que el usuario que hace uso de ella tenga que explícitamente recargar o refrescar los datos.

Este es un caso típico en que el patrón Observer nos será de gran utilidad y será la tabla de datos quien deberá observar los eventos que ocurran en el formulario de edición.

Además de los formularios para editar y mostrar los datos, la solución constará de dos clases adicionales, una interfase IObserver y una clase que hace de nexo entre las distintas partes que componen la solución y que llamaremos ObserverServer. La interfase IObserver deberá ser implementada por aquellas entidades que están observando a otras y contendrá los métodos necesarios para comunicar los cambios:

public interface IObserver {

public void notifyEvent( String eventName, HashMap parameters );

}

La clase interesada en la actualización de datos deberá implementar el método notifyEvent y deberá suscribirse ante el ObserverServer como observador del evento de actualización de datos. Veamos el ObserverServer para aclarar esta situación:

public class ObserverServer {

 private ObserverServer instance = null;
 private HashMap events;

 private ObserverServer getInstance() {
 if( instance == null ) instance = new ObserverServer();
 return instance;
 }

 private ObserverServer() {
 events = new HashMap();
 }

 public void registerTo( String eventName, IObserver caller ) {
 LinkedList callers = (LinkedList) events.get(eventName);
 if( callers == null ) {
 callers = new LinkedList();
 callers.add(caller);
 events.put(eventName, caller);
 } else {
 callers.add(caller);
 }
 }

 public void fireEvent( String eventName, HashMap parameters ) {
 LinkedList callers = (LinkedList)events.get(eventName);
 if( ! (callers==null) ) {
 Iterator it = callers.iterator();
 IObserver caller;
 while( it.hasNext() ) {
 caller = (IObserver)it.next();
 caller.notifyEvent(eventName, parameters);
 }
 }
 }

Dos precisiones sobre esta clase:

1) La calse es un singleton. Esto es necesario ya que todas las entidades que se comunican a través de ella deben acceder a la misma lista de eventos

2) La lista de eventos se modela como un HashMap (events) que contiene para cada evento una LinkedList con las entidades que esperan ser notificadas cuando el evento ocurra. Esto podría alterarse en función de necesidades específicas.

La clase ObserverServer cuenta con dos métodos, uno para suscribirse como observador de un evento, registerTo y otro para iniciar las notificaciones ante la ocurrencia de un evento, fireEvent.

El método registerTo recibe como parámetros el nombre del evento y una referencia a la clase que debe ser notificada y que implementa la interfase IObserver. El método simplemente agrega la referencia al observador en la lista correspondiente y en caso de que se la primera vez que una entidad se suscribe a dicho evento, creará una nueva lista. Nótese que no hay ningún tipo de control sobre los eventos a los que una entidad se registra. Esto podría cambiar según las necesidades especificas de lo que se está implementando, pero para mostrar el funcionamiento básico del patrón no es necesario agregar tal complejidad al ejemplo.

El método fireEvent recibe como parámetros el evento que ocurrió y una HashMap de parámetros relativos a dicho evento. En este caso, se recorrerá la lista de entidades suscritas al evento y para cada una de ellas se invocará el método notifyEvent. Nuevamente, aqui no hay ningún control que verifique que el evento sea válido. Quien dispara el evento y quien está suscrito a él deberán acordar los parámetros que se envían en el HashMap. El ObserverServer solo actúa como vehículo para la comunicación y no impone ningún tipo de restricción a los parámetros.

Veamos escuetamente como se usa todo esto para algo útil

Nuestro formulario de despliegue de datos quiere ser notificado cuando los datos cambian para mostrar la nueva realidad, por lo que va a suscribirse a un evento de notificación en su constructor


public class ContainerList implements IObserver {

private ObserverServer obs;

public ContainerList() {

obs = ObserverServer.getInstance();

obs.registerTo("ContainerUpdate",this);

}

...

public void notifyEvent( String eventName, HashMap parameters ) {

if( eventName.equals("ContainerUpdate") ) {

...

}

}

}

El formulario que muestra los datos se suscribe al evento ContainerUpdate y pasa una referencia a sí mismo. Cuando el evento ocurra, el ObserverServer invocará el evento notifyEvent el cual implementa la actualización de datos necesaria.

La otra parte de esto, el formulario de edición de datos deberá avisar cada vez que una modificación ocurrra


public class FrmContainerUpdate() {

public ObserverServer obs = ObserverServer.getInstance();

...

private SaveData() {

// Proceso los datos del formulario como normalmente lo haría

// y notifico del evento

HashMap params = new HashMap();

params.put("containerId",id); //Agrego todos los parametros necesarios al HashMap

obs.fireEvent("ContainerUpdate", params);

}

De esta manera, cada vez que ocurre una actualización de los datos de un contenedor, la tabla de datos es actualizada.

Este patrón es útil en una gran cantidad de situaciones que las que se quiere comunicar a varias entidades, sin embargo, no debe ser confundido con un mecanismo para la comunicación entre procesos en el que existe un flujo de información más o menos continuo, para eso existen otras herramientas.

Integración de JasperReports en aplicaciones Java

Domingo, 10 de octubre de 2010 Sin comentarios

Lo más complicado de integrar JasperReports en una aplicación Java es encontrar buena documentación de como hacerlo. JasperReports es muy potente y a la vez complejo que las alternativas que brinda son muchísimas. Fui encontrando distintos ejemplos y pequeños tutoriales que realmente no me sirvieron. Esta entrada está basada en el libro JasperReports 3.5 for Java Developers de David R. Heffelfinger. Para aquellos que no quieran comprar el libro, acá va un muy somero tutorial de como hacer funcionar esto. Básicamente es el tutorial que no pude encontrar en mi lucha y que hago como ayuda-memoria para mí y quizás sirva a alguien. Realmente recomiendo comprar el libro ya que es muchísimo más amplio que esta pequeña guía y muestra el uso de muchas más opciones y alternativas que simplemente voy a omitir. Se supone que el código que aparece en esta entrada debería ser correcto y funcionar, pero si encuentran algún error háganme saber y será corregido.

Todo el software necesario puede conseguirse en http://www.jaspersoft.com/

Otros links de interés:

iReport http://sourceforge.net/projects/ireport/files/

jFreeChart http://sourceforge.net/projects/jfreechart/

Breve introducción

Hay un par de conceptos que deben tenerse presentes a la hora de utilizar JasperReports para que no se convierta todo en un gran dolor de cabeza. Los pasos que daremos para alcanzar nuestro objetivo son los siguientes:

  1. crear una plantilla para el reporte (jrxml file)
  2. compilar la plantilla
  3. generar el reporte (a un archivo jrprint o a un pdf o por pantalla)

Creando la plantilla para el reporte

Esta parte es una de las más simples. Para la generación de la plantilla utilizamos iReport (yo estoy usando la versión 3.7.5). iReport tiene un muy buen wizard para la creación de plantillas y muchas opciones para cambiar el layout. Las plantillas pueden incluir elementos estáticos y dinámicos y tener embebidas las consultas sql necesarias para obtener los datos de una base de datos. También existe la posibilidad de pasar los datos programáticamente al reporte. Los ejemplos que están a continuación asumen que la consulta está embebida en el reporte y solo se hará referencia al objeto de datos que puede pasarse al reporte.

Las plantillas de reportes son archivos de extensión jrxml. El formato del archivo es en realidad un xml estándar con otra extensión.

Compilando el reporte

JasperReports no utiliza las plantillas de reportes tal como son guardadas por iReport sino que las compila. Durante este proceso el xml es parseado y la información se guarda como objetos serializados en archivos de extensión jasper. No me puse a escarbar aún en estos archivos así que no puedo decir mucho de ellos.

En la medida que las plantillas no cambien no tienen porque ser compiladas cada vez que necesitamos el reporte, sino que usaremos como entrada los archivos .jasper ya compilados.

El siguiente fragmento de código compila una plantilla (.jrxml) para convertirla en un reporte .jasper


import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JasperCompileManager;

public class compileReport {

public static void main(String[] args) {
    try {
        System.out.println("Compiling report...");
        JasperCompileManager.compileReportToFile("firstTestReport.jrxml");
       System.out.println("Done!");
    } catch (JRException e) {
        e.printStackTrace();
    }
}

La clase JasperCompileManager provee varios métodos para compilar las plantillas ya sea utilizando archivos o streams como salida. En este caso estamos mandando la salida a un archivo el cual tendrá el mismo nombre que la plantilla con extensión .jasper.

Generando el reporte a un archivo pdf
Una vez que tenemos nuestra plantilla compilada podemos llenar el reporte y utilizar distintas formas de salida para el mismo, ya sea un archivo pdf, html, excel, un visor en pantalla y una larga lista de etcéteras. El siguiente fragmento de código genera un archivo pdf

import java.io.File;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JRExporterParameter;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.export.JRPdfExporter;
import net.sf.jasperreports.engine.util.JRLoader;

public class exportToPdf {
    public static void main(String[] args) {
        File file = new File("firstTestReport.jrprint");
        try {
            JasperPrint jasperPrint = (JasperPrint) JRLoader.loadObject(file);
            JRPdfExporter pdfExporter = new JRPdfExporter();
            pdfExporter.setParameter(JRExporterParameter.JASPER_PRINT,  jasperPrint);
            pdfExporter.setParameter(JRExporterParameter.OUTPUT_FILE_NAME,  "firstTestReport.pdf");
            System.out.println("Exporting report...");
            pdfExporter.exportReport();
            System.out.println("Done!");
        } catch (JRException e){ }
    }
}

Generando el reporte a pantalla

Ya mencionamos que los reportes podían generarse y enviarse a distintas salida, la pantalla es un muy buena alternativa para la pre visualización de reportes, así que acá va el fragmente de código que genera el reporte y lo envía a pantalla

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Properties;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.view.JasperViewer;

public class reportView {

    public static void main(String[] args) {
        try {
            JasperPrint print;
            String url = "jdbc:postgresql://miserver/midatabase/";
            Properties props = new Properties();
            props.setProperty("user","miuser");
            props.setProperty("password","mipassword");
            props.setProperty("port", "5432");
            Connection conn = DriverManager.getConnection(url, props);
            print = JasperFillManager.fillReport("firstTestReport.jasper", new HashMap(), conn);
            JasperViewer view = new JasperViewer(print);
            view.setVisible(true);
        } catch (JRException ex) {}
          catch ( SQLException e ){}
     }
}

En este caso estamos pasando una conexión al método fillReport, la cual será utilizada para conectarse a la base de datos y ejecutar las sentencias sql embebidas en la plantilla. El método también soporta, en lugar de una conexión, una fuente de datos que contenga los valores necesarios para el reporte. JasperReports provee de un data source vacio (JREmptyDataSource) que puede ser utilizado para probar los reportes sin datos.

PostgreSQL, Java y fechas

Sábado, 17 de julio de 2010 3 comentarios

Trabajando para un proyecto tuve que cambiar de mi querida MySQL a PosgreSQL y me encontré como era de esperar con unos cuantos problemas. Entre ellos, el que más dolores de cabeza me dio y sobre el que más variantes tuve que probar hasta encontrar con la más adecuada para las necesidades del momento fue el del almacenamiento y recuperación de fechas.

Evalué varias alternativas y vi mucha cosa al respecto y me quedé con la solución que a continuación presento, y que describo para salvar mis problemas de memoria y quizás para ayudar un poco a algún eventual lector que ande tras una solución para ese mismo problema.

La tabla

Las fechas son guardadas en campos de tipo timestamp without time zone. En el siguiente ejemplo solo se muestran dos columnas de la tabla y omití todo lo que no viene al caso.

CREATE TABLE trabajo ( nombre character varying(50) fecha timestamp without time zone );

La aplicación

La aplicación almacena las fechas en variables de tipo GregorianCalendar que es un objeto de Java muy flexible para el manejo de fechas.

class Trabajo {

private String nombre;
private GregorianCalendar fecha;

}

Además de los getters y setters habituales, escribí un par de métodos que permiten setear las fechas a partir de un String y recuperarlas como un String, lo cual es muy práctico al momento de mostrarlas o de setear fechas a partir del valor obtenido de un date picker.

public String getFechaAsString() {
SimpleDateFormat sdf = new SimpleDateFormat(“dd-MM-yyyy HH:mm”);
return( sdf.format( (this.fecha).getTime() ) );
}

El formato de fecha elegido está hardcoded pero en este caso no es una limitación. La clase SimpleDateFormat es muy flexible a la hora de parsear fechas con lo que el código anterior puede ser fácilmente modificable para soportar cualquier formato. En caso de quererse formatos más flexibles que se correspondan con las preferencias del usuario en el sistema operativo, tendrás que echar mano a la clase TimeZone. En el ejemplo no se manejan posibles errores al parsear la fecha (asumo que this.fecha siempre tiene un valor válido, el setter debería encargarse de eso).

public void setFechaAsString( String fecha ) {
if( fecha.length() != 0 ) {
SimpleDateFormat sdf = new SimpleDateFormat(“dd-MM-yyyy HH:mm”);
try {
java.util.Date d = sdf.parse( fecha );
(this.fecha) = new GregorianCalendar();
(this.fecha).setTime( d );
} catch ( ParseException e ) {
e.printStackTrace();
}
}

Las fechas son seteadas a partir de un String, el cual es parseado y en caso de no verificar exactamente el formato necesario, tira una excepción (en el ejemplo imprimo el stack trace, aunque en la realidad la excepción se maneja de manera un poquito más feliz).

La clase SimpleDateformat devuelve un objeto de tipo java.util.Date (no confundir con java.sql.Date) a partir del cual podemos establecer la fecha en nuestro GregorianCalendar.

Guardando fechas

Guardar las fechas es muy simple si echamos mano a nuestra clase getFechaAsString().

sql = “INSERT INTO trabajo (nombre,fecha) values(‘”+ this.nombre+”‘,’”+this.getFechaAsString()+”‘)”;

(Ojo con las comillas) y ese sql lo mandamos a la base de datos.

Recuperando fechas

Recuperar las fechas tiene tambien su truqito, veamos..

sql = “SELECT * FROM trabajo”;

ResultSet rs = st.executeQuery( sql ); donde st es un Statement valido conectado a la base de datos

Iteramos sobre el resultado


java.sql.Date d = rs.getDate(“fecha”);
if( d1 != null ) {
GregorianCalendar gc1 = new GregorianCalendar();
gc1.setTimeInMillis( d1.getTime() );
this.fecha = gc1;

Observar que getDate devuelve un objeto de java.sql.Date, pero igualmente podemos crear un GregorianCalendar a partir de él pasando la fecha a un timestamp.

Conclusiones

No es el más flexible de los métodos para almacenar y mostrar fechas, pero es lo suficientemente flexible si manejamos las fechas en un formato que no dependa de la configuración regional del usuario. Sin embargo, podemos mostrar las fechas en cualquier formato utilizando SimpleDateFormat para formatearlas de la manera que mas nos convenga.

Categories: General, IT Tags: , ,