text:
package grails.orm;

import grails.gorm.DetachedCriteria;
import groovy.lang.Closure;
import groovy.lang.GroovyObjectSupport;
import groovy.lang.GroovySystem;
import groovy.lang.MetaClass;
import groovy.lang.MetaMethod;
import groovy.lang.MissingMethodException;

import java.beans.PropertyDescriptor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.codehaus.groovy.grails.commons.GrailsApplication;
import org.codehaus.groovy.grails.commons.GrailsClassUtils;
import org.codehaus.groovy.grails.orm.hibernate.GrailsHibernateTemplate;
import org.codehaus.groovy.grails.orm.hibernate.cfg.GrailsHibernateUtil;
import org.codehaus.groovy.grails.orm.hibernate.query.HibernateCriterionAdapter;
import org.codehaus.groovy.grails.orm.hibernate.query.HibernateProjectionAdapter;
import org.grails.datastore.mapping.query.Query;
import org.grails.datastore.mapping.query.api.QueryableCriteria;
import org.hibernate.Criteria;
import org.hibernate.EntityMode;
import org.hibernate.FetchMode;
import org.hibernate.LockMode;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.TypeHelper;
import org.hibernate.criterion.AggregateProjection;
import org.hibernate.criterion.CountProjection;
import org.hibernate.criterion.CriteriaSpecification;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.IdentifierProjection;
import org.hibernate.criterion.Junction;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projection;
import org.hibernate.criterion.ProjectionList;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Property;
import org.hibernate.criterion.PropertyProjection;
import org.hibernate.criterion.Restrictions;
import org.hibernate.criterion.SimpleExpression;
import org.hibernate.engine.SessionFactoryImplementor;
import org.hibernate.metadata.ClassMetadata;
import org.hibernate.transform.ResultTransformer;
import org.hibernate.type.AssociationType;
import org.hibernate.type.EmbeddedComponentType;
import org.hibernate.type.Type;
import org.springframework.beans.BeanUtils;
import org.springframework.orm.hibernate3.SessionHolder;
import org.springframework.transaction.support.TransactionSynchronizationManager;

/**
 *
Wraps the Hibernate Criteria API in a builder. The builder can be retrieved through the "createCriteria()" dynamic static
 * method of Grails domain classes (Example in Groovy):
 *
 *

 *         def c = Account.createCriteria()
 *         def results = c {
 *             projections {
 *                 groupProperty("branch")
 *             }
 *             like("holderFirstName", "Fred%")
 *             and {
 *                 between("balance", 500, 1000)
 *                 eq("branch", "London")
 *             }
 *             maxResults(10)
 *             order("holderLastName", "desc")
 *         }
 *

 *
 *
The builder can also be instantiated standalone with a SessionFactory and persistent Class instance:
 *
 *

 *      new HibernateCriteriaBuilder(clazz, sessionFactory).list {
 *         eq("firstName", "Fred")
 *      }
 *

 *
 * @author Graeme Rocher
 */
public class HibernateCriteriaBuilder extends GroovyObjectSupport implements org.grails.datastore.mapping.query.api.Criteria, org.grails.datastore.mapping.query.api.ProjectionList {

    public static final String AND = "and"; // builder
    public static final String IS_NULL = "isNull"; // builder
    public static final String IS_NOT_NULL = "isNotNull"; // builder
    public static final String NOT = "not";// builder
    public static final String OR = "or"; // builder
    public static final String ID_EQUALS = "idEq"; // builder
    public static final String IS_EMPTY = "isEmpty"; //builder
    public static final String IS_NOT_EMPTY = "isNotEmpty"; //builder
    public static final String RLIKE = "rlike";//method
    public static final String BETWEEN = "between";//method
    public static final String EQUALS = "eq";//method
    public static final String EQUALS_PROPERTY = "eqProperty";//method
    public static final String GREATER_THAN = "gt";//method
    public static final String GREATER_THAN_PROPERTY = "gtProperty";//method
    public static final String GREATER_THAN_OR_EQUAL = "ge";//method
    public static final String GREATER_THAN_OR_EQUAL_PROPERTY = "geProperty";//method
    public static final String ILIKE = "ilike";//method
    public static final String IN = "in";//method
    public static final String LESS_THAN = "lt"; //method
    public static final String LESS_THAN_PROPERTY = "ltProperty";//method
    public static final String LESS_THAN_OR_EQUAL = "le";//method
    public static final String LESS_THAN_OR_EQUAL_PROPERTY = "leProperty";//method
    public static final String LIKE = "like";//method
    public static final String NOT_EQUAL = "ne";//method
    public static final String NOT_EQUAL_PROPERTY = "neProperty";//method
    public static final String SIZE_EQUALS = "sizeEq"; //method
    public static final String ORDER_DESCENDING = "desc";
    public static final String ORDER_ASCENDING = "asc";
    private static final String ROOT_DO_CALL = "doCall";
    private static final String ROOT_CALL = "call";
    private static final String LIST_CALL = "list";
    private static final String LIST_DISTINCT_CALL = "listDistinct";
    private static final String COUNT_CALL = "count";
    private static final String GET_CALL = "get";
    private static final String SCROLL_CALL = "scroll";
    private static final String SET_RESULT_TRANSFORMER_CALL = "setResultTransformer";
    private static final String PROJECTIONS = "projections";

    private SessionFactory sessionFactory;
    private Session hibernateSession;
    private Class<?> targetClass;
    private Criteria criteria;
    private MetaClass criteriaMetaClass;

    private boolean uniqueResult = false;
    private List<LogicalExpression> logicalExpressionStack = new ArrayList<LogicalExpression>();
    private List<String> associationStack = new ArrayList<String>();
    private boolean participate;
    private boolean scroll;
    private boolean count;
    private ProjectionList projectionList = Projections.projectionList();
    private List<String> aliasStack = new ArrayList<String>();
    private List<Criteria> aliasInstanceStack = new ArrayList<Criteria>();
    private Map<String, String> aliasMap = new HashMap<String, String>();
    private static final String ALIAS = "_alias";
    private ResultTransformer resultTransformer;
    private int aliasCount;

    private boolean paginationEnabledList = false;
    private List<Order> orderEntries;
    private GrailsApplication grailsApplication;

    @SuppressWarnings("rawtypes")
    public HibernateCriteriaBuilder(Class targetClass, SessionFactory sessionFactory) {
        this.targetClass = targetClass;
        this.sessionFactory = sessionFactory;
    }

    @SuppressWarnings("rawtypes")
    public HibernateCriteriaBuilder(Class targetClass, SessionFactory sessionFactory, boolean uniqueResult) {
        this.targetClass = targetClass;
        this.sessionFactory = sessionFactory;
        this.uniqueResult = uniqueResult;
    }

    public void setGrailsApplication(GrailsApplication grailsApplication) {
        this.grailsApplication = grailsApplication;
    }

    /**
     * Returns the criteria instance
     * @return The criteria instance
     */
    public Criteria getInstance() {
        return criteria;
    }

    /**
     * Set whether a unique result should be returned
     * @param uniqueResult True if a unique result should be returned
     */
    public void setUniqueResult(boolean uniqueResult) {
        this.uniqueResult = uniqueResult;
    }

    /**
     * A projection that selects a property name
     * @param propertyName The name of the property
     */
    public org.grails.datastore.mapping.query.api.ProjectionList property(String propertyName) {
        return property(propertyName, null);
    }

    /**
     * A projection that selects a property name
     * @param propertyName The name of the property
     * @param alias The alias to use
     */
    public org.grails.datastore.mapping.query.api.ProjectionList property(String propertyName, String alias) {
        final PropertyProjection propertyProjection = Projections.property(calculatePropertyName(propertyName));
        addProjectionToList(propertyProjection, alias);
        return this;
    }

    /**
     * Adds a projection to the projectList for the given alias
     *
     * @param propertyProjection The projection
     * @param alias The alias
     */
    protected void addProjectionToList(Projection propertyProjection, String alias) {
        if (alias != null) {
            projectionList.add(propertyProjection,alias);
        }
        else {
            projectionList.add(propertyProjection);
        }
    }

    /**
     * A projection that selects a distince property name
     * @param propertyName The property name
     */
    public org.grails.datastore.mapping.query.api.ProjectionList distinct(String propertyName) {
        distinct(propertyName, null);
        return this;
    }

    /**
     * A projection that selects a distince property name
     * @param propertyName The property name
     * @param alias The alias to use
     */
    public org.grails.datastore.mapping.query.api.ProjectionList distinct(String propertyName, String alias) {
        final Projection proj = Projections.distinct(Projections.property(calculatePropertyName(propertyName)));
        addProjectionToList(proj,alias);
        return this;
    }

    /**
     * A distinct projection that takes a list
     *
     * @param propertyNames The list of distince property names
     */
    @SuppressWarnings("rawtypes")
    public org.grails.datastore.mapping.query.api.ProjectionList distinct(Collection propertyNames) {
        return distinct(propertyNames, null);
    }

    /**
     * A distinct projection that takes a list
     *
     * @param propertyNames The list of distince property names
     * @param alias The alias to use
     */
    @SuppressWarnings("rawtypes")
    public org.grails.datastore.mapping.query.api.ProjectionList distinct(Collection propertyNames, String alias) {
        ProjectionList list = Projections.projectionList();
        for (Object o : propertyNames) {
            list.add(Projections.property(calculatePropertyName(o.toString())));
        }
        final Projection proj = Projections.distinct(list);
        addProjectionToList(proj, alias);
        return this;
    }

    /**
     * Adds a projection that allows the criteria to return the property average value
     *
     * @param propertyName The name of the property
     */
    public org.grails.datastore.mapping.query.api.ProjectionList avg(String propertyName) {
        return avg(propertyName, null);
    }

    /**
     * Adds a projection that allows the criteria to return the property average value
     *
     * @param propertyName The name of the property
     * @param alias The alias to use
     */
    public org.grails.datastore.mapping.query.api.ProjectionList avg(String propertyName, String alias) {
        final AggregateProjection aggregateProjection = Projections.avg(calculatePropertyName(propertyName));
        addProjectionToList(aggregateProjection, alias);
        return this;
    }

    /**
     * Use a join query
     *
     * @param associationPath The path of the association
     */
    public void join(String associationPath) {
        criteria.setFetchMode(calculatePropertyName(associationPath), FetchMode.JOIN);
    }

    /**
     * Whether a pessimistic lock should be obtained.
     *
     * @param shouldLock True if it should
     */
    public void lock(boolean shouldLock) {
        String lastAlias = getLastAlias();

        if (shouldLock) {
            if (lastAlias != null) {
                criteria.setLockMode(lastAlias, LockMode.PESSIMISTIC_WRITE);
            }
            else {
                criteria.setLockMode(LockMode.PESSIMISTIC_WRITE);
            }
        }
        else {
            if (lastAlias != null) {
                criteria.setLockMode(lastAlias, LockMode.NONE);
            }
            else {
                criteria.setLockMode(LockMode.NONE);
            }
        }
    }

    /**
     * Use a select query
     *
     * @param associationPath The path of the association
     */
    public void select(String associationPath) {
        criteria.setFetchMode(calculatePropertyName(associationPath), FetchMode.SELECT);
    }

    /**
     * Whether to use the query cache
     * @param shouldCache True if the query should be cached
     */
    public void cache(boolean shouldCache) {
        criteria.setCacheable(shouldCache);
    }

