All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.usergrid.mq.QueryProcessor Maven / Gradle / Ivy

There is a newer version: 0.0.27.1
Show newest version
package org.usergrid.mq;

import static java.lang.Integer.parseInt;
import static org.apache.commons.codec.binary.Base64.decodeBase64;
import static org.apache.commons.lang.StringUtils.isBlank;
import static org.apache.commons.lang.StringUtils.isNotBlank;
import static org.apache.commons.lang.StringUtils.removeEnd;
import static org.apache.commons.lang.StringUtils.split;
import static org.usergrid.mq.Query.SortDirection.DESCENDING;
import static org.usergrid.persistence.cassandra.IndexUpdate.indexValueCode;
import static org.usergrid.persistence.cassandra.IndexUpdate.toIndexableValue;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;

import org.apache.commons.collections.comparators.ComparatorChain;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.usergrid.mq.Query.FilterOperator;
import org.usergrid.mq.Query.FilterPredicate;
import org.usergrid.mq.Query.SortPredicate;
import org.usergrid.persistence.Entity;
import org.usergrid.persistence.EntityPropertyComparator;
import org.usergrid.utils.ListUtils;
import org.usergrid.utils.NumberUtils;
import org.usergrid.utils.StringUtils;

public class QueryProcessor {

	private static final Logger logger = LoggerFactory
			.getLogger(QueryProcessor.class);

	Query query;

	String cursor;
	List slices;
	List filters;
	List sorts;

	public QueryProcessor(Query query) {
		this.query = query;
		cursor = query.getCursor();
		filters = query.getFilterPredicates();
		sorts = query.getSortPredicates();
		process();
	}

	public Query getQuery() {
		return query;
	}

	public String getCursor() {
		return cursor;
	}

	public List getSlices() {
		return slices;
	}

	public List getFilters() {
		return filters;
	}

	public List getSorts() {
		return sorts;
	}

	private void process() {
		slices = new ArrayList();

		// consolidate all the filters into a set of ranges
		Set names = getFilterPropertyNames();
		for (String name : names) {
			FilterOperator operator = null;
			Object value = null;
			RangeValue start = null;
			RangeValue finish = null;
			for (FilterPredicate f : filters) {
				if (f.getPropertyName().equals(name)) {
					operator = f.getOperator();
					value = f.getValue();
					RangePair r = getRangeForFilter(f);
					if (r.start != null) {
						if ((start == null)
								|| (r.start.compareTo(start, false) < 0)) {
							start = r.start;
						}
					}
					if (r.finish != null) {
						if ((finish == null)
								|| (r.finish.compareTo(finish, true) > 0)) {
							finish = r.finish;
						}
					}
				}
			}
			slices.add(new QuerySlice(name, operator, value, start, finish,
					null, false));
		}

		// process sorts
		if ((slices.size() == 0) && (sorts.size() > 0)) {
			// if no filters, turn first filter into a sort
			SortPredicate sort = ListUtils.dequeue(sorts);
			slices.add(new QuerySlice(sort.getPropertyName(), null, null, null,
					null, null, sort.getDirection() == DESCENDING));
		} else if (sorts.size() > 0) {
			// match up sorts with existing filters
			for (ListIterator iter = sorts.listIterator(); iter
					.hasNext();) {
				SortPredicate sort = iter.next();
				QuerySlice slice = getSliceForProperty(sort.getPropertyName());
				if (slice != null) {
					slice.reversed = sort.getDirection() == DESCENDING;
					iter.remove();
				}
			}
		}

		// attach cursors to slices
		if ((cursor != null) && (cursor.indexOf(':') >= 0)) {
			String[] cursors = split(cursor, '|');
			for (String c : cursors) {
				String[] parts = split(c, ':');
				if (parts.length == 2) {
					int cursorHashCode = parseInt(parts[0]);
					for (QuerySlice slice : slices) {
						int sliceHashCode = slice.hashCode();
						logger.info("Comparing cursor hashcode "
								+ cursorHashCode + " to " + sliceHashCode);
						if (sliceHashCode == cursorHashCode) {
							if (isNotBlank(parts[1])) {
								ByteBuffer cursorBytes = ByteBuffer
										.wrap(decodeBase64(parts[1]));
								slice.setCursor(cursorBytes);
							}
						}
					}
				}
			}
		}
	}

