package org.molgenis.datatable.model;
import java.sql.Connection;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.IteratorUtils;
import org.apache.commons.collections.Predicate;
import org.apache.commons.collections.Transformer;
import org.molgenis.MolgenisFieldTypes;
import org.molgenis.framework.db.QueryRule;
import org.molgenis.framework.db.QueryRule.Operator;
import org.molgenis.framework.tupletable.AbstractFilterableTupleTable;
import org.molgenis.framework.tupletable.TableException;
import org.molgenis.model.elements.Field;
import org.molgenis.util.SimpleTuple;
import org.molgenis.util.Tuple;
import org.springframework.util.StringUtils;
import com.mysema.commons.lang.CloseableIterator;
import com.mysema.query.sql.SQLQuery;
import com.mysema.query.sql.SQLQueryImpl;
import com.mysema.query.sql.SQLTemplates;
import com.mysema.query.types.Expression;
import com.mysema.query.types.expr.BooleanExpression;
import com.mysema.query.types.expr.ComparableExpressionBase;
import com.mysema.query.types.expr.NumberExpression;
import com.mysema.query.types.expr.SimpleExpression;
import com.mysema.query.types.expr.StringExpression;
/**
* A class to allow the use of {@link SQLQuery}s on a TupleTable.
*/
public class QueryTable extends AbstractFilterableTupleTable
{
private final QueryCreator queryCreator;
private final Connection conn;
private final SQLTemplates dialect;
private final LinkedHashMap<String, Field> columnsByName;
private final List<String> hiddenColumns;
private CloseableIterator<Object[]> rs;
public QueryTable(final QueryCreator queryCreator, final Connection conn, final SQLTemplates dialect)
{
this.queryCreator = queryCreator;
this.conn = conn;
this.dialect = dialect;
columnsByName = new LinkedHashMap<String, Field>();
for (final Field col : queryCreator.getFields())
{
columnsByName.put(col.getSqlName(), col);
}
List<String> tmpHiddenFieldNames = queryCreator.getHiddenFieldNames();
hiddenColumns = tmpHiddenFieldNames == null ? Collections.<String> emptyList() : tmpHiddenFieldNames;
}
@Override
public List<Field> getAllColumns() throws TableException
{
return queryCreator.getFields();
}
public Field getColumnByName(String name)
{
return columnsByName.get(name);
}
private SQLQueryImpl createQuery() throws TableException
{
final SQLQueryImpl query = queryCreator.createQuery(conn, dialect);
final BooleanExpression where = convertQueryRulesToQueryExpression(query);
if (where != null)
{
query.where(where);
}
return query;
}
@SuppressWarnings("unchecked")
@Override
public Iterator<Tuple> iterator()
{
try
{
final SQLQueryImpl query = createQuery();
final LinkedHashMap<String, SimpleExpression<? extends Object>> select = queryCreator
.getAttributeExpressions();
CollectionUtils.select(select.entrySet(), new Predicate()
{
@Override
public boolean evaluate(Object arg0)
{
final Entry<String, SimpleExpression<? extends Object>> entry = (Entry<String, SimpleExpression<? extends Object>>) arg0;
return hiddenColumns.contains(entry.getKey());
}
});
final Expression<?>[] selectArray = select.values().toArray(new Expression<?>[0]);
final ArrayList<String> names = new ArrayList<String>(select.keySet());
for (int i = 0; i < selectArray.length; ++i)
{
final Expression<?> expr = selectArray[i];
final String alias = StringUtils.replace(names.get(i), ".", "_");
selectArray[i] = ((SimpleExpression<?>) expr).as(alias);
}
int limit = getLimit();
if (limit > 0)
{
query.limit(limit);
}
int offset = getOffset();
if (offset > 0)
{
query.offset(offset);
}
// TODO: replace by log4j if configured correctly
System.err.println(query.toString());
rs = query.iterate(selectArray);
return IteratorUtils.transformedIterator(rs, new Transformer()
{
@Override
public Object transform(Object o)
{
final Tuple tuple = new SimpleTuple();
final Object[] record = (Object[]) o;
try
{
int idx = 0;
for (final Field f : getColumns())
{
tuple.set(f.getSqlName(), record[idx]);
idx++;
}
}
catch (final TableException ex)
{
Logger.getLogger(QueryTable.class.getName()).log(Level.SEVERE, null, ex);
return null;
}
return tuple;
}
});
}
catch (final Exception ex)
{
Logger.getLogger(QueryTable.class.getName()).log(Level.SEVERE, null, ex);
return null;
}
}
@Override
public int getCount() throws TableException
{
return (int) createQuery().count();
}
@Override
public void close() throws TableException
{
if (rs != null)
{
rs.close();
}
}
@SuppressWarnings("unchecked")
private static BooleanExpression getExpression(QueryRule rule, final SimpleExpression<? extends Object> selectExpr,
final Field column) throws ParseException
{
final Operator op = rule.getOperator();
final MolgenisFieldTypes.FieldTypeEnum type = column.getType().getEnumType();
final String value = rule.getValue().toString();
BooleanExpression expr = null;
switch (type)
{
case DECIMAL:
{
final Double val = (Double) column.getType().getTypedValue(value);
switch (op)
{
case EQUALS:
expr = ((NumberExpression<Double>) selectExpr).eq(val);
break;
case LESS:
expr = ((NumberExpression<Double>) selectExpr).lt(val);
break;
case LESS_EQUAL:
expr = ((NumberExpression<Double>) selectExpr).loe(val);
break;
case GREATER:
expr = ((NumberExpression<Double>) selectExpr).gt(val);
break;
case GREATER_EQUAL:
expr = ((NumberExpression<Double>) selectExpr).goe(val);
break;
default:
throw new UnsupportedOperationException(String.format(
"Operation: %s not implemented yet for type %s!", op, type));
}
break;
}
case INT:
{
final Integer val = (Integer) column.getType().getTypedValue(value);
switch (op)
{
case EQUALS:
expr = ((NumberExpression<Integer>) selectExpr).eq(val);
break;
case LESS:
expr = ((NumberExpression<Integer>) selectExpr).lt(val);
break;
case LESS_EQUAL:
expr = ((NumberExpression<Integer>) selectExpr).loe(val);
break;
case GREATER:
expr = ((NumberExpression<Integer>) selectExpr).gt(val);
break;
case GREATER_EQUAL:
expr = ((NumberExpression<Integer>) selectExpr).goe(val);
break;
default:
throw new UnsupportedOperationException(String.format(
"Operation: %s not implemented yet for type %s!", op, type));
}
break;
}
case STRING:
{
final String val = (String) column.getType().getTypedValue(value);
switch (op)
{
case EQUALS:
expr = ((StringExpression) selectExpr).eq(val);
break;
case LIKE:
expr = ((StringExpression) selectExpr).like(val + "%");
break;
default:
throw new UnsupportedOperationException(String.format(
"Operation: %s not implemented yet for type %s!", op, type));
}
break;
}
default:
throw new UnsupportedOperationException(String.format("Operation: %s not implemented yet for type %s!",
op, type));
}
return expr;
}
@SuppressWarnings("rawtypes")
private BooleanExpression convertQueryRulesToQueryExpression(final SQLQueryImpl query) throws TableException
{
try
{
final LinkedHashMap<String, SimpleExpression<? extends Object>> select = queryCreator
.getAttributeExpressions();
final List<QueryRule> filters = super.getFilters();
BooleanExpression expr = null;
for (final QueryRule rule : filters)
{
final Operator op = rule.getOperator();
final String value = rule.getValue().toString();
final String fieldName = rule.getField() == null ? value : rule.getField();
if (op == Operator.SORTASC || op == Operator.SORTDESC)
{
final SimpleExpression<? extends Object> sortField = select.get(fieldName);
if (op == Operator.SORTASC)
{
query.orderBy(((ComparableExpressionBase) sortField).asc());
}
else
{
query.orderBy(((ComparableExpressionBase) sortField).desc());
}
}
else
{
final SimpleExpression<? extends Object> selectExpr = select.get(fieldName);
Field column = getColumnByName(fieldName);
final BooleanExpression rhs = getExpression(rule, selectExpr, column);
if (expr != null)
{
expr = expr.and(rhs);
}
else
{
expr = rhs;
}
}
}
return expr;
}
catch (final Exception ex)
{
throw new TableException(ex);
}
}
@Override
public int getColCount() throws TableException
{
// TODO Auto-generated method stub
return 0;
}
@Override
public int getColLimit()
{
// TODO Auto-generated method stub
return 0;
}
@Override
public void setColLimit(int limit)
{
// TODO Auto-generated method stub
}
@Override
public int getColOffset()
{
// TODO Auto-generated method stub
return 0;
}
@Override
public void setColOffset(int offset)
{
// TODO Auto-generated method stub
}
}