package com.im.imjutil.query;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.im.imjutil.exception.QueryException;
import com.im.imjutil.exception.ValidationException;
import com.im.imjutil.util.Filter;
import com.im.imjutil.util.Pair;
import com.im.imjutil.validation.Convert;
import com.im.imjutil.validation.Validator;
/**
* Classe abstrata que implementa a base para geracao de consulta
* personalizadas do tipo {@link Query} para consultas do JPA.
* <br>
* Deve-se extender essa classe e sobrescrever o seu metodo
* {@link AbstractJPAQuery#createQuery(Filter...)} para obter
* suas funcionalidades.
* <br>
* Para constucao de um decorador, usar a seguinte sintaxe na sobrescrita:
* <code>
* <pre>
* public String createQuery(Filter... filters) throws QueryException {
* // Adiciona a clausula personalizada
* super.addClause("t.name IN ('AA', 'BB')");
* super.addClause("t.number == :number");
*
* if (super.hasDecorator()) {
* // Retorna a chamada para o decorador
* return super.decorate.createQuery(filters);
* }
* // Retorna a chamada para o superior
* return super.createQuery(filters);
* }
* </pre>
* </code>
*
* @param <T> O tipo T alvo da consulta.
*
* @author Felipe Zappala
*/
public abstract class AbstractJPAQuery<T> implements Query {
/*
*
* http://www.jpox.org/docs/1_2/jpa/jpql.html
* http://www.jpox.org/docs/1_2/jpa/query.html
* http://java.dzone.com/articles/jpa-20-concurrency-and-locking
SELECT [<result>]
[FROM <candidate-class(es)>]
[WHERE <filter>]
[GROUP BY <grouping>]
[HAVING <having>]
[ORDER BY <ordering>]
*/
/** Clausulas where personalizadas. */
private final List<String> clauses;
/** Campos de ordenacao personalizada. */
private final List<String> ordering;
/** A consulta base padrao. */
private final StringBuilder query;
/** Campos validos para uso no objeto algo. */
@SuppressWarnings("unused")
private final Set<String> availableFields;
/** A classe do objeto alvo do tipo T da consulta. */
private Class<T> clazz;
/** A classe decorada pela implementacao. */
protected final AbstractJPAQuery<T> decorator;
/**
* Construtor padrao das consultas do tipo JPA.
* <br>
* Esse inicializa uma consulta padrao do tipo:
* <code>SELECT t FROM T t</code>
*
* @param clazz A classe alvo do tipo T da consulta.
*/
protected AbstractJPAQuery(Class<T> clazz, AbstractJPAQuery<T> decorator) {
if (clazz == null) {
throw new ValidationException("A classe da entidade e' nula.");
}
this.decorator = decorator;
this.clazz = clazz;
this.query = new StringBuilder();
this.clauses = new ArrayList<String>();
this.ordering = new ArrayList<String>();
this.availableFields = getAvailableFields();
}
/**
* Cria a consulta base padrao para todas as consultas.
*
* @return A consulta base padrao.
*/
protected void createBaseQuery() {
query.append("SELECT t FROM ");
query.append(clazz.getSimpleName());
query.append(" AS t");
}
/**
* Configura a consulta de base a ser usada na cricao da consulta.
*
* @param query A consulta de base.
*/
protected final void setQueryBase(String query) {
if (Validator.isValid(query)) {
String className = clazz.getSimpleName();
if (!query.contains(className)) {
throw new ValidationException(Convert.toString("Consulta '",
query, "' invalida para o tipo: ", className));
}
this.query.setLength(0);
this.query.append(query);
}
}
/**
* Adiciona as clausulas whare personalizadas na consulta.
* Formato esperado: <code>t.fieldName = :fieldValue</code>
*
* @param clauses As clausulas whare personalizadas.
*/
protected final void addWhere(String... clauses) {
if (!Validator.isEmpty(clauses)) {
for (String clause : clauses) {
this.clauses.add(clause);
}
}
}
/**
* Adiciona campos de ordenacao na consulta.
* Formato esperado: <code>t.fieldName</code>
*
* @param fields Os campos a ordenar
*/
protected final void addOrderBy(String... fields) {
if (!Validator.isEmpty(fields)) {
for (String field : fields) {
this.ordering.add(field);
}
}
}
/**
* Verifica se a consulta possui um decorador.
*
* @return Verdadeiro caso o decorador for diferente de nulo.
*/
public final boolean isDecorator() {
return this.decorator != null;
}
@Override
public String createQuery(Filter... filters) throws QueryException {
throw new UnsupportedOperationException(
"Classe com implementacao ainda nao finalizada.");
/*
// Cria a consulta de base
createBaseQuery();
// Obtem um filtro unificado
Filter filter = getUnifiedFilter(filters);
// Caso existir filtros, cria a clausula whare.
if (!filter.isEmpty()) {
// Obtem os campos disponiveis na classe alvo
query.append(" WHERE ");
if (!this.clauses.isEmpty()) {
// Adiciona as clausulas no where
for (String clause : this.clauses) {
query.append("(").append(clause).append(")");
query.append(filter.isExclusive() ? " AND " : " OR ");
}
query.setLength(query.length() -5);
// Remove os filtros usados nas clausulas personalizadas
removeFilterInClauses(filter);
}
// Adiciona os filtros restantes no where
if (!filter.isEmpty()) {
for (Pair<String, Object> pair : filter) {
// Valida se o campo do filtro esta disponivel no objeto alvo
if (!availableFields.contains(pair.getFirst())) {
throw new ValidationException(Convert.toString(
"Campo invalido para filtro da classe ",
clazz.getSimpleName(), ": ", pair.getFirst()));
}
query.append("o.").append(pair.getFirst());
query.append((pair.getLast() instanceof String)?" LIKE ":" = ");
query.append(":").append(pair.getFirst());
query.append(filter.isExclusive() ? " AND " : " OR ");
}
query.setLength(query.length() -5);
}
}
// Retorna a consulta JPA resultante
return query.toString();
*/
}
/**
* Obtem os campos disponiveis para filtro na classe alvo.
*
* @return O conjunto de campos.
*/
private Set<String> getAvailableFields() {
Field[] fields = clazz.getDeclaredFields();
Set<String> availableFields = new HashSet<String>(fields.length);
for (Field field : fields) {
availableFields.add(field.getName());
}
return availableFields;
}
/**
* Remove os filtros contidos nas clausulas whare personalizadas,
* para que sejam usados uma unica vez na montagem da consulta.
*
* @param filter O filtro a ser removido.
*/
@SuppressWarnings("unused")
private void removeFilterInClauses(Filter filter) {
if (!filter.isEmpty()) {
List<String> fields = new ArrayList<String>();
// Marca os campos a serem removidos
for (String clause : this.clauses) {
for (Pair<String, Object> pair : filter) {
if(clause.contains(pair.getFirst())) {
fields.add(pair.getFirst());
}
}
}
// Remove os campos do filtro
for (String field : fields) {
filter.removeFilter(field);
}
}
}
/**
* Obtem um filtro que unifica todos os filtros passados.
*
* @param filters Os filtros a serem unificados.
* @return O filtro unificado
*/
@SuppressWarnings("unused")
private Filter getUnifiedFilter(Filter... filters) {
Filter filter = new Filter();
if (!Validator.isEmpty(filters)) {
for (Filter f : filters) {
filter.addFilters(f);
if (f.isExclusive()) {
filter.setExclusive();
} else {
filter.setInclusive();
}
}
}
return filter;
}
/*
public static void main(String[] args) {
class P {
public String name = "";
public int age = 0;
}
P p1 = new P(); p1.name = "Joao"; p1.age = 5;
P p2 = new P(); p2.name = "Maria"; p1.age = 3;
AbstractJPAQuery<P> q1 = new AbstractJPAQuery<P>(P.class, null) {
@Override
public String createQuery(Filter... filters) throws QueryException {
String clause1 = "t.name IN ('Maria', 'Jose')";
String clause2 = "t.age == :age";
if (this.isDecorator()) {
// super.decorator.addWhereClauses(clause1, clause2);
return super.decorator.createQuery(filters);
}
// super.addWhereClauses(clause1, clause2);
return super.createQuery(filters);
}
};
AbstractJPAQuery<P> q2 = new AbstractJPAQuery<P>(P.class, q1) {
@Override
public String createQuery(Filter... filters) throws QueryException {
String clause = "t.name LIKE :name";
// if (this.hasDecorator()) {
// super.decorator.addClause(clause);
// return super.decorator.createQuery(filters);
// }
// super.addClause(clause);
return super.createQuery(filters);
}
};
AbstractJPAQuery<P> q3 = new AbstractJPAQuery<P>(P.class, null) {
@Override
public String createQuery(Filter... filters) throws QueryException {
// super.addClause("t.name IN ('Maria', 'Jose') ORDER BY t.name");
return super.createQuery(filters);
}
};
AbstractJPAQuery<P> q4 = new AbstractJPAQuery<P>(P.class, null) {
@Override
protected void createBaseQuery() {
// super.query.append(b)
}
@Override
public String createQuery(Filter... filters) throws QueryException {
// super.addClause("t.name IN ('Maria', 'Jose') ORDER BY t.name");
return super.createQuery(filters);
}
};
//SELECT e FROM Event AS e, IN(e.places) p, IN(p.address) a, IN(a.city) c, IN(c.state) s where s = :state
String query = q3.createQuery(new Filter()
.addFilter("name", "Maria")
.addFilter("age", 5)
.setInclusive());
System.out.println(query);
}
// public static void main(String[] args) {
// EntityManager manager = Persistence.createEntityManagerFactory("jpa-dindin").createEntityManager();
//
// //ErrorCode e = manager.find(ErrorCode.class, 1);
//
//// javax.persistence.Query q = manager.createQuery("SELECT e FORM ErrorCode e WHERE e.description LIKE :desc");
//// q.setParameter("desc", "%INVaLIDO%");
//
// javax.persistence.Query q = manager.createQuery("SELECT e FROM ErrorCode e " +
// "WHERE (e.code = :code1) OR (e.code = :code2)");
// q.setParameter("code1", 1);
// q.setParameter("code2", 2);
//
// List l = q.getResultList();
//
// System.out.println(l);
// System.out.println(l.size());
// }
*/
/*
protected <T> List<T> findAll(Class<T> clazz,
EntityManager entityManager, Filter filter) {
if (clazz == null) {
throw new ValidationException("A classe da entidade e' nula.");
}
if (entityManager == null) {
throw new ValidationException("O EntityManager e' nulo.");
}
if (filter == null) {
throw new ValidationException("O filtro e' nulo.");
}
// Prepara a consulta
Query query;
StringBuilder sql = new StringBuilder();
sql.append("SELECT o FROM ");
sql.append(clazz.getSimpleName());
sql.append(" AS o");
// Obtem os filtros disponiveis
Set<String> availableFields = new HashSet<String>();
for (Field field : clazz.getDeclaredFields()) {
availableFields.add(field.getName());
}
// Associa os filtros
if (!filter.isEmpty()) {
sql.append(" WHERE ");
for (Pair<String, Object> pair : filter) {
if (!availableFields.contains(pair.getFirst())) {
throw new ValidationException(
"Campo invalido para filtro da classe "
+ clazz.getSimpleName() + ": " + pair.getFirst());
}
sql.append("o.").append(pair.getFirst());
sql.append((pair.getLast() instanceof String) ?" LIKE ":" = ");
sql.append(":").append(pair.getFirst());
sql.append(" AND ");
}
// Remove o ultimo ' AND '
sql.setLength(sql.length() -5);
}
//Cria a consulta
query = entityManager.createQuery(sql.toString());
// Associa os valores
for (Pair<String, Object> pair : filter) {
if (pair.getLast() instanceof String) {
query.setParameter(pair.getFirst(), new StringBuilder("%")
.append(pair.getLast()).append("%").toString());
} else {
query.setParameter(pair.getFirst(), pair.getLast());
}
}
// Executa a consulta
return query.getResultList();
}
*/
// public static void main(String[] args) {
// EntityManagerFactory factory = Persistence.createEntityManagerFactory("jpa-plugin");
// EntityManager manager = factory.createEntityManager();
//
// //Seleciona eventos pelo estado
// String sql = "SELECT e FROM Event AS e, IN(e.places) p, IN(p.address) a, IN(a.city) c, IN(c.state) s where s = :state";
//
// Query query = manager.createQuery(sql);
// query.setParameter("state", StateManager.get(1));
// //query.setParameter("id", 1);
//
// System.out.println(query.getResultList());
// System.out.println(query.getResultList().size());
//
// manager.close();
// factory.close();
// }
}
/*
// Exemplo de classe
public class QueryUserByAddress extends AbstractJPAQuery<CSVMaker> {
public QueryUserByAddress() {
super(CSVMaker.class, null);
}
public QueryUserByAddress(AbstractJPAQuery<CSVMaker> decorate) {
super(CSVMaker.class, decorate);
}
@Override
public String createQuery(Filter... filters) throws QueryException {
// Adiciona a clausula personalizada
// super.addClause("t.name IN ('AA', 'BB')");
// super.addClause("t.number == :number");
//
// if (this.hasDecorator()) {
// // Retorna a chamada para o decorador
// return super.decorator.createQuery(filters);
// }
// Retorna a chamada para o superior
return super.createQuery(filters);
}
public class QueryUserByName extends AbstractJPAQuery<Filter> {
@SuppressWarnings("unused")
private AbstractJPAQuery<Filter> decorate;
public QueryUserByName() {
super(Filter.class, null);
}
public QueryUserByName(AbstractJPAQuery<Filter> decorate) {
super(Filter.class, null);
this.decorate = decorate;
}
@Override
public String createQuery(Filter... filters) throws QueryException {
// addClause("a = b");
// addClause("asdfasdf");
// addClause("asdfasdf");
// if (this.decorate != null) {
// return this.decorate.createQuery(filters);
// }
return super.createQuery(filters);
}
public static void main(String[] args) {
// Query q = new QueryUserByName(new QueryUserByAddress());
//
// String sql = q.createQuery(new Filter().addFilter("a", "b"));
//
// System.out.println(sql);
}
}
*/