    /**
     * Calculates the property name including any alias paths
     *
     * @param propertyName The property name
     * @return The calculated property name
     */
    private String calculatePropertyName(String propertyName) {
        String lastAlias = getLastAlias();
        if (lastAlias != null) {
            return lastAlias +'.'+propertyName;
        }

        return propertyName;
    }

    private String getLastAlias() {
        if (aliasStack.size() > 0) {
            return aliasStack.get(aliasStack.size() - 1).toString();
        }
        return null;
    }

    public Class<?> getTargetClass() {
        return targetClass;
    }

    /**
     * Calculates the property value, converting GStrings if necessary
     *
     * @param propertyValue The property value
     * @return The calculated property value
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    private Object calculatePropertyValue(Object propertyValue) {
        if (propertyValue instanceof CharSequence) {
            return propertyValue.toString();
        }
        if (propertyValue instanceof QueryableCriteria) {
            propertyValue = getHibernateDetachedCriteria((QueryableCriteria<?>)propertyValue);
        }
        else if (propertyValue instanceof Closure) {
            propertyValue = getHibernateDetachedCriteria(
                  new DetachedCriteria(targetClass).build((Closure<?>)propertyValue));
        }
        return propertyValue;
    }

    public static org.hibernate.criterion.DetachedCriteria getHibernateDetachedCriteria(QueryableCriteria<?> queryableCriteria) {
        org.hibernate.criterion.DetachedCriteria detachedCriteria = org.hibernate.criterion.DetachedCriteria.forClass(
             queryableCriteria.getPersistentEntity().getJavaClass());
        populateHibernateDetachedCriteria(detachedCriteria, queryableCriteria);
        return detachedCriteria;
    }

    private static void populateHibernateDetachedCriteria(org.hibernate.criterion.DetachedCriteria detachedCriteria, QueryableCriteria<?> queryableCriteria) {
        List<Query.Criterion> criteriaList = queryableCriteria.getCriteria();
        for (Query.Criterion criterion : criteriaList) {
            Criterion hibernateCriterion = new HibernateCriterionAdapter(criterion).toHibernateCriterion(null);
            if(hibernateCriterion != null) {
                detachedCriteria.add(hibernateCriterion);
            }
        }

        List<Query.Projection> projections = queryableCriteria.getProjections();
        ProjectionList projectionList = Projections.projectionList();
        for (Query.Projection projection : projections) {
            Projection hibernateProjection = new HibernateProjectionAdapter(projection).toHibernateProjection();
            if(hibernateProjection != null) {
                 projectionList.add(hibernateProjection);
            }
        }
        detachedCriteria.setProjection(projectionList);
    }

    /**
     * Adds a projection that allows the criteria to return the property count
     *
     * @param propertyName The name of the property
     */
    public void count(String propertyName) {
        count(propertyName, null);
    }

    /**
     * Adds a projection that allows the criteria to return the property count
     *
     * @param propertyName The name of the property
     * @param alias The alias to use
     */
    public void count(String propertyName, String alias) {
        final CountProjection proj = Projections.count(calculatePropertyName(propertyName));
        addProjectionToList(proj, alias);
    }

    public org.grails.datastore.mapping.query.api.ProjectionList id() {
        final IdentifierProjection proj = Projections.id();
        addProjectionToList(proj, null);
        return this;
    }

    public org.grails.datastore.mapping.query.api.ProjectionList count() {
        return rowCount();
    }

    /**
     * Adds a projection that allows the criteria to return the distinct property count
     *
     * @param propertyName The name of the property
     */
    public org.grails.datastore.mapping.query.api.ProjectionList countDistinct(String propertyName) {
        return countDistinct(propertyName, null);
    }

    public org.grails.datastore.mapping.query.api.ProjectionList distinct() {
        return this;
    }

    /**
     * Adds a projection that allows the criteria to return the distinct property count
     *
     * @param propertyName The name of the property
     * @param alias The alias to use
     */
    public org.grails.datastore.mapping.query.api.ProjectionList countDistinct(String propertyName, String alias) {
        final CountProjection proj = Projections.countDistinct(calculatePropertyName(propertyName));
        addProjectionToList(proj, alias);
        return this;
    }

    /**
     * Adds a projection that allows the criteria's result to be grouped by a property
     *
     * @param propertyName The name of the property
     */
    public void groupProperty(String propertyName) {
        groupProperty(propertyName, null);
    }

    /**
     * Adds a projection that allows the criteria's result to be grouped by a property
     *
     * @param propertyName The name of the property
     * @param alias The alias to use
     */
    public void groupProperty(String propertyName, String alias) {
        final PropertyProjection proj = Projections.groupProperty(calculatePropertyName(propertyName));
        addProjectionToList(proj, alias);
    }

    /**
     * Adds a projection that allows the criteria to retrieve a  maximum property value
     *
     * @param propertyName The name of the property
     */
    public org.grails.datastore.mapping.query.api.ProjectionList max(String propertyName) {
        return max(propertyName, null);
    }

    /**
     * Adds a projection that allows the criteria to retrieve a  maximum property value
     *
     * @param propertyName The name of the property
     * @param alias The alias to use
     */
    public org.grails.datastore.mapping.query.api.ProjectionList max(String propertyName, String alias) {
        final AggregateProjection proj = Projections.max(calculatePropertyName(propertyName));
        addProjectionToList(proj, alias);
        return this;
    }

    /**
     * Adds a projection that allows the criteria to retrieve a  minimum property value
     *
     * @param propertyName The name of the property
     */
    public org.grails.datastore.mapping.query.api.ProjectionList min(String propertyName) {
        return min(propertyName, null);
    }

    /**
     * Adds a projection that allows the criteria to retrieve a  minimum property value
     *
     * @param alias The alias to use
     */
    public org.grails.datastore.mapping.query.api.ProjectionList min(String propertyName, String alias) {
        final AggregateProjection aggregateProjection = Projections.min(calculatePropertyName(propertyName));
        addProjectionToList(aggregateProjection, alias);
        return this;
    }

    /**
     * Adds a projection that allows the criteria to return the row count
     *
     */
    public org.grails.datastore.mapping.query.api.ProjectionList rowCount() {
        return rowCount(null);
    }

    /**
     * Adds a projection that allows the criteria to return the row count
     *
     * @param alias The alias to use
     */
    public org.grails.datastore.mapping.query.api.ProjectionList rowCount(String alias) {
        final Projection proj = Projections.rowCount();
        addProjectionToList(proj, alias);
        return this;
    }

    /**
     * Adds a projection that allows the criteria to retrieve the sum of the results of a property
     *
     * @param propertyName The name of the property
     */
    public org.grails.datastore.mapping.query.api.ProjectionList sum(String propertyName) {
        return sum(propertyName, null);
    }

    /**
     * Adds a projection that allows the criteria to retrieve the sum of the results of a property
     *
     * @param propertyName The name of the property
     * @param alias The alias to use
     */
    public org.grails.datastore.mapping.query.api.ProjectionList sum(String propertyName, String alias) {
        final AggregateProjection proj = Projections.sum(calculatePropertyName(propertyName));
        addProjectionToList(proj, alias);
        return this;
    }

    /**
     * Sets the fetch mode of an associated path
     *
     * @param associationPath The name of the associated path
     * @param fetchMode The fetch mode to set
     */
    public void fetchMode(String associationPath, FetchMode fetchMode) {
        if (criteria != null) {
            criteria.setFetchMode(associationPath, fetchMode);
        }
    }

    /**
     * Sets the resultTransformer.
     * @param transformer The result transformer to use.
     */
    public void resultTransformer(ResultTransformer transformer) {
        if (criteria == null) {
            throwRuntimeException(new IllegalArgumentException("Call to [resultTransformer] not supported here"));
        }
        resultTransformer = transformer;
    }

    /**
     * Creates a Criterion that compares to class properties for equality
     * @param propertyName The first property name
     * @param otherPropertyName The second property name
     * @return A Criterion instance
     */
    public org.grails.datastore.mapping.query.api.Criteria eqProperty(String propertyName, String otherPropertyName) {
        if (!validateSimpleExpression()) {
            throwRuntimeException(new IllegalArgumentException("Call to [eqProperty] with propertyName [" +
                    propertyName + "] and other property name [" + otherPropertyName + "] not allowed here."));
        }

        propertyName = calculatePropertyName(propertyName);
        otherPropertyName = calculatePropertyName(otherPropertyName);
        addToCriteria(Restrictions.eqProperty(propertyName, otherPropertyName));
        return this;
    }

    /**
     * Creates a Criterion that compares to class properties for !equality
     * @param propertyName The first property name
     * @param otherPropertyName The second property name
     * @return A Criterion instance
     */
    public org.grails.datastore.mapping.query.api.Criteria neProperty(String propertyName, String otherPropertyName) {
        if (!validateSimpleExpression()) {
            throwRuntimeException(new IllegalArgumentException("Call to [neProperty] with propertyName [" +
                    propertyName + "] and other property name [" + otherPropertyName + "] not allowed here."));
        }

        propertyName = calculatePropertyName(propertyName);
        otherPropertyName = calculatePropertyName(otherPropertyName);
        addToCriteria(Restrictions.neProperty(propertyName, otherPropertyName));
        return this;
    }

    /**
     * Creates a Criterion that tests if the first property is greater than the second property
     * @param propertyName The first property name
     * @param otherPropertyName The second property name
     * @return A Criterion instance
     */
    public org.grails.datastore.mapping.query.api.Criteria gtProperty(String propertyName, String otherPropertyName) {
        if (!validateSimpleExpression()) {
            throwRuntimeException(new IllegalArgumentException("Call to [gtProperty] with propertyName [" +
                    propertyName + "] and other property name [" + otherPropertyName + "] not allowed here."));
        }

        propertyName = calculatePropertyName(propertyName);
        otherPropertyName = calculatePropertyName(otherPropertyName);
        addToCriteria(Restrictions.gtProperty(propertyName, otherPropertyName));
        return this;
    }

    /**
     * Creates a Criterion that tests if the first property is greater than or equal to the second property
     * @param propertyName The first property name
     * @param otherPropertyName The second property name
     * @return A Criterion instance
     */
    public org.grails.datastore.mapping.query.api.Criteria geProperty(String propertyName, String otherPropertyName) {
        if (!validateSimpleExpression()) {
            throwRuntimeException(new IllegalArgumentException("Call to [geProperty] with propertyName [" +
                    propertyName + "] and other property name [" + otherPropertyName + "] not allowed here."));
        }

        propertyName = calculatePropertyName(propertyName);
        otherPropertyName = calculatePropertyName(otherPropertyName);
        addToCriteria(Restrictions.geProperty(propertyName, otherPropertyName));
        return this;
    }

    /**
     * Creates a Criterion that tests if the first property is less than the second property
     * @param propertyName The first property name
     * @param otherPropertyName The second property name
     * @return A Criterion instance
     */
    public org.grails.datastore.mapping.query.api.Criteria ltProperty(String propertyName, String otherPropertyName) {
        if (!validateSimpleExpression()) {
            throwRuntimeException(new IllegalArgumentException("Call to [ltProperty] with propertyName [" +
                    propertyName + "] and other property name [" + otherPropertyName + "] not allowed here."));
        }

        propertyName = calculatePropertyName(propertyName);
        otherPropertyName = calculatePropertyName(otherPropertyName);
        addToCriteria(Restrictions.ltProperty(propertyName, otherPropertyName));
        return this;
    }

