Upload de arquivos com JSF 2.2
Aprenda como fazer upload de arquivos usando o novo componente h:inputFile do JSF 2.2
Somente no JSF 2.2 é que foi introduzido o suporte a upload de arquivos. Antes éramos obrigados a usar alguma biblioteca de componentes, como PrimeFaces ou RichFaces. Apesar da espera de quase 10 anos para criarem um componente padrão de upload de arquivos na especificação, por sorte, ele já foi criado com suporte a AJAX e integrado a API do Servlet 3.0.
Podemos usar o upload de arquivos do JSF 2.2 através do componente h:inputFile
, na qual funciona como qualquer outro componente de entrada de dados. O único pré-requisito, é modificar o encoding da submissão do formulário para multipart/form-data
através do atributo enctype
do componente h:form
. Por exemplo, um formulário de importação de arquivos não seria muito diferente do código abaixo:
<html ...
xmlns:h="http://xmlns.jcp.org/jsf/html">
<h:form id="form" enctype="multipart/form-data">
<h:outputLabel value="Arquivo" for="arquivo" />
<h:inputFile id="arquivo" value="#{importadorBean.arquivo}"
required="true" label="Arquivo" />
<h:commandButton value="Importar"
action="#{importadorBean.importa}" />
</h:form>
</html>
É importante observar que para utilizarmos o componente h:inputFile
nas páginas nós tivemos antes que declarar a nova namespace para a tag html
: http://xmlns.jcp.org/jsf/html
. As namespaces antigas continuam funcionando devido a retrocompatibilidade do framework. Porém, para tirar proveito dos novos componentes e recursos do JSF 2.2 em diante se faz necessário usar a nova namespace.
Para receber o arquivo e processá-lo no lado servidor, precisamos implementar o managed bean ImportadorBean
, como a seguir:
// outros imports
import javax.servlet.http.Part;
@ManagedBean
public class ImportadorBean {
private Part arquivo;
public void importa() {
try {
String conteudo = new Scanner(arquivo.getInputStream())
.useDelimiter("\\A").next();
} catch (IOException e) {
// trata o erro
}
}
// getter e setter
}
O arquivo de upload submetido é armazenado no atributo arquivo
do tipo javax.servlet.http.Part
, que faz parte da API do Servlet 3.0. Além disso, para ler o conteúdo do arquivo de upload, podemos obter um input stream a partir do método getInputStream
da classe Part
e usar a classe Scanner
da própria API Java para extrair o conteúdo do arquivo para uma String
.
Validação customizada
Por se tratar de um componente de entrada de dados, nós podemos registrar conversores e validadores para ele, semelhante ao que fazemos com os demais componentes da especificação. Por exemplo, podemos registrar um método de validação customizado que verificará o tamanho do arquivo e se o tipo de arquivo enviado é do tipo texto, ou seja, se o seu conteúdo (mime-type) é text/plain
. O código do método de validação dentro do managed bean será semelhante a este:
@ManagedBean
public class ImportadorBean {
private static final int MAX_SIZE = 2 * 1024 * 1024;
// outros métodos
public void valida(FacesContext context, UIComponent component, Object value) {
Part arquivo = (Part) value;
if (arquivo.getSize() > MAX_SIZE) {
FacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_ERROR,
"Arquivo muito grande", "O arquivo deve ter o tamanho máximo de 2mb.");
throw new ValidatorException(msg);
}
if (!"text/plain".equals(arquivo.getContentType())) {
FacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_ERROR,
"Tipo de arquivo inválido", "O arquivo deve ser do tipo texto.");
throw new ValidatorException(msg);
}
}
}
Por fim, para registrar o método de validação customizado no componente, basta fazer o binding do método no atributo validator
do componente h:inputFile
, como abaixo:
<h:inputFile ... validator="#{importadorBean.valida}" />
Para ter uma melhor reutilização de código, também é possível implementar uma classe de validação customizada em vez de um método dentro do managed bean.
Submetendo arquivos via AJAX
Habilitar o suporte a AJAX do componente é uma tarefa muito simples, basta submetermos o formulário com o uso da tag f:ajax
, como a seguir:
<h:form id="form" enctype="multipart/form-data">
<h:outputLabel value="Arquivo" for="arquivo" />
<h:inputFile id="arquivo" value="#{importadorBean.arquivo}"
required="true" label="Arquivo" />
<h:commandButton value="Importar"
action="#{importadorBean.importa}">
<f:ajax execute="@form" render="@all" />
</h:commandButton>
</h:form>
Repare que após o evento AJAX, nós estamos repintando a página inteira (@all
) e não somente o formulário como de costume. Isto se deve ao fato de haver um bug no Mojarra que nos força a repintar toda a página após uma requisição AJAX se estivermos fazendo o upload de arquivos. Aparentemente, este bug foi corrigido nas versões mais recentes do Mojarra.
Apesar do bug, como podemos ver, submeter um formulário via AJAX com suporte a upload de arquivos é tão simples quanto submeter qualquer outro formulário numa página JSF.
Upload de múltiplos arquivos
Apesar do JSF 2.2 ter investido grandes esforços para aderir as novidades do HTML5, infelizmente o componente h:inputFile
não suporta upload de múltiplos arquivos, mas já existem planos de implementar este recurso no JSF 2.3 - quem sabe até antes!
Outra solução, seria aproveitar o recurso pass-through attributes para adicionar o atributo multiple
do HTML5 e alterar o renderer padrão do componente, algo semelhante ao explicado nesse artigo. Mas talvez valha mais a pena utilizar um componente rico e mais completo, como o componente p:fileUpload
do PrimeFaces.
Mesmo com a demora de quase 10 anos para implementarem o componente de upload na especificação, podemos acreditar que as bibliotecas de componentes irão se adaptar e estendê-lo em vez de continuarem com suas próprias soluções, dessa forma seria mais fácil integrar na mesma página componentes de diferentes bibliotecas. Por esse motivo, é importante estar ciente do funcionamento do componente, suas peculiaridades e das novidades do JSF 2.2, temas que abordamos no nosso curso de JSF 2.
E você, já utilizou ou utiliza o componente h:inputFile
do JSF 2.2 nos seus projetos?
Desenvolvedor e instrutor na TriadWorks