Jasper Report + Tapestry

Editado:
Les dejo como implemente mi generacion de reportes usando jasper Reports en Tapestry5.

Comienzo desde la parte en que ya se tiene el reporte.

Caracteristicas:

Manejo JRBeanCollectionDataSource puesto que estamos utilizando un ORM para la obtencion de los datos y no queremos queries en el reporte.
Manejamos el template en otro servidor para poderlos cambiar si asi se requiere (creanme funciona lo he realizado incontables veces)

De ahi tengo 2 Clases principales: una que genera el reporte y otra mas para generar la respuesta de stream de tapestry.

Como manejo archivos dentro de la app tengo un wrapper para eso.

Receta:

Primeramente agregamos la dependencia de reports yo estoy utilizando maven, esta version tendra que ser compatible con nuestro editor de las plantillas:

               <dependency>
                        <groupId>net.sf.jasperreports</groupId>
                        <artifactId>jasperreports</artifactId>
                        <version>5.0.0</version>
                </dependency>

Como les mecione utilizo archivos en la app asi que tengo una clase wrapper de archivos, la cual es la siguiente. P.D. utilizo lombok por eso las anotaciones:

@Getter
@Setter
@EqualsAndHashCode(of = {"id"})
public final class File {

        private long id;

        private String title;

        private String name;

        private String type;

        private long size;

        private byte[] content;

        private String extension;

       //Getters y setters si no usan lombok

        /**
         * NO CAMBIAR esta parte pues es utilizada en la clase de creacion de StreamResponse (StreamResponseFactory.java)
         */

        @Override
        public String toString() {
                StringBuilder builder = new StringBuilder();
                builder.append("inline;filename=");
                builder.append(name);
                builder.append(".");
                builder.append(extension);
                return builder.toString();
        }
}

Por aqui tenemos la clase que es encargada de tomar la informacion que se exportara al reporte, el tipo de reporte que se requiere CSV, PDF, EXCEL o HTML, en mi caso solo tengo implementados PDF y EXCEL y para eso utilizo un enumerador.

/**
  *Clase para la generacion del reporte.
  */

public class JasperReportGenerator {

    private static final Logger LOG = LoggerFactory.getLogger(JasperReportGenerator.class);
   
   /**
    * name: Nombre del reporte
    * asset: Enum que contiene la URL de la plantilla del reporte ya compilado
    * type: Enum que contiene el tipo de impresora de jasper, asi como el tipo de archivo y extencion del archivo
    * beanCollection: Es la colleccion de objetos que se desplegaran en el reporte y puede ser cualquier POJO, las propiedades del POJO tendran que estar como fields en el reporte de jasper, jasper se encargara de la obtencion de los valores de cada objeto.
    */

    public File generate(
            String name,
            JasperAssets asset,
            JasperReportType type,
            Collection<?> beanCollection) {

        // baos se encarga de almacenar el reporte ya impreso en un arreglo de bytes
        ByteArrayOutputStream baos = new ByteArrayOutputStream();

        JasperReport report = null;
        JasperPrint jasperPrint = null;

        try {
            report = (JasperReport) JRLoader
                    .loadObject(new URL(asset.getURL()));
           
            jasperPrint = JasperFillManager.fillReport(
                    report,
                    null,
                    new JRBeanCollectionDataSource(beanCollection));
           
            JRExporter exporter = type.getExporter();

            exporter.setParameter(JRExporterParameter.JASPER_PRINT, jasperPrint);
            exporter.setParameter(JRExporterParameter.OUTPUT_STREAM, baos);
            exporter.exportReport();

        } catch (MalformedURLException e) {
            LOG.error("------ [ Error de malformacion ] ------", e);
            LOG.error("------ [ {} ] ------", e.getMessage());
        } catch (JRException e) {
            LOG.error("------ [ Error de JasperReport ] ------", e);
            LOG.error("------ [ {} ] ------", e.getMessage());
        }
       
        //Retornamos un File pues es nuestro wrapper de archivos
        return buildFile(name, baos, type);
    }
   
    private File buildFile(
            String name,
            ByteArrayOutputStream baos,
            JasperReportType type) {
       
        File file = new File();
        file.setContent(baos.toByteArray());
        file.setExtension(type.getExtension());
        file.setName(name);
        file.setSize(baos.size());
        file.setTitle(name);
        file.setType(type.getType());
       
        return file;
    }
}

Esta clase se encargara de generar las respuestas que acepta Tapestry (StreamResponse) y que la utilizaremos en nuestras paginas Tapestry:

/**
  * Genera StreamResponse de tapestry. Se puede utilizar para abrir una nueva pagina o descarga con liga en la misma pagina.
  * Se utiliza para la obtencion de Reportes en Jasper u obtencion de archivos almacenados en DB.
  */

public final class StreamResponseFactory {

