org.grails.datastore.mapping.query.Query.java Source code

Java tutorial

Introduction

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

Source

/* Copyright (C) 2010 SpringSource
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.grails.datastore.mapping.query;

import java.io.Serializable;
import java.util.*;

import javax.persistence.FetchType;
import javax.persistence.FlushModeType;

import org.grails.datastore.mapping.core.ConnectionNotFoundException;
import org.grails.datastore.mapping.model.MappingContext;
import org.grails.datastore.mapping.query.api.AssociationCriteria;
import org.grails.datastore.mapping.query.api.QueryableCriteria;
import org.springframework.dao.InvalidDataAccessResourceUsageException;
import org.grails.datastore.mapping.core.AbstractDatastore;
import org.grails.datastore.mapping.core.Session;
import org.grails.datastore.mapping.core.SessionImplementor;
import org.grails.datastore.mapping.engine.EntityAccess;
import org.grails.datastore.mapping.engine.EntityPersister;
import org.grails.datastore.mapping.model.PersistentEntity;
import org.grails.datastore.mapping.model.PersistentProperty;
import org.grails.datastore.mapping.model.types.Association;
import org.springframework.util.Assert;

/**
 * Models a query that can be executed against a data store
 *
 * @author Graeme Rocher
 * @since 1.0
 */
@SuppressWarnings({ "hiding", "rawtypes", "unchecked" })
public abstract class Query {

    protected PersistentEntity entity;
    protected Junction criteria = new Conjunction();
    protected ProjectionList projections = new ProjectionList();
    protected int max = -1;
    protected int offset = 0;
    protected List<Order> orderBy = new ArrayList<Order>();
    protected Session session;
    protected boolean uniqueResult;
    protected Map<String, FetchType> fetchStrategies = new HashMap<String, FetchType>();
    protected boolean queryCache;

    protected Query(Session session, PersistentEntity entity) {
        this.entity = entity;
        this.session = session;
    }

    /**
     * @return The criteria defined by this query
     */
    public Junction getCriteria() {
        return criteria;
    }

    /**
     * Specifies whether a join query should be used (if join queries are supported by the underlying datastore)
     *
     * @param property The property
     * @return The query
     */
    public Query join(String property) {
        fetchStrategies.put(property, FetchType.EAGER);
        return this;
    }

    /**
     * Specifies whether the query results should be cached (if supported by the underlying datastore)
     *
     * @param cache True if caching should be enabled
     * @return The query
     */
    public Query cache(boolean cache) {
        queryCache = true;
        return this;
    }

    public ProjectionList projections() {
        return projections;
    }

    /**
     * Adds the specified criterion instance to the query
     *
     * @param criterion The criterion instance
     */
    public void add(Criterion criterion) {
        Junction currentJunction = criteria;
        addToJunction(currentJunction, criterion);
    }

    /**
     * @return The session that created the query
     */
    public Session getSession() {
        return session;
    }

    /**
     * @return The PersistentEntity being query
     */
    public PersistentEntity getEntity() {
        return entity;
    }

    /**
     * Creates a disjunction (OR) query
     * @return The Junction instance
     */
    public Junction disjunction() {
        Junction currentJunction = criteria;
        return disjunction(currentJunction);
    }

    /**
     * Creates a disjunction (OR) query
     * @return The Junction instance
     */
    public Junction conjunction() {
        Junction currentJunction = criteria;
        return conjunction(currentJunction);
    }

    /**
     * Creates a negation of several criterion
     * @return The negation
     */
    public Junction negation() {
        Junction currentJunction = criteria;
        return negation(currentJunction);
    }

    private Junction negation(Junction currentJunction) {
        Negation dis = new Negation();
        currentJunction.add(dis);
        return dis;
    }

    /**
     * Defines the maximum number of results to return
     * @param max The max results
     * @return This query instance
     */
    public Query max(int max) {
        this.max = max;
        return this;
    }

    /**
     * Defines the maximum number of results to return
     * @param max The max results
     * @return This query instance
     */
    public Query maxResults(int max) {
        return max(max);
    }

    /**
     * Defines the offset (the first result index) of the query
     * @param offset The offset
     * @return This query instance
     */
    public Query offset(int offset) {
        this.offset = offset;
        return this;
    }

    /**
     * Defines the offset (the first result index) of the query
     * @param offset The offset
     * @return This query instance
     */
    public Query firstResult(int offset) {
        return offset(offset);
    }

    /**
     * Specifies the order of results
     * @param order The order object
     * @return The Query instance
     */
    public Query order(Order order) {
        if (order != null) {
            orderBy.add(order);
        }
        return this;
    }

    /**
     * Gets the Order entries for this query
     * @return The order entries
     */
    public List<Order> getOrderBy() {
        return orderBy;
    }

    /**
     * Restricts the results by the given properties value
     *
     * @param property The name of the property
     * @param value The value to restrict by
     * @return This query instance
     */
    public Query eq(String property, Object value) {
        Object resolved = resolveIdIfEntity(value);
        if (resolved == value) {
            criteria.add(Restrictions.eq(property, value));
        } else {
            criteria.add(Restrictions.eq(property, resolved));
        }
        return this;
    }

    /**
     * Shortcut to restrict the query to multiple given property values
     *
     * @param values The values
     * @return This query instance
     */
    public Query allEq(Map<String, Object> values) {
        for (String property : values.keySet()) {
            eq(property, values.get(property));
        }
        return this;
    }

    /**
     * Used to restrict a value to be empty (such as a blank string or an empty collection)
     *
     * @param property The property name
    */
    public Query isEmpty(String property) {
        criteria.add(Restrictions.isEmpty(property));
        return this;
    }

    /**
     * Used to restrict a value to be not empty (such as a blank string or an empty collection)
     *
     * @param property The property name
    */
    public Query isNotEmpty(String property) {
        criteria.add(Restrictions.isNotEmpty(property));
        return this;
    }

    /**
     * Used to restrict a property to be null
     *
     * @param property The property name
    */
    public Query isNull(String property) {
        criteria.add(Restrictions.isNull(property));
        return this;
    }

    /**
     * Used to restrict a property to be not null
     *
     * @param property The property name
    */
    public Query isNotNull(String property) {
        criteria.add(Restrictions.isNotNull(property));
        return this;
    }

    /**
     * Creates an association query
     *
     * @param associationName The assocation name
     * @return The Query instance
     */
    public AssociationQuery createQuery(String associationName) {
        final PersistentProperty property = entity.getPropertyByName(associationName);
        if (property == null || !(property instanceof Association)) {
            throw new InvalidDataAccessResourceUsageException("Cannot query association [" + associationName
                    + "] of class [" + entity + "]. The specified property is not an association.");
        }

        Association association = (Association) property;

        final PersistentEntity associatedEntity = association.getAssociatedEntity();

        return new AssociationQuery(session, associatedEntity, association);
    }

    /**
     * Restricts the results by the given properties value
     *
     * @param value The value to restrict by
     * @return This query instance
     */
    public Query idEq(Object value) {
        value = resolveIdIfEntity(value);

        criteria.add(Restrictions.idEq(value));
        return this;
    }

    /**
     * Used to restrict a value to be greater than the given value
     *
     * @param property The name of the property
     * @param value The value to restrict by
     * @return This query instance
     */
    public Query gt(String property, Object value) {
        criteria.add(Restrictions.gt(property, value));
        return this;
    }

    /**
     * Used to restrict a value to be greater than or equal to the given value
     *
     * @param property The name of the property
     * @param value The value to restrict by
     * @return This query instance
     */
    public Query gte(String property, Object value) {
        criteria.add(Restrictions.gte(property, value));
        return this;
    }

    /**
     * Used to restrict a value to be less than or equal to the given value
     *
     * @param property The name of the property
     * @param value The value to restrict by
     * @return This query instance
     */
    public Query lte(String property, Object value) {
        criteria.add(Restrictions.lte(property, value));
        return this;
    }

    /**
     * Used to restrict a value to be greater than or equal to the given value
     *
     * @param property The name of the property
     * @param value The value to restrict by
     * @return This query instance
     */
    public Query ge(String property, Object value) {
        return gte(property, value);
    }

    /**
     * Used to restrict a value to be less than or equal to the given value
     *
     * @param property The name of the property
     * @param value The value to restrict by
     * @return This query instance
     */
    public Query le(String property, Object value) {
        return lte(property, value);
    }

    /**
     * Used to restrict a value to be less than the given value
     *
     * @param property The name of the property
     * @param value The value to restrict by
     * @return This query instance
     */
    public Query lt(String property, Object value) {
        criteria.add(Restrictions.lt(property, value));
        return this;
    }

    /**
     * Restricts the results by the given property values
     *
     * @param property The name of the property
     * @param values The values to restrict by
     * @return This query instance
     */
    public Query in(String property, List values) {
        criteria.add(Restrictions.in(property, values));
        return this;
    }

    /**
     * Restricts the results by the given property value range
     *
     * @param property The name of the property
     * @param start The start of the range
     * @param end The end of the range
     * @return This query instance
     */
    public Query between(String property, Object start, Object end) {
        criteria.add(Restrictions.between(property, start, end));
        return this;
    }

    /**
     * Restricts the results by the given properties value
     *
     * @param property The name of the property
     * @param expr The expression to restrict by
     * @return This query instance
     */
    public Query like(String property, String expr) {
        criteria.add(Restrictions.like(property, expr));
        return this;
    }

    /**
     * Restricts the results by the given properties value
     *
     * @param property The name of the property
     * @param expr The expression to restrict by
     * @return This query instance
     */
    public Query ilike(String property, String expr) {
        criteria.add(Restrictions.ilike(property, expr));
        return this;
    }

    /**
     * Restricts the results by the given properties value
     *
     * @param property The name of the property
     * @param expr The expression to restrict by
     * @return This query instance
     */
    public Query rlike(String property, String expr) {
        criteria.add(Restrictions.rlike(property, expr));
        return this;
    }

    /**
     * Creates a conjunction using two specified criterion
     *
     * @param a The left hand side
     * @param b The right hand side
     * @return This query instance
     */
    public Query and(Criterion a, Criterion b) {
        Assert.notNull(a, "Left hand side of AND cannot be null");
        Assert.notNull(b, "Right hand side of AND cannot be null");
        criteria.add(Restrictions.and(a, b));
        return this;
    }

    /**
     * Creates a disjunction using two specified criterion
     *
     * @param a The left hand side
     * @param b The right hand side
     * @return This query instance
     */
    public Query or(Criterion a, Criterion b) {
        Assert.notNull(a, "Left hand side of AND cannot be null");
        Assert.notNull(b, "Right hand side of AND cannot be null");
        criteria.add(Restrictions.or(a, b));
        return this;
    }

    /**
     * Executes the query returning zero or many results as a list.
     *
     * @return The results
     */
    public List list() {
        uniqueResult = false;
        flushBeforeQuery();

        List results = executeQuery(entity, criteria);

        if (session instanceof SessionImplementor) {
            SessionImplementor sessionImplementor = (SessionImplementor) session;
            for (ListIterator iter = results.listIterator(); iter.hasNext();) {
                Object instance = iter.next();
                EntityPersister ep = (EntityPersister) session.getPersister(instance);
                if (ep == null) {
                    // not persistent, could be a count() or report query
                    continue;
                }

                Serializable id = findInstanceId(instance);
                if (sessionImplementor.isCached(instance.getClass(), id)) {
                    iter.set(sessionImplementor.getCachedInstance(instance.getClass(), id));
                } else {
                    sessionImplementor.cacheInstance(instance.getClass(), id, instance);
                }
            }
        }

        return results;
    }

    /**
     * Executes the query returning a single result or null
     * @return The result
     */
    public Object singleResult() {
        uniqueResult = true;
        List results = list();
        return results.isEmpty() ? null : results.get(0);
    }

    /**
     * Here purely for compatibility
     *
     * @param uniqueResult Whether it is a unique result
     * @deprecated
     */
    @Deprecated
    public void setUniqueResult(boolean uniqueResult) {
        this.uniqueResult = uniqueResult;
    }

    /**
     * Subclasses should implement this to provide the concrete implementation
     * of querying
     *
     * @param entity The entity
     * @param criteria The criteria
     * @return The results
     */
    protected abstract List executeQuery(PersistentEntity entity, Junction criteria);

    protected Object resolveIdIfEntity(Object value) {
        // use the object id as the value if its a persistent entity
        MappingContext mappingContext = session.getMappingContext();
        if (mappingContext.getProxyFactory().isProxy(value)) {
            return mappingContext.getProxyFactory().getIdentifier(value);
        } else {
            return mappingContext.isPersistentEntity(value) ? findInstanceId(value) : value;
        }
    }

    private Serializable findInstanceId(Object value) {
        EntityPersister ep = (EntityPersister) session.getPersister(value);
        if (ep != null) {
            return ep.getObjectIdentifier(value);
        }

        return (Serializable) new EntityAccess(
                session.getMappingContext().getPersistentEntity(value.getClass().getName()), value).getIdentifier();
    }

    private Junction disjunction(Junction currentJunction) {
        Disjunction dis = new Disjunction();
        currentJunction.add(dis);
        return dis;
    }

    private Junction conjunction(Junction currentJunction) {
        Conjunction dis = new Conjunction();
        currentJunction.add(dis);
        return dis;
    }

    /**
     * Default behavior is the flush the session before a query in the case of FlushModeType.AUTO.
     * Subclasses can override this method to disable that.
     */
    protected void flushBeforeQuery() {
        // flush before query execution in FlushModeType.AUTO
        if (session.getFlushMode() == FlushModeType.AUTO) {
            session.flush();
        }
    }

    /**
     * A criterion is used to restrict the results of a query
     */
    private void addToJunction(Junction currentJunction, Criterion criterion) {
        if (criterion instanceof PropertyCriterion) {
            final PropertyCriterion pc = (PropertyCriterion) criterion;
            Object value = resolveIdIfEntity(pc.getValue());
            pc.setValue(value);
        }
        if (criterion instanceof AssociationCriteria) {
            AssociationCriteria ac = (AssociationCriteria) criterion;
            AssociationQuery associationQuery = createQuery(ac.getAssociation().getName());
            for (Criterion associationCriterion : ac.getCriteria()) {
                associationQuery.add(associationCriterion);
            }
            currentJunction.add(associationQuery);
        } else if (criterion instanceof Junction) {
            Junction j = (Junction) criterion;
            Junction newj;
            if (j instanceof Disjunction) {
                newj = disjunction(currentJunction);
            } else if (j instanceof Negation) {
                newj = negation(currentJunction);
            } else {
                newj = conjunction(currentJunction);
            }
            for (Criterion c : j.getCriteria()) {
                addToJunction(newj, c);
            }
        } else {
            currentJunction.add(criterion);
        }
    }

    public static interface Criterion {

    }

    /**
     * The ordering of results.
     */
    public static class Order {
        private Direction direction = Direction.ASC;
        private String property;

        public Order(String property) {
            this.property = property;
        }

        public Order(String property, Direction direction) {
            this.direction = direction;
            this.property = property;
        }

        public Direction getDirection() {
            return direction;
        }

        public String getProperty() {
            return property;
        }

        public static Order desc(String property) {
            return new Order(property, Direction.DESC);
        }

        public static Order asc(String property) {
            return new Order(property, Direction.ASC);
        }

        public static enum Direction {
            ASC, DESC
        }
    }

    /**
     * Restricts a property to be null
     */
    public static class IsNull extends PropertyNameCriterion {
        public IsNull(String name) {
            super(name);
        }
    }

    /**
     * Restricts a property to be empty (such as a blank string)
     */
    public static class IsEmpty extends PropertyNameCriterion {
        public IsEmpty(String name) {
            super(name);
        }
    }

    /**
     * Restricts a property to be empty (such as a blank string)
     */
    public static class IsNotEmpty extends PropertyNameCriterion {
        public IsNotEmpty(String name) {
            super(name);
        }
    }

    /**
     * Restricts a property to be not null
     */
    public static class IsNotNull extends PropertyNameCriterion {
        public IsNotNull(String name) {
            super(name);
        }
    }

    /**
     * A Criterion that applies to a property
     */
    public static class PropertyNameCriterion implements Criterion {
        protected String name;

        public PropertyNameCriterion(String name) {
            this.name = name;
        }

        public String getProperty() {
            return name;
        }
    }

    /**
     * A Criterion that compares to properties
     */
    public static class PropertyComparisonCriterion extends PropertyNameCriterion {
        protected String otherProperty;

        public PropertyComparisonCriterion(String property, String otherProperty) {
            super(property);
            this.otherProperty = otherProperty;
        }

        public String getOtherProperty() {
            return otherProperty;
        }
    }

    public static class EqualsProperty extends PropertyComparisonCriterion {
        public EqualsProperty(String property, String otherProperty) {
            super(property, otherProperty);
        }
    }

    public static class NotEqualsProperty extends PropertyComparisonCriterion {
        public NotEqualsProperty(String property, String otherProperty) {
            super(property, otherProperty);
        }
    }

    public static class GreaterThanProperty extends PropertyComparisonCriterion {
        public GreaterThanProperty(String property, String otherProperty) {
            super(property, otherProperty);
        }
    }

    public static class GreaterThanEqualsProperty extends PropertyComparisonCriterion {
        public GreaterThanEqualsProperty(String property, String otherProperty) {
            super(property, otherProperty);
        }
    }

    public static class LessThanProperty extends PropertyComparisonCriterion {
        public LessThanProperty(String property, String otherProperty) {
            super(property, otherProperty);
        }
    }

    public static class LessThanEqualsProperty extends PropertyComparisonCriterion {
        public LessThanEqualsProperty(String property, String otherProperty) {
            super(property, otherProperty);
        }
    }

    /**
     * Criterion that applies to a property and value
     */
    public static class PropertyCriterion extends PropertyNameCriterion {

        protected Object value;

        public PropertyCriterion(String name, Object value) {
            super(name);
            this.value = value;
        }

        public Object getValue() {
            return value;
        }

        public void setValue(Object v) {
            this.value = v;
        }
    }

    /**
     * Used to differentiate criterion that require a subquery
     */
    public static class SubqueryCriterion extends PropertyCriterion {
        public SubqueryCriterion(String name, QueryableCriteria value) {
            super(name, value);
        }
    }

    public static class EqualsAll extends SubqueryCriterion {
        public EqualsAll(String name, QueryableCriteria value) {
            super(name, value);
        }
    }

    public static class NotEqualsAll extends SubqueryCriterion {
        public NotEqualsAll(String name, QueryableCriteria value) {
            super(name, value);
        }
    }

    public static class GreaterThanAll extends SubqueryCriterion {
        public GreaterThanAll(String name, QueryableCriteria value) {
            super(name, value);
        }
    }

    public static class LessThanAll extends SubqueryCriterion {
        public LessThanAll(String name, QueryableCriteria value) {
            super(name, value);
        }
    }

    public static class GreaterThanEqualsAll extends SubqueryCriterion {
        public GreaterThanEqualsAll(String name, QueryableCriteria value) {
            super(name, value);
        }
    }

    public static class LessThanEqualsAll extends SubqueryCriterion {
        public LessThanEqualsAll(String name, QueryableCriteria value) {
            super(name, value);
        }
    }

    /**
     * A criterion that restricts the results based on equality
     */
    public static class Equals extends PropertyCriterion {

        public Equals(String name, Object value) {
            super(name, value);
        }

        @Override
        public void setValue(Object value) {
            this.value = value;
        }
    }

    public static class SizeEquals extends PropertyCriterion {
        public SizeEquals(String name, int value) {
            super(name, value);
        }
    }

    public static class SizeNotEquals extends PropertyCriterion {
        public SizeNotEquals(String name, int value) {
            super(name, value);
        }
    }

    public static class SizeGreaterThan extends PropertyCriterion {
        public SizeGreaterThan(String name, int value) {
            super(name, value);
        }
    }

    public static class SizeGreaterThanEquals extends PropertyCriterion {
        public SizeGreaterThanEquals(String name, int value) {
            super(name, value);
        }
    }

    public static class SizeLessThanEquals extends PropertyCriterion {
        public SizeLessThanEquals(String name, int value) {
            super(name, value);
        }
    }

    public static class SizeLessThan extends PropertyCriterion {
        public SizeLessThan(String name, int value) {
            super(name, value);
        }
    }

    /**
     * A criterion that restricts the results based on the equality of the identifier
     */
    public static class IdEquals extends PropertyCriterion {

        private static final String ID = "id";

        public IdEquals(Object value) {
            super(ID, value);
        }

        @Override
        public void setValue(Object value) {
            this.value = value;
        }
    }

    /**
     * A criterion that restricts the results based on equality
     */
    public static class NotEquals extends PropertyCriterion {

        public NotEquals(String name, Object value) {
            super(name, value);
        }

        @Override
        public void setValue(Object value) {
            this.value = value;
        }
    }

    /**
     * Criterion used to restrict the results based on a list of values
     */
    public static class In extends PropertyCriterion {
        private String name;
        private Collection values = Collections.emptyList();

        public In(String name, Collection values) {
            super(name, values);
            this.name = name;
            this.values = values;
        }

        public String getName() {
            return name;
        }

        @Override
        public String getProperty() {
            return getName();
        }

        public Collection getValues() {
            return Collections.unmodifiableCollection(values);
        }
    }

    /**
     * Used to restrict a value to be greater than the given value
     */
    public static class GreaterThan extends PropertyCriterion {
        public GreaterThan(String name, Object value) {
            super(name, value);
        }
    }

    /**
     * Used to restrict a value to be greater than or equal to the given value
     */
    public static class GreaterThanEquals extends PropertyCriterion {
        public GreaterThanEquals(String name, Object value) {
            super(name, value);
        }
    }

    /**
     * Used to restrict a value to be less than the given value
     */
    public static class LessThan extends PropertyCriterion {
        public LessThan(String name, Object value) {
            super(name, value);
        }
    }

    /**
     * Used to restrict a value to be less than the given value
     */
    public static class LessThanEquals extends PropertyCriterion {
        public LessThanEquals(String name, Object value) {
            super(name, value);
        }
    }

    /**
     * Criterion used to restrict the result to be between values (range query)
     */
    public static class Between extends PropertyCriterion {
        private String property;
        private Object from;
        private Object to;

        public Between(String property, Object from, Object to) {
            super(property, from);
            this.property = property;
            this.from = from;
            this.to = to;
        }

        @Override
        public String getProperty() {
            return property;
        }

        public Object getFrom() {
            return from;
        }

        public Object getTo() {
            return to;
        }
    }

    /**
     * Criterion used to restrict the results based on a pattern (likeness)
     */
    public static class Like extends PropertyCriterion {
        public Like(String name, String expression) {
            super(name, expression);
        }

        public String getPattern() {
            return getValue().toString();
        }
    }

    /**
     * Criterion used to restrict the results based on a pattern (likeness)
     */
    public static class ILike extends Like {
        public ILike(String name, String expression) {
            super(name, expression);
        }
    }

    /**
     * Criterion used to restrict the results based on a regular expression pattern
     */
    public static class RLike extends Like {
        public RLike(String name, String expression) {
            super(name, expression);
        }

        @Override
        public String getPattern() {
            return getValue().toString();
        }
    }

    public static abstract class Junction implements Criterion {
        private List<Criterion> criteria = new ArrayList<Criterion>();

        protected Junction() {
        }

        public Junction(List<Criterion> criteria) {
            this.criteria = criteria;
        }

        public Junction add(Criterion c) {
            if (c != null) {
                if (c instanceof Equals) {
                    final Equals eq = (Equals) c;
                    Object value = eq.getValue();

                    if (value != null) {

                        try {
                            Session session = AbstractDatastore.retrieveSession();
                            final PersistentEntity persistentEntity = session.getMappingContext()
                                    .getPersistentEntity(value.getClass().getName());
                            if (persistentEntity != null) {
                                EntityPersister ep = (EntityPersister) session.getPersister(value);

                                if (ep != null) {
                                    c = new Equals(eq.getProperty(), ep.getObjectIdentifier(value));
                                }
                            }
                        } catch (ConnectionNotFoundException e) {
                            // continue, use original value
                        }
                    }
                }
                criteria.add(c);
            }
            return this;
        }

        public List<Criterion> getCriteria() {
            return criteria;
        }

        public boolean isEmpty() {
            return criteria.isEmpty();
        }
    }

    /**
     * A Criterion used to combine to criterion in a logical AND
     */
    public static class Conjunction extends Junction {
        public Conjunction() {
        }

        public Conjunction(List<Criterion> criteria) {
            super(criteria);
        }
    }

    /**
     * A Criterion used to combine to criterion in a logical OR
     */
    public static class Disjunction extends Junction {
        public Disjunction() {
        }

        public Disjunction(List<Criterion> criteria) {
            super(criteria);
        }
    }

    /**
     * A criterion used to negate several other criterion
     */
    public static class Negation extends Junction {
    }

    /**
     * A projection
     */
    public static class Projection {
    }

    /**
     * A projection used to obtain the identifier of an object
     */
    public static class IdProjection extends Projection {
    }

    /**
     * Used to count the results of a query
     */
    public static class CountProjection extends Projection {
    }

    public static class DistinctProjection extends Projection {
    }

    /**
     * A projection that obtains the value of a property of an entity
     */
    public static class PropertyProjection extends Projection {
        private String propertyName;

        protected PropertyProjection(String propertyName) {
            this.propertyName = propertyName;
        }

        public String getPropertyName() {
            return propertyName;
        }
    }

    public static class DistinctPropertyProjection extends PropertyProjection {
        protected DistinctPropertyProjection(String propertyName) {
            super(propertyName);
        }
    }

    public static class CountDistinctProjection extends PropertyProjection {
        public CountDistinctProjection(String property) {
            super(property);
        }
    }

    /**
     * Computes the average value of a property
     */
    public static class AvgProjection extends PropertyProjection {
        protected AvgProjection(String propertyName) {
            super(propertyName);
        }
    }

    /**
     * Computes the max value of a property
     */
    public static class MaxProjection extends PropertyProjection {
        protected MaxProjection(String propertyName) {
            super(propertyName);
        }
    }

    /**
     * Computes the min value of a property
     */
    public static class MinProjection extends PropertyProjection {
        protected MinProjection(String propertyName) {
            super(propertyName);
        }
    }

    /**
     * Computes the sum of a property
     */
    public static class SumProjection extends PropertyProjection {
        protected SumProjection(String propertyName) {
            super(propertyName);
        }
    }

    /**
     * A list of projections
     */
    public static class ProjectionList implements org.grails.datastore.mapping.query.api.ProjectionList {

        private List<Projection> projections = new ArrayList();

        public List<Projection> getProjectionList() {
            return Collections.unmodifiableList(projections);
        }

        public ProjectionList add(Projection p) {
            projections.add(p);
            return this;
        }

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

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

        public org.grails.datastore.mapping.query.api.ProjectionList countDistinct(String property) {
            add(Projections.countDistinct(property));
            return this;
        }

        public boolean isEmpty() {
            return projections.isEmpty();
        }

        public ProjectionList distinct() {
            return this;
        }

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

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

        /**
         * A projection that obtains the value of a property of an entity
         * @param name The name of the property
         * @return The PropertyProjection instance
         */
        public org.grails.datastore.mapping.query.api.ProjectionList property(String name) {
            add(Projections.property(name));
            return this;
        }

        /**
         * Computes the sum of a property
         *
         * @param name The name of the property
         * @return The PropertyProjection instance
         */
        public org.grails.datastore.mapping.query.api.ProjectionList sum(String name) {
            add(Projections.sum(name));
            return this;
        }

        /**
         * Computes the min value of a property
         *
         * @param name The name of the property
         * @return The PropertyProjection instance
         */
        public org.grails.datastore.mapping.query.api.ProjectionList min(String name) {
            add(Projections.min(name));
            return this;
        }

        /**
         * Computes the max value of a property
         *
         * @param name The name of the property
         * @return The PropertyProjection instance
         */
        public org.grails.datastore.mapping.query.api.ProjectionList max(String name) {
            add(Projections.max(name));
            return this;
        }

        /**
         * Computes the average value of a property
         *
         * @param name The name of the property
         * @return The PropertyProjection instance
         */
        public org.grails.datastore.mapping.query.api.ProjectionList avg(String name) {
            add(Projections.avg(name));
            return this;
        }
    }

}