Gerar relatórios é um processo comum a quase todos os tipos de sistemas, desde simples a complexos. Relatórios são artefatos finais que podem prover ao usuário/cliente uma forma de saber se as informações que estão sendo alimentadas no sistema estão corretas, ou mesmo se o sistema está realizando os procedimentos adequados.

As vezes a maior dificuldade em gerar relatórios na Web não é sua construção, mas o processo para que este seja mostrado ao usuário da forma mais usual possível.

Neste artigo iremos construir classes que serão responsáveis por tornar a geração do nosso relatório o mais simples possível, isso significa que ao criar relatórios não deveremos nos preocupar com a forma que ele será mostrado ao usuário, mas sim se seu conteúdo está correto.

Usaremos uma das bibliotecas mais conhecidas para geração de relatórios, o JasperReport. Esta é inteiramente escrita em Java e pode usar dados provenientes de quase todo tipo de datasource (fonte de dados) que você imaginar. Isso faz dele um dos melhores, se não o melhor, engine de relatórios open source para Java.

Vamos começar criando uma classe Helper chamada ReportHelper, conforme mostra a Listagem 1, que é responsável por realizar o processo principal, ou seja, gerar o relatório e enviar ao navegador.

Listagem 1. ReportHelper


  1 import java.io.File;
 2 import java.io.FileOutputStream;
 3 import java.io.IOException;
 4 import java.text.SimpleDateFormat;
 5 import java.util.Date;
 6 import java.util.HashMap;
 7 import java.util.List;
 8 import java.util.Map;
 9 
 10 import javax.faces.context.FacesContext;
 11 import javax.servlet.http.HttpServletRequest;
 12 
 13 import net.sf.jasperreports.engine.JREmptyDataSource;
 14 import net.sf.jasperreports.engine.JRException;
 15 import net.sf.jasperreports.engine.JRExporter;
 16 import net.sf.jasperreports.engine.JRExporterParameter;
 17 import net.sf.jasperreports.engine.JasperFillManager;
 18 import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource;
 19 import net.sf.jasperreports.engine.export.JRPdfExporter;
 20 import net.sf.jasperreports.engine.export.JRXlsExporter;
 21 
 22 import org.primefaces.context.RequestContext;
 23 import org.springframework.beans.factory.annotation.Autowired;
 24 import org.springframework.stereotype.Component;
 25 
 26 @Component(value = "reportHelper")
 27 public class ReportHelper {
 28 
 29 public enum FORMATO_RELATORIO{
 30 XLS, PDF;
 31 }
 32 
 33 private String gerarIdReport() {
 34 SimpleDateFormat simpleDateFormat = new SimpleDateFormat(
 35 "ddMMyyyyhhmmss");
 36 String id = simpleDateFormat.format(new Date());
 37 
 38 return id;
 39 }
 40 
 41 /**
 42 * Retorna caminho onde os relatórios (.jasper e .jrxml e tmps) ficam
 43 * armazenados
 44 * */
 45 private String reportSourcePath() {
 46 
 47 return FacesContext.getCurrentInstance().getExternalContext()
 48 .getRealPath("/WEB-INF/report/")
 49 + "/";
 50 }
 51 
 52 /**
 53 * Retorna o caminho onde os relatórios finais ficam no servidor (ex: .PDF)
 54 * */
 55 private String reportFile() {
 56 return FacesContext.getCurrentInstance().getExternalContext()
 57 .getInitParameter("reportDirectory");
 58 }
 59 
 60 /**
 61 * Abrir Janela com Arquivo (PDF, XLS, TXT e etc)
 62 * */
 63 public void abrirPoupUp(String fileName) {
 64 abrirPoupUp(fileName, null);
 65 }
 66 
 67 public void abrirPoupUp(String fileName, String nomeJanela){
 68 HttpServletRequest req = (HttpServletRequest) FacesContext
 69 .getCurrentInstance().getExternalContext().getRequest();
 70 String contextPath = req.getContextPath().replace("/", "");
 71 if (nomeJanela == null){
 72 nomeJanela = "Relatório";
 73 }
 74 
 75 RequestContext.getCurrentInstance().execute(
 76 "window.open("/" + contextPath + "/report/" + fileName
 77 + "",'"+nomeJanela+"','width=800px,height=800px')");
 78 }
 79 
 80 /**
 81 * Gera o relatório e retorna o nome do relatório gerado
 82 * */
 83 private String gerarRelatorio(String relatorio,
 84 List beans, Map<String, Object> params, FORMATO_RELATORIO formatoExportacao) {
 85 try {
 86 if (params == null) {
 87 params = new HashMap<String, Object>();
 88 }
 89 
 90 
 91 JRBeanCollectionDataSource beanCollectionDataSource = new JRBeanCollectionDataSource(
 92 beans);
 93 
 94 String relatorioFormated = relatorio.endsWith(".jasper") ? relatorio
 95 : (new StringBuilder()).append(relatorio).append(".jasper")
 96 .toString();
 97 net.sf.jasperreports.engine.JasperPrint jasperPrint = null;
 98 
 99 if (beans != null && beans.size() > 0) {
 100 jasperPrint = JasperFillManager
 101 .fillReport(reportSourcePath() + relatorioFormated, params,
 102 beanCollectionDataSource);
 103 } else {
 104 jasperPrint = JasperFillManager
 105 .fillReport(reportSourcePath() + relatorioFormated, params, new JREmptyDataSource());
 106 }
 107 
 108 
 109 StringBuilder nomeRelatorio = new StringBuilder();
 110 nomeRelatorio.append(reportFile() + relatorio + "_"
 111 + gerarIdReport());
 112 
 113 
 114 JRExporter exporter = null;
 115 
 116 if (formatoExportacao.equals(FORMATO_RELATORIO.PDF)){
 117 exporter = new JRPdfExporter();
 118 nomeRelatorio.append(".pdf");
 119 }else if (formatoExportacao.equals(FORMATO_RELATORIO.XLS)){
 120 exporter = new JRXlsExporter(); 
 121 nomeRelatorio.append(".xls");
 122 }
 123 
 124 File fTarget = new File(nomeRelatorio.toString());
 125 
 126 exporter.setParameter(JRExporterParameter.JASPER_PRINT, jasperPrint);
 127 exporter.setParameter(JRExporterParameter.OUTPUT_STREAM,
 128 new FileOutputStream(fTarget.getAbsolutePath()));
 129 exporter.exportReport();
 130 
 131 return fTarget.getName();
 132 } catch (JRException e) {
 133 e.printStackTrace();
 134 return null;
 135 } catch (IOException e) {
 136 // TODO Auto-generated catch block
 137 e.printStackTrace();
 138 return null;
 139 }
 140 }
 141 
 142 /**
 143 * Geração de Relatório em XLS
 144 */
 145 public String gerarRelatorioXLS(String relatorio,
 146 List beans, Map<String, Object> params){
 147 
 148 return gerarRelatorio(relatorio, beans, params, FORMATO_RELATORIO.XLS);
 149 
 150 }
 151 
 152 /**
 153 * Padrão para Arquivos PDF 
 154 * */
 155 public String gerarRelatorio(String relatorio,
 156 List beans, Map<String, Object> params){
 157 
 158 return gerarRelatorio(relatorio, beans, params, FORMATO_RELATORIO.PDF);
 159 
 160 }
 161 
 162 }