    /**
     * Creates a Criterion that tests if the first property is less than or equal to the second property
     * @param propertyName The first property name
     * @param otherPropertyName The second property name
     * @return A Criterion instance
     */
    public org.grails.datastore.mapping.query.api.Criteria leProperty(String propertyName, String otherPropertyName) {
        if (!validateSimpleExpression()) {
            throwRuntimeException(new IllegalArgumentException("Call to [leProperty] with propertyName [" +
                    propertyName + "] and other property name [" + otherPropertyName + "] not allowed here."));
        }

        propertyName = calculatePropertyName(propertyName);
        otherPropertyName = calculatePropertyName(otherPropertyName);
        addToCriteria(Restrictions.leProperty(propertyName, otherPropertyName));
        return this;
    }

    /**
     * Creates a subquery criterion that ensures the given property is equal to all the given returned values
     *
     * @param propertyName  The property name
     * @param propertyValue The property value
     * @return A Criterion instance
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public org.grails.datastore.mapping.query.api.Criteria eqAll(String propertyName, Closure<?> propertyValue) {
        return eqAll(propertyName, new DetachedCriteria(targetClass).build(propertyValue));
    }

    /**
     * Creates a subquery criterion that ensures the given property is greater than all the given returned values
     *
     * @param propertyName  The property name
     * @param propertyValue The property value
     * @return A Criterion instance
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public org.grails.datastore.mapping.query.api.Criteria gtAll(String propertyName, Closure<?> propertyValue) {
        return gtAll(propertyName, new DetachedCriteria(targetClass).build(propertyValue));
    }

    /**
     * Creates a subquery criterion that ensures the given property is less than all the given returned values
     *
     * @param propertyName  The property name
     * @param propertyValue The property value
     * @return A Criterion instance
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public org.grails.datastore.mapping.query.api.Criteria ltAll(String propertyName, Closure<?> propertyValue) {
        return ltAll(propertyName, new DetachedCriteria(targetClass).build(propertyValue));
    }

    /**
     * Creates a subquery criterion that ensures the given property is greater than all the given returned values
     *
     * @param propertyName  The property name
     * @param propertyValue The property value
     * @return A Criterion instance
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public org.grails.datastore.mapping.query.api.Criteria geAll(String propertyName, Closure<?> propertyValue) {
        return geAll(propertyName, new DetachedCriteria(targetClass).build(propertyValue));
    }

    /**
     * Creates a subquery criterion that ensures the given property is less than all the given returned values
     *
     * @param propertyName  The property name
     * @param propertyValue The property value
     * @return A Criterion instance
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public org.grails.datastore.mapping.query.api.Criteria leAll(String propertyName, Closure<?> propertyValue) {
        return leAll(propertyName, new DetachedCriteria(targetClass).build(propertyValue));
    }

    /**
     * Creates a subquery criterion that ensures the given property is equal to all the given returned values
     *
     * @param propertyName  The property name
     * @param propertyValue The property value
     * @return A Criterion instance
     */
    public org.grails.datastore.mapping.query.api.Criteria eqAll(String propertyName,
             @SuppressWarnings("rawtypes") QueryableCriteria propertyValue) {
        addToCriteria(Property.forName(propertyName).eqAll(getHibernateDetachedCriteria(propertyValue)));
        return this;
    }

    /**
     * Creates a subquery criterion that ensures the given property is greater than all the given returned values
     *
     * @param propertyName  The property name
     * @param propertyValue The property value
     * @return A Criterion instance
     */
    public org.grails.datastore.mapping.query.api.Criteria gtAll(String propertyName,
             @SuppressWarnings("rawtypes") QueryableCriteria propertyValue) {
        addToCriteria(Property.forName(propertyName).gtAll(getHibernateDetachedCriteria(propertyValue)));
        return this;
    }

    /**
     * Creates a subquery criterion that ensures the given property is less than all the given returned values
     *
     * @param propertyName  The property name
     * @param propertyValue The property value
     * @return A Criterion instance
     */
    public org.grails.datastore.mapping.query.api.Criteria ltAll(String propertyName,
             @SuppressWarnings("rawtypes") QueryableCriteria propertyValue) {
        addToCriteria(Property.forName(propertyName).ltAll(getHibernateDetachedCriteria(propertyValue)));
        return this;

    }

    /**
     * Creates a subquery criterion that ensures the given property is greater than all the given returned values
     *
     * @param propertyName  The property name
     * @param propertyValue The property value
     * @return A Criterion instance
     */
    public org.grails.datastore.mapping.query.api.Criteria geAll(String propertyName,
             @SuppressWarnings("rawtypes") QueryableCriteria propertyValue) {
        addToCriteria(Property.forName(propertyName).geAll(getHibernateDetachedCriteria(propertyValue)));
        return this;

    }

    /**
     * Creates a subquery criterion that ensures the given property is less than all the given returned values
     *
     * @param propertyName  The property name
     * @param propertyValue The property value
     * @return A Criterion instance
     */
    public org.grails.datastore.mapping.query.api.Criteria leAll(String propertyName,
             @SuppressWarnings("rawtypes") QueryableCriteria propertyValue) {
        addToCriteria(Property.forName(propertyName).leAll(getHibernateDetachedCriteria(propertyValue)));
        return this;

    }

    /**
     * Creates a "greater than" Criterion based on the specified property name and value
     * @param propertyName The property name
     * @param propertyValue The property value
     * @return A Criterion instance
     */
    public org.grails.datastore.mapping.query.api.Criteria gt(String propertyName, Object propertyValue) {
        if (!validateSimpleExpression()) {
            throwRuntimeException(new IllegalArgumentException("Call to [gt] with propertyName [" +
                    propertyName + "] and value [" + propertyValue + "] not allowed here."));
        }

        propertyName = calculatePropertyName(propertyName);
        propertyValue = calculatePropertyValue(propertyValue);

        Criterion gt;
        if(propertyValue instanceof org.hibernate.criterion.DetachedCriteria) {
            gt = Property.forName(propertyName).gt((org.hibernate.criterion.DetachedCriteria)propertyValue);
        }
        else {

            gt = Restrictions.gt(propertyName, propertyValue);
        }
        addToCriteria(gt);
        return this;
    }

    public org.grails.datastore.mapping.query.api.Criteria lte(String s, Object o) {
        return le(s,o);
    }

    /**
     * Creates a "greater than or equal to" Criterion based on the specified property name and value
     * @param propertyName The property name
     * @param propertyValue The property value
     * @return A Criterion instance
     */
    public org.grails.datastore.mapping.query.api.Criteria ge(String propertyName, Object propertyValue) {
        if (!validateSimpleExpression()) {
            throwRuntimeException(new IllegalArgumentException("Call to [ge] with propertyName [" +
                    propertyName + "] and value [" + propertyValue + "] not allowed here."));
        }
        propertyName = calculatePropertyName(propertyName);
        propertyValue = calculatePropertyValue(propertyValue);

        Criterion ge;
        if(propertyValue instanceof org.hibernate.criterion.DetachedCriteria) {
            ge = Property.forName(propertyName).ge((org.hibernate.criterion.DetachedCriteria) propertyValue);
        }
        else {

            ge = Restrictions.ge(propertyName, propertyValue);
        }
        addToCriteria(ge);
        return this;
    }

    /**
     * Creates a "less than" Criterion based on the specified property name and value
     * @param propertyName The property name
     * @param propertyValue The property value
     * @return A Criterion instance
     */
    public org.grails.datastore.mapping.query.api.Criteria lt(String propertyName, Object propertyValue) {
        if (!validateSimpleExpression()) {
            throwRuntimeException(new IllegalArgumentException("Call to [lt] with propertyName [" +
                    propertyName + "] and value [" + propertyValue + "] not allowed here."));
        }

        propertyName = calculatePropertyName(propertyName);
        propertyValue = calculatePropertyValue(propertyValue);
        Criterion lt;
        if(propertyValue instanceof org.hibernate.criterion.DetachedCriteria) {
            lt = Property.forName(propertyName).lt((org.hibernate.criterion.DetachedCriteria) propertyValue);
        }
        else {

            lt = Restrictions.lt(propertyName, propertyValue);
        }
        addToCriteria(lt);
        return this;
    }

    /**
     * Creates a "less than or equal to" Criterion based on the specified property name and value
     * @param propertyName The property name
     * @param propertyValue The property value
     * @return A Criterion instance
     */
    public org.grails.datastore.mapping.query.api.Criteria le(String propertyName, Object propertyValue) {
        if (!validateSimpleExpression()) {
            throwRuntimeException(new IllegalArgumentException("Call to [le] with propertyName [" +
                    propertyName + "] and value [" + propertyValue + "] not allowed here."));
        }

        propertyName = calculatePropertyName(propertyName);
        propertyValue = calculatePropertyValue(propertyValue);
        Criterion le;
        if(propertyValue instanceof org.hibernate.criterion.DetachedCriteria) {
            le = Property.forName(propertyName).le((org.hibernate.criterion.DetachedCriteria) propertyValue);
        }
        else {

            le = Restrictions.le(propertyName, propertyValue);
        }
        addToCriteria(le);
        return this;
    }

    public org.grails.datastore.mapping.query.api.Criteria idEquals(Object o) {
        return idEq(o);
    }

    public org.grails.datastore.mapping.query.api.Criteria isEmpty(String property) {
        String propertyName = calculatePropertyName(property);
        addToCriteria(Restrictions.isEmpty(propertyName));
        return this;
    }

    public org.grails.datastore.mapping.query.api.Criteria isNotEmpty(String property) {
        String propertyName = calculatePropertyName(property);
        addToCriteria(Restrictions.isNotEmpty(propertyName));
        return this;

    }

    public org.grails.datastore.mapping.query.api.Criteria isNull(String property) {
        String propertyName = calculatePropertyName(property);
        addToCriteria(Restrictions.isNull(propertyName));
        return this;

    }

    public org.grails.datastore.mapping.query.api.Criteria isNotNull(String property) {
        String propertyName = calculatePropertyName(property);
        addToCriteria(Restrictions.isNotNull(propertyName));
        return this;
    }

    /**
     * Creates an "equals" Criterion based on the specified property name and value. Case-sensitive.
     * @param propertyName The property name
     * @param propertyValue The property value
     *
     * @return A Criterion instance
     */
    public org.grails.datastore.mapping.query.api.Criteria eq(String propertyName, Object propertyValue) {
        return eq(propertyName, propertyValue, Collections.emptyMap());
    }

    public org.grails.datastore.mapping.query.api.Criteria idEq(Object o) {
        return eq("id", o);
    }

    /**
     * Groovy moves the map to the first parameter if using the idiomatic form, e.g.
     * eq 'firstName', 'Fred', ignoreCase: true.
     * @param params optional map with customization parameters; currently only 'ignoreCase' is supported.
     * @param propertyName
     * @param propertyValue
     * @return A Criterion instance
     */
    @SuppressWarnings("rawtypes")
    public org.grails.datastore.mapping.query.api.Criteria eq(Map params, String propertyName, Object propertyValue) {
        return eq(propertyName, propertyValue, params);
    }

    /**
     * Creates an "equals" Criterion based on the specified property name and value.
     * Supports case-insensitive search if the params map contains true
     * under the 'ignoreCase' key.
     * @param propertyName The property name
     * @param propertyValue The property value
     * @param params optional map with customization parameters; currently only 'ignoreCase' is supported.
     *
     * @return A Criterion instance
     */
    @SuppressWarnings("rawtypes")
    public org.grails.datastore.mapping.query.api.Criteria eq(String propertyName, Object propertyValue, Map params) {
        if (!validateSimpleExpression()) {
            throwRuntimeException(new IllegalArgumentException("Call to [eq] with propertyName [" +
                    propertyName + "] and value [" + propertyValue + "] not allowed here."));
        }

        propertyName = calculatePropertyName(propertyName);
        propertyValue = calculatePropertyValue(propertyValue);
        Criterion eq;
        if(propertyValue instanceof org.hibernate.criterion.DetachedCriteria) {
            eq = Property.forName(propertyName).eq((org.hibernate.criterion.DetachedCriteria) propertyValue);
        }
        else {

            eq =  Restrictions.eq(propertyName, propertyValue);
        }
        if (params != null && (eq instanceof SimpleExpression)) {
            Object ignoreCase = params.get("ignoreCase");
            if (ignoreCase instanceof Boolean && (Boolean)ignoreCase) {
                eq = ((SimpleExpression)eq).ignoreCase();
            }
        }
        addToCriteria(eq);
        return this;
    }

    /**
     * Applies a sql restriction to the results to allow something like:

       def results = Person.withCriteria {
           sqlRestriction "char_length(first_name) <= 4"
       }

     *
     * @param sqlRestriction the sql restriction
     * @return a Criteria instance
     */
    public org.grails.datastore.mapping.query.api.Criteria sqlRestriction(String sqlRestriction) {
        if (!validateSimpleExpression()) {
            throwRuntimeException(new IllegalArgumentException("Call to [sqlRestriction] with value [" +
                    sqlRestriction + "] not allowed here."));
        }
        return sqlRestriction(sqlRestriction, Collections.EMPTY_LIST);
    }

    /**
     * Applies a sql restriction to the results to allow something like:

       def results = Person.withCriteria {
           sqlRestriction "char_length(first_name) < ? AND char_length(first_name) > ?", [4, 9]
       }

     *
     * @param sqlRestriction the sql restriction
     * @param values jdbc parameters
     * @return a Criteria instance
     */
    public org.grails.datastore.mapping.query.api.Criteria sqlRestriction(String sqlRestriction, List<?> values) {
        if (!validateSimpleExpression()) {
            throwRuntimeException(new IllegalArgumentException("Call to [sqlRestriction] with value [" +
                    sqlRestriction + "] not allowed here."));
        }
        final int numberOfParameters = values.size();

        final Type[] typesArray = new Type[numberOfParameters];
        final Object[] valuesArray = new Object[numberOfParameters];

        if (numberOfParameters > 0) {
            final TypeHelper typeHelper = sessionFactory.getTypeHelper();
            for(int i = 0; i < typesArray.length; i++) {
                final Object value = values.get(i);
                typesArray[i] =  typeHelper.basic(value.getClass());
                valuesArray[i] = value;
            }
        }
        addToCriteria(Restrictions.sqlRestriction(sqlRestriction, valuesArray, typesArray));
        return this;
    }

    /**
     * Creates a Criterion with from the specified property name and "like" expression
     * @param propertyName The property name
     * @param propertyValue The like value
     *
     * @return A Criterion instance
     */
    public org.grails.datastore.mapping.query.api.Criteria like(String propertyName, Object propertyValue) {
        if (!validateSimpleExpression()) {
            throwRuntimeException(new IllegalArgumentException("Call to [like] with propertyName [" +
                    propertyName + "] and value [" + propertyValue + "] not allowed here."));
        }

        propertyName = calculatePropertyName(propertyName);
        propertyValue = calculatePropertyValue(propertyValue);
        addToCriteria(Restrictions.like(propertyName, propertyValue));
        return this;
    }

    /**
     * Creates a Criterion with from the specified property name and "rlike" (a regular expression version of "like") expression
     * @param propertyName The property name
     * @param propertyValue The ilike value
     *
     * @return A Criterion instance
     */
    public org.grails.datastore.mapping.query.api.Criteria rlike(String propertyName, Object propertyValue) {
        if (!validateSimpleExpression()) {
            throwRuntimeException(new IllegalArgumentException("Call to [rlike] with propertyName [" +
                    propertyName + "] and value [" + propertyValue + "] not allowed here."));
        }

        propertyName = calculatePropertyName(propertyName);
        propertyValue = calculatePropertyValue(propertyValue);
        addToCriteria(new RlikeExpression(propertyName, propertyValue));
        return this;
    }

    /**
     * Creates a Criterion with from the specified property name and "ilike" (a case sensitive version of "like") expression
     * @param propertyName The property name
     * @param propertyValue The ilike value
     *
     * @return A Criterion instance
     */
    public org.grails.datastore.mapping.query.api.Criteria ilike(String propertyName, Object propertyValue) {
        if (!validateSimpleExpression()) {
            throwRuntimeException(new IllegalArgumentException("Call to [ilike] with propertyName [" +
                    propertyName + "] and value [" + propertyValue + "] not allowed here."));
        }

        propertyName = calculatePropertyName(propertyName);
        propertyValue = calculatePropertyValue(propertyValue);
        addToCriteria(Restrictions.ilike(propertyName, propertyValue));
        return this;
    }

    /**
     * Applys a "in" contrain on the specified property
     * @param propertyName The property name
     * @param values A collection of values
     *
     * @return A Criterion instance
     */
    @SuppressWarnings("rawtypes")
    public org.grails.datastore.mapping.query.api.Criteria in(String propertyName, Collection values) {
        if (!validateSimpleExpression()) {
            throwRuntimeException(new IllegalArgumentException("Call to [in] with propertyName [" +
                    propertyName + "] and values [" + values + "] not allowed here."));
        }

        propertyName = calculatePropertyName(propertyName);
        addToCriteria(Restrictions.in(propertyName, values == null ? Collections.EMPTY_LIST : values));
        return this;
    }

    /**
     * Delegates to in as in is a Groovy keyword
     */
    @SuppressWarnings("rawtypes")
    public org.grails.datastore.mapping.query.api.Criteria inList(String propertyName, Collection values) {
        return in(propertyName, values);
    }

    /**
     * Delegates to in as in is a Groovy keyword
     */
    public org.grails.datastore.mapping.query.api.Criteria inList(String propertyName, Object[] values) {
        return in(propertyName, values);
    }

    /**
     * Applys a "in" contrain on the specified property
     * @param propertyName The property name
     * @param values A collection of values
     *
     * @return A Criterion instance
     */
    public org.grails.datastore.mapping.query.api.Criteria in(String propertyName, Object[] values) {
        if (!validateSimpleExpression()) {
            throwRuntimeException(new IllegalArgumentException("Call to [in] with propertyName [" +
                    propertyName + "] and values [" + values + "] not allowed here."));
        }

        propertyName = calculatePropertyName(propertyName);
        addToCriteria(Restrictions.in(propertyName, values));
        return this;
    }

    /**
     * Orders by the specified property name (defaults to ascending)
     *
     * @param propertyName The property name to order by
     * @return A Order instance
     */
    public org.grails.datastore.mapping.query.api.Criteria order(String propertyName) {
        if (criteria == null) {
            throwRuntimeException(new IllegalArgumentException("Call to [order] with propertyName [" +
                    propertyName + "]not allowed here."));
        }
        propertyName = calculatePropertyName(propertyName);
        Order o = Order.asc(propertyName);
        if (paginationEnabledList) {
            orderEntries.add(o);
        }
        else {
            criteria.addOrder(o);
        }
        return this;
    }

    /**
     * Orders by the specified property name (defaults to ascending)
     *
     * @param o The property name to order by
     * @return A Order instance
     */
    public org.grails.datastore.mapping.query.api.Criteria order(Order o) {
        if (criteria == null) {
            throwRuntimeException(new IllegalArgumentException("Call to [order] not allowed here."));
        }
        if (paginationEnabledList) {
            orderEntries.add(o);
        }
        else {
            criteria.addOrder(o);
        }
        return this;
    }

    /**
     * Orders by the specified property name and direction
     *
     * @param propertyName The property name to order by
     * @param direction Either "asc" for ascending or "desc" for descending
     *
     * @return A Order instance
     */
    public org.grails.datastore.mapping.query.api.Criteria order(String propertyName, String direction) {
        if (criteria == null) {
            throwRuntimeException(new IllegalArgumentException("Call to [order] with propertyName [" +
                    propertyName + "]not allowed here."));
        }
        propertyName = calculatePropertyName(propertyName);
        Order o;
        if (direction.equals(ORDER_DESCENDING)) {
            o = Order.desc(propertyName);
        }
        else {
            o = Order.asc(propertyName);
        }
        if (paginationEnabledList) {
            orderEntries.add(o);
        }
        else {
            criteria.addOrder(o);
        }
        return this;
    }

    /**
     * Creates a Criterion that contrains a collection property by size
     *
     * @param propertyName The property name
     * @param size The size to constrain by
     *
     * @return A Criterion instance
     */
    public org.grails.datastore.mapping.query.api.Criteria sizeEq(String propertyName, int size) {
        if (!validateSimpleExpression()) {
            throwRuntimeException(new IllegalArgumentException("Call to [sizeEq] with propertyName [" +
                    propertyName + "] and size [" + size + "] not allowed here."));
        }

        propertyName = calculatePropertyName(propertyName);
        addToCriteria(Restrictions.sizeEq(propertyName, size));
        return this;
    }

    /**
     * Creates a Criterion that contrains a collection property to be greater than the given size
     *
     * @param propertyName The property name
     * @param size The size to constrain by
     *
     * @return A Criterion instance
     */
    public org.grails.datastore.mapping.query.api.Criteria sizeGt(String propertyName, int size) {
        if (!validateSimpleExpression()) {
            throwRuntimeException(new IllegalArgumentException("Call to [sizeGt] with propertyName [" +
                    propertyName + "] and size [" + size + "] not allowed here."));
        }

        propertyName = calculatePropertyName(propertyName);
        addToCriteria(Restrictions.sizeGt(propertyName, size));
        return this;
    }

    /**
     * Creates a Criterion that contrains a collection property to be greater than or equal to the given size
     *
     * @param propertyName The property name
     * @param size The size to constrain by
     *
     * @return A Criterion instance
     */
    public org.grails.datastore.mapping.query.api.Criteria sizeGe(String propertyName, int size) {
        if (!validateSimpleExpression()) {
            throwRuntimeException(new IllegalArgumentException("Call to [sizeGe] with propertyName [" +
                    propertyName + "] and size [" + size + "] not allowed here."));
        }

        propertyName = calculatePropertyName(propertyName);
        addToCriteria(Restrictions.sizeGe(propertyName, size));
        return this;
    }

    /**
     * Creates a Criterion that contrains a collection property to be less than or equal to the given size
     *
     * @param propertyName The property name
     * @param size The size to constrain by
     *
     * @return A Criterion instance
     */
    public org.grails.datastore.mapping.query.api.Criteria sizeLe(String propertyName, int size) {
        if (!validateSimpleExpression()) {
            throwRuntimeException(new IllegalArgumentException("Call to [sizeLe] with propertyName [" +
                    propertyName + "] and size [" + size + "] not allowed here."));
        }

        propertyName = calculatePropertyName(propertyName);
        addToCriteria(Restrictions.sizeLe(propertyName, size));
        return this;
    }

