org.grails.datastore.mapping.gemfire.query.GemfireQuery.java Source code

Java tutorial

Introduction

Here is the source code for org.grails.datastore.mapping.gemfire.query.GemfireQuery.java

Source

/* Copyright (C) 2010 SpringSource
 *
 * 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.grails.datastore.mapping.gemfire.query;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.springframework.dao.InvalidDataAccessResourceUsageException;
import org.springframework.data.gemfire.GemfireCallback;
import org.springframework.data.gemfire.GemfireTemplate;
import org.grails.datastore.mapping.gemfire.GemfireDatastore;
import org.grails.datastore.mapping.gemfire.GemfireSession;
import org.grails.datastore.mapping.model.PersistentEntity;
import org.grails.datastore.mapping.model.PersistentProperty;
import org.grails.datastore.mapping.model.types.ToOne;
import org.grails.datastore.mapping.query.Query;
import org.grails.datastore.mapping.query.order.ManualEntityOrdering;
import org.grails.datastore.mapping.query.projections.ManualProjections;

import com.gemstone.gemfire.GemFireCheckedException;
import com.gemstone.gemfire.GemFireException;
import com.gemstone.gemfire.cache.Region;
import com.gemstone.gemfire.cache.query.QueryService;
import com.gemstone.gemfire.cache.query.SelectResults;

/**
 * <p>Adds query support for Gemfire. Note that due to limitations in the Gemfire API some operations
 * are more expensive than in other stores.</p>
 *
 * <p>In particular Gemfire doesn't support native ORDER BY or OFFSET clauses hence these are handled manually by this
 * implementation which results in some performance implications when ordering or offsets are used in queries.</p>
 *
 * @author Graeme Rocher
 * @since 1.0
 */
@SuppressWarnings({ "hiding", "rawtypes", "unchecked" })
public class GemfireQuery extends Query {
    public static final String SELECT_CLAUSE = "SELECT ";
    public static final String SELECT_DISTINCT = " DISTINCT ";
    public static final String WHERE_CLAUSE = " WHERE ";
    public static final String FROM_CLAUSE = " FROM /";
    public static final String LIMIT_CLAUSE = " LIMIT ";
    public static final String NOT_CLAUSE = " NOT ";
    public static final String LOGICAL_OR = " OR ";
    private ManualEntityOrdering ordering;
    private ManualProjections manualProjections;
    private String regionName;

    private static interface QueryHandler {
        public int handle(PersistentEntity entity, Criterion criterion, StringBuilder query, List params,
                int index);
    }

    private static Map<Class, QueryHandler> queryHandlers = new HashMap<Class, QueryHandler>();

    public static final String GREATER_THAN_EQUALS = " >= ";
    public static final String LESS_THAN_EQUALS = " <= ";
    public static final String LOGICAL_AND = " AND ";
    public static final String GREATER_THAN = " > ";
    public static final String WILDCARD = " * ";
    public static final char SPACE = ' ';
    public static final char DOLLAR_SIGN = '$';
    public static final String LESS_THAN = " < ";
    public static final String EQUALS = " = ";
    public static final String NOT_EQUALS = " != ";
    public static final String LIKE = " like ";

