12 de maio de 2011

JasperReports 4.0.1: JavaBean datasource - abrir relatório em projeto web

Dando continuidade na postagem: JasperReports 4.0.1: Utilizando JavaBean datasource, agora vamos usar o .jasper gerado no iReport, em um projeto Java EE 6.

Começando...
Para começar, no NetBeans crie uma nova Aplicação Web com o nome de PrimeiroRelatorio (seguindo os passos já vistos em postagens anteriores)...
Feito isso, vamos criar a estrutura que usamos para gerar o .jasper (postagem do link), então em pacotes de código-fonte crie um novo pacote com o nome de primeirorelatorio e dentro dele um pacote model, dentro desse pacote crie a classe Produto (lembrando que tem que ser idêntica à aquela usada no .jar para gerar o relatório), ficando com esta estrutura:


Relembrando a classe Produto:
public class Produto {
    private int codigo;
    private String nome;
    private BigDecimal valor;

    //gerar getters e setters
}
Agora dentro do WEB-INF, crie um novo diretório com o nome de relatorios, dentro dele coloque o arquivo .jasper, ficando assim:


Caso não tenha baixado as bibliotecas do JasperReports, baixe por aqui.
De todas as bibliotecas disponibilizadas no link, para este tipo de relatório iremos usar apenas algumas delas, acabei filtrando e chegando as que realmente precisavam, então adicione ao projeto as seguintes bibliotecas:


Depois disso assim como foi criado o pacote model, precisamos de um pacote controle, para armazenar nossos beans (nesse caso, teremos apenas um bean), então dentro do pacote primeirorelatorio, crie um novo pacote com o nome de controle, ficando assim: primeirorelatorio.controle, agora aqui dentro crie  uma classe java com o nome de RelatorioBean.java, ela será assim (abaixo dela eu explico algumas coisas):
package primeirorelatorio.controle;

import java.io.File;
import java.io.FileInputStream;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.enterprise.context.RequestScoped;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.inject.Named;
import javax.servlet.ServletContext;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import net.sf.jasperreports.engine.JRDataSource;
import net.sf.jasperreports.engine.JasperRunManager;
import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource;
import primeirorelatorio.model.Produto;

/**
 *
 * @author andii
 */
@Named
@RequestScoped
public class RelatorioBean {

    public void gerarRelatorio() {
        ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext();
        ServletContext context = (ServletContext) externalContext.getContext();
        String arquivo = context.getRealPath("WEB-INF/relatorios/PrimeiroRelatorio.jasper");

        JRDataSource jrds = new JRBeanCollectionDataSource(listarProdutos());

        gerarRelatorioWeb(jrds, null, arquivo);
    }

    private void gerarRelatorioWeb(JRDataSource jrds, Map<Object, Object> parametros, String arquivo) {
        ServletOutputStream servletOutputStream = null;
        FacesContext context = FacesContext.getCurrentInstance();
        HttpServletResponse response = (HttpServletResponse) context.getExternalContext().getResponse();

        try {
            servletOutputStream = response.getOutputStream();
            JasperRunManager.runReportToPdfStream(new FileInputStream(new File(arquivo)), response.getOutputStream(), parametros, jrds);
            response.setContentType("application/pdf");
            servletOutputStream.flush();
            servletOutputStream.close();
            context.renderResponse();
            context.responseComplete();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private List<Produto> listarProdutos() {
        List<Produto> produtos = new ArrayList<Produto>();
        Produto p1 = new Produto();
        p1.setCodigo(1);
        p1.setNome("Produto 1");
        p1.setValor(new BigDecimal(1.99));
        produtos.add(p1);

        Produto p2 = new Produto();
        p2.setCodigo(2);
        p2.setNome("Produto 2");
        p2.setValor(new BigDecimal(3000.50));
        produtos.add(p2);

        Produto p3 = new Produto();
        p3.setCodigo(3);
        p3.setNome("Produto 3");
        p3.setValor(new BigDecimal(500.00));
        produtos.add(p3);
        return produtos;
    }
}

Vamos ver o que esses métodos fazem: 

public void gerarRelatorio()
Inicialmente este método será chamado pela nossa página JSF, nele nós precisamos pegar o caminho exato do nosso arquivo .jasper, que lá no começo da postagem colocamos dentro de WEB-INF/relatorios, para isso usamos o context.getRealPath.
Quanto ao jrds: por termos utilizado uma classe Java para gerar nosso .jasper, nos precisamos passar para nosso relatório um JRDataSource, que é formado por uma lista de Produto (pois o método listarProdutos(), retorna uma lista de produtos que poderia muito bem estar vindo de um banco de dados).
Ao chamar o gerarRelatorioWeb, estamos passando um parâmetro null, esse corresponderia a uma Map, que serviria para passarmos parâmetros(que vai ficar pra outra postagem) para o nosso relatório...
Agora vamos ver o que acontece no:
gerarRelatorioWeb(JRDataSource jrds, Map<Object, Object> parametros, String arquivo)
Neste método nós dependemos do response para mostrar o nosso relatório no navegador, quanto a essa parte não vou dar muitas explicações, pois sairia do nosso foco que é o JasperReports...
Bom, no final o responsável por gerar o que vai ser o nosso relatório, é essa linha aqui:
JasperRunManager.runReportToPdfStream... 
esse método não cria um arquivo .pdf fisicamente no computador, apenas gera ele como se fosse um arquivo temporário, e quem decide se quer salvar o relatório ou não é o usuário, depois disso, o response entra em campo novamente dizendo ao navegador que o que será enviado é do tipo application/pdf, cada navegador reage de uma forma quando se trata de PDF, no caso do Google Chrome, mostra nele mesmo, se não me engano o Firefox, já dá a opção de fazer download do PDF...
e por fim o servletOutputStream, é quem manda o relatório para o navegador!

Depois de entendermos como funciona essa classe, vamos alterar nossa página index.xhtml, para que ela chame o método e mostre o relatório:
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html">
    <h:head>
        <title>Facelet Title</title>
    </h:head>
    <h:body>
        <h:form>
            <h:commandButton value="Ver relatorio" actionListener="#{relatorioBean.gerarRelatorio}" onclick="this.form.target='_blank'" />
        </h:form>
    </h:body>
</html

Quanto ao onclick do commandButton, eu uso para poder abrir o relatório em outra aba do navegador. Visualmente a página index, fica assim:



Quem estiver usando o Google Chrome para fazer estes testes, terá o seguinte resultado ao clicar no botão:


E por fim... a estrutura completa do projeto é esta:



E por aqui finalizamos a postagem de hoje, logo logo posto como usar este mesmo relatório em um projeto desktop! =)

19 comentários:

  1. Estou tentando adaptar seu codigo para mostrar relatorios no meu projeto de TCC, mas quando eu clico no botão Ver relatorio ele abre a mesma página em outra aba e não mostra o relatorio. O que pode ser? Você poderia me ajudar?

    ResponderExcluir
    Respostas
    1. Boa tarde!!
      Claudinei, você conseguiu resolver o seu problema?
      estou com o mesmo problema aqui, e não sei como resolver.

      Excluir
  2. Claudinei, olha só vendo o codigo fonte para saber o que pode estar acontecendo, não tive nada parecido com isto.

    ResponderExcluir
  3. Ola, tudo bem ?
    Apenas uma dúvida, msm usando um projeto web preciso colocar o .jar no classpath ?

    ResponderExcluir
  4. Olá Naldo! Vc quer dizer colocar no classpath do windows? se for isso não precisa não, já é suficiente adicioná-las no projeto apenas.

    ResponderExcluir
  5. Olá, parabéns pela série de tutoriais, tentei este relatório e tive o seguinte erro

    Servlet.service() for servlet Faces Servlet threw exception...
    java.lang.IllegalStateException: PWC3991: getOutputStream() has already been called for this response

    Sabe o que pode ser?

    Obrigado,
    Diego

    ResponderExcluir
  6. Oi, vc sabe como nao abrir em uma nova aba caso o relatorio nao seja gerado. Eu quero exibir uma mensagem de erro, mas com target blank, acaba gerando a mensagem na nova aba.

    ResponderExcluir
  7. Olá Antonio! Para mostrar o relatório na mesma página basta tirar o onclick="this.form.target='_blank'"

    ResponderExcluir
  8. Diego Moura, quando acontecem erros dessa forma, basta vc olhar o que diz no log do servidor de aplicação, ele geralmente diz exatamente o que está acontecendo

    ResponderExcluir
  9. meu deu esse erro:

    java.lang.IllegalStateException: getOutputStream() has already been called for this response.

    o que pode ser??

    ResponderExcluir
    Respostas
    1. Os problemas podem ser diversos, vc tem que ler o que erro aparece no console do seu servidor de aplicação.

      Excluir
  10. Olá andii.brunetta
    Eu também estou com o erro "java.lang.IllegalStateException: getOutputStream() has already been called for this response".
    Pesquisando na internet parece ser porque o org.apache.catalina.connector.Response.getWriter é executado antes do getOutputStream(). Em páginas JSP, parece ser problema com espaços em branco no código java entre <% %>
    Alguma dica sobre a solução desse problema?

    ResponderExcluir
    Respostas
    1. Ketellin, qual o erro que aparece no glassfish???

      Excluir
  11. Por favor, pode me ajudar?

    O unico tutorial que consegui fazer foi o seu, deu certinho.

    Mas preciso de uma ajuda simples, acredito eu!

    Eu monto meu DataSource desta forma: JRDataSource jrds = new JRBeanCollectionDataSource(lista);

    Mas acontece que eu quero montar sem "LISTA", ou seja quero enviar:


    String nome;
    String cpf;
    String telefone;

    Sem que se repita através de uma lista.
    Eu consigo enviar no DataSource somente o meu DTO?

    Por exemplo:
    JRDataSource jrds = new JRBeanCollectionDataSource(DTOCONTATOS);

    Pode me ajudar?

    ResponderExcluir
    Respostas
    1. Olá, já pensou em passar esses valores por parametro? veja essa postagem http://javasemcafe.blogspot.com.br/2011/06/jasperreports-401-utilizando-subreports.html talvez ela possa te ajudar.

      Excluir
  12. Poxa, show, tinha perdido a esperança da resposta.
    Irei seguir este tutorial, muito obrigado!

    ResponderExcluir
  13. Obtive o seguinte erro:
    The method runReportToPdfStream(InputStream, OutputStream, Map, JRDataSource) in the type JasperRunManager is not applicable for the arguments (FileInputStream, ServletOutputStream, Map, JRDataSource)

    ResponderExcluir

Deixe seu comentário... ;)