    /**
     * Creates a Criterion that contrains a collection property to be less than to the given size
     *
     * @param propertyName The property name
     * @param size The size to constrain by
     *
     * @return A Criterion instance
     */
    public org.grails.datastore.mapping.query.api.Criteria sizeLt(String propertyName, int size) {
        if (!validateSimpleExpression()) {
            throwRuntimeException(new IllegalArgumentException("Call to [sizeLt] with propertyName [" +
                    propertyName + "] and size [" + size + "] not allowed here."));
        }

        propertyName = calculatePropertyName(propertyName);
        addToCriteria(Restrictions.sizeLt(propertyName, size));
        return this;
    }

    /**
     * Creates a Criterion that contrains a collection property to be not equal to the given size
     *
     * @param propertyName The property name
     * @param size The size to constrain by
     *
     * @return A Criterion instance
     */
    public org.grails.datastore.mapping.query.api.Criteria sizeNe(String propertyName, int size) {
        if (!validateSimpleExpression()) {
            throwRuntimeException(new IllegalArgumentException("Call to [sizeNe] with propertyName [" +
                    propertyName + "] and size [" + size + "] not allowed here."));
        }

        propertyName = calculatePropertyName(propertyName);
        addToCriteria(Restrictions.sizeNe(propertyName, size));
        return this;
    }

    /**
     * Creates a "not equal" Criterion based on the specified property name and value
     * @param propertyName The property name
     * @param propertyValue The property value
     * @return The criterion object
     */
    public org.grails.datastore.mapping.query.api.Criteria ne(String propertyName, Object propertyValue) {
        if (!validateSimpleExpression()) {
            throwRuntimeException(new IllegalArgumentException("Call to [ne] with propertyName [" +
                    propertyName + "] and value [" + propertyValue + "] not allowed here."));
        }

        propertyName = calculatePropertyName(propertyName);
        propertyValue = calculatePropertyValue(propertyValue);
        addToCriteria(Restrictions.ne(propertyName, propertyValue));
        return this;
    }

    public org.grails.datastore.mapping.query.api.Criteria notEqual(String propertyName, Object propertyValue) {
        return ne(propertyName, propertyValue);
    }

    /**
     * Creates a "between" Criterion based on the property name and specified lo and hi values
     * @param propertyName The property name
     * @param lo The low value
     * @param hi The high value
     * @return A Criterion instance
     */
    public org.grails.datastore.mapping.query.api.Criteria between(String propertyName, Object lo, Object hi) {
        if (!validateSimpleExpression()) {
            throwRuntimeException(new IllegalArgumentException("Call to [between] with propertyName [" +
                    propertyName + "]  not allowed here."));
        }

        propertyName = calculatePropertyName(propertyName);
        addToCriteria(Restrictions.between(propertyName, lo, hi));
        return this;
    }

    public org.grails.datastore.mapping.query.api.Criteria gte(String s, Object o) {
        return ge(s, o);
    }

    private boolean validateSimpleExpression() {
        return criteria != null;
    }

    @SuppressWarnings("rawtypes")
    @Override
    public Object invokeMethod(String name, Object obj) {
        Object[] args = obj.getClass().isArray() ? (Object[])obj : new Object[]{obj};

        if (paginationEnabledList && SET_RESULT_TRANSFORMER_CALL.equals(name) && args.length == 1 &&
                args[0] instanceof ResultTransformer) {
            resultTransformer = (ResultTransformer) args[0];
            return null;
        }

        if (isCriteriaConstructionMethod(name, args)) {
            if (criteria != null) {
                throwRuntimeException(new IllegalArgumentException("call to [" + name + "] not supported here"));
            }

            if (name.equals(GET_CALL)) {
                uniqueResult = true;
            }
            else if (name.equals(SCROLL_CALL)) {
                scroll = true;
            }
            else if (name.equals(COUNT_CALL)) {
                count = true;
            }
            else if (name.equals(LIST_DISTINCT_CALL)) {
                resultTransformer = CriteriaSpecification.DISTINCT_ROOT_ENTITY;
            }

            createCriteriaInstance();

            // Check for pagination params
            if (name.equals(LIST_CALL) && args.length == 2) {
                paginationEnabledList = true;
                orderEntries = new ArrayList<Order>();
                invokeClosureNode(args[1]);
            }
            else {
                invokeClosureNode(args[0]);
            }

            if (resultTransformer != null) {
                criteria.setResultTransformer(resultTransformer);
            }
            Object result;
            if (!uniqueResult) {
                if (scroll) {
                    result = criteria.scroll();
                }
                else if (count) {
                    criteria.setProjection(Projections.rowCount());
                    result = criteria.uniqueResult();
                }
                else if (paginationEnabledList) {
                    // Calculate how many results there are in total. This has been
                    // moved to before the 'list()' invocation to avoid any "ORDER
                    // BY" clause added by 'populateArgumentsForCriteria()', otherwise
                    // an exception is thrown for non-string sort fields (GRAILS-2690).
                    criteria.setFirstResult(0);
                    criteria.setMaxResults(Integer.MAX_VALUE);

                    // Restore the previous projection, add settings for the pagination parameters,
                    // and then execute the query.
                    if (projectionList != null && projectionList.getLength() > 0) {
                        criteria.setProjection(projectionList);
                    } else {
                        criteria.setProjection(null);
                    }
                    for (Order orderEntry : orderEntries) {
                        criteria.addOrder(orderEntry);
                    }
                    if (resultTransformer == null) {
                        criteria.setResultTransformer(CriteriaSpecification.ROOT_ENTITY);
                    }
                    else if (paginationEnabledList) {
                        // relevant to GRAILS-5692
                        criteria.setResultTransformer(resultTransformer);
                    }
                    // GRAILS-7324 look if we already have association to sort by
                    Map argMap = (Map)args[0];
                    final String sort = (String) argMap.get(GrailsHibernateUtil.ARGUMENT_SORT);
                    if (sort != null) {
                        boolean ignoreCase = true;
                        Object caseArg = argMap.get(GrailsHibernateUtil.ARGUMENT_IGNORE_CASE);
                        if (caseArg instanceof Boolean) {
                            ignoreCase = (Boolean) caseArg;
                        }
                        final String orderParam = (String) argMap.get(GrailsHibernateUtil.ARGUMENT_ORDER);
                        final String order = GrailsHibernateUtil.ORDER_DESC.equalsIgnoreCase(orderParam) ?
                                GrailsHibernateUtil.ORDER_DESC : GrailsHibernateUtil.ORDER_ASC;
                        int lastPropertyPos = sort.lastIndexOf('.');
                        String associationForOrdering = lastPropertyPos >= 0 ? sort.substring(0, lastPropertyPos) : null;
                        if (associationForOrdering != null && aliasMap.containsKey(associationForOrdering)) {
                            addOrder(criteria, aliasMap.get(associationForOrdering) + "." + sort.substring(lastPropertyPos + 1),
                                    order, ignoreCase);
                            // remove sort from arguments map to exclude from default processing.
                            @SuppressWarnings("unchecked") Map argMap2 = new HashMap(argMap);
                            argMap2.remove(GrailsHibernateUtil.ARGUMENT_SORT);
                            argMap = argMap2;
                        }
                    }
                    GrailsHibernateUtil.populateArgumentsForCriteria(grailsApplication, targetClass, criteria, argMap);
                    GrailsHibernateTemplate ght = new GrailsHibernateTemplate(sessionFactory, grailsApplication);
                    PagedResultList pagedRes = new PagedResultList(ght, criteria);
                    result = pagedRes;
                }
                else {
                    result = criteria.list();
                }
            }
            else {
                result = GrailsHibernateUtil.unwrapIfProxy(criteria.uniqueResult());
            }
            if (!participate) {
                hibernateSession.close();
            }
            return result;
        }

        if (criteria == null) createCriteriaInstance();

        MetaMethod metaMethod = getMetaClass().getMetaMethod(name, args);
        if (metaMethod != null) {
            return metaMethod.invoke(this, args);
        }

        metaMethod = criteriaMetaClass.getMetaMethod(name, args);
        if (metaMethod != null) {
            return metaMethod.invoke(criteria, args);
        }
        metaMethod = criteriaMetaClass.getMetaMethod(GrailsClassUtils.getSetterName(name), args);
        if (metaMethod != null) {
            return metaMethod.invoke(criteria, args);
        }

        if (isAssociationQueryMethod(args) || isAssociationQueryWithJoinSpecificationMethod(args)) {
            final boolean hasMoreThanOneArg = args.length > 1;
            Object callable = hasMoreThanOneArg ? args[1] : args[0];
            int joinType = hasMoreThanOneArg ? (Integer)args[0] : CriteriaSpecification.INNER_JOIN;

            if (name.equals(AND) || name.equals(OR) || name.equals(NOT)) {
                if (criteria == null) {
                    throwRuntimeException(new IllegalArgumentException("call to [" + name + "] not supported here"));
                }

                logicalExpressionStack.add(new LogicalExpression(name));
                invokeClosureNode(callable);

                LogicalExpression logicalExpression = logicalExpressionStack.remove(logicalExpressionStack.size()-1);
                addToCriteria(logicalExpression.toCriterion());

                return name;
            }

            if (name.equals(PROJECTIONS) && args.length == 1 && (args[0] instanceof Closure)) {
                if (criteria == null) {
                    throwRuntimeException(new IllegalArgumentException("call to [" + name + "] not supported here"));
                }

                projectionList = Projections.projectionList();
                invokeClosureNode(callable);

                if (projectionList != null && projectionList.getLength() > 0) {
                    criteria.setProjection(projectionList);
                }

                return name;
            }

            final PropertyDescriptor pd = BeanUtils.getPropertyDescriptor(targetClass, name);
            if (pd != null && pd.getReadMethod() != null) {
                ClassMetadata meta = sessionFactory.getClassMetadata(targetClass);
                Type type = meta.getPropertyType(name);
                if (type.isAssociationType()) {
                    String otherSideEntityName =
                        ((AssociationType) type).getAssociatedEntityName((SessionFactoryImplementor) sessionFactory);
                    Class oldTargetClass = targetClass;
                    targetClass = sessionFactory.getClassMetadata(otherSideEntityName).getMappedClass(EntityMode.POJO);
                    if (targetClass.equals(oldTargetClass) && !hasMoreThanOneArg) {
                        joinType = CriteriaSpecification.LEFT_JOIN; // default to left join if joining on the same table
                    }
                    associationStack.add(name);
                    final String associationPath = getAssociationPath();
                    createAliasIfNeccessary(name, associationPath,joinType);
                    // the criteria within an association node are grouped with an implicit AND
                    logicalExpressionStack.add(new LogicalExpression(AND));
                    invokeClosureNode(callable);
                    aliasStack.remove(aliasStack.size() - 1);
                    if (!aliasInstanceStack.isEmpty()) {
                        aliasInstanceStack.remove(aliasInstanceStack.size() - 1);
                    }
                    LogicalExpression logicalExpression = logicalExpressionStack.remove(logicalExpressionStack.size()-1);
                    if (!logicalExpression.args.isEmpty()) {
                        addToCriteria(logicalExpression.toCriterion());
                    }
                    associationStack.remove(associationStack.size()-1);
                    targetClass = oldTargetClass;

                    return name;
                } else if(type instanceof EmbeddedComponentType) {
                    associationStack.add(name);
                    logicalExpressionStack.add(new LogicalExpression(AND));
                    Class oldTargetClass = targetClass;
                    targetClass = pd.getPropertyType();
                    invokeClosureNode(callable);
                    targetClass = oldTargetClass;
                    LogicalExpression logicalExpression = logicalExpressionStack.remove(logicalExpressionStack.size()-1);
                    if (!logicalExpression.args.isEmpty()) {
                        addToCriteria(logicalExpression.toCriterion());
                    }
                    associationStack.remove(associationStack.size()-1);
                    return name;
                }
            }
        }
        else if (args.length == 1 && args[0] != null) {
            if (criteria == null) {
                throwRuntimeException(new IllegalArgumentException("call to [" + name + "] not supported here"));
            }

            Object value = args[0];
            Criterion c = null;
            if (name.equals(ID_EQUALS)) {
                return eq("id", value);
            }

            if (name.equals(IS_NULL) ||
                    name.equals(IS_NOT_NULL) ||
                    name.equals(IS_EMPTY) ||
                    name.equals(IS_NOT_EMPTY)) {
                if (!(value instanceof String)) {
                    throwRuntimeException(new IllegalArgumentException("call to [" + name + "] with value [" +
                            value + "] requires a String value."));
                }
                String propertyName = calculatePropertyName((String)value);
                if (name.equals(IS_NULL)) {
                    c = Restrictions.isNull(propertyName);
                }
                else if (name.equals(IS_NOT_NULL)) {
                    c = Restrictions.isNotNull(propertyName);
                }
                else if (name.equals(IS_EMPTY)) {
                    c = Restrictions.isEmpty(propertyName);
                }
                else if (name.equals(IS_NOT_EMPTY)) {
                    c = Restrictions.isNotEmpty(propertyName);
                }
            }

            if (c != null) {
                return addToCriteria(c);
            }
        }

        throw new MissingMethodException(name, getClass(), args);
    }