	@SuppressWarnings("unchecked")
	public List sort(List entities) {

		if ((entities != null) && (sorts.size() > 0)) {
			// Performing in memory sort
			logger.info("Performing in-memory sort of {} entities",  entities.size());
			ComparatorChain chain = new ComparatorChain();
			for (SortPredicate sort : sorts) {
				chain.addComparator(
						new EntityPropertyComparator(sort.getPropertyName()),
						sort.getDirection() == DESCENDING);
			}
			Collections.sort(entities, chain);
		}
		return entities;
	}

	private Set getFilterPropertyNames() {
		Set names = new LinkedHashSet();
		for (FilterPredicate f : filters) {
			names.add(f.getPropertyName());
		}
		return names;
	}

	public QuerySlice getSliceForProperty(String name) {
		for (QuerySlice s : slices) {
			if (s.propertyName.equals(name)) {
				return s;
			}
		}
		return null;
	}

	public static class RangeValue {
		byte code;
		Object value;
		boolean inclusive;

		public RangeValue(byte code, Object value, boolean inclusive) {
			this.code = code;
			this.value = value;
			this.inclusive = inclusive;
		}

		public byte getCode() {
			return code;
		}

		public void setCode(byte code) {
			this.code = code;
		}

		public Object getValue() {
			return value;
		}

		public void setValue(Object value) {
			this.value = value;
		}

		public boolean isInclusive() {
			return inclusive;
		}

		public void setInclusive(boolean inclusive) {
			this.inclusive = inclusive;
		}

		@Override
		public int hashCode() {
			final int prime = 31;
			int result = 1;
			result = prime * result + code;
			result = prime * result + (inclusive ? 1231 : 1237);
			result = prime * result + ((value == null) ? 0 : value.hashCode());
			return result;
		}

		@Override
		public boolean equals(Object obj) {
			if (this == obj) {
				return true;
			}
			if (obj == null) {
				return false;
			}
			if (getClass() != obj.getClass()) {
				return false;
			}
			RangeValue other = (RangeValue) obj;
			if (code != other.code) {
				return false;
			}
			if (inclusive != other.inclusive) {
				return false;
			}
			if (value == null) {
				if (other.value != null) {
					return false;
				}
			} else if (!value.equals(other.value)) {
				return false;
			}
			return true;
		}

		public int compareTo(RangeValue other, boolean finish) {
			if (other == null) {
				return 1;
			}
			if (code != other.code) {
				return NumberUtils.sign(code - other.code);
			}
			@SuppressWarnings({ "unchecked", "rawtypes" })
			int c = ((Comparable) value).compareTo(other.value);
			if (c != 0) {
				return c;
			}
			if (finish) {
				// for finish values, inclusive means <= which is greater than <
				if (inclusive != other.inclusive) {
					return inclusive ? 1 : -1;
				}
			} else {
				// for start values, inclusive means >= which is lest than >
				if (inclusive != other.inclusive) {
					return inclusive ? -1 : 1;
				}
			}
			return 0;
		}

		public static int compare(RangeValue v1, RangeValue v2, boolean finish) {
			if (v1 == null) {
				if (v2 == null) {
					return 0;
				}
				return -1;
			}
			return v1.compareTo(v2, finish);
		}
	}

	public static class RangePair {
		RangeValue start;
		RangeValue finish;

		public RangePair(RangeValue start, RangeValue finish) {
			this.start = start;
			this.finish = finish;
		}

		public RangeValue getStart() {
			return start;
		}

		public void setStart(RangeValue start) {
			this.start = start;
		}

		public RangeValue getFinish() {
			return finish;
		}

		public void setFinish(RangeValue finish) {
			this.finish = finish;
		}

		@Override
		public int hashCode() {
			final int prime = 31;
			int result = 1;
			result = prime * result
					+ ((finish == null) ? 0 : finish.hashCode());
			result = prime * result + ((start == null) ? 0 : start.hashCode());
			return result;
		}

		@Override
		public boolean equals(Object obj) {
			if (this == obj) {
				return true;
			}
			if (obj == null) {
				return false;
			}
			if (getClass() != obj.getClass()) {
				return false;
			}
			RangePair other = (RangePair) obj;
			if (finish == null) {
				if (other.finish != null) {
					return false;
				}
			} else if (!finish.equals(other.finish)) {
				return false;
			}
			if (start == null) {
				if (other.start != null) {
					return false;
				}
			} else if (!start.equals(other.start)) {
				return false;
			}
			return true;
		}
	}

