Si vis pacem para bellum

iReport sobre Struts – Paso 3 : Generar el informe desde Java

Ahora tenemos la plantilla (.jrxml) y la plantilla compilada (.jasper). La plantilla la podemos compilar también en tiempo de ejecución pero no es lo habitual.

Para entender mejor cómo funciona Jasper Reports puedes consultar la siguiente imagen (sacada de aquí):

 

Nosotros utilizaremos JasperRunManager.runReportToPdf para generar el informe en PDF desde el jasper que hemos generado. Colocamos el informe .jasper en nuestro directorio (yo lo he creado en un paquete llamado "informes"). Creamos una clase que nos genere el pdf es.lycka.holamundoStruts138Jasper.utilidades.GenerarPDF :

package es.lycka.holamundoStruts138Jasper.utilidades;

import java.util.HashMap;

import net.sf.jasperreports.engine.JREmptyDataSource;
import net.sf.jasperreports.engine.JasperReport;
import net.sf.jasperreports.engine.JasperRunManager;
import net.sf.jasperreports.engine.util.JRLoader;
import es.lycka.holamundoStruts138Jasper.model.Informe;

public class GenerarPDF {

    public byte[] generarInforme (Informe informe) {
        byte[] pdf = null;
        String rutaInforme = "/es/lycka/holamundoStruts138Jasper/informes/informe.jasper";
        HashMap<String, Informe> parametros = new HashMap<String, Informe> ();
        parametros.put("informe", informe);

        try {
            JasperReport masterReport = (JasperReport) JRLoader.loadObject(getClass().getResource(rutaInforme));
            pdf = JasperRunManager.runReportToPdf(masterReport, parametros, new JREmptyDataSource());
        } catch (Exception e) {
            e.printStackTrace();
        }

        return pdf;
    }
}

Ahora modificamos el InformeBO para invocar este método :

    public byte[] generarInforme (Informe informe) throws Exception {
        return (new GenerarPDF ()).generarInforme(informe);
    }

Ahora tenermos que hacer que nuestro action nos devuelva el pdf en la response. Para ello hacemos que el método execute nos devuelva null, y ahora implementamos el método que dejamos vacío, mostrarFichero

    private void mostrarPDF (byte[] informePDF, HttpServletResponse response) throws IOException {
        //Incializamos el array
        ByteArrayOutputStream bos    = new ByteArrayOutputStream();
        ServletOutputStream out        = response.getOutputStream();

        bos.write(informePDF);

        response.setContentType("application/pdf");
        //para que el pdf se pueda ver en microsoft explorer
        response.setHeader("Cache-Control", "cache");
        //para que aparezca el diálogo abrir/guardar
        response.setHeader("Content-Disposition", "attachment; filename=informe.pdf");
        response.setHeader("Pragma", "cache");
       
        response.setContentLength(bos.size());
        out.write(bos.toByteArray());
        out.flush();
        bos.close();
        out.close();
        response.flushBuffer();           
    }

Vale, haz la prueba y ya está. He subido el war de este proyecto a hordit, ahí puedes ver la estructura de los ficheros y las fuentes.

Joer ha costado ! Muchas gracias al Espín y a Chema que se curraron todo esto.

iReport sobre Struts – Paso 2 : Construyendo la plantilla

Bueno, pues evidentemente necesitamos descargarnos e instalarnos el software de iReport. Podemos bajarlo aquí. Yo utilizaré la versión 2.0.5 que podéis descargaros aquí.

La instalación es sencilla si os bajáis el ejecutable.

Abrimos el programa y aabrimos un nuevo documento. Nos abre con cuadro de diálogo en el que introducimos el nombre de la plantilla, reportePruebas. Veremos algo como lo siguiente:

 

Primero vemos que el informe tiene varias bandas (title, PageHeader…). Eliminamos las que no vayamos a utilizar dándoles una altura 0 (botón derecho sobre la banda > Propiedades de la banda). Ahora sólo utilizaremos la banda "Detail".

Ahora pondremos un título. Para poner en la plantilla texto estático seguimos la ruta Editar > Insertar elemento… > Texto estático. Centramos el campo y escribimos "INFORME METEOROLÓGICO". En las propiedades del elemento (panel de la derecha) centramos el texto, le damos un tamaño 18, en negrita, centrado y de tipo Helvética.

