teichmann@6847: /* Copyright (C) 2011, 2012, 2013 by Bundesanstalt für Gewässerkunde teichmann@6847: * Software engineering by Intevation GmbH teichmann@6847: * teichmann@6847: * This file is Free Software under the GNU AGPL (>=v3) teichmann@6847: * and comes with ABSOLUTELY NO WARRANTY! Check out the teichmann@6847: * documentation coming with Dive4Elements River for details. teichmann@6847: */ teichmann@6847: teichmann@6847: package org.dive4elements.river.utils; teichmann@6847: teichmann@6847: import java.util.ArrayList; teichmann@6847: import java.util.HashMap; teichmann@6847: import java.util.Iterator; teichmann@6847: import java.util.List; teichmann@6847: import java.util.Map; teichmann@6847: teichmann@6847: import org.apache.log4j.Logger; teichmann@6847: import org.hibernate.SQLQuery; teichmann@6847: import org.hibernate.Session; teichmann@6847: teichmann@6847: /** To reduce the number of SQL queries send to the backend teichmann@6847: * (mainly by the fixings overviews) we execute them in batches of ids teichmann@6847: * and store the results in a small cache. teichmann@6847: * TODO: It currently relies on dynamic SQL. teichmann@6847: * Is there a way to use Hibernate with java.sql.Array teichmann@6847: * in cross database compatible manner? teichmann@6847: */ teichmann@6847: public abstract class BatchLoader { teichmann@6847: teichmann@6847: private static Logger log = Logger.getLogger(BatchLoader.class); teichmann@6847: teichmann@6847: public static final int BATCH_SIZE = 100; teichmann@6847: teichmann@6847: private Map loaded; teichmann@6847: private List rest; teichmann@6847: private Session session; teichmann@6847: private String sqlTemplate; teichmann@6847: teichmann@6847: public BatchLoader( teichmann@6847: List columns, teichmann@6847: Session session, teichmann@6847: String sqlTemplate teichmann@6847: ) { teichmann@6847: rest = new ArrayList(columns.size()); teichmann@6847: loaded = new HashMap(); teichmann@6847: this.session = session; teichmann@6847: this.sqlTemplate = sqlTemplate; teichmann@6847: teichmann@6847: // Insert in reverse order to minize searching. teichmann@6847: for (int i = columns.size()-1; i >= 0; --i) { teichmann@6847: rest.add(columns.get(i)); teichmann@6847: } teichmann@6847: } teichmann@6847: teichmann@6847: /** Searches for id and fill a batch to load containing the found id. */ teichmann@6847: private List prepareBatch(int id) { teichmann@6847: List batch = new ArrayList(BATCH_SIZE); teichmann@6847: teichmann@6847: boolean found = false; teichmann@6847: teichmann@6847: for (int i = rest.size()-1; batch.size() < BATCH_SIZE && i >= 0; --i) { teichmann@6847: Integer cid = rest.get(i); teichmann@6847: if (cid == id) { teichmann@6847: found = true; teichmann@6847: batch.add(cid); teichmann@6847: rest.remove(i); teichmann@6847: } teichmann@6847: else if ((found && batch.size() < BATCH_SIZE) teichmann@6847: || (!found && batch.size() < BATCH_SIZE-1)) { teichmann@6847: batch.add(cid); teichmann@6847: rest.remove(i); teichmann@6847: } teichmann@6847: } teichmann@6847: teichmann@6847: return batch; teichmann@6847: } teichmann@6847: teichmann@6847: /** Converts id to a list of comma separated ints. */ teichmann@6847: private static String idsAsString(List ids) { teichmann@6847: StringBuilder sb = new StringBuilder(); teichmann@6847: for (Iterator i = ids.iterator(); i.hasNext();) { teichmann@6847: sb.append(i.next()); teichmann@6847: if (i.hasNext()) { teichmann@6847: sb.append(','); teichmann@6847: } teichmann@6847: } teichmann@6847: return sb.toString(); teichmann@6847: } teichmann@6847: teichmann@6847: /** Get data for id. */ teichmann@6847: public T get(int id) { teichmann@6847: T already = loaded.get(id); teichmann@6847: if (already != null) { teichmann@6847: return already; teichmann@6847: } teichmann@6847: teichmann@6847: List batch = prepareBatch(id); teichmann@6847: if (batch.isEmpty()) { teichmann@6847: return null; teichmann@6847: } teichmann@6847: String sql = sqlTemplate.replace("$IDS", idsAsString(batch)); teichmann@6847: if (log.isDebugEnabled()) { teichmann@6847: log.debug(sql + " " + sql.length()); teichmann@6847: } teichmann@6847: fill(session.createSQLQuery(sql)); teichmann@6847: return get(id); teichmann@6847: } teichmann@6847: teichmann@6847: /** Call this from fill() to store data in the cache. */ teichmann@6847: protected void cache(int key, T data) { teichmann@6847: loaded.put(key, data); teichmann@6847: } teichmann@6847: teichmann@6847: /** Override this to fill the cache */ teichmann@6847: protected abstract void fill(SQLQuery query); teichmann@6847: }