Apresentar relatórios PDF em tela.

570 views
Skip to first unread message

Nilson Uehara

unread,
Oct 1, 2013, 1:42:54 PM10/1/13
to Grupo JSF
Olá pessoal,
estou gerando relatórios em PDF pelo Jasper Reports da seguinte forma:

ManagedBean:

    public void rpt() {
        ... gera collections, hashmap, etc...
        String reportPath=FacesContext.getCurrentInstance().getExternalContext().getRealPath(relatorio);
        JRBeanCollectionDataSource jr=new JRBeanCollectionDataSource(colecao);
        JasperPrint jasperPrint = JasperFillManager.fillReport(reportPath, parametros, jr);
        HttpServletResponse httpResponse=(HttpServletResponse)FacesContext.getCurrentInstance().getExternalContext().getResponse();
        httpResponse.setContentType("application/pdf");
        httpResponse.addHeader("Content-disposition", "inline; filename=nome.pdf");
        //httpResponse.addHeader("Content-disposition", "attachment; filename=nome.pdf");
        ServletOutputStream servletOutputStream = httpResponse.getOutputStream();
        JasperExportManager.exportReportToPdfStream(jasperPrint, servletOutputStream);
        FacesContext.getCurrentInstance().responseComplete();        
    }


Para visualização, estou criando uma janela popup da seguinte forma:

Teste.xhtml:

<p:commandButton value="Abrir relatório" actionListener="#{MB.rpt()}" ajax="false" onclick="window.open('#', 'Relatorio', 'STATUS=NO, TOOLBAR=NO, LOCATION=NO, DIRECTORIES=NO, RESISABLE=YES, SCROLLBARS=YES, width='+screen.width-20+',height='+screen.height-10,true);target='Relatorio'"/>

Funciona, mas a apresentação é muito ruim, pois eu não gostaria de ter que abrir este popup. Pensei em abrir o PDF direto num dialog do Primefaces.


Tente fazer da seguinte forma:

ManagedBean:

    private StreamedContent sc;
    public void geraPDF(){
        pdfPronto=false;
        String reportPath=FacesContext.getCurrentInstance().getExternalContext().getRealPath("/resources/relatorios/relteste.jasper");
        ByteArrayOutputStream os=new ByteArrayOutputStream();
        try{
            JRBeanCollectionDataSource jr=new JRBeanCollectionDataSource(dev);
            JasperPrint jasperPrint = JasperFillManager.fillReport(reportPath, param, jr);
            JasperExportManager.exportReportToPdfStream(jasperPrint, os);
            os.close();
        }catch(Exception e){e.printStackTrace();}
        sc=new DefaultStreamedContent(new ByteArrayInputStream(os.toByteArray()),"application/pdf","Relatorio.pdf");
        pdfPronto=true;
    }
    public StreamedContent getPDF(){
        return sc;
    }


Teste.xhtml:

    <h:form>
    <p:commandButton value="Abrir relatório" action="#{MB.geraPDF()}" icon="ui-icon-person" update=":relatorio" oncomplete="dlg.show();"/>
    </h:form>
    <p:dialog widgetVar="dlg" id="relatorio">
        <h:panelGroup rendered="#{MB.pdfPronto}">
            #{MB.PDF.contentType}
            #{MB.PDF.name}
            <p:media id="pdf2" value="#{MB.PDF}" player="pdf" width="500px" height="500px" >Não foi possível abrir o arquivo PDF</p:media>                    
        </h:panelGroup>
    </p:dialog>


Acontece que o dialog abre, as informações ContentType e Name são mostradas confirmando a geração do pdf mas o p:media fica cinza... 
Se eu deixar o ManagedBean no escopo de Sessão funciona, mas não acho que isso seja uma boa ideia, uma vez que a tendencia é a quantidade de ManagedBeans aumente bastante...

Alguém tem alguma ideia de como contornar esse problema?

Um abraço,
Nilson Uehara

Rafael Ponte

unread,
Oct 1, 2013, 3:29:11 PM10/1/13
to jav...@googlegroups.com
Nilson,

