Neste artigo veremos como construir uma classe genérica o suficiente para gerar relatórios baseados no JasperReport em Java. Nossa classe será capaz de gerar um relatório em formato PDF através dos parâmetros passados, tornando a tarefa simples e eficaz em qualquer parte do sistema.

Trabalharemos neste artigo com o ambiente totalmente web e partiremos do princípio que estamos trabalhando com um servidor que não possui um ambiente gráfico, sendo assim, não podemos contar com recursos como o JasperView. A nossa lógica será gerar o PDF no lado do servidor (Server-side) e apenas disponibilizar o mesmo para que o usuário possa baixar através do seu browser de preferência.

Usaremos JSF 2.0 e Primefaces para disponibilizar os recursos no lado do cliente e os relatórios já estão prontos, pois não é foco deste artigo mostrar como construí-los. Independente da ferramenta que você utilizar, o resultado final sempre será um “.jasper” que é o arquivo lido pelo JasperReport. No iReport, por exemplo, é gerado um “.jrxml” que contém todo o layout para que o iReport leia, e quando compilado, torna-se um “.jasper”. É muito importante que a diferença entre o JasperReport e as ferramentas que criam relatórios fiquem bem definidas.

Importando as librarys necessárias

Como gerenciador de dependências estamos utilizando o Maven. Vamos mostrar quais as dependências necessárias para funcionamento correto do JasperReport e para isso elas devem estar no seu arquivo pom.xml. Observe a Listagem 1.


<!-- JASPER REPORT IMPORTS -->

   <dependency>
          <groupId>net.sourceforge.barbecue</groupId>
          <artifactId>barbecue</artifactId>
          <version>1.5-beta1</version>
   </dependency>


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

   <dependency>
          <groupId>net.sf.jasperreports</groupId>
          <artifactId>jasperreports-fonts</artifactId>
          <version>4.0.0</version>
   </dependency>

   <dependency>
          <groupId>xml-apis</groupId>
          <artifactId>xml-apis</artifactId>
          <version>1.4.01</version>
   </dependency>
   <dependency>
          <groupId>xerces</groupId>
          <artifactId>xercesImpl</artifactId>
          <version>2.10.0</version>
   </dependency>

   <!-- FIM JASPER RESPORT IMPORTS -->
Listagem 1. Dependências inclusas no pom.xml

Construção da classe ReportHelper

Após os imports necessários serem adicionados no nosso pom.xml, iremos começar a construir nossa classe genérica, que chamaremos de ReportHelper, pelo fato de ser um auxiliar na geração de nosso relatório. Precisamos antes entender alguns pontos importantes:

  1. Em nosso caso, os relatórios (.jasper) ficarão sempre dentro do diretório “WEB-INF/report”. Será esse o caminho padrão que utilizaremos ao gerar um relatório.
  2. Em nosso arquivo web.xml criamos um “context-param” que irá armazenar o diretório onde os relatórios gerados serão armazenados, ou seja, onde os nossos “.pdf” serão armazenados para download após geração do mesmo. Veja como ficou nosso web.xml na Listagem 2.
    
      <context-param>
        <param-name>reportDirectory</param-name>
        <param-value>${user.home}/meuprojeto/report/</param-value>
      </context-param>
    Listagem 2. Context-param no web.xml

    Em nosso caso utilizamos o ${user.home}, mas fique a vontade para mudar o caminho se sentir necessidade.

  3. Toda vez que um relatório for gerado em PDF, optamos por criar um arquivo com um nome diferente, ex: pdf1,pdf2 e assim por diante. Desta forma, mantemos um histórico de tudo que foi gerado para futuras auditorias. Poderíamos, por exemplo, gerar um arquivo genérico chamado “report.pdf” e toda vez que um novo relatório for gerado ele irá sobrescrever o antigo, mas como já dissemos, queremos manter um histórico de relatórios gerados, mesmo que isso exija mais espaço em disco do nosso servidor. Para criamos um método bem simples que retorna um ID único para nomear nosso PDF, usaremos o código da Listagem 3.
    
      private static String gerarIdReport() {
         SimpleDateFormat simpleDateFormat = new SimpleDateFormat(
                 "ddMMyyyyhhmmss");
         String id = simpleDateFormat.format(new Date());
        
         return id;
      }
    Listagem 3. Gerador de ID para nosso PDF

Perceba que nosso método retorna um ID baseado na data,hora,minuto e segundo atual. Garantimos assim que o nome dos relatórios não irão se repetir.

