Author: fdesbois Date: 2009-12-29 13:12:40 +0100 (Tue, 29 Dec 2009) New Revision: 1741 Added: trunk/topia-persistence/src/main/java/org/nuiton/topia/framework/TopiaQuery.java Modified: trunk/topia-persistence/src/main/java/org/nuiton/topia/persistence/TopiaDAOImpl.java Log: Evol #178 : TopiaQuery + use it for count in TopiaDAO Added: trunk/topia-persistence/src/main/java/org/nuiton/topia/framework/TopiaQuery.java =================================================================== --- trunk/topia-persistence/src/main/java/org/nuiton/topia/framework/TopiaQuery.java (rev 0) +++ trunk/topia-persistence/src/main/java/org/nuiton/topia/framework/TopiaQuery.java 2009-12-29 12:12:40 UTC (rev 1741) @@ -0,0 +1,688 @@ +/* + * *##% + * ToPIA :: Persistence + * Copyright (C) 2004 - 2009 CodeLutin + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Lesser Public License for more details. + * + * You should have received a copy of the GNU General Lesser Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/lgpl-3.0.html>. + * ##%* + */ + +package org.nuiton.topia.framework; + +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import org.apache.commons.beanutils.PropertyUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.nuiton.topia.TopiaContext; +import org.nuiton.topia.TopiaException; +import org.nuiton.topia.persistence.TopiaEntity; + +/** + * Query HQL managment to simplify utilisation of {@link TopiaContext#find(java.lang.String, java.lang.Object[]) }. + * + * TODO-FD20091224 Complete documentation of this class + JUnit Tests + * + * Created: 21 déc. 2009 + * + * @author fdesbois + * @version $Revision$ + * + * Mise a jour: $Date$ + * par : $Author$ + */ +public class TopiaQuery { + + private static final Log log = LogFactory.getLog(TopiaQuery.class); + + /** Params for HQL query **/ + protected List<Object> params; + + /** Select part of the query **/ + protected String select; + + /** From part of the query **/ + protected String from; + + /** Where part of the query **/ + protected String where; + + /** Order By part of the query **/ + protected String orderBy; + + /** Group By part of the query **/ + protected String groupBy; + + protected Integer startIndex; + + protected Integer endIndex; + + /** Used to determine if parentheses are needed for Where input **/ + protected boolean parentheses; + + protected List<String> propertiesToLoad; + + protected Class<? extends TopiaEntity> mainEntityClass; + + /** + * Enum to simmplify using operation in query + */ + public static enum Op { + /** EQUALS **/ + EQ("="), + /** GREATER THAN **/ + GT(">"), + /** GREATER OR EQUALS **/ + GE(">="), + /** LIKE for String manipulation **/ + LIKE("LIKE"), + /** LESS THAN **/ + LT("<"), + /** LESS OR EQUALS **/ + LE("<="), + /** IS NOT NULL **/ + NOT_NULL("IS NOT NULL"); + + protected String value; + + /** + * Constructor of the Op Enum. + * + * @param value corresponding to the String for the query + */ + Op(String value) { + this.value = value; + } + + @Override + public String toString() { + return this.value; + } + } + + /** + * Constructor of TopiaQuery with entityClass initialization. + * + * @param entityClass Class for an entity Query + */ + public TopiaQuery(Class<? extends TopiaEntity> entityClass) { + this(entityClass.getName()); + this.mainEntityClass = entityClass; + } + + /** + * Constructor of TopiaQuery with String form initialization. + * + * @param from From part for the Query + */ + public TopiaQuery(String from) { + this.from = " FROM " + from; + parentheses = true; + } + + @Override + public String toString() { + return fullQuery(); + } + + /** + * Get the full query. + * + * @return a String corresponding to the full query. + */ + public String fullQuery() { + String result = ""; + if (select != null) { + result = select; + } + result += from; + if (where != null) { + result += where; + } + if (groupBy != null) { + result += groupBy; + } + if (orderBy != null) { + result += orderBy; + } + return result.trim(); + } + + /** + * Add a HQL parameter to the Query. + * + * @param id identification of the param in the query + * @param e value of the param + * @return the TopiaQuery + */ + public TopiaQuery addParam(String id, Object e) { + getParams().add(id); + getParams().add(e); + return this; + } + + protected List<Object> getParams() { + if (params == null) { + params = new ArrayList<Object>(); + } + return params; + } + + /** + * Add a property to load when query is executed. + * Used to avoid LazyInitializationException for property needed after closing context. + * The property is a string like those in HQL query. + * <pre> + * Exemples : + * - "person.company" (Property TopiaEntity person linked to the result entity in query and company linked to person) + * --> calling myEntity.getPerson().getCompany(); + * - "partyRoles" (Property Collection partyRoles linked to the result entity in query) + * --> calling myEntity.getPartyRoles().size(); + * </pre> + * + * @param properties + * @return + */ + public TopiaQuery addLoad(String... properties) { + getPropertiesToLoad().addAll(Arrays.asList(properties)); + return this; + } + + protected List<String> getPropertiesToLoad() { + if (propertiesToLoad == null) { + propertiesToLoad = new ArrayList<String>(); + } + return propertiesToLoad; + } + + /** + * Add a where element to the Query. Could be anything. + * Parentheses are added automatically (even if there are not needed). + * + * @param where element to add + * @return the TopiaQuery + */ + public TopiaQuery add(String where) { + if (this.where == null) { + this.where = " WHERE "; + } else { + this.where += " AND "; + } + if (parentheses) { + this.where += "("; + } + this.where += where; + if (parentheses) { + this.where += ")"; + } + parentheses = true; + return this; + } + + /** + * Add an element to the query. The parameter will be automatically added. + * The constraint is needed to determine what type of operation it is. + * + * @param paramName the name of the parameter in the query (attribute of the entity) + * @param constraint the operation concerned + * @param paramValue the value of the parameter (an other entity, a String, ...) + * @return the TopiaQuery + */ + public TopiaQuery add(String paramName, Op constraint, Object paramValue) { + int dot = paramName.lastIndexOf("."); + String valueName = paramName; + if (dot != -1) { + valueName = paramName.substring(dot+1); + } + parentheses = false; + return add(paramName + " " + constraint + " :" + valueName).addParam(valueName, paramValue); + } + + /** + * Add an element to the query with the constraint Not null. + * + * @param paramName name of the parameter in the query + * @return the TopiaQuery + */ + public TopiaQuery addNotNull(String paramName) { + return add(paramName + " " + Op.NOT_NULL); + } + + /** + * Add an element to the query. The parameter will be automatically added. + * The default constrainst operation is Op.EQ for EQUALS. + * Ex : add("boat", boat) means -> boat = :boat. + * + * @param paramName name of the parameter in the query + * @param paramValue value of the parameter + * @return the TopiaQuery + * @see #add(java.lang.String, fr.ifremer.suiviobsmer.TopiaQuery.Op, java.lang.Object) + */ + public TopiaQuery add(String paramName, Object paramValue) { + return add(paramName, Op.EQ, paramValue); + } + + /** + * Add an element to the from in the query. Used to add some other data in the query or for join. + * + * @param str the element to add + * @return the TopiaQuery + */ + public TopiaQuery addFrom(String str) { + from += ", " + str; + return this; + } + + /** + * Add an element to the select in the query. Used to add some parameters for the return of query. + * + * @param select element to add + * @return the TopiaQuery + */ + public TopiaQuery addSelect(String select) { + if (this.select == null) { + this.select = "SELECT "; + } else { + this.select += ", "; + } + this.select += select; + return this; + } + + /** + * Add an element to the order in the query. Used to add some parameters to order by. + * + * @param order element to add + * @return the TopiaQuery + */ + public TopiaQuery addOrder(String order) { + if (orderBy == null) { + orderBy = " ORDER BY "; + } else { + orderBy += ", "; + } + orderBy += order; + return this; + } + + public TopiaQuery addOrderDesc(String order) { + return addOrder(order + " DESC"); + } + + /** + * Add an element to the group of the query. Used to add some paramters to group by. + * + * @param group element to add + * @return the TopiaQuery + */ + public TopiaQuery addGroup(String group) { + if (groupBy == null) { + groupBy = " GROUP BY "; + } else { + groupBy += ", "; + } + groupBy += group; + return this; + } + + /** + * Limit the result of the query with startIndex and endIndex. + * + * @param start first index to get from the results + * @param end last index to get from the results + * @return the TopiaQuery + */ + public TopiaQuery setLimit(int start, int end) { + this.startIndex = start; + this.endIndex = end; + return this; + } + + /** + * Set the max results wanted for the query. + * + * @param max the number of elements wanted + * @return the TopiaQuery + */ + public TopiaQuery setMaxResults(int max) { + return setLimit(0,max-1); + } + + /** + * Simple execution of the query. This method use directly the find method in TopiaContext interface. + * + * @param transaction the TopiaContext to use for execution + * @return a List of results + * @throws TopiaException + * @see org.nuiton.topia.TopiaContext#find(java.lang.String, java.lang.Object[]) + */ + public List execute(TopiaContext transaction) throws TopiaException { + if (log.isTraceEnabled()) { + log.trace("# QUERY : " + fullQuery()); + log.trace("# PARAMS : " + Arrays.toString(params.toArray())); + } + if (startIndex != null && endIndex != null) { + return transaction.find(fullQuery(), startIndex, endIndex, getParams().toArray()); + } + return transaction.find(fullQuery(), getParams().toArray()); + } + + /** + * Execute the query and get a List of entity. Some properties will be loaded if they are + * prealably set using ${@link #addLoad(java.lang.String[]) }. + * + * @param <T> the type of TopiaEntity to return + * @param transaction the TopiaContext to use for execution + * @param entityClass the class of the TopiaEntity used for return type + * @return a List of TopiaEntity corresponding to the entityClass in argument + * @throws TopiaException + * @throws ClassCastException + */ + public <T extends TopiaEntity> List<T> executeToEntityList(TopiaContext transaction, Class<T> entityClass) + throws TopiaException, ClassCastException { + List res = execute(transaction); + if (log.isTraceEnabled()) { + log.trace("Properties to load : " + getPropertiesToLoad()); + } + List<T> results = new ArrayList<T>(); + for (Object o : res) { + if (o != null && !entityClass.isAssignableFrom(o.getClass())) { + throw new ClassCastException("Invalid cast for " + entityClass.getName()); + } + T entity = (T)o; + if (!getPropertiesToLoad().isEmpty()) { + loadProperties(entity); + } + results.add(entity); + } + return results; + } + + /** + * {@link #executeToEntity(org.nuiton.topia.TopiaContext, java.lang.Class) } with default entityClass set from constructor. + * + * @param <T> + * @param transaction + * @return the TopiaEntity from entityClass set from ${@link #TopiaQuery(java.lang.Class) } constructor + * @throws TopiaException + * @throws ClassCastException + */ + public <T extends TopiaEntity> List<T> executeToEntityList(TopiaContext transaction) throws TopiaException, ClassCastException { + if (this.mainEntityClass == null) { + throw new NullPointerException("Main entity class from constructor can't be null to use executeToEntityList method without entityClass"); + } + return (List<T>)executeToEntityList(transaction, mainEntityClass); + } + + /** + * Load all properties for the entity. + * + * @param <T> type of the entity extends TopiaEntity + * @param entity used to load properties + * @throws TopiaException + */ + protected <T extends TopiaEntity> void loadProperties(T entity) throws TopiaException { + for (String prop : getPropertiesToLoad()) { + if (log.isTraceEnabled()) { + log.trace("load property " + prop + " ..."); + } + List<String> str = Arrays.asList(prop.split("\\.")); + Iterator<String> it = str.iterator(); + TopiaEntity currEntity = entity; + while (it.hasNext()) { + String s = it.next(); + if (log.isTraceEnabled()) { + log.trace("Current entity : " + currEntity.getClass().getSimpleName()); + log.trace("Current loading : " + s); + } + if (it.hasNext()) { + currEntity = loadEntityProperty(currEntity, s); + } else { + loadProperty(currEntity, s); + } + } + } + } + + /** + * Load a property of type TopiaEntity from an other entity. + * + * @param <T> type of the entity extends TopiaEntity + * @param entity used to load the property + * @param property name of the property in the entity + * @return a TopiaEntity corresponding to the property loaded + * @throws TopiaException + */ + protected <T extends TopiaEntity> TopiaEntity loadEntityProperty(T entity, String property) throws TopiaException { + return (TopiaEntity)loadProperty(entity, property); + } + + /** + * Load a property from an entity. + * + * @param <T> type of the entity extends TopiaEntity + * @param entity used to load the property + * @param property name of the property in the entity + * @return an Object corresponding to the property loaded + * @throws TopiaException + */ + protected <T extends TopiaEntity> Object loadProperty(T entity, String property) throws TopiaException { + try { + Object res = PropertyUtils.getProperty(entity, property); + if (log.isTraceEnabled()) { + log.trace("load property '" + property + "' for '" + entity.getClass().getSimpleName() + "'"); + } + if (Collection.class.isAssignableFrom(res.getClass())) { + Collection list = (Collection) res; + list.size(); + } + return res; + } catch (IllegalAccessException eee) { + throw new TopiaException("Illegal access on property " + property + " from entity " + entity.getClass().getName(), eee); + } catch (InvocationTargetException eee) { + throw new TopiaException("Invocation error on entity " + entity.getClass().getName() + " for property " + property, eee); + } catch (NoSuchMethodException eee) { + throw new TopiaException("Getter method does not exist for property " + property + " from entity " + entity.getClass().getName(), eee); + } + } + + /** + * Execute the query and get a Map of entity with key type in argument. Some properties will be loaded if they are + * prealably set using ${@link #addLoad(java.lang.String[]) }. + * + * @param <K> the type of the map key + * @param <T> the type of entity, value of the map + * @param transaction the TopiaContext to use for execution + * @param entityClass the class of the TopiaEntity used for return + * @param keyName the property name of the key in the entity + * @param keyClass the key class for the result map + * @return a Map with the key type defined and the entity in value + * @throws TopiaException + * @throws ClassCastException + */ + public <K, T extends TopiaEntity> Map<K, T> executeToEntityMap(TopiaContext transaction, Class<T> entityClass, String keyName, Class<K> keyClass) + throws TopiaException, ClassCastException { + + Map<K, T> results = new HashMap<K, T>(); + for (T elmt : executeToEntityList(transaction, entityClass)) { + Object value = loadProperty(elmt, keyName); + if (value != null && !keyClass.isAssignableFrom(value.getClass())) { + throw new ClassCastException("Invalid cast for " + keyClass.getName()); + } + results.put((K)value, elmt); + } + return results; + } + + /** + * {@link #executeToEntityMap(org.nuiton.topia.TopiaContext, java.lang.Class, java.lang.String, java.lang.Class) } + * with default entityClass set from constructor. + * + * @param <K> + * @param <T> + * @param transaction + * @param keyName + * @param keyClass + * @return the TopiaEntity from entityClass set from ${@link #TopiaQuery(java.lang.Class) } constructor + * @throws TopiaException + * @throws ClassCastException + */ + public <K, T extends TopiaEntity> Map<K, T> executeToEntityMap(TopiaContext transaction, String keyName, Class<K> keyClass) + throws TopiaException, ClassCastException { + if (this.mainEntityClass == null) { + throw new NullPointerException("Main entity class from constructor can't be null to use executeToEntityMap method without entityClass"); + } + return (Map<K, T>)executeToEntityMap(transaction, mainEntityClass, keyName, keyClass); + } + + /** + * Execute the query and get a Map of entity with topiaId in key. Some properties will be loaded if they are + * prealably set using ${@link #addLoad(java.lang.String[]) }. + * + * @param <T> the type of entity, value of the map + * @param transaction the TopiaContext to use for execution + * @param entityClass the class of the TopiaEntity used for return + * @return a Map with the key type defined and the entity in value + * @throws TopiaException + * @throws ClassCastException + */ + public <T extends TopiaEntity> Map<String, T> executeToEntityMap(TopiaContext transaction, Class<T> entityClass) + throws TopiaException, ClassCastException { + return executeToEntityMap(transaction, entityClass, TopiaEntity.TOPIA_ID, String.class); + } + + /** + * {@link #executeToEntityMap(org.nuiton.topia.TopiaContext, java.lang.Class) } with default entityClass set from constructor. + * + * @param <T> + * @param transaction + * @return the TopiaEntity from entityClass set from ${@link #TopiaQuery(java.lang.Class) } constructor + * @throws TopiaException + * @throws ClassCastException + */ + public <T extends TopiaEntity> Map<String, T> executeToEntityMap(TopiaContext transaction) + throws TopiaException, ClassCastException { + if (this.mainEntityClass == null) { + throw new NullPointerException("Main entity class from constructor can't be null to use executeToEntityMap method without entityClass"); + } + return (Map<String, T>)executeToEntityMap(transaction, mainEntityClass); + } + + /** + * Execute the query and get the first result entity. Some properties will be loaded if they are + * prealably set using ${@link #addLoad(java.lang.String[]) }. + * + * @param <T> the type of TopiaEntity to return + * @param transaction the TopiaContext to use for execution + * @param entityClass the class of the TopiaEntity used for return type + * @return a TopiaEntity corresponding to the entityClass in argument + * @throws TopiaException + * @throws ClassCastException + */ + public <T extends TopiaEntity> T executeToEntity(TopiaContext transaction, Class<T> entityClass) + throws TopiaException, ClassCastException { + setMaxResults(1); + List<T> results = executeToEntityList(transaction, entityClass); + return !results.isEmpty() ? results.get(0) : null; + } + + /** + * {@link #executeToEntity(org.nuiton.topia.TopiaContext, java.lang.Class) } with default entityClass set from constructor. + * + * @param <T> + * @param transaction + * @return the TopiaEntity from entityClass set from ${@link #TopiaQuery(java.lang.Class) } constructor + * @throws TopiaException + * @throws ClassCastException + */ + public <T extends TopiaEntity> T executeToEntity(TopiaContext transaction) + throws TopiaException, ClassCastException { + if (this.mainEntityClass == null) { + throw new NullPointerException("Main entity class from constructor can't be null to use executeToEntity method without entityClass"); + } + return (T)executeToEntity(transaction, mainEntityClass); + } + + /** + * Execute the query and get an Integer for result. Used for query with COUNT or SUM, ... + * The select is overriden to get only the right value for return. + * + * @param transaction the TopiaContext to use for execution + * @param select the Select overriden (ex : SUM(myParam)) + * @return an Integer + * @throws TopiaException + */ + public int executeToInteger(TopiaContext transaction, String select) throws TopiaException { + Long res = (Long)executeToObject(transaction, select); + return res != null ? res.intValue() : 0; + } + + /** + * Execute the query and get a String for result. Used for query with MAX, ... + * The select is overriden to get only the right value for return. + * + * @param transaction the TopiaContext to use for execution + * @param select the Select overriden (ex : MAX(myParam)) + * @return a String + * @throws TopiaException + */ + public String executeToString(TopiaContext transaction, String select) throws TopiaException { + Object res = executeToObject(transaction, select); + return res != null ? (String)res : ""; + } + + /** + * Execute the query and get an Object for result. + * The select is overriden to get only the right value for return. + * + * @param transaction the TopiaContext to use for execution + * @param select the Select overriden + * @return an Object + * @throws TopiaException + */ + public Object executeToObject(TopiaContext transaction, String select) throws TopiaException { + String oldValue = this.select; + if (!StringUtils.isEmpty(select)) { + this.select = "SELECT " + select; + } + Object result = null; + setMaxResults(1); + List results = execute(transaction); + if (!results.isEmpty()) { + result = results.get(0); + } + this.select = oldValue; + return result; + } + + /** + * Execute a simple count on the query, i.e. the number of results get from the query. + * + * @param transaction the TopiaContext to use for execution + * @return an int corresponding to the number of result in the query + * @throws TopiaException + */ + public int executeCount(TopiaContext transaction) throws TopiaException { + return executeToInteger(transaction, "COUNT(*)"); + } + +} Property changes on: trunk/topia-persistence/src/main/java/org/nuiton/topia/framework/TopiaQuery.java ___________________________________________________________________ Added: svn:keywords + "Author Date Id Revision HeadURL" Modified: trunk/topia-persistence/src/main/java/org/nuiton/topia/persistence/TopiaDAOImpl.java =================================================================== --- trunk/topia-persistence/src/main/java/org/nuiton/topia/persistence/TopiaDAOImpl.java 2009-12-29 12:11:40 UTC (rev 1740) +++ trunk/topia-persistence/src/main/java/org/nuiton/topia/persistence/TopiaDAOImpl.java 2009-12-29 12:12:40 UTC (rev 1741) @@ -56,6 +56,7 @@ import org.hibernate.criterion.Order; import org.hibernate.criterion.Restrictions; import org.hibernate.metadata.ClassMetadata; +import org.nuiton.topia.framework.TopiaQuery; /** * Cette classe permet d'avoir un ensemble de méthode implantée de façon @@ -731,15 +732,17 @@ } /** - * Utilisation du count(*) en HQL pour recuperer le nombre d'entites du type lie au DAO - * @return un long correspondant au nombre d'entités existantes en base + * Count number of existing entities using {@link org.nuiton.topia.framework.TopiaQuery#executeCount(org.nuiton.topia.TopiaContext) } + * FIXME-FD20091224 change type to int like in 2.2.2 version + * + * @return a long for the number of entities in database */ @Override public long size() throws TopiaException { //int result = findAll().size(); - - List result = this.getContext().find("SELECT count(*) FROM " + getEntityClass().getName() + "Impl"); - return (Long)result.get(0); +// List result = this.getContext().find("SELECT count(*) FROM " + getEntityClass().getName() + "Impl"); +// return (Long)result.get(0); + return new TopiaQuery(getEntityClass()).executeCount(context); } @Override