    static {
        queryHandlers.put(Equals.class, new QueryHandler() {
            public int handle(PersistentEntity entity, Criterion criterion, StringBuilder q, List params,
                    int index) {
                Equals eq = (Equals) criterion;
                final String name = eq.getProperty();
                validateProperty(entity, name, Equals.class);

                q.append(calculateName(entity, name));
                return appendOrEmbedValue(q, params, index, eq.getValue(), EQUALS);
            }
        });
        queryHandlers.put(IdEquals.class, new QueryHandler() {
            public int handle(PersistentEntity entity, Criterion criterion, StringBuilder q, List params,
                    int index) {
                IdEquals eq = (IdEquals) criterion;
                String name = entity.getIdentity().getName();
                validateProperty(entity, name, Equals.class);

                q.append(calculateName(entity, name));
                return appendOrEmbedValue(q, params, index, eq.getValue(), EQUALS);
            }
        });
        queryHandlers.put(NotEquals.class, new QueryHandler() {
            public int handle(PersistentEntity entity, Criterion criterion, StringBuilder q, List params,
                    int index) {
                NotEquals eq = (NotEquals) criterion;
                final String name = eq.getProperty();
                validateProperty(entity, name, NotEquals.class);

                q.append(calculateName(entity, name));
                return appendOrEmbedValue(q, params, index, eq.getValue(), NOT_EQUALS);
            }
        });
        queryHandlers.put(Like.class, new QueryHandler() {
            public int handle(PersistentEntity entity, Criterion criterion, StringBuilder q, List params,
                    int index) {
                Like eq = (Like) criterion;
                final String name = eq.getProperty();
                validateProperty(entity, name, Like.class);
                q.append(calculateName(entity, name)).append(" like '").append(eq.getValue()).append("' ");

                //                params.add(eq.getValue());
                return index;
            }
        });

        queryHandlers.put(In.class, new QueryHandler() {
            public int handle(PersistentEntity entity, Criterion criterion, StringBuilder q, List params,
                    int index) {
                In eq = (In) criterion;
                final String name = eq.getProperty();
                validateProperty(entity, name, In.class);
                q.append(calculateName(entity, name)).append(" IN SET (");

                final Collection values = eq.getValues();
                for (Iterator iterator = values.iterator(); iterator.hasNext();) {
                    Object value = iterator.next();
                    index = appendOrEmbedValue(q, params, index, value, "");
                    if (iterator.hasNext())
                        q.append(",");
                }

                q.append(") ");

                return index;
            }
        });
        queryHandlers.put(IsNull.class, new QueryHandler() {
            public int handle(PersistentEntity entity, Criterion criterion, StringBuilder q, List params,
                    int index) {
                IsNull eq = (IsNull) criterion;
                final String name = eq.getProperty();
                validateProperty(entity, name, IsNull.class);
                q.append(calculateName(entity, name)).append(" = NULL ");

                return index;
            }
        });
        queryHandlers.put(Disjunction.class, new QueryHandler() {
            public int handle(PersistentEntity entity, Criterion criterion, StringBuilder query, List params,
                    int index) {
                return buildWhereClause(entity, (Junction) criterion, query, index, params);
            }
        });
        queryHandlers.put(Conjunction.class, new QueryHandler() {
            public int handle(PersistentEntity entity, Criterion criterion, StringBuilder query, List params,
                    int index) {
                return buildWhereClause(entity, (Junction) criterion, query, index, params);
            }
        });
        queryHandlers.put(Negation.class, new QueryHandler() {
            public int handle(PersistentEntity entity, Criterion criterion, StringBuilder query, List params,
                    int index) {
                return buildWhereClause(entity, (Junction) criterion, query, index, params);
            }
        });

        queryHandlers.put(Between.class, new QueryHandler() {

            public int handle(PersistentEntity entity, Criterion criterion, StringBuilder q, List params,
                    int index) {
                Between eq = (Between) criterion;
                final String name = eq.getProperty();
                validateProperty(entity, name, Between.class);
                q.append("(");
                final String calculatedName = calculateName(entity, name);
                q.append(calculatedName);

                index = appendOrEmbedValue(q, params, index, eq.getFrom(), GREATER_THAN_EQUALS);
                q.append(LOGICAL_AND).append(calculatedName);

                index = appendOrEmbedValue(q, params, index, eq.getTo(), LESS_THAN_EQUALS);
                q.append(") ");

                return index;
            }
        });

        queryHandlers.put(GreaterThan.class, new QueryHandler() {

            public int handle(PersistentEntity entity, Criterion criterion, StringBuilder q, List params,
                    int index) {
                PropertyCriterion eq = (PropertyCriterion) criterion;
                final String name = eq.getProperty();
                validateProperty(entity, name, GreaterThan.class);
                q.append(calculateName(entity, name));
                return appendOrEmbedValue(q, params, index, eq.getValue(), GREATER_THAN);
            }
        });

        queryHandlers.put(GreaterThanEquals.class, new QueryHandler() {

            public int handle(PersistentEntity entity, Criterion criterion, StringBuilder q, List params,
                    int index) {
                PropertyCriterion eq = (PropertyCriterion) criterion;
                final String name = eq.getProperty();
                validateProperty(entity, name, GreaterThanEquals.class);
                q.append(calculateName(entity, name));
                return appendOrEmbedValue(q, params, index, eq.getValue(), GREATER_THAN_EQUALS);
            }
        });

        queryHandlers.put(LessThanEquals.class, new QueryHandler() {

            public int handle(PersistentEntity entity, Criterion criterion, StringBuilder q, List params,
                    int index) {
                PropertyCriterion eq = (PropertyCriterion) criterion;
                final String name = eq.getProperty();
                validateProperty(entity, name, LessThanEquals.class);
                q.append(calculateName(entity, name));
                return appendOrEmbedValue(q, params, index, eq.getValue(), LESS_THAN_EQUALS);
            }
        });

        queryHandlers.put(LessThan.class, new QueryHandler() {

            public int handle(PersistentEntity entity, Criterion criterion, StringBuilder q, List params,
                    int index) {
                PropertyCriterion eq = (PropertyCriterion) criterion;
                final String name = eq.getProperty();
                validateProperty(entity, name, LessThan.class);
                q.append(calculateName(entity, name));
                return appendOrEmbedValue(q, params, index, eq.getValue(), LESS_THAN);
            }
        });

    }

