package it.mice.voila.runtime.hibernate.dao.impl;

import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

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.hibernate.FlushMode;
import org.hibernate.Hibernate;
import org.hibernate.ObjectNotFoundException;
import org.hibernate.PropertyValueException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.StaleObjectStateException;
import org.hibernate.TypeMismatchException;
import org.hibernate.engine.query.spi.ParamLocationRecognizer;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.exception.ConstraintViolationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.GenericTypeResolver;

import it.mice.voila.runtime.dao.CriteriaType;
import it.mice.voila.runtime.dao.Dao;
import it.mice.voila.runtime.dao.SqlCommandOption;
import it.mice.voila.runtime.entity.Entity;
import it.mice.voila.runtime.entity.EntityGeneric;
import it.mice.voila.runtime.exception.BusinessException;
import it.mice.voila.runtime.security.SecurityUtils;
import it.mice.voila.runtime.security.UserDetailImpl;
import it.mice.voila.runtime.util.UserMessage;

/**
 * Created by IntelliJ IDEA. User: zzy9v4 Date: 2-feb-2007 Time: 23.53.04 To
 * change this template use File | Settings | File Templates.
 */
public abstract class DaoHbmGeneric<T extends Entity> implements Dao<T> {
	private static final String USER = "user";
	public static final Class[] EMPTY_CLASS_TYPE = {};
	public static final Object[] EMPTY_OBJECT = {};
	public static final String ORDER_BY_CLAUSE = " ORDER BY ";
	private static final String COUNT_HQL = "select count(*) ";
	/**
	 * The DOT
	 */
	protected static final String DOT = ".";

	/**
	 * Criteria map vuoto.
	 */
	public static final Map<String, Object> EMPTY_CRITERIA_MAP = new HashMap<String, Object>();

	/**
	 * default logger
	 */
	private static Log logger = LogFactory.getLog(DaoHbmGeneric.class);

	@Autowired
	@Qualifier("sessionFactory")
	private SessionFactory sessionFactory;
	@Autowired
	private SqlCommandOption sqlCommandOption;

	private boolean enableNotDeletedEntityFilter = false;

	private final Class<T> genericType;

	@SuppressWarnings("unchecked")
	public DaoHbmGeneric() {
		super();

		this.genericType = (Class<T>) GenericTypeResolver.resolveTypeArgument(getClass(), DaoHbmGeneric.class);
	}

	/**
	 * Ricerca di un Customer accedendo per chiave primaria.<br>
	 * Il metodo prende in input l'oggetto di tipo {Customer} dal quale prelevera'
	 * la chiave primaria, e con essa attiverà la load hibernate che restituirà i
	 * dati per popolare gli altri campi.
	 * 
	 * 
	 * Entity {Customer} valorizzato della sola chiave primaria e da utilizzare per
	 * l'accesso al DB.
	 * 
	 * @return il riferimento all'oggetto fornito in input, oppure null se non
	 *         trovato.
	 */
	public T findByPrimaryKey(T theEntity) {
		return (T) findByPrimaryKey(theEntity, true);
	}

	/**
	 * Ricerca di un Customer accedendo per chiave primaria.<br>
	 * Il metodo prende in input l'oggetto di tipo {Customer} dal quale prelevera'
	 * la chiave primaria, e con essa attiverà la load hibernate che restituirà i
	 * dati per popolare gli altri campi.
	 * 
	 *
	 * Entity {Customer} valorizzato della sola chiave primaria e da utilizzare per
	 * l'accesso al DB.
	 * 
	 * @return il riferimento all'oggetto fornito in input, oppure null se non
	 *         trovato.
	 */
	public T findByPrimaryKey(T theEntity, boolean useAcl) {
		return (T) findByPrimaryKeyInternal(theEntity, useAcl);
	}

	/**
	 * Ricerca di un Customer accedendo per chiave primaria.<br>
	 * A differenza del metodo findByPrimaryKey, esso evita la registrazione nella
	 * hibernate cache dell'oggetto appena letto, consentendo il suo utilizzo per
	 * dei check senza per questo incorrere nella noiosissima eccezione
	 * NonUniqueObjectException di hibernate.
	 * 
	 * 
	 * Entity {Customer} valorizzato della sola chiave primaria e da utilizzare per
	 * l'accesso al DB.
	 * 
	 * @return il riferimento all'oggetto fornito in input, oppure null se non
	 *         trovato.
	 */
	public T findByPrimaryKeyNoCache(final T theEntity) {
		T filter = theEntity;
		try {
			filter = (T) theEntity.clone();
			filter.setObjectDump(StringUtils.repeat("null", "|", 200));
			filter.setObjectKey(theEntity.getObjectKey());
		} catch (CloneNotSupportedException e) {
		}

		Collection<T> result = findByFilter(filter, filter);
		if (result.size() == 0) {
			return null;
		}
		if (result.size() > 1) {
			throw new IllegalArgumentException(
					"More than one row selected using the following query by template object: " + theEntity);
		}
		return (T) result.iterator().next();
	}

	@SuppressWarnings("unchecked")
	protected T findByPrimaryKeyInternal(T theEntity, boolean useAcl) {
		try {
			if (theEntity.isPkNotNull()) {
				T loadedObject;
				try {
					loadedObject = (T) getCurrentSession().get(theEntity.getClass(), theEntity.getEntityId());
				} catch (TypeMismatchException ex) {
					loadedObject = (T) getCurrentSession().get(theEntity.getClass(), theEntity);
				}
				if (loadedObject != null) {
					Set<String> aclQuery = null;
					if (useAcl) {
						aclQuery = SecurityUtils.extractAclQueries(getEntityName(theEntity), "findByPrimaryKey");
					}
					if (!useAcl || aclQuery.size() == 0 || existByPrimaryKey(theEntity)) {
						fillParameterEntity(theEntity, loadedObject);
						return loadedObject;
					}
				}
			} else {
				logger.warn("Unable to load the PK null entity: " + theEntity);
			}
			return null;
		} catch (ObjectNotFoundException e) {
			logger.warn("Entity not found: " + theEntity);
			return null;
		}
	}

	/**
	 * Controlla l'esistenza di un Customer accedendo per chiave primaria.<br>
	 * Il metodo prende in input l'oggetto di tipo {Customer} dal quale prelevera'
	 * la chiave primaria, e con essa accederà al db per controllarne l'esistenza.
	 * 
	 * 
	 * Entity {Customer} valorizzato della sola chiave primaria.
	 * 
	 * @return <code>true</code> se l'oggetto Customer è stato trovato;<br>
	 *         <code>false</code> se l'oggetto Customer non è stato trovato.
	 */
	public boolean existByPrimaryKey(T theEntity) {
		String entityName = getEntityName(theEntity);
		StringBuilder sql = new StringBuilder("select 1 from ");
		sql.append(StringUtils.capitalize(entityName)).append(" ").append(entityName).append(" WHERE ")
				.append(entityName).append(" = :").append(entityName);
		extendTheQueryCriteriaWithAcl(sql, " AND ", entityName, "findByPrimaryKey");
		Query query = getCurrentSession().createQuery(sql.toString());
		query.setEntity(entityName, theEntity);
		setTheStatementFilterForCriteriaWithAcl(query, entityName, "findByPrimaryKey");
		return (query.uniqueResult() != null);
	}

	public Collection<T> findAll() {
		return findAll(Boolean.FALSE);
	}

	public Collection<T> findAllWithAcl() {
		return findAll(Boolean.FALSE, true);
	}

	public Collection<T> findAll(Boolean cacheable) {
		return findAll(cacheable, false);
	}

	public Collection<T> findAll(Boolean cacheable, Boolean applyAcl) {
		String entityName = StringUtils.uncapitalize(genericType.getSimpleName());
		StringBuilder hql = new StringBuilder(getFromQuery());
		if (applyAcl) {
			extendTheQueryCriteriaWithAcl(hql, " WHERE ", entityName, "findAll");
		}
		hql = hql.append(getOrderBy());
		Query q = getCurrentSession().createQuery(hql.toString()).setCacheable(cacheable.booleanValue());
		if (applyAcl) {
			setTheStatementFilterForCriteriaWithAcl(q, entityName, "findAll");
		}
		return list(q);
	}

	public T insert(T entity) {
		return (T) insertInternal(entity);
	}

	public Collection<T> insert(Collection<T> entityList) {
		if (entityList != null) {
			Iterator<T> i = entityList.iterator();
			while (i.hasNext()) {
				// For each entity Customer of the list,
				// call insert method if its current status is NEW.
				insert(i.next());
			}
		}
		return entityList;
	}

	public T update(T entity) {
		return (T) updateInternal(entity);
	}

	public Collection<T> update(Collection<T> entityList) {
		if (entityList != null) {
			Iterator<T> i = entityList.iterator();
			while (i.hasNext()) {
				update(i.next());
			}
		}
		return entityList;
	}

	public T store(T entity) {
		return saveOrUpdate(entity);
	}

	public boolean delete(T entity) {
		if (!existByPrimaryKey(entity)) {
			return false;
		}
		try {
			deleteInternal(entity);
			return true;
		} catch (ConstraintViolationException e) {
			UserMessage userMessage = new UserMessage("message.genericErrorOnDelete",
					entity.getClass().getSimpleName());
			throw new BusinessException(userMessage);
		}
	}

	public boolean delete(Collection<T> entityList) {
		if (entityList != null) {
			Iterator<T> i = entityList.iterator();
			while (i.hasNext()) {
				// For each entity Customer of the list,
				// call delete method if its current status is NOT NEW.
				if (!delete(i.next())) {
					return false;
				}
			}
		}
		return true;
	}

	public Collection<T> findByFilter(T entityFrom, T entityTo) {
		return findByFilter(entityFrom, entityTo, EMPTY_CRITERIA_MAP, buildNewSql(), 0, 0, null);
	}

	protected abstract String getFromQuery();

	public Collection<T> findByFilter(T entityFrom, T entityTo, Map<String, Object> criteria) {
		return findByFilter(entityFrom, entityTo, criteria, buildNewSql(), 0, 0, null);
	}

	public Collection<T> findByFilter(T entityFrom, T entityTo, int firstResult, int maxResult) {
		return findByFilter(entityFrom, entityTo, EMPTY_CRITERIA_MAP, buildNewSql(), firstResult, maxResult, null);
	}

	public Collection<T> findByFilter(T entityFrom, T entityTo, Map<String, Object> criteria, int firstResult,
			int maxResult) {
		return findByFilter(entityFrom, entityTo, criteria, buildNewSql(), firstResult, maxResult, null);
	}

	public Collection<T> findByFilter(T entityFrom, T entityTo, Map<String, Object> criteria, int firstResult,
			int maxResult, String orderBy) {
		return findByFilter(entityFrom, entityTo, criteria, buildNewSql(), firstResult, maxResult, orderBy);
	}

	protected StringBuilder buildNewSql() {
		return new StringBuilder(getFromQuery());
	}

	public Collection<T> findByFilter(T entityFrom, T entityTo, Map<String, Object> criteria, StringBuilder sql,
			int firstResult, int maxResult, String orderBy) {
		String prefix = " WHERE  ";
		String entityName = getEntityName(entityFrom);
		prefix = extendTheQueryCriteria(entityFrom, entityTo, criteria, sql, prefix, entityName + ".", "");
		extendTheQueryCriteriaWithAcl(sql, prefix, entityName, "findByFilter");
		prepareGroupBy(entityFrom, entityTo, criteria, sql);
		sql.append(enhanceOrderByWithPrefix(orderBy, entityName, getOrderBy()));
		finalizeFindByFilter(entityFrom, entityTo, criteria, sql);
		if (logger.isDebugEnabled()) {
			logger.debug("findByFilter: BEAN FROM=" + entityFrom);
			logger.debug("findByFilter: BEAN   TO=" + entityTo);
			logger.debug("findByFilter: SQL=" + sql);
		}
		Query query = getCurrentSession().createQuery(sql.toString());
		if (maxResult > 0) {
			query.setFirstResult(firstResult);
			query.setMaxResults(maxResult);
		}
		setTheStatementFilterForCriteria(entityFrom, entityTo, criteria, query, 0, "");
		setTheStatementFilterForCriteriaWithAcl(query, entityName, "findByFilter");
		return list(query);
	}

