package me.prettyprint.cassandra.service;
import com.google.common.collect.Iterators;
import com.google.common.collect.PeekingIterator;
import java.util.Iterator;
import java.util.List;
import me.prettyprint.cassandra.service.template.SliceFilter;
import me.prettyprint.hector.api.beans.HCounterColumn;
import me.prettyprint.hector.api.query.SliceCounterQuery;
/**
* Iterates over the SliceCounterQuery, refreshing the query until all qualifing
* columns are retrieved. If column deletion can occur synchronously with
* calls to {@link #hasNext hasNext()}, the column name object type must
* override Object.equals().
*
* @author thrykol
*/
public class SliceCounterIterator<K, N> implements Iterator<HCounterColumn<N>> {
private static final int DEFAULT_COUNT = 100;
private SliceCounterQuery<K, N> query;
private PeekingIterator<HCounterColumn<N>> iterator;
private N start;
private SliceCounterFinish<N> finish;
private SliceFilter<HCounterColumn<N>> filter = null;
private boolean reversed;
private int count = DEFAULT_COUNT;
private int columns = 0;
/**
* Constructor
*
* @param query Base SliceQuery to execute
* @param start Starting point of the range
* @param finish Finish point of the range.
* @param reversed Whether or not the columns should be reversed
*/
public SliceCounterIterator(SliceCounterQuery<K, N> query, N start, final N finish, boolean reversed) {
this(query, start, finish, reversed, DEFAULT_COUNT);
}
/**
* Constructor
*
* @param query Base SliceQuery to execute
* @param start Starting point of the range
* @param finish Finish point of the range.
* @param reversed Whether or not the columns should be reversed
* @param count the amount of columns to retrieve per batch
*/
public SliceCounterIterator(SliceCounterQuery<K, N> query, N start, final N finish, boolean reversed, int count) {
this(query, start, new SliceCounterFinish<N>() {
@Override
public N function() {
return finish;
}
}, reversed, count);
}
/**
* Constructor
*
* @param query Base SliceQuery to execute
* @param start Starting point of the range
* @param finish Finish point of the range. Allows for a dynamically
* determined point
* @param reversed Whether or not the columns should be reversed
*/
public SliceCounterIterator(SliceCounterQuery<K, N> query, N start, SliceCounterFinish<N> finish, boolean reversed) {
this(query, start, finish, reversed, DEFAULT_COUNT);
}
/**
* Constructor
*
* @param query Base SliceQuery to execute
* @param start Starting point of the range
* @param finish Finish point of the range. Allows for a dynamically
* determined point
* @param reversed Whether or not the columns should be reversed
* @param count the amount of columns to retrieve per batch
*/
public SliceCounterIterator(SliceCounterQuery<K, N> query, N start, SliceCounterFinish<N> finish, boolean reversed, int count) {
this.query = query;
this.start = start;
this.finish = finish;
this.reversed = reversed;
this.count = count;
this.query.setRange(this.start, this.finish.function(), this.reversed, this.count);
}
/**
* Set a filter to determine which columns will be returned.
*
* @param filter Filter to determine which columns will be returned
* @return <this>
*/
public SliceCounterIterator setFilter(SliceFilter<HCounterColumn<N>> filter) {
this.filter = filter;
return this;
}
@Override
public boolean hasNext() {
if (iterator == null) {
iterator = Iterators.peekingIterator(query.execute().get().getColumns().iterator());
} else if (!iterator.hasNext() && columns == count) { // only need to do another query if maximum columns were retrieved
refresh();
}
while(filter != null && iterator != null && iterator.hasNext() && !filter.accept(iterator.peek())) {
next();
if(!iterator.hasNext() && columns == count) {
refresh();
}
}
return iterator.hasNext();
}
@Override
public HCounterColumn<N> next() {
HCounterColumn<N> column = iterator.next();
start = column.getName();
columns++;
return column;
}
@Override
public void remove() {
iterator.remove();
}
private void refresh() {
query.setRange(start, finish.function(), reversed, count);
columns = 0;
List<HCounterColumn<N>> list = query.execute().get().getColumns();
iterator = Iterators.peekingIterator(list.iterator());
if (iterator.hasNext()) {
// The lower bound column may have been removed prior to the query executing,
// so check to see if the first column returned by the current query is the same
// as the lower bound column. If both columns are the same, skip the column
N first = list.get(0).getName();
if (first.equals(start)) {
next();
}
}
}
/**
* When iterating over a SliceCounter, it may be desirable to move the finish
* point for each query. This interface allows for a user defined function
* which will return the new finish point. This is especially useful for
* column families which have a TimeUUID as the column name.
*/
public interface SliceCounterFinish<N> {
/**
* Generic function for deriving a new finish point.
*
* @return New finish point
*/
N function();
}
}