	public RangePair getRangeForFilter(FilterPredicate f) {
		Object searchStartValue = toIndexableValue(f.getStartValue());
		Object searchFinishValue = toIndexableValue(f.getFinishValue());
		if (StringUtils.isString(searchStartValue)
				&& StringUtils.isStringOrNull(searchFinishValue)) {
			if ("*".equals(searchStartValue)) {
				searchStartValue = null;
			}
			if (searchFinishValue == null) {
				searchFinishValue = searchStartValue;
				;
			}
			if ((searchStartValue != null)
					&& searchStartValue.toString().endsWith("*")) {
				searchStartValue = removeEnd(searchStartValue.toString(), "*");
				searchFinishValue = searchStartValue + "\uFFFF";
				if (isBlank(searchStartValue.toString())) {
					searchStartValue = "\0000";
				}
			} else if (searchFinishValue != null) {
				searchFinishValue = searchFinishValue + "\u0000";
			}
		}
		RangeValue rangeStart = null;
		if (searchStartValue != null) {
			rangeStart = new RangeValue(indexValueCode(searchStartValue),
					searchStartValue,
					f.getOperator() != FilterOperator.GREATER_THAN);
		}
		RangeValue rangeFinish = null;
		if (searchFinishValue != null) {
			rangeFinish = new RangeValue(indexValueCode(searchFinishValue),
					searchFinishValue,
					f.getOperator() != FilterOperator.LESS_THAN);
		}
		return new RangePair(rangeStart, rangeFinish);
	}

	public static class QuerySlice {

		String propertyName;
		FilterOperator operator;
		Object value;
		RangeValue start;
		RangeValue finish;
		ByteBuffer cursor;
		boolean reversed;

		QuerySlice(String propertyName, FilterOperator operator, Object value,
				RangeValue start, RangeValue finish, ByteBuffer cursor,
				boolean reversed) {
			this.propertyName = propertyName;
			this.operator = operator;
			this.value = value;
			this.start = start;
			this.finish = finish;
			this.cursor = cursor;
			this.reversed = reversed;
		}

		public String getPropertyName() {
			return propertyName;
		}

		public void setPropertyName(String propertyName) {
			this.propertyName = propertyName;
		}

		public RangeValue getStart() {
			return start;
		}

		public void setStart(RangeValue start) {
			this.start = start;
		}

		public RangeValue getFinish() {
			return finish;
		}

		public void setFinish(RangeValue finish) {
			this.finish = finish;
		}

		public Object getValue() {
			return value;
		}

		public void setValue(Object value) {
			this.value = value;
		}

		public ByteBuffer getCursor() {
			return cursor;
		}

		public void setCursor(ByteBuffer cursor) {
			this.cursor = cursor;
		}

		public boolean isReversed() {
			return reversed;
		}

		public void setReversed(boolean reversed) {
			this.reversed = reversed;
		}

		@Override
		public int hashCode() {
			final int prime = 31;
			int result = 1;
			result = prime * result
					+ ((finish == null) ? 0 : finish.hashCode());
			result = prime * result
					+ ((propertyName == null) ? 0 : propertyName.hashCode());
			result = prime * result + ((start == null) ? 0 : start.hashCode());
			
			//NOTE.  We have explicitly left out direction.  According to IndexTest:testCollectionOrdering, a cursor can be used and change direction
			//of the ordering.  
			return result;
		}

		@Override
		public boolean equals(Object obj) {
			if (this == obj) {
				return true;
			}
			if (obj == null) {
				return false;
			}
			if (getClass() != obj.getClass()) {
				return false;
			}
			QuerySlice other = (QuerySlice) obj;
			if (finish == null) {
				if (other.finish != null) {
					return false;
				}
			} else if (!finish.equals(other.finish)) {
				return false;
			}
			if (propertyName == null) {
				if (other.propertyName != null) {
					return false;
				}
			} else if (!propertyName.equals(other.propertyName)) {
				return false;
			}
		
			if (start == null) {
				if (other.start != null) {
					return false;
				}
			} else if (!start.equals(other.start)) {
				return false;
			}
			return true;
		}

	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy