Páginas

sábado, 16 de marzo de 2013

Subreports con JasperReports usando JRBeanCollectionDataSource

Introducción

Siempre que usamos Jasperreports para montar informes, pasamos todos los parámetros, incluso las etiquetas, desde el aplicativo Java. Esto ofrece varias ventajas:
  • Si por ejemplo cambia el motor de la BBDD de un aplicativo en producción, únicamente ajustamos la configuración de Hibernate sin regenerar los informes.
  • Si necesitamos el informe en varios idiomas, controlamos el idioma del usuario desde el aplicativo Java (por ejemplo recogiendo las preferencias del navegador con JSF).
Hasta la fecha siempre montábamos informes simples, es decir, con una única banda de detalle. Ahora que hemos necesitado montar algunos informes con varias bandas de detalle hemos necesitado incluir "subreports" y, como en mecanismo de envío de las distintas "JRBeanCollectionDataSource" asociadas a cada banda de detalle no me ha parecido trivial, dejo resumidos los pasos a realizar.

Clase Java para generar el informe

package org.dune.app;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JasperExportManager;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.JasperReport;
import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource;
import net.sf.jasperreports.engine.util.JRLoader;

import org.dune.pojo.Coche;
import org.dune.pojo.Telefono;

public class TestReport {

    //Datos cabecera
    private static Map<String,Object> datosCabeceraReport;
   
    //Datos detalle
    private static List<Coche> datosDetalleReport;
    private static List<Telefono> datosDetalleSubReport;
   
    public static void main(String args[]) throws JRException{
       
        //Preparamos los datos de cabecera del report, cuando en
        //el report.jrxml añadamos los Parameter "titulo" y "fecha"
        //tendremos que asegurarnos que sean de tipo "String"
        datosCabeceraReport = new HashMap<String,Object>();
        datosCabeceraReport.put("titulo", "Inventario de bienes");
        datosCabeceraReport.put("fecha", "15/03/2013");
       
        //Preparamos los datos de detalle del report
        datosDetalleReport = new ArrayList<Coche>();
        datosDetalleReport.add(new Coche("1234-AAA", "Peugeot 207"));
        datosDetalleReport.add(new Coche("5678-AAA", "Peugeot 307"));
        datosDetalleReport.add(new Coche("9012-AAA", "Peugeot 407"));
       
        //Preparamos los datos de detalle del subreport
        datosDetalleSubReport = new ArrayList<Telefono>();
        datosDetalleSubReport.add(new Telefono("111111111", "Nokia 1000"));
        datosDetalleSubReport.add(new Telefono("222222222", "Nokia 2000"));
        datosDetalleSubReport.add(new Telefono("333333333", "Nokia 3000"));
       
        //Añadimos los datos de detalle del subreport al Map de parametros del
        //padre y montandolos sobre un JRBeanCollectionDataSource. Cuando en
        //el report.jrxml añadamos el Parameter "datosSubRep" tendremos que
        //forzar el tipo a: "net.sf.jasperreports.engine.data.JRBeanCollectionDataSource"
        datosCabeceraReport.put("datosSubRep", new JRBeanCollectionDataSource(datosDetalleSubReport));
       
        //Montamos el objeto JRMapCollectionDataSource con los datos de la bandas de detalle del report
        final JRBeanCollectionDataSource datosReport = new JRBeanCollectionDataSource(datosDetalleReport);
       
        //Generamos el informe compilado
        creaInforme(datosCabeceraReport, datosReport);
    }
   
    private static void creaInforme(final Map<String,Object> datosCabeceraReport, final JRBeanCollectionDataSource datosReport) throws JRException{
       
        //Cargamos el informe previamente compilado
        final String urlReport = TestReport.class.getResource("/report.jasper").getFile();
        final JasperReport informe = (JasperReport) JRLoader.loadObject(urlReport);

        //Añadimos los datos
        final JasperPrint informePrint = JasperFillManager.fillReport(informe, datosCabeceraReport, datosReport);

        //Exportamos a pdf
        JasperExportManager.exportReportToPdfFile(informePrint,"./src/main/resources/report.pdf");
    }
}


Cuestiones fundamentales