    /**
     * Se le pasa un File que contiene la informacion del archivo para generar un streamResponse
     */

    public StreamResponse getResponse(final File file) {
       
        return new StreamResponse() {

            @Override
            public void prepareResponse(Response response) {
                response.setHeader("Content-Disposition", file.toString());
            }

            @Override
            public InputStream getStream() throws IOException {
                return new ByteArrayInputStream(file.getContent());
            }

            @Override
            public String getContentType() {
                return file.getType();
            }
        };
       
    }

}

Como les mencione tengo implementados unicamente PDF y EXCEL (uds pueden agragar los que faltan o necesitan) en mi app, asi que tengo un Enumerador que almacena el comportamiento de: impresion de reporte, extencion de archivo y content-type de http:

public enum JasperReportType {
   
    PDF(new JRPdfExporter(), "pdf", "application/pdf"),
    XLS(new JRXlsExporter(), "xls", "application/vnd.ms-excel");
   
    private JRExporter exporter;
   
    private String extension;
   
    private String type;

    /**
     * @param exporter
     * @param extension
     * @param type
     */

    private JasperReportType(
            JRExporter exporter,
            String extension,
            String type) {
        this.exporter = exporter;
        this.extension = extension;
        this.type = type;
    }

    /**
     * @return the exporter
     */

    public JRExporter getExporter() {
        return exporter;
    }

    /**
     * @return the extension
     */

    public String getExtension() {
        return extension;
    }

    /**
     * @return the type
     */

    public String getType() {
        return type;
    }

}

Por este lado tenemos el Enumerador que contiene las direcciones y nombres de los reportes, recordemos que los manejo en otro servidor.

public enum JasperAssets {
   
    RECEIPT_TOOLS_STOCK("toolsStock.jasper"),
    TICKET_EXPORTER("ticketExporter.jasper");
    //TODOS los demas ...

    private final String name;
   
    private SimaAssets(String name) {
        this.name = name;
    }
   
    public String getURL() {
        return "http://host/" + name;
    }
   
}

Y para darle uso a todo esto pues tengo en una parte de mi servicio un metodo que invoca el llamado dada una colecion de objetos que se mandaran a un reporte:

En este caso invocare una exportacion a EXCEL asi que queda de la siguiente manera:

    public StreamResponse toExcel(final List<Ticket> tickets){
               
                File file = jasperReportGenerator.generate(
                        "Concentrado",      //Nombre del reporte
                        JasperAssets.TICKET_EXPORTER,  //Que plantilla deseo
                        JasperReportType.XLS,      //Que tipo de plantilla es (EXCEL en este caso)
                        tickets);    // Y la informacion que llevara el reporte
               
                LOG.info("------ [ Saliendo: toExcel() ] ------");
               
                return streamResponseFactory.getResponse(file);  // Dado el File generado lo mando aun StreamResponse
        }

Y finalmente mi invocacion desde un evento en la pagina de Tapestry

       /** LLamada desde un action link, los ticket ya han sido cargados en el onActivate de la pagina */
       StreamResponse onActionFromToExcel(){
                return ticketService.toExcel(tickets);
        }

Bueno se preguntaran por que tengo separado el generador de reporte y la generacion del stream, por dos cosas una codigo mas limpio y dos por que tambien tengo que enviar por correo el reporte asi que mando el file :) a mi servicio de correos y listo...

Recordemos que el POJO que se envia en la coleccion de datos debera tener las propiedades que el reporte tenemos como fields,

Si en el reporte tenemos:
String name
String age
String description
Date date

en el POJO debemos tener

public class DatosDeReporte {
  private String   name
  private String   age
  private String   description
  private Date   date

  //getters y setters...
}

Bueno espero les ayude y si tienen preguntas o comentarios son bien venidos...

Comentarios

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.
Imagen de Mauricio89

Duda

en que direccion van los archivos .java no se en que parte ubicarlos...

Imagen de arterzatij

Va a depender que archivos

Va a depender que archivos tienes pero segun la estructura de Tapestry

Ticket es una entidad asi que va con los modelos
ReportGenerator es un servicio
StreamResponseFactory es una utileria desde mi punto de vista asi que la puedes ubicar en un utils
Las paginas pues con los pages
Enumeradores los ubico en un paquete data

foo.bar.data
foo.bar.model
foo.bar.pages
foo.bar.services
foo.bar.utils

Imagen de Mauricio89

Errores

Disculpa me salen algunos errores como en la clase para generar el reporte en esta parte..
File file = new File();
file.setContent(baos.toByteArray());
file.setExtension(type.getExtension());
file.setName(name);
file.setSize(baos.size());
file.setTitle(name);
file.setType(type.getType());

Imagen de arterzatij

Lo mas seguro es que no estan

Lo mas seguro es que no estan generados los getters y setters (lombok), solo remueve las anotaciones @Getter @Setter Y @HashCode... y genera tu los getters y setters.