Vamos ver agora o método responsável por gerar o relatório (salvando no diretório definido no context-param do web.xml). Toda nossa classe da Listagem 4 está comentada para ajudar a entender o funcionamento da mesma.


/*
 * Explicação dos parâmetros
 * String relatorio = Nome do relatório em formato .jasper, 
 * que será encontrado do diretório WEB-INF/report
 * List<? extends AbstractBean> beans = Lista de Beans que serão mostrados 
 * no relatório, ou seja, todo filtro já foi feito
 * pela nossa aplicação, apenas mostraremos os beans que foram passados pelo relatório.
 * Map<String, Object> params = Parâmetros que serão usados em nosso relatório
 * Usuario usuario = Usuário que gerou o relatório, assim podemos imprimir 
 * no relatório que foi o responsável por gerar este relatório
 * boolean showHeaderAndFooter = Em nosso relatório temos a opção de mostrar
 * ou não o cabeçalho e o rodapé. Quando o usuário
 * precisar aproveitar todo o espaço do papel e desejar remover o cabeçalho
 * e rodapé que contém informações sobre a empresa,
 * ele deverá passar o valor "false" para este parâmetro.
 * */
public static String gerarRelatorio(String relatorio,
       List<? extends AbstractBean> beans, Map<String, Object> params, 
       Usuario usuario, boolean showHeaderAndFooter) {
   try {

       //Capturamos o valor do context-param reportDirectory, 
      //que está no web.xml
       String reportExportPath = FacesContext.getCurrentInstance().getExternalContext()
             .getInitParameter("reportDirectory");

       //Capturamos o caminho real de WEB-INF/report, para que possamos 
       //ler o relatório .jasper
       String pathRelatorio = FacesContext.getCurrentInstance()
             .getExternalContext().getRealPath("/WEB-INF/report/")
             + "/";

       if (params == null) {
         params = new HashMap<String, Object>();
       }
       
       //Temos aqui uma lista de parâmetros que utilizamos em nosso relatório, 
       //se você tiver mais parâmetros
       //basta adicioná-los nesta seção
       params.put("endereco", Codigos.ENDERECO);
       params.put("razaoSocial", Codigos.RAZAO_SOCIAL);
       params.put("cidade", Codigos.CIDADE);
       params.put("estado", Codigos.ESTADO);
       params.put("cep", Codigos.CEP);
       params.put("pathLogomarca", pathRelatorio + "logo.jpg");
       params.put("pathSubreport", pathRelatorio);
       params.put("usuario", usuario.getLogin());
       params.put("base", usuario.getBase().getPessoaJuridica().getNome());
       params.put("showHeaderAndFooter", showHeaderAndFooter);
       
       //Criamos nosso DataSource baseado nos beans passados via parâmetro
       JRBeanCollectionDataSource beanCollectionDataSource = 
         new JRBeanCollectionDataSource(beans);

       //Verificamos que o nome do relatório passado possui ou 
       //não o .jasper no fim, se não tiver
       //adicionamos o .jasper ao fim do nome, garantindo uma leitura correta.
       String relatorioFormated = relatorio.endsWith(".jasper") ? relatorio
             : (new StringBuilder()).append(relatorio).append(".jasper")
                     .toString();

       //Iniciamos a geração do relatório através do método 
       //"fillReport" passando o nome do relatório .jasper,
       //os parâmetros e a coleção de beans, o restante é por conta do JasperReport
       net.sf.jasperreports.engine.JasperPrint jasperPrint = JasperFillManager
             .fillReport(pathRelatorio + relatorioFormated, params,
                     beanCollectionDataSource);

       //Preparamos o novo nome do nosso PDF
       String pdfFileName = reportExportPath + relatorio +"_"+ gerarIdReport() + ".pdf";
       
       //Começamos a exportar o relatório armazeando na variável jasperPrint
       // para o formato PDF
       JRExporter exporter = new JRPdfExporter();
       exporter.setParameter(JRExporterParameter.JASPER_PRINT, jasperPrint);
       exporter.setParameter(JRExporterParameter.OUTPUT_STREAM, 
        new FileOutputStream(pdfFileName));
       exporter.exportReport();

       return pdfFileName;
   } catch (JRException e) {
       e.printStackTrace();
       return null;
   } catch (IOException e) {
       // TODO Auto-generated catch block
       e.printStackTrace();
       return null;
   }
}
Listagem 4. Método para gerar relatório

