/**
*
*/
package meetup.beeno;
import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import meetup.beeno.mapping.EntityInfo;
import meetup.beeno.mapping.IndexMapping;
import meetup.beeno.mapping.MappingException;
import meetup.beeno.util.HUtil;
import meetup.beeno.util.PBUtil;
import org.apache.hadoop.hbase.HConstants;
import org.apache.hadoop.hbase.client.Get;
import org.apache.hadoop.hbase.client.HTable;
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.client.ResultScanner;
import org.apache.hadoop.hbase.client.Scan;
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;
/**
* @author garyh
*
*/
public class ScanByIndex implements QueryStrategy {
private static Logger log = Logger.getLogger(ScanByIndex.class);
private final EntityInfo info;
private final QueryOpts opts;
private final Criteria indexConditions;
private final FilterList baseFilter;
public ScanByIndex( EntityInfo info, QueryOpts opts, Criteria indexConditions, FilterList baseFilter ) {
this.info = info;
this.opts = opts;
this.indexConditions = indexConditions;
this.baseFilter = baseFilter;
}
/* (non-Javadoc)
* @see com.meetup.db.hbase.QueryStrategy#createScanner(com.meetup.db.hbase.EntityMetadata.EntityInfo, org.apache.hadoop.hbase.filter.RowFilterInterface)
*/
@Override
public ResultScanner createScanner()
throws QueryException {
ResultScanner scanner = null;
try {
HTable table = null;
try {
table = HUtil.getTable(info.getTablename());
Criteria.PropertyExpression indexedExpr = selectIndexedExpression(info, indexConditions.getExpressions());
if (indexedExpr != null) {
log.debug("Using indexed expression: "+indexedExpr);
// add on while match filter for exit at end of index value
baseFilter.addFilter( Criteria.require(indexedExpr).getFilter(info) );
IndexMapping idx = info.getFirstPropertyIndex(indexedExpr.getProperty());
if (idx != null)
log.debug("Using index table: "+idx.getTableName());
byte[] startrow = getStartRow(opts, indexedExpr, idx);
//RowFilterInterface filter = addIndexFilters(baseFilter, startrow);
log.debug("Using filter: "+baseFilter);
long t1 = System.nanoTime();
scanner = getIndexScanner(idx.getTableName(),
startrow,
opts.getStopKey(),
baseFilter,
table,
null);
long t2 = System.nanoTime();
log.info(String.format("HBASE TIMER: created indexed scanner in %f msec.", ((t2-t1)/1000000.0)));
}
else {
log.warn("Creating non-indexed scanner. THIS MAY BE VERY SLOW!!!");
byte[] startrow = getStartRow(opts, null, null);
log.debug("Using filter: "+baseFilter);
long t1 = System.nanoTime();
Scan scan = new Scan();
if (startrow != null)
scan.setStartRow(startrow);
if (opts.getStopKey() != null)
scan.setStopRow(opts.getStopKey());
if (baseFilter != null)
scan.setFilter(baseFilter);
scanner = table.getScanner(scan);
long t2 = System.nanoTime();
log.info(String.format("HBASE TIMER: created scanner in %f msec.", ((t2-t1)/1000000.0)));
}
}
finally {
HUtil.releaseTable(table);
}
}
catch (HBaseException he) {
throw new QueryException(he);
}
catch (IOException ioe) {
throw new QueryException(ioe);
}
return scanner;
}
protected ResultScanner getIndexScanner(String tablename,
byte[] startrow,
byte[] stoprow,
Filter filter,
HTable baseTable,
byte[][] families)
throws IOException {
Scan idxScan = new Scan();
idxScan.setStartRow(startrow);
if (stoprow != null)
idxScan.setStopRow(stoprow);
if (filter != null)
idxScan.setFilter(filter);
HTable idxTable = null;
ResultScanner wrapper = null;
try {
idxTable = HUtil.getTable(tablename);
ResultScanner idxScanner = idxTable.getScanner(idxScan);
wrapper = new IndexScannerWrapper(idxScanner, baseTable, families);
}
finally {
if (idxTable != null)
HUtil.releaseTable(idxTable);
}
return wrapper;
}
/**
* Tries to find which expression in the list will be able to use a secondary index table
* for the query.
* @param expressions
* @return
* @throws MappingException
*/
protected Criteria.PropertyExpression selectIndexedExpression(EntityInfo info,
List<Criteria.Expression> expressions) throws MappingException {
// first look for a direct match expression
for (Criteria.Expression e : expressions) {
if (e instanceof Criteria.RequireExpression)
e = ((Criteria.RequireExpression)e).getRequired();
if (e instanceof Criteria.PropertyComparison) {
Criteria.PropertyComparison propExpr = (Criteria.PropertyComparison)e;
PropertyDescriptor prop = info.getProperty(propExpr.getProperty());
if (prop != null && info.getFirstPropertyIndex(prop) != null)
return propExpr;
log.warn("No index found for expression property: "+propExpr.getProperty());
}
}
// not property match found
// notifying user to avoid returning wrong result set
throw new MappingException(info.getClass(), "Can't find property " + expressions);
}
protected byte[] getStartRow(QueryOpts opts, Criteria.PropertyExpression expr, IndexMapping idx) throws HBaseException {
if (opts.getStartKey() != null) {
return opts.getStartKey();
}
if (expr == null || idx == null) {
return HConstants.EMPTY_START_ROW;
}
byte[] encValue = PBUtil.toBytes(expr.getValue());
EntityIndexer generator = idx.getGenerator();
return generator.createIndexKey(encValue, opts.getStartTime(), null);
}
protected Filter addIndexFilters(Filter baseFilter, byte[] startrow) {
if (startrow != null) {
List<Filter> orfilters = new ArrayList<Filter>(2);
// allow the start row to pass regardless
orfilters.add(new PageFilter(1));
orfilters.add(baseFilter);
FilterList filterset = new FilterList(FilterList.Operator.MUST_PASS_ONE, orfilters);
return filterset;
}
// nothing else to add
return baseFilter;
}
public static class IndexScannerWrapper implements ResultScanner {
private final ResultScanner indexScanner;
private final HTable baseTable;
private final byte[][] baseFamilies;
IndexScannerWrapper(ResultScanner indexScanner, HTable baseTable) {
this(indexScanner, baseTable, null);
}
IndexScannerWrapper(ResultScanner indexScanner, HTable baseTable, byte[][] families) {
this.indexScanner = indexScanner;
this.baseTable = baseTable;
this.baseFamilies = families;
}
@Override
public Iterator<Result> iterator() {
return new Iterator<Result>() {
// store next item to support look ahead
private Result next = null;
public boolean hasNext() {
if (next == null) {
try {
next = IndexScannerWrapper.this.next();
return next != null;
}
catch (IOException ioe) {
throw new RuntimeException(ioe);
}
}
return true;
}
public Result next() {
// use hasNext to advance
if (!hasNext())
return null;
// clear next pointer for next operation
Result tmp = next;
next = null;
return tmp;
}
/*
* Not supported
*/
public void remove() {
throw new UnsupportedOperationException("Not supported");
}
};
}
@Override
public void close() {
this.indexScanner.close();
HUtil.releaseTable(this.baseTable);
}
@Override
/**
* Advances the index scanner and reads the next record from the underlying table
*/
public Result next() throws IOException {
Result idxRow = this.indexScanner.next();
if (idxRow != null && !idxRow.isEmpty()) {
byte[] rowkey = idxRow.getValue(EntityIndexer.INDEX_FAMILY, EntityIndexer.INDEX_KEY_COLUMN);
if (rowkey != null && rowkey.length > 0) {
Get get = new Get(rowkey);
if (this.baseFamilies != null)
for (byte[] fam : this.baseFamilies)
get.addFamily(fam);
return this.baseTable.get(get);
}
else {
if (log.isDebugEnabled())
log.debug("No base record found for index key");
}
}
return null;
}
@Override
public Result[] next( int count ) throws IOException {
ArrayList<Result> results = new ArrayList<Result>(count);
int iter = 0;
while (iter < count) {
iter++;
Result next = next();
if (next == null)
break;
results.add(next);
}
return results.toArray(new Result[0]);
}
}
}