2 de abril de 2009

Creación de Reportes con JasperRepots y iReports - Parte 4: Reportes en Aplicaciones Web

Esta es la cuarta entrega de esta serie de tutoriales sobre JasperReports. En la primer parte, vimos cómo generar reportes haciendo uso de una conexión a base de datos como nuestro DataSource. Después, en la segunda parte, aprendimos a hacer esto mismo pero con un DataSource propio, lo que nos evita en tener las consultas SQL directamente en nuestro reporte y tener que pasarle posteriormente un objeto de tipo “java.sql.Connection”. Finalmente, en la tercer parte, vimos cómo hacer uso de parametros y variables para hacer nuestros reportes más dinámicos.

Ahora, en esta cuarta parte, veremos cómo hacer que un usuario pueda descargar o ver en su navegador estos reportes haciendo uso de Servlets y JSPs respectivamente.

Para este tutorial usaremos el reporte que creamos en el tutorial anterior. Así que si no lo tienen pueden ver cómo crearlo en la segunda parte del tutorial de JasperReports.

Usaremos solo el archivo "reporte2.jasper" y el código que ya habíamos creado lo pasaremos a un Servlet un poco más adelante.

Para comenzar con este tutorial lo primero que debemos hacer es abrir nuestro IDE de trabajo. Los que han seguido este blog saben que el IDE que más me gusta para trabajar con Java es el NetBeans. Así que será el que usaremos en este tutorial (y para todos los demás ^_^).

Al final del tutorial de instalación de plugins en NetBeans vimos cómo configurar Tomcat para usarlo como contenedor de Servlets y JSPs; y servidor web. Usaremos el Tomcat para este proyecto, así que si no lo tienen instalado o configurado sigan los pasos indicados en este tutorial.

Ahora creamos un nuevo proyecto web yendo al menú “File -> New Project”. En la ventana que se abre seleccionamos la categoría "Java Web" y seleccionamos "Web Application".



Hacemos clic en el botón "Next". En el siguiente paso le damos un nombre (que en mi caso será "ReporteWeb") y una ubicación al proyecto. Hacemos clic en el botón "Next". Para terminar seleccionamos el servidor que vamos a usar. En este caso, y como dije antes, usaremos el Tomcat 6, por lo que lo seleccionamos y hacemos clic en el botón "Finish". Con esto debe aparecer nuestro proyecto en la ventana "Projects", y un archivo llamado "index.jsp" en la ventana del editor.



Ahora agregamos la biblioteca de “JasperReports” que creamos en el primer tutorial de la serie, como lo hemos venido haciendo ("Add Library" en el nodo "Library" del panel "Projects").

Además también debemos copiar el archivo "reporte2.jasper" al directorio "web/WEB-INF" debajo de la raíz del proyecto. Esto es muuuuy importante porque este archivo debe poder ser encontrado por los componentes web (Servlets) de nuestra aplicación, y lo mejor es colocarlos en esta ubicación para poder obtener la ruta absoluta de dónde está, usando los métodos que nos proporcionan los Servlets. Lo colocamos dentro de “WEB-INF” para que nuestro servidor no lo envíe al usuario como lo hace, por ejemplo, con las páginas web o con las imágenes.

En el tutorial anterior usamos una clase "Participante", y eran los valores de los atributos de las instancias de esta clase los que se mostraban en el reporte. Como estamos usando la misma plantilla de reporte (“reporte2.jasper”), también usaremos la misma clase para almacenar los datos que se mostrarán. Por lo que creamos una nueva clase "Participante" (colocándola en el paquete "pruebas.reportes.jasperreports.web") y copiamos el código que ya teníamos al nuevo archivo. Para que no se regresen al tutorial anterior a ver el código, lo dejo aquí:


public class Participante
{ 
    private int id; 
    private String nombre; 
    private String username; 
    private String password; 
    private String comentarios; 
    private int puntos;  

    public Participante() 
    { 
    }  

    public Participante(int id, String nombre, String username, String password, String comentarios) 
    { 
        this.id = id; 
        this.nombre = nombre; 
        this.username = username; 
        this.password = password; 
        this.comentarios = comentarios; 
    }  

    public String getComentarios() 
    { 
        return comentarios; 
    }  

    public void setComentarios(String comentarios) 
    { 
        this.comentarios = comentarios; 
    }  

    public int getId() 
    { 
        return id; 
    }  
    
    public void setId(int id) 
    { 
        this.id = id; 
    }  

    public String getNombre() 
    { 
        return nombre; 
    }  

    public void setNombre(String nombre) 
    { 
        this.nombre = nombre; 
    }  

    public String getPassword() 
    { 
        return password; 
    }  

    public void setPassword(String password) 
    { 
        this.password = password; 
    }  

    public String getUsername() 
    { 
        return username; 
    }  

    public void setUsername(String username) 
    { 
        this.username = username; 
    }  

    public int getPuntos() 
    { 
        return puntos; 
    }  

    public void setPuntos(int puntos) 
    { 
        this.puntos = puntos; 
    } 
} 


