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.