JavaServer Faces

Domine o componente FileUpload do PrimeFaces

Você que usa PrimeFaces, gostaria de dominar o componente de upload de arquivos?

Saber que existe um componente para upload de arquivos na biblioteca do PrimeFaces não diminui muito a quantidade de problemas que podemos ter ao utilizá-lo.

Uma simples configuração errada pode fazer você perder 1 hora enquanto poderia ter gasto apenas 10 minutos, se soubesse um pouco mais de detalhes sobre o componente.

Considerando que o carregamento de arquivos é uma funcionalidade bastante utilizada, acredito (e acho que você vai concordar comigo) que o legal mesmo é conhecer o funcionamento do p:fileUpload.

Caso você queira ser proativo e tomar conhecimento sobre esse componente útil em nosso dia a dia, então continue lendo o artigo para aprender mais sobre:

  • Como fazer um upload compatível com browsers mais antigos ou não-ajax
  • Fazer upload utilizando o modo avançado
  • Carregar mais de um arquivo por vez
  • Fazer upload automático
  • Limitar o tamanho e a quantidade de arquivos carregados
  • Filtrar os tipos dos arquivos que poderão ser escolhidos
  • Escolher arquivos utilizando drag and drop
  • Como fazer o download dos arquivos

Interessado? Vamos lá então.

Primeiras configurações

O componente p:fileUpload consegue fazer o upload de arquivos de duas formas:

  • native: para quem utiliza a API de Servlets 3+
  • commons: utiliza o projeto Commons FileUpload e funciona mesmo em um ambiente com a API de Servlets na versão 2.5

Caso tenha necessidade de escolher uma específica, você precisa configurar o parâmetro primefaces.UPLOADER no web.xml, como abaixo:

<context-param>
  <param-name>primefaces.UPLOADER</param-name>
  <param-value>auto|native|commons</param-value>
</context-param>

Como você pode ver, temos também a opção auto que é a padrão e, simplesmente, entrega para o componente a escolha de qual utilizar.

Para essa escolha, o componente verifica se você está utilizando o JSF 2.2+, se sim, então é selecionado o uploader native e, caso não, seleciona-se o commons.

Se você optar por native explicitamente, é preciso certificar que está em um ambiente com API de Servlets 3+, com a vantagem que não depende de nenhuma biblioteca externa.

Optando pelo modo commons vai ser necessário, claro, incluir o projeto Commons FileUpload no path da sua aplicação (como, por exemplo, na pasta WEB-INF/lib).

A vantagem do modo commons é que o upload vai funcionar independente da versão da API de Servlets (tanto na 2.5 quando na 3.0+).

Uma ressalva ao se utilizar o modo commons, é que você também vai precisar configurar o filtro FileUploadFilter:

<filter>
  <filter-name>FileUploadFilter</filter-name>
  <filter-class>org.primefaces.webapp.filter.FileUploadFilter</filter-class>
</filter>

<filter-mapping>
  <filter-name>FileUploadFilter</filter-name>
  <servlet-name>FacesServlet</servlet-name>
</filter-mapping>

O filtro é necessário, pois, é feita uma espécie de pré-configuração para que o componente consiga terminar o processo de upload utilizando o projeto Commons FileUpload.

Ainda falando do filtro, é possível configurar dois parâmetros junto com ele:

<filter>
  <filter-name>FileUploadFilter</filter-name>
  <filter-class>org.primefaces.webapp.filter.FileUploadFilter</filter-class>
  <init-param>
    <param-name>thresholdSize</param-name>
    <param-value>10240</param-value>
  </init-param>
  <init-param>
    <param-name>uploadDirectory</param-name>
    <param-value>/tmp/algaworks/uploads</param-value>
  </init-param>
</filter>

O parâmetro thresholdSize determina até que tamanho é permitido manter o arquivo na memória. Passando da quantidade configurada, o arquivo é escrito no disco. Ele é configurado em bytes.

Já o parâmetro uploadDirectory é somente para indicar em qual diretório que você deseja que os arquivos sejam salvos durante o upload.

Lembrando que o diretório do parâmetro uploadDirectory não é algo definitivo. Esse é um lugar provisório para que os arquivos fiquem até que você dê o devido tratamento a eles como veremos mais tarde neste artigo.

Afinal, qual configuração é necessária?