Ahora crearemos un nuevo Servlets, que será el componente desde el que generaremos el reporte. Para eso hacemos clic derecho sobre el nodo "Source Package" del panel "Projects" y en el menú contextual que se abre seleccionamos "New -> Other...". En la ventana que se abre seleccionamos la categoría “web” y como tipo de archivo “Servlet”:



Escribimos "ServletReporte" como nombre del Servlet y lo colocamos en el paquete "pruebas.reportes.jasperreports.web":



Hacemos clic en el botón "Finish" y tendremos nuestro nuevo Servlet en la ventana del editor. Este Servlet contendrá tres métodos generados desde las plantilla de Servlets:

  • doGet (para atender la peticiones HTTP GET)
  • doPost (para atender las peticiones HTTP POST)
  • processRequest que es llamado por los dos métodos anteriores

La funcionalidad para generar el reporte la colocaremos en este último método, así que lo primero que haremos es borrar el código que ya contiene, dejándolo vacio, principalmente porque el método utiliza un “java.io.PrintWriter”, que es un objeto para generar respuestas en texto, y nosotros usaremos un “javax.servlet.ServletOutputStream”, que es un objeto para generar respuestas binarias, que en este caso será el reporte:


public class ServletReporte extends HttpServlet 
{   
    protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException 
    {

    } 

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException 
    {
        processRequest(request, response);
    } 

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException 
    {
        processRequest(request, response);
    }

    @Override
    public String getServletInfo() 
    {
        return "Short description";
    }
}


Como el reporte que regresaremos estará en formato PDF, y este es un formato binario, (todo lo que no pueda abrirse con un bloc de notas, o cualquier editor de texto plano, y entenderse directamente es un archivo binario) la única forma que existe para regresarlo de forma correcta al cliente es haciendo uso de un Stream, en este caso de un “ServletOutputStream”. Es también por esta razón que debemos hacer uso de un Servlet y no de una JSP, ya que la llamada al método que regresa el “ServletOutputStream” solo es permitida dentro de un Servlet.

Lo primero que haremos dentro de nuestro Servlet es indicar el tipo de contenido que regresaremos (que por default es "text/html"). Como se trata de un archivo PDF el tipo de retorno es "application/pdf". Indicamos esto con el método "setContentType" del objeto "HttpServletResponse" que recibimos como segundo argumento, de la siguiente forma:


response.setContentType("application/pdf");


Este método se encarga de establecer las cabeceras adecuadas en la respuesta regresada al cliente para que este sepa cómo tratar los datos que está recibiendo. En este caso esperamos que abra el reporte con su aplicación habitual para leer archivos PDF (Acrobat Reader o algún otro). Es importante establecer este valor antes de obtener el “ServletOutputStream”, de lo contrario podríamos obtener algún error inesperado al enviar la respuesta.

Ahora que el Servlet ya conoce el tipo de respuesta que regresará podemos proceder a obtener el “ServletOutputStream” correspondiente para enviar la respuesta al cliente:


ServletOutputStream out = response.getOutputStream();


Ahora generamos el reporte como lo hemos estado haciendo hasta ahora, haciendo unos pequeños cambios: como ahora el archivo de la plantilla del reporte no se encuentra en la raíz del proyecto, sino dentro del directorio web, debemos indicar la ruta completa en la que se encuentra este archivo. Por lo que es necesario cambiar la línea


JasperReport reporte = (JasperReport) JRLoader.loadObject("reporte2.jasper");


Por:


JasperReport reporte = (JasperReport) JRLoader.loadObject(getServletContext().getRealPath("WEB-INF/reporte2.jasper"));        


Además, como ahora el reporte no será generado en un archivo, sino que será enviado directamente al cliente, debemos cambiar la línea:


exporter.setParameter(JRExporterParameter.OUTPUT_FILE, new java.io.File("reporte2PDF_2.pdf")); 


Por:


exporter.setParameter(JRExporterParameter.OUTPUT_STREAM, out);


En realidad la línea anterior es la que hace el truco. El flujo del reporte es dirigido al flujo que es regresado al cliente, logrando así que este vea el reporte directamente en su navegador.

El resto del código de “processRequest” es igual al que ya hemos visto en los tutoriales anteriores, y queda de la siguiente forma:


protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
   response.setContentType("application/pdf");

   ServletOutputStream out = response.getOutputStream();

   List listaPariticipantes = new ArrayList();

   for (int i = 1; i <= 10; i++)
   {
      Participante p = new Participante(i, "Particpante " + i, "Usuario " + i, "Pass " + i, "Comentarios para " + i);
      p.setPuntos(i);
      listaPariticipantes.add(p);
   }

   try
   {
      JasperReport reporte = (JasperReport) JRLoader.loadObject(getServletContext().getRealPath("WEB-INF/reporte2.jasper"));

      Map parametros = new HashMap();
      parametros.put("autor", "Juan");
      parametros.put("titulo", "Reporte");

      JasperPrint jasperPrint = JasperFillManager.fillReport(reporte, parametros, new JRBeanCollectionDataSource(listaPariticipantes));

      JRExporter exporter = new JRPdfExporter();
      exporter.setParameter(JRExporterParameter.JASPER_PRINT, jasperPrint);
      exporter.setParameter(JRExporterParameter.OUTPUT_STREAM, out);
      exporter.exportReport();
   }
   catch (Exception e)
   {
      e.printStackTrace();
   }
}


