package meetup.beeno;
import java.util.ArrayList;
import java.util.List;
import meetup.beeno.mapping.EntityInfo;
import meetup.beeno.mapping.EntityMetadata;
import meetup.beeno.mapping.MappingException;
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.client.ResultScanner;
import org.apache.hadoop.hbase.filter.Filter;
import org.apache.hadoop.hbase.filter.FilterList;
import org.apache.hadoop.hbase.filter.PageFilter;
import org.apache.log4j.Logger;
/**
* Provides a higher-level interface to retrieving stored HBase data than the HBase
* {@link org.apache.hadoop.hbase.client.Scanner} interface. Generation of the internal Scanner
* is based on the index information annotated in the entity class.
* @author garyh
*
*/
public class Query<T> {
public static Logger log = Logger.getLogger(Query.class);
protected EntityInfo entityInfo = null;
protected QueryOpts opts = null;
protected Criteria criteria = new Criteria();
protected Criteria indexCriteria = new Criteria();
protected EntityService<T> service = null;
public Query(EntityService<T> service, Class<? extends T> entityClass) throws MappingException {
this.service = service;
this.entityInfo = EntityMetadata.getInstance().getInfo(entityClass);
this.opts = new QueryOpts();
}
public QueryOpts getOptions() {
return this.opts;
}
public void setOptions(QueryOpts opts) {
this.opts = opts;
}
/**
* Sets the start key used for the query
* @param expression
* @return
*/
public Query<T> start(String key) {
this.opts.setStartKey(key);
return this;
}
/**
* Sets the start key used for the query
* @param expression
* @return
*/
public Query<T> start(byte[] keybytes) {
this.opts.setStartKey(keybytes);
return this;
}
/**
* Sets the timestamp portion to use in constructing
* start keys for scanners
*/
public Query<T> startTime(Long time) {
this.opts.setStartTime(time);
return this;
}
/**
* Sets the stop row key to be used in the generated <code>Scan</code>
* instance.
*/
public Query<T> stop(String key) {
this.opts.setStopKey(key);
return this;
}
/**
* Sets the stop row key to be used in the generated <code>Scan</code>
* instance.
*/
public Query<T> stop(byte[] key) {
this.opts.setStopKey(key);
return this;
}
/**
* Defines an expression to be used in filtering query results
* @param expression
* @return
*/
public Query<T> where(Criteria.Expression expression) {
this.criteria.add(expression);
return this;
}
/**
* Specifies an indexed expression to use for the query
* @return
* @throws HBaseException
*/
public Query<T> using(Criteria.Expression expression) {
this.indexCriteria.add(expression);
return this;
}
/**
* Sets the maximum number of items to retrieve
* @return
* @throws HBaseException
*/
public Query<T> limit(int size) {
this.opts.setPageSize(size);
return this;
}
public List<T> execute() throws HBaseException {
long t1 = System.nanoTime();
List<T> entities = new ArrayList<T>();
FilterList baseFilter = getCriteriaFilter(this.criteria.getExpressions());
ResultScanner scanner = null;
int processCnt = 0;
try {
scanner = getStrategy(baseFilter).createScanner();
for (Result res : scanner) {
processCnt++;
T entity = this.service.createFromRow(res);
if (entity != null)
entities.add( entity );
}
}
finally {
// always clean up scanner resources
if (scanner != null)
try { scanner.close(); } catch (Exception e) { log.error("Error closing scanner", e); }
}
long t2 = System.nanoTime();
log.info(String.format("HBASE TIMER: [%s] fetched %d records (processed %d) in %f msec.",
this.entityInfo.getEntityClass().getSimpleName(), entities.size(), processCnt, ((t2-t1)/1000000.0)));
return entities;
}
public T executeSingle() throws HBaseException {
// TODO: explicitly limit to 1 record in filter?
List<T> results = execute();
if (results != null && results.iterator().hasNext())
return (T) results.iterator().next();
return null;
}
protected QueryStrategy getStrategy(FilterList baseFilter) {
QueryStrategy strat = null;
if (!this.indexCriteria.isEmpty())
strat = new ScanByIndex(this.entityInfo, this.opts, this.indexCriteria, baseFilter);
else
strat = new ScanNoIndex(this.entityInfo, this.opts, baseFilter);
log.debug("Using strategy impl.: "+strat.getClass().getSimpleName());
return strat;
}
protected FilterList getCriteriaFilter(List<Criteria.Expression> expressions)
throws HBaseException {
FilterList filterset = new FilterList(FilterList.Operator.MUST_PASS_ALL, new ArrayList<Filter>());
for (Criteria.Expression e : expressions) {
filterset.addFilter( e.getFilter(this.entityInfo) );
}
if (this.opts.getPageSize() != -1 ) {
// add on any query option filters
if (log.isDebugEnabled())
log.debug(String.format("Adding PageFilter size=%d", this.opts.getPageSize()));
filterset.addFilter( new PageFilter(this.opts.getPageSize()) );
}
//return new WhileMatchRowFilter(filterset);
return filterset;
}
protected void debugFilter(StringBuilder str, Filter filter, int depth) {
if (depth == 0) {
str.append("Using filter:\n");
}
else {
for (int i=0; i<depth; i++)
str.append('\t');
}
str.append('[').append(filter.getClass().getSimpleName());
if (filter instanceof FilterList) {
str.append('\n');
}
str.append(']').append('\n');
}
}