Primeiramente é importante salientar alguns pontos importantes da nossa classe antes de explicar os blocos de código-fonte importantes:

  • Utilizamos o Primefaces apenas para poder fazer chamada a uma função em Javascript, por isso fizemos o import da classe org.primefaces.context.RequestContext;
  • O Spring Framework nos ajudará a realizar a injeção de dependências em outros locais da nossa classe ReportHelper, mas você pode ficar à vontade para usar algum outro framework ou mesmo nem usá-lo. O nosso foco não é mostrar conceitos e práticos sobre Injeção de dependência então fique à vontade para usar algum outro se sentir necessidade.
Além disso, vamos entender como o código se comporta:
  • Linha 26-27: Temos aqui a declaração da classe em conjunto com o nome que será usado pela injeção de dependências do Spring, ou seja, se quisermos fazer referência a nossa classe através do Spring devemos chamá-la de “reportHelper”;
  • Linha 29-31: Em nosso caso vamos trabalhar apenas com dois formatos possíveis: PDF e XLS, que são os mais utilizados. Criamos um ENUM para evitar que outros tipos, que não sejam esses, possam ser passados como parâmetro para nosso método de geração de relatórios;
  • Linha 33-39: Nosso relatório precisa ter um nome único no servidor de aplicação, isso porque o mesmo relatório pode ser gerado várias vezes e por usuários diferentes. Sendo assim garantimos que nunca um relatório irá sobrescrever o outro. Claro que isso tem um impacto no futuro, pois haverá muitos relatórios armazenados no servidor e eles deverão ser limpos de tempos em tempos, mas esse é o menor dos problemas. A nossa solução para criar uma forma única de numeração e ainda saber quando o relatório foi gerado é usar o SimpleDateFormat com o padrão ddMMyyyyhhmmss, assim temos dia-mês-ano-hora-minuto-segundo em cada arquivo. Raramente serão gerados dois arquivos no mesmo segundo, e mesmo se isto ocorrer será um caso muito peculiar e de pouca importância agora;
  • Linha 45-50: E onde ficam nossos relatórios? Optamos por usar a pasta WEB-INF/report/, é neste diretório que todos os nossos arquivos “.jasper” ficarão. Para que o Java consiga mapear este diretório usamos o método getRealPath() do JSF, assim ele consegue capturar o caminho real do diretório que passamos dentro da estrutura de todo o projeto. É importante que trabalhemos assim em vez de digitar o caminho completo, pois se amanhã precisarmos mudar o local onde o WEB-INF/report/ fica no projeto, o JSF automaticamente achará ele e isso será transparente para nosso código;
  • Linha 55-58: Através do FacesContext conseguimos capturar o diretório em que os arquivos gerados ficarão. O método getInitParameter() retornar o valor do parâmetro que está configurado no nosso web.xml como padrão. Em nosso caso precisamos do “reportDirectory”;
  • Linha 75-79: Com o uso do Primefaces conseguimos fazer chamados ao JavaScript dentro da nossa classe Java. Para abrir um popup usamos o RequestContext passando um comando JavaScript de abertura de popup, o “window.open”. O contextpath é o caminho da nossa aplicação, ex: localhost:8080/minhaaplicacao, ele é essencial para que o window.open consiga localizar onde encontra-se nosso relatório;
  • Linha 91: Já dentro do método gerarRelatorio() criamos um objeto do tipo JRBeanCollectionDataSource para receber o datasource que nosso relatório irá ler, em nosso caso temos uma lista de beans, que pode ser qualquer coisa que desejarmos. Ex: Uma lista de carros, pessoas e etc;
  • Linha 94: Precisamos certificar que o arquivo de relatório tenha o final ".jasper" para que a geração ocorra sem erros. Para isso usamos um if-ternário afim de adicionar o final ".jasper" caso ele não exista;
  • Linha 99-106: Caso haja algum registro na lista passada então podemos preencher nosso JasperFillManager, por outro lado, caso não haja nenhum registro então criamos o relatório com o JREmptyDataSource();
  • Linha 109: Criamos o nome do arquivo que será gerado, seja PDF ou EXCEL, usando nosso método gerarIdReport() que garantirá que o nome não irá se repetir;
  • Linha 116-122: Caso seja PDF então usamos o JRPdfExporter() e adicionamos o ".pdf" ao final do nome gerado, caso contrário usamos o JRXlsExporter() e adicionamos o ".xls" ao final do arquivo;
  • Linha 126-129: O nosso JRExporter gravará os parâmetros que utilizaremos para exportar o relatório para PDF ou EXCEL. Usamos dois parâmetros, onde o primeiro é um objeto jasperPrint que é exatamente o objeto do nosso relatório que preenchemos com o JasperFillManager e o segundo é o destino para onde nosso relatório irá, em nosso caso enviamos para um arquivo. Por último usamos o exporterReport() para gerar o relatório no arquivo a qual apontamos anteriormente;
  • Linha 131: Nosso método retorna o nome do relatório gerado para que seja possível que o usuário faça o download do mesmo.

