Terzo di tre articoli su JasperReports, una libreria Java Open Source che consente la generazione dinamica di report a partire da una fonte dati e la successiva renderizzazione in svariati formati, tra i quali PDF ed HTML. In questa parte conclusiva, vediamo un esempio di creazione di un JRDataSource custom e di utilizzo di iReport per la creazione visuale dei layout JRML.
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.
Creazione di un JRDataSource custom
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. 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;}
city, id, name, e street sono i nomi indicati per i campi nel layout JRML.
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 PDFJasperExportManager.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;}
Il report generato è mostrato in figura 1.
L‘esempio completo è allegato all‘articolo.
iReport per la creazione dei layout
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).
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 (figura 3), oppure salvata su file in uno dei formati previsti
da JasperReports. 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.
Bibliografia
[1] Teodor Danciu - "Documentazione ufficiale di JasperReports"
http://jasperreports.sourceforge.net/
[2] Giulio Toffoli - "Documentazione ufficiale di iReports", JasperSoft,
http://ireport.sourceforge.net/