	protected void prepareGroupBy(T entityFrom, T entityTo, Map<String, Object> criteria, StringBuilder sql) {
	}

	protected void finalizeFindByFilter(T entityFrom, T entityTo, Map<String, Object> criteria, StringBuilder sql) {
	}

	protected void finalizeCountByFilter(T entityFrom, T entityTo, Map<String, Object> criteria, StringBuilder sql) {
	}

	protected abstract String getOrderBy();

	public abstract String extendTheQueryCriteria(T entityFrom, T entityTo, Map<String, Object> criteria,
			StringBuilder sql, String prefix, String queryPrefix, String criteriaPrefix);

	public abstract int setTheStatementFilterForCriteria(T entityFrom, T entityTo, Map<String, Object> criteria,
			Query query, int counter, String criteriaPrefix);

	public String extendTheQueryCriteriaWithAcl(StringBuilder sql, String prefix, String entityName, String queryName) {

		Set<String> aclQueries = SecurityUtils.extractAclQueries(entityName, queryName);
		if (aclQueries.size() > 0) {
			String or = "";
			sql.append(prefix).append(" (");
			for (Iterator iterator = aclQueries.iterator(); iterator.hasNext();) {
				String aclQuery = (String) iterator.next();
				sql.append(or).append(aclQuery);
				or = " OR ";
			}
			sql.append(") ");
			prefix = "AND ";
		}
		return prefix;
	}

	public void setTheStatementFilterForCriteriaWithAcl(Query query, String entityName, String queryId) {

		Set<String> aclQueries = SecurityUtils.extractAclQueries(entityName, queryId);
		if (aclQueries.size() > 0) {
			for (Iterator iterator = aclQueries.iterator(); iterator.hasNext();) {
				String aclQuery = (String) iterator.next();
				ParamLocationRecognizer recognizer = ParamLocationRecognizer.parseLocations(aclQuery,
						(SessionFactoryImplementor) getSessionFactory());
				Iterator itr = recognizer.getNamedParameterDescriptionMap().entrySet().iterator();
				UserDetailImpl userDetailImpl = (UserDetailImpl) SecurityUtils.getCurrentUserDetail();
				Properties p = userDetailImpl.getUserChainProperties();
				while (itr.hasNext()) {
					Map.Entry entry = (Map.Entry) itr.next();
					String name = (String) entry.getKey();
					Object value = determineType(p.getProperty(name));
					if (value == null) {
						try {
							value = PropertyUtils.getProperty(userDetailImpl, name.replaceAll("\\_", "."));
						} catch (Exception e) {
							e.printStackTrace();
						}
					}
					query.setParameter(name, value);
				}
			}
		}
	}

	private Object determineType(String value) {
		if (value == null) {
			return value;
		}
		if (value.startsWith("\"") || value.startsWith("'")) {
			return value;
		}
		try {
			return Integer.parseInt(value);
		} catch (Exception e) {
			return value;
		}
	}

	public int countByFilter(T entityFrom, T entityTo) {
		return countByFilter(entityFrom, entityTo, EMPTY_CRITERIA_MAP, getCountQuery());
	}

	public int countByFilter(T entityFrom, T entityTo, Map<String, Object> criteria) {
		return countByFilter(entityFrom, entityTo, criteria, getCountQuery());
	}

	public StringBuilder getCountQuery() {
		return new StringBuilder(COUNT_HQL + getCountFromQuery());
	}

	/**
	 * Restituisce la clausola iniziale per la costruzione di HQL Query.
	 * 
	 * @return la clausola iniziale per la costruzione di HQL Query.
	 * @see it.mice.voila.runtime.hibernate.dao.impl.DaoHbmGeneric#getFromQuery()
	 */
	protected String getCountFromQuery() {
		return getFromQuery();
	}

	public int countByFilter(T entityFrom, T entityTo, Map<String, Object> criteria, StringBuilder sql) {
		String entityName = getEntityName(entityFrom);
		String prefix = " WHERE ";

		prefix = extendTheQueryCriteria(entityFrom, entityTo, criteria, sql, prefix, entityName + ".", "");
		extendTheQueryCriteriaWithAcl(sql, prefix, entityName, "findByFilter");
//		prepareGroupBy(entityFrom, entityTo, criteria, sql);
		finalizeCountByFilter(entityFrom, entityTo, criteria, sql);

		Query query = getCurrentSession().createQuery(sql.toString());
		setTheStatementFilterForCriteria(entityFrom, entityTo, criteria, query, 0, "");
		setTheStatementFilterForCriteriaWithAcl(query, entityName, "findByFilter");
		Long totRecords = (Long) query.uniqueResult();
		if (logger.isDebugEnabled()) {
			logger.debug("countByFilter: BEAN FROM=" + entityFrom);
			logger.debug("countByFilter: BEAN   TO=" + entityTo);
			logger.debug("countByFilter: SQL=" + sql);
			logger.debug("countByFilter: Num of records=" + totRecords);
		}
		return totRecords.intValue();
	}

	public SessionFactory getSessionFactory() {
		return sessionFactory;
	}

	public void setSessionFactory(SessionFactory sessionFactory) {
		this.sessionFactory = sessionFactory;
	}

	public Session getCurrentSession() {
		Session retSession = getSessionFactory().getCurrentSession();
		if (isEnableNotDeletedEntityFilter()) {
			retSession.enableFilter("notDeletedEntities");
		}
//    	retSession.setFlushMode(FlushMode.parse(getSqlCommandOption().getFlushMode()));
		retSession.setFlushMode(FlushMode.interpretExternalSetting(getSqlCommandOption().getFlushMode()));
		return retSession;
	}

	public SqlCommandOption getSqlCommandOption() {
		return sqlCommandOption;
	}

	public void setSqlCommandOption(SqlCommandOption sqlCommandOption) {
		this.sqlCommandOption = sqlCommandOption;
	}

	public boolean isEnableNotDeletedEntityFilter() {
		return enableNotDeletedEntityFilter;
	}

	public void setEnableNotDeletedEntityFilter(boolean enableNotDeletedEntityFilter) {
		this.enableNotDeletedEntityFilter = enableNotDeletedEntityFilter;
	}

	public java.sql.Timestamp getCurrentTimestamp() {
		return new java.sql.Timestamp(System.currentTimeMillis());
	}

	private void fillParameterEntity(T parameterEntity, T loadedObject) {
		evictIfNecessary(loadedObject);
		if (loadedObject != parameterEntity) {
			if (logger.isDebugEnabled()) {
				logger.debug(
						"Found a duplicated object with the same identifier in the hibernate session. Try to resolve aniway. Please check results !");
			}
			parameterEntity.setInternalEntityStateToLoaded();
			parameterEntity.setObjectDump(loadedObject.getObjectDump());
			parameterEntity.setInternalEntityStateToLoaded();
		}
	}

	public T insertInternal(T theEntity) {
		if (!theEntity.isNewEntity()) {
			if (logger.isDebugEnabled()) {
				logger.debug(
						"Operation 'insert' discarded because the entity is not in new internal status: " + theEntity);
			}
			return theEntity;
		}

		getCurrentSession().save(theEntity);
		flushIfNecessary();
		if (logger.isDebugEnabled()) {
			logger.debug("insert -> success: " + theEntity);
		}
		evictIfNecessary(theEntity);
		return theEntity;
	}

	public T updateInternal(T theEntity) {
		clearSessionBeforeUpdateIfNecessary();
		try {
			getCurrentSession().update(theEntity);
			flushIfNecessary();
		} catch (StaleObjectStateException sose) {
			throw new BusinessException("message.staleObjectStateException");
		}
		if (logger.isDebugEnabled()) {
			logger.debug("update -> success: " + theEntity);
		}
		evictIfNecessary(theEntity);
		return theEntity;
	}

	public void deleteInternal(T theEntity) {
		try {
			getCurrentSession().delete(theEntity);
		} catch (PropertyValueException e) {
			if (logger.isWarnEnabled()) {
				logger.warn("Exception trying to delete an entity, try to load it before retry deletion...");
			}
			getCurrentSession().evict(theEntity);
			T entityToDelete = findByPrimaryKeyNoCache(theEntity);
			entityToDelete.setInternalEntityStateToDeleted();
			getCurrentSession().delete(entityToDelete);
		}
		flushIfNecessary();
		if (logger.isDebugEnabled()) {
			logger.debug("delete -> success: " + theEntity);
		}
		evictIfNecessary(theEntity);
	}

	public Collection<T> list(Query query) {
		Collection<T> result = query.getResultList();
		if (getSqlCommandOption().isEvictHibernateListElementImmediatly()) {
			Iterator<T> resultIt = result.iterator();
			while (resultIt.hasNext()) {
				T element = (T) resultIt.next();
				evictListElementIfNecessary(element);
			}
		}
		return result;
	}

	public T saveOrUpdate(T theEntity) {
		if (theEntity.isChangedEntity()) {
			return updateInternal(theEntity);
		}
		if (theEntity.isNewEntity()) {
			return insertInternal(theEntity);
		}
		if (logger.isDebugEnabled()) {
			logger.debug(
					"Operation 'saveOrUpdate' discarded because the entity is not in new or changed internal status: "
							+ theEntity);
		}
		return theEntity;
	}

	private void flushIfNecessary() {
		if (getSqlCommandOption().isFlushHibernateSessionImmediatly()) {
			getCurrentSession().flush();
			if (logger.isDebugEnabled()) {
				logger.debug("Flushing entity immediatly.");
			}
		}
	}

	private void clearSessionBeforeUpdateIfNecessary() {
		if (getSqlCommandOption().isClearHibernateSessionBeforeUpdate()) {
			getCurrentSession().clear();
			if (logger.isDebugEnabled()) {
				logger.debug("Clear hibernate session before update command execution.");
			}
		}
	}

	private void evictIfNecessary(T theEntity) {
		if (getSqlCommandOption().isEvictHibernateElementImmediatly()) {
			getCurrentSession().evict(theEntity);
			if (logger.isDebugEnabled()) {
				logger.debug("Evict entity immediatly.");
			}
		}
	}

	/**
	 * Evict all object list if evictHibernateListElement is set to true in
	 * applicationDAO
	 * 
	 * @param theEntity
	 */
	private void evictListElementIfNecessary(T theEntity) {
		getCurrentSession().evict(theEntity);
		if (logger.isDebugEnabled()) {
			logger.debug("Evict list entity element immediatly.");
		}

	}

	protected String extendQueryCriteria(StringBuilder sql, String prefix, Object valueFrom, Object valueTo,
			String parameterName, Object criteriaType, String parameter) {
		return extendQueryCriteria(sql, prefix, valueFrom, valueTo, parameterName, criteriaType, true, parameter);
	}

