Introduzione

In questo ultima parte vedremo come è possibile creare una classe JRDataSource ad hoc per le nostre applicazioni e una breve descrizione di come utilizzare iReport, un'applicazione Open Source simile a Crystal Report, per la creazione visuale dei layout JRML.

Nel precedente articolo abbiamo visto un esempio pratico di utilizzo delle API JasperReports all'interno di un'applicazione Java. Nel layout JRML dell'esempio era presente il TAG, nel quale avevamo definito una query SQL con cui recuperare i dati da visualizzare nel report. Al momento della creazione del report quindi bisognava aprire una connessione verso il database applicativo e passarla come parametro al metodo JasperFillManager.fillReportToFile(). Tale metodo esegue la query sul database al momento della creazione del report e tiene occupata la connessione finché il report non è stato creato. Questa modo di procedere potrebbe però causare perdite di performance nei casi in cui i dati siano tanti e il layout parecchio complesso. Ma abbiamo un'alternativa: JasperReports prevede delle classi che fungono da contenitori dei dati e che evitano di dovere eseguire la query esclusivamente al momento della creazione e di tenere occupata una connessione verso il database per l'intero processo di creazione.

Le classi in questione, appartenenti tutte al package net.sf.jasperreports.engine.data, sono le seguenti:

  • JRBeanArrayDataSource
  • JRBeanCollectionDataSource
  • JREmptyDataSource
  • JRMapArrayDataSource
  • JRMapCollectionDataSource
  • JRResultSetDataSource
  • JRTabelModelDataSource
  • JRXMLDataSource

Tutte implementano l'interfaccia net.sf.jasperreports.engine.JRDataSource. Sono dei contenitori di dati specializzati, come si evince dai nomi. Ad un report, al momento della creazione, si passa come parametro la classe JRDataSource utilizzata e già popolata con i dati necessari, recuperati dalla fonte già in precedenza.

Le applicazioni in cui verrà integrato JasperReports sicuramente prevedono delle loro classi contenitori dei dati. Piuttosto che implementare una logica che travasi i dati, prima della generazione di ogni report, dalla classe contenitore dell'applicazione ad uno dei JRDataSource già previsti in JasperReports, è più agevole implementare un JRDataSource che fa uso direttamente della nostra classe contenitore.

Implementare un JRDataSource Custom

Nell'interfaccia JRDataSource sono dichiarati solo due metodi:

public Object getFieldValue( JRField jrField ) throws JRException;

il quale restituisce il valore del campo del report passato in ingresso al metodo e

public boolean next() throws JRException;

il quale non fa altro che incrementare il puntatore all'interno della struttura dati. La nostra classe di esempio, MyDataSource, dovrà quindi implementare l'interfaccia JRDataSource:

public class MyDataSource implements JRDataSource

