Search code examples
groovyclojurejasper-reports

Clojure: passing a list of records to a Java object


I am experimenting with Clojure. I was able to generate a report in JasperReports, but it gives null values. The Java object in question is JRBeanCollectionDataSource. I pass it a list of records, but somehow, the PDF contains only nulls.

Also, why am I not able to pass it {} as an empty HashMap? In Groovy, the [:] syntax works OK.

(ns jasper.core
  (:import
   (net.sf.jasperreports.engine JasperCompileManager
                                JasperFillManager
                                JasperExportManager)
   (net.sf.jasperreports.engine.data JRBeanCollectionDataSource)))
(import 'java.util.HashMap)


(defrecord Car [id name price])

(def data [(->Car 1, "Audi", 52642)
           (->Car 2, "Mercedes", 57127)
           (->Car 3, "Skoda", 9000)
           (->Car 4, "Volvo", 29000)
           (->Car 5, "Bentley", 350000)
           (->Car 6, "Citroen", 21000)
           (->Car 7, "Hummer", 41400)
           (->Car 8, "Volkswagen", 21600)])

(def xmlFile "resources/report.xml")
(def jrReport (JasperCompileManager/compileReport xmlFile))

;; (def params {})
(def params (HashMap.))

(def ds (JRBeanCollectionDataSource. data))
(println (.toString ds))

(def jrPrint (JasperFillManager/fillReport jrReport params ds))

(defn -main
  []
  (JasperExportManager/exportReportToPdfFile jrPrint "report.pdf"))

This is a rewrite of the following working Groovy solution:

@Grab(group='net.sf.jasperreports', module='jasperreports', version='6.17.0')

import net.sf.jasperreports.engine.JasperCompileManager
import net.sf.jasperreports.engine.JasperFillManager
import net.sf.jasperreports.engine.JasperExportManager
import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource
import groovy.transform.Immutable

@Immutable
class Car {
    Long id;
    String name;
    int price;
}

def data = [
    new Car(1L, 'Audi', 52642),
    new Car(2L, 'Mercedes', 57127),
    new Car(3L, 'Skoda', 9000),
    new Car(4L, 'Volvo', 29000),
    new Car(5L, 'Bentley', 350000),
    new Car(6L, 'Citroen', 21000),
    new Car(7L, 'Hummer', 41400),
    new Car(8L, 'Volkswagen', 21600),
]

def empty = []

def xmlFile = "report.xml"

def jrReport = JasperCompileManager.compileReport(xmlFile)
def ds = new JRBeanCollectionDataSource(data)

def params = [:]
def jrPrint = JasperFillManager.fillReport(jrReport, params, ds)

JasperExportManager.exportReportToPdfFile(jrPrint, "report.pdf")

Edit For a fully working example, place the following file in the current working directory for Groovy, and resources directory for Clojure.

<?xml version = "1.0" encoding = "UTF-8"?>
<!DOCTYPE jasperReport PUBLIC "//JasperReports//DTD Report Design//EN"
        "http://jasperreports.sourceforge.net/dtds/jasperreport.dtd">

<jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports
   http://jasperreports.sourceforge.net/xsd/jasperreport.xsd"
              whenNoDataType="NoDataSection"
              name="report" topMargin="20" bottomMargin="20">

    <field name="id" class="java.lang.Long"/>
    <field name="name"/>
    <field name="price" class="java.lang.Integer"/>

    <detail>
        <band height="15">

            <textField>
                <reportElement x="0" y="0" width="50" height="15"/>

                <textElement textAlignment="Right" verticalAlignment="Middle"/>

                <textFieldExpression class="java.lang.Long">
                    <![CDATA[$F{id}]]>
                </textFieldExpression>
            </textField>

            <textField>
                <reportElement x="150" y="0" width="100" height="15" />

                <textElement textAlignment="Left" verticalAlignment="Middle"/>

                <textFieldExpression class="java.lang.String">
                    <![CDATA[$F{name}]]>
                </textFieldExpression>
            </textField>

            <textField>
                <reportElement x="200" y="0" width="100" height="15" />
                <textElement textAlignment="Right" verticalAlignment="Middle"/>

                <textFieldExpression class="java.lang.Integer">
                    <![CDATA[$F{price}]]>
                </textFieldExpression>
            </textField>

        </band>
    </detail>

    <noData>
        <band height="15">
            <staticText>
                <reportElement x="0" y="0" width="200" height="15"/>
                <box>
                    <bottomPen lineWidth="1.0" lineColor="#CCCCCC"/>
                </box>
                <textElement />
                <text><![CDATA[The report has no data]]></text>
            </staticText>
        </band>
    </noData>

</jasperReport>

Solution

  • With the hints from the comments, I was able to resolve the issue. The problem is that Clojure records do not implement the JavaBeans spec, while the JRBeanCollectionDataSource expects such beans.

    With the clj-bean library, I was able to make it work.

    (defbean Car
     [[Long id]
      [String name]
      [Integer price]])
    
    (def data [(Car. 1 "Audi" 52642),
               (Car. 2 "Mercedes" 57127),
               (Car. 3 "Skoda" 9000),
               (Car. 4 "Volvo" 29000),
               (Car. 5 "Bentley" 350000),
               (Car. 6 "Citroen" 21000),
               (Car. 7 "Hummer" 41400),
               (Car. 8 "Volkswagen" 21600)])
    

    Now the report contains data.