Depois dessas opções que passei para você, talvez fique na dúvida sobre qual combinação escolher. Quero esclarecer isso.

Se você utiliza:

  • Servlet 3.0+
  • JSF 2.2+

Então não vai precisar de configuração alguma para utilizar o componente p:fileUpload.

Isso porque, sem configuração explícita, o PrimeFaces vai assumir o valor auto para o parâmetro primefaces.UPLOADER e com isso vai utilizar a API nativa de Servlet para realizar o upload.

Caso você utilize qualquer uma das duas opções abaixo:

  • Servlet 2.5; ou
  • Até a versão 2.1 do JSF

Então vai precisar utilizar o projeto Commons FileUpload.

Para utilizar o projeto Commons FileUpload você pode informar explicitamente o primefaces.UPLOADER com o valor commons ou deixar sem configuração, caso esteja utilizando até a versão 2.1 do JSF.

Não esqueça do filtro se for utilizar o valor commons. :)

Como fazer upload básico (ou não-ajax)

Apesar dos muitos casos em que já podemos fazer upload com HTML5, ainda é preciso contar com um suporte para upload básico.

No upload básico, o resultado em HTML que é retornado ao navegador, é uma simples tag input com o atributo type igual a file.

Seria para quem precisa manter compatibilidade com navegadores antigos ou, quem sabe, por algum requisito específico do seu projeto.

Para fazer o uso do upload básico você necessita somente da configuração abaixo:

<h:form enctype="multipart/form-data">
  <p:fileUpload value="#{uploadBasicoBean.uploadedFile}" mode="simple"/>
  <p:commandButton value="Enviar" ajax="false" 
          actionListener="#{uploadBasicoBean.upload}" />
</h:form>

A primeira coisa a se reparar é que a tag h:form precisa do atributo enctype para que o componente p:fileUpload funcione com as configurações acima.

O padrão do atributo mode é advanced, então precisamos configurá-lo com a opção simple para utilizar o upload básico.

No valor do componente (atributo value) precisamos fazer o vínculo com uma propriedade do tipo UploadedFile:

import org.primefaces.model.UploadedFile;

public class UploadBasicoBean {

  private UploadedFile uploadedFile;

  // getters e setters omitidos

}

Para submeter o formulário, no modo simples, precisamos de um botão com o atributo ajax igual a false (a submissão da página será total).

Salvando o arquivo no modo simples

Quando o método que está no actionListener do seu botão for invocado, você usará a propriedade do tipo UploadedFile para salvar o arquivo.

O seu atributo do tipo UploadedFile vai lhe dar acesso aos seguintes métodos:

  • getFileName() para acessar o nome do arquivo
  • getContents() (ou getInputstream()) para pegar os bytes do arquivo
  • getContentType() que retorna o tipo do conteúdo do arquivo

Com isso você pode utilizar o código abaixo para salvar o arquivo no disco:

public void upload() {
  try {
    File file = new File(diretorioRaiz(), uploadedFile.getFileName());

    OutputStream out = new FileOutputStream(file);
    out.write(uploadedFile.getContents());
    out.close();

    FacesContext.getCurrentInstance().addMessage(
               null, new FacesMessage("Upload completo", 
               "O arquivo " + uploadedFile.getFileName() + " foi salvo!"));
  } catch(IOException e) {
    FacesContext.getCurrentInstance().addMessage(
              null, new FacesMessage(FacesMessage.SEVERITY_WARN, "Erro", e.getMessage()));
  }

}

Obviamente você pode salvar o arquivo onde quiser. O código acima foi só como um exemplo para guiar quem está utilizando essa pequena API a primeira vez.

No trecho acima não utilizamos o método getContentType. Em nosso exemplo ele não foi necessário, mas você precisará guardar o retorno dele em algum lugar caso queira configurar o cabeçalho HTTP Content-Type em uma possível função de download desses arquivos.

Aparência do componente de upload no modo simples

Da forma como mostrei a configuração do componente p:fileUpload, ele será impresso da seguinte maneira:

Upload básico sem skin

Caso queira o input mais parecido com o resto dos componentes do PrimeFaces, você vai precisar configurar o atributo skinSimple como true:

<p:fileUpload value="#{uploadBasicoBean.uploadedFile}" mode="simple" 
            skinSimple="true" />

A configuração acima vai assumir uma aparência similar aos outros componentes:

Upload básico com skin

Upload avançado