    private boolean isAssociationQueryMethod(Object[] args) {
        return args.length == 1 && args[0] instanceof Closure;
    }

    private boolean isAssociationQueryWithJoinSpecificationMethod(Object[] args) {
        return args.length == 2 && (args[0] instanceof Number) && (args[1] instanceof Closure);
    }

    @SuppressWarnings("unused")
    private void addToCurrentOrAliasedCriteria(Criterion criterion) {
        if (!aliasInstanceStack.isEmpty()) {
            Criteria c = aliasInstanceStack.get(aliasInstanceStack.size()-1);
            c.add(criterion);
        }
        else {
            criteria.add(criterion);
        }
    }

    private void createAliasIfNeccessary(String associationName, String associationPath, int joinType) {
        String newAlias;
        if (aliasMap.containsKey(associationPath)) {
            newAlias = aliasMap.get(associationPath);
        }
        else {
            aliasCount++;
            newAlias = associationName + ALIAS + aliasCount;
            aliasMap.put(associationPath, newAlias);
            aliasInstanceStack.add(criteria.createAlias(associationPath, newAlias,
                    joinType));
        }
        aliasStack.add(newAlias);
    }

    private String getAssociationPath() {
        StringBuilder fullPath = new StringBuilder();
        for (Object anAssociationStack : associationStack) {
            String propertyName = (String) anAssociationStack;
            if (fullPath.length() > 0) fullPath.append(".");
            fullPath.append(propertyName);
        }
        return fullPath.toString();
    }

    private boolean isCriteriaConstructionMethod(String name, Object[] args) {
        return (name.equals(LIST_CALL) && args.length == 2 && args[0] instanceof Map && args[1] instanceof Closure) ||
                  (name.equals(ROOT_CALL) ||
                    name.equals(ROOT_DO_CALL) ||
                    name.equals(LIST_CALL) ||
                    name.equals(LIST_DISTINCT_CALL) ||
                    name.equals(GET_CALL) ||
                    name.equals(COUNT_CALL) ||
                    name.equals(SCROLL_CALL) && args.length == 1 && args[0] instanceof Closure);
    }

    public Criteria buildCriteria(Closure<?> criteriaClosure) {
        createCriteriaInstance();
        criteriaClosure.setDelegate(this);
        criteriaClosure.call();
        return criteria;
    }

    private void createCriteriaInstance() {
        if (TransactionSynchronizationManager.hasResource(sessionFactory)) {
            participate = true;
            hibernateSession = ((SessionHolder)TransactionSynchronizationManager.getResource(sessionFactory)).getSession();
        }
        else {
            hibernateSession = sessionFactory.openSession();
        }

        criteria = hibernateSession.createCriteria(targetClass);
        GrailsHibernateUtil.cacheCriteriaByMapping(grailsApplication, targetClass, criteria);
        criteriaMetaClass = GroovySystem.getMetaClassRegistry().getMetaClass(criteria.getClass());
    }

    private void invokeClosureNode(Object args) {
        Closure<?> callable = (Closure<?>)args;
        callable.setDelegate(this);
        callable.setResolveStrategy(Closure.DELEGATE_FIRST);
        callable.call();
    }

    /**
     * Throws a runtime exception where necessary to ensure the session gets closed
     */
    private void throwRuntimeException(RuntimeException t) {
        closeSessionFollowingException();
        throw t;
    }

    private void closeSessionFollowingException() {
        if (hibernateSession != null && hibernateSession.isOpen() && !participate) {
            hibernateSession.close();
        }
        criteria = null;
    }

    /**
     * adds and returns the given criterion to the currently active criteria set.
     * this might be either the root criteria or a currently open
     * LogicalExpression.
     */
    private Criterion addToCriteria(Criterion c) {
        if (!logicalExpressionStack.isEmpty()) {
            logicalExpressionStack.get(logicalExpressionStack.size() - 1).args.add(c);
        }
        else {
            criteria.add(c);
        }
        return c;
    }

    /**
     * instances of this class are pushed onto the logicalExpressionStack
     * to represent all the unfinished "and", "or", and "not" expressions.
     */
    private class LogicalExpression {
        final Object name;
        final List<Criterion> args = new ArrayList<Criterion>();

        LogicalExpression(Object name) {
            this.name = name;
        }

        Criterion toCriterion() {
            if (name.equals(NOT)) {
                switch (args.size()) {
                    case 0:
                        throwRuntimeException(new IllegalArgumentException("Logical expression [not] must contain at least 1 expression"));
                        return null;

                    case 1:
                        return Restrictions.not(args.get(0));

                    default:
                        // treat multiple sub-criteria as an implicit "OR"
                        return Restrictions.not(buildJunction(Restrictions.disjunction(), args));
                }
            }

            if (name.equals(AND)) {
                return buildJunction(Restrictions.conjunction(), args);
            }

            if (name.equals(OR)) {
                return buildJunction(Restrictions.disjunction(), args);
            }

            throwRuntimeException(new IllegalStateException("Logical expression [" + name + "] not handled!"));
            return null;
        }

        // add the Criterion objects in the given list to the given junction.
        Junction buildJunction(Junction junction, List<Criterion> criterions) {
            for (Criterion c : criterions) {
                junction.add(c);
            }

            return junction;
        }
    }

    /**
     * Add order directly to criteria.
     */
    private static void addOrder(Criteria c, String sort, String order, boolean ignoreCase) {
        if (GrailsHibernateUtil.ORDER_DESC.equals(order)) {
            c.addOrder( ignoreCase ? Order.desc(sort).ignoreCase() : Order.desc(sort));
        }
        else {
            c.addOrder( ignoreCase ? Order.asc(sort).ignoreCase() : Order.asc(sort) );
        }
    }
}


text:
<!DOCTYPE html>
<html lang="sk">

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
  <meta name="author" content="lab.SNG">
  <meta name="robots" content="noindex, nofollow">

  <title>
      {!! $item->title !!} | {{ trans('zoom.title') }}
  </title>

  <!--  favicons-->
  @include('includes.favicons')
  <!--  /favicons-->
  <!--  Open Graph protocol -->
  @include('includes.og_tags')
  <!--  Open Graph protocol -->

  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css">
  {!! Html::style('css/style.css') !!}
 </head>

 <body class="template-zoom">
  @include('components.zoomviewer', ['item' => $item, 'images' => $images, 'index' => $index])
  @include('components.zoomviewer_js', ['item' => $item, 'images' => $images, 'index' => $index])
 </body>

</html>

redpajama_set_name: RedPajamaGithub


text:
package test.integration.throttle;

import com.bazaarvoice.emodb.auth.apikey.ApiKey;
import com.bazaarvoice.emodb.auth.apikey.ApiKeyModification;
import com.bazaarvoice.emodb.auth.identity.InMemoryAuthIdentityManager;
import com.bazaarvoice.emodb.auth.permissions.InMemoryPermissionManager;
import com.bazaarvoice.emodb.auth.role.InMemoryRoleManager;
import com.bazaarvoice.emodb.blob.api.BlobStore;
import com.bazaarvoice.emodb.common.jersey.dropwizard.JerseyEmoClient;
import com.bazaarvoice.emodb.common.uuid.TimeUUIDs;
import com.bazaarvoice.emodb.common.zookeeper.store.ZkMapStore;
import com.bazaarvoice.emodb.job.api.JobHandlerRegistry;
import com.bazaarvoice.emodb.job.api.JobService;
import com.bazaarvoice.emodb.sor.api.AuditBuilder;
import com.bazaarvoice.emodb.sor.api.DataStore;
import com.bazaarvoice.emodb.sor.api.Update;
import com.bazaarvoice.emodb.sor.client.DataStoreAuthenticator;
import com.bazaarvoice.emodb.sor.client.DataStoreClient;
import com.bazaarvoice.emodb.sor.compactioncontrol.InMemoryCompactionControlSource;
import com.bazaarvoice.emodb.sor.core.DefaultDataStoreAsync;
import com.bazaarvoice.emodb.sor.delta.Deltas;
import com.bazaarvoice.emodb.test.ResourceTest;
import com.bazaarvoice.emodb.web.auth.EmoPermissionResolver;
import com.bazaarvoice.emodb.web.resources.sor.DataStoreResource1;
import com.bazaarvoice.emodb.web.throttling.DataStoreUpdateThrottle;
import com.bazaarvoice.emodb.web.throttling.DataStoreUpdateThrottleManager;
import com.bazaarvoice.emodb.web.throttling.DataStoreUpdateThrottler;
import com.bazaarvoice.emodb.web.throttling.ZkDataStoreUpdateThrottleSerializer;
import com.codahale.metrics.MetricRegistry;
import com.google.common.base.Stopwatch;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.RateLimiter;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import io.dropwizard.testing.junit.ResourceTestRule;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.RetryNTimes;
import org.apache.curator.test.TestingServer;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;

import java.net.URI;
import java.time.Clock;
import java.time.Instant;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyDouble;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;

public class DataStoreUpdateThrottleTest extends ResourceTest {

    private final static String API_KEY_LIMITED = "apikeylimited";
    private final static String API_KEY_UNLIMITED = "apikeyunlimited";

    private static TestingServer _testingServer;
    private static CuratorFramework _rootCurator;

    private final DataStore _dataStore = mock(DataStore.class);
    private String _limitedId;
    private String _unlimitedId;
    private CuratorFramework _curator;
    private ZkMapStore<DataStoreUpdateThrottle> _mapStore;
    private DataStoreUpdateThrottleManager _throttleManager;
    private String _zkNamespace;
    private Instant _now;
    private volatile RateLimiterFactory _rateLimiterFactory;

    @Rule
    public ResourceTestRule _resourceTestRule = setupDataStoreResourceTestRule();

    private ResourceTestRule setupDataStoreResourceTestRule() {
        InMemoryAuthIdentityManager<ApiKey> authIdentityManager = new InMemoryAuthIdentityManager<>();
        _limitedId = authIdentityManager.createIdentity(API_KEY_LIMITED, new ApiKeyModification().addRoles("all-sor-role"));
        _unlimitedId = authIdentityManager.createIdentity(API_KEY_UNLIMITED, new ApiKeyModification().addRoles("all-sor-role"));
        EmoPermissionResolver permissionResolver = new EmoPermissionResolver(_dataStore, mock(BlobStore.class));
        InMemoryPermissionManager permissionManager = new InMemoryPermissionManager(permissionResolver);
        InMemoryRoleManager roleManager = new InMemoryRoleManager(permissionManager);

        createRole(roleManager, null, "all-sor-role", ImmutableSet.of("sor|*|*"));

        // Since a unique throttler will be created for each test construct the resource with a throttle which delegates
        // the the test-specific instance.

        DataStoreUpdateThrottler throttler = id -> _throttleManager.beforeUpdate(id);

        return setupResourceTestRule(
                Collections.<Object>singletonList(new DataStoreResource1(
                        _dataStore, new DefaultDataStoreAsync(_dataStore, mock(JobService.class), mock(JobHandlerRegistry.class)),
                        new InMemoryCompactionControlSource(), throttler)),
                ImmutableList.of(),
                authIdentityManager, permissionManager);
    }

    @BeforeClass
    public static void startZookeeper() throws Exception {
        _testingServer = new TestingServer();
        _rootCurator = CuratorFrameworkFactory.builder()
                .connectString(_testingServer.getConnectString())
                .retryPolicy(new RetryNTimes(3, 100))
                .threadFactory(new ThreadFactoryBuilder().setNameFormat("test-%d").setDaemon(true).build())
                .build();
        _rootCurator.start();
    }

    @AfterClass
    public static void stopZookeeper() throws Exception {
        _rootCurator.close();
        _testingServer.stop();
    }

    @Before
    public void setUp() throws Exception {
        // Create a unique base path for each test so each tests ZooKeeper data is independent.
        _zkNamespace = "emodb/test" + UUID.randomUUID();
        _curator = _rootCurator.usingNamespace(_zkNamespace);

        _mapStore = new ZkMapStore<>(_curator, "sor-update-throttle", new ZkDataStoreUpdateThrottleSerializer());
        _mapStore.start();

        Clock clock = mock(Clock.class);
        when(clock.instant()).then(ignore -> _now);
        when(clock.millis()).then(ignore-> _now.toEpochMilli());
        _now = Instant.ofEpochMilli(1514764800000L);

        _rateLimiterFactory = mock(RateLimiterFactory.class);

        _throttleManager = new DataStoreUpdateThrottleManager(_mapStore, clock, new MetricRegistry()) {
            // Defer rate limit generation to our controlled mock
            @Override
            protected RateLimiter createRateLimiter(double rate) {
                return _rateLimiterFactory.create(rate);
            }
        };

        // Whenever an update to the data store occurs it is passed in a list of updates.  Rate limiting is applied
        // as the list is iterated, so we have to configure the mock data store accordingly.
        doAnswer(invocation -> {
            //noinspection unchecked
            Iterable<Update> updates = (Iterable<Update>) invocation.getArguments()[0];
            updates.forEach(ignore -> {});
            return null;
        }).when(_dataStore).updateAll(any(), any());
    }

    @After
    public void tearDown() throws Exception {
        _mapStore.stop();
        reset(_dataStore);
        verifyNoMoreInteractions(_rateLimiterFactory);
    }

    @Test
    public void testNoLimits() throws Exception {
        // Simply update a document with no rate limits applied
        createClient(API_KEY_LIMITED).update("table", "key", TimeUUIDs.newUUID(), Deltas.delete(), new AuditBuilder().build());
    }

    @Test
    public void testApiKeyLimited() throws Exception {
        final RateLimiter rateLimiter = mock(RateLimiter.class);
        when(_rateLimiterFactory.create(10)).thenReturn(rateLimiter);

        updateAPIKeyRateLimit(_limitedId, new DataStoreUpdateThrottle(10, _now.plusSeconds(1)));

        createClient(API_KEY_LIMITED).update("table", "key", TimeUUIDs.newUUID(), Deltas.delete(), new AuditBuilder().build());

        verify(_rateLimiterFactory).create(10);
        verify(rateLimiter).acquire();
    }

    @Test
    public void testNotThisApiKeyLimited() throws Exception {
        final RateLimiter rateLimiter = mock(RateLimiter.class);
        when(rateLimiter.acquire()).thenThrow(new AssertionError("Unlimited key should not have been rate limited"));
        when(_rateLimiterFactory.create(anyDouble())).thenReturn(rateLimiter);

        updateAPIKeyRateLimit(_limitedId, new DataStoreUpdateThrottle(10, _now.plusSeconds(1)));

        createClient(API_KEY_UNLIMITED).update("table", "key", TimeUUIDs.newUUID(), Deltas.delete(), new AuditBuilder().build());

        verify(_rateLimiterFactory).create(10);
    }

    @Test
    public void testInstanceRateLimited() throws Exception {
        final RateLimiter keyRateLimiter = mock(RateLimiter.class);
        final RateLimiter instanceRateLimiter = mock(RateLimiter.class);
        when(_rateLimiterFactory.create(10)).thenReturn(keyRateLimiter);
        when(_rateLimiterFactory.create(20)).thenReturn(instanceRateLimiter);

        updateAPIKeyRateLimit(_limitedId, new DataStoreUpdateThrottle(10, _now.plusSeconds(1)));
        updateAPIKeyRateLimit("*", new DataStoreUpdateThrottle(20, _now.plusSeconds(1)));

        createClient(API_KEY_LIMITED).update("table", "key", TimeUUIDs.newUUID(), Deltas.delete(), new AuditBuilder().build());
        createClient(API_KEY_UNLIMITED).update("table", "key", TimeUUIDs.newUUID(), Deltas.delete(), new AuditBuilder().build());

        verify(_rateLimiterFactory).create(10);
        verify(_rateLimiterFactory).create(20);

        verify(keyRateLimiter).acquire();
        verify(instanceRateLimiter, times(2)).acquire();
    }

    @Test
    public void testStreamRateLimited() throws Exception {
        final RateLimiter rateLimiter = mock(RateLimiter.class);
        when(_rateLimiterFactory.create(10)).thenReturn(rateLimiter);

        updateAPIKeyRateLimit(_limitedId, new DataStoreUpdateThrottle(10, _now.plusSeconds(1)));

        List<Update> updates = Lists.newArrayListWithCapacity(20);
        for (int i=0; i < 20; i++) {
            updates.add(new Update("table", "key" + i, TimeUUIDs.newUUID(), Deltas.delete(), new AuditBuilder().build()));
        }
        createClient(API_KEY_LIMITED).updateAll(updates);

        verify(_rateLimiterFactory).create(10);
        verify(rateLimiter, times(20)).acquire();
    }

    @Test
    public void testRemoveRateLimit() throws Exception {
        final RateLimiter rateLimiter = mock(RateLimiter.class);
        when(_rateLimiterFactory.create(10)).thenReturn(rateLimiter);
        when(rateLimiter.acquire()).thenReturn(0.0).thenThrow(new AssertionError("Rate limit should only have been applied once"));

        updateAPIKeyRateLimit(_limitedId, new DataStoreUpdateThrottle(10, _now.plusSeconds(1)));
        createClient(API_KEY_LIMITED).update("table", "key", TimeUUIDs.newUUID(), Deltas.delete(), new AuditBuilder().build());

        clearAPIKeyRateLimit(_limitedId);
        createClient(API_KEY_LIMITED).update("table", "key", TimeUUIDs.newUUID(), Deltas.delete(), new AuditBuilder().build());

        verify(_rateLimiterFactory).create(10);
        verify(rateLimiter).acquire();
    }

    @Test
    public void testRateLimitExpiration() throws Exception {
        final RateLimiter rateLimiter = mock(RateLimiter.class);
        when(_rateLimiterFactory.create(10)).thenReturn(rateLimiter);
        when(rateLimiter.acquire()).thenReturn(0.0).thenThrow(new AssertionError("Rate limit should only have been applied once"));

        updateAPIKeyRateLimit(_limitedId, new DataStoreUpdateThrottle(10, _now.plusSeconds(1)));
        createClient(API_KEY_LIMITED).update("table", "key", TimeUUIDs.newUUID(), Deltas.delete(), new AuditBuilder().build());

        // Advance time to just after the rate limit's expiration
        _now = _now.plusSeconds(1).plusMillis(1);

        createClient(API_KEY_LIMITED).update("table", "key", TimeUUIDs.newUUID(), Deltas.delete(), new AuditBuilder().build());
        verify(_rateLimiterFactory).create(10);
        verify(rateLimiter).acquire();
    }

    /**
     * Behind the scenes when the rate limit for an API key is changed the manager creates a new {@link RateLimiter}.
     * This test depends on that behavior.  If it is changed such that the same RateLimiter instance is updated
     * using {@link RateLimiter#setRate(double)} then this test will fail unless it is similarly updated.
     */
    @Test
    public void testRateLimitChange() throws Exception {
        final RateLimiter rateLimiter10 = mock(RateLimiter.class);
        when(_rateLimiterFactory.create(10)).thenReturn(rateLimiter10);
        final RateLimiter rateLimiter20 = mock(RateLimiter.class);
        when(_rateLimiterFactory.create(20)).thenReturn(rateLimiter20);

        updateAPIKeyRateLimit(_limitedId, new DataStoreUpdateThrottle(10, _now.plusSeconds(1)));
        DataStore client = createClient(API_KEY_LIMITED);
        for (int i=0; i < 3; i++) {
            client.update("table", "key", TimeUUIDs.newUUID(), Deltas.delete(), new AuditBuilder().build());
        }

        updateAPIKeyRateLimit(_limitedId, new DataStoreUpdateThrottle(20, _now.plusSeconds(1)));
        client.update("table", "key", TimeUUIDs.newUUID(), Deltas.delete(), new AuditBuilder().build());

        verify(_rateLimiterFactory).create(10);
        verify(_rateLimiterFactory).create(20);

        verify(rateLimiter10, times(3)).acquire();
        verify(rateLimiter20).acquire();
    }

    private void updateAPIKeyRateLimit(String id, DataStoreUpdateThrottle throttle) throws Exception {
        // Make the underlying call
        _throttleManager.updateAPIKeyRateLimit(id, throttle);
        // Wait up to 10 seconds for the MapStore on the throttle manager to be updated.
        Stopwatch stopwatch = Stopwatch.createStarted();
        while (stopwatch.elapsed(TimeUnit.SECONDS) < 10) {
            Thread.sleep(10);
            DataStoreUpdateThrottle actualThrottle = _throttleManager.getThrottle(id);
            if (actualThrottle != null && actualThrottle.equals(throttle)) {
                return;
            }
        }
        fail("Updated rate limit was not applied after 10 seconds");
    }