    private static int appendOrEmbedValue(StringBuilder q, List params, int index, Object value, String operator) {
        if (params == null) {
            q.append(operator);
            appendValue(q, value);
            q.append(SPACE);
        } else {
            q.append(operator).append(DOLLAR_SIGN).append(++index).append(SPACE);
            params.add(value);
        }
        return index;
    }

    private static void appendValue(StringBuilder q, Object value) {
        if (value instanceof Number || value instanceof Boolean) {
            q.append(value);
        } else {
            q.append(quote(value));
        }
    }

    private static Object quote(Object value) {
        return "'" + value + "'";
    }

    private static String calculateName(PersistentEntity entity, String name) {
        final PersistentProperty prop = entity.getPropertyByName(name);
        if (prop instanceof ToOne) {
            ToOne association = (ToOne) prop;
            final PersistentEntity associated = association.getAssociatedEntity();
            return name + "." + associated.getIdentity().getName();
        }
        return name;
    }

    private static void validateProperty(PersistentEntity entity, String name, Class criterionType) {
        if (entity.getIdentity().getName().equals(name))
            return;
        PersistentProperty prop = entity.getPropertyByName(name);
        if (prop == null) {
            throw new InvalidDataAccessResourceUsageException("Cannot use [" + criterionType.getSimpleName()
                    + "] criterion on non-existent property: " + name);
        }
    }

    GemfireDatastore gemfireDatastore;

    public GemfireQuery(GemfireSession session, PersistentEntity entity) {
        super(session, entity);
        gemfireDatastore = (GemfireDatastore) session.getDatastore();
        this.ordering = new ManualEntityOrdering(entity);
        this.manualProjections = new ManualProjections(entity);
        final org.grails.datastore.mapping.gemfire.config.Region region = getMappedRegionInfo(entity);

        if (region != null && region.getRegion() != null) {
            this.regionName = region.getRegion();
        } else {
            this.regionName = entity.getDecapitalizedName();
        }
    }

    private org.grails.datastore.mapping.gemfire.config.Region getMappedRegionInfo(PersistentEntity entity) {
        final Object mappedForm = entity.getMapping().getMappedForm();

        org.grails.datastore.mapping.gemfire.config.Region mappedRegion = null;
        if (mappedForm instanceof org.grails.datastore.mapping.gemfire.config.Region) {
            mappedRegion = (org.grails.datastore.mapping.gemfire.config.Region) mappedForm;
        }
        return mappedRegion;
    }

    @Override
    protected List executeQuery(final PersistentEntity entity, Junction criteria) {
        final ProjectionList projectionList = projections();
        if (criteria.isEmpty() && !(max > -1)) {
            return (List) gemfireDatastore.getTemplate(entity).execute(new GemfireCallback() {

                public Object doInGemfire(Region region) throws GemFireCheckedException, GemFireException {

                    List finalResults;
                    if (projectionList.isEmpty()) {
                        finalResults = new ArrayList(region.values());
                    } else {
                        List results = new ArrayList();
                        for (Projection projection : projectionList.getProjectionList()) {
                            Collection values = null;
                            if (projection instanceof CountProjection) {
                                results.add(region.size());
                            } else if (projection instanceof MinProjection) {
                                MinProjection min = (MinProjection) projection;
                                if (values == null) {
                                    values = region.values();
                                }
                                results.add(manualProjections.min(values, min.getPropertyName()));
                            } else if (projection instanceof MaxProjection) {
                                if (values == null) {
                                    values = region.values();
                                }
                                MaxProjection maxProjection = (MaxProjection) projection;
                                results.add(manualProjections.max(values, maxProjection.getPropertyName()));
                            } else if (projection instanceof IdProjection) {
                                results.add(region.keySet());
                            } else if (projection instanceof CountDistinctProjection) {
                                if (values == null) {
                                    values = region.values();
                                }
                                results.add(manualProjections.countDistinct(values,
                                        ((PropertyProjection) projection).getPropertyName()));
                            } else if (projection.getClass() == PropertyProjection.class) {
                                if (values == null) {
                                    values = region.values();
                                }
                                final List propertyProjectionResults = manualProjections.property(values,
                                        ((PropertyProjection) projection).getPropertyName());
                                if (projectionList.getProjectionList().size() == 1) {
                                    results = propertyProjectionResults;
                                } else {
                                    results.add(propertyProjectionResults);
                                }
                            }

                        }
                        finalResults = results;
                    }
                    finalResults = ordering.applyOrder(finalResults, getOrderBy());
                    return finalResults;
                }
            });
        }

        GemfireTemplate template = gemfireDatastore.getTemplate(entity);
        final List params = new ArrayList();
        final String queryString = getQueryString(params, true);

        return (List) template.execute(new GemfireCallback() {

            public Object doInGemfire(Region region) throws GemFireCheckedException, GemFireException {

                final QueryService queryService = gemfireDatastore.getGemfireCache().getQueryService();

                final com.gemstone.gemfire.cache.query.Query gemfireQuery = queryService.newQuery(queryString);

                final Object result = gemfireQuery.execute(params.toArray());
                List finalResults = Collections.emptyList();
                if (projectionList.isEmpty()) {
                    if (result instanceof SelectResults) {
                        finalResults = ((SelectResults) result).asList();
                    } else {
                        finalResults = wrapResultInList(result);
                    }
                } else {
                    if (result instanceof SelectResults) {
                        SelectResults selectResults = (SelectResults) result;
                        finalResults = applyProjections(selectResults.asList(), projectionList);
                    } else {
                        finalResults = wrapResultInList(result);
                    }

                }

                finalResults = ordering.applyOrder(finalResults, getOrderBy());

                if (offset > 0) {

                    final int resultSize = finalResults.size();
                    if (offset > resultSize) {
                        finalResults = Collections.emptyList();
                    } else {
                        int end = resultSize;
                        if (max > -1) {
                            end = offset + max;
                            if (end > resultSize) {
                                end = resultSize;
                            }
                        }
                        finalResults = finalResults.subList(offset, end);
                    }
                }

                return finalResults;
            }
        });
    }

