package javango.contrib.hibernate;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javango.contrib.i18n.I18NProvider;
import javango.db.ManagerException;
import javango.db.QuerySet;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.ScrollableResults;
import org.hibernate.criterion.DetachedCriteria;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
public class HibernateQuerySet<T> implements QuerySet<T>, Iterable<T> {
protected Map<String, Object> valueFilters = new LinkedHashMap<String, Object>();
protected Map<String, String> propertyFilters = new LinkedHashMap<String, String>();
protected List<String> orderbys = new ArrayList<String>();
protected Integer first;
protected Integer last;
protected Class<? extends T> clazz;
@Inject
protected I18NProvider i18n;
private String _(String format, Object... args) {
if (i18n == null) {
return String.format(format, args);
}
return i18n.getText(format, args);
}
// cached local copy of my criteria, as I am immutable this can be cached once created
private Criteria _criteria;
HibernateQuerySetHelper qsHelper;
// cached local copy of my count, as I am immutable this can be cached once known.
// subsequent calls to count() will always return the same value even if the underlying db changes.
// i guess this could cause strangeness if the db was updated. oh well DDN
Long _count;
HibernateUtil hibernateUtil;
protected HibernateQuerySet<T> newInstance() throws ManagerException {
return new HibernateQuerySet<T>(hibernateUtil, clazz);
}
@Override
public QuerySet<T> clone() {
try {
return newInstance().cloneFrom(this);
} catch (ManagerException e) {
LogFactory.getLog(HibernateQuerySet.class).error(e,e);
}
return null;
}
// clone any properties from this object to the other object. Returns this
protected HibernateQuerySet<T> cloneFrom(HibernateQuerySet<T> other) throws ManagerException {
this.valueFilters.putAll(other.valueFilters);
this.propertyFilters.putAll(other.propertyFilters);
this.orderbys.addAll(other.orderbys);
this.first = other.first;
this.last = other.last;
return this;
}
@AssistedInject
public HibernateQuerySet(HibernateUtil hibernateUtil, @Assisted Class<? extends T> clazz) throws ManagerException {
super();
this.hibernateUtil = hibernateUtil;
this.clazz = clazz;
}
public QuerySet<T> filter(Map<String, ?> params, boolean ignoreNulls) throws ManagerException {
return mapFilter(params, ignoreNulls);
}
public QuerySet<T> mapFilter(Map<String, ?> params, boolean ignoreNulls) throws ManagerException {
HibernateQuerySet<T> other = newInstance().cloneFrom(this);
// ignore null values, this will change in .34
// other.filters.putAll(params);
for(Entry<String, ?> entry : params.entrySet()) {
if (!ignoreNulls || entry.getValue() != null) other.valueFilters.put(entry.getKey(), entry.getValue());
}
return other;
}
public QuerySet<T> filter(Map<String, ?> params) throws ManagerException {
return mapFilter(params,true);
}
public QuerySet<T> mapFilter(Map<String, ?> params) throws ManagerException {
return mapFilter(params, true);
}
public QuerySet<T> filter(Object params) throws ManagerException {
if (params instanceof Map) {
return mapFilter((Map)params);
}
HibernateQuerySet<T> other = newInstance().cloneFrom(this);
try {
Class c = params.getClass();
java.lang.reflect.Field[] fields = c.getDeclaredFields();
for (int i=0; i<fields.length; i++) {
String fieldName = fields[i].getName();
Object value = PropertyUtils.getProperty(params, fieldName);
if (value != null) other.valueFilters.put(fieldName, value);
}
} catch (IllegalAccessException e) {
LogFactory.getLog(HibernateQuerySet.class).error(e,e);
throw new ManagerException(e);
} catch (InvocationTargetException e) {
LogFactory.getLog(HibernateQuerySet.class).error(e,e);
throw new ManagerException(e);
} catch (NoSuchMethodException e) {
LogFactory.getLog(HibernateQuerySet.class).error(e,e);
throw new ManagerException(e);
}
return other;
}
public QuerySet<T> filter(String property, Object value) throws ManagerException {
HibernateQuerySet<T> other = newInstance().cloneFrom(this);
other.valueFilters.put(property, value);
return other;
}
public QuerySet<T> filterByProperty(String propertyA, String propertyB) throws ManagerException {
HibernateQuerySet<T> other = newInstance().cloneFrom(this);
other.propertyFilters.put(propertyA, propertyB);
return other;
}
public QuerySet<T> limit(int first, int last) throws ManagerException {
HibernateQuerySet<T> other = newInstance().cloneFrom(this);
other.first = first;
other.last = last;
return other;
}
public T get() throws ManagerException {
try {
return (T)getCriteria().uniqueResult();
} catch (HibernateException e) {
LogFactory.getLog(HibernateQuerySet.class).error(_("Hibernate Exception 'get()' while running '%s'", this.toString()));
throw new ManagerException(e);
}
}
public List<T> list() throws ManagerException {
try {
return getCriteria().list();
} catch (HibernateException e) {
LogFactory.getLog(HibernateQuerySet.class).error(_("Hibernate Exception 'list()' while running '%s'", this.toString()));
LogFactory.getLog(HibernateQuerySet.class).error(e,e);
LogFactory.getLog(HibernateQuerySet.class).error(e,e.getCause());
throw new ManagerException("Unable to run query");
}
}
public static class QuerySetIterator<T> implements Iterator<T> {
public QuerySetIterator(ScrollableResults scrollableResult) {
this.scrollableResult = scrollableResult;
}
private ScrollableResults scrollableResult = null;
private Object[] nextValue; // for some reason isLast returns false on empty lists..
public boolean hasNext() {
if (nextValue != null) {
return true;
}
boolean found = scrollableResult.next();
if (!found) return false;
nextValue = scrollableResult.get();
return nextValue != null;
}
@SuppressWarnings("unchecked")
public T next() {
if (nextValue == null || nextValue.length == 0) {
return null;
}
T v = (T)nextValue[0];
nextValue = null;
return v;
}
public void remove() {
throw new UnsupportedOperationException("remove not supported");
}
}
public Iterator<T> iterator() {
try {
return new QuerySetIterator<T>(getCriteria().scroll());
} catch (ManagerException e) {
LogFactory.getLog(HibernateQuerySet.class).error(_("Hibernate Exception on 'iterator()' while running '%s'", this.toString()));
LogFactory.getLog(HibernateQuerySet.class).error(e,e);
throw new RuntimeException(e);
}
}
public QuerySet<T> orderBy(String... orderbys) throws ManagerException {
HibernateQuerySet<T> other = newInstance().cloneFrom(this);
for(String order : orderbys) {
other.orderbys.add(order);
}
return other;
}
// public QuerySet<T> or(QuerySet<T> _orOther) throws ManagerException {
// if (_orOther instanceof HibernateQuerySet) {
// HibernateQuerySet<T> orOther = (HibernateQuerySet<T>)_orOther;
// if (orOther.clazz != this.clazz) {
// throw new ManagerException("ORs only supported for the same model");
// }
// HibernateQuerySet<T> other = new HibernateQuerySet<T>(this);
// other.ors = new ArrayList<HibernateQuerySet>();
// other.ors.add(this);
// other.ors.add(orOther);
// return other;
// }
// throw new ManagerException("ORs only supported with HibernateQuerySets");
// }
// impelemtation specific functions
public long count() throws ManagerException {
if (_count == null) {
try {
_count = new Long(hibernateUtil.doCount(getRawCriteria().getExecutableCriteria(hibernateUtil.getSession())));
} catch (HibernateException e) {
LogFactory.getLog(HibernateQuerySet.class).error(_("Hibernate Exception while running %s", this.toString()));
throw new ManagerException(e);
}
}
return _count;
}
/**
* Returns a Criteria prior to any limit or orders being applied.
*
* @return
* @throws ManagerException
*/
public DetachedCriteria getRawCriteria() throws ManagerException {
DetachedCriteria dc = DetachedCriteria.forClass(clazz);
qsHelper = new HibernateQuerySetHelper(hibernateUtil, clazz, dc);
qsHelper.updateCriteria(valueFilters);
qsHelper.updateCriteriaFromPropertyMap(propertyFilters);
return dc;
}
/**
* Returns a hibernate criteria representing this queryset.
*
* @return
*/
public org.hibernate.Criteria getCriteria() throws ManagerException {
if (_criteria != null) return _criteria;
_criteria = getRawCriteria().getExecutableCriteria(hibernateUtil.getSession());
if (this.first != null) {
_criteria.setFirstResult(first < 0 ? 0 : first);
if (this.last != null) {
int count = this.last - this.first;
_criteria.setMaxResults(count > 0 ? count : 1);
}
} else if (this.last != null) {
_criteria.setMaxResults(this.last > 0 ? this.last : 1);
}
for (String s : orderbys) {
qsHelper.addOrderBy(s);
}
return _criteria;
}
@Override
public String toString() {
return "HibernateQuerySet [valueFilters=" + valueFilters.toString()
+ ", propertyFilters=" + propertyFilters.toString() + ", orderbys="
+ orderbys + ", first=" + first + ", last=" + last + "]";
}
public Class<? extends T> getType() {
return this.clazz;
}
//
// void getPropertyInformation(String path) {
// String[] parts = path.split("__");
//
// QuerySet.Lookup lookupType = QuerySet.Lookup.EQ; // DEFAULT
// if (parts.length > 1) {
// String match = parts[parts.length-1];
// for(QuerySet.Lookup lookup : QuerySet.Lookup.values()) {
// if (lookup.equals(match)) {
// lookupType = lookup;
// break;
// }
// }
// }
//
//
// }
}