Upload via AJAX com o componente h:inputFile do JSF 2.2

Demorou quase 10 anos para que implementassem um componente de upload no JSF. O componente h:inputFile só foi criado na versão 2.2 da especificação. Apesar da longa espera, o componente já trouxe suporte nativo a AJAX e integração com a API do Servlet 3.

Utilizá-lo é tão simples quanto qualquer outro componente de entrada de dados, tanto é que podemos registrar conversores e validadores no componente de maneira semelhante. Além disso, basta usarmos a tag f:ajax para submetermos o formulário via AJAX, ou seja, nada de JavaScript! Por exemplo, o código abaixo irá disparar uma requisição AJAX após selecionarmos o arquivo:


    

Vale a pena conhecer o componente h:inputFile e ficar por dentro das suas atuais limitações que serão resolvidas em versões futuras do JSF.

O componente de upload de arquivos e muitas outras novidades do JSF 2.2 são exploradas no nosso curso de JSF 2 com Spring.

Controle fino do HTML gerado com Pass Through Attributes e Elements do JSF 2.2

Uma das maiores vantagens e também um dos problemas mais irritantes do JSF é o controle fino do HTML gerado pelo componentes. Em certas situações não temos a necessidade de nos preocupar com o HTML, CSS ou JavaScript gerado pelos componentes; noutras, por outro lado, precisamos fazer ajustes finos no código renderizado para o browser pelos componentes para fazer correções na página relacionados a cross-browser, responsividade ou simplesmente utilizar determinados plugins do jQuery.

Esse problema ficou mais critico com a chegado do HTML5, na qual trouxe vários atributos e elementos para simplificar e melhorar o desenvolvimento Web, como o atributo placeholder, que adiciona um texto em um input vazio. Se tentarmos adicionar estes atributos do HTML5 (ou mesmo customizados) diretamente no componente eles serão ignorados pelas classes renderer’s do JSF, como a seguir:

<h:inputText id="email" value="#{bean.email}" placeholder="placeholder" />

O código acima não renderizará o atributo placeholder para o browser. Ele simplesmente será ignorado pelo JSF. Até a versão 2.1 do JSF isso só se resolvia estendendo e alterando as classes renderizadores dos componentes, o que pode dar mais trabalho do que um desenvolvedor gostaria.

Mas com o JSF 2.2 este problema não mais existe, pois esta nova versão trouxe consigo o suporte HTML5 através do HTML Friendly Markup, na qual nos permite inserir e modificar atributos diretamente no componente (pass through attributes) e até mesmo escrever páginas 100% HTML na qual podem ter seus elementos ligados a componentes no lado servidor (pass through elements), o que é ótimo para tirar proveito das novidades do HTML5.

@ViewScoped, o ovo e a galinha

Uma das funcionalidades mais esperadas do JSF2 sem dúvida foi o escopo de visão (view scope), mais conhecido como @ViewScoped. Com esta simples anotação se tornou possível manter managed beans num escopo maior do que requisição e menor do que sessão. O que resolveu a maioria dos problemas relacionados a ajax, uso inadequado do escopo de sessão e idas e vindas desnecessárias ao banco de dados.

Apesar de todas as coisas boas trazidas pelo @ViewScoped, ele trouxe consigo alguns problemas e limitações que a maioria dos desenvolvedores não estavam acostumados, como problemas de serialização, escopo restrito apenas a mesma página, integração precária com CDI e Spring, incompatibilidade com JSTL e a necessidade de limpar a todo instante os dados do managed bean.

Entre estes problemas, o que gerou bastante frustração para muitos foi o uso do @ViewScoped juntamente com component bindings ou tag handlers (Issue #1492), tanto é que o problema acabou recebendo um apelido interessante, “chicken/egg issue“, ou em tradução livre, o problema do ovo e da galinha. Esse bug ficou mais conhecido durante os primeiros anos de vida do JSF2, mas hoje, após a sua correção, ele não é tão recorrente assim.

O problema do ovo e da galinha

O bug causa a instanciação dos managed beans em @ViewScoped a cada requisição, seja ela ajax ou não. A princípio isso não parece muito sério, mas vai de contra a natureza do escopo, que deveria ser instanciado uma única vez. Isso pode acarretar problemas como processamento desnecessário de métodos @PostConstruct, erros em conversores e validadores ou problemas mais difíceis de detectar, como a criação da árvore de componentes em um estado inválido.

O erro se manifesta ao “linkar” (fazer binding) um bean em @ViewScoped com tag handlers ou component bindings (os atributos id e binding dos componentes) e ter o Partial State Saving habilitado. Entenda tag handlers como qualquer tag que não seja um componente (UIComponent), elas são facilmente reconhecidas por não terem o atributo rendered, como por exemplo JSTL.

Um caso bem comum onde o problema se manifesta é quando você tenta utilizar a tag handler ui:include (não, ela não é um componente!) com @ViewScoped:

<ui:include src="#{menuViewScopedBean.menuName}.xhtml" />

Ou até em casos aparentemente inofensivos, como iterar uma coleção com JSTL, como abaixo:

<c:forEach var="car" items="#{carViewScopedBean.allCars}">
	<h:outputText value="#{car}" />
</c:forEach>

Não esqueça que component bindings (os atributos id e binding são avaliados em build time) também disparam o problema:

<h:form binding="#{viewScopedBean.uiForm}">
	…
</h:form>

Segue a lista das tag handlers que usamos ao trabalhar com JSF. Logo você deveria evita-las ao usar @ViewScoped:

Entendendo o que acontece

Tag handlers e component bindings são avaliados durante a fase de (re)construção da árvore de componentes (build time), enquanto os componentes do JSF são avaliados somente na fase de renderização (render time).

O problema é que beans @ViewScoped são armazenados na árvore de componentes, mas a árvore só estará disponível após a fase de construção, e a tag handler precisa do bean para construir a árvore (por isso o apelido do bug, “chicken/egg issue“).

Como tag handlers são executadas antes da árvore estar disponível e, existe um binding da tag com o bean, o JSF é obrigado a instanciar um novo bean (com as propriedades não inicializadas) para satisfazer a EL (binding) da tag handler, quando na verdade o que ele deveria ter feito era referenciar o bean que já estava armazenado na árvore. Basicamente você acaba com duas instâncias do managed bean por requisição. Onde o primeiro é usado na reconstrução da árvore de componentes e o segundo durante o ciclo de vida do JSF.

No fim de tudo, após a árvore ter sido reconstruída e o bean ter sido colocado de volta no escopo de visão (view scope), sobrescrevendo o bean que foi criado, o ciclo de vida continua normalmente, porém os danos causados pelo bug já aconteceram e não tem como voltar atrás.

Soluções

Existem algumas soluções para o problema, algumas simples e outras nem tanto. A notícia boa é que para quem já utiliza JSF 2.2 o problema já foi resolvido e não precisa se preocupar com ele!

Mas como bem sabemos, nem todos os projetos podem se dar ao luxo de se manterem atualizados a cada nova release, seja por qual for o motivo. Sendo, segue as principais soluções:

1. Atualize a versão do JSF

Como disse, o bug foi mais crítico nos primeiros anos do JSF 2.0 e 2.1, mas hoje em dia ele só atormenta quem não se manteve atualizado com as releases periódicas do Mojarra e/ou Myfaces.

O bug foi resolvido juntamente com a release do JSF 2.2, logo, quem já trabalha com a versão mais recente do faces não terá do que reclamar. Para quem ainda trabalha com JSF 2.1 existe a correção desde a versão 2.1.18 do Mojarra, qualquer versão antes desta é totalmente bugada e apresentará todos os problemas relatados.

Para quem não pode atualizar a versão do faces, as próximas soluções podem ajudar.

2. Desligue o Partial State Saving

Nas primeiras semanas em que o bug apareceu, a solução mais simples, porém a mais radical para aqueles que não poderiam abrir mão de suas tag handlers era desligar o Partial State Saving (PSS) globalmente, já que o problema estava intimamente ligado à ele. Para isso, basta configurar seu web.xml com:

<context-param>
	<param-name>javax.faces.PARTIAL_STATE_SAVING</param-name>
	<param-value>false</param-value>
</context-param>

Desligar o PSS é abrir mão de uma das features mais importantes do JSF2, pois ela é responsável por manter a árvore de componentes parcialmente em memória, com isso ela diminui consideravelmente o consumo de banda de rede, memória e cpu do servidor. Ao desligar o PSS a aplicação volta a trabalhar como no JSF 1.2, ou seja, armazenando toda a árvore de componentes em memória. E isto pode ser um problema se sua aplicação mantém o estado da árvore no lado cliente.

3. Desligue o Partial State Saving por página

Uma solução melhor do que desligar o PSS globalmente, é desliga-lo somente para as páginas (views) problemáticas, as páginas que utilizam tag handlers ou component bindings. Dessa forma sua aplicação, certamente a maior parte dela, continua tirando proveito das otimizações do JSF 2. Para isso, basta configurar seu web.xml com:

<context-param>
	<param-name>javax.faces.FULL_STATE_SAVING_VIEW_IDS</param-name>
	<param-value>/home.xhtml,/pages/booking.xhtml</param-value>
</context-param>

O parâmetro aceita uma lista de views (páginas) separadas por vírgula na qual terão o PSS desligado.

4. Substitua tag handlers por componentes

Se por acaso você não quiser abrir mão do PSS então a única solução que lhe resta é substituir todas as tag handlers que tem binding com beans @ViewScoped por componentes normais do JSF.

Para isso, segue uma listas com alternativas para cada tag handler:

  • <c:choose>: use o atributo rendered no lugar dele
  • <c:forEach>: substitua pelo componente <ui:repeat>
  • <c:if>: use o atributo rendered no lugar dele
  • <c:set>: substitua por <ui:param>, <f:viewParam>, @ManagedProperty ou @PostConstruct
  • <f:actionListener>: use o atributo actionListener do componente em vez dele
  • <f:convertXxx> como <f:convertNumber>: use o atributo converter do componente ou use o <f:converter> em vez dele
  • <f:facet>: não existe alternativa para ele, simplesmente não use EL em qualquer atributo desta tag handler
  • <f:validateXxx> como <f:validateLongRange>: use o atributo validator ou use o <f:validator> em vez dele
  • <f:valueChangeListener>: use o atributo valueChangeListener no lugar dele
  • <ui:decorate>: não existe alternativa para ele, simplesmente não use EL em qualquer atributo desta tag handler
  • <ui:composition>: não existe alternativa para ele, simplesmente não use EL em qualquer atributo desta tag handler
  • <ui:include>: use componentes <ui:fragment rendered> para cada <ui:include> com páginas estáticas
  • qualquer tag file customizada: substitua por componentes do JSF (UIComponent)

Concluindo

Hoje em dia o problema não é dos mais críticos, mas ainda existem muitas aplicações que sofrem com ele. Se você tem um projeto com versões antigas do JSF 2, é bem provável que o problema aconteça em algumas de suas páginas, mas talvez não chegue a ser algo sério e você não tenha percebido.

Tão importante quanto resolver o problema é entendê-lo, o que leva muitas vezes ao aprendizado de novos conceitos e de como o JSF trabalha por debaixo dos panos. Conceitos e discussões sobre este problema e outros são tratados no curso de JSF 2 e Spring da TriadWorks.

E aí, você já conhecia o problema do ovo e da galinha? Usou alguma outra solução que não comentei?