org.springframework.datastore.mapping.jpa.query.JpaQuery.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.datastore.mapping.jpa.query.JpaQuery.java

Source

/* Copyright (C) 2011 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.springframework.datastore.mapping.jpa.query;

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

import javax.persistence.EntityManager;
import javax.persistence.PersistenceException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.convert.ConversionService;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.dao.InvalidDataAccessResourceUsageException;
import org.springframework.datastore.mapping.jpa.JpaSession;
import org.springframework.datastore.mapping.model.PersistentEntity;
import org.springframework.datastore.mapping.model.PersistentProperty;
import org.springframework.datastore.mapping.model.types.Association;
import org.springframework.datastore.mapping.model.types.ToOne;
import org.springframework.datastore.mapping.query.AssociationQuery;
import org.springframework.datastore.mapping.query.Query;
import org.springframework.orm.jpa.JpaCallback;
import org.springframework.orm.jpa.JpaTemplate;

/**
 * Query implementation for JPA
 * 
 * @author Graeme Rocher
 * @since 1.0
 *
 */
public class JpaQuery extends Query {

    private static final String DISTINCT_CLAUSE = "DISTINCT ";
    private static final String SELECT_CLAUSE = "SELECT ";
    private static final String AS_CLAUSE = " AS ";
    private static final String FROM_CLAUSE = " FROM ";
    private static final String ORDER_BY_CLAUSE = " ORDER BY ";
    private static final String WHERE_CLAUSE = " WHERE ";
    private static final char COMMA = ',';
    private static final char CLOSE_BRACKET = ')';
    private static final char OPEN_BRACKET = '(';
    private static final char SPACE = ' ';
    private static final char QUESTIONMARK = '?';
    private static final char DOT = '.';
    public static final String NOT_CLAUSE = " NOT";
    public static final String LOGICAL_AND = " AND ";
    public static final String LOGICAL_OR = " OR ";

    private static final Log LOG = LogFactory
            .getLog(org.springframework.datastore.mapping.jpa.query.JpaQuery.class);
    private static final Map<Class, QueryHandler> queryHandlers = new HashMap<Class, QueryHandler>();

    static public int appendCriteriaForOperator(StringBuilder q, String logicalName, final String name,
            int position, String operator) {
        q.append(logicalName).append(DOT).append(name).append(operator).append(QUESTIONMARK).append(++position);
        return position;
    }

    static {
        queryHandlers.put(AssociationQuery.class, new QueryHandler() {
            @Override
            public int handle(PersistentEntity entity, Criterion criterion, StringBuilder q,
                    StringBuilder whereClause, String logicalName, int position, List parameters,
                    ConversionService conversionService) {
                AssociationQuery aq = (AssociationQuery) criterion;
                final Association<?> association = aq.getAssociation();
                if (association instanceof ToOne) {

                    final String associationName = association.getName();
                    logicalName = logicalName + DOT + associationName;
                    return buildWhereClauseForCriterion(association.getAssociatedEntity(), aq.getCriteria(), q,
                            whereClause, logicalName, aq.getCriteria().getCriteria(), position, parameters,
                            conversionService);
                } else if (association != null) {
                    final String associationName = association.getName();
                    q.append(" INNER JOIN ").append(logicalName).append(DOT).append(associationName).append(SPACE)
                            .append(associationName);

                    return buildWhereClauseForCriterion(association.getAssociatedEntity(), aq.getCriteria(), q,
                            whereClause, associationName, aq.getCriteria().getCriteria(), position, parameters,
                            conversionService);
                }
                return position;
            }
        });
        queryHandlers.put(Negation.class, new QueryHandler() {
            @Override
            public int handle(PersistentEntity entity, Criterion criterion, StringBuilder q,
                    StringBuilder whereClause, String logicalName, int position, List parameters,
                    ConversionService conversionService) {
                whereClause.append(NOT_CLAUSE).append(OPEN_BRACKET);

                final Negation negation = (Negation) criterion;
                position = buildWhereClauseForCriterion(entity, negation, q, whereClause, logicalName,
                        negation.getCriteria(), position, parameters, conversionService);
                whereClause.append(CLOSE_BRACKET);

                return position;
            }
        });
        queryHandlers.put(Conjunction.class, new QueryHandler() {
            @Override
            public int handle(PersistentEntity entity, Criterion criterion, StringBuilder q,
                    StringBuilder whereClause, String logicalName, int position, List parameters,
                    ConversionService conversionService) {
                whereClause.append(OPEN_BRACKET);

                final Conjunction conjunction = (Conjunction) criterion;
                position = buildWhereClauseForCriterion(entity, conjunction, q, whereClause, logicalName,
                        conjunction.getCriteria(), position, parameters, conversionService);
                whereClause.append(CLOSE_BRACKET);

                return position;
            }
        });
        queryHandlers.put(Disjunction.class, new QueryHandler() {
            @Override
            public int handle(PersistentEntity entity, Criterion criterion, StringBuilder q,
                    StringBuilder whereClause, String logicalName, int position, List parameters,
                    ConversionService conversionService) {
                whereClause.append(OPEN_BRACKET);

                final Disjunction disjunction = (Disjunction) criterion;
                position = buildWhereClauseForCriterion(entity, disjunction, q, whereClause, logicalName,
                        disjunction.getCriteria(), position, parameters, conversionService);
                whereClause.append(CLOSE_BRACKET);

                return position;
            }
        });
        queryHandlers.put(Equals.class, new QueryHandler() {
            public int handle(PersistentEntity entity, Criterion criterion, StringBuilder q,
                    StringBuilder whereClause, String logicalName, int position, List parameters,
                    ConversionService conversionService) {
                Equals eq = (Equals) criterion;
                final String name = eq.getProperty();
                PersistentProperty prop = validateProperty(entity, name, Equals.class);
                Class propType = prop.getType();
                position = appendCriteriaForOperator(whereClause, logicalName, name, position, "=");
                parameters.add(conversionService.convert(eq.getValue(), propType));
                return position;
            }
        });
        queryHandlers.put(IsNull.class, new QueryHandler() {
            public int handle(PersistentEntity entity, Criterion criterion, StringBuilder q,
                    StringBuilder whereClause, String logicalName, int position, List parameters,
                    ConversionService conversionService) {
                IsNull isNull = (IsNull) criterion;
                final String name = isNull.getProperty();
                validateProperty(entity, name, IsNull.class);
                whereClause.append(logicalName).append(DOT).append(name).append(" IS NULL ");

                return position;
            }
        });
        queryHandlers.put(IsNotNull.class, new QueryHandler() {
            public int handle(PersistentEntity entity, Criterion criterion, StringBuilder q,
                    StringBuilder whereClause, String logicalName, int position, List parameters,
                    ConversionService conversionService) {
                IsNotNull isNotNull = (IsNotNull) criterion;
                final String name = isNotNull.getProperty();
                validateProperty(entity, name, IsNotNull.class);
                whereClause.append(logicalName).append(DOT).append(name).append(" IS NOT NULL ");

                return position;
            }
        });

        queryHandlers.put(IsEmpty.class, new QueryHandler() {
            public int handle(PersistentEntity entity, Criterion criterion, StringBuilder q,
                    StringBuilder whereClause, String logicalName, int position, List parameters,
                    ConversionService conversionService) {
                IsEmpty isEmpty = (IsEmpty) criterion;
                final String name = isEmpty.getProperty();
                validateProperty(entity, name, IsEmpty.class);
                whereClause.append(logicalName).append(DOT).append(name).append(" IS EMPTY ");

                return position;
            }
        });
        queryHandlers.put(IsNotEmpty.class, new QueryHandler() {
            public int handle(PersistentEntity entity, Criterion criterion, StringBuilder q,
                    StringBuilder whereClause, String logicalName, int position, List parameters,
                    ConversionService conversionService) {
                IsNotEmpty isNotEmpty = (IsNotEmpty) criterion;
                final String name = isNotEmpty.getProperty();
                validateProperty(entity, name, IsNotEmpty.class);
                whereClause.append(logicalName).append(DOT).append(name).append(" IS NOT EMPTY ");

                return position;
            }
        });
        queryHandlers.put(IsNotNull.class, new QueryHandler() {
            public int handle(PersistentEntity entity, Criterion criterion, StringBuilder q,
                    StringBuilder whereClause, String logicalName, int position, List parameters,
                    ConversionService conversionService) {
                IsNotNull isNotNull = (IsNotNull) criterion;
                final String name = isNotNull.getProperty();
                validateProperty(entity, name, IsNotNull.class);
                whereClause.append(logicalName).append(DOT).append(name).append(" IS NOT NULL ");

                return position;
            }
        });
        queryHandlers.put(IdEquals.class, new QueryHandler() {
            public int handle(PersistentEntity entity, Criterion criterion, StringBuilder q,
                    StringBuilder whereClause, String logicalName, int position, List parameters,
                    ConversionService conversionService) {
                IdEquals eq = (IdEquals) criterion;
                PersistentProperty prop = entity.getIdentity();
                Class propType = prop.getType();
                position = appendCriteriaForOperator(whereClause, logicalName, prop.getName(), position, "=");
                parameters.add(conversionService.convert(eq.getValue(), propType));
                return position;
            }
        });
        queryHandlers.put(NotEquals.class, new QueryHandler() {
            public int handle(PersistentEntity entity, Criterion criterion, StringBuilder q,
                    StringBuilder whereClause, String logicalName, int position, List parameters,
                    ConversionService conversionService) {
                NotEquals eq = (NotEquals) criterion;
                final String name = eq.getProperty();
                PersistentProperty prop = validateProperty(entity, name, Equals.class);
                Class propType = prop.getType();
                position = appendCriteriaForOperator(whereClause, logicalName, name, position, " != ");
                parameters.add(conversionService.convert(eq.getValue(), propType));
                return position;
            }
        });
        queryHandlers.put(GreaterThan.class, new QueryHandler() {
            public int handle(PersistentEntity entity, Criterion criterion, StringBuilder q,
                    StringBuilder whereClause, String logicalName, int position, List parameters,
                    ConversionService conversionService) {
                GreaterThan eq = (GreaterThan) criterion;
                final String name = eq.getProperty();
                PersistentProperty prop = validateProperty(entity, name, GreaterThan.class);
                Class propType = prop.getType();
                position = appendCriteriaForOperator(whereClause, logicalName, name, position, " > ");
                parameters.add(conversionService.convert(eq.getValue(), propType));
                return position;
            }
        });
        queryHandlers.put(LessThanEquals.class, new QueryHandler() {
            public int handle(PersistentEntity entity, Criterion criterion, StringBuilder q,
                    StringBuilder whereClause, String logicalName, int position, List parameters,
                    ConversionService conversionService) {
                LessThanEquals eq = (LessThanEquals) criterion;
                final String name = eq.getProperty();
                PersistentProperty prop = validateProperty(entity, name, LessThanEquals.class);
                Class propType = prop.getType();
                position = appendCriteriaForOperator(whereClause, logicalName, name, position, " <= ");
                parameters.add(conversionService.convert(eq.getValue(), propType));
                return position;
            }
        });
        queryHandlers.put(GreaterThanEquals.class, new QueryHandler() {
            public int handle(PersistentEntity entity, Criterion criterion, StringBuilder q,
                    StringBuilder whereClause, String logicalName, int position, List parameters,
                    ConversionService conversionService) {
                GreaterThanEquals eq = (GreaterThanEquals) criterion;
                final String name = eq.getProperty();
                PersistentProperty prop = validateProperty(entity, name, GreaterThanEquals.class);
                Class propType = prop.getType();
                position = appendCriteriaForOperator(whereClause, logicalName, name, position, " >= ");
                parameters.add(conversionService.convert(eq.getValue(), propType));
                return position;
            }
        });
        queryHandlers.put(Between.class, new QueryHandler() {
            public int handle(PersistentEntity entity, Criterion criterion, StringBuilder q,
                    StringBuilder whereClause, String logicalName, int position, List parameters,
                    ConversionService conversionService) {
                Between between = (Between) criterion;
                final Object from = between.getFrom();
                final Object to = between.getTo();

                final String name = between.getProperty();
                PersistentProperty prop = validateProperty(entity, name, Between.class);
                Class propType = prop.getType();
                final String qualifiedName = logicalName + DOT + name;
                whereClause.append(OPEN_BRACKET).append(qualifiedName).append(" >= ").append(QUESTIONMARK)
                        .append(++position).append(" AND ").append(qualifiedName).append(" <= ")
                        .append(QUESTIONMARK).append(++position).append(CLOSE_BRACKET);

                parameters.add(conversionService.convert(from, propType));
                parameters.add(conversionService.convert(to, propType));
                return position;
            }
        });
        queryHandlers.put(LessThan.class, new QueryHandler() {
            public int handle(PersistentEntity entity, Criterion criterion, StringBuilder q,
                    StringBuilder whereClause, String logicalName, int position, List parameters,
                    ConversionService conversionService) {
                LessThan eq = (LessThan) criterion;
                final String name = eq.getProperty();
                PersistentProperty prop = validateProperty(entity, name, LessThan.class);
                Class propType = prop.getType();
                position = appendCriteriaForOperator(whereClause, logicalName, name, position, " < ");
                parameters.add(conversionService.convert(eq.getValue(), propType));
                return position;
            }
        });

        queryHandlers.put(Like.class, new QueryHandler() {
            public int handle(PersistentEntity entity, Criterion criterion, StringBuilder q,
                    StringBuilder whereClause, String logicalName, int position, List parameters,
                    ConversionService conversionService) {
                Like eq = (Like) criterion;
                final String name = eq.getProperty();
                PersistentProperty prop = validateProperty(entity, name, Like.class);
                Class propType = prop.getType();
                position = appendCriteriaForOperator(whereClause, logicalName, name, position, " like ");
                parameters.add(conversionService.convert(eq.getValue(), propType));
                return position;

            }
        });
        queryHandlers.put(In.class, new QueryHandler() {
            public int handle(PersistentEntity entity, Criterion criterion, StringBuilder q,
                    StringBuilder whereClause, String logicalName, int position, List parameters,
                    ConversionService conversionService) {
                In eq = (In) criterion;
                final String name = eq.getProperty();
                PersistentProperty prop = validateProperty(entity, name, In.class);
                Class propType = prop.getType();
                whereClause.append(logicalName).append(DOT).append(name).append(" IN (");
                for (Iterator i = eq.getValues().iterator(); i.hasNext();) {
                    Object val = i.next();
                    whereClause.append(QUESTIONMARK).append(++position);
                    if (i.hasNext())
                        whereClause.append(COMMA);
                    parameters.add(conversionService.convert(val, propType));

                }
                whereClause.append(CLOSE_BRACKET);

                return position;
            }
        });
    }

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

    public JpaQuery(JpaSession session, PersistentEntity entity) {
        super(session, entity);

        if (session == null) {
            throw new InvalidDataAccessApiUsageException("Argument session cannot be null");
        }
        if (entity == null) {
            throw new InvalidDataAccessApiUsageException("No persistent entity specified");
        }

    }

    @Override
    public JpaSession getSession() {
        return (JpaSession) super.getSession();
    }

    @Override
    protected List executeQuery(final PersistentEntity entity, final Junction criteria) {
        final JpaTemplate jpaTemplate = getSession().getJpaTemplate();

        return (List) jpaTemplate.execute(new JpaCallback<Object>() {

            @Override
            public Object doInJpa(EntityManager em) throws PersistenceException {

                return executeQuery(entity, criteria, em, false);
            }
        });
    }

    @Override
    public Object singleResult() {
        final JpaTemplate jpaTemplate = getSession().getJpaTemplate();
        try {
            return jpaTemplate.execute(new JpaCallback<Object>() {

                @Override
                public Object doInJpa(EntityManager em) throws PersistenceException {

                    return executeQuery(entity, criteria, em, true);
                }
            });
        } catch (EmptyResultDataAccessException e) {
            return null;
        }

    }

    protected void appendOrder(StringBuilder queryString, String logicalName) {
        if (!orderBy.isEmpty()) {
            queryString.append(ORDER_BY_CLAUSE);
            for (Order order : orderBy) {
                queryString.append(logicalName).append(DOT).append(order.getProperty()).append(SPACE)
                        .append(order.getDirection().toString()).append(SPACE);
            }
        }
    }

    private static interface QueryHandler {
        public int handle(PersistentEntity entity, Criterion criterion, StringBuilder q, StringBuilder whereClause,
                String logicalName, int position, List parameters, ConversionService conversionService);
    }

    private List buildWhereClause(PersistentEntity entity, Junction criteria, StringBuilder q,
            StringBuilder whereClause, String logicalName) {
        final List<Criterion> criterionList = criteria.getCriteria();
        whereClause.append(WHERE_CLAUSE);
        if (criteria instanceof Negation) {
            whereClause.append(NOT_CLAUSE);
        }
        whereClause.append(OPEN_BRACKET);
        int position = 0;
        List parameters = new ArrayList();
        position = buildWhereClauseForCriterion(entity, criteria, q, whereClause, logicalName, criterionList,
                position, parameters, getSession().getMappingContext().getConversionService());
        q.append(whereClause.toString());
        q.append(CLOSE_BRACKET);
        return parameters;
    }

    Object executeQuery(final PersistentEntity entity, final Junction criteria, EntityManager em,
            boolean singleResult) {
        final String logicalName = entity.getDecapitalizedName();
        StringBuilder queryString = new StringBuilder(SELECT_CLAUSE);

        if (projections.isEmpty()) {
            queryString.append(DISTINCT_CLAUSE).append(logicalName);
        } else {
            for (Iterator i = projections.getProjectionList().iterator(); i.hasNext();) {
                Projection projection = (Projection) i.next();
                if (projection instanceof CountProjection) {
                    queryString.append("COUNT(").append(logicalName).append(CLOSE_BRACKET);
                } else if (projection instanceof IdProjection) {
                    queryString.append(logicalName).append(DOT).append(entity.getIdentity().getName());
                } else if (projection instanceof PropertyProjection) {
                    PropertyProjection pp = (PropertyProjection) projection;
                    if (projection instanceof AvgProjection) {
                        queryString.append("AVG(").append(logicalName).append(DOT).append(pp.getPropertyName())
                                .append(CLOSE_BRACKET);
                    } else if (projection instanceof SumProjection) {
                        queryString.append("SUM(").append(logicalName).append(DOT).append(pp.getPropertyName())
                                .append(CLOSE_BRACKET);
                    } else if (projection instanceof MinProjection) {
                        queryString.append("MIN(").append(logicalName).append(DOT).append(pp.getPropertyName())
                                .append(CLOSE_BRACKET);
                    } else if (projection instanceof MaxProjection) {
                        queryString.append("MAX(").append(logicalName).append(DOT).append(pp.getPropertyName())
                                .append(CLOSE_BRACKET);
                    } else {
                        queryString.append(logicalName).append(DOT).append(pp.getPropertyName());
                    }
                }

                if (i.hasNext()) {
                    queryString.append(COMMA);
                }
            }

        }
        queryString.append(FROM_CLAUSE).append(entity.getName()).append(AS_CLAUSE).append(logicalName);

        List parameters = null;
        StringBuilder whereClause = new StringBuilder();
        if (!criteria.isEmpty()) {
            parameters = buildWhereClause(entity, criteria, queryString, whereClause, logicalName);
        }

        appendOrder(queryString, logicalName);
        final String queryToString = queryString.toString();

        if (LOG.isDebugEnabled()) {
            LOG.debug("Built JPQL to execute: " + queryToString);
        }
        final javax.persistence.Query q = em.createQuery(queryToString);

        if (parameters != null) {
            for (int i = 0; i < parameters.size(); i++) {
                final Object value = parameters.get(i);
                q.setParameter(i + 1, value);
            }
        }
        q.setFirstResult(offset);
        if (max > -1)
            q.setMaxResults(max);

        if (!singleResult)
            return q.getResultList();
        else
            return q.getSingleResult();
    }

    static int buildWhereClauseForCriterion(PersistentEntity entity, Junction criteria, StringBuilder q,
            StringBuilder whereClause, String logicalName, final List<Criterion> criterionList, int position,
            List parameters, ConversionService conversionService) {
        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) {
                position = qh.handle(entity, criterion, q, whereClause, logicalName, position, parameters,
                        conversionService);
            }

            if (iterator.hasNext())
                whereClause.append(operator);

        }
        return position;
    }
}