e dovremo implementare i due metodi in essa dichiarati. Il JRDataSource che andremo a realizzare ingloberà anche il nostro contenitore dei dati (nell'esempio, per semplicità, sarà un array bidimensionale, ma è possibile utilizzare qualsiasi tipo di classe):

private Object[][] data = {
  {
    "Bologna", new Integer(22), "Luca Rizzoli", "Via Ugo Bassi, 2"
  },
  {
    "Bologna", new Integer(9), "Gabriele Marconi", "Via Rizzoli, 87"
  },
  {
    "Roma", new Integer(32), "Mario Rossi", "Via Flaminia, 6"
  },
  {
    "Roma", new Integer(23), "Cesare Ottaviano", "Via Ostiense, 12"
  }
};

L'implementazione del metodo next dovrà prevedere l'incremento del puntatore all'interno della struttura dati:

private int lIntIndex = -1;

public boolean next() throws JRException {
  lIntIndex++;
  return ( lIntIndex < data.length );
}

Nel metodo getFieldValue dovremo stabilire l'implementazione delle regole di recupero dei valori dei campi del record. Il corpo di questo metodo varia a seconda della struttura dati utilizzata. Nel nostro esempio:

public Object getFieldValue( JRField field ) throws JRException {
  Object lValue = null;
  String lStrFieldName = field.getName();
  if ( "city".equals( lStrFieldName ) ) {
    lValue = data[lIntIndex][0];
  }
  else if ( "id".equals( lStrFieldName ) ) {
    lValue = data[lIntIndex][1];
  }
  else if ( "name".equals( lStrFieldName ) ) {
    lValue = data[lIntIndex][2];
  }
  else if ( "street".equals( lStrFieldName ) ) {
    lValue = data[lIntIndex][3];
  }
  return lValue;
}

I nomi city, id, name e street sono quelli indicati per i campi nel layout JRML.

Utilizzo del JRDataSource Custom

Vediamo come sarà il codice della classe che, a partire dal layout JRML, compilerà e genererà un report a partire da un'istanza di MyDataSource. Nel metodo createReport si avrà una sequenza di istruzioni simile a quella dell'omonimo metodo della classe di esempio dell'articolo precedente. In questo caso però passeremo, fra i parametri del report, quello (DataFile) con l'indicazione della classe che abbiamo intenzione di usare come JRDatasource:

Map lParameters = new HashMap();
lParameters.put( "ReportTitle", "My Report" );
lParameters.put( "DataFile", "MyDataSource.java" );

Quindi bisogna richiamare, per la generazione del report, il metodo JasperFillManager.fillReportToFile che prevede come terzo parametro, rispetto alla firma utilizzata nell'esempio del mese scorso, un'istanza di JRDataSource invece che una java.sql.Connection:

// creazione del report (file .jrprint)
JasperFillManager.fillReportToFile( pStrArgs[1], lParameters, new MyDataSource() );

Il metodo createReport completo è quindi il seguente:

private int createReport( String[] pStrArgs ) {
  int lIntRet = 0;
  long lLngStart = System.currentTimeMillis();
  // compilazione del report (creazione del .jasper a partire dal .jrxml)
  try {
    JasperCompileManager.compileReportToFile( pStrArgs[0] );
  }
  catch( Exception jre ) {
    System.out.println( "createReport(): eccezione: " + jre.getMessage() );
    return -1;
  }
  System.out.println( "createReport(): Compilazione (ms): " +
    ( System.currentTimeMillis() - lLngStart ) );
  Map lParameters = new HashMap();
  lParameters.put( "ReportTitle", "My Report" );
  lParameters.put( "DataFile", "MyDataSource.java" );
  try {
    // creazione del report (file .jrprint)
    JasperFillManager.fillReportToFile( pStrArgs[1], lParameters, new MyDataSource() );
    // export del report in PDF
    JasperExportManager.exportReportToPdfFile( pStrArgs[2], pStrArgs[3] );
    System.out.println( "createReport(): Creazione del PDF (ms): " +
      ( System.currentTimeMillis() - lLngStart ) );
  }
  catch( Exception ex ) {
    System.out.println( "createReport(): eccezione: " + ex.getMessage() );
    return -2;
  }
  System.out.println( "createReport(): Durata totale (ms): " +
    ( System.currentTimeMillis() - lLngStart ) );
  return lIntRet;
}
Figura 1 - Il report generato con il JRDataSource custom
Figura 1 - Il report generato con il JRDataSource custom

L'esempio completo è allegato all'articolo.

iReport: creazione visuale dei layout JRML

La parte che può risultare più "noiosa" in fase di design è la definizione dei layout per i report generati dalle applicazioni da noi realizzate. Piuttosto che scrivere a mano i file .jrml è sicuramente più comodo utilizzare iReport, un software Open Source di reportistica (il cui project leader è un italiano, Giulio Toffoli [2]) realizzato interamente in Java e basato su JasperReports.

iReport non necessita di installazione: basta scompattare in una directory qualsiasi il contenuto dell'archivio ZIP scaricabile dal sito ufficiale (http://ireport.sourceforge.net). Per avviare iReport basta eseguire iReport.bat (su sistemi operativi Microsoft) o iReport.sh (su distribuzioni Linux). Una volta avviata l'applicazione, dal menu Datasource → Connections/DataSources, bisogna creare una connessione al database applicativo.

Figura 2 - Configurazione della connessione al database in iReport
Figura 2 - Configurazione della connessione al database in iReport

Quindi va creato un nuovo documento. Gli elementi del report vanno inseriti in maniera visuale. E' possibile in ogni momento creare il report prima compilando, tramite la voce di menu Build → Compile, e quindi eseguirlo per avere un'anteprima dell'aspetto finale con i dati, tramite la voce di menu Build → Execute report (using active conn.). L'anteprima può essere visualizzata in iReport stesso tramite il suo visualizzatore interno, oppure salvata su file in uno dei formati previsti da JasperReports.

Figura 3 - Il visualizzatore interno di iReport
Figura 3 - Il visualizzatore interno di iReport

Quando si è soddisfatti del layout, lo si salva con estensione .jrml. Questo file potrà poi essere utilizzato dalla nostra applicazione Java come spiegato nell'articolo precedente.

Conclusioni

Abbiamo così terminato la panoramica su JasperReports. La potenzialità di queste API è enorme. Accoppiando all'adozione di JasperReports anche l'utilizzo di iReport per la definizione dei layout, i risultati che si ottengono sono molto pregevoli. JasperReports e iReport sono l'ennesima risposta a tutti coloro che purtroppo pensano ancora che tutto ciò che è Open Source non possa dare risultati validi in ambito professionale.

Riferimenti

  • [1] Teodor Danciu - "Documentazione ufficiale di JasperReports"
  • [http://jasperreports.sourceforge.net/](http://jasperreports.sourceforge.net/)
  • [2] Giulio Toffoli - "Documentazione ufficiale di iReports", JasperSoft,
  • [http://ireport.sourceforge.net/](http://ireport.sourceforge.net/)