package com.googlecode.tawus.hibernate.internal.services;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.hibernate.Criteria;
import org.hibernate.EntityMode;
import org.hibernate.Session;
import org.hibernate.criterion.Conjunction;
import org.hibernate.criterion.CriteriaSpecification;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Disjunction;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Restrictions;
import org.hibernate.criterion.SimpleExpression;
import org.hibernate.engine.SessionImplementor;
import org.hibernate.metadata.ClassMetadata;
import org.hibernate.type.Type;
import com.googlecode.tawus.SearchCriteria;
import com.googlecode.tawus.hibernate.annotations.DefaultOrder;
import com.googlecode.tawus.hibernate.services.SearchCriteriaConverter;
import com.googlecode.tawus.search.Condition;
import com.googlecode.tawus.search.ConditionType;
import com.googlecode.tawus.search.ICondition;
import com.googlecode.tawus.search.SearchType;
public class HibernateSearchCriteriaConverter implements SearchCriteriaConverter {
private static class Context {
private Map<String, Object> valueMap = new HashMap<String, Object>();
private Map<String, String> aliasMap = new HashMap<String, String>();
public void setValue(String name, Object value){
valueMap.put(name, value);
}
public Map<String, Object> getValueMap(){
return valueMap;
}
public void setAlias(String name, String alias){
aliasMap.put(name, alias);
}
public Object getValue(String name){
return valueMap.get(name);
}
public String getAlias(String name){
return aliasMap.get(name);
}
}
public Criteria toCriteria(SearchCriteria<?> sp, Session session, boolean sort, boolean paginate) {
final Criteria criteria = session.createCriteria(sp.getType());
criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
//If paginate is specified, then paginate
if (paginate && sp.getFirstResult() >= 0 && sp.getMaxResults() > 0) {
criteria.setFirstResult(sp.getFirstResult());
criteria.setMaxResults(sp.getMaxResults());
}
Context context = new Context();
loadPropertyValues(sp.getEntity(), null, session, context);
ICondition condition = sp.getCondition();
if (condition == null) {
List<ICondition> conditions = new ArrayList<ICondition>();
for (String propertyName : context.getValueMap().keySet()) {
conditions.add(Condition.eq(propertyName));
}
condition = Condition.and(conditions.toArray(new ICondition[] {}));
}
if (condition != null) {
Criterion c = applyCondition(criteria, condition, sp.getSearchType(), context);
if (c != null) {
criteria.add(c);
}
}
if (sort) {
Map<String, Boolean> sorting = sp.getOrder();
if (sorting == null || sorting.size() == 0) {
DefaultOrder annotation = sp.getType().getAnnotation(DefaultOrder.class);
if (annotation != null) {
String colAlias = getPropertyName(criteria, annotation.column(), context);
criteria.addOrder(annotation.ascending() ? Order.asc(colAlias)
: Order.desc(colAlias));
}
} else {
for (String col : sorting.keySet()) {
boolean order = sorting.get(col);
String colAlias = getPropertyName(criteria, col, context);
criteria.addOrder(order ? Order.asc(colAlias) : Order.desc(colAlias));
}
}
}
return criteria;
}
Criterion applyCondition(final Criteria criteria, final ICondition condition, SearchType searchType,
Context context) {
Criterion c = null;
if (condition.isJoin()) {
switch (condition.getType()) {
case AND:
c = Restrictions.conjunction();
for (Object object : condition.getArgs()) {
Criterion child = applyCondition(criteria, (ICondition) object, searchType, context);
if (child != null) {
((Conjunction) c).add(child);
}
}
break;
case OR:
c = Restrictions.disjunction();
for (Object object : condition.getArgs()) {
Criterion child = applyCondition(criteria, (ICondition) object, searchType, context);
if (child != null) {
((Disjunction) c).add(child);
}
}
break;
case NOT:
c = Restrictions.not(applyCondition(criteria,
(ICondition) condition.getArgs().get(0), searchType, context));
break;
}
} else {
String firstArg = getPropertyName(criteria, (String) condition.getArgs().get(0), context);
int argCount = condition.getArgs().size();
Object secondArg = null;
if (condition.getType() == ConditionType.NOT_NULL
|| condition.getType() == ConditionType.IS_NULL) {
// do nothing
} else if (argCount == 1) {
secondArg = context.getValue((String) condition.getArgs().get(0));
if (secondArg == null) {
return null;
}
} else {
if (condition.isProperty()) {
secondArg = getPropertyName(criteria, (String) condition.getArgs().get(1), context);
} else {
secondArg = condition.getArgs().get(1);
if (secondArg == null) {
return null;
}
}
}
switch (condition.getType()) {
case EQ:
if ((argCount == 1) && (searchType == SearchType.Like)
&& (secondArg instanceof String)) {
c = Restrictions.like(firstArg, "%" + secondArg + "%");
} else {
c = argCount == 1 || !condition.isProperty() ? Restrictions.eq(firstArg,
secondArg) : Restrictions.eqProperty(firstArg, (String) secondArg);
}
break;
case NE:
c = argCount == 1 || !condition.isProperty() ? Restrictions.ne(firstArg, secondArg)
: Restrictions.neProperty(firstArg, (String) secondArg);
break;
case LE:
c = argCount == 1 || !condition.isProperty() ? Restrictions.le(firstArg, secondArg)
: Restrictions.leProperty(firstArg, (String) secondArg);
break;
case LT:
c = argCount == 1 || !condition.isProperty() ? Restrictions.lt(firstArg, secondArg)
: Restrictions.ltProperty(firstArg, (String) secondArg);
break;
case GE:
c = argCount == 1 || !condition.isProperty() ? Restrictions.ge(firstArg, secondArg)
: Restrictions.geProperty(firstArg, (String) secondArg);
break;
case GT:
c = argCount == 1 || !condition.isProperty() ? Restrictions.gt(firstArg, secondArg)
: Restrictions.gtProperty(firstArg, (String) secondArg);
break;
case LIKE:
c = Restrictions.like(firstArg, secondArg);
break;
case NOT_LIKE:
c = Restrictions.not(Restrictions.like(firstArg, secondArg));
break;
case IS_NULL:
c = Restrictions.isNull(firstArg);
break;
case NOT_NULL:
c = Restrictions.isNotNull(firstArg);
break;
}
if (c != null && SimpleExpression.class.isAssignableFrom(c.getClass())) {
if (secondArg instanceof String) {
c = ((SimpleExpression) c).ignoreCase();
}
}
}
return c;
}
String getPropertyName(final Criteria criteria, final String expr, Context context) {
if (!expr.contains(".")) {
return expr;
}
final String[] beans = expr.split("\\.");
String beanAlias = "";
String beanName = "";
for (int i = 0; i < beans.length - 1; ++i) {
if (beanAlias != "") {
beanAlias += "_" + beans[i];
beanName += "." + beans[i];
} else {
beanAlias = beans[i];
beanName = beans[i];
}
if (context.getAlias(beanName) == null) {
context.setAlias(beanName, beanAlias);
criteria.createAlias(beanName, beanAlias, CriteriaSpecification.LEFT_JOIN);
}
}
return beanAlias + "." + beans[beans.length - 1];
}
void loadPropertyValues(Object object, String namePrefix, Session session, Context context) {
final ClassMetadata meta = getMetadata(object, session);
assert meta != null;
final String[] propertyNames = meta.getPropertyNames();
String idName = meta.getIdentifierPropertyName();
if (idName != null) {
final Serializable id = meta.getIdentifier(object, (SessionImplementor) session);
if (id != null) {
if (namePrefix != null) {
idName = namePrefix + "." + idName;
}
context.setValue(idName, id);
if(namePrefix != null){
return; //If it is a nested object and id is given, don't bother to add other properties
//of the nested object
}
}
}
for (int i = 0; i < propertyNames.length; ++i) {
final Type type = meta.getPropertyType(propertyNames[i]);
final Object value = meta.getPropertyValue(object, propertyNames[i], EntityMode.POJO);
if (value == null) {
continue;
}
if (value instanceof Number && ((Number) value).longValue() == 0) {
continue;
}
String name;
if (namePrefix == null) {
name = propertyNames[i];
} else {
name = namePrefix + "." + propertyNames[i];
}
if (type.isCollectionType()) {
Collection<?> val = (Collection<?>) value;
if (val.size() == 1) {
Iterator<?> iterator = val.iterator();
Object obj = iterator.next();
if (obj != null) {
loadPropertyValues(obj, name, session, context);
}
}
} else if (type.isEntityType()) {
loadPropertyValues(value, name, session, context);
} else {
context.setValue(name, value);
}
}
}
//For testing purposes set the scope to package
ClassMetadata getMetadata(Object object, Session session) {
return session.getSessionFactory().getClassMetadata(object.getClass());
}
}