	protected String extendQueryCriteria(StringBuilder sql, String prefix, Object valueFrom, Object valueTo,
			String parameterName, Object criteriaType, boolean useUpper, String parameter) {

		criteriaType = convertCriteriaToByte(criteriaType);

		if (criteriaType == CriteriaType.DEFAULT && valueFrom instanceof String && valueFrom != null
				&& valueFrom.equals(valueTo)) {
			criteriaType = CriteriaType.EQUALS;
		}

		if (criteriaType == CriteriaType.DEFAULT) {
			if (valueFrom instanceof String) {
				return appendToFilterLike(sql, prefix, valueFrom, parameterName, useUpper, parameter);
			} else {
				return appendToFilterFromTo(sql, prefix, valueFrom, valueTo, parameterName, parameter);
			}
		}

		if (criteriaType.equals(CriteriaType.NONE)) {
			return prefix;
		}

		if (criteriaType.equals(CriteriaType.EQUALS)) {
			if (valueFrom != null) {
				if (valueFrom instanceof String) {
					sql.append(prefix).append(sqlCommandOption.getStatementFilterColumn(parameterName, useUpper))
							.append(" = :" + parameter);
				} else {
					sql.append(prefix).append(parameterName).append(" = :" + parameter);
				}
				prefix = " AND ";
			}
			return prefix;
		}

		if (criteriaType.equals(CriteriaType.NOTEQUALS)) {
			if (valueFrom != null) {
				if (valueFrom instanceof String) {
					sql.append(prefix).append(sqlCommandOption.getStatementFilterColumn(parameterName, useUpper))
							.append(" != :" + parameter);
				} else {
					sql.append(prefix).append(parameterName).append(" != :" + parameter);
				}
				prefix = " AND ";
			}
			return prefix;
		}

		if (criteriaType.equals(CriteriaType.IS_NULL)) {
			sql.append(prefix).append(parameterName).append(" IS NULL ");
			prefix = " AND ";
			return prefix;
		}

		if (criteriaType.equals(CriteriaType.IS_NOT_NULL)) {
			sql.append(prefix).append(parameterName).append(" IS NOT NULL ");
			prefix = " AND ";
			return prefix;
		}

		if (criteriaType.equals(CriteriaType.STARTWITH) || criteriaType.equals(CriteriaType.ENDWITH)
				|| criteriaType.equals(CriteriaType.CONTAINS)) {
			return appendToFilterLike(sql, prefix, valueFrom, parameterName, useUpper, parameter);
		}

		if (criteriaType.equals(CriteriaType.NOT_STARTWITH) || criteriaType.equals(CriteriaType.NOT_ENDWITH)
				|| criteriaType.equals(CriteriaType.NOT_CONTAINS)) {
			return appendToFilterNotLike(sql, prefix, valueFrom, parameterName, useUpper, parameter);
		}

		if (criteriaType.equals(CriteriaType.GREATER_OR_EQUAL_THAN)) {
			return appendToFilterFromTo(sql, prefix, valueFrom, null, parameterName, parameter);
		}

		if (criteriaType.equals(CriteriaType.LESS_OR_EQUAL_THAN)) {
			return appendToFilterFromTo(sql, prefix, null, valueFrom, parameterName, parameter);
		}

		if (criteriaType.equals(CriteriaType.BETWEEN)) {
			return appendToFilterFromTo(sql, prefix, valueFrom, valueTo, parameterName, parameter);
		}

		if (criteriaType.equals(CriteriaType.NOT_BETWEEN)) {
			return appendToFilterNotBetween(sql, prefix, valueFrom, valueTo, parameterName, parameter);
		}

		if (criteriaType instanceof CriteriaType.InClause) {
			Object listValue = ((CriteriaType.InClause) criteriaType).getListValue();
			return appendToFilterIn(sql, prefix, listValue, parameterName, parameter);
		}

		if (criteriaType instanceof CriteriaType.NotInClause) {
			Object listValue = ((CriteriaType.NotInClause) criteriaType).getListValue();
			return appendToFilterNotIn(sql, prefix, listValue, parameterName, parameter);
		}

		logger.warn("Unrecognized criteria type: " + criteriaType + ". Check your query. Filter discarded !");
		return prefix;
	}

	protected int setStatementFilterForCriteria(Query query, int counter, Object valueFrom, Object valueTo,
			Object criteriaType, String parameterName) {
		return setStatementFilterForCriteria(query, counter, valueFrom, valueTo, criteriaType, true, parameterName);
	}

	protected int setStatementFilterForCriteria(Query query, int counter, Object valueFrom, Object valueTo,
			Object criteriaType, boolean useUpper, String parameterName) {
		criteriaType = convertCriteriaToByte(criteriaType);

		if (criteriaType == CriteriaType.DEFAULT && valueFrom instanceof String && valueFrom != null
				&& valueFrom.equals(valueTo)) {
			criteriaType = CriteriaType.EQUALS;
		}

		if (criteriaType == CriteriaType.DEFAULT) {
			if (valueFrom instanceof String) {
				return setStatementFilterLike(query, counter, valueFrom, useUpper, parameterName);
			} else {
				return setStatementFilterFromTo(query, counter, valueFrom, valueTo, parameterName);
			}
		}

		if (criteriaType.equals(CriteriaType.NONE) || criteriaType.equals(CriteriaType.IS_NULL)
				|| criteriaType.equals(CriteriaType.IS_NOT_NULL)) {
			return counter;
		}

		if (criteriaType.equals(CriteriaType.EQUALS) || criteriaType.equals(CriteriaType.NOTEQUALS)) {
			return setStatementFilter(query, counter, valueFrom, useUpper, parameterName, null);
		}

		if (criteriaType.equals(CriteriaType.STARTWITH) || criteriaType.equals(CriteriaType.NOT_STARTWITH)) {
			return setStatementFilterLike(query, counter, valueFrom, "", "%", useUpper, parameterName);
		}

		if (criteriaType.equals(CriteriaType.ENDWITH) || criteriaType.equals(CriteriaType.NOT_ENDWITH)) {
			return setStatementFilterLike(query, counter, valueFrom, "%", "", useUpper, parameterName);
		}

		if (criteriaType.equals(CriteriaType.CONTAINS) || criteriaType.equals(CriteriaType.NOT_CONTAINS)) {
			return setStatementFilterLike(query, counter, valueFrom, "%", "%", useUpper, parameterName);
		}

		if (criteriaType.equals(CriteriaType.GREATER_OR_EQUAL_THAN)) {
			return setStatementFilterFromTo(query, counter, valueFrom, null, parameterName);
		}

		if (criteriaType.equals(CriteriaType.LESS_OR_EQUAL_THAN)) {
			return setStatementFilterFromTo(query, counter, null, valueFrom, parameterName);
		}

		if (criteriaType.equals(CriteriaType.BETWEEN) || criteriaType.equals(CriteriaType.NOT_BETWEEN)) {
			return setStatementFilterFromTo(query, counter, valueFrom, valueTo, parameterName);
		}

		if (criteriaType instanceof CriteriaType.InClause) {
			Object listValue = ((CriteriaType.InClause) criteriaType).getListValue();
			return setStatementFilterIn(query, counter, listValue, parameterName);
		}

		if (criteriaType instanceof CriteriaType.NotInClause) {
			Object listValue = ((CriteriaType.NotInClause) criteriaType).getListValue();
			return setStatementFilterIn(query, counter, listValue, parameterName);
		}

		logger.warn("Unrecognized criteria type: " + criteriaType + ". Check your query. Filter discarded !");
		return counter;
	}

	protected String appendToFilterLike(StringBuilder sql, String prefix, Object fieldFrom, String columnName,
			String parameterName) {
		return appendToFilterLike(sql, prefix, fieldFrom, columnName, true, parameterName);
	}

	protected String appendToFilterLike(StringBuilder sql, String prefix, Object fieldFrom, String columnName,
			boolean useUpper, String parameterName) {
		if (fieldFrom != null) {
			sql.append(prefix).append(sqlCommandOption.getStatementFilterColumn(columnName, useUpper))
					.append(" LIKE :" + parameterName);
			prefix = " AND    ";
		}
		return prefix;
	}

	protected String appendToFilterNotLike(StringBuilder sql, String prefix, Object fieldFrom, String columnName,
			String parameterName) {
		return appendToFilterNotLike(sql, prefix, fieldFrom, columnName, true, parameterName);
	}

	protected String appendToFilterNotLike(StringBuilder sql, String prefix, Object fieldFrom, String columnName,
			boolean useUpper, String parameterName) {
		if (fieldFrom != null) {
			sql.append(prefix).append(sqlCommandOption.getStatementFilterColumn(columnName, useUpper))
					.append(" NOT LIKE :" + parameterName);
			prefix = " AND ";
		}
		return prefix;
	}

	protected String appendToFilterIn(StringBuilder sql, String prefix, Object array, String columnName,
			String parameter) {
		return appendToFilterIn(sql, prefix, array, columnName, false, parameter);
	}

	protected String appendToFilterNotIn(StringBuilder sql, String prefix, Object array, String columnName,
			String parameter) {
		return appendToFilterIn(sql, prefix, array, columnName, true, parameter);
	}

	protected String appendToFilterIn(StringBuilder sql, String prefix, Object array, String columnName, boolean useNot,
			String parameter) {
		if (array != null) {
			if (useNot) {
				sql.append(prefix).append(columnName).append(" NOT IN (");
			} else {
				sql.append(prefix).append(columnName).append(" IN (");
			}
			String delimiter = "";
			sql.append(delimiter).append(":").append(parameter);
			sql.append(") ");
			prefix = " AND ";
		}
		return prefix;
	}

	/**
	 * @param sql
	 * @param prefix
	 * @param fieldFrom
	 */
	protected String appendToFilterFromTo(StringBuilder sql, String prefix, Object fieldFrom, Object fieldTo,
			String columnName, String parameterName) {
		if (fieldFrom != null && fieldTo != null) {
			if (fieldFrom.equals(fieldTo) ) {
				if(fieldFrom instanceof java.util.Date) {
					sql.append(prefix).append(columnName).append(" like :" + parameterName);
				} else {
					sql.append(prefix).append(columnName).append(" = :" + parameterName);
				}
			} else {
				// sql.append(prefix).append(columnName).append(" BETWEEN " + ":" +
				// parameterName + " AND ?1");
				sql.append(prefix).append(columnName).append(" >= :" + parameterName).append(" AND ").append(columnName)
						.append(" <= ?1");
			}
			prefix = " AND ";
		}
		if (fieldFrom != null && fieldTo == null) {
			sql.append(prefix).append(columnName).append(" >= :" + parameterName);
			prefix = " AND ";
		}
		if (fieldFrom == null && fieldTo != null) {
			sql.append(prefix).append(columnName).append(" <= :" + parameterName);
			prefix = " AND ";
		}
		return prefix;
	}

	protected String appendToFilterNotBetween(StringBuilder sql, String prefix, Object fieldFrom, Object fieldTo,
			String columnName, String parameterName) {
		sql.append(prefix).append(columnName).append(" NOT BETWEEN ? AND ?");
		prefix = " AND ";
		return prefix;
	}

	/**
	 * @param query
	 * @param counter
	 * @param fieldFrom
	 */
	protected int setStatementFilterFromTo(Query query, int counter, Object fieldFrom, Object fieldTo,
			String parameterName) {
		if (fieldFrom != null && fieldTo != null) {
			if (!fieldFrom.equals(fieldTo)) {
				setStatementFilter(query, counter++, fieldFrom, parameterName, fieldTo);
			}
			if (fieldFrom.equals(fieldTo)) {
				if (fieldFrom instanceof java.util.Date) {
					setStatementFilterLikeFormatted(query, counter++, fieldFrom, "%", "%", parameterName);
				} else {
					setStatementFilter(query, counter++, fieldFrom, parameterName, fieldTo);
				}
			}
		}
		if (fieldFrom != null && fieldTo == null) {
			setStatementFilter(query, counter++, fieldFrom, parameterName, fieldTo);
		}
		if (fieldFrom == null && fieldTo != null) {
			setStatementFilter(query, counter++, fieldTo, parameterName, null);
		}
		return counter;
	}