Los detalles fundamentales se han introducido como comentarios en el código de la clase anterior, pero merece la pena repasarlos:
  • Los "parámetros estáticos" del report padre, esto es, todos aquellos que no aparecen en la banda de detalle del informe padre, se pasan al JasperFillManager dentro del objeto: final Map<String,Object> datosCabeceraReport.
  • Los datos de detalle del subreport (lista de telefonos) se pasan al JasperFillManager como un parámetro de tipo JRBeanCollectionDataSource dentro del objeto: final Map<String,Object> datosCabeceraReport .
  • Los datos de detalle del report (lista de coches) padre se pasan al JasperFillManager dentro del objeto: final JRBeanCollectionDataSource datosReport.
  • En el report.jrxml hemos definido los parámetros: titulo, fecha y datosSubRep. Los dos primeros, de tipo String, se usarán en la banda título del report padre. El tercer parámetro, de tipo JRBeanCollectionDataSource, se usará en la propiedad "Data Source Expression" del subreport: $P{datosSubRep}.
  • En el report.jrxml hemos definido los "Fields": matricula y modelo, que son las columnas del área de detalle y que son los atributos de la clase Coche.java.
  • En el subreport.jrxml hemos definido los "Fields": numero y tipo, que son las columnas del área de detalle y que son los atributos de la clase Telefono.java.
Clases java con los "POJO" de las bandas de detalle

La clase Coche.java se usará en la banda de detalle del informe padre.

package org.dune.pojo;

public class Coche {

    private String matricula;
    private String modelo;
       
    public Coche(final String matricula, final String modelo) {
        super();
        this.matricula = matricula;
        this.modelo = modelo;
    }

    public String getMatricula() {
        return matricula;
    }

    public void setMatricula(String matricula) {
        this.matricula = matricula;
    }

    public String getModelo() {
        return modelo;
    }

    public void setModelo(String modelo) {
        this.modelo = modelo;
    }
}


La clase Telefono.java se usará en la banda de detalle del subinforme.

package org.dune.pojo;

public class Telefono {

    private String numero;
    private String tipo;

    public Telefono(final String numero, final String tipo) {
        this.numero = numero;
        this.tipo = tipo;
    }

    public String getNumero() {
        return numero;
    }

    public void setNumero(String numero) {
        this.numero = numero;
    }

    public String getTipo() {
        return tipo;
    }

    public void setTipo(String tipo) {
        this.tipo = tipo;
    }
}


report.jrxml

El aspecto general del informe padre en el diseñador es:


El código xml del informe padre es:

