package org.radargun.service;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import com.tangosol.io.pof.PofReader;
import com.tangosol.io.pof.PofWriter;
import com.tangosol.io.pof.PortableObject;
import com.tangosol.net.NamedCache;
import com.tangosol.util.Filter;
import com.tangosol.util.InvocableMap;
import com.tangosol.util.ValueExtractor;
import com.tangosol.util.aggregator.ReducerAggregator;
import com.tangosol.util.comparator.ChainedComparator;
import com.tangosol.util.comparator.EntryComparator;
import com.tangosol.util.comparator.InverseComparator;
import com.tangosol.util.extractor.ChainedExtractor;
import com.tangosol.util.extractor.MultiExtractor;
import com.tangosol.util.extractor.ReflectionExtractor;
import com.tangosol.util.filter.AllFilter;
import com.tangosol.util.filter.AnyFilter;
import com.tangosol.util.filter.BetweenFilter;
import com.tangosol.util.filter.ContainsFilter;
import com.tangosol.util.filter.EqualsFilter;
import com.tangosol.util.filter.GreaterEqualsFilter;
import com.tangosol.util.filter.GreaterFilter;
import com.tangosol.util.filter.IsNullFilter;
import com.tangosol.util.filter.LessEqualsFilter;
import com.tangosol.util.filter.LessFilter;
import com.tangosol.util.filter.LikeFilter;
import com.tangosol.util.filter.LimitFilter;
import com.tangosol.util.filter.NotFilter;
import org.radargun.aggregators.LimitAggregator;
import org.radargun.traits.Query;
import org.radargun.traits.Query.Builder;
import org.radargun.traits.Queryable;
/**
* @author Radim Vansa <rvansa@redhat.com>
*/
public class CoherenceQueryable implements Queryable {
protected final Coherence3Service service;
public CoherenceQueryable(Coherence3Service service) {
this.service = service;
}
@Override
public Query.Builder getBuilder(String containerName, Class<?> clazz) {
return new QueryBuilderImpl();
}
@Override
public QueryContextImpl createContext(String containerName) {
return new QueryContextImpl(service.getCache(containerName));
}
@Override
public void reindex(String containerName) {
// noop - indices should be in sync
}
public void registerIndices(NamedCache cache, List<Coherence3Service.IndexedColumn> indexedColumns) {
for (Coherence3Service.IndexedColumn c : service.indexedColumns) {
if (cache.getCacheName().equals(c.cache)) {
ReflectionExtractor extractor = new ReflectionExtractor(c.attribute);
cache.addIndex(extractor, c.ordered, null);
}
}
}
private static class QueryBuilderImpl implements Query.Builder {
private final ArrayList<Filter> filters = new ArrayList<Filter>();
private LinkedHashMap<String, Boolean> orderBy;
private long offset = 0, limit = -1;
private String[] projection;
@Override
public Query.Builder subquery() {
return new QueryBuilderImpl();
}
@Override
public Query.Builder eq(Query.SelectExpression expression, Object value) {
filters.add(new EqualsFilter(expression.attribute, value));
return this;
}
@Override
public Query.Builder lt(Query.SelectExpression expression, Object value) {
filters.add(new LessFilter(expression.attribute, (Comparable) value));
return this;
}
@Override
public Query.Builder le(Query.SelectExpression expression, Object value) {
filters.add(new LessEqualsFilter(expression.attribute, (Comparable) value));
return this;
}
@Override
public Query.Builder gt(Query.SelectExpression expression, Object value) {
filters.add(new GreaterFilter(expression.attribute, (Comparable) value));
return this;
}
@Override
public Query.Builder ge(Query.SelectExpression expression, Object value) {
filters.add(new GreaterEqualsFilter(expression.attribute, (Comparable) value));
return this;
}
@Override
public Query.Builder between(Query.SelectExpression expression, Object lowerBound, boolean lowerInclusive, Object upperBound,
boolean upperInclusive) {
filters.add(new BetweenFilter(expression.attribute, (Comparable) lowerBound, (Comparable) upperBound));
return this;
}
@Override
public Query.Builder isNull(Query.SelectExpression expression) {
filters.add(new IsNullFilter(expression.attribute));
return this;
}
@Override
public Query.Builder like(Query.SelectExpression expression, String pattern) {
filters.add(new LikeFilter(expression.attribute, pattern));
return this;
}
@Override
public Query.Builder contains(Query.SelectExpression expression, Object value) {
filters.add(new ContainsFilter(expression.attribute, value));
return this;
}
@Override
public Query.Builder not(Query.Builder subquery) {
filters.add(new NotFilter(((QueryBuilderImpl) subquery).getFilter()));
return this;
}
@Override
public Query.Builder any(Query.Builder... subqueries) {
Filter[] subs = new Filter[subqueries.length];
for (int i = 0; i < subqueries.length; ++i) {
subs[i] = ((QueryBuilderImpl) subqueries[i]).getFilter();
}
filters.add(new AnyFilter(subs));
return this;
}
@Override
public Query.Builder orderBy(Query.SelectExpression expression) {
if (orderBy == null)
orderBy = new LinkedHashMap<String, Boolean>();
if (orderBy.put(expression.attribute, expression.asc) != null) {
throw new IllegalArgumentException("Order by " + expression.attribute + " already set");
}
return this;
}
@Override
public Query.Builder projection(Query.SelectExpression... expression) {
if (expression == null || expression.length == 0)
throw new IllegalArgumentException();
if (projection != null)
throw new IllegalStateException("Projection already set");
this.projection = Arrays.stream(expression).map(Query.SelectExpression::attribute).toArray(String[]::new);
return this;
}
@Override
public Query.Builder offset(long offset) {
if (this.offset < 0)
throw new IllegalArgumentException("Offset " + offset + " < 0 not allowed");
this.offset = offset;
return this;
}
@Override
public Query.Builder limit(long limit) {
if (limit < 1)
throw new IllegalArgumentException("Limit " + limit + " < 1 not allowed");
this.limit = limit;
return this;
}
@Override
public Query build() {
return new QueryImpl(getFilter(), projection, orderBy, offset, limit);
}
public Filter getFilter() {
if (filters.size() == 1) {
return filters.get(0);
} else {
return new AllFilter(filters.toArray(new Filter[filters.size()]));
}
}
public Builder groupBy(String[] attribute) {
throw new UnsupportedOperationException();
// TODO Needs implementing
}
}
private static class QueryImpl implements Query {
private final Filter filter;
private final Comparator comparator;
private final int skip;
private final int limit;
private final String[] projection;
public QueryImpl(Filter filter, String[] projection, LinkedHashMap<String, Boolean> orderBy, long offset, long limit) {
this.projection = projection;
if (projection != null) {
// we delay this to LimitAggregator
this.skip = (int) offset;
this.limit = (int) limit;
this.filter = filter;
if (orderBy == null) {
comparator = null;
} else {
// TODO: we can project composite object and sort by its inner
// attributes
ArrayList<Comparator> comparators = new ArrayList<Comparator>(orderBy.size());
for (String attribute : orderBy.keySet()) {
int index = -1;
for (int i = 0; i < projection.length; ++i) {
if (projection[i].equals(attribute)) {
index = i;
break;
}
}
if (index < 0) {
throw new IllegalStateException("Attribute " + attribute + " has to be projected to perform sort");
} else {
comparators.add(new ProjectionComparator(index, orderBy.get(attribute)));
}
}
if (comparators.size() == 1) {
comparator = comparators.get(0);
} else {
comparator = new ChainedComparator(comparators.toArray(new Comparator[comparators.size()]));
}
}
} else {
if (orderBy == null) {
comparator = null;
} else if (orderBy.size() == 1) {
Map.Entry<String, Boolean> entry = orderBy.entrySet().iterator().next();
Comparator comp = new ReflectionExtractor(entry.getKey());
comparator = entry.getValue() ? comp : new InverseComparator(comp);
} else {
ArrayList<Comparator> comparators = new ArrayList<Comparator>(orderBy.size());
for (Map.Entry<String, Boolean> entry : orderBy.entrySet()) {
Comparator comp = new ReflectionExtractor(entry.getKey());
comparators.add(entry.getValue() ? comp : new InverseComparator(comp));
}
comparator = new ChainedComparator(comparators.toArray(new Comparator[comparators.size()]));
}
if (offset > 0 && limit >= 0) {
long bestSize = -1;
int bestExtra = Integer.MAX_VALUE;
for (long pageSize = limit; pageSize < 2 * limit || bestSize < 0; ++pageSize) {
// if the pageSize does not cover our range properly, we
// cannot use it
if (pageSize * (offset / pageSize + 1) < offset + limit)
continue;
int extra = (int) (pageSize - limit);
if (extra < bestExtra) {
bestExtra = extra;
bestSize = pageSize;
}
if (extra == 0)
break;
}
this.skip = (int) (offset % bestSize);
this.limit = (int) limit;
LimitFilter lf = new LimitFilter(filter, (int) bestSize);
lf.setPage((int) (offset / bestSize));
this.filter = lf;
} else if (offset > 0) {
this.skip = (int) offset;
this.limit = Integer.MAX_VALUE;
this.filter = filter;
} else if (limit >= 0) {
this.skip = 0;
this.limit = (int) limit;
this.filter = new LimitFilter(filter, this.limit);
} else {
this.skip = 0;
this.limit = -1;
this.filter = filter;
}
}
}
@Override
public Query.Result execute(Query.Context resource) {
NamedCache cache = getCache(resource);
if (projection == null) {
if (comparator == null) {
return new QueryResultImpl(cache.entrySet(filter), skip, limit);
} else {
return new QueryResultImpl(cache.entrySet(filter, comparator), skip, limit);
}
} else {
InvocableMap.EntryAggregator aggregator;
if (projection.length == 1) {
aggregator = new ReducerAggregator(projection[0]);
} else {
ValueExtractor[] extractors = new ValueExtractor[projection.length];
for (int i = 0; i < projection.length; ++i) {
extractors[i] = projection[i].indexOf('.') < 0 ? new ReflectionExtractor(projection[i])
: new ChainedExtractor(projection[i]);
}
aggregator = new ReducerAggregator(new MultiExtractor(extractors));
}
Map map;
if (comparator != null || skip > 0 || limit >= 0) {
int laLimit = limit < 0 ? Integer.MAX_VALUE : limit + skip;
map = (Map) cache.aggregate(filter,
new LimitAggregator(aggregator, laLimit, comparator != null, comparator, EntryComparator.CMP_VALUE));
} else {
map = (Map) cache.aggregate(filter, aggregator);
}
return new QueryResultImpl(map.values(), skip, limit);
}
}
protected NamedCache getCache(Query.Context context) {
QueryContextImpl impl = (QueryContextImpl) context;
return impl.cache;
}
}
private static class QueryResultImpl implements Query.Result {
private final Set<Map.Entry> entrySet;
private final Collection valueSet;
private final int skip;
private final int limit;
public QueryResultImpl(Set<Map.Entry> entrySet, int skip, int limit) {
this.entrySet = entrySet;
this.valueSet = null;
this.skip = skip;
this.limit = limit;
}
public QueryResultImpl(Collection valueSet, int skip, int limit) {
this.entrySet = null;
this.valueSet = valueSet;
this.skip = skip;
this.limit = limit;
}
@Override
public int size() {
return Math.min(Math.max(0, (entrySet != null ? entrySet.size() : valueSet.size()) - skip), limit());
}
@Override
public Collection values() {
if (entrySet != null) {
return entrySet.stream().skip(skip).limit(limit()).map(e -> e.getValue()).collect(Collectors.toCollection(HashSet::new));
} else {
return (Collection) valueSet.stream().skip(skip).limit(limit()).map(e -> (e instanceof List ? ((List) e).toArray() : e))
.collect(Collectors.toCollection(HashSet::new));
}
}
private int limit() {
return limit < 0 ? Integer.MAX_VALUE : limit;
}
}
protected static class QueryContextImpl implements Query.Context {
protected final NamedCache cache;
public QueryContextImpl(NamedCache cache) {
this.cache = cache;
}
@Override
public void close() {
}
}
public static class ProjectionComparator implements Comparator, Serializable, PortableObject {
private int index;
private boolean asc;
public ProjectionComparator() {
// for POF deserialization only
}
private ProjectionComparator(int index, boolean asc) {
this.index = index;
this.asc = asc;
}
@Override
public int compare(Object o1, Object o2) {
return asc ? compareAsc(o1, o2) : compareAsc(o2, o1);
}
private final int compareAsc(Object o1, Object o2) {
Object attr1 = o1 instanceof List ? ((List) o1).get(index) : o1;
Object attr2 = o2 instanceof List ? ((List) o2).get(index) : o2;
if (attr1 == null) {
return attr2 == null ? 0 : -1;
} else if (attr2 == null) {
return 1;
} else {
return ((Comparable) attr1).compareTo(attr2);
}
}
@Override
public void readExternal(PofReader pofReader) throws IOException {
index = pofReader.readInt(0);
asc = pofReader.readBoolean(1);
}
@Override
public void writeExternal(PofWriter pofWriter) throws IOException {
pofWriter.writeInt(0, index);
pofWriter.writeBoolean(1, asc);
}
}
}