O método acima retorna uma String que contém nome do PDF gerado, assim podemos disponibilizá-lo para download em nosso XHTML através do JSF. Utilizando o JSF + Primefaces podemos disponibilizar o download de nosso relatório usando o recurso da Listagem 5.


<p:commandButton id="downloadReport" value="Download"
     disabled="#{geradorRelatorioMB.reportDownload == null}"
     ajax="false" icon="ui-icon-arrowthichk-s" immediate="true">
     <p:fileDownload value="#{geradorRelatorioMB.reportDownload}" />
</p:commandButton>
Listagem 5. Primefaces fileDownload

Através de um ManagedBean chamado “GeradorRelatorioMB” conseguimos capturar o relatório. Veja o getReportDownload() da Listagem 6.


public StreamedContent getReportDownload() {
     if (relatorioGeradoPath == null)
         return null;
     else
         return ReportHelper.convertStringToStreamedContent(relatorioGeradoPath);
  }
Listagem 6. Método getReportDownload()

Perceba que através do relatório gerado em formato PDF, que está no caminho “relatorioGeradoPath”, devemos converter para um “StreamedContent” para que o componente fileDownload consiga “ler”. Veja na Listagem 7 nosso método de conversão.


public static StreamedContent convertStringToStreamedContent(String pathRelatorio) {
     InputStream inputStream = null;
     try {
         File file = new File(pathRelatorio);           
         inputStream = new FileInputStream(file);

         return new DefaultStreamedContent(inputStream, "application/pdf", file.getName());

     } catch (FileNotFoundException e) {
         // TODO Auto-generated catch block
         e.printStackTrace();
         return null;
     }
  }
Listagem 7. Conversor de String para StreamedContent

Vamos agora entender como o mecanismo funciona, passo a passo:

  1. O usuário solicita a geração do relatório através do método “gerarRelatório()” mostrado na Listagem 4.
  2. O relatório é gerado em formato PDF e armazenado no diretório especificado através do context-param do web.xml. Suponha que seja algo como “protocolo16022014232545.pdf”
  3. Agora precisamos disponibilizar para download o arquivo acima, e para isso usamos o componente fileDownload do Primefaces. O problema é que este componente só aceita um objeto do tipo StreamedContent, sendo assim, criamos um método chamado “convertStringToStreamedContent” que recebe o caminho do PDF no servidor e retorna um StreamedContent.

Veja na Listagem 8 um exemplo do uso do método gerarRelatorio() em um ManagedBean.