<?xml version="1.0" encoding="UTF-8"?>
<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" name="report1" language="groovy" pageWidth="595" pageHeight="842" columnWidth="555" leftMargin="20" rightMargin="20" topMargin="20" bottomMargin="20">
    <property name="ireport.zoom" value="1.4641000000000008"/>
    <property name="ireport.x" value="0"/>
    <property name="ireport.y" value="0"/>
    <parameter name="titulo" class="java.lang.String"/>
    <parameter name="fecha" class="java.lang.String"/>
    <parameter name="datosSubRep" class="net.sf.jasperreports.engine.data.JRBeanCollectionDataSource"/>
    <field name="matricula" class="java.lang.String"/>
    <field name="modelo" class="java.lang.String"/>
    <title>
        <band height="51" splitType="Stretch">
            <rectangle radius="5">
                <reportElement mode="Transparent" x="2" y="4" width="547" height="44"/>
            </rectangle>
            <textField>
                <reportElement x="61" y="26" width="488" height="20"/>
                <textElement verticalAlignment="Middle"/>
                <textFieldExpression class="java.lang.String"><![CDATA[$P{fecha}]]></textFieldExpression>
            </textField>
            <staticText>
                <reportElement x="6" y="6" width="55" height="20"/>
                <textElement verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[TITULO:]]></text>
            </staticText>
            <staticText>
                <reportElement x="6" y="26" width="55" height="20"/>
                <textElement verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[FECHA:]]></text>
            </staticText>
            <textField>
                <reportElement x="61" y="6" width="488" height="20"/>
                <textElement verticalAlignment="Middle"/>
                <textFieldExpression class="java.lang.String"><![CDATA[$P{titulo}]]></textFieldExpression>
            </textField>
        </band>
    </title>
    <pageHeader>
        <band height="73">
            <rectangle radius="5">
                <reportElement positionType="Float" mode="Opaque" x="2" y="50" width="547" height="22" backcolor="#C68B8B"/>
            </rectangle>
            <rectangle radius="5">
                <reportElement mode="Opaque" x="2" y="1" width="547" height="22" forecolor="#010101" backcolor="#C68B8B"/>
            </rectangle>
            <staticText>
                <reportElement x="6" y="2" width="100" height="20"/>
                <textElement verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[TELEFONOS:]]></text>
            </staticText>
            <staticText>
                <reportElement positionType="Float" x="6" y="51" width="100" height="20"/>
                <textElement verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[COCHES:]]></text>
            </staticText>
            <subreport>
                <reportElement x="0" y="24" width="555" height="24"/>
                <dataSourceExpression><![CDATA[$P{datosSubRep}]]></dataSourceExpression>
                <subreportExpression class="java.lang.String"><![CDATA["/home/eduardo/trabajo/workspaces-helios/wksTestJasper/TestJasper/src/main/resources/subreport.jasper"]]></subreportExpression>
            </subreport>
        </band>
    </pageHeader>
    <columnHeader>
        <band height="25" splitType="Stretch">
            <rectangle radius="5">
                <reportElement mode="Transparent" x="2" y="2" width="547" height="22"/>
            </rectangle>
            <staticText>
                <reportElement x="104" y="3" width="445" height="20"/>
                <textElement verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[MODELO]]></text>
            </staticText>
            <staticText>
                <reportElement x="6" y="3" width="100" height="20"/>
                <textElement verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[MATRICULA]]></text>
            </staticText>
        </band>
    </columnHeader>
    <detail>
        <band height="27" splitType="Stretch">
            <rectangle radius="5">
                <reportElement stretchType="RelativeToBandHeight" isPrintRepeatedValues="false" mode="Transparent" x="2" y="2" width="547" height="22" isPrintWhenDetailOverflows="true"/>
            </rectangle>
            <textField>
                <reportElement x="6" y="3" width="100" height="20"/>
                <textElement verticalAlignment="Middle"/>
                <textFieldExpression class="java.lang.String"><![CDATA[$F{matricula}]]></textFieldExpression>
            </textField>
            <textField>
                <reportElement x="104" y="3" width="445" height="20"/>
                <textElement verticalAlignment="Middle"/>
                <textFieldExpression class="java.lang.String"><![CDATA[$F{modelo}]]></textFieldExpression>
            </textField>
        </band>
    </detail>
</jasperReport>


subreport.jrxml

El aspecto general del subinforme en el diseñador es:


El código xml del subinforme padre es:

<?xml version="1.0" encoding="UTF-8"?>
<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" name="subreport" language="groovy" pageWidth="555" pageHeight="100" columnWidth="555" leftMargin="0" rightMargin="0" topMargin="0" bottomMargin="0">
    <property name="ireport.zoom" value="1.464100000000001"/>
    <property name="ireport.x" value="0"/>
    <property name="ireport.y" value="0"/>
    <field name="numero" class="java.lang.String"/>
    <field name="tipo" class="java.lang.String"/>
    <columnHeader>
        <band height="25" splitType="Stretch">
            <rectangle radius="5">
                <reportElement mode="Transparent" x="2" y="2" width="547" height="22"/>
            </rectangle>
            <staticText>
                <reportElement x="6" y="3" width="100" height="20"/>
                <textElement verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[NUMERO]]></text>
            </staticText>
            <staticText>
                <reportElement x="106" y="3" width="441" height="20"/>
                <textElement verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[TIPO]]></text>
            </staticText>
        </band>
    </columnHeader>
    <detail>
        <band height="25" splitType="Stretch">
            <rectangle radius="5">
                <reportElement stretchType="RelativeToTallestObject" isPrintRepeatedValues="false" mode="Transparent" x="2" y="1" width="547" height="22" isPrintWhenDetailOverflows="true"/>
            </rectangle>
            <textField>
                <reportElement x="106" y="2" width="441" height="20"/>
                <textElement/>
                <textFieldExpression class="java.lang.String"><![CDATA[$F{tipo}]]></textFieldExpression>
            </textField>
            <textField>
                <reportElement x="6" y="2" width="100" height="20"/>
                <textElement/>
                <textFieldExpression class="java.lang.String"><![CDATA[$F{numero}]]></textFieldExpression>
            </textField>
        </band>
    </detail>