A menos que alguma circunstância do projeto te obrigue a utilizar o modo simples, imagino que você vai preferir o modo avançado.

O modo avançado é padrão. Você pode tanto utilizar o atributo mode com o valor advanced como omiti-lo:

<h:form>
  <p:fileUpload fileUploadListener="#{uploadAvancadoBean.upload}" />
</h:form>

Note que fica até mais simples a configuração do modo advanced, não é mesmo? :)

É claro que podemos (e iremos ver isso) incluir muitos outros atributos para customizar o modo avançado, mas, para ter o mesmo efeito que o modo básico, precisamos somente do trecho acima.

A maior diferença na tag do componente é que, ao invés de utilizar o atributo value com vínculo para uma propriedade do tipo UploadedFile, temos o atributo fileUploadListener sendo vinculado ao método upload que agora recebe um parâmetro do tipo FileUploadEvent:

import org.primefaces.event.FileUploadEvent;

public class UploadAvancadoBean {

  public void upload(FileUploadEvent event) {
    UploadedFile uploadedFile = event.getFile();

    ...
  }
}

Dessa forma não precisamos ter um botão com algum actionListener como tínhamos o botão “Enviar” no exemplo do modo simples.

No modo avançado o próprio componente já nos dá um botão para iniciarmos o upload:

Upload avançado

Seria o botão “Upload” da imagem acima.

Como você viu, na renderização do componente, também temos mudanças consideráveis.

O botão “Choose”, que abre a janela do browser para escolha do arquivo, continua e são acrescentados mais dois: “Upload” e “Cancel”.

Sobre o botão “Upload”, acabamos de falar, ele inicia o envio dos arquivos para o servidor.

Já o botão “Cancel”, bem intuitivo, serve para cancelar o envio do arquivo.

Quanto ao rótulo de cada botão, podem ser customizados através dos atributos:

  • label
  • uploadLabel
  • cancelLabel

O componente fica:

<p:fileUpload fileUploadListener="#{uploadAvancadoBean.upload}" 
    label="Escolher" uploadLabel="Enviar" cancelLabel="Cancelar" />

E a renderização:

Upload avançado com rótulo customizado

Nota sobre o atributo label

Muitas vezes utilizamos o componente p:fileUpload junto com uma tag p:outputLabel.

Repare nessa configuração:

<h:form>
  <p:outputLabel value="Arquivo: " for="fileUpload" />
  <p:fileUpload id="fileUpload" fileUploadListener="#{uploadAvancadoBean.upload}" 
              label="Escolher" uploadLabel="Enviar" cancelLabel="Cancelar" />
</h:form>

Agora veja o que ela renderiza:

p:outputlabel e p:fileupload

Observe que o rótulo do botão “Choose” não ficou com o valor “Escolher” que foi configurado no atributo label.

Ele ficou com o valor “Arquivo” que é igual ao que está no atributo value do componente p:outputLabel.

Isso é porque a tag outputLabel do PrimeFaces procura o componente que é informado em seu atributo for e, se esse componente estender javax.faces.component.UIInput, ele configura o rótulo dele com o mesmo valor que está em seu atributo value.

Veja que apesar do atributo value do p:outputLabel ser “Arquivo: ”, o caractere “:” (dois pontos) é retirado antes de se configurar o atributo label do UIInput (que, nesse caso, é o org.primefaces.component.fileupload.FileUpload).

Customizando o modo avançado do componente p:fileUpload

Temos algumas opções bem bacanas de customização desse componente.

Gostaria de destacar aqui as seguintes:

  • allowType
  • sizeLimit
  • fileLimit
  • multiple
  • auto
  • dragDropSupport
  • sequential
  • onstart
  • onerror
  • oncomplete

Filtrar o arquivo por tipo

A primeira opção é a de filtrar o arquivo que o usuário pode escolher. O mais comum é filtrar pela extensão.

Fazemos isso com o uso do atributo allowTypes:

<p:fileUpload fileUploadListener="#{uploadAvancadoBean.upload}" 
    allowTypes="/(\.|\/)(gif|jpe?g|png)$/" 
    invalidFileMessage="São permitidas somente imagens (gif, jpeg, jpg e png)" />

No exemplo de configuração acima estamos permitindo somente imagens (gif, jpeg, jpg e png).

Mesmo que você já tenha alguma validação no servidor, esse atributo economiza a requisição de um upload desnecessário.