Os outros métodos de geração de relatório são apenas sobrecargas do método principal, para possibilitar a chamada ao mesmo de várias formas possíveis.

Temos pronta nossa classe Helper que irá fazer a parte mais difícil, que é gerar o relatório, vamos agora ver a nossa classe que irá tratar do front-end, ou seja, será o Backing Bean responsável por cuidar dos filtros para gerar nosso relatório correto. Observe a Listagem 2.

Listagem 2. ReportItem


 1 import javax.faces.application.FacesMessage;
 2 import javax.faces.context.FacesContext;
 3 
 4 import br.com.projetoteste.helper.ReportHelper.FORMATO_RELATORIO;
 5 
 6 public abstract class ReportItem {
 7 
 8 private String nome;
 9 private String descricao;
 10 private String jasperName; 
 11 
 12 public ReportItem(String nome, String descricao, String jasperName) {
 13 this.nome = nome;
 14 this.descricao = descricao;
 15 this.jasperName = jasperName;
 16 }
 17 
 18 public ReportItem(String descricao) {
 19 this.nome = this.getClass().getSimpleName();
 20 this.descricao = descricao;
 21 this. jasperName = this.getClass().getSimpleName().toLowerCase();
 22 }
 23 
 24 public abstract void gerarRelatorio(FORMATO_RELATORIO formato);
 25 
 26 public String getNome() {
 27 return nome;
 28 }
 29 
 30 public void setNome(String nome) {
 31 this.nome = nome;
 32 }
 33 
 34 public String getDescricao() {
 35 return descricao;
 36 }
 37 
 38 public void setDescricao(String descricao) {
 39 this.descricao = descricao;
 40 }
 41 
 42 public String getJasperame() {
 43 return jasperName;
 44 }
 45 
 46 public void setJasperName(String jasperName) {
 47 this. jasperName = jasperName;
 48 }
 49 
 50 }

A ideia de criar uma classe ReportItem é para que todos os Backing Beans de relatórios possam estender dela, criando assim uma padronização para a geração de relatórios. Ela é simples, mas eficaz. Vejamos:

  • Linha 6: Ela é abstrata pois ninguém poderá criar uma instância desta, apenas estender da mesma;
  • Linha 8-10: Precisamos de três propriedades, nome, descrição e jasperName. Onde nome é o nome do relatório que será mostrado no XHTML, descrição é um resumo sobre o que o relatório faz, geralmente usando no "title" dos atributos do HTML e por último o jasperName que é o nome do arquivo jasper para geração do relatório;
  • Linha 24: É obrigatório que todo Backing Bean que use o nosso ReportItem tenha o método gerarRelatorio(), pois assim saberemos que todo gerarRelatorio() recebe um enum FORMATO_RELATORIO, que até agora pode ser PDF ou XLS.

Vamos ver agora como usar o nosso ReportItem em conjunto com o ReportHelper. Digamos que nossos relatórios tenham a seguinte nomenclatura: R001, R002 e assim por diante. Então vamos criar um Backing Bean chamado R001, que estende de ReportItem. Isso já caracteriza que nosso Backing Bean é um gerenciador de algum relatório específico, como mostra a Listagem 3.

Listagem 3. R001 extends ReportItem


  1 import java.text.SimpleDateFormat;
 2 import java.util.Date;
 3 import java.util.HashMap;
 4 import java.util.List;
 5 import java.util.Map;
 6 
 7 import javax.annotation.PostConstruct;
 8 import javax.faces.bean.ManagedBean;
 9 import javax.faces.bean.ManagedProperty;
 10 import javax.faces.bean.ViewScoped;
 11 
 12 
 13 import br.com.projetoteste.helper.ReportHelper;
 14 import br.com.projetoteste.helper.ReportHelper.FORMATO_RELATORIO;
 15 import br.com.projetoteste.report.bean.ReportItem;
 16 
 17 @ManagedBean(name = "r001")
 18 @ViewScoped
 19 public class R001 extends ReportItem {
 20 
 21 
 22 @ManagedProperty(value = "#{reportHelper}")
 23 private ReportHelper reportHelper;
 24 
 25 private Date dataInicial, dataFinal;
 26 
 27 @PostConstruct
 28 public void init() {
 29 dataInicial = new Date();
 30 dataFinal = new Date();
 31 }
 32 
 33 public R001() {
 34 super("Listagem de Funcionarios da Empresa");
 35 }
 36 
 37 @Override
 38 public void gerarRelatorio(FORMATO_RELATORIO formato) {
 39 List<Funcionario> funcionarios = (List<Funcionario>) MeuServico.getListaFuncionario();
 40 Map<String, Object> params = new HashMap<String, Object>();
 41 params.put("titulo", getNome());
 42 
 43 SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
 44 params.put("dataInicial", sdf.format(dataInicial));
 45 params.put("dataFinal", sdf.format(dataFinal));
 46 
 47 String fileName = reportHelper.gerarRelatorio("r001", funcionarios, params);
 48 reportHelper.abrirPoupUp(fileName);
 49 
 50 }
 51 
 52 public void gerarXLS() {
 53 gerarRelatorio(FORMATO_RELATORIO.XLS);
 54 }
 55 
 56 public void gerarPDF() {
 57 gerarRelatorio(FORMATO_RELATORIO.PDF);
 58 }
 59 
 60 public ReportHelper getReportHelper() {
 61 return reportHelper;
 62 }
 63 
 64 public void setReportHelper(ReportHelper reportHelper) {
 65 this.reportHelper = reportHelper;
 66 }
 67 
 68 public Date getDataInicial() {
 69 return dataInicial;
 70 }
 71 
 72 public void setDataInicial(Date dataInicial) {
 73 this.dataInicial = dataInicial;
 74 }
 75 
 76 public Date getDataFinal() {
 77 return dataFinal;
 78 }
 79 
 80 public void setDataFinal(Date dataFinal) {
 81 this.dataFinal = dataFinal;
 82 }
 83 
 84 }

Vamos entender o código apresentado:

  • Linha 17: Nomeamos o nosso backing bean com o valor "r001", pois será este o valor que usaremos no XHTML;
  • Linha 22: Fizemos a injeção do reportHelper através do ManagedProperty;
  • Linha 25: As duas propriedades para que o usuário diga qual período ele deseja emitir o relatório;
  • Linha 28: Inicializamos as duas datas como sendo a data atual;
  • Linha 34: Nomeamos o relatório como “Listagem de Funcionários da Empresa” esse valor poderá ser usamos tanto em parâmetros do relatório como no próprio XHTML para mostrar ao usuário, veremos como logo em seguida;
  • Linha 39: Aqui é onde realmente a “mágica” acontece, no método gerarRelatorio(), logo no início já capturamos a lista de funcionarios e colocamos em um List<Funcionario>. Abstraia a parte de como isso é feito, o importante é saber que a lista de funcionários foi capturada de algum local, seja um arquivo, banco, xml ou etc.;
  • Linha 40-45: Criamos um Map para armazenar os parâmetros que nosso relatório precisará. Adicionamos 3 parâmetros, são eles: título do relatório, data inicial e data final;
  • Linha 47: Finalmente usamos o reportHelper.gerarRelatorio() onde passamos o nome do arquivo "r001", a lista de beans e os parâmetros que serão usados. Lembrando que poderíamos mudar o "r001" para algo mais genérico como "getJaspername()";
  • Linha 48: Usamos ainda o reportHelper para abrir o popup com o relatório que foi gerado anteriormente.

Agora você pode utilizar o backing bean em qualquer XHTML, fazendo chamada sempre ao actionListener gerarRelatorio().

Com base neste artigo você terá uma estrutura genérica para geração de relatórios em JSF, abstraindo toda a complexidade para a geração do mesmo. A única coisa que você precisará se preocupar é com o filtro do seu relatório e a criação propriamente dita do mesmo.

Espero que tenham gostado e até a próxima.