Introduzione
Nel precedente articolo è stata descritta l'architettura di JasperReports. In questo vedremo un esempio pratico di utilizzo delle API di tale libreria all'interno di un'applicazione Java. Per l'esempio faremo riferimento, come fonte dati, ad un database Access (per semplicità; ovviamente il db potrà essere di un vendor diverso da Microsoft) chiamato jasper e contenente una sola tabella, addresses avente cinque campi: id di tipo numerico, firstname, lastname, street e city di tipo testo.
Layout di un report
Ricordando lo schema di principio spiegato nell'articolo precedente (e riportato per comodità anche in questo in figura 1), la prima cosa da fare è quella di definire il layout dei report che dovrà generare l'applicazione.
Figura 1 – Processo di generazione di un report
Un layout non è altro che un file XML, con estensione .jrml, e costruito secondo lo schema definito in jasperreport.dtd. In testa al layout troviamo la dichiarazione della versione dell’XML e l’indicazione del DTD adottato.
<?xml version="1.0"?> <!DOCTYPE jasperReport PUBLIC "-//JasperReports//DTD Report Design//EN" "http://jasperreports.sourceforge.net/dtds/jasperreport.dtd" [ <!ENTITY reportFonts SYSTEM "./src/jr/test/ReportFonts.ent"> ]>
Il tag root del file sarà <jasperReport>:
<jasperReport name="GeneralTest" pageWidth="595" pageHeight="842" columnWidth="515" columnSpacing="0" leftMargin="40" rightMargin="40" topMargin="50" bottomMargin="50" whenNoDataType="AllSectionsNoDetail" isTitleNewPage="false" isSummaryNewPage="false">
Gli attributi di questo tag sono gli attributi di pagina del report. Un report Jasper è in grado di accettare parametri in ingresso dalla classe Java preposta alla sua generazione. Il tag <parameter> serve a dichiarare tale parametri. L'attributo name indica il nome del parametro, mentre class ne indica il tipo Java. Nel nostro esempio i parametri in ingresso sono tre:
<parameter name="ReportTitle" class="java.lang.String"/> <parameter name="FilterClause" class="java.lang.String"/> <parameter name="OrderClause" class="java.lang.String"/>
Il tag <querystring> stabilisce la query SQL con la quale vengono recuperati dal database i dati che popoleranno il report.
<queryString><![CDATA[SELECT * FROM addresses]]></queryString>
Quindi, tramite il tag <field>, bisogna dichiarare i campi delle tabelle coinvolte e la loro tipologia Java:
<field name="Id" class="java.lang.Integer"/> <field name="FirstName" class="java.lang.String"/> <field name="LastName" class="java.lang.String"/> <field name="Street" class="java.lang.String"/> <field name="City" class="java.lang.String"/>
È possibile definire anche delle variabili tramite il tag <variable>:
<variable name="CityNumber" class="java.lang.Integer" resetType="Group"
resetGroup="CityGroup" calculation="System">
<initialValueExpression>
($V{CityNumber} != null)?(new Integer($V{CityNumber}.intValue() + 1)):(new Integer(1))
</initialValueExpression>
</variable>
A questo punto possiamo definire le logiche di raggruppamento dei dati tramite il tag <group>:
<group name="CityGroup" minHeightToStartNewPage="60">
<groupExpression>$F{City}</groupExpression>
<groupHeader>
<band height="20">
<rectangle>
<reportElement x="0" y="4" width="515" height="15"
forecolor="#C0C0C0" backcolor="#C0C0C0"/>
</rectangle>
<textField>
<reportElement x="0" y="4" width="515" height="15"
backcolor="#C0C0C0" mode="Opaque"/>
<textElement textAlignment="Left">
<font reportFont="Arial_Bold"/>
</textElement>
<textFieldExpression class="java.lang.String">
" " + String.valueOf($V{CityNumber}) + ". " + String.valueOf($F{City})
</textFieldExpression>
</textField>
<line>
<reportElement x="0" y="19" width="515" height="0"/>
</line>
</band>
</groupHeader>
<groupFooter>
<band height="20">
<line>
<reportElement x="0" y="-1" width="515" height="0"/>
</line>
<staticText>
<reportElement x="400" y="0" width="60" height="15"/>
<textElement textAlignment="Right" lineSpacing="Single">
<font reportFont="Arial_Bold"/>
</textElement>
<text>Count : </text>
</staticText>
<textField>
<reportElement x="460" y="0" width="30" height="15"/>
<textElement textAlignment="Right" lineSpacing="Single">
<font reportFont="Arial_Bold"/>
</textElement>
<textFieldExpression class="java.lang.Integer">
$V{CityGroup_COUNT}
</textFieldExpression>
</textField>
</band>
</groupFooter>
</group>
Qualora volessimo uno sfondo personalizzato diverso da quello bianco di default, possiamo utilizzare il tag <background>. Nel nostro esempio aggiungeremo una immagine trasparente come sfondo per tutte le pagine:
<background>
<band height="742">
<image scaleImage="Clip" hAlign="Left" vAlign="Bottom">
<reportElement x="0" y="0" width="150" height="742"/>
<imageExpression>"src/jr/test/jr.watermark.gif"</imageExpression>
</image>
</band>
</background>
Infine dobbiamo definire il layout delle regioni della pagina titolo, header, dettaglio, footer e sommario, rispettivamente tramite i tag <title>, <pageHeader>, <detail>, <pageFooter> e <summary>.
<title>
<band height="50">
<line>
<reportElement x="0" y="0" width="515" height="0" forecolor="black"/>
</line>
<textField isBlankWhenNull="true">
<reportElement x="0" y="10" width="515" height="30"/>
<textElement textAlignment="Center" lineSpacing="Single">
<font reportFont="Arial_Normal" size="22"/>
</textElement>
<textFieldExpression class="java.lang.String">
$P{ReportTitle}
</textFieldExpression>
</textField>
</band>
</title>
<pageHeader>
<band height="20">
<rectangle>
<reportElement x="0" y="5" width="515" height="15"
forecolor="#333333" backcolor="#333333"/>
</rectangle>
<staticText>
<reportElement x="0" y="5" width="55" height="15"
forecolor="white" backcolor="#333333" mode="Opaque"/>
<textElement textAlignment="Center">
<font reportFont="Arial_Bold"/>
</textElement>
<text>ID</text>
</staticText>
<staticText>
<reportElement x="55" y="5" width="205" height="15"
forecolor="white" backcolor="#333333" mode="Opaque"/>
<textElement>
<font reportFont="Arial_Bold"/>
</textElement>
<text>Name</text>
</staticText>
<staticText>
<reportElement x="260" y="5" width="255" height="15"
forecolor="white" backcolor="#333333" mode="Opaque"/>
<textElement>
<font reportFont="Arial_Bold"/>
</textElement>
<text>Street</text>
</staticText>
</band>
</pageHeader>
<detail>
<band height="20">
<textField>
<reportElement x="0" y="4" width="50" height="15"/>
<textElement textAlignment="Right"/>
<textFieldExpression class="java.lang.Integer">
$F{Id}
</textFieldExpression>
</textField>
<textField isStretchWithOverflow="true">
<reportElement x="55" y="4" width="200" height="15" positionType="Float"/>
<textFieldExpression class="java.lang.String">
$F{FirstName} + " " + $F{LastName}
</textFieldExpression>
</textField>
<textField isStretchWithOverflow="true">
<reportElement x="260" y="4" width="255" height="15" positionType="Float"/>
<textFieldExpression class="java.lang.String">
$F{Street}
</textFieldExpression>
</textField>
<line>
<reportElement x="0" y="19" width="515" height="0"
forecolor="#808080" positionType="Float"/>
</line>
</band>
</detail>
<pageFooter>
<band height="40">
<line>
<reportElement x="0" y="10" width="515" height="0"/>
</line>
<textField>
<reportElement x="200" y="20" width="80" height="15"/>
<textElement textAlignment="Right"/>
<textFieldExpression class="java.lang.String">
"Page " + String.valueOf($V{PAGE_NUMBER}) + " of"
</textFieldExpression>
</textField>
<textField evaluationTime="Report">
<reportElement x="280" y="20" width="75" height="15"/>
<textElement textAlignment="Left"/>
<textFieldExpression class="java.lang.String">
" " + String.valueOf($V{PAGE_NUMBER})
</textFieldExpression>
</textField>
</band>
</pageFooter>
<summary>
<band height="60">
<textField isStretchWithOverflow="true">
<reportElement x="175" y="20" width="165" height="15"/>
<textElement textAlignment="Center">
<font reportFont="Arial_Italic"/>
</textElement>
<textFieldExpression class="java.lang.String">
"There were " + String.valueOf($V{REPORT_COUNT}) +
" address records on this report."
</textFieldExpression>
</textField>
</band>
</summary>
Codice Java per la generazione di report
La classe Java del nostro esempio preposta alla generazione del report si chiama GeneralTest. Il metodo che esegue tutte le operazioni è createReport:
private int createReport(String[] pStrArgs)
Come parametro in ingresso riceve un array di stringhe. La prima operazione che viene eseguita è la creazione del semicompilato (file .jasper) secondo il template indicato come primo argomento dell'array pStrArgs:
// compilazione del report (creazione del .jasper a partire dal .jrxml)
try {
JasperCompileManager.compileReportToFile(pStrArgs[0]);
}
catch(Exception jre) {
System.out.println("GeneralTest.createReport(): eccezione: " + jre.getMessage());
return -1;
}
Il metodo compileReportToFile della classe JasperCompilerManager esegue la compilazione. A questo punto, ricordando la figura 2, abbiamo ottenuto il file .jasper da cui l'applicazione, in qualsiasi momento, sarà in grado di generare il report. Nella nostra classe di esempio la generazione del report avviene subito dopo la creazione del semicompilato, ma nulla ovviamente vieta di eseguire l'operazione in un secondo momento. Per la generazione del report, il metodo fillReportToFile della classe JasperFillManager ha bisogno di una connessione verso il database da cui recuperare i dati. Il metodo getConnection della nostra classe di esempio crea la connessione:
private Connection getConnection() throws ClassNotFoundException, SQLException {
String lStrDriver = "sun.jdbc.odbc.JdbcOdbcDriver";
String lStrConnectString = "jdbc:odbc:Jasper";
String lStrUser = "";
String lStrPassword = "";
Class.forName(lStrDriver);
Connection lConn = DriverManager.getConnection(lStrConnectString, lStrUser, lStrPassword);
return lConn;
}
Il codice necessario per la creazione del report (file .jrprint) è il seguente:
// passaggio dei parametri al report
Map lParameters = new HashMap();
lParameters.put("ReportTitle", "General test");
lParameters.put("FilterClause", "");
lParameters.put("OrderClause", "Id");
// creazione della connessione verso il database
Connection lConnection = getConnection();
// creazione del report (file .jrprint)
JasperFillManager.fillReportToFile(pStrArgs[1], lParameters, lConnection);
Il primo argomento del metodo fillReportToFile è il nome del file .jprint da creare. Adesso dobbiamo decidere cosa fare del nostro report. Possiamo visualizzarlo, mandarlo in stampa o salvarlo su file in uno dei formati previsti da JasperReports. Nel nostro esempio viene salvato su un file PDF, grazie al metodo exportReportToPdfFile della classe JasperExportManager:
// export del report in PDF JasperExportManager.exportReportToPdfFile(pStrArgs[2], pStrArgs[3]);
I due argomenti in ingresso al metodo exportReportToPdfFile sono, rispettivamente, il nome del report .jprint e il nome da assegnare al file PDF su cui viene salvato. Il risultato finale sarà un report come quello mostrato in figura 2:
Figura 2 – Report generato tramite il codice di esempio
L’esempio completo è allegato all’articolo.
Conclusioni
In questo articolo abbiamo visto quali sono i passi comuni a tutte le operazioni di generazione di report tramite JasperReports e come viene realizzata la perfetta separazione del layout dai dati. Una volta definito un layout, la logica di generazione dei report diventa una operazione meccanica. Nel prossimo ed ultimo articolo vedremo come è possibile implementare un JRDataSource ad hoc per l’integrazione della libreria con una nostra applicazione e come rendere più agevole la creazione dei layout JRML mediante l’utilizzo di iReport, una applicazione Open Source basata su JasperReports.
Bibliografia
- [1] Teodor Danciu - "Documentazione ufficiale di JasperReports", http://jasperreports.sourceforge.net/
- [2] Wostmann & Associates - "JasperReports and iReports", http://www.wainet.com/Presentations/JasperReports%20&%20iReports.ppt
