This is really an amazing contribution! Thank you Scott.

Jacopo

On Sep 27, 2014, at 11:22 AM, lekt...@apache.org wrote:

> Author: lektran
> Date: Sat Sep 27 09:22:31 2014
> New Revision: 1627940
> 
> URL: http://svn.apache.org/r1627940
> Log:
> OFBIZ-4053 Implement an entity query builder to be used as a friendlier API 
> for executing entity queries.
> 
> Entry point is the static EntityQuery.use(Delegator) method which will then 
> return an EntityQuery instance whose methods support method chaining to set 
> query options.
> The query can then be executed using the first(), list(), iterator() and 
> one() methods which respectively return:
> - The first result from a result set
> - The full list of results from a result set
> - An EntityListIterator to iterate over a result set
> - The single record from a query that will return only one record (such as a 
> lookup by primary key)
> 
> Added:
>    ofbiz/trunk/framework/entity/src/org/ofbiz/entity/util/EntityQuery.java   
> (with props)
> 
> Added: ofbiz/trunk/framework/entity/src/org/ofbiz/entity/util/EntityQuery.java
> URL: 
> http://svn.apache.org/viewvc/ofbiz/trunk/framework/entity/src/org/ofbiz/entity/util/EntityQuery.java?rev=1627940&view=auto
> ==============================================================================
> --- ofbiz/trunk/framework/entity/src/org/ofbiz/entity/util/EntityQuery.java 
> (added)
> +++ ofbiz/trunk/framework/entity/src/org/ofbiz/entity/util/EntityQuery.java 
> Sat Sep 27 09:22:31 2014
> @@ -0,0 +1,416 @@
> +/*******************************************************************************
> + * Licensed to the Apache Software Foundation (ASF) under one
> + * or more contributor license agreements.  See the NOTICE file
> + * distributed with this work for additional information
> + * regarding copyright ownership.  The ASF licenses this file
> + * to you under the Apache License, Version 2.0 (the
> + * "License"); you may not use this file except in compliance
> + * with the License.  You may obtain a copy of the License at
> + *
> + * 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.ofbiz.entity.util;
> +
> +import java.sql.Timestamp;
> +import java.util.Arrays;
> +import java.util.List;
> +import java.util.Map;
> +import java.util.Set;
> +
> +import org.ofbiz.base.util.Debug;
> +import org.ofbiz.base.util.UtilMisc;
> +import org.ofbiz.entity.Delegator;
> +import org.ofbiz.entity.GenericEntityException;
> +import org.ofbiz.entity.GenericValue;
> +import org.ofbiz.entity.condition.EntityCondition;
> +import org.ofbiz.entity.model.DynamicViewEntity;
> +import org.ofbiz.entity.util.EntityFindOptions;
> +import org.ofbiz.entity.util.EntityListIterator;
> +import org.ofbiz.entity.util.EntityUtil;
> +
> +/**
> + * Used to setup various options for and subsequently execute entity queries.
> + *
> + * All methods to set options modify the EntityQuery instance then return 
> this modified object to allow method call chaining. It is
> + * important to note that this object is not immutable and is modified 
> internally, and returning EntityQuery is just a
> + * self reference for convenience.
> + *
> + * After a query the object can be further modified and then used to perform 
> another query if desired.
> + */
> +public class EntityQuery {
> +
> +    public static final String module = EntityQuery.class.getName();
> +
> +    private Delegator delegator;
> +    private String entityName = null;
> +    private DynamicViewEntity dynamicViewEntity = null;
> +    private boolean useCache = false;
> +    private EntityCondition whereEntityCondition = null;
> +    private Set<String> fieldsToSelect = null;
> +    private List<String> orderBy = null;
> +    private Integer resultSetType = EntityFindOptions.TYPE_FORWARD_ONLY;
> +    private Integer fetchSize = null;
> +    private Integer maxRows = null;
> +    private Boolean distinct = null;
> +    private EntityCondition havingEntityCondition = null;
> +    private boolean filterByDate = false;
> +    private Timestamp filterByDateMoment;
> +
> +
> +
> +    /** Construct an EntityQuery object for use against the specified 
> Delegator
> +     * @param delegator - The delegator instance to use for the query
> +     * @return Returns a new EntityQuery object
> +     */
> +    public static EntityQuery use(Delegator delegator) {
> +        return new EntityQuery(delegator);
> +    }
> +
> +    /** Construct an EntityQuery object for use against the specified 
> Delegator
> +     * @param delegator - The delegator instance to use for the query
> +     * @return Returns a new EntityQuery object
> +     */
> +    public EntityQuery(Delegator delegator) {
> +        this.delegator = delegator;
> +    }
> +
> +    /** Set the fields to be returned when the query is executed.
> +     * 
> +     * Note that the select methods are not additive, if a subsequent 
> +     * call is made to select then the existing fields for selection 
> +     * will be replaced.
> +     * @param fieldsToSelect - A Set of Strings containing the field names 
> to be selected
> +     * @return this EntityQuery object, to enable chaining
> +     */
> +    public EntityQuery select(Set<String> fieldsToSelect) {
> +        this.fieldsToSelect = fieldsToSelect;
> +        return this;
> +    }
> +
> +    /** Set the fields to be returned when the query is executed.
> +     * 
> +     * Note that the select methods are not additive, if a subsequent 
> +     * call is made to select then the existing fields for selection 
> +     * will be replaced.
> +     * @param fieldsToSelect - Strings containing the field names to be 
> selected
> +     * @return this EntityQuery object, to enable chaining
> +     */
> +    public EntityQuery select(String...fields) {
> +        this.fieldsToSelect = UtilMisc.toSetArray(fields);
> +        return this;
> +    }
> +
> +    /** Set the entity to query against
> +     * @param entityName - The name of the entity to query against
> +     * @return this EntityQuery object, to enable chaining
> +     */
> +    public EntityQuery from(String entityName) {
> +        this.entityName = entityName;
> +        this.dynamicViewEntity = null;
> +        return this;
> +    }
> +
> +    /** Set the entity to query against
> +     * @param dynamicViewEntity - The DynamicViewEntity object to query 
> against
> +     * @return this EntityQuery object, to enable chaining
> +     */
> +    public EntityQuery from(DynamicViewEntity dynamicViewEntity) {
> +        this.dynamicViewEntity  = dynamicViewEntity;
> +        this.entityName = null;
> +        return this;
> +    }
> +
> +    /** Set the EntityCondition to be used as the WHERE clause for the query
> +     * 
> +     * NOTE: Each successive call to any of the where(...) methods will 
> replace the currently set condition for the query.
> +     * @param entityCondition - An EntityCondition object to be used as the 
> where clause for this query
> +     * @return this EntityQuery object, to enable chaining
> +     */
> +    public EntityQuery where(EntityCondition entityCondition) {
> +        this.whereEntityCondition = entityCondition;
> +        return this;
> +    }
> +
> +    /** Set a Map of field name/values to be ANDed together as the WHERE 
> clause for the query
> +     * 
> +     * NOTE: Each successive call to any of the where(...) methods will 
> replace the currently set condition for the query.
> +     * @param fieldMap - A Map of field names/values to be ANDed together as 
> the where clause for the query
> +     * @return this EntityQuery object, to enable chaining
> +     */
> +    public EntityQuery where(Map<String, Object> fieldMap) {
> +        this.whereEntityCondition = EntityCondition.makeCondition(fieldMap);
> +        return this;
> +    }
> +
> +    /** Set a series of field name/values to be ANDed together as the WHERE 
> clause for the query
> +     * 
> +     * NOTE: Each successive call to any of the where(...) methods will 
> replace the currently set condition for the query.
> +     * @param fieldMap - A series of field names/values to be ANDed together 
> as the where clause for the query
> +     * @return this EntityQuery object, to enable chaining
> +     */
> +    public EntityQuery where(Object...fields) {
> +        this.whereEntityCondition = 
> EntityCondition.makeCondition(UtilMisc.toMap(fields));
> +        return this;
> +    }
> +
> +    /** Set a list of EntityCondition objects to be ANDed together as the 
> WHERE clause for the query
> +     * 
> +     * NOTE: Each successive call to any of the where(...) methods will 
> replace the currently set condition for the query.
> +     * @param fieldMap - A list of EntityCondition objects to be ANDed 
> together as the WHERE clause for the query
> +     * @return this EntityQuery object, to enable chaining
> +     */
> +    public EntityQuery where(List<EntityCondition> andConditions) {
> +        this.whereEntityCondition = 
> EntityCondition.makeCondition(andConditions);
> +        return this;
> +    }
> +
> +    /** Set the EntityCondition to be used as the HAVING clause for the 
> query.
> +     * 
> +     * NOTE: Each successive call to any of the having(...) methods will 
> replace the currently set condition for the query.
> +     * @param entityCondition - The EntityCondition object that specifies 
> how to constrain
> +     *            this query after any groupings are done (if this is a view
> +     *            entity with group-by aliases)
> +     * @return this EntityQuery object, to enable chaining
> +     */
> +    public EntityQuery having(EntityCondition entityCondition) {
> +        this.havingEntityCondition = entityCondition;
> +        return this;
> +    }
> +
> +    /** The fields of the named entity to order the resultset by; optionally 
> add a " ASC" for ascending or " DESC" for descending
> +     * 
> +     * NOTE: Each successive call to any of the orderBy(...) methods will 
> replace the currently set orderBy fields for the query.
> +     * @param orderBy - The fields of the named entity to order the 
> resultset by
> +     * @return this EntityQuery object, to enable chaining
> +     */
> +    public EntityQuery orderBy(List<String> orderBy) {
> +        this.orderBy = orderBy;
> +        return this;
> +    }
> +
> +    /** The fields of the named entity to order the resultset by; optionally 
> add a " ASC" for ascending or " DESC" for descending
> +     * 
> +     * NOTE: Each successive call to any of the orderBy(...) methods will 
> replace the currently set orderBy fields for the query.
> +     * @param orderBy - The fields of the named entity to order the 
> resultset by
> +     * @return this EntityQuery object, to enable chaining
> +     */
> +    public EntityQuery orderBy(String...fields) {
> +        this.orderBy = Arrays.asList(fields);
> +        return this;
> +    }
> +
> +    /** Indicate that the ResultSet object's cursor may move only forward 
> (this is the default behavior)
> +     * 
> +     * @return this EntityQuery object, to enable chaining
> +     */
> +    public EntityQuery cursorForwardOnly() {
> +        this.resultSetType = EntityFindOptions.TYPE_FORWARD_ONLY;
> +        return this;
> +    }
> +
> +    /** Indicate that the ResultSet object's cursor is scrollable but 
> generally sensitive to changes to the data that underlies the ResultSet.
> +     * 
> +     * @return this EntityQuery object, to enable chaining
> +     */
> +    public EntityQuery cursorScrollSensitive() {
> +        this.resultSetType = EntityFindOptions.TYPE_SCROLL_SENSITIVE;
> +        return this;
> +    }
> +
> +    /** Indicate that the ResultSet object's cursor is scrollable but 
> generally not sensitive to changes to the data that underlies the ResultSet.
> +     * 
> +     * @return this EntityQuery object, to enable chaining
> +     */
> +    public EntityQuery cursorScrollInsensitive() {
> +        this.resultSetType = EntityFindOptions.TYPE_SCROLL_INSENSITIVE;
> +        return this;
> +    }
> +
> +    /** Specifies the fetch size for this query. -1 will fall back to 
> datasource settings.
> +     * 
> +     * @param fetchSize - The fetch size for this query
> +     * @return this EntityQuery object, to enable chaining
> +     */
> +    public EntityQuery fetchSize(int fetchSize) {
> +        this.fetchSize = fetchSize;
> +        return this;
> +    }
> +
> +    /** Specifies the max number of rows to return, 0 means all rows.
> +     * 
> +     * @param maxRows - the max number of rows to return
> +     * @return this EntityQuery object, to enable chaining
> +     */
> +    public EntityQuery maxRows(int maxRows) {
> +        this.maxRows = maxRows;
> +        return this;
> +    }
> +
> +    /** Specifies that the values returned should be filtered to remove 
> duplicate values.
> +     * 
> +     * @return this EntityQuery object, to enable chaining
> +     */
> +    public EntityQuery distinct() {
> +        this.distinct = true;
> +        return this;
> +    }
> +
> +    /** Specifies whether the values returned should be filtered to remove 
> duplicate values.
> +     * 
> +     * @param distinct - boolean indicating whether the values returned 
> should be filtered to remove duplicate values
> +     * @return this EntityQuery object, to enable chaining
> +     */
> +    public EntityQuery distinct(boolean distinct) {
> +        this.distinct = distinct;
> +        return this;
> +    }
> +
> +    /** Specifies whether results should be read from the cache (or written 
> to the cache if the results have not yet been cached)
> +     * 
> +     * @return this EntityQuery object, to enable chaining
> +     */
> +    public EntityQuery cache() {
> +        this.useCache = true;
> +        return this;
> +    }
> +
> +    /** Specifies whether the query should return only values that are 
> currently active using from/thruDate fields.
> +     * 
> +     * @return this EntityQuery object, to enable chaining
> +     */
> +    public EntityQuery filterByDate() {
> +        this.filterByDate  = true;
> +        this.filterByDateMoment = null;
> +        return this;
> +    }
> +
> +    /** Specifies whether the query should return only values that are 
> active during the specified moment using from/thruDate fields.
> +     * 
> +     * @param moment - Timestamp representing the moment in time that the 
> values should be active during
> +     * @return this EntityQuery object, to enable chaining
> +     */
> +    public EntityQuery filterByDate(Timestamp moment) {
> +        this.filterByDate = true;
> +        this.filterByDateMoment = moment;
> +        return this;
> +    }
> +
> +    /** Executes the EntityQuery and returns a list of results
> +     * 
> +     * @return Returns a List of GenericValues representing the results of 
> the query
> +     */
> +    public List<GenericValue> queryList() throws GenericEntityException {
> +        return query(null);
> +    }
> +
> +    /** Executes the EntityQuery and returns an EntityListIterator 
> representing the result of the query. 
> +     * 
> +     * NOTE:  THAT THIS MUST BE CLOSED (preferably in a finally block) WHEN 
> YOU
> +     *        ARE DONE WITH IT, AND DON'T LEAVE IT OPEN TOO LONG BEACUSE IT
> +     *        WILL MAINTAIN A DATABASE CONNECTION.
> +     * 
> +     * @return Returns an EntityListIterator representing the result of the 
> query
> +     */
> +    public EntityListIterator queryIterator() throws GenericEntityException {
> +        if (useCache) {
> +            Debug.logWarning("Call to iterator() with cache, ignoring 
> cache", module);
> +        }
> +        if (dynamicViewEntity == null) {
> +            return delegator.find(entityName, makeWhereCondition(false), 
> havingEntityCondition, fieldsToSelect, orderBy, makeEntityFindOptions());
> +        } else {
> +            return delegator.findListIteratorByCondition(dynamicViewEntity, 
> makeWhereCondition(false), havingEntityCondition, fieldsToSelect, orderBy, 
> makeEntityFindOptions());
> +        }
> +    }
> +
> +    /** Executes the EntityQuery and returns the first result
> +     * 
> +     * @return GenericValue representing the first result record from the 
> query
> +     */
> +    public GenericValue queryFirst() throws GenericEntityException {
> +        EntityFindOptions efo = makeEntityFindOptions();
> +        efo.setMaxRows(1);
> +        GenericValue result =  EntityUtil.getFirst(query(efo));
> +        return result;
> +    }
> +
> +    /** Executes the EntityQuery and a single result record
> +     * 
> +     * @return GenericValue representing the only result record from the 
> query
> +     */
> +    public GenericValue queryOne() throws GenericEntityException {
> +        GenericValue result =  EntityUtil.getOnly(queryList());
> +        return result;
> +    }
> +
> +    /** Executes the EntityQuery and returns the result count
> +     * 
> +     * If the query generates more than a single result then an exception is 
> thrown
> +     * 
> +     * @return GenericValue representing the only result record from the 
> query
> +     */
> +    public long queryCount() throws GenericEntityException {
> +        if (dynamicViewEntity != null) {
> +            return queryIterator().getResultsSizeAfterPartialList();
> +        }
> +        return delegator.findCountByCondition(entityName, 
> makeWhereCondition(false), havingEntityCondition, makeEntityFindOptions());
> +    }
> +
> +    private List<GenericValue> query(EntityFindOptions efo) throws 
> GenericEntityException {
> +        EntityFindOptions findOptions = null;
> +        if (efo == null) {
> +            findOptions = makeEntityFindOptions();
> +        } else {
> +            findOptions = efo;
> +        }
> +        List<GenericValue> result = null;
> +        if (dynamicViewEntity == null) {
> +            result = delegator.findList(entityName, 
> makeWhereCondition(useCache), fieldsToSelect, orderBy, findOptions, useCache);
> +        } else {
> +            result = queryIterator().getCompleteList();
> +        }
> +        if (filterByDate && useCache) {
> +            if (filterByDateMoment == null) {
> +                return EntityUtil.filterByDate(result);
> +            } else {
> +                return EntityUtil.filterByDate(result, filterByDateMoment);
> +            }
> +        }
> +        return result;
> +    }
> +
> +    private EntityFindOptions makeEntityFindOptions() {
> +        EntityFindOptions findOptions = new EntityFindOptions();
> +        if (resultSetType != null) {
> +            findOptions.setResultSetType(resultSetType);
> +        }
> +        if (fetchSize != null) {
> +            findOptions.setFetchSize(fetchSize);
> +        }
> +        if (maxRows != null) {
> +            findOptions.setMaxRows(maxRows);
> +        }
> +        if (distinct != null) {
> +            findOptions.setDistinct(distinct);
> +        }
> +        return findOptions;
> +    }
> +
> +    private EntityCondition makeWhereCondition(boolean usingCache) {
> +        // we don't use the useCache field here because not all queries will 
> actually use the cache, e.g. findCountByCondition never uses the cache
> +        if (filterByDate && !usingCache) {
> +            if (filterByDateMoment == null) {
> +                return EntityCondition.makeCondition(whereEntityCondition, 
> EntityUtil.getFilterByDateExpr());
> +            } else {
> +                return EntityCondition.makeCondition(whereEntityCondition, 
> EntityUtil.getFilterByDateExpr(filterByDateMoment));
> +            }
> +        }
> +        return whereEntityCondition;
> +    }
> +}
> 
> Propchange: 
> ofbiz/trunk/framework/entity/src/org/ofbiz/entity/util/EntityQuery.java
> ------------------------------------------------------------------------------
>    svn:eol-style = native
> 
> Propchange: 
> ofbiz/trunk/framework/entity/src/org/ofbiz/entity/util/EntityQuery.java
> ------------------------------------------------------------------------------
>    svn:keywords = "Date Rev Author URL Id"
> 
> Propchange: 
> ofbiz/trunk/framework/entity/src/org/ofbiz/entity/util/EntityQuery.java
> ------------------------------------------------------------------------------
>    svn:mime-type = text/plain
> 
> 

Reply via email to