Ahora insertaremos debajo del título la imagen de la cabecera de este blog. Seguimos la ruta Editar > Insertar elemento… > Imagen.


Añadimos los datos que tienen que aparecer en el informe, primero sus etiquetas como texto estático ("CIUDAD", "TEMPERATURA MÍNIMA", "TEMPERATURA MÁXIMA"). También añadimos un rectángulo que contenga a los datos a modo de formulario, siguiendo Editar > Insertar elemento… > Rectángulo. Dibujamos el rectángulo y en las propiedades le hacemos transparente.

Finalmente añadimos los datos ! Para obtener los datos podemos realizar llamadas a base de datos desde el informe (yo lo desaconsejo totalmente) o pasar un objeto con los datos a mostrar, y en cada campo pintamos el dato que nos interese. Para ello Editar > Insertar elemento… > Campo de Texto.

A continuación le decimos el nombre del Bean y el método para obtener el campo. En nuestro caso pasaremos el bean "Informe" que ya hemos definido en el paso anterior y utilizaremos los métodos getCiudad(), getTemperaturaMinma() y getTemperaturaMaxima() respectivamente :

${informe}.getCiudad()

Ahora deberíamos de tener algo así en la pantalla:


No te engañes, no hemos acabado zZzZzZzZz. Ahora podemos compilar la plantilla (Construir > Compliar) y podemos hacer una previsualización del informe (Construir > Ejecutar informe). Verás que no se muestra exactamente como se ve en la plantilla, así que probablemente tendrás que retocar la plantilla.

Actualización

Ops, falta definir el parámetro "informe". Debemos añadir al classpath del informe nuestra clase "Informe". Vamos a Opciones > Localización de clases (classpath) y añadimos el classpath de nuestro proyecto.

Ahora vamos a Ver > Parámetros del Informe y añadimos un nuevo parámetro de nombre "informe" y nuestra clase como tipo de clase.

Reinicia el iReport y ahora debería de compilar (y generar el .jasper) y podríamos ver la previsualización.

iReport sobre Struts – Paso 1 : Preparando Struts

Sobre el código del ejemplo anterior añadiremos una nueva funcionalidad para permitir que los usuarios identificados en la aplicación puedan introducir unos datos y generar un informe en PDF (invocando un método de negocio de momento vacío).

Haremos un informe meteorológico que constará de tres campos introducidos por el usuario :

  • Ciudad sobre la que se realiza el informe
  • Temperatura mínima del día actual
  • Temperatura máxima del día actual

También añadiremos nosotros automáticamente la fecha del informe, que será la fecha de hoy.

Para ello necesitaremos implementar una nueva "rama" :

  • jsp/generarInforme.jsp – Una jsp de entrada de la que recoger el formulario introducido por el usuario.
  • es.lycka.holamundoStruts138Jasper.model.Informe – Un objeto de modelo que represente el informe.
  • es.lycka.holamundoStruts138Jasper.form.InformeForm – Un actionForm asociado
  • es.lycka.holamundoStruts138Jasper.action.InformeAction – Un action que atienda la petición
  • es.lycka.holamundoStruts138Jasper.negocio.InformeBO – Una clase de negocio que nos devuelva el informe en formato PDF
  • Configurar Struts para que todo esto funcione

Me habré dejado algo en el tintero ? Probemos.

Vista

En el jsp que ven los usuarios identificados (jsp/indexIdentificado.jsp) añadimos un enlace a nuestra nueva funcionalidad: 

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"

    pageEncoding="ISO-8859-1"%>

<%@ taglib uri="http://struts.apache.org/tags-bean" prefix="bean" %>

<%@ taglib uri="http://struts.apache.org/tags-html" prefix="html" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<html>

<head>

      <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">

      <title>Zona Identificada</title>

</head>

<body>

      <h2>Bienvenido a la zona de administración</h2>

      <p>Su identificador de usuario es : <bean:write scope="session" name="usuario" property="identificador" /></p>

      <p>Usted puede realizar las siguientes operaciones:</p>

      <ul>

            <li><html:link href="jsp/generarInforme.jsp">Generar Informe</html:link></li>

      </ul>