Si ahora ejecutamos el proyecto (den algún tiempo para que el Tomcat se levante, hasta que vean el mensaje "INFO: Server startup in ***** ms" en el panel del salida) e ingresamos a la siguiente dirección:


http://localhost:8080/ReporteWeb/ServletReporte


Veremos que el reporte generado se muestra directamente en la pantalla del navegador:



Aunque esto es muy útil tal y como esta, algunas veces es mejor que el usuario decida si quiere guardar este reporte en su máquina o abrirlo con alguna otra aplicación en vez de usar directamente el navegador. Para poder lograr esto es necesario agregar una cabera llamada "Content-Disposition" a la respuesta. Esta cabecera tiene como valor "attachment; filename=" y el nombre que queremos darle a nuestro reporte. Para establecer esta cabecera usamos el método "setHeader" del objeto "HttpServletResponse", de la siguiente forma:


response.setHeader("Content-Disposition","attachment; filename=\"reporte.pdf\";");


Ahora compilamos y ejecutamos nuevamente nuestra aplicación y veremos que en esta ocasión se nos pregunta qué acción queremos realizar: abrir el reporte con alguna aplicación que seleccionemos, o guardarlo en nuestra máquina (tal vez será necesario que detengan y vuelvan a reiniciar su servidor para que tome los cambios):



Para evitar que nuestro navegador guarde el archivo en cache podemos establecer, como se indica en esta página del sitio jGuru, las siguientes cabeceras:


response.setHeader("Cache-Control","no-cache"); 
response.setHeader("Pragma","no-cache");  
response.setDateHeader ("Expires", 0);


Aunque el darle al usuario la oportunidad de abrir o guardar el archivo es también bastante útil, algunas veces nos gustaría poder mostrar este archivo como parte de la información de nuestra página JSP embebiéndolo en la misma. Hacer esto es bastante sencillo, como se menciona en este artículo de flash colony. Solo hay que agregar la etiqueta <object>, con algunos atributos de configuración, en nuestra página JSP.

Si agregamos la siguiente etiqueta en nuestro archivo "index.jsp":


<object type="application/pdf" data="http://localhost:8080/ReporteWeb/ServletReporte" width="500" height="650"></object>


e ingresamos a la dirección:


http://localhost:8080/ReporteWeb/


Obtendremos el siguiente resultado:



Lo cual también puede sernos de mucha utilidad ^-^.

El código del método “processRequest” queda finalmente de la siguiente forma:


protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
    response.setHeader("Content-Disposition", "attachment; filename=\"reporte.pdf\";");
    response.setHeader("Cache-Control", "no-cache");
    response.setHeader("Pragma", "no-cache");
    response.setDateHeader("Expires", 0);

    response.setContentType("application/pdf");
        
    ServletOutputStream out = response.getOutputStream();

    List listaPariticipantes = new ArrayList();

    for (int i = 1; i <= 10; i++)
    {
        Participante p = new Participante(i, "Particpante " + i, "Usuario " + i, "Pass " + i, "Comentarios para " + i);
        p.setPuntos(i);
        listaPariticipantes.add(p);
    }

    try
    {
        JasperReport reporte = (JasperReport) JRLoader.loadObject(getServletContext().getRealPath("WEB-INF/reporte2.jasper"));

        Map parametros = new HashMap();
        parametros.put("autor", "Juan");
        parametros.put("titulo", "Reporte");

        JasperPrint jasperPrint = JasperFillManager.fillReport(reporte, parametros, new JRBeanCollectionDataSource(listaPariticipantes));

        JRExporter exporter = new JRPdfExporter();
        exporter.setParameter(JRExporterParameter.JASPER_PRINT, jasperPrint);
        exporter.setParameter(JRExporterParameter.OUTPUT_STREAM, out);
        exporter.exportReport();
    }
    catch (Exception e)
        {
        e.printStackTrace();
    }
}


Y el código del archivo “index.jsp” queda así:


<%@page contentType="text/html" pageEncoding="UTF-8">
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN""http://www.w3.org/TR/html4/loose.dtd">

<html>
    <head>
        <meta equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>JSP Page</title>
    </head>    
    <body>   
        <h1>Este es el archivo</h1>

        <object type="application/pdf" data="http://localhost:8080/ReporteWeb/ServletReporte" width="500" height="650"></object>

    </body>
<html>


Bien, este es el final de este cuarto tutorial sobre el uso de JasperReports. Espero que les sea de utilidad. No olviden dejar sus dudas, comentarios y sugerencias.


Saludos.

Descarga los archivos de este tutorial desde aquí:

Entradas Relacionadas: