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 arquivogetContents()
(ougetInputstream()
) para pegar os bytes do arquivogetContentType()
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:
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 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:
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:
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:
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:
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
:
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:
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
Olá,
o que você achou deste conteúdo? Conte nos comentários.