</body>

</html>

Se vería así :

 

 

 

Y ahora tenemos que implementar una nueva página JSP (jsp/generarInforme.jsp) que permita al usuario introducir unos datos y enviarlos al servidor para que genere el informe.

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"

    pageEncoding="ISO-8859-1"%>

<%@ taglib uri="http://struts.apache.org/tags-html" prefix="html" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<html>

<head>

      <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">

      <title>Generar Informe</title>

      <script>

      function enviar (){

            document.informeForm.submit();

      }

      </script>

</head>

<body>

<h2>Generación de Informe</h2>

<html:form action="generarInforme">

<div>

      <span style="text-aling:rigth">Ciudad</span>

      <span>&nbsp;&nbsp;&nbsp;<html:text property="informe.ciudad" /></span>

</div>

<div>

      <span style="text-aling:rigth">Temperatura Máxima Hoy</span>

      <span>&nbsp;&nbsp;&nbsp;<html:text property="informe.temperaturaMaxima" /></span>

</div>

<div>

      <span style="text-aling:rigth">Temperatura Mínima Hoy</span>

      <span>&nbsp;&nbsp;&nbsp;<html:text property="informe.temperaturaMinima" /></span>

</div>

<div>&nbsp;</div>

<div>

      <input type="button" value="Generar Informe" onclick="javascript:enviar();" />

</div>

</html:form>

</body>

</html>

El resultado sería algo parecido a :

 

 

 

Modelo

Necesitamos crear un bean que modele nuestro informe, es.lycka.holamundoStruts138Jasper.model.Informe :

package es.lycka.holamundoStruts138Jasper.model;

public class Informe {

      private String ciudad;

      private String temperaturaMaxima;

      private String temperaturaMinima;

      public String getCiudad() {

            return ciudad;

      }

      public void setCiudad(String ciudad) {

            this.ciudad = ciudad;

      }

      public String getTemperaturaMaxima() {

            return temperaturaMaxima;

      }

      public void setTemperaturaMaxima(String temperaturaMaxima) {

            this.temperaturaMaxima = temperaturaMaxima;

      }

      public String getTemperaturaMinima() {

            return temperaturaMinima;

      }

      public void setTemperaturaMinima(String temperaturaMinima) {

            this.temperaturaMinima = temperaturaMinima;

      }

}

Controlador

El formulario asociado (es.lycka.holamundoStruts138Jasper.form.InformeForm) es muy sencillo, sólo lleva un informe (objeto Informe) :

package es.lycka.holamundoStruts138Jasper.form;

import org.apache.struts.action.ActionForm;

import es.lycka.holamundoStruts138Jasper.model.Informe;

public class InformeForm extends ActionForm {

      private Informe informe = new Informe ();

      public Informe getInforme() {

            return informe;

      }

      public void setInforme(Informe informe) {

            this.informe = informe;

      }

}

La clase action que necesitamos (es.lycka.holamundoStruts138Jasper.action.InformeAction) no es muy complicada, así que vamos a introducir nostros una complicación. Sabemos que por defecto Struts ejecuta el método execute de nuestro action. Pero y si queremos tener varios métodos en el mismo action ? Por ejemplo si queremos asociar varias acciones al mismo tipo de objetos : listar Informes, obtener Informe, insertar Informe, actualizar Informe, borrar Informe…

Una solución sencilla es tener varios métodos en el mismo action y utilizar uno u otro según la configuración de la acción (en el struts-config.xml). Por eso crearemos el método:

package es.lycka.holamundoStruts138Jasper.action;

import java.io.ByteArrayOutputStream;

import java.io.IOException;

import javax.servlet.ServletOutputStream;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import org.apache.struts.action.Action;

import org.apache.struts.action.ActionForm;

import org.apache.struts.action.ActionForward;

import org.apache.struts.action.ActionMapping;

import es.lycka.holamundoStruts138Jasper.form.InformeForm;

import es.lycka.holamundoStruts138Jasper.negocio.InformeBO;

public class InformeAction extends Action {