	/**
	 * @param query
	 * @param counter
	 * @param fieldFrom
	 */
	protected int setStatementFilter(Query query, int counter, Object fieldFrom, String parameterName, Object fieldTo) {
		return setStatementFilter(query, counter, fieldFrom, true, parameterName, fieldTo);
	}

	protected int setStatementFilter(Query query, int counter, Object fieldFrom, boolean useUpper, String parameterName,
			Object fieldTo) {
		if (fieldFrom != null) {
			if (fieldFrom instanceof String) {
				if (getSqlCommandOption().isLikeFilterIgnoreCase() && useUpper) {
					if (logger.isDebugEnabled()) {
						logger.debug("Setting String parameter " + parameterName + " to value <"
								+ ((String) fieldFrom).toUpperCase() + ">");
					}
					query.setString(parameterName, ((String) fieldFrom).toUpperCase());
				} else {
					if (logger.isDebugEnabled()) {
						logger.debug("Setting String parameter " + parameterName + " to value <" + fieldFrom + ">");
					}
					query.setString(parameterName, ((String) fieldFrom));
				}
			} else if (fieldFrom instanceof java.sql.Timestamp) {
				if (logger.isDebugEnabled()) {
					logger.debug("Setting Timestamp parameter " + parameterName + " to value <" + fieldFrom + ">");
				}
				query.setTimestamp(parameterName, (Timestamp) fieldFrom);
				if (fieldFrom != null && fieldTo != null && !fieldFrom.equals(fieldTo)
						&& fieldTo instanceof java.sql.Timestamp)
					query.setTimestamp(1, (Timestamp) fieldTo);
			} else if (fieldFrom instanceof java.util.Date) {
				if (logger.isDebugEnabled()) {
					logger.debug("Setting java.util.Date parameter " + parameterName + " to value <" + fieldFrom + ">");
				}
				query.setDate(parameterName, (Date) fieldFrom);
				if (fieldFrom != null && fieldTo != null && !fieldFrom.equals(fieldTo)
						&& fieldTo instanceof java.util.Date)
					query.setDate(1, (Date) fieldTo);
			} else if (fieldFrom instanceof java.sql.Date) {
				if (logger.isDebugEnabled()) {
					logger.debug("Setting java.sql.Date parameter " + parameterName + " to value <" + fieldFrom + ">");
				}
				query.setDate(parameterName, (java.sql.Date) fieldFrom);
				if (fieldFrom != null && fieldTo != null && !fieldFrom.equals(fieldTo)
						&& fieldTo instanceof java.sql.Date)
					query.setDate(1, (Date) fieldTo);
			} else if (fieldFrom instanceof java.lang.Number) {
				if (logger.isDebugEnabled()) {
					logger.debug("Setting Double parameter " + parameterName + " to value <" + fieldFrom + ">");
				}
				query.setDouble(parameterName, ((Number) fieldFrom).doubleValue());
				if (fieldFrom != null && fieldTo != null && !fieldFrom.equals(fieldTo)
						&& fieldTo instanceof java.lang.Number)
					query.setDouble(1, ((Number) fieldTo).doubleValue());
			} else if (fieldFrom instanceof java.lang.Boolean) {
				if (logger.isDebugEnabled()) {
					logger.debug("Setting Boolean parameter " + parameterName + " to value <" + fieldFrom + ">");
				}
				query.setBoolean(parameterName, ((Boolean) fieldFrom).booleanValue());
				if (fieldFrom != null && fieldTo != null && !fieldFrom.equals(fieldTo)
						&& fieldTo instanceof java.lang.Boolean)
					query.setBoolean(1, ((Boolean) fieldTo).booleanValue());
			}
		}
		return counter;
	}

	/**
	 * @param query
	 * @param counter
	 * @param fieldFrom
	 */
	protected int setStatementFilterLike(Query query, int counter, Object fieldFrom, String parameterName) {
		return setStatementFilterLike(query, counter, fieldFrom, true, parameterName);
	}

	/**
	 * 
	 * @param query
	 * @param counter
	 * @param fieldFrom
	 * @param useUpper
	 * @return
	 */
	protected int setStatementFilterLike(Query query, int counter, Object fieldFrom, boolean useUpper,
			String parameterName) {
		if (fieldFrom != null) {
			if (fieldFrom instanceof String) {
				if (logger.isDebugEnabled()) {
					logger.debug("Setting String parameter " + parameterName + " to value <" + fieldFrom + ">");
				}
				query.setString(parameterName, sqlCommandOption.getStatementFilterLike((String) fieldFrom, useUpper));
			}
		}
		return counter;
	}

	protected int setStatementFilterLike(Query query, int counter, Object fieldFrom,
			String likeFilterMethodLeftSymbolParam, String likeFilterMethodRightSymbolParam, String parameterName) {
		return setStatementFilterLike(query, counter, fieldFrom, likeFilterMethodLeftSymbolParam,
				likeFilterMethodRightSymbolParam, true, parameterName);
	}

	protected int setStatementFilterLike(Query query, int counter, Object fieldFrom,
			String likeFilterMethodLeftSymbolParam, String likeFilterMethodRightSymbolParam, boolean useUpper,
			String parameterName) {
		if (fieldFrom != null) {
			if (logger.isDebugEnabled()) {
				logger.debug("Setting String parameter " + parameterName + " to value <" + fieldFrom + ">");
			}
			query.setString(parameterName, sqlCommandOption.getStatementFilterLike(fieldFrom.toString(),
					likeFilterMethodLeftSymbolParam, likeFilterMethodRightSymbolParam, useUpper));
		}
		return counter;
	}

	protected int setStatementFilterIn(Query query, int counter, Object array, String parameterName) {
		if (array != null) {
			if (array instanceof Object[]) {
				query.setParameterList(parameterName, (Object[]) array);
			}
			if (array instanceof Collection) {
				query.setParameterList(parameterName, (Collection) array);
			}
		}
		return counter;
	}

	protected String enhanceOrderByWithPrefix(String orderBy, String prefix, String defaultOrderBy) {
		if (orderBy == null || orderBy.trim().length() == 0) {
			return defaultOrderBy;
		}

		StringBuilder result = new StringBuilder(" ORDER BY ");
		String delimiter = "";
		String[] orderKeys = orderBy.split(",");
		for (int i = 0; i < orderKeys.length; i++) {
			if (orderKeys[i].startsWith("{")) {
				String key = StringUtils.remove(orderKeys[i], "{");
				key = StringUtils.remove(key, "}");
				result.append(delimiter).append(key.replaceAll("NullSafe", "").trim());
			} else {
				result.append(delimiter).append(prefix).append(".")
						.append(orderKeys[i].replaceAll("NullSafe", "").trim());
			}
			delimiter = ", ";
		}
		return result.toString();
	}

	protected Object convertCriteriaToByte(Object criteriaType) {
		if (criteriaType instanceof String) {
			try {
				return new Byte((String) criteriaType);
			} catch (NumberFormatException e) {
				return criteriaType;
			}
		}
		return criteriaType;
	}

	protected String getEntityName(T theEntity) {
		return StringUtils.uncapitalize(theEntity.getClass().getSimpleName());
	}

	/**
	 * Utility method to create a Clob object from a String using the hibernate
	 * specific implementation
	 * 
	 * @param clobContent the Clob content as a String
	 * @return the Clob object
	 */
	public Clob createClob(String clobContent) {
		return Hibernate.getLobCreator(getCurrentSession()).createClob(clobContent);
	}

	/**
	 * Utility method to create a Clob object from a Reader using the hibernate
	 * specific implementation
	 * 
	 * @param reader the Clob content as a String
	 * @param length the number of characters in the stream
	 * @return the Clob object
	 */
	public Clob createClob(Reader reader, long length) {
		// future versiona of Hibernate use long for length
		// the current method is deprecated from 3.5
		return Hibernate.getLobCreator(getCurrentSession()).createClob(reader, (int) length);
	}

	/**
	 * Utility method to create a Blob object from a byte array using the hibernate
	 * specific implementation. The returned object will be initially immutable.
	 * 
	 * @param bytes a byte array
	 * @return the Blob object
	 */
	public Blob createBlob(byte[] bytes) {
		return Hibernate.getLobCreator(getCurrentSession()).createBlob(bytes);
	}

	/**
	 * Utility method to create a Blob object from an InputStram using the hibernate
	 * specific implementation. The returned object will be initially immutable.
	 * 
	 * @param stream a binary stream
	 * @return the Blob object
	 * @throws IOException Errors
	 */
	public Blob createBlob(InputStream stream) throws IOException {
		return Hibernate.getLobCreator(getCurrentSession()).createBlob(stream, stream.available());
	}

	/**
	 * Utility method to create a Blob object from an InputStram using the hibernate
	 * specific implementation. The returned object will be initially immutable.
	 * 
	 * @param stream a binary stream
	 * @param length the number of bytes in the stream
	 * @return the Blob object
	 */
	public Blob createBlob(InputStream stream, int length) {
		return Hibernate.getLobCreator(getCurrentSession()).createBlob(stream, length);
	}

	public Object getCriteriaObject(Map<String, Object> criteria, String name) {
		return criteria.get(name);
	}

	public boolean hasCriteria(Map<String, Object> criteria, String name) {
		return !StringUtils.isEmpty(getCriteriaAsString(criteria, name));
	}

	public String getCriteriaAsString(Map<String, Object> criteria, String name) {
		Object result = getCriteriaObject(criteria, name);
		if (result != null) {
			return result.toString();
		}
		return null;
	}

	protected Collection<T> findByParent(String fromQuery, String entityName, String parentEntityName,
			Entity parentEntity, String operationName) {
		StringBuilder sql = new StringBuilder(fromQuery);
		sql.append(" WHERE ").append(entityName).append(".").append(parentEntityName).append(" = :")
				.append(parentEntityName);
		extendTheQueryCriteriaWithAcl(sql, " AND ", entityName, operationName);
		sql.append(getOrderBy());
		Query query = getCurrentSession().createQuery(sql.toString());
		query.setParameter(parentEntityName, parentEntity);
		setTheStatementFilterForCriteriaWithAcl(query, entityName, operationName);
		return list(query);
	}
	
	protected String convertDateToString(Object formTo) {
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

	    // Convert the Date object to a String
	    String str = sdf.format((Date)formTo);
	    return str;
	}
	
	protected int setStatementFilterLikeFormatted(Query query, int counter, Object fieldFrom,
			String likeFilterMethodLeftSymbolParam, String likeFilterMethodRightSymbolParam, String parameterName) {
		return setStatementFilterLikeFormatted(query, counter, fieldFrom, likeFilterMethodLeftSymbolParam,
				likeFilterMethodRightSymbolParam, true, parameterName);
	}
	
	protected int setStatementFilterLikeFormatted(Query query, int counter, Object fieldFrom,
			String likeFilterMethodLeftSymbolParam, String likeFilterMethodRightSymbolParam, boolean useUpper,
			String parameterName) {
		if (fieldFrom != null) {
			if (logger.isDebugEnabled()) {
				logger.debug("Setting String parameter " + parameterName + " to value <" + fieldFrom + ">");
			}
			query.setString(parameterName, sqlCommandOption.getStatementFilterLike(convertDateToString(fieldFrom),
					likeFilterMethodLeftSymbolParam, likeFilterMethodRightSymbolParam, useUpper));
		}
		return counter;
	}
}