public void gerarProtocolos() {
   try {
       List<Protocolo> protocolos = new ArrayList<Protocolo>();

       for (int i = 0; i < 100; i++) {         
         protocolos.add(buscarProtocoloNaBase(i));
       }

       relatorioGeradoPath = ReportHelper.gerarRelatorio("protocolo", protocolos,
             new NamedParams("titulo", "Protocolo AR10",
             usuarioLogadoMB.getUsuarioLogado(), showHeaderAndFooter);
   } catch (BOException e) {
       addErrorMessage(e.getMessage());
       FacesContext.getCurrentInstance().validationFailed();
   }
}
Listagem 8. Exemplo de uso do método gerarRelatorio()

No exemplo acima geramos um relatório e o valor retornado é salvo na variável “relatorioGeradoPath”, assim podemos usar essa variável para converter para um StreamedContent e disponibilizar através do fileDownload do primefaces.

Para você que sentiu-se um pouco perdido com todas listagens para construção de um Helper, na Listagem 9 disponibilizamos o Helper completo após todas as explicações acima.


package br.com.meuprojeto.helper;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.faces.context.FacesContext;

import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JRExporter;
import net.sf.jasperreports.engine.JRExporterParameter;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource;
import net.sf.jasperreports.engine.export.JRPdfExporter;

import org.primefaces.model.DefaultStreamedContent;
import org.primefaces.model.StreamedContent;

import br.com.meuprojeto.bean.AbstractBean;
import br.com.meuprojeto.security.bean.Usuario;
import br.com.meuprojeto.util.Codigos;

public class ReportHelper {

  private static String gerarIdReport() {
       SimpleDateFormat simpleDateFormat = new SimpleDateFormat(
               "ddMMyyyyhhmmss");
       String id = simpleDateFormat.format(new Date());
       
       return id;
  }

  public static StreamedContent convertStringToStreamedContent(String pathRelatorio) {
       InputStream inputStream = null;
       try {
           File file = new File(pathRelatorio);           
           inputStream = new FileInputStream(file);

           return new DefaultStreamedContent(inputStream, "application/pdf", 
            file.getName());

       } catch (FileNotFoundException e) {
           // TODO Auto-generated catch block
           e.printStackTrace();
           return null;
       }
  }

  // Gera relatorio retornando o caminho do PDF gerado para download
  public static String gerarRelatorio(String relatorio,
           List<? extends AbstractBean> beans, Map<String, Object> params,
             Usuario usuario, boolean showHeaderAndFooter) {
       try {

           String reportExportPath = FacesContext.getCurrentInstance().getExternalContext()
                     .getInitParameter("reportDirectory");

           String pathRelatorio = FacesContext.getCurrentInstance()
                     .getExternalContext().getRealPath("/WEB-INF/report/")
                     + "/";

           if (params == null) {
                 params = new HashMap<String, Object>();
           }

           params.put("endereco", Codigos.ENDERECO);
           params.put("razaoSocial", Codigos.RAZAO_SOCIAL);
           params.put("cidade", Codigos.CIDADE);
           params.put("estado", Codigos.ESTADO);
           params.put("cep", Codigos.CEP);
           params.put("pathLogomarca", pathRelatorio + "logo.jpg");
           params.put("pathSubreport", pathRelatorio);
           params.put("usuario", usuario.getLogin());
           params.put("base", usuario.getBase().getPessoaJuridica().getNome());
           params.put("showHeaderAndFooter", showHeaderAndFooter);

           JRBeanCollectionDataSource beanCollectionDataSource = 
             new JRBeanCollectionDataSource(beans);

           String relatorioFormated = relatorio.endsWith(".jasper") ? relatorio
                     : (new StringBuilder()).append(relatorio).append(".jasper")
                             .toString();

           net.sf.jasperreports.engine.JasperPrint jasperPrint = JasperFillManager
                     .fillReport(pathRelatorio + relatorioFormated, params,
                             beanCollectionDataSource);

           String pdfFileName = reportExportPath + relatorio +"_"+ gerarIdReport() + ".pdf";

           JRExporter exporter = new JRPdfExporter();
           exporter.setParameter(JRExporterParameter.JASPER_PRINT, jasperPrint);
           exporter.setParameter(JRExporterParameter.OUTPUT_STREAM, 
              new FileOutputStream(pdfFileName));
           exporter.exportReport();

           return pdfFileName;
       } catch (JRException e) {
           e.printStackTrace();
           return null;
       } catch (IOException e) {
           // TODO Auto-generated catch block
           e.printStackTrace();
           return null;
       }
  }
}
Listagem 9. ReportHelper

Você também pode melhorar um pouco mais nossa classe dando a possibilidade de gerar não só PDF, mas outros formatos. Em nosso caso usamos apenas PDF, mas a biblioteca do JasperReport nos da possibilidade de gerar mais alguns formatos.

Um ponto importante que você pode questionar é porque o uso de um parâmetro para subReport? Em nosso caso utilizamos um cabeçalho e rodapé através de subReport, sendo assim, quando mudamos algum valor do cabeçalho (header), todos os relatórios que o utilizam também mudam, por isso que precisamos dizer explicitamente qual o caminho deste relatório (subreport). Optamos também por criar uma classe chamada “Codigos” que possuem valores padrões para nosso sistema, como é o caso das informações da empresa, mas você pode optar por salvar essas informações no banco de dados caso elas possam ser alteradas futuramente.

Com isso, podemos concluir que o uso de relatórios atualmente é algo essencial para qualquer sistema. E quando falamos em “relatório” não podemos considerar apenas um processo para realizar análise ou auditoria de informações, mas também qualquer tipo de impressão realizada no sistema, seja a impressão de uma etiqueta adesiva com código de barras ou mesmo um relatório para análise de despesas mensais da empresa.

Sendo assim nossa classe serve para gerar qualquer tipo de impressão necessária ao sistema, por exemplo, a Ficha de um funcionário em PDF, o crachá de um funcionário, relatório de entradas e saídas do caixa, fotos e assim por diante.

Vale ressaltar que estamos trabalhando em um ambiente web onde não podemos contar com interface gráfica (GUI), por isso optamos por usar o PDF que pode ser disponibilizado para download, diferente de um ambiente desktop, onde podemos usar diretamente o JasperViewer que mostrará o relatório em real-time para o usuário, sem necessidade de download.