You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ofbiz.apache.org by le...@apache.org on 2014/09/27 11:22:31 UTC

svn commit: r1627940 - /ofbiz/trunk/framework/entity/src/org/ofbiz/entity/util/EntityQuery.java

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



Re: svn commit: r1627940 - /ofbiz/trunk/framework/entity/src/org/ofbiz/entity/util/EntityQuery.java

Posted by Scott Gray <sc...@hotwaxmedia.com>.
Thanks Jacopo!  I've begun converting the accounting component to use EntityQuery in r1628288, if anyone wants to help please feel free :-)

Regards
Scott

On 27/09/2014, at 9:59 pm, Jacopo Cappellato <ja...@hotwaxmedia.com> wrote:

> This is really an amazing contribution! Thank you Scott.
> 
> Jacopo
> 
> On Sep 27, 2014, at 11:22 AM, lektran@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
>> 
>> 
> 


Re: svn commit: r1627940 - /ofbiz/trunk/framework/entity/src/org/ofbiz/entity/util/EntityQuery.java

Posted by Pranay Pandey <pr...@hotwaxmedia.com>.
+1

Thanks Scott.

Pranay Pandey
HotWax Media
http://www.hotwaxmedia.com



On Sep 27, 2014, at 3:29 PM, Jacopo Cappellato <ja...@hotwaxmedia.com> wrote:

> This is really an amazing contribution! Thank you Scott.
> 
> Jacopo
> 
> On Sep 27, 2014, at 11:22 AM, lektran@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
>> 
>> 
> 


Re: svn commit: r1627940 - /ofbiz/trunk/framework/entity/src/org/ofbiz/entity/util/EntityQuery.java

Posted by Jacopo Cappellato <ja...@hotwaxmedia.com>.
This is really an amazing contribution! Thank you Scott.

Jacopo

On Sep 27, 2014, at 11:22 AM, lektran@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
> 
> 


Re: svn commit: r1627940 - /ofbiz/trunk/framework/entity/src/org/ofbiz/entity/util/EntityQuery.java

Posted by Hans Bakker <ma...@antwebsystems.com>.
Hi Scott,

this is indeed nice, although a duplicate of an existing feature....yes 
every advantage often has a disadvantage. :-)

However, can you please add a junit test? These days in the continuous 
integration age i do not think it is acceptable anymore adding new 
functions to the existing API without Junit test?

Regards,
Hans


On 27/09/14 16:22, lektran@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
>
>