    /**
     * Obtains the query string with variables embedded within the Query
     * @return The query string
     */
    public String getQueryString() {
        return getQueryString(null, false);
    }

    protected String getQueryString(List params, boolean distinct) {
        ProjectionList projectionList = projections();
        String select = SELECT_CLAUSE;
        String from = FROM_CLAUSE + regionName;
        String where = WHERE_CLAUSE;

        final StringBuilder q = new StringBuilder();
        q.append(select);
        if (distinct) {
            q.append(SELECT_DISTINCT);
        }
        if (projectionList.isEmpty()) {
            q.append(WILDCARD);
        } else {
            boolean modifiedQuery = false;
            for (Projection projection : projectionList.getProjectionList()) {
                if (projection instanceof IdProjection) {
                    if (modifiedQuery) {
                        q.append(',');
                    }
                    q.append(SPACE).append(entity.getIdentity().getName());
                    modifiedQuery = true;

                } else if (projection.getClass() == PropertyProjection.class) {
                    if (modifiedQuery) {
                        q.append(',');
                    }

                    q.append(SPACE).append(((PropertyProjection) projection).getPropertyName());
                    modifiedQuery = true;
                }
            }

            if (!modifiedQuery) {
                q.append(WILDCARD);
            } else {
                q.append(SPACE);
            }
        }
        q.append(from);

        if (!criteria.isEmpty()) {
            q.append(where);
            buildWhereClause(entity, criteria, q, 0, params);
        }

        if (max > 0 && offset == 0) {
            q.append(LIMIT_CLAUSE).append(max);
        }

        return q.toString();
    }

    private List applyProjections(List results, ProjectionList projections) {
        List projectedResults = new ArrayList();
        for (Projection projection : projections.getProjectionList()) {
            if (projection instanceof CountProjection) {
                projectedResults.add(results.size());
            } else if (projection instanceof MinProjection) {
                MinProjection min = (MinProjection) projection;
                projectedResults.add(manualProjections.min(results, min.getPropertyName()));
            } else if (projection instanceof MaxProjection) {
                MaxProjection min = (MaxProjection) projection;
                projectedResults.add(manualProjections.max(results, min.getPropertyName()));
            }
        }
        if (projectedResults.isEmpty()) {
            return results;
        }
        return projectedResults;
    }

    private List wrapResultInList(Object result) {
        List listResult = new ArrayList();
        listResult.add(result);
        return listResult;
    }

    private static int buildWhereClause(PersistentEntity entity, Junction criteria, StringBuilder q, int index,
            List params) {
        final List<Criterion> criterionList = criteria.getCriteria();
        if (criteria instanceof Negation) {
            q.append(NOT_CLAUSE);
        }
        q.append('(');
        for (Iterator<Criterion> iterator = criterionList.iterator(); iterator.hasNext();) {
            Criterion criterion = iterator.next();

            final String operator = criteria instanceof Conjunction ? LOGICAL_AND : LOGICAL_OR;
            QueryHandler qh = queryHandlers.get(criterion.getClass());
            if (qh != null) {
                index = qh.handle(entity, criterion, q, params, index);
            }

            if (iterator.hasNext()) {
                q.append(operator);
            }
        }
        q.append(')');
        return index;
    }
}