O Seam criou uma solução bastante elegante e prática que lida bem com filtros dinâmicos. Com base em minha experiencia com o Seam, decidi implementar algo com funcionalidade parecida com JSF2 e Primefaces.
O DataTable do Primefaces suporta Lazy Loading através da classe tipada org.primefaces.model.LazyDataModel, mas o exemplo deles é feito em cima de uma lista pronta em vez de um consulta EJB-QL. Vamos utilizar os métodos setFirstResult e setMaxResults para trazer o range correto da consulta de acordo com a paginação.
Para permitir paginação é preciso saber o total de elementos que a consulta traria e para isto será utilizado uma consulta com count. Na solução do Seam esta consulta é criada automaticamente e funciona bem para a maioria dos casos, mas é possível criar manualmente a consulta. Para simplificar as coisas nesta primeira versão, a consulta do count deve ser criada manualmente.
Para os filtros o funcionamento vai ser semelhante ao do Seam, mas sem a restrição de permitir apenas uma expressão el por filtro.
A classe abstrata tipada DataList estende a LazyDataModel fazendo todo o tratamento. Criei a classe como abstrata para que seja obrigatória a implementação dos métodos abaixo:
protected Map<String, DataListFilter> getFiltros(); protected abstract String getSqlList(); protected abstract String getSqlCount();
O principal método da classe LazyDataModel é o public List
O DataTable do Primefaces possui sistema de ordenação e de filtros e eles são repassados por esses parâmetros, juntamente com os dados referentes a paginação:
int first - Equivalente ao OFFSET
int pageSize - Equivalente ao LIMIT
String sortField - Nome do campo usado na ordenação
SortOrder sortOrder - Enum que indica se a ordenação será ASC ou DESC
Map
Como o sistema de filtro do método load suporta apenas Strings, fiz um suporte para filtros com valores dinâmicos baseados em 'el'.
Para definir o filtro é preciso apenas sobrescrever o método getFiltros() como pode ser visto no exemplo abaixo.
protected Map<String, DataListFilter> getFiltros() {
filtros = new HashMap<String, DataListFilter>();
filtros.put("nome", new DataListFilter("AND o.nome like #{empty estadoDataList.nomeEstado ? null : estadoDataList.nomeEstado.concat('%')}"));
filtros.put("sigla", new DataListFilter("AND o.sigla = #{estadoDataList.siglaEstado}"));
return filtros;
}
A classe DataListFilter será a responsável pelas operações em cima do filtro. Basicamente ela precisa fazer o parse do filtro para encontrar todas as expressões #{el} e transformar em um parâmetro da Query. Por exemplo para o filtro "AND o.sigla = #{estadoDataList.siglaEstado}" será encontrada a el #{estadoDataList.siglaEstado} e a consulta EJB-QL será AND o.sigla = :estadoDataList_siglaEstado.
Outra coisa importante é avalizar todas as expressões do filtro e caso todas retornem valor o método isPopulatedExpressions() retornará true. Isto é usado pela classe DataList para saber se o filtro deve ser adicionado na String da Query bem como no momento de aplicar os parâmetros.
O regex que identifica e captura as expressões é bastante simples e utiliza grupos para poder obter as informações de maneira mais fácil: (\#\{(.*?)\})
Para avaliar as expressões criei o método abaixo, ele é bastante útil em uma aplicação JSF:
private <C> C eval(String el, Class<C> classReturn) {
FacesContext facesContext = FacesContext.getCurrentInstance();
Application application = facesContext.getApplication();
ExpressionFactory factory = application.getExpressionFactory();
ELContext elContext = facesContext.getELContext();
ValueExpression valueExpression = factory.createValueExpression(elContext, el, classReturn);
return (C) valueExpression.getValue(elContext);
}
Na pratica a criação do ManagedBean referente do DaraTable fica simples:
package br.rodrigo.dao.list;
import java.util.HashMap;
import java.util.Map;
import javax.enterprise.context.RequestScoped;
import javax.faces.bean.ManagedBean;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import br.rodrigo.dao.DataList;
import br.rodrigo.dao.DataListFilter;
import br.rodrigo.model.Estado;
@ManagedBean
@RequestScoped
public class EstadoDataList extends DataList<Estado> {
private static final String SQL_LIST = "select o from Estado o";
private static final String SQL_COUNT = "select count(o) from Estado o";
private Map<String, DataListFilter> filtros;
private String nomeEstado;
private String siglaEstado;
@Inject
private EntityManager em;
private static final long serialVersionUID = 1L;
public String getNomeEstado() {
return nomeEstado;
}
public void setNomeEstado(String nomeEstado) {
this.nomeEstado = nomeEstado;
}
public String getSiglaEstado() {
return siglaEstado;
}
public void setSiglaEstado(String siglaEstado) {
this.siglaEstado = siglaEstado;
}
@Override
protected Map<String, DataListFilter> getFiltros() {
filtros = new HashMap<String, DataListFilter>();
filtros.put("nome", new DataListFilter("AND o.nome like #{empty estadoDataList.nomeEstado ? null : estadoDataList.nomeEstado.concat('%')}"));
filtros.put("sigla", new DataListFilter("AND o.sigla = #{estadoDataList.siglaEstado}"));
return filtros;
}
@Override
protected String getSqlList() {
return SQL_LIST;
}
@Override
protected String getSqlCount() {
return SQL_COUNT;
}
}
No arquivo xhtml o código ficaria:
Fiz esse mecanismo como prova de conceito e exercício, embora tudo esteja funcional é preciso trabalhar melhor as classes. Abaixo vocês podem fazer o download das classes e do projeto eclipse. Utilizei como servidor o Jboss 7.0.1 final e como IDE o Eclipse Juno e Jboss tools instalado via Eclipse Market.
DataList
DataListFilter
EstadoDataList
StringUtil
ScriptBanco
Página com a grid
Projeto