Guardar o pdf em memória jamais será uma boa ideia, rapidinho seu servidor senta!

O que você pode fazer é gerar o pdf, salva-lo com um id/hash especifico em uma área temporária (como /TOMCAT_HOME/temp por exemplo) e para depois conseguir carrega-lo via uma url especifica no próximo request.

O que acha?



2013/10/1 Nilson Uehara <nilu...@gmail.com>

--
Você está recebendo esta mensagem porque se inscreveu no grupo "javasf: JavaServer Faces Group" dos Grupos do Google.
Para cancelar a inscrição neste grupo e parar de receber seus e-mails, envie um e-mail para javasf+un...@googlegroups.com.
Visite este grupo em http://groups.google.com/group/javasf.
Para ver esta discussão na web, acesse https://groups.google.com/d/msgid/javasf/CAAMaVtR0uKFAa4xcW8c2MNR%3DNGbNW9rj3DD_CpJCXDDYiK0UWA%40mail.gmail.com.
Para obter mais opções, acesse https://groups.google.com/groups/opt_out.



--
Rafael Ponte
http://www.triadworks.com.br

Nilson Uehara

unread,
Oct 1, 2013, 4:00:45 PM10/1/13
to Grupo JSF
Entendi, 
eu não tinha pensado que, apesar do pdf estar na memória só por um breve tempo (só o tempo necessário para o client abri-lo), caso haja muitas requisições grandes de uma só vez, a memória vai pro beleléu.
Vou seguir seu conselho e criar o temp.

Um abraço,
Nilson Uehara


Alecindro Castilho

unread,
Oct 1, 2013, 4:19:04 PM10/1/13
to jav...@googlegroups.com
O PDF não fica na memória. Ele é um Stream. E como stream você pode carregá-lo sobre demanda para o cliente, ou seja, você pode criar o relatório de tal forma que a cada página criada ele envia o stream para tela ... Para salvá-lo em disco existe o custo do IO e algumas vezes problemas de permissão em diretórios dependendo do S.O.. 


Em 1 de outubro de 2013 16:29, Rafael Ponte <rpo...@gmail.com> escreveu:

Alecindro Castilho

unread,
Oct 1, 2013, 4:21:41 PM10/1/13
to jav...@googlegroups.com
Das duas maneiras tu vai ter um Stream que vai consumir a mesma memória. 
Para gerar o relatório em disco haverá um custo adicional de IO. O benefício de guardar o arquivo temporário é que você pode consumi-lo mais de uma vez, sem gerar a consulta novamente. 

Alecindro Castilho

unread,
Oct 1, 2013, 4:24:21 PM10/1/13
to jav...@googlegroups.com
O próprio jasper pode gerar o relatório em disco, se não me engano é o JasperVitualizer. Mas o custo disso é alto. Se forem relatórios grandes, com mais de 300 páginas não recomendo.

Nilson Uehara

unread,
Oct 1, 2013, 4:30:05 PM10/1/13
to Grupo JSF
Eu estava agora batendo a cabeça nisso e cheguei a mesma conclusão... inclusive cheguei numa alternativa e gostaria de saber a opinião de vocês.

Criei um ManagedBean (que é um ManagedProperty dos MB geradores de relatório) com escopo de sessão que possui um atributo StreamedContent onde guardo o relatório recém gerado pela requisição.
Agora, basta o xhtml chamar o metódo de geração de relatório normalmente e abrir o dialog contendo o p:media ligado ao StreamedContent do ManagedBean de sessão!!!

Pelo menos nos testes está funcionando que é uma magavilha!

Um abraço,
Nilson Uehara


Em 1 de outubro de 2013 17:21, Alecindro Castilho <alecindr...@gmail.com> escreveu:

Ricardo Johannsen

unread,
Mar 10, 2014, 12:34:36 PM3/10/14
to jav...@googlegroups.com
No stack overflow o BalusC usa a seguinte solução para renderizar streams com o p:media do primefaces:



@ManagedBean
@ApplicationScoped
public class MediaManager {