</jasperReport>


Aspecto del informe generado



Dependencias

Las dependencias Maven del proyecto son:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.dune</groupId>
  <artifactId>TestJasper</artifactId>
  <version>1.0</version>
  <dependencies>
      <dependency>
          <groupId>net.sf.jasperreports</groupId>
          <artifactId>jasperreports</artifactId>
          <version>4.0.0</version>
          <type>jar</type>
          <scope>compile</scope>
      </dependency>
      <dependency>
          <groupId>org.codehaus.groovy</groupId>
          <artifactId>groovy-all</artifactId>
          <version>1.7.5</version>
          <type>jar</type>
          <scope>compile</scope>
      </dependency>
  </dependencies>
</project>

7 comentarios:

  1. podrias generar un zip para descargar con el ejemplo?

    ResponderEliminar
  2. Claro te dejo el enlace al zip del proyecto (maven2)

    https://mega.co.nz/#!ZcgzzJwT!ak0DgSthP-2TnQfxEmDZiHiSyAMj6CiQwDvkpzrtDdY

    Un saludo

    ResponderEliminar
  3. Me salvas el pellejo hermano, tengo 2 dias tratando de resolver esto. Precisamente es lo que estaba buscando y que mejor si funciona con algunas correcciones subo mis cambios para algun otro despistado que utilice jasper 5.5 (Ojo el ireport debe ser de la misma version que el jasper del proyecto). Corrige algunas dependencias con rutas y dependencias de librerias. https://mega.co.nz/#!45BQlbzJ!XjTsAXtkxWF_EtAjfKexqlinlm4ukreMHb8FOnb9X2Y

    ResponderEliminar
  4. Muchas gracias! Me ha servido mucho. Saludos!

    ResponderEliminar
  5. Buenas, tengo un problema para pasar los datos (fields) del report padre al report hijo. Con los parámetros no he tenido muchos problemas.
    Todos los fields los tengo visibles y los puedo usar en el report padre, pero a la hora de pasarlos al subReport no soy capaz. A ver si hay alguien que me pueda ayudar.
    Gracias por adelantado.

    ResponderEliminar
  6. Hola

    Por lo que dices, entiendo que necesitas que ciertos parámetros sean visibles tanto en el report padre como en el report hijo y una vez recibidos en jasper, no hay manera de acceder a ellos desde el subreport. Como no he investigado ese caso de uso, ¿porqué no pasas esos parámetros dos veces?, es decir algo del estilo siguiente:

    //Datos de la cabecera del report principal
    datosCabeceraReport = new HashMap();
    datosCabeceraReport.put("titulo", "Inventario de bienes");
    datosCabeceraReport.put("fecha", "15/03/2013");

    //Datos del detalle del report principal
    datosDetalleReport = new ArrayList();
    datosDetalleReport.add(new Coche("1234-AAA", "Peugeot 207"));
    datosDetalleReport.add(new Coche("5678-AAA", "Peugeot 307"));
    datosDetalleReport.add(new Coche("9012-AAA", "Peugeot 407"));

    //Preparamos los datos de detalle del subreport, que tiene datos tanto telefónicos como del coche asociado.
    datosDetalleSubReport = new ArrayList();
    datosDetalleSubReport.add(new Coche("1234-AAA", "Peugeot 207"), new Telefono("111111111", "Nokia 1000"));
    datosDetalleSubReport.add(new Coche("1234-AAA", "Peugeot 207"), new Telefono("222222222", "Nokia 2000"));
    datosDetalleSubReport.add(new Coche("5678-AAA", "Peugeot 307"), new Telefono("333333333", "Nokia 3000"));

    ResponderEliminar
  7. Pregunta, quizás este hilo ya cerro.. sin embargo lo pregunto por las dudas:
    Como puedo resolver en caso de que, por ejemplo, la Clase Coche tenga una arreglo (ArrayList) de por ejemplo objetos "Butacas"...

    package org.dune.pojo;

    public class Coche {

    private String matricula;
    private String modelo;
    private ArrayList butaca;

    En ese caso...como se muestra un subdetalle de coches y sus butacas.

    (perdón no me puse a pensar en algo mejor).

    Saludos.

    ResponderEliminar