Limitar o tamanho de cada arquivo

Muitas aplicações precisam estar protegidas da inocência de usuários que não ligam para o tamanho dos arquivos na hora de fazer algum upload. :)

Para isso temos o atributo sizeLimit:

<p:fileUpload fileUploadListener="#{uploadAvancadoBean.upload}" 
    sizeLimit="1024" 
    invalidSizeMessage="O tamanho máximo permitido é de 1KB." />

Ele, claro, limita o tamanho do arquivo que o usuário pode escolher. O valor dele deve ser informado em bytes.

No exemplo acima o limite para upload é de 1KB.

Limitar a quantidade de arquivos

O p:fileUpload, no modo avançado, permite que o usuário escolha vários arquivos antes de clicar em “Upload” (ou “Enviar”).

Você consegue limitar essa quantidade através do atributo fileLimit:

<p:fileUpload fileUploadListener="#{uploadAvancadoBean.upload}" 
    fileLimit="5" 
    fileLimitMessage="Só é possível escolher 5 arquivos por vez." />

Com a configuração acima você consegue limitar em somente 5 arquivos.

Permitir a seleção de múltiplos arquivos

Como comentei no tópico anterior, no modo avançado é possível escolher vários arquivos antes de enviá-los, mas, por padrão, para cada arquivo que o usuário escolher, ele precisa clicar no botão “Choose” (ou “Escolher”) novamente.

Através do atributo multiple você consegue permitir com que o usuário selecione quantos arquivos ele quiser.

Assim, se ele precisar selecionar 5 arquivos que estão em uma mesma pasta, não será preciso clicar no botão “Choose” 5 vezes. Vai dar para fazer a seleção dos 5 arquivos de uma só vez:

Upload avançado com seleção múltipla

E a tag fica:

<p:fileUpload fileUploadListener="#{uploadAvancadoBean.upload}" 
    multiple="true" />

Enviar os arquivos automaticamente

Uma opção que pode ser bacana para os usuários é a de enviar os arquivos automaticamente, ou seja, não será necessário o clique no botão “Upload” (ou “Enviar”).

Na verdade, o botão “Upload” nem é renderizado quando configuramos o atributo auto como true:

Upload avançado com envio automático

Veja também a tag:

<p:fileUpload fileUploadListener="#{uploadAvancadoBean.upload}" 
    auto="true" />

Suporte a drag and drop

Esse já vem configurado por padrão. Logicamente o browser precisa suportar essa funcionalidade.

Para desabilitá-la, você deve fazer como abaixo:

<p:fileUpload fileUploadListener="#{uploadAvancadoBean.upload}" 
    dragDropSupport="false" />

Fazer o envio dos arquivos de forma sequencial

Quando apertamos o botão “Upload” (ou “Enviar”) do componente p:fileUpload, os arquivos são enviados em requisições concorrentes.

Se, por algum motivo, você quiser que as requisições sejam feitas uma por vez, então será preciso configurar o atributo sequential como true:

<p:fileUpload fileUploadListener="#{uploadAvancadoBean.upload}" 
    sequential="true" />

Com isso você garante que o segundo arquivo vai esperar o primeiro ser enviado, e assim sucessivamente.

Eventos JavaScript (onstart, onerror, oncomplete)

O p:fileUpload dá a possibilidade de você configurar três tipos de callbacks diferentes, caso ache necessário.

São bem intuitivos, mas eu gostaria de explicá-los também.

No onstart você pode deixar algum JavaScript que vai ser disparado a cada vez que o processo para envio de arquivos se inicia.

Se você estiver utilizando o atributo auto como true, o callback onstart será disparado a cada arquivo que o usuário escolher. Já se o envio não estiver no automático, então o callback será disparado sempre que o botão “Upload” (ou “Enviar”) for clicado.

Quanto ao callback onerror, você deixa algum código JavaScript que queira disparar para o caso da requisição falhar.

Teríamos o callback onerror disparado nos casos de, por exemplo, o servidor ter saído do ar pouco antes do usuário clicar no botão “Upload” (ou “Enviar”).

Por último, o callback oncomplete será chamado sempre que o processo terminar. Digo processo e não requisição, pois, no caso do servidor estar fora do ar, o oncomplete será invocado mesmo não tendo ocorrido requisição.

O  callback oncomplete sempre é chamado, em caso de sucesso ou erro.

