org.apache.usergrid.mq.Query.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.usergrid.mq.Query.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.apache.usergrid.mq;

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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.usergrid.persistence.CounterResolution;
import org.apache.usergrid.persistence.Entity;
import org.apache.usergrid.persistence.Identifier;
import org.apache.usergrid.persistence.Results;
import org.apache.usergrid.persistence.Results.Level;
import org.apache.usergrid.utils.JsonUtils;
import org.springframework.util.Assert;

import org.apache.commons.lang.StringUtils;

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.apache.usergrid.persistence.Schema.PROPERTY_TYPE;
import static org.apache.usergrid.persistence.Schema.PROPERTY_UUID;
import static org.apache.usergrid.utils.ClassUtils.cast;
import static org.apache.usergrid.utils.ConversionUtils.uuid;
import static org.apache.usergrid.utils.ListUtils.first;
import static org.apache.usergrid.utils.ListUtils.firstBoolean;
import static org.apache.usergrid.utils.ListUtils.firstInteger;
import static org.apache.usergrid.utils.ListUtils.firstLong;
import static org.apache.usergrid.utils.ListUtils.firstUuid;
import static org.apache.usergrid.utils.ListUtils.isEmpty;
import static org.apache.usergrid.utils.MapUtils.toMapList;

public class Query {

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

    public static final int DEFAULT_LIMIT = 10;

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

    protected Map<String, String> selectSubjects = new LinkedHashMap<String, String>();
    protected boolean mergeSelectResults = false;
    protected Level level = Level.ALL_PROPERTIES;
    protected String connection;
    protected List<String> permissions;
    protected boolean reversed;
    protected boolean reversedSet = false;
    protected Long startTime;
    protected Long finishTime;
    protected boolean pad;
    protected CounterResolution resolution = CounterResolution.ALL;
    protected List<Identifier> users;
    protected List<Identifier> groups;
    protected List<Identifier> identifiers;
    protected List<String> categories;
    protected List<CounterFilterPredicate> 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<SortPredicate>(q.sortPredicates) : null;
            filterPredicates = q.filterPredicates != null ? new ArrayList<FilterPredicate>(q.filterPredicates)
                    : null;
            startResult = q.startResult;
            cursor = q.cursor;
            limit = q.limit;
            limitSet = q.limitSet;
            selectSubjects = q.selectSubjects != null ? new LinkedHashMap<String, String>(q.selectSubjects) : null;
            mergeSelectResults = q.mergeSelectResults;
            level = q.level;
            connection = q.connection;
            permissions = q.permissions != null ? new ArrayList<String>(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<Identifier>(q.users) : null;
            groups = q.groups != null ? new ArrayList<Identifier>(q.groups) : null;
            identifiers = q.identifiers != null ? new ArrayList<Identifier>(q.identifiers) : null;
            categories = q.categories != null ? new ArrayList<String>(q.categories) : null;
            counterFilters = q.counterFilters != null ? new ArrayList<CounterFilterPredicate>(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<String, List<String>> params = cast(toMapList((Map) o));
            return fromQueryParams(params);
        }
        return null;
    }

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

        List<String> 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<String, List<String>> 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<Identifier>();
                    }
                    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<String, String>();
            selectSubjects.put(PROPERTY_UUID, PROPERTY_UUID);
            level = Level.IDS;
        } else if (isIdsOnly()) {
            selectSubjects = new LinkedHashMap<String, String>();
            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<String> getPermissions() {
        return permissions;
    }

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

    public Query withPermissions(List<String> 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;
        }

        mergeSelectResults = StringUtils.isNotEmpty(output);

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

        selectSubjects.put(select, output);

        return this;
    }

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

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

    public Map<String, String> 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<SortPredicate> 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<FilterPredicate> getFilterPredicates() {
        return filterPredicates;
    }

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

    public Map<String, Object> getEqualityFilters() {
        Map<String, Object> map = new LinkedHashMap<String, Object>();

        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<FilterPredicate> 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<FilterPredicate> 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<Identifier> getUsers() {
        return users;
    }

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

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

    public List<Identifier> getGroups() {
        return groups;
    }

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

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

    public List<Identifier> getIdentifiers() {
        return identifiers;
    }

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

    public void setIdentifiers(List<Identifier> 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<UUID> getUuidIdentifiers() {
        if ((identifiers == null) || identifiers.isEmpty()) {
            return null;
        }
        List<UUID> ids = new ArrayList<UUID>();
        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<String> getNameIdentifiers() {
        if ((identifiers == null) || identifiers.isEmpty()) {
            return null;
        }
        List<String> names = new ArrayList<String>();
        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<String> getEmailIdentifiers() {
        if ((identifiers == null) || identifiers.isEmpty()) {
            return null;
        }
        List<String> emails = new ArrayList<String>();
        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<String> getNameAndEmailIdentifiers() {
        if ((identifiers == null) || identifiers.isEmpty()) {
            return null;
        }
        List<String> ids = new ArrayList<String>();
        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<String> getCategories() {
        return categories;
    }

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

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

    public List<CounterFilterPredicate> getCounterFilters() {
        return counterFilters;
    }

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

    public void setCounterFilters(List<CounterFilterPredicate> 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<String, String> 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<String, FilterOperator> nameMap = new ConcurrentHashMap<String, FilterOperator>();

        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) {
            Assert.notNull(propertyName, "Property name was null");
            Assert.notNull(operator, "Operator was null");
            if ((operator == Query.FilterOperator.IN) || (operator == Query.FilterOperator.WITHIN)) {
                if ((!(value instanceof Collection)) && (value instanceof Iterable)) {
                    List newValue = new ArrayList();
                    for (Object val : ((Iterable) value)) {
                        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<Object> l = (List<Object>) 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<Object> l = (List<Object>) 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<CounterFilterPredicate> fromList(List<String> l) {
            if ((l == null) || (l.size() == 0)) {
                return null;
            }
            List<CounterFilterPredicate> counterFilters = new ArrayList<CounterFilterPredicate>();
            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<Object> getSelectionResults(Results rs) {

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

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

        List<Object> results = new ArrayList<Object>();

        for (Entity entity : entities) {
            if (isMergeSelectResults()) {
                boolean include = false;
                Map<String, Object> result = new LinkedHashMap<String, Object>();
                Map<String, String> selects = getSelectAssignments();
                for (Map.Entry<String, String> 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<Object> result = new ArrayList<Object>();
                Set<String> 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<Object> r = getSelectionResults(rs);
        if ((r != null) && (r.size() > 0)) {
            return r.get(0);
        }
        return null;
    }
}