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

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

There is a newer version: 0.0.27.1
Show newest version
/*******************************************************************************
 * Copyright 2012 Apigee Corporation
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/
package org.usergrid.mq;

import static org.apache.commons.codec.binary.Base64.decodeBase64;
import static org.apache.commons.lang.StringUtils.isBlank;
import static org.apache.commons.lang.StringUtils.split;
import static org.usergrid.persistence.Schema.PROPERTY_TYPE;
import static org.usergrid.persistence.Schema.PROPERTY_UUID;
import static org.usergrid.utils.ClassUtils.cast;
import static org.usergrid.utils.ConversionUtils.uuid;
import static org.usergrid.utils.ListUtils.first;
import static org.usergrid.utils.ListUtils.firstBoolean;
import static org.usergrid.utils.ListUtils.firstInteger;
import static org.usergrid.utils.ListUtils.firstLong;
import static org.usergrid.utils.ListUtils.firstUuid;
import static org.usergrid.utils.ListUtils.isEmpty;
import static org.usergrid.utils.MapUtils.toMapList;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

import org.antlr.runtime.ANTLRStringStream;
import org.antlr.runtime.CommonTokenStream;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.usergrid.persistence.CounterResolution;
import org.usergrid.persistence.Entity;
import org.usergrid.persistence.Identifier;
import org.usergrid.persistence.Results;
import org.usergrid.persistence.Results.Level;
import org.usergrid.utils.JsonUtils;

public class Query {

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

	public static final int DEFAULT_LIMIT = 10;

	protected String type;
	protected List sortPredicates = new ArrayList();
	protected List filterPredicates = new ArrayList();
	protected UUID startResult;
	protected String cursor;
	protected int limit = 0;
	protected boolean limitSet = false;

	protected Map selectSubjects = new LinkedHashMap();
	protected boolean mergeSelectResults = false;
	protected Level level = Level.ALL_PROPERTIES;
	protected String connection;
	protected List permissions;
	protected boolean reversed;
	protected boolean reversedSet = false;
	protected Long startTime;
	protected Long finishTime;
	protected boolean pad;
	protected CounterResolution resolution = CounterResolution.ALL;
	protected List users;
	protected List groups;
	protected List identifiers;
	protected List categories;
	protected List counterFilters;

	public Query() {
	}

	public Query(String type) {
		this.type = type;
	}

	public Query(Query q) {
		if (q != null) {
			type = q.type;
			sortPredicates = q.sortPredicates != null ? new ArrayList(
					q.sortPredicates) : null;
			filterPredicates = q.filterPredicates != null ? new ArrayList(
					q.filterPredicates) : null;
			startResult = q.startResult;
			cursor = q.cursor;
			limit = q.limit;
			limitSet = q.limitSet;
			selectSubjects = q.selectSubjects != null ? new LinkedHashMap(
					q.selectSubjects) : null;
			mergeSelectResults = q.mergeSelectResults;
			level = q.level;
			connection = q.connection;
			permissions = q.permissions != null ? new ArrayList(
					q.permissions) : null;
			reversed = q.reversed;
			reversedSet = q.reversedSet;
			startTime = q.startTime;
			finishTime = q.finishTime;
			resolution = q.resolution;
			pad = q.pad;
			users = q.users != null ? new ArrayList(q.users) : null;
			groups = q.groups != null ? new ArrayList(q.groups)
					: null;
			identifiers = q.identifiers != null ? new ArrayList(
					q.identifiers) : null;
			categories = q.categories != null ? new ArrayList(
					q.categories) : null;
			counterFilters = q.counterFilters != null ? new ArrayList(
					q.counterFilters) : null;
		}
	}

	public static Query fromQL(String ql) {
		if (ql == null) {
			return null;
		}
		ql = ql.trim();

		String qlt = ql.toLowerCase();
		if (!qlt.startsWith("select") && !qlt.startsWith("insert")
				&& !qlt.startsWith("update") && !qlt.startsWith("delete")) {
			if (qlt.startsWith("order by")) {
				ql = "select * " + ql;
			} else {
				ql = "select * where " + ql;
			}
		}

		try {
			ANTLRStringStream in = new ANTLRStringStream(ql.trim());
			QueryFilterLexer lexer = new QueryFilterLexer(in);
			CommonTokenStream tokens = new CommonTokenStream(lexer);
			QueryFilterParser parser = new QueryFilterParser(tokens);
			Query q = parser.ql();
			return q;
		} catch (Exception e) {
			logger.error("Unable to parse \"" + ql + "\"", e);
		}
		return null;
	}

	public static Query newQueryIfNull(Query query) {
		if (query == null) {
			query = new Query();
		}
		return query;
	}

	public static Query fromJsonString(String json) {
		Object o = JsonUtils.parse(json);
		if (o instanceof Map) {
			@SuppressWarnings({ "unchecked", "rawtypes" })
			Map> params = cast(toMapList((Map) o));
			return fromQueryParams(params);
		}
		return null;
	}

	public static Query fromQueryParams(Map> params) {
		String type = null;
		Query q = null;
		String ql = null;
		String connection = null;
		UUID start = null;
		String cursor = null;
		Integer limit = null;
		List permissions = null;
		Boolean reversed = null;
		Long startTime = null;
		Long finishTime = null;
		Boolean pad = null;
		CounterResolution resolution = null;
		List users = null;
		List groups = null;
		List identifiers = null;
		List categories = null;
		List counterFilters = null;

		List l = null;

		ql = first(params.get("ql"));
		type = first(params.get("type"));
		reversed = firstBoolean(params.get("reversed"));
		connection = first(params.get("connection"));
		start = firstUuid(params.get("start"));
		cursor = first(params.get("cursor"));
		limit = firstInteger(params.get("limit"));
		permissions = params.get("permission");
		startTime = firstLong(params.get("start_time"));
		finishTime = firstLong(params.get("end_time"));

		l = params.get("resolution");
		if (!isEmpty(l)) {
			resolution = CounterResolution.fromString(l.get(0));
		}

		users = Identifier.fromList(params.get("user"));
		groups = Identifier.fromList(params.get("group"));

		categories = params.get("category");

		l = params.get("counter");
		if (!isEmpty(l)) {
			counterFilters = CounterFilterPredicate.fromList(l);
		}

		pad = firstBoolean(params.get("pad"));

		for (Entry> param : params.entrySet()) {
			if ((param.getValue() == null) || (param.getValue().size() == 0)) {
				Identifier identifier = Identifier.from(param.getKey());
				if (identifier != null) {
					if (identifiers == null) {
						identifiers = new ArrayList();
					}
					identifiers.add(identifier);
				}
			}
		}

		if (ql != null) {
			q = Query.fromQL(ql);
		}

		l = params.get("filter");
		if (!isEmpty(l)) {
			q = newQueryIfNull(q);
			for (String s : l) {
				q.addFilter(s);
			}
		}

		l = params.get("sort");
		if (!isEmpty(l)) {
			q = newQueryIfNull(q);
			for (String s : l) {
				q.addSort(s);
			}
		}

		if (type != null) {
			q = newQueryIfNull(q);
			q.setEntityType(type);
		}

		if (connection != null) {
			q = newQueryIfNull(q);
			q.setConnectionType(connection);
		}

		if (permissions != null) {
			q = newQueryIfNull(q);
			q.setPermissions(permissions);
		}

		if (start != null) {
			q = newQueryIfNull(q);
			q.setStartResult(start);
		}

		if (cursor != null) {
			q = newQueryIfNull(q);
			q.setCursor(cursor);
		}

		if (limit != null) {
			q = newQueryIfNull(q);
			q.setLimit(limit);
		}

		if (startTime != null) {
			q = newQueryIfNull(q);
			q.setStartTime(startTime);
		}

		if (finishTime != null) {
			q = newQueryIfNull(q);
			q.setFinishTime(finishTime);
		}

		if (resolution != null) {
			q = newQueryIfNull(q);
			q.setResolution(resolution);
		}

		if (categories != null) {
			q = newQueryIfNull(q);
			q.setCategories(categories);
		}

		if (counterFilters != null) {
			q = newQueryIfNull(q);
			q.setCounterFilters(counterFilters);
		}

		if (pad != null) {
			q = newQueryIfNull(q);
			q.setPad(pad);
		}

		if (users != null) {
			q = newQueryIfNull(q);
			q.setUsers(users);
		}

		if (groups != null) {
			q = newQueryIfNull(q);
			q.setGroups(groups);
		}

		if (identifiers != null) {
			q = newQueryIfNull(q);
			q.setIdentifiers(identifiers);
		}

		if (reversed != null) {
			q = newQueryIfNull(q);
			q.setReversed(reversed);
		}

		return q;
	}

	public static Query searchForProperty(String propertyName,
			Object propertyValue) {
		Query q = new Query();
		q.addEqualityFilter(propertyName, propertyValue);
		return q;
	}

	public static Query findForProperty(String propertyName,
			Object propertyValue) {
		Query q = new Query();
		q.addEqualityFilter(propertyName, propertyValue);
		q.setLimit(1);
		return q;
	}

	public static Query fromUUID(UUID uuid) {
		Query q = new Query();
		q.addIdentifier(Identifier.fromUUID(uuid));
		return q;
	}

	public static Query fromName(String name) {
		Query q = new Query();
		q.addIdentifier(Identifier.fromName(name));
		return q;
	}

	public static Query fromEmail(String email) {
		Query q = new Query();
		q.addIdentifier(Identifier.fromEmail(email));
		return q;
	}

	public static Query fromIdentifier(Object id) {
		Query q = new Query();
		q.addIdentifier(Identifier.from(id));
		return q;
	}

	public boolean isIdsOnly() {
		if ((selectSubjects.size() == 1)
				&& selectSubjects.containsKey(PROPERTY_UUID)) {
			level = Level.IDS;
			return true;
		}
		return false;
	}

	public void setIdsOnly(boolean idsOnly) {
		if (idsOnly) {
			selectSubjects = new LinkedHashMap();
			selectSubjects.put(PROPERTY_UUID, PROPERTY_UUID);
			level = Level.IDS;
		} else if (isIdsOnly()) {
			selectSubjects = new LinkedHashMap();
			level = Level.ALL_PROPERTIES;
		}
	}

	public Level getResultsLevel() {
		isIdsOnly();
		return level;
	}

	public void setResultsLevel(Level level) {
		setIdsOnly(level == Level.IDS);
		this.level = level;
	}

	public Query withResultsLevel(Level level) {
		setIdsOnly(level == Level.IDS);
		this.level = level;
		return this;
	}

	public String getEntityType() {
		return type;
	}

	public void setEntityType(String type) {
		this.type = type;
	}

	public Query withEntityType(String type) {
		this.type = type;
		return this;
	}

	public String getConnectionType() {
		return connection;
	}

	public void setConnectionType(String connection) {
		this.connection = connection;
	}

	public Query withConnectionType(String connection) {
		this.connection = connection;
		return this;
	}

	public List getPermissions() {
		return permissions;
	}

	public void setPermissions(List permissions) {
		this.permissions = permissions;
	}

	public Query withPermissions(List permissions) {
		this.permissions = permissions;
		return this;
	}

	public Query addSelect(String select) {

		return addSelect(select, null);
	}

	public Query addSelect(String select, String output) {
		// be paranoid with the null checks because
		// the query parser sometimes flakes out
		if (select == null) {
			return this;
		}
		select = select.trim();

		if (select.equals("*")) {
			return this;
		}

		if (StringUtils.isNotEmpty(output)) {
			mergeSelectResults = true;
		} else {
			mergeSelectResults = false;
		}

		if (output == null) {
			output = "";
		}

		selectSubjects.put(select, output);

		return this;
	}

	public boolean hasSelectSubjects() {
		return !selectSubjects.isEmpty();
	}

	public Set getSelectSubjects() {
		return selectSubjects.keySet();
	}

	public Map getSelectAssignments() {
		return selectSubjects;
	}

	public void setMergeSelectResults(boolean mergeSelectResults) {
		this.mergeSelectResults = mergeSelectResults;
	}

	public Query withMergeSelectResults(boolean mergeSelectResults) {
		this.mergeSelectResults = mergeSelectResults;
		return this;
	}

	public boolean isMergeSelectResults() {
		return mergeSelectResults;
	}

	public Query addSort(String propertyName) {
		if (isBlank(propertyName)) {
			return this;
		}
		propertyName = propertyName.trim();
		if (propertyName.indexOf(',') >= 0) {
			String[] propertyNames = split(propertyName, ',');
			for (String s : propertyNames) {
				addSort(s);
			}
			return this;
		}

		SortDirection direction = SortDirection.ASCENDING;
		if (propertyName.indexOf(' ') >= 0) {
			String[] parts = split(propertyName, ' ');
			if (parts.length > 1) {
				propertyName = parts[0];
				direction = SortDirection.find(parts[1]);
			}
		} else if (propertyName.startsWith("-")) {
			propertyName = propertyName.substring(1);
			direction = SortDirection.DESCENDING;
		} else if (propertyName.startsWith("+")) {
			propertyName = propertyName.substring(1);
			direction = SortDirection.ASCENDING;
		}

		return addSort(propertyName, direction);
	}

	public Query addSort(String propertyName, SortDirection direction) {
		if (isBlank(propertyName)) {
			return this;
		}
		propertyName = propertyName.trim();
		for (SortPredicate s : sortPredicates) {
			if (s.getPropertyName().equals(propertyName)) {
				logger.error("Attempted to set sort order for "
						+ s.getPropertyName() + " more than once, discardng...");
				return this;
			}
		}
		sortPredicates.add(new SortPredicate(propertyName, direction));
		return this;
	}

	public Query addSort(SortPredicate sort) {
		if (sort == null) {
			return this;
		}
		for (SortPredicate s : sortPredicates) {
			if (s.getPropertyName().equals(sort.getPropertyName())) {
				logger.error("Attempted to set sort order for "
						+ s.getPropertyName() + " more than once, discardng...");
				return this;
			}
		}
		sortPredicates.add(sort);
		return this;
	}

	public List getSortPredicates() {
		return sortPredicates;
	}

	public boolean hasSortPredicates() {
		return !sortPredicates.isEmpty();
	}

	public Query addEqualityFilter(String propertyName, Object value) {
		return addFilter(propertyName, FilterOperator.EQUAL, value);
	}

	public Query addFilter(String propertyName, FilterOperator operator,
			Object value) {
		if ((propertyName == null) || (operator == null) || (value == null)) {
			return this;
		}
		if (PROPERTY_TYPE.equalsIgnoreCase(propertyName) && (value != null)) {
			if (operator == FilterOperator.EQUAL) {
				type = value.toString();
			}
		} else if ("connection".equalsIgnoreCase(propertyName)
				&& (value != null)) {
			if (operator == FilterOperator.EQUAL) {
				connection = value.toString();
			}
		} else {
			for (FilterPredicate f : filterPredicates) {
				if (f.getPropertyName().equals(propertyName)
						&& f.getValue().equals(value) && "*".equals(value)) {
					logger.error("Attempted to set wildcard wilder for "
							+ f.getPropertyName()
							+ " more than once, discardng...");
					return this;
				}
			}
			filterPredicates.add(FilterPredicate.normalize(new FilterPredicate(
					propertyName, operator, value)));
		}
		return this;
	}

	public Query addFilter(String filterStr) {
		if (filterStr == null) {
			return this;
		}
		FilterPredicate filter = FilterPredicate.valueOf(filterStr);
		if ((filter != null) && (filter.propertyName != null)
				&& (filter.operator != null) && (filter.value != null)) {

			if (PROPERTY_TYPE.equalsIgnoreCase(filter.propertyName)) {
				if (filter.operator == FilterOperator.EQUAL) {
					type = filter.value.toString();
				}
			} else if ("connection".equalsIgnoreCase(filter.propertyName)) {
				if (filter.operator == FilterOperator.EQUAL) {
					connection = filter.value.toString();
				}
			} else {
				for (FilterPredicate f : filterPredicates) {
					if (f.getPropertyName().equals(filter.getPropertyName())
							&& f.getValue().equals(filter.getValue())
							&& "*".equals(filter.getValue())) {
						logger.error("Attempted to set wildcard wilder for "
								+ f.getPropertyName()
								+ " more than once, discardng...");
						return this;
					}
				}
				filterPredicates.add(filter);
			}
		} else {
			logger.error("Unable to add filter to query: " + filterStr);
		}
		return this;
	}

	public Query addFilter(FilterPredicate filter) {
		filter = FilterPredicate.normalize(filter);
		if ((filter != null) && (filter.propertyName != null)
				&& (filter.operator != null) && (filter.value != null)) {

			if (PROPERTY_TYPE.equalsIgnoreCase(filter.propertyName)) {
				if (filter.operator == FilterOperator.EQUAL) {
					type = filter.value.toString();
				}
			} else if ("connection".equalsIgnoreCase(filter.propertyName)) {
				if (filter.operator == FilterOperator.EQUAL) {
					connection = filter.value.toString();
				}
			} else {
				filterPredicates.add(filter);
			}
		}
		return this;
	}

	public List getFilterPredicates() {
		return filterPredicates;
	}

	public boolean hasFilterPredicates() {
		return !filterPredicates.isEmpty();
	}

	public boolean hasFilterPredicatesExcludingSubkeys(
			Map subkeyProperties) {
		return !filterPredicates.isEmpty();
	}

	public Map getEqualityFilters() {
		Map map = new LinkedHashMap();

		for (FilterPredicate f : filterPredicates) {
			if (f.operator == FilterOperator.EQUAL) {
				Object val = f.getStartValue();
				if (val != null) {
					map.put(f.getPropertyName(), val);
				}
			}
		}
		return map.size() > 0 ? map : null;
	}

	public boolean hasFiltersForProperty(String name) {
		return hasFiltersForProperty(FilterOperator.EQUAL, name);
	}

	public boolean hasFiltersForProperty(FilterOperator operator, String name) {
		return getFilterForProperty(operator, name) != null;
	}

	public FilterPredicate getFilterForProperty(FilterOperator operator,
			String name) {
		if (name == null) {
			return null;
		}
		ListIterator iterator = filterPredicates
				.listIterator();
		while (iterator.hasNext()) {
			FilterPredicate f = iterator.next();
			if (f.propertyName.equalsIgnoreCase(name)) {
				if (operator != null) {
					if (operator == f.operator) {
						return f;
					}
				} else {
					return f;
				}
			}
		}
		return null;
	}

	public void removeFiltersForProperty(String name) {
		if (name == null) {
			return;
		}
		ListIterator iterator = filterPredicates
				.listIterator();
		while (iterator.hasNext()) {
			FilterPredicate f = iterator.next();
			if (f.propertyName.equalsIgnoreCase(name)) {
				iterator.remove();
			}
		}
	}

	public void setStartResult(UUID startResult) {
		this.startResult = startResult;
	}

	public Query withStartResult(UUID startResult) {
		this.startResult = startResult;
		return this;
	}

	public UUID getStartResult() {
		if ((startResult == null) && (cursor != null)) {
			byte[] cursorBytes = decodeBase64(cursor);
			if ((cursorBytes != null) && (cursorBytes.length == 16)) {
				startResult = uuid(cursorBytes);
			}
		}
		return startResult;
	}

	public String getCursor() {
		return cursor;
	}

	public void setCursor(String cursor) {
		if (cursor != null) {
			if (cursor.length() == 22) {
				byte[] cursorBytes = decodeBase64(cursor);
				if ((cursorBytes != null) && (cursorBytes.length == 16)) {
					startResult = uuid(cursorBytes);
					cursor = null;
				}
			}
		}
		this.cursor = cursor;
	}

	public Query withCursor(String cursor) {
		setCursor(cursor);
		return this;
	}

	public int getLimit() {
		return getLimit(DEFAULT_LIMIT);
	}

	public int getLimit(int defaultLimit) {
		if (limit <= 0) {
			if (defaultLimit > 0) {
				return defaultLimit;
			} else {
				return DEFAULT_LIMIT;
			}
		}
		return limit;
	}

	public void setLimit(int limit) {
		limitSet = true;
		this.limit = limit;
	}

	public Query withLimit(int limit) {
		limitSet = true;
		this.limit = limit;
		return this;
	}

	public boolean isLimitSet() {
		return limitSet;
	}

	public boolean isReversed() {
		return reversed;
	}

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

	public boolean isReversedSet() {
		return reversedSet;
	}

	public Long getStartTime() {
		return startTime;
	}

	public void setStartTime(Long startTime) {
		this.startTime = startTime;
	}

	public Long getFinishTime() {
		return finishTime;
	}

	public void setFinishTime(Long finishTime) {
		this.finishTime = finishTime;
	}

	public boolean isPad() {
		return pad;
	}

	public void setPad(boolean pad) {
		this.pad = pad;
	}

	public void setResolution(CounterResolution resolution) {
		this.resolution = resolution;
	}

	public CounterResolution getResolution() {
		return resolution;
	}

	public List getUsers() {
		return users;
	}

	public void addUser(Identifier user) {
		if (users == null) {
			users = new ArrayList();
		}
		users.add(user);
	}

	public void setUsers(List users) {
		this.users = users;
	}

	public List getGroups() {
		return groups;
	}

	public void addGroup(Identifier group) {
		if (groups == null) {
			groups = new ArrayList();
		}
		groups.add(group);
	}

	public void setGroups(List groups) {
		this.groups = groups;
	}

	public List getIdentifiers() {
		return identifiers;
	}

	public void addIdentifier(Identifier identifier) {
		if (identifiers == null) {
			identifiers = new ArrayList();
		}
		identifiers.add(identifier);
	}

	public void setIdentifiers(List identifiers) {
		this.identifiers = identifiers;
	}

	public boolean containsUuidIdentifersOnly() {
		if (hasFilterPredicates()) {
			return false;
		}
		if ((identifiers == null) || identifiers.isEmpty()) {
			return false;
		}
		for (Identifier identifier : identifiers) {
			if (!identifier.isUUID()) {
				return false;
			}
		}
		return true;
	}

	public boolean containsSingleUuidIdentifier() {
		return containsUuidIdentifersOnly() && (identifiers.size() == 1);
	}

	public List getUuidIdentifiers() {
		if ((identifiers == null) || identifiers.isEmpty()) {
			return null;
		}
		List ids = new ArrayList();
		for (Identifier identifier : identifiers) {
			if (identifier.isUUID()) {
				ids.add(identifier.getUUID());
			}
		}
		return ids;
	}

	public UUID getSingleUuidIdentifier() {
		if (!containsSingleUuidIdentifier()) {
			return null;
		}
		return (identifiers.get(0).getUUID());
	}

	public boolean containsNameIdentifiersOnly() {
		if (hasFilterPredicates()) {
			return false;
		}
		if ((identifiers == null) || identifiers.isEmpty()) {
			return false;
		}
		for (Identifier identifier : identifiers) {
			if (!identifier.isName()) {
				return false;
			}
		}
		return true;
	}

	public boolean containsSingleNameIdentifier() {
		return containsNameIdentifiersOnly() && (identifiers.size() == 1);
	}

	public List getNameIdentifiers() {
		if ((identifiers == null) || identifiers.isEmpty()) {
			return null;
		}
		List names = new ArrayList();
		for (Identifier identifier : identifiers) {
			if (identifier.isName()) {
				names.add(identifier.getName());
			}
		}
		return names;
	}

	public String getSingleNameIdentifier() {
		if (!containsSingleNameIdentifier()) {
			return null;
		}
		return (identifiers.get(0).toString());
	}

	public boolean containsEmailIdentifiersOnly() {
		if (hasFilterPredicates()) {
			return false;
		}
		if ((identifiers == null) || identifiers.isEmpty()) {
			return false;
		}
		for (Identifier identifier : identifiers) {
			if (identifier.isEmail()) {
				return false;
			}
		}
		return true;
	}

	public boolean containsSingleEmailIdentifier() {
		return containsEmailIdentifiersOnly() && (identifiers.size() == 1);
	}

	public List getEmailIdentifiers() {
		if ((identifiers == null) || identifiers.isEmpty()) {
			return null;
		}
		List emails = new ArrayList();
		for (Identifier identifier : identifiers) {
			if (identifier.isEmail()) {
				emails.add(identifier.getEmail());
			}
		}
		return emails;
	}

	public String getSingleEmailIdentifier() {
		if (!containsSingleEmailIdentifier()) {
			return null;
		}
		return (identifiers.get(0).toString());
	}

	public boolean containsNameOrEmailIdentifiersOnly() {
		if (hasFilterPredicates()) {
			return false;
		}
		if ((identifiers == null) || identifiers.isEmpty()) {
			return false;
		}
		for (Identifier identifier : identifiers) {
			if (!identifier.isEmail() && !identifier.isName()) {
				return false;
			}
		}
		return true;
	}

	public boolean containsSingleNameOrEmailIdentifier() {
		return containsNameOrEmailIdentifiersOnly()
				&& (identifiers.size() == 1);
	}

	public List getNameAndEmailIdentifiers() {
		if ((identifiers == null) || identifiers.isEmpty()) {
			return null;
		}
		List ids = new ArrayList();
		for (Identifier identifier : identifiers) {
			if (identifier.isEmail()) {
				ids.add(identifier.getEmail());
			} else if (identifier.isName()) {
				ids.add(identifier.getName());
			}
		}
		return ids;
	}

	public String getSingleNameOrEmailIdentifier() {
		if (!containsSingleNameOrEmailIdentifier()) {
			return null;
		}
		return (identifiers.get(0).toString());
	}

	public List getCategories() {
		return categories;
	}

	public void addCategory(String category) {
		if (categories == null) {
			categories = new ArrayList();
		}
		categories.add(category);
	}

	public void setCategories(List categories) {
		this.categories = categories;
	}

	public List getCounterFilters() {
		return counterFilters;
	}

	public void addCounterFilter(String counter) {
		CounterFilterPredicate p = CounterFilterPredicate.fromString(counter);
		if (p == null) {
			return;
		}
		if (counterFilters == null) {
			counterFilters = new ArrayList();
		}
		counterFilters.add(p);
	}

	public void setCounterFilters(List counterFilters) {
		this.counterFilters = counterFilters;
	}

	@Override
	public String toString() {
		if (selectSubjects.isEmpty() && filterPredicates.isEmpty()) {
			return "";
		}

		StringBuilder s = new StringBuilder("select ");
		if (type == null) {
			if (selectSubjects.isEmpty()) {
				s.append("*");
			} else {
				if (mergeSelectResults) {
					s.append("{ ");
					boolean first = true;
					for (Map.Entry select : selectSubjects
							.entrySet()) {
						if (!first) {
							s.append(", ");
						}
						s.append(select.getValue() + " : " + select.getKey());
						first = false;
					}
					s.append(" }");
				} else {
					boolean first = true;
					for (String select : selectSubjects.keySet()) {
						if (!first) {
							s.append(", ");
						}
						s.append(select);
						first = false;
					}
				}
			}
		} else {
			s.append(type);
		}
		if (!filterPredicates.isEmpty()) {
			s.append(" where ");
			boolean first = true;
			for (FilterPredicate f : filterPredicates) {
				if (!first) {
					s.append(" and ");
				}
				s.append(f.toString());
				first = false;
			}
		}
		return s.toString();
	}

	public static enum FilterOperator {
		LESS_THAN("<", "lt"), LESS_THAN_OR_EQUAL("<=", "lte"), GREATER_THAN(
				">", "gt"), GREATER_THAN_OR_EQUAL(">=", "gte"), EQUAL("=", "eq"), NOT_EQUAL(
				"!=", "ne"), IN("in", null), CONTAINS("contains", null), WITHIN(
				"within", null);

		private final String shortName;
		private final String textName;

		FilterOperator(String shortName, String textName) {
			this.shortName = shortName;
			this.textName = textName;
		}

		static Map nameMap = new ConcurrentHashMap();

		static {
			for (FilterOperator op : EnumSet.allOf(FilterOperator.class)) {
				if (op.shortName != null) {
					nameMap.put(op.shortName, op);
				}
				if (op.textName != null) {
					nameMap.put(op.textName, op);
				}
			}
		}

		public static FilterOperator find(String s) {
			if (s == null) {
				return null;
			}
			return nameMap.get(s);
		}

		@Override
		public String toString() {
			return shortName;
		}
	}

	public static enum SortDirection {
		ASCENDING, DESCENDING;

		public static SortDirection find(String s) {
			if (s == null) {
				return ASCENDING;
			}
			s = s.toLowerCase();
			if (s.startsWith("asc")) {
				return ASCENDING;
			}
			if (s.startsWith("des")) {
				return DESCENDING;
			}
			if (s.equals("+")) {
				return ASCENDING;
			}
			if (s.equals("-")) {
				return DESCENDING;
			}
			return ASCENDING;
		}
	}

	public static final class SortPredicate implements Serializable {
		private static final long serialVersionUID = 1L;
		private final String propertyName;
		private final Query.SortDirection direction;

		public SortPredicate(String propertyName, Query.SortDirection direction) {
			if (propertyName == null) {
				throw new NullPointerException("Property name was null");
			}

			if (direction == null) {
				direction = SortDirection.ASCENDING;
			}

			this.propertyName = propertyName.trim();
			this.direction = direction;
		}

		public SortPredicate(String propertyName, String direction) {
			this(propertyName, SortDirection.find(direction));
		}

		public String getPropertyName() {
			return propertyName;
		}

		public Query.SortDirection getDirection() {
			return direction;
		}

		public FilterPredicate toFilter() {
			return new FilterPredicate(propertyName, FilterOperator.EQUAL, "*");
		}

		@Override
		public boolean equals(Object o) {
			if (this == o) {
				return true;
			}
			if ((o == null) || (super.getClass() != o.getClass())) {
				return false;
			}

			SortPredicate that = (SortPredicate) o;

			if (direction != that.direction) {
				return false;
			}

			return (propertyName.equals(that.propertyName));
		}

		@Override
		public int hashCode() {
			int result = propertyName.hashCode();
			result = (31 * result) + direction.hashCode();
			return result;
		}

		@Override
		public String toString() {
			return propertyName
					+ ((direction == Query.SortDirection.DESCENDING) ? " DESC"
							: "");
		}
	}

	public static final class FilterPredicate implements Serializable {
		private static final long serialVersionUID = 1L;
		private final String propertyName;
		private final Query.FilterOperator operator;
		private final Object value;
		private String cursor;

		@SuppressWarnings({ "rawtypes", "unchecked" })
		public FilterPredicate(String propertyName,
				Query.FilterOperator operator, Object value) {
			if (propertyName == null) {
				throw new NullPointerException("Property name was null");
			}
			if (operator == null) {
				throw new NullPointerException("Operator was null");
			}
			if ((operator == Query.FilterOperator.IN)
					|| (operator == Query.FilterOperator.WITHIN)) {
				if ((!(value instanceof Collection))
						&& (value instanceof Iterable)) {
					List newValue = new ArrayList();
					for (Iterator i$ = ((Iterable) value).iterator(); i$
							.hasNext();) {
						Object val = i$.next();
						newValue.add(val);
					}
					value = newValue;
				}
				// DataTypeUtils.checkSupportedValue(propertyName, value, true,
				// true);
			} else {
				// DataTypeUtils.checkSupportedValue(propertyName, value, false,
				// false);
			}
			this.propertyName = propertyName;
			this.operator = operator;
			this.value = value;
		}

		public FilterPredicate(String propertyName, String operator,
				String value, String secondValue, String thirdValue) {
			this.propertyName = propertyName;
			this.operator = FilterOperator.find(operator);
			Object first_obj = parseValue(value, 0);
			Object second_obj = parseValue(secondValue, 0);
			Object third_obj = parseValue(thirdValue, 0);
			if (second_obj != null) {
				if (third_obj != null) {
					this.value = Arrays
							.asList(first_obj, second_obj, third_obj);
				} else {
					this.value = Arrays.asList(first_obj, second_obj);
				}
			} else {
				this.value = first_obj;
			}
		}

		public FilterPredicate(String propertyName, String operator,
				String value, int valueType, String secondValue,
				int secondValueType, String thirdValue, int thirdValueType) {
			this.propertyName = propertyName;
			this.operator = FilterOperator.find(operator);
			Object first_obj = parseValue(value, valueType);
			Object second_obj = parseValue(secondValue, secondValueType);
			Object third_obj = parseValue(thirdValue, thirdValueType);
			if (second_obj != null) {
				if (third_obj != null) {
					this.value = Arrays
							.asList(first_obj, second_obj, third_obj);
				} else {
					this.value = Arrays.asList(first_obj, second_obj);
				}
			} else {
				this.value = first_obj;
			}
		}

		private static Object parseValue(String val, int valueType) {
			if (val == null) {
				return null;
			}

			if (val.startsWith("'") && (val.length() > 1)) {
				return val.substring(1, val.length() - 1);
			}

			if (val.equalsIgnoreCase("true") || val.equalsIgnoreCase("false")) {
				return Boolean.valueOf(val);
			}

			if (val.length() == 36) {
				try {
					return UUID.fromString(val);
				} catch (IllegalArgumentException e) {
				}
			}

			try {
				return Long.valueOf(val);
			} catch (NumberFormatException e) {
			}

			try {
				return Float.valueOf(val);
			} catch (NumberFormatException e) {

			}

			return null;
		}

		public static FilterPredicate valueOf(String str) {
			if (str == null) {
				return null;
			}
			try {
				ANTLRStringStream in = new ANTLRStringStream(str.trim());
				QueryFilterLexer lexer = new QueryFilterLexer(in);
				CommonTokenStream tokens = new CommonTokenStream(lexer);
				QueryFilterParser parser = new QueryFilterParser(tokens);
				FilterPredicate filter = parser.filter();
				return normalize(filter);
			} catch (Exception e) {
				logger.error("Unable to parse \"" + str + "\"", e);
			}
			return null;
		}

		public static FilterPredicate normalize(FilterPredicate p) {
			if (p == null) {
				return null;
			}
			if (p.operator == FilterOperator.CONTAINS) {
				String propertyName = appendSuffix(p.propertyName, "keywords");
				return new FilterPredicate(propertyName, FilterOperator.EQUAL,
						p.value);
			} else if (p.operator == FilterOperator.WITHIN) {
				String propertyName = appendSuffix(p.propertyName,
						"coordinates");
				return new FilterPredicate(propertyName, FilterOperator.WITHIN,
						p.value);
			}

			return p;
		}

		private static String appendSuffix(String str, String suffix) {
			if (StringUtils.isNotEmpty(str)) {
				if (!str.endsWith("." + suffix)) {
					str += "." + suffix;
				}
			} else {
				str = suffix;
			}
			return str;
		}

		public String getPropertyName() {
			return propertyName;
		}

		public Query.FilterOperator getOperator() {
			return operator;
		}

		public Object getValue() {
			return value;
		}

		@SuppressWarnings("unchecked")
		public Object getStartValue() {
			if (value instanceof List) {
				List l = (List) value;
				return l.get(0);
			}
			if ((operator == FilterOperator.GREATER_THAN)
					|| (operator == FilterOperator.GREATER_THAN_OR_EQUAL)
					|| (operator == FilterOperator.EQUAL)) {
				return value;
			} else {
				return null;
			}
		}

		@SuppressWarnings("unchecked")
		public Object getFinishValue() {
			if (value instanceof List) {
				List l = (List) value;
				if (l.size() > 1) {
					return l.get(1);
				}
				return null;
			}
			if ((operator == FilterOperator.LESS_THAN)
					|| (operator == FilterOperator.LESS_THAN_OR_EQUAL)
					|| (operator == FilterOperator.EQUAL)) {
				return value;
			} else {
				return null;
			}
		}

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

		public String getCursor() {
			return cursor;
		}

		@Override
		public int hashCode() {
			final int prime = 31;
			int result = 1;
			result = (prime * result)
					+ ((operator == null) ? 0 : operator.hashCode());
			result = (prime * result)
					+ ((propertyName == null) ? 0 : propertyName.hashCode());
			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;
			}
			FilterPredicate other = (FilterPredicate) obj;
			if (operator != other.operator) {
				return false;
			}
			if (propertyName == null) {
				if (other.propertyName != null) {
					return false;
				}
			} else if (!propertyName.equals(other.propertyName)) {
				return false;
			}
			if (value == null) {
				if (other.value != null) {
					return false;
				}
			} else if (!value.equals(other.value)) {
				return false;
			}
			return true;
		}

		@Override
		public String toString() {
			String valueStr = "\'\'";
			if (value != null) {
				if (value instanceof String) {
					valueStr = "\'" + value + "\'";
				} else {
					valueStr = value.toString();
				}
			}
			return propertyName + " " + operator.toString() + " " + valueStr;
		}
	}

	public static final class CounterFilterPredicate implements Serializable {

		private static final long serialVersionUID = 1L;
		private final String name;
		private final Identifier user;
		private final Identifier group;
		private final String queue;
		private final String category;

		public CounterFilterPredicate(String name, Identifier user,
				Identifier group, String queue, String category) {
			this.name = name;
			this.user = user;
			this.group = group;
			this.queue = queue;
			this.category = category;
		}

		public Identifier getUser() {
			return user;
		}

		public Identifier getGroup() {
			return group;
		}

		public String getQueue() {
			return queue;
		}

		public String getCategory() {
			return category;
		}

		public String getName() {
			return name;
		}

		public static CounterFilterPredicate fromString(String s) {
			Identifier user = null;
			Identifier group = null;
			String category = null;
			String name = null;
			String[] l = split(s, ':');

			if (l.length > 0) {
				if (!"*".equals(l[0])) {
					name = l[0];
				}
			}

			if (l.length > 1) {
				if (!"*".equals(l[1])) {
					user = Identifier.from(l[1]);
				}
			}

			if (l.length > 2) {
				if (!"*".equals(l[2])) {
					group = Identifier.from(l[3]);
				}
			}

			if (l.length > 3) {
				if (!"*".equals(l[3])) {
					category = l[3];
				}
			}

			if ((user == null) && (group == null) && (category == null)
					&& (name == null)) {
				return null;
			}

			return new CounterFilterPredicate(name, user, group, null, category);
		}

		public static List fromList(List l) {
			if ((l == null) || (l.size() == 0)) {
				return null;
			}
			List counterFilters = new ArrayList();
			for (String s : l) {
				CounterFilterPredicate filter = CounterFilterPredicate
						.fromString(s);
				if (filter != null) {
					counterFilters.add(filter);
				}
			}
			if (counterFilters.size() == 0) {
				return null;
			}
			return counterFilters;
		}
	}

	public List getSelectionResults(Results rs) {

		List entities = rs.getEntities();
		if (entities == null) {
			return null;
		}

		if (!hasSelectSubjects()) {
			return cast(entities);
		}

		List results = new ArrayList();

		for (Entity entity : entities) {
			if (isMergeSelectResults()) {
				boolean include = false;
				Map result = new LinkedHashMap();
				Map selects = getSelectAssignments();
				for (Map.Entry select : selects.entrySet()) {
					Object obj = JsonUtils.select(entity, select.getKey(),
							false);
					if (obj == null) {
						obj = "";
					} else {
						include = true;
					}
					result.put(select.getValue(), obj);
				}
				if (include) {
					results.add(result);
				}
			} else {
				boolean include = false;
				List result = new ArrayList();
				Set selects = getSelectSubjects();
				for (String select : selects) {
					Object obj = JsonUtils.select(entity, select);
					if (obj == null) {
						obj = "";
					} else {
						include = true;
					}
					result.add(obj);
				}
				if (include) {
					results.add(result);
				}
			}
		}

		if (results.size() == 0) {
			return null;
		}

		return results;
	}

	public Object getSelectionResult(Results rs) {
		List r = getSelectionResults(rs);
		if ((r != null) && (r.size() > 0)) {
			return r.get(0);
		}
		return null;
	}
}