    @EJB
    private MediaService service;

    public StreamedContent getStream() throws IOException {
        FacesContext context = FacesContext.getCurrentInstance();

        if (context.getCurrentPhaseId() == PhaseId.RENDER_RESPONSE) {
            // So, we're rendering the HTML. Return a stub StreamedContent so that it will generate right URL.
            return new DefaultStreamedContent();
        } else {
            // So, browser is requesting the media. Return a real StreamedContent with the media bytes.
            String id = context.getExternalContext().getRequestParameterMap().get("id");
            Media media = service.find(Long.valueOf(id));
            return new DefaultStreamedContent(new ByteArrayInputStream(media.getBytes()));
        }
    }

}

pelo que entendi,e não sei se é correto,embora o bean esteja em applicationScope,ele vai
gerar o stream e jogar no browser do usuario e essa stream não ia ficar pendurada no servidor,estou falando besteira?


links:
http://stackoverflow.com/questions/14232339/how-to-bind-dynamic-pdf-content-using-pmedia?rq=1
http://stackoverflow.com/questions/8207325/display-image-from-database-with-pgraphicimage/12452144#12452144

Nilson Uehara

unread,
Mar 11, 2014, 4:03:41 PM3/11/14
to Grupo JSF
Olá Ricardo,
não sei como funciona o EJB (isso está na minha lista de "a estudar"). Mas pelo o que percebi, o managedbean MediaManager vai permanecer na memória durante toda a vida da aplicação e ainda carregará MediaService com todos seus atributos, etc.

Achei um outro caminho para a apresentação dos PDF, vejam só e me digam se esta implementação está melhor ou não:

ManagedBean:
JRPdfExporter exporter = new JRPdfExporter();
exporter.setParameter(JRExporterParameter.JASPER_PRINT, jasperPrint);
exporter.setParameter(JRPdfExporterParameter.OUTPUT_STREAM, baos);
exporter.exportReport();
FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put("rel", baos.toByteArray());


Servlet:
@WebServlet("/report.pdf")
public class PdfReportServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String id=request.getParameter("id");
        byte[] content = (byte[]) request.getSession().getAttribute(id);
        response.setContentType("application/pdf");
        response.setContentLength(content.length);
        response.addHeader("Content-disposition", "inline; filename=rel.pdf");
        response.getOutputStream().write(content);
    }
}

XHTML:
<p:media value="/report.pdf?id=rel" player="pdf" width="100%" height="100%"/>


OBS:
Ainda estou com uma séria dúvida:

FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put("rel", baos.toByteArray());

Este map está sendo armazenado no cliente ou no servidor? (Se for no servidor, ainda vou ter problemas de memória)

Um abraço,
Nilson Uehara


--
Você recebeu essa mensagem porque está inscrito no grupo quot;javasf: JavaServer Faces Group" dos Grupos do Google.
Para cancelar inscrição nesse grupo e parar de receber e-mails dele, envie um e-mail para javasf+un...@googlegroups.com.

Ricardo Johannsen

unread,
Mar 11, 2014, 4:36:29 PM3/11/14
to jav...@googlegroups.com
Olá Nilson, essa solução com servlet também funciona perfeitamente, quanto ao exemplo lá do Balusc acredito que ter o o mediaService  ali não deve ser problema pois acredito que esse ejb seja stateless(normalmente é) e não guarde nenhum estado, a dúvida é: ao realizar o método getStream() ele simplesmente vai gerar o stream,mandar o stream para o browser do usuário, liberar a memória do servidor e consumir apenas memoria no lado cliente? ou ele vai manter esse stream na memória do servidor? Se for a opção 2 acredito que  até mesmo a solução com servlet ele vai manter esse stream na memória para exibir no p:media. É uma funcionalidade bacana mostrar o pdf numa dialog e talz, os usuários gostam bastante, mas dependendo das respostas do pessoal ai que tem mais experiencia com JSF, talvez seja melhor mesmo forçar o download do arquivo pdf.



Everton William Fujimoto

unread,
Mar 11, 2014, 5:17:36 PM3/11/14
to jav...@googlegroups.com
Algumas sugestoes:


Podes utilizar ao inves do p:media um iframe com "target" no commandButton. (Talvez embed tambem funcione)

Podes usar um bean escopo "ViewScoped".

Podes usar f:changePropertyListener(ou algo assim) para preparar o bean de escopo request para que ele tenha os dados para preparar o pdf.

Obs. Na maioria dos casos, nao querer usar um bean "escopo sessao" é mais paranoia que "economia de recursos".


Conectado pelo MOTOBLUR™


-----Mensagen Original-----

Ricardo Johannsen

unread,
Mar 11, 2014, 6:02:25 PM3/11/14
to jav...@googlegroups.com
- a opção 1 pode ser uma boa

-a opção 2 esquece,não  rola com viewscope, limitação do proprio prime,o mesmo acontece com o p:dinamicImage

-com request tbm não rola, pelo menos com o f:setPropertyActionListener no botão que chama o dialog, talvez tacando lá um <t:saveState>


Nilson Uehara

unread,
Mar 12, 2014, 8:31:30 AM3/12/14
to Grupo JSF
Já tentei usar o iFrame e até mesmo direcionar para uma nova janela e popup via target, mas não funcionaram (ou funcionava de vez em quando... não me lembro agora)


Um abraço,
Nilson Uehara


Rafael Ponte

unread,
Mar 12, 2014, 4:46:59 PM3/12/14
to jav...@googlegroups.com
Nilson,

Trabalhe o máximo possível independente do jsf, ao menos para exibir o relatório. Explico, gere o relatório normalmente e salve-o em disco, você pode até atribuir um ID único se quiser. Depois, disponibilize uma URI para carrega-lo do disco, algo como: "/relatorios/report_280923728937208290398.pdf"(pode ser um servlet ou mesmo uma página jsf que executará uma action no managed bean). 

Com a uri em mãos, via javascript mesmo você pode criar um iframe dinamicamente já apontando para esta uri, se o iframe vai estar na página principal, num dialog ou mesmo numa div pequena, tanto faz. Pronto! O browser faz o restante!





Para mais opções, acesse https://groups.google.com/d/optout.

Nilson Uehara

unread,
Mar 12, 2014, 4:53:30 PM3/12/14
to Grupo JSF
Mas fazendo assim eu vou perder qualquer segurança que eu tenha implementado, pois basta um usuário qualquer descobrir que o padrão para relatórios é "/relatorios/report_<<numero_qualquer>>.pdf" que vai virar uma festa, não é?

Um abraço,
Nilson Uehara


Fabio Luciano Goes dos Santos

unread,
Mar 12, 2014, 5:02:24 PM3/12/14
to jav...@googlegroups.com
Acho dificil, pois o usuário teria que descobrir esse "numero_qualquer", que o sistema gera.
Geralmente eu converto a data do dia para Long, e converto para string, gerando assim o nome do arquivo. Se não estou engando a data em long é em milesegundos.

Rafael Ponte

unread,
Mar 12, 2014, 5:03:00 PM3/12/14
to jav...@googlegroups.com
Nilson,

Você pode melhorar as restrições criando uma estrutura básica de segurança (somente o usuário ou grupo que criou pode ler o arquivo etc). Além do que, o número gerado não precisa ser sequencial, o ideal é que seja aleatório. Ao criar um arquivo temporário pela API da classe File ele já recebe um nome bem aleatório, por exemplo.

O que dei foi um exemplo de como trabalhar com relatórios de forma mais flexível.




Para mais opções, acesse https://groups.google.com/d/optout.

Nilson Uehara

unread,
Mar 12, 2014, 5:10:32 PM3/12/14
to Grupo JSF
Entendi.
Acho que essa forma é a melhor mesmo, pois não vai ter impacto na memória do servidor, independente da quantidade de requisições de relatórios.
Vou colocar em prática e dou um feedback.

Um abraço,
Nilson Uehara


Reply all
Reply to author
Forward
0 new messages