quarta-feira, 23 de março de 2016

Fazendo upload de imagens em uma aplicação JavaWEB com JSF + Primefaces

         UpLoad de imagens é usado em praticamente todos os sistemas, seja para cadastro de funcionários, dos produtos ou serviços oferecidos pela empresa, dos clientes, etc. Como tudo na vida, existe mais de uma forma de salvar essas imagens, e de exibi-las no navegador.

        Quando salva-se apenas o endereço da imagem no banco de dados, e a imagem é salva diretamente em um diretório, o qual é o método mais usado, temos que tomar sempre o cuidado de não fazer o backup apenas do banco, mas das pastas que contém as imagens também. Por outro lado, quando salvamos a imagem no banco, iremos estar usando sempre a conexão com o banco para carregar essa imagem o que pode sobrecarregar o banco de dados, mas traz a certeza que a imagem estará segura juntamente com os dados da aplicação.

       Qual opção é a melhor? Depende... depende de cada projeto, do que são essas imagens, a quantidade de visualizações, etc. O mais usado hoje como eu já falei e salvar a imagem em um diretório e então guardar apenas o endereço no banco de dados.

         A forma que vou mostrar, é uma forma mista, a imagem é lida e guardada no banco, na hora de exibir, ela é escrita em uma pasta para a exibição, porém se por algum motivo a pasta for apagada, não tem problema pois a imagem está no banco, a pasta continha apenas uma cópia. Porém, caso queira salvar apenas no diretório, vou dar a dica no momento certo...

         Vamos começar pelo web.xml, adicione o seguinte trecho de código:

<context-param>
      <param-name>primefaces.UPLOADER</param-name>
      <param-value>auto</param-value>
</context-param>
     
<context-param>
      <param-name>javax.faces.PROJECT_STAGE</param-name>
      <param-value>Development</param-value>
</context-param>

      Vamos a página XHTML de cadastro... ela terá um componente chamado <p:fileUpload> que terá a seguinte configuração:

<p:fileUpload value="#{cadastroCarroBean.file}" mode="simple" id="foto"/>

      A única exigência para que o upload funcione, fora a configuração do componente é a de que a função “Ajax” do botão que irá chamar a função responsável por passar os dados para ManagedBean seja setada para false. Como sabemos, no primefaces, o Ajax por padrão é true nos componentes. Isso não trará nenhum impacto negativo pois geralmente na hora de salvar é comum esperarmos receber todos os dados que estão na página para serem devidamente trabalhados. Mas caso para você seja um problema, você pode colocar o campo do upload em um form separado.

<p:commandButton value="Salvar" id="botaoSalvar" action="#{cadastroCarroBean.salvar}" ajax="false"/>

O meu objeto carro tem uma propriedade chamada imagem do tipo bite[] que possui seu método getter e setter como qualquer propriedade privada de uma classe. Caso opte por salvar apenas o caminho no banco, crie a propriedade imagem do tipo String.

private byte[] imagem;

public byte[] getImagem() {
      return imagem;
}

public void setImagem(byte[] imagem) {
      this.imagem = imagem;
}

O ManagedBean terá uma propriedade chamada file, como percebido no value do componente fileUpload que terá seu getter e setter como qualquer propriedade privada de uma classe.

private UploadedFile file;

public UploadedFile getFile() {
return file;
}

public void setFile(UploadedFile file) {
      this.file = file;
}

E quem fará todo o trabalho será método responsável por salvar as informações no banco. Caso você tenha optado por salvar a imagem em um diretório, você terá que fazer no método salvar do ManagedBean o que eu irei fazer na hora de mostrar a imagem, e então pegar o caminho e guardar no campo imagem. Continue acompanhando que ficará mais claro quando você ver.

