JasperReports
Jasperreports ist ein Reportgenerator, dessen Möglichkeiten weit über die der in Adempiere eingebauten Report-Fähigkeiten hinausgehen. Es ist gut in ADempiere integriert.
Links zum Thema
Die folgenden Links verweisen auf Anleitungen, die eine gute Einführung von ADempiere und Jasperreports anbieten.
- ADempiere/Compiere_JasperReports_Integration_HowTo - ursprüngliches Dokument, das die Einbindung beschreibt
- Workshop:Integration_von_JasperReports_in_ADempiere - informativer Workshop von nwessel auf deutsch
- http://adempiere.metas.de/mediawiki/index.php/Neuen_Bericht_anlegen#Einrichten_eines_Jasper_Berichts_in_ADempiere - kurzgehaltene Eindeutschung von Metas
Wie legt man los?
Zuerst sollte man ein aktuelles "iReport" herunterladen. Ohne grosse Anmeldung geht das von https://sourceforge.net/projects/ireport/ aus. Unter http://jasperforge.org/projects/ireport findet sich die eigentliche Projektseite mit einigen Hinweisen. Ansonsten empfehle ich auch das Buch "iReport Ultimate Guide", was man da (nach einer Registrierung) kostenlos herunterladen kann.
Grobe Gliederung der ersten Schritte:
- Ggf. Konfiguration eines Benutzers, der auf das benutzte Datenbank-Schema zugreifen kann ohne dieses immer angeben zu müssen. Dazu u.U. setzen der PostreSQL-Variable search_path fest im Benutzerprofil (per PgAdmin3)
- Anlegen einer Datenquelle in iReport, d.h. Datenbank-Zugangsparameter
- Anlegen eines neuen Reports entweder über Templates oder eines leeren Blattes.
- Anlegen eines Parameters "RECORD_ID" zur Übergabe einer Datensatznummer von iDempiere nach JasperReports (Der Name dieses Parameters ist von iDempiere vorgegeben)
- Man kann iReport so einstellen, das bei jedem Test (in iReport, also ausserhalb von iDempiere) diese Nummer von Hand eingegeben werden muss (etwas lästig) und/oder das ein Default-Wert verwendet wird. Hier kann man einen vorhandenen Datensatz für Tests fest verdrahten. Dazu den Parameter anklicken und in die Einstellungen zu diesem Element schauen.
- Eingeben eines Datenbank-Queries
- Dabei $P{RECORD_ID} als Platzhalter verwenden. (Der Query-Generator hilft übrigens durch Drag-and-Drop dabei, wenn man einen Parameter-Namen vergessen hat.)
- Im Query definiert man Felder, die aus dem Select in eine Art lokale Variablen ("Felder") geladen werden.
- Diese Felder kann man links im Dokumentenbaum aufklappen und dann in den Bericht ziehen. Der einzelne Datensatz kommt in den "Detail"-Bereich, die Überschriften z.B. in den "Page Header".
Subreports
Für den Zugriff auf Subreports, Bilder und sonstige Resourcen sollte man die Beschreibung unter JasperReportsClassic oder JasperReportsFreiBier lesen. Einer der Vorteile des FreiBier-Plugins ist, das es hierbei viel flexibler ist und somit eine Modularisierung des Reports erlaubt.
Übergabe von Parametern
Ein JasperReport wird in iDempiere über das Application-Dictionary in der Tabelle "Bericht & Prozeß" eingerichtet. Einen solchen Prozeß kann man dann in das Menü einhängen oder mit einem Button verknüpfen. Man kann hier auch Parameter angeben. Wird das getan, öffnet sich vor dem Start des eigentlichen Berichts ein kleines Eingabefenster, in dem man Parameter eingeben kann. Dabei sind die üblichen Möglichkeiten von ADempiere, um Datentypen zu definieren, vorhanden. Das bedeutet, man kann also Texte oder Zahlen eingeben aber auch Datensätze aus einer Combobox oder mit Hilfe eines Suchfensters auswählen. Der Name des Parameters kann dort konfiguriert werden. Diesen Namen kann man dann in iReport als Parameter konfigurieren. Im Fall eines ausgewählten Datensatzes wird dann z.B. die ID dieses Satzes als Integer-Wert (bzw. BigDecimal im neuen Plugin) übergeben. Wird ein Report über das Druck-Icon der Toolbar aufgerufen, so wird automatisch der Parameter RECORD_ID mit der ID des momentanen Datensatzes gefüllt.
Vorbereiten von *.jasper-Dateien mit unterschiedlichen JasperReports-Versionen
Das Erzeugen von Subreports in Jasperreports ist nicht ganz trivial, aber vielfach erklärt. Hierzu benutzt man iReport. Leider gibt es bei der Zusammenarbeit mit ADempiere noch einige weitere Hürden. Einen normalen Report kann man als *.jrxml-Datei im Filesystem oder im Classpath ablegen. Diese Datei wird dann von iDempiere automatisch in eine *.jasper-Datei kompiliert. Diese stellt dann den eigentlichen Report dar, der sodann Daten aus der Datenbank zieht und ein Ausgabedokument (zumeist ein PDF) erzeugt. Subreports werden nun allerdings durch einen String deklariert, der den Dateinamen des Subreports enthält. An dieser Stelle erwartet JasperReports (zumindest nach meiner Erfahrung) auf jeden Fall den Namen einer *.jasper-Datei. Also muss man diese vorübersetzen.
An sich ist das nicht schlimm, weil iReport das sowieso für uns macht. Dabei trat bei mir allerdings ein Problem auf: das letzte echte ADempiere (Version 3.6.1) enthält jasperreports-3.7.3.jar, iDempiere 2.0 enthält JasperReports 5.2, während mein aktuell heruntergeladenes iReport bereits die Version 5.5 hat. Dadurch ergab sich nun leider nach der Installation der Datei in ADempiere eine Exception, weil irgendwelche Abhängigkeiten nicht aufgelöst werden konnten.
Um das zu beheben muss man also sicherstellen, das der in iReport erzeugte Report erstens grundsätzlich von den verwendeten Funktionen her kompatibel mit der Version im Programmcode ist (3.7.3 bzw. 5.2) ist und zweitens muss man die *.jasper-Datei mit einer entsprechenden Version übersetzen. Ersteres geht, indem wir in iReport in den Einstellungen (Menü Extras->Optionen) im Register "General" im Unterregister "Compatibility" einstellen, das wir *.jrxml-Dateien der Version 5.2 erstellen wollen.
Zweiteres geht entweder mit einem Ant-Skript (siehe JasperReportsClassic) oder indem man die übersetzte Datei aus dem Cache fischt (siehe JasperReportsFreiBier).
Images in Reporten
Es gibt relativ wenige Erklärungen im Netz zum Thema, wie man ein Bild in einen Report integriert. All diese Anleitungen erklären zuerst einmal, wie man statische Bilder in einen Report einfügt, um z.B. das Logo der eigenen Firma oben an den Rand jeder Seite oder auf ein Deckblatt zu drucken. Das ist aber natürlich nur der halbe Spaß! Ich möchte gerne Bilder aus der Datenbank in meinen Report übernehmen. Also z.B. ein Kunden-Stammblatt, auf dem das Logo des Kunden steht oder eine Preisliste mit Abbildungen meiner Produkte.
Hierzu benötigt man zuerst einmal das Image als Daten. In iDempiere (und überhaupt) gibt es verschiedene Systeme, wie Images abgespeichert werden. Die Logos der Geschäftspartner werden dabei standardmäßig im (meiner Meinung nach) sinnvollsten Format abgespeichert: Als Strom von Binärdaten, die in der Datenbank in einer eigenen Tabelle gespeichert werden. Warum ist das sinnvoll: Bilder sind genauso wie alle anderen Daten Teil der Summe aller Geschäftsdaten und gehören damit in die Datenbank (und nicht ins Filesystem, ins Internet oder sonstwohin). Speichert man sie in den normalen Datensatz (z.B. die Tabelle C_BPartner), so wird dieser allerdings extrem aufgebläht und somit verlangsamt sich der Zugriff auf Geschäftspartner sehr stark. Daher werden solche Bilder in iDempiere in die Tabelle AD_Image gespeichert. Wer möchte, kann diese Tabelle dann sogar auf eine eigene Festplatte auslagern, um so weder Performance noch Speicherplatz der eigentlichen Datenbank zu stören.
Nun gibt es folgende Fälle:
- statisches Bild: Ein solches Bild hat eine feste URL als String. Für diese Bilder kann man in iReport einstellen, das diese gecachet werden. Das bedeutet einerseits, das ein Logo, das auf jeder Seite gedruckt werden soll, bei der Erstellung des Berichts nur einmal geladen wird. Andererseits bedeutet es, das ebendieses Logo auch in einem am Ende erzeugten PDF nur einmal enthalten ist (diese Datei also relativ klein ist).
- dynamische geladenes Bild: Ein solches Bild kommt aus einer Datenquelle, die sich jederzeit ändern kann. Der hier interessanteste Fall ist, das es aus der iDempiere-Datenbank geladen werden kann. Ein solches Bild wird dann als Datenstrom (Java-Objekt vom Typ InputStream) in den Report eingelesen und kann nicht gecachet werden.
- statisches Bild aus dynamischer Quelle: Das Plugin JasperReportsFreiBier erlaubt noch eine weitere Variante: Ein Bild hat einen String-Namen (was für JasperReports und iReport bedeutet, das es gecachet werden kann) wie z.B. "AD_Image:1000123.png", wird aber trotzdem dynamisch aus der Datenbank geladen. Die Übertragung vom Namen in eine Datenbankabfrage übernimmt dabei eine Implementation des "FileResolver" Interface namens ReportFileResolverImage.
Nun zur Erklärung des dynamisch geladenen Bildes. Dieser Fall ist aus Sicht des Berichtes der komplizierteste:
Man muss die Daten as AD_Image, (die mit Absicht aus der Haupt-Tabelle herausgehalten werden), mit dieser mit einlesen. Das geht wie üblich mit so etwas wie:
SELECT * FROM C_BPartner LEFT JOIN AD_Image ON(C_BPartner.logo_ID = AD_Image.AD_Image_ID) WHERE C_BPartner.C_BPartner_ID=$P{RECORD_ID}
Ein derartiger Query-String wird in iReport als Haupt-Query eingegeben. Dann kann man ein ganz normales Image-Element aus dem Panel nehmen und in seinen Report ziehen. In den Eigenschaften dieses Images kann man nun die "Expression Class" einstellen. Falls man doch seine Bilder im Filesystem gespeichert hat (und eine URL in der Datenbank), kann man hier String angeben. Hat man die Bilder als Binärdaten z.B. aus AD_Image geholt, nimmt man am besten java.io.InputStream. Als Image Expression nimmt man in ersterem Fall einfach die entsprechende Feldvariable. Für Binärdaten nimmt man den folgenden String:
new java.io.ByteArrayInputStream((byte[])$F{binarydata})
Dieser Ausdruck ist im Grunde das ganze Geheimnis. Damit läuft es dann auch schon - Viel Spaß! :-)
Problem mit Images aus InputStreams und PostgreSQL 9.x
Bis heute (Januar 2014) wird bei iReport ein PostgreSQL-Treiber 8.x mitgeliefert. Leider hat sich in den Grundeinstellungen von PostgreSQL etwas geändert zwischen 8.x und 9.x bzgl. des bytea_output-Wertes etwas geändert. Es gibt auch eine Beschreibung aus JasperReports-Sicht hierzu. Um das Problem zu beheben kann man entweder diese Einstellung ändern (als Voreinstellung im Datenbank-User oder bei jedem Query) oder (besser) einen aktuellen JDBC-Treiber als JAR-File herunterladen und in den iReport-Optionen in der Registerkarte "Classpath" einbinden. Danach ging es bei mir problemlos.
Parameter in Reporten
ADempiere übergibt verschiedene Parameter an JasperReports. Diese muss man nicht benutzen, kann es aber, indem man sie in iReport als Parameter definiert. Dabei muss man einfach einen Parameter des angegebenen Namens (Gross- und Kleinschreibung beachten) anlegen. Um seine Reporte auch in iReport sinnvoll testen zu können, sollte man dann noch einen intelligenten Default-Wert angeben, der einem beim Test halbwegs sinnvolle Daten in die Ausgabe zaubert. Natürlich kann es von Fall zu Fall auch sinnvoll sein, einen Default-Wert im SQL-Code, der diese Parameter benutzt, z.B. mit COALESCE(...) zu erzeugen.
Da sich die konkrete Ausgestaltung der automatisch übergebenen Parameter und deren Benennung von Parametern bei den beiden alternativen Plugins unterscheidet, bitte ich auch hier, unter JasperReportsClassic bzw. JasperReportsFreiBier nachzusehen.
iReport-Tips
ADempiere hatte früher Probleme mit Groovy als Skriptsprache (aufgrund von Versionsdifferenzen). Einerseits erleichtert Groovy das Leben ganz allgemein und andererseits laufen Reporte in ADempiere eher, wenn man Java verwendet. Mit iDempiere habe ich es ehrlich gesagt noch nicht probiert (könnte aber besser klappen, weil genau derartige Probleme durch OSGi gelöst werden sollten). Um die Skriptsprache umzustellen kann man in iReports in "Extras/Optionen" auf der Registerkarte "General", Unterkarte "Report Defaults" die Language umstellen. Diese Einstellung gilt dann für alle neuen Reporte. Die eigentliche Einstellung nimmt man im Objektbaum des Reports auf der allerobersten Ebene vor. Dort gibt es eine Einstellung "Language". Eigentlich hätte ich erwartet, das man diese Einstellung auch noch je Ausgabefeld einstellen kann, was aber scheinbar nicht geht.
Fonts
Das Benutzen von eigenen Zeichensätzen wird in den Dokus immer so beschrieben, als ob es mit besonderen Fallstricken versehen wäre. Daher dokumentiere ich hier vorsichtshalber, was mir zu dem Thema so begegnet ist:
- http://jasperforge.org/uploads/publish/jasperreportswebsite/trunk/sample.reference/fonts/index.html - offizielles Fonts-Beispiel von der JasperReports-Seite
- http://www.alexander-merz.com/print_43.html - Artikel von 2006 zum Thema (sind das die Basics?)
- http://grysz.com/2010/06/01/use-custom-fonts-in-jasperreports-pdf-exporter/ - neuerer Artikel zum Thema
- http://mdahlman.wordpress.com/2010/05/28/jaspersoft-v3-7-font-extensions/ - beschreibt das neue Font-Handling ab Version 3.7, bezieht sich aber leider explizit auf den (kostenpflichtigen) Ultimate Guide
- http://www.faqs.org/docs/Linux-mini/TT-Debian.html - Recht altes HOWTO über TrueType und Debian
- http://wiki.debian.org/Fonts - aktuelle Seite über Debian und Fonts
- http://fonts.debian.net/ - Abbildungen von allen Fonts in Debian
- http://dejavu-fonts.org - gut ausgearbeitete Fontfamilie für alle Gelegenheiten
Welcher Font in einem Report wirklich benutzt wird, ist immer eine lange Geschichte. Eigentlich ist nur der Dejavu-Font in iReport selber enthalten. Dazu gibt es noch einige Fontbezeichnungen, die je nach Umgebung auf einen möglichst passenden Systemfont abgebildet werden. Diese sind "Monospaced", "Serif" und "SansSerif". Wenn ich versuche, einen Font einzustellen (z.B. bei den Eigenschaften eines Textfeldes), so stehen diese "ordentlich konfigurierten" Fonts ganz oben (über dem Strich). Darunter stehen dann noch die im aktuellen Betriebssystem installierten Fonts. Diese sollte man normalerweise nicht benutzen. Sie haben erstens den Nachteil (je nach Anwendungsfall), das sie nicht plattformübergreifend zur Verfügung stehen und zweitens, das sie nicht in PDF-Dokumenten verwendet werden.
Der moderne Weg, um Fonts in einem Report zu benutzen, ist, diese in eine sogenannte Font Extension zu packen. Das ist ein JAR-File, das den Font beschreibt. Dieses JAR-File kommt dann in den Classpath z.B. von iReport (geschieht ganz automatisch, wenn man einen Font richtig installiert). Man kann Fonts ausgehend von ganz normalen TTF-Dateien in den Einstellungen von iReport hinzufügen. Dort kann man dann aus einem solchen hinzugefügten Font auch eine Font Extension (sprich: ein JAR-File) machen. Dieses kann man dann ganz normal seiner Applikation beifügen. Für ADempiere/FreiBier heisst das z.B., das ich diese Datei nach packages/freibier/lib kopiere (es gibt verschiedene Wege, JARs zu ADempiere hinzuzufügen, Packages sind einer davon). In iDempiere habe ich das bisher noch nicht gemacht.
Früher konnte man wohl für die PDF-Ausgabe einen eigenen Font angeben, das scheint aber nicht mehr empfohlen zu werden. In den alten Zeiten war es wohl nicht so einfach (oder nicht möglich) einen eigenen Font in ein PDF zu integrieren. Außerdem finde ich es extrem blöde, wenn der normale, im Viewer angezeigte Report anders aussieht als dein PDF-Report. Zu überlegen ist allerdings, ob man den Font in das PDF-Dokument einbettet oder nicht. Ersteres garantiert immer eine ordentliche Anzeige auf allen Zielrechnern (und ist eigentlich der übliche Weg), zweiteres sorgt für kleinere PDF-Dateien und ist vielleicht platzsparender, wenn man im Unternehmenseinsatz sehr viele Dokumente erzeugt und diese dann auch archivieren will (in einem kleinen Test mit einem einseitigen Dokuemnt hatte ich 2,5KB für das blanke PDF und 7,1KB für das mit integrierem Font, allerdings wurden wohl nur die verwendeten ca. 10 Buchstaben eingebettet, der praktische Unterschied könnte also durchaus im Bereich von einigen -zig KB liegen).
In einem Beispiel hatte ich den Standard-Font "SansSerif" ausgesucht, damit ein PDF generiert und dieses dann auf einem recht einfach installierten Linux-System (einem LTSP Thin Client) in einem Chrome-Browser angesehen. Die Ausgabe war unschön verschoben. Dieselbe Datei konnte aber z.B. auf einem Android Gerät korrekt wiedergegeben werden. Soweit ich das Problem nachverfolgt habe, benutzt JasperReports in diesem Fall bei der Erzeugung des PDF den Adobe-Standardfont Helvetica, der dort nicht installiert war (ich bin da aber nicht ganz sicher, es kann auch ein anderer Fontname sein, der benutzt wird). Auf jeden Fall half es, das Debian-Paket "fonts-liberation" auf dem Thin Client zu installieren.
Styles
Seit einiger Zeit gibt es in iReport sogenannte Styles. Damit kann man einen Schriftstil (inklusive Font) einmalig vorgeben und braucht ihn dann nicht für jedes Feld neu festzulegen. Vor allem kann man aber den Stil auch an einer Stelle global ändern.
Fragen, die bleiben:
- Die speziellen Angaben für PDF-Fonts sind in meinem iReport vorhanden, aber durchgestrichen - was bedeutet das?
- Ich sollte mal versuchen, einen ganz fremden TTF-Font zu integrieren und das dann dokumentieren, um das obige alles beweisen zu können
Auf der Seite Using Jasper Report Form in place of Standard Forms ist (ziemlich unten) auch ein ausführlicher Teil zur Arbeit mit einem eigenen Font.
verwandte Links
- http://dynamicreports.sourceforge.net/ - interessante Erweiterung von JasperReports, um Reporte dynamisch per Java-Code zu erzeugen
- https://sourceforge.net/projects/jrdesigner/ - Seite zu einem ColumnDesigner mit wenig Informationen. Vielleicht handelt es sich um die gleiche Funktion, die unter http://www.jaspersoft.com/de/column-manager-0 verkauft wird. Dort steht, das man damit parametergesteuert Spalten ein- und ausblenden kann.