      public ActionForward execute (ActionMapping mapping, ActionForm form,

                  HttpServletRequest request, HttpServletResponse response) {

            InformeForm informeForm = (InformeForm) form;

            String destino                           = "ERROR";

            try {

                  byte[] informePDF = InformeBO.generarInforme (informeForm.getInforme ());

                  mostrarInforme (informePDF, response);

            } catch (Exception e) {

                  e.printStackTrace();

                  request.setAttribute("mensaje", "Error interno. Por favor, inténtelo otra vez en unos minutos.");

            }

            return mapping.findForward(destino);

      }

}

Negocio

Creamos un objeto de negocio (es.lycka.holamundoStruts138Jasper.negocio.InformeBO) pero no implementamos todavía el método.

package es.lycka.holamundoStruts138Jasper.negocio;

import es.lycka.holamundoStruts138Jasper.model.Informe;

public class InformeBO {

      public static byte[] generarInforme (Informe informe) throws Exception {

            return null;

      }

}

Configurar todo esto

En la etiqueta <form-beans> declaramos nuestro nuevo formulario :

<form-bean name="informeForm" type="es.lycka.holamundoStruts138Jasper.form.InformeForm" />

Y en la etiqueta <action-mappings> declaramos la nueva acción. Utilizamos parameter para indicar qué método del action se ejecutará.

<action path="/generarInforme" type="es.lycka.holamundoStruts138Jasper.action.InformeAction"

      name="informeForm" scope="request" validate="false" parameter="generar">

      <forward name="SUCCESS" path="/jsp/indexIdentificado.jsp" />

      <forward name="ERROR" path="/jsp/indexIdentificado.jsp" />

</action>

Finalmente la estructura del proyecto quedaría así (pincha en la imagen para verla ampliada) :

 

Generar ficheros PDF – iReport sobre Struts

Ya hemos mencionado iReport anteriormente. Es una herramienta opensource para generar ficheros PDF desde Java de una forma visual y fácil de entender y mantener. Nos permite generar plantillas (ficheros .jrxml) que una vez compiladas (ficheros .jasper) nos permitirán generar los ficheros PDF.

En este ejemplo vamos a partir de otro ejemplo previo, el código que generamos en el tutorial sobre Struts 1.3.8. Puedes obtener el war del proyecto en mi cuenta de hordit si no quieres montárlo tú mismo.

A grandes rasgos lo que haremos en este ejemplo será :

  1. Sobre el código del ejemplo anterior añadiremos una nueva funcionalidad para permitir que los usuarios identificados en la aplicación puedan introducir unos datos y generar un informe en PDF (invocando un método de negocio de momento vacío).
  2. Con iReport generaremos una plantilla (.jrxml)
  3. Implementaremos un método que genere un informe a partir de la anterior plantilla y de los datos que se le pase. La plantilla la compilaremos en tiempo de ejecución en este método.
  4. Desde el método de negocio que creamos vacío invocaremos al método anterior, y haremos que nuestro action nos devuelva el fichero en su respuesta (response).Cada paso lo explicaré en una entrada venidera.
  5. A estas alturas tendremos un código que funciona y que cumple con la nueva funcionalidad, pero no un código apto : he dejado un fallo de seguridad muy grave y hay otros puntos que pulir. Momento para la reflexión.

Cada paso lo explicaré en una entrada para no crear un mega monstruo. El primer paso deberías de saber hacerlo tú mismo siguiendo el mencionado tutorial de Struts 1.3.8. Inténtalo !

iReport

Recientemente hablé de iText como herramienta para generar ficheros PDF desde Java. Su gran debilidad es su forma de componer el documento mediante una estructura de tablas sin rowspan, porque complica el desarrollo y especialmente el mantenimiento.

iReport LogoA priori yo no soy amigo de generar código de forma visual, precisamente por la inconsistencia del código y la dificultad de depurarlo y mantenerlo. iReport es la primera herramienta de este tipo que recomiendo, ya que simplifica enormemente la creación de ficheros PDF desde Java, y no tenemos los problemas que he mencionado antes.

Es una herramienta opensource y estable.

Chema me apunta que no hay mucha documentación ni soporte gratuitos, pero no obstante coincide conmigo y con el Espince en recomendar esta herramienta.

Comenta