A configuração da tag é bem simples, mas vou deixar um exemplo aqui também:

<p:fileUpload fileUploadListener="#{uploadAvancadoBean.upload}"
    onstart="console.log('Iniciando envio de arquivo')"
    onerror="console.log('Erro na requisição de envio')"
    oncomplete="console.log('Envio concluído')" />

Como fazer o download com PrimeFaces

Já que, muitas das vezes que o arquivo vai, ele precisa voltar, veremos também como descarregar um arquivo utilizando o componente p:fileDownload do PrimeFaces.

O que você precisa é da configuração:

<p:commandButton value="Baixar" ajax="false">
  <p:fileDownload value="#{descarregadorBean.streamedContent}" />
</p:commandButton>

Temos um p:commandButton com o p:fileDownload aninhado a ele. Para que a configuração acima funcione é preciso que a propriedade vinculada ao atributo value seja de algum tipo que implemente a interface StreamedContent:

import org.primefaces.model.StreamedContent;

public class DescarregadorBean {

  private StreamedContent streamedContent;

  // getter omitido
}

Esse tipo pode ser, por exemplo, DefaultStreamedContent:

import org.primefaces.model.DefaultStreamedContent;
import org.primefaces.model.StreamedContent;

public class DescarregadorBean {

  private StreamedContent streamedContent;

  public DescarregadorBean() throws IOException {
    InputStream in = new FileInputStream(new File("arquivo.jpg"));
    streamedContent = new DefaultStreamedContent(in, "image/jpg", "arquivo.jpg");
  }
  
  // getter omitido
}

Assim você já consegue fazer o download de arquivos, mas, caso queira preparar o download no momento em que o usuário clicar no botão, então você pode configurar um actionListener no botão:

<p:commandButton value="Baixar" ajax="false" actionListener="#{descarregadorBean.descarregar}">
  <p:fileDownload value="#{descarregadorBean.streamedContent}" />
</p:commandButton>

… e no managed bean:

import org.primefaces.model.DefaultStreamedContent;
import org.primefaces.model.StreamedContent;

public class DescarregadorBean {

  private StreamedContent streamedContent;

  public void descarregar() throws IOException {
    // Abaixo temos um código estático, mas
    // obviamente você pode buscar o arquivo de onde quiser :)
    InputStream in = new FileInputStream(new File("arquivo.jpg"));
    streamedContent = new DefaultStreamedContent(in, "image/jpg", "arquivo.jpg");
  }

  // getter omitido
}

Uma nota para quem usa Java 7+: é possível ter acesso ao Content-Type de um arquivo (que no exemplo acima é “image/jpg”) com o código:

String contentType = Files.probeContentType(file.toPath());

É útil para quem não guardou o Content-Type dos arquivos que salvamos no momento do upload. :)

Conclusão

Acredito que falamos muito mais do que você precisa para simplesmente utilizar o componente p:fileUpload. Com o conhecimento que está aqui, você pode dizer que domina esse componente. :)

Vimos como fazer o upload básico para o caso de precisar de compatibilidade com browsers antigos.

Depois passamos pelo upload avançado e as várias configurações que podemos fazer com ele.

Por último, aprendemos a fazer o download, já que muitas das vezes ele será necessário, principalmente, para quem já teve que implementar o upload.

Você que chegou até aqui, provavelmente gosta de PrimeFaces ou, pelo menos, usa ele em seu trabalho. Estou certo?

Se sim, então vai gostar do e-book de Java EE 7 (com JSF, PrimeFaces e CDI). Esse artigo aqui é só um incremento diante de tudo que você irá encontrar no e-book.

Clique abaixo para fazer o download:

E-Book Java EE 7 com JSF, Primefaces e CDI

No mais, um abraço pra você e até uma próxima!

PS: Você pode acessar o código-fonte dos exemplos em nosso perfil do GitHub: http://github.com/algaworks/artigo-jsf-primefaces-fileupload

Trabalha como programador Java há mais de uma década, principalmente com desenvolvimento de sistemas corporativos. Além de colaborar com o blog, também é instrutor de vários cursos de Java na AlgaWorks.

Olá,

o que você achou deste conteúdo? Conte nos comentários.

Junte-se a mais de 100.000 pessoas

Entre para nossa lista e receba conteúdos exclusivos e com prioridade

Você se Inscreveu com Sucesso!