public void salvar(){
      try{
            if(file != null){
            carro.setImagem(file.getContents());
      }
      cadastroCarroService.salvar(carro);
      this.carro = new Carro();
      this.file = null;
      FacesUtil.addSuccessMessage("O carro foi cadastrado com sucesso.");
                 
      }catch(Exception e){
            e.printStackTrace();
            FacesUtil.addErrorMessage(e.getMessage());
      }
           
}

       Nesse momento, se tudo deu certo, sua imagem já está salva no banco de dados juntamente com o restante do objeto carro. Agora para exibirmos a imagem, vamos ter outro arquivo XHTML e outro ManagedBean (Poderia ser o mesmo ManagedBean). 

         Na página XHTML que eu fiz para exibição, por questão de preferência para a situação, eu não mostro a imagem do carro assim que a página carrega, porém deixo um botão que ao ser clicado, a imagem é exibida através de um componente <p:dialog>.


       Segue o código do botão e do componente p:dialog usado para exibir a imagem. O botão apesar de não mostrado está dentro do <p:dataTable> e o dialog está fora do <p:dataTable>, porém ambos estão dentro do mesmo formulário(<h:form id="formPesquisa">).

<p:commandButton action="#{pesquisaCarroBean.imagemAPartirDoArrayDeByte}" oncomplete="PF('exibirCarro').show()" process="@this" update=":formPesquisa:exibirCarroDialog" icon="ui-icon-image" title="Visualizar imagem do carro">

      <f:setPropertyActionListener value="#{carro}" target="#{pesquisaCarroBean.carroSelecionado}" />

</p:commandButton>

!!!Aqui entraria o fechamento da tag da tabela de dados (</p:dataTable>)

<p:dialog widgetVar="exibirCarro" id="exibirCarroDialog" closable="false" header="#{pesquisaCarroBean.carroSelecionado.modelo.descricao}" >

      <p:graphicImage value="#{pesquisaCarroBean.imagemDoCarroSelecionado}"/>

      <h:outputText rendered="#{pesquisaCarroBean.imagemDoCarroSelecionado == null}" value="Nenhuma imagem cadastrada."/>

      <br />

      <p:commandButton process="@this" style="margin-top:20px;" onclick="PF('exibirCarro').hide();" value="OK"/>

</p:dialog>

          Note que no botão eu seto uma propriedade do ManagedBean e chamo um método também do ManagedBean alpem de mandar mostrar o componente de dialogo com oncomplete="PF('exibirCarro').show().

    Então vamos para o ManagedBean, que terá a propriedade imagemDoCarroSelecionado que é do tipo String e carroSelecionado que é do tipo Carro, onde a propriedade imagemDoCarroSelecionado só precisa do getter, porém carroSelecionado deve possuir tanto o getter quanto o setter. Dito isto, vamos ao método responsável por escrever a imagem em uma pasta e retornar o endereço para a propriedade imagemDoCarroSelecionado.

public void imagemAPartirDoArrayDeByte(){
           
      try{
            if(carroSelecionado.getImagem() != null){
                                        
                  Path path = Paths.get(FacesContext.getCurrentInstance().getExternalContext().getRealPath("/").toString()+"/temp/carro");
                       
                  if(!Files.exists(path)){                      
                        Files.createDirectories(path);
                  }
                       
                  path = Paths.get(path.toRealPath() + "/" + carroSelecionado.getCodigo() + ".jpg");
                  if(!Files.exists(path)){
                        FileOutputStream fos = new FileOutputStream(path.toString());                  
                        fos.write(carroSelecionado.getImagem());
                        fos.close();
                  }
                       
                imagemDoCarroSelecionado = "../temp/carro/"+carroSelecionado.getCodigo() + ".jpg";
                     
            }else {
                       
                  imagemDoCarroSelecionado = null;
                       
            }
      }catch(IOException e){
                 
            FacesUtil.addErrorMessage(e.getMessage());
            imagemDoCarroSelecionado = null;
                 
      }
                
}

O código é bem simples, o que faço é verificar se o diretório existe, e caso não exista eu crio, depois verifico se o arquivo já existe no diretório se não existir eu crio e passo o endereço para propriedade responsável. Caso queira salvar a imagem em um diretório, terá que fazer isso na hora da leitura e passar o caminho para o banco de dados. Você terá que adequar a criação do Path a sua necessidade, teste e veja que caminho quer usar para salvar as imagens!

O sistema que usei como base para criar esse exemplo é um sistema que fiz com baseado em um curso da algaworks, porém alterei várias coisas nele, inclusive essa parte de upload de imagens que não tinha. Espero ter ajudado, fique a vontade para comentar, dar alguma sugestão, ou compartilhar o post se quiser...



Esse link traz mais detalhes do componente p:fileUpload: http://blog.algaworks.com/primefaces-fileupload/

Abraços!!!

4 comentários: