org.jasig.ssp.util.hibernate.MultipleCountProjection.java Source code

Java tutorial

Introduction

Here is the source code for org.jasig.ssp.util.hibernate.MultipleCountProjection.java

Source

/**
 * Licensed to Apereo under one or more contributor license
 * agreements. See the NOTICE file distributed with this work
 * for additional information regarding copyright ownership.
 * Apereo 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 the following location:
 *
 *   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.jasig.ssp.util.hibernate;

import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.criterion.AggregateProjection;
import org.hibernate.criterion.CriteriaQuery;
import org.hibernate.type.LongType;
import org.hibernate.type.StringType;
import org.hibernate.type.Type;

import com.google.common.collect.Lists;

/**
 * Hibernate {@link org.hibernate.criterion.Projection} for counting combinations
 * of possibly distinct column values. This is a workaround for
 * {@link org.hibernate.criterion.Projections#countDistinct(String)}
 * only accepting a single column reference. The original version of this
 * class was found <a href="https://forum.hibernate.org/viewtopic.php?p=2321638#p2321638">here</a>.
 * Customized here to delegate to the current
 * {@link org.hibernate.dialect.Dialect} to use string coercion and
 * concatenation functions appropriate for the current db.
 *
 * <p>The original use case for this was needing to select a count (for
 * pagination) from a subquery where the subquery had an aggregate function
 * over multiple group by columns. I.e.
 * <code>select count(*) from ( select count(foo) from bar group by baz, baz2 )</code>.
 * <a href="http://stackoverflow.com/questions/7406954/count-of-the-result-of-a-hibernate-criteria-group-by-total-grouped-records-ret">This post</a>
 * pointed out this this is conceptually identical to what this class allows
 * you to do, e.g. for the above:</p>
 *
 * <p>
 * <code>query.setProjection(new MultipleCountProjection("baz;baz2").setDistinct()).uniqueResult();</code>
 * </p>
 */
public class MultipleCountProjection extends AggregateProjection {

    private boolean distinct;

    public MultipleCountProjection(String prop) {
        super("count", prop);
    }

    public String toString() {
        if (distinct) {
            return "distinct " + super.toString();
        } else {
            return super.toString();
        }
    }

    public Type[] getTypes(Criteria criteria, CriteriaQuery criteriaQuery) throws HibernateException {
        return new Type[] { LongType.INSTANCE };
    }

    public String toSqlString(Criteria criteria, int position, CriteriaQuery criteriaQuery)
            throws HibernateException {
        StringBuffer buf = new StringBuffer();
        buf.append("count(");
        if (distinct)
            buf.append("distinct ");
        String[] properties = propertyName.split(";");
        //concat(cast(col), cast_col))
        if (properties.length == 1) {
            buf.append(criteriaQuery.getColumn(criteria, properties[0]));
        } else {
            String concatSql = null;
            for (int i = 0; i < properties.length; i++) {
                String asSqlStr = asSqlStr(properties[i], criteria, criteriaQuery);
                if (concatSql == null) {
                    concatSql = asSqlStr;
                    continue;
                }
                concatSql = this.sqlConcatLiterals(concatSql, asSqlStr, criteria, criteriaQuery);
            }
            buf.append(concatSql);
        }
        buf.append(") as y");
        buf.append(position);
        buf.append('_');
        return buf.toString();
    }

    public MultipleCountProjection setDistinct() {
        distinct = true;
        return this;
    }

    /**
     * Coerce a proerty to a string-ish type using the current
     * <code>Dialect</code>'s "str" function.
     *
     * @param propertyName
     * @param criteria
     * @param criteriaQuery
     * @return
     */
    private String asSqlStr(String propertyName, Criteria criteria, CriteriaQuery criteriaQuery) {
        String castCall = super.getFunction("cast", criteriaQuery).render(
                criteriaQuery.getType(criteria, propertyName),
                Lists.newArrayList(
                        (String) buildFunctionParameterList(criteriaQuery.getColumn(criteria, propertyName)).get(0),
                        "string"),
                criteriaQuery.getFactory());
        return super.getFunction("coalesce", criteriaQuery).render(StringType.INSTANCE,
                Lists.newArrayList(castCall, "''"), criteriaQuery.getFactory());
    }

    /**
     * Concats literals as opposed to field references. Useful for concating
     * the SQL output by
     * {@link #asSqlStr(String, org.hibernate.Criteria, org.hibernate.criterion.CriteriaQuery)}
     *
     * @param str1
     * @param str2
     * @param criteria
     * @param criteriaQuery
     * @return
     */
    private String sqlConcatLiterals(String str1, String str2, Criteria criteria, CriteriaQuery criteriaQuery) {
        return super.getFunction("concat", criteriaQuery).render(StringType.INSTANCE,
                Lists.newArrayList(str1, str2), criteriaQuery.getFactory());
    }
}