    private void clearAPIKeyRateLimit(String id) throws Exception {
        // Make the underlying call
        _throttleManager.clearAPIKeyRateLimit(id);
        // Wait up to 10 seconds for the MapStore on the throttle manager to be updated.
        Stopwatch stopwatch = Stopwatch.createStarted();
        while (stopwatch.elapsed(TimeUnit.SECONDS) < 10) {
            Thread.sleep(10);
            if (_throttleManager.getThrottle(id) == null) {
                return;
            }
        }
        fail("Updated rate limit was not applied after 10 seconds");
    }

    private DataStore createClient(String apiKey) {
        return DataStoreAuthenticator.proxied(new DataStoreClient(URI.create("/sor/1"), new JerseyEmoClient(_resourceTestRule.client())))
                .usingCredentials(apiKey);
    }

    private interface RateLimiterFactory {
        RateLimiter create(double rate);
    }
}


text:
package v7

import (
	"strconv"

	"code.cloudfoundry.org/cli/resources"
	"code.cloudfoundry.org/cli/util/ui"
)

type BuildpacksCommand struct {
	BaseCommand

	usage           interface{} `usage:"CF_NAME buildpacks [--labels SELECTOR]\n\nEXAMPLES:\n   CF_NAME buildpacks\n   CF_NAME buildpacks --labels 'environment in (production,staging),tier in (backend)'\n   CF_NAME buildpacks --labels 'env=dev,!chargeback-code,tier in (backend,worker)'"`
	relatedCommands interface{} `related_commands:"create-buildpack, delete-buildpack, rename-buildpack, update-buildpack"`
	Labels          string      `long:"labels" description:"Selector to filter buildpacks by labels"`
}

func (cmd BuildpacksCommand) Execute(args []string) error {
	err := cmd.SharedActor.CheckTarget(false, false)
	if err != nil {
		return err
	}

	user, err := cmd.Actor.GetCurrentUser()
	if err != nil {
		return err
	}

	cmd.UI.DisplayTextWithFlavor("Getting buildpacks as {{.Username}}...", map[string]interface{}{
		"Username": user.Name,
	})
	cmd.UI.DisplayNewline()

	buildpacks, warnings, err := cmd.Actor.GetBuildpacks(cmd.Labels)
	cmd.UI.DisplayWarnings(warnings)
	if err != nil {
		return err
	}

	if len(buildpacks) == 0 {
		cmd.UI.DisplayTextWithFlavor("No buildpacks found")
	} else {
		cmd.displayTable(buildpacks)
	}
	return nil
}

func (cmd BuildpacksCommand) displayTable(buildpacks []resources.Buildpack) {
	if len(buildpacks) > 0 {
		var keyValueTable = [][]string{
			{"position", "name", "stack", "enabled", "locked", "filename"},
		}
		for _, buildpack := range buildpacks {
			keyValueTable = append(keyValueTable, []string{
				strconv.Itoa(buildpack.Position.Value),
				buildpack.Name,
				buildpack.Stack,
				strconv.FormatBool(buildpack.Enabled.Value),
				strconv.FormatBool(buildpack.Locked.Value),
				buildpack.Filename,
			})
		}

		cmd.UI.DisplayTableWithHeader("", keyValueTable, ui.DefaultTableSpacePadding)
	}
}


text:
GKE_ZONE=${GKE_ZONE:-'us-central1-b'}
GKE_CLUSTER_NAME=${GKE_CLUSTER_NAME:-'example'}
SHARDS=${SHARDS:-'-80,80-'}
TABLETS_PER_SHARD=${TABLETS_PER_SHARD:-3}
RDONLY_COUNT=${RDONLY_COUNT:-0}
MAX_TASK_WAIT_RETRIES=${MAX_TASK_WAIT_RETRIES:-300}
MAX_VTTABLET_TOPO_WAIT_RETRIES=${MAX_VTTABLET_TOPO_WAIT_RETRIES:-180}
VTTABLET_TEMPLATE=${VTTABLET_TEMPLATE:-'vttablet-pod-benchmarking-template.yaml'}
VTGATE_TEMPLATE=${VTGATE_TEMPLATE:-'vtgate-controller-benchmarking-template.yaml'}
VTGATE_COUNT=${VTGATE_COUNT:-0}
VTDATAROOT_VOLUME=${VTDATAROOT_VOLUME:-''}
CELLS=${CELLS:-'test'}

cells=`echo $CELLS | tr ',' ' '`
num_cells=`echo $cells | wc -w`

# Get region from zone (everything to last dash)
gke_region=`echo $GKE_ZONE | sed "s/-[^-]*$//"`

# export for vttablet scripts
export SHARDS=$SHARDS
export TABLETS_PER_SHARD=$TABLETS_PER_SHARD
export RDONLY_COUNT=$RDONLY_COUNT
export VTDATAROOT_VOLUME=$VTDATAROOT_VOLUME
export VTGATE_TEMPLATE=$VTGATE_TEMPLATE
export VTTABLET_TEMPLATE=$VTTABLET_TEMPLATE
export VTGATE_REPLICAS=$VTGATE_COUNT

function update_spinner_value () {
  spinner='-\|/'
  cur_spinner=${spinner:$(($1%${#spinner})):1}
}

function wait_for_running_tasks () {
  # This function waits for pods to be in the "Running" state
  # 1. task_name: Name that the desired task begins with
  # 2. num_tasks: Number of tasks to wait for
  # Returns:
  #   0 if successful, -1 if timed out
  task_name=$1
  num_tasks=$2
  counter=0

  echo "Waiting for ${num_tasks}x $task_name to enter state Running"

  while [ $counter -lt $MAX_TASK_WAIT_RETRIES ]; do
    # Get status column of pods with name starting with $task_name,
    # count how many are in state Running
    num_running=`$KUBECTL get pods | grep ^$task_name | grep Running | wc -l`

    echo -en "\r$task_name: $num_running out of $num_tasks in state Running..."
    if [ $num_running -eq $num_tasks ]
    then
      echo Complete
      return 0
    fi
    update_spinner_value $counter
    echo -n $cur_spinner
    let counter=counter+1
    sleep 1
  done
  echo Timed out
  return -1
}

if [ -z "$GOPATH" ]; then
  echo "ERROR: GOPATH undefined, can't obtain vtctlclient"
  exit -1
fi

export KUBECTL='kubectl'
go get github.com/youtube/vitess/go/cmd/vtctlclient
num_shards=`echo $SHARDS | tr "," " " | wc -w`
total_tablet_count=$(($num_shards*$TABLETS_PER_SHARD*$num_cells))
vtgate_count=$VTGATE_COUNT
if [ $vtgate_count -eq 0 ]; then
  vtgate_count=$(($total_tablet_count/4>3?$total_tablet_count/4:3))
fi

echo "****************************"
echo "*Creating vitess cluster:"
echo "*  Zone: $GKE_ZONE"
echo "*  Shards: $SHARDS"
echo "*  Tablets per shard: $TABLETS_PER_SHARD"
echo "*  Rdonly per shard: $RDONLY_COUNT"
echo "*  VTGate count: $vtgate_count"
echo "*  Cells: $cells"
echo "****************************"

echo 'Running etcd-up.sh' && CELLS=$CELLS ./etcd-up.sh
wait_for_running_tasks etcd-global 3
for cell in $cells; do
  wait_for_running_tasks etcd-$cell 3
done

echo 'Running vtctld-up.sh' && ./vtctld-up.sh
echo 'Running vttablet-up.sh' && CELLS=$CELLS ./vttablet-up.sh
echo 'Running vtgate-up.sh' && ./vtgate-up.sh

wait_for_running_tasks vtctld 1
wait_for_running_tasks vttablet $total_tablet_count
wait_for_running_tasks vtgate $vtgate_count

echo Creating firewall rule for vtctld...
vtctld_port=30001
gcloud compute firewall-rules create ${GKE_CLUSTER_NAME}-vtctld --allow tcp:$vtctld_port
vtctld_ip=`$KUBECTL get -o yaml nodes | grep 'type: ExternalIP' -B 1 | head -1 | awk '{print $NF}'`
vtctl_server="$vtctld_ip:$vtctld_port"
kvtctl="$GOPATH/bin/vtctlclient -server $vtctl_server"

echo Waiting for tablets to be visible in the topology
counter=0
while [ $counter -lt $MAX_VTTABLET_TOPO_WAIT_RETRIES ]; do
  num_tablets=0
  for cell in $cells; do
    num_tablets=$(($num_tablets+`$kvtctl ListAllTablets $cell | wc -l`))
  done
  echo -en "\r$num_tablets out of $total_tablet_count in topology..."
  if [ $num_tablets -eq $total_tablet_count ]
  then
    echo Complete
    break
  fi
  update_spinner_value $counter
  echo -n $cur_spinner
  let counter=counter+1
  sleep 1
  if [ $counter -eq $MAX_VTTABLET_TOPO_WAIT_RETRIES ]
  then
    echo Timed out
  fi
done

# split_shard_count = num_shards for sharded keyspace, 0 for unsharded
split_shard_count=$num_shards
if [ $split_shard_count -eq 1 ]; then
  split_shard_count=0
fi

echo -n Setting Keyspace Sharding Info...
$kvtctl SetKeyspaceShardingInfo -force -split_shard_count $split_shard_count test_keyspace keyspace_id uint64
echo Done
echo -n Rebuilding Keyspace Graph...
$kvtctl RebuildKeyspaceGraph test_keyspace
echo Done
echo -n Reparenting...
shard_num=1
for shard in $(echo $SHARDS | tr "," " "); do
  $kvtctl InitShardMaster -force test_keyspace/$shard `echo $cells | awk '{print $1}'`-0000000${shard_num}00
  let shard_num=shard_num+1
done
echo Done
echo -n Applying Schema...
$kvtctl ApplySchema -sql "$(cat create_test_table.sql)" test_keyspace
echo Done

echo Creating firewall rule for vtgate
vtgate_port=15001
gcloud compute firewall-rules create ${GKE_CLUSTER_NAME}-vtgate --allow tcp:$vtgate_port
vtgate_pool=`util/get_forwarded_pool.sh $GKE_CLUSTER_NAME $gke_region $vtgate_port`
vtgate_ip=`gcloud compute forwarding-rules list | grep $vtgate_pool | awk '{print $3}'`
vtgate_server="$vtgate_ip:$vtgate_port"

if [ -n "$NEWRELIC_LICENSE_KEY" ]; then
  echo Setting up Newrelic monitoring
  i=1
  for nodename in `$KUBECTL get nodes --no-headers | awk '{print $1}'`; do
    gcloud compute copy-files newrelic.sh $nodename:~/
    gcloud compute copy-files newrelic_start_agent.sh $nodename:~/
    gcloud compute copy-files newrelic_start_mysql_plugin.sh $nodename:~/
    gcloud compute ssh $nodename --command "bash -c '~/newrelic.sh ${NEWRELIC_LICENSE_KEY} ${VTDATAROOT}'"
    let i=i+1
  done
fi

echo "****************************"
echo "* Complete!"
echo "* Use the following line to make an alias to kvtctl:"
echo "* alias kvtctl='\$GOPATH/bin/vtctlclient -server $vtctl_server'"
echo "* vtctld: [http://${vtctl_server}]"
echo "* vtgate: $vtgate_server"
echo "****************************"