All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.blazebit.persistence.impl.hibernate.HibernateExtendedQuerySupport Maven / Gradle / Ivy

There is a newer version: 1.6.14
Show newest version
package com.blazebit.persistence.impl.hibernate;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.logging.Logger;

import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.NonUniqueResultException;
import javax.persistence.PersistenceException;
import javax.persistence.Query;
import javax.persistence.metamodel.EntityType;

import com.blazebit.persistence.spi.ConfigurationSource;
import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.TypeMismatchException;
import org.hibernate.engine.query.spi.HQLQueryPlan;
import org.hibernate.engine.query.spi.QueryPlanCache;
import org.hibernate.engine.spi.*;
import org.hibernate.event.service.spi.EventListenerRegistry;
import org.hibernate.event.spi.AutoFlushEvent;
import org.hibernate.event.spi.AutoFlushEventListener;
import org.hibernate.event.spi.EventSource;
import org.hibernate.event.spi.EventType;
import org.hibernate.hql.internal.QueryExecutionRequestException;
import org.hibernate.hql.internal.ast.ParameterTranslationsImpl;
import org.hibernate.hql.internal.ast.exec.BasicExecutor;
import org.hibernate.hql.internal.ast.exec.DeleteExecutor;
import org.hibernate.hql.internal.ast.exec.StatementExecutor;
import org.hibernate.hql.internal.ast.tree.FromElement;
import org.hibernate.hql.internal.ast.tree.QueryNode;
import org.hibernate.hql.internal.ast.tree.SelectClause;
import org.hibernate.hql.spi.ParameterTranslations;
import org.hibernate.hql.spi.QueryTranslator;
import org.hibernate.internal.util.collections.BoundedConcurrentHashMap;
import org.hibernate.loader.hql.QueryLoader;
import org.hibernate.param.ParameterSpecification;
import org.hibernate.persister.entity.AbstractEntityPersister;
import org.hibernate.type.ManyToOneType;
import org.hibernate.type.Type;

import com.blazebit.apt.service.ServiceProvider;
import com.blazebit.persistence.CommonQueryBuilder;
import com.blazebit.persistence.ReturningResult;
import com.blazebit.persistence.spi.CteQueryWrapper;
import com.blazebit.persistence.spi.DbmsDialect;
import com.blazebit.persistence.spi.ExtendedQuerySupport;
import com.blazebit.reflection.ReflectionUtils;

@ServiceProvider(ExtendedQuerySupport.class)
public class HibernateExtendedQuerySupport implements ExtendedQuerySupport {

    private static final Logger LOG = Logger.getLogger(HibernateExtendedQuerySupport.class.getName());
    
    private final ConcurrentMap> queryPlanCachesCache = new ConcurrentHashMap>();
    private final HibernateAccess hibernateAccess;
    
	public HibernateExtendedQuerySupport() {
	    Iterator serviceIter = ServiceLoader.load(HibernateAccess.class).iterator();
	    if (!serviceIter.hasNext()) {
	        throw new IllegalStateException("Hibernate integration was not found on the class path!");
	    }
	    this.hibernateAccess = serviceIter.next();
    }

    @Override
	public String getSql(EntityManager em, Query query) {
    	SessionImplementor session = em.unwrap(SessionImplementor.class);
		HQLQueryPlan queryPlan = getOriginalQueryPlan(session, query);
		String sql = queryPlan.getSqlStrings()[0];
		return sql;
	}

    @Override
    public List getCascadingDeleteSql(EntityManager em, Query query) {
        SessionImplementor session = em.unwrap(SessionImplementor.class);
        HQLQueryPlan queryPlan = getOriginalQueryPlan(session, query);
        if (queryPlan.getTranslators().length > 1) {
            throw new IllegalArgumentException("No support for multiple translators yet!");
        }
        QueryTranslator queryTranslator = queryPlan.getTranslators()[0];
        BasicExecutor executor = getStatementExecutor(queryTranslator);
        if (executor == null || !(executor instanceof DeleteExecutor)) {
            return Collections.EMPTY_LIST;
        }

        List deletes = getField(executor, "deletes");
        if (deletes == null) {
            return Collections.EMPTY_LIST;
        }
        return deletes;
    }
    
    private HQLQueryPlan getOriginalQueryPlan(SessionImplementor session, Query query) {
        SessionFactoryImplementor sfi = session.getFactory();
        org.hibernate.Query hibernateQuery = query.unwrap(org.hibernate.Query.class);
        
        Map namedParams = new HashMap(hibernateAccess.getNamedParams(hibernateQuery));
        String queryString = hibernateAccess.expandParameterLists(session, hibernateQuery, namedParams);
        return sfi.getQueryPlanCache().getHQLQueryPlan(queryString, false, Collections.EMPTY_MAP);
    }

    @Override
	public String[] getColumnNames(EntityManager em, EntityType entityType, String attributeName) {
        SessionImplementor session = em.unwrap(SessionImplementor.class);
        SessionFactoryImplementor sfi = session.getFactory();
	    return ((AbstractEntityPersister) sfi.getClassMetadata(entityType.getJavaType())).getPropertyColumnNames(attributeName);
	}

    @Override
    public String getSqlAlias(EntityManager em, Query query, String alias) {
        SessionImplementor session = em.unwrap(SessionImplementor.class);
        HQLQueryPlan plan = getOriginalQueryPlan(session, query);
        if (plan.getTranslators().length > 1) {
            throw new IllegalArgumentException("No support for multiple translators yet!");
        }
        QueryTranslator translator = plan.getTranslators()[0];
        
        QueryNode queryNode = getField(translator, "sqlAst");
        FromElement fromElement = queryNode.getFromClause().getFromElement(alias);
        
        if (fromElement == null) {
            throw new IllegalArgumentException("The alias " + alias + " could not be found in the query: " + query);
        }
        
        return fromElement.getTableAlias();
    }

    @Override
    public int getSqlSelectAliasPosition(EntityManager em, Query query, String alias) {
        SessionImplementor session = em.unwrap(SessionImplementor.class);
        HQLQueryPlan plan = getOriginalQueryPlan(session, query);
        if (plan.getTranslators().length > 1) {
            throw new IllegalArgumentException("No support for multiple translators yet!");
        }
        QueryTranslator translator = plan.getTranslators()[0];

        try {
            QueryNode queryNode = getField(translator, "sqlAst");
            
            String[] aliases = queryNode.getSelectClause().getQueryReturnAliases();

            for (int i = 0; i < aliases.length; i++) {
                if (alias.equals(aliases[i])) {
                    // the ordinal is 1 based
                    return i + 1;
                }
            }
            
            return -1;
        } catch (Exception e1) {
            throw new RuntimeException(e1);
        }
    }
    
    @Override
    public int getSqlSelectAttributePosition(EntityManager em, Query query, String expression) {
        if (expression.contains(".")) {
            // TODO: implement
            throw new UnsupportedOperationException("Embeddables are not yet supported!");
        }
        
        SessionImplementor session = em.unwrap(SessionImplementor.class);
        HQLQueryPlan plan = getOriginalQueryPlan(session, query);
        if (plan.getTranslators().length > 1) {
            throw new IllegalArgumentException("No support for multiple translators yet!");
        }
        QueryTranslator translator = plan.getTranslators()[0];

        try {
            QueryNode queryNode = getField(translator, "sqlAst");
            SelectClause selectClause = queryNode.getSelectClause();
            Type[] queryReturnTypes = selectClause.getQueryReturnTypes();
            
            boolean found = false;
            // The ordinal is 1 based
            int position = 1;
            for (int i = 0; i < queryReturnTypes.length; i++) {
                Type t = queryReturnTypes[i];
                if (t instanceof ManyToOneType) {
                    ManyToOneType manyToOneType = (ManyToOneType) t;
                    AbstractEntityPersister persister = (AbstractEntityPersister) session.getFactory().getEntityPersister(manyToOneType.getAssociatedEntityName());
                    
                    int propertyIndex = persister.getPropertyIndex(expression);
                    found = true;
                    for (int j = 0; j < propertyIndex; j++) {
                        position += persister.getPropertyColumnNames(j).length;
                    }
                    // Increment to the actual property position
                    position++;
                } else {
                    position++;
                }
            }
            
            if (found) {
                return position;
            }
            
            return -1;
        } catch (Exception e1) {
            throw new RuntimeException(e1);
        }
    }

    @Override
    @SuppressWarnings("rawtypes")
	public List getResultList(com.blazebit.persistence.spi.ServiceProvider serviceProvider, List participatingQueries, Query query, String sqlOverride) {
        EntityManager em = serviceProvider.getService(EntityManager.class);
		try {
			return list(serviceProvider, em, participatingQueries, query, sqlOverride);
		} catch (QueryExecutionRequestException he) {
            LOG.severe("Could not execute the following SQL query: " + sqlOverride);
			throw new IllegalStateException(he);
		} catch(TypeMismatchException e) {
            LOG.severe("Could not execute the following SQL query: " + sqlOverride);
			throw new IllegalArgumentException(e);
		} catch (HibernateException he) {
		    LOG.severe("Could not execute the following SQL query: " + sqlOverride);
			throw hibernateAccess.convert(em, he);
		}
	}
	
	@Override
    @SuppressWarnings({ "rawtypes", "unchecked" })
	public Object getSingleResult(com.blazebit.persistence.spi.ServiceProvider serviceProvider, List participatingQueries, Query query, String sqlOverride) {
        EntityManager em = serviceProvider.getService(EntityManager.class);
		try {
			final List result = list(serviceProvider, em, participatingQueries, query, sqlOverride);

			if (result.size() == 0) {
				NoResultException nre = new NoResultException("No entity found for query");
				hibernateAccess.handlePersistenceException(em, nre);
				throw nre;
			} else if (result.size() > 1) {
				final Set uniqueResult = new HashSet(result);
				if (uniqueResult.size() > 1) {
					NonUniqueResultException nure = new NonUniqueResultException("result returns more than one element");
					hibernateAccess.handlePersistenceException(em, nure);
					throw nure;
				} else {
					return uniqueResult.iterator().next();
				}
			} else {
				return result.get(0);
			}
		} catch (QueryExecutionRequestException he) {
            LOG.severe("Could not execute the following SQL query: " + sqlOverride);
			throw new IllegalStateException(he);
		} catch(TypeMismatchException e) {
            LOG.severe("Could not execute the following SQL query: " + sqlOverride);
			throw new IllegalArgumentException(e);
		} catch (HibernateException he) {
            LOG.severe("Could not execute the following SQL query: " + sqlOverride);
			throw hibernateAccess.convert(em, he);
		}
	}

    @SuppressWarnings("rawtypes")
    private List list(com.blazebit.persistence.spi.ServiceProvider serviceProvider, EntityManager em, List participatingQueries, Query query, String finalSql) {
        SessionImplementor session = em.unwrap(SessionImplementor.class);
        SessionFactoryImplementor sfi = session.getFactory();

        if (session.isClosed()) {
            throw new PersistenceException("Entity manager is closed!");
        }

        QueryPlanCacheKey cacheKey = createCacheKey(participatingQueries);
        CacheEntry queryPlanEntry = getQueryPlan(sfi, query, cacheKey);
        HQLQueryPlan queryPlan = queryPlanEntry.getValue();

        // Create combined query parameters
        QueryParamEntry queryParametersEntry = createQueryParameters(em, participatingQueries);
        QueryParameters queryParameters = queryParametersEntry.queryParameters;
        
        if (!queryPlanEntry.isFromCache()) {
            prepareQueryPlan(queryPlan, queryParametersEntry.specifications, finalSql, session, participatingQueries.get(participatingQueries.size() - 1), false, serviceProvider.getService(DbmsDialect.class));
            queryPlan = putQueryPlanIfAbsent(sfi, cacheKey, queryPlan);
        }
        
        return hibernateAccess.performList(queryPlan, session, queryParameters);
    }

    @Override
    public int executeUpdate(com.blazebit.persistence.spi.ServiceProvider serviceProvider, List participatingQueries, Query query, String finalSql) {
        EntityManager em = serviceProvider.getService(EntityManager.class);
        SessionImplementor session = em.unwrap(SessionImplementor.class);
        SessionFactoryImplementor sfi = session.getFactory();

        if (session.isClosed()) {
            throw new PersistenceException("Entity manager is closed!");
        }

        Integer firstResult = null;
        Integer maxResults = null;

        if (query.getFirstResult() > 0) {
            firstResult = query.getFirstResult();
        }
        if (query.getMaxResults() != Integer.MAX_VALUE) {
            maxResults = query.getMaxResults();
        }

        QueryPlanCacheKey cacheKey = createCacheKey(participatingQueries, firstResult, maxResults);
        CacheEntry queryPlanEntry = getQueryPlan(sfi, query, cacheKey);
        HQLQueryPlan queryPlan = queryPlanEntry.getValue();

        // Create combined query parameters
        QueryParamEntry queryParametersEntry = createQueryParameters(em, participatingQueries);
        QueryParameters queryParameters = queryParametersEntry.queryParameters;

        if (!queryPlanEntry.isFromCache()) {
            prepareQueryPlan(queryPlan, queryParametersEntry.specifications, finalSql, session, participatingQueries.get(participatingQueries.size() - 1), true, serviceProvider.getService(DbmsDialect.class));
            queryPlan = putQueryPlanIfAbsent(sfi, cacheKey, queryPlan);
        }
        
        if (queryPlan.getReturnMetadata() == null) {
            return hibernateAccess.performExecuteUpdate(queryPlan, session, queryParameters);
        }

        boolean caseInsensitive = !Boolean.valueOf(serviceProvider.getService(ConfigurationSource.class).getProperty("com.blazebit.persistence.returning_clause_case_sensitive"));
        String exampleQuerySql = queryPlan.getSqlStrings()[0];
        String[][] returningColumns = getReturningColumns(caseInsensitive, exampleQuerySql);
        
        try {
            @SuppressWarnings("unchecked")
            List results = hibernateAccess.performList(queryPlan, wrapSession(session, true, returningColumns, null), queryParameters);
            
            if (results.size() != 1) {
                throw new IllegalArgumentException("Expected size 1 but was: " + results.size());
            }
            
            Number count = (Number) results.get(0);
            return count.intValue();
        } catch (QueryExecutionRequestException he) {
            LOG.severe("Could not execute the following SQL query: " + finalSql);
            throw new IllegalStateException(he);
        } catch(TypeMismatchException e) {
            LOG.severe("Could not execute the following SQL query: " + finalSql);
            throw new IllegalArgumentException(e);
        } catch (HibernateException he) {
            LOG.severe("Could not execute the following SQL query: " + finalSql);
            hibernateAccess.throwPersistenceException(em, he);
            return 0;
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public ReturningResult executeReturning(com.blazebit.persistence.spi.ServiceProvider serviceProvider, List participatingQueries, Query exampleQuery, String sqlOverride) {
        DbmsDialect dialect = serviceProvider.getService(DbmsDialect.class);
        EntityManager em = serviceProvider.getService(EntityManager.class);
        SessionImplementor session = em.unwrap(SessionImplementor.class);
        SessionFactoryImplementor sfi = session.getFactory();

        if (session.isClosed()) {
            throw new PersistenceException("Entity manager is closed!");
        }
        
        // Create plan for example query
        QueryPlanCacheKey cacheKey = createCacheKey(participatingQueries);
        CacheEntry queryPlanEntry = getQueryPlan(sfi, exampleQuery, cacheKey);
        HQLQueryPlan queryPlan = queryPlanEntry.getValue();
        String exampleQuerySql = queryPlan.getSqlStrings()[0];
        
        StringBuilder sqlSb = new StringBuilder(sqlOverride.length() + 100);
        sqlSb.append(sqlOverride);

        boolean caseInsensitive = !Boolean.valueOf(serviceProvider.getService(ConfigurationSource.class).getProperty("com.blazebit.persistence.returning_clause_case_sensitive"));
        String[][] returningColumns = getReturningColumns(caseInsensitive, exampleQuerySql);
        boolean generatedKeys = !dialect.supportsReturningColumns();
        String finalSql = sqlSb.toString();
        
        // Create combined query parameters
        QueryParamEntry queryParametersEntry = createQueryParameters(em, participatingQueries);
        QueryParameters queryParameters = queryParametersEntry.queryParameters;
        
        try {
            HibernateReturningResult returningResult = new HibernateReturningResult();
            if (!queryPlanEntry.isFromCache()) {
                prepareQueryPlan(queryPlan, queryParametersEntry.specifications, finalSql, session, participatingQueries.get(participatingQueries.size() - 1), true, serviceProvider.getService(DbmsDialect.class));
                queryPlan = putQueryPlanIfAbsent(sfi, cacheKey, queryPlan);
            }

            if (queryPlan.getTranslators().length > 1) {
                throw new IllegalArgumentException("No support for multiple translators yet!");
            }

            QueryTranslator queryTranslator = queryPlan.getTranslators()[0];
            
            // Extract query loader for native listing
            QueryLoader queryLoader = getField(queryTranslator, "queryLoader");
            
            // Do the native list operation with custom session and combined parameters
            
            /*
             * NATIVE LIST START
             */
            hibernateAccess.checkTransactionSynchStatus(session);
            queryParameters.validateParameters();
            AutoFlushEvent event = new AutoFlushEvent(queryPlan.getQuerySpaces(), (EventSource) session);
            for (AutoFlushEventListener listener : listeners(sfi, EventType.AUTO_FLUSH) ) {
                listener.onAutoFlush( event );
            }

            List results = Collections.EMPTY_LIST;
            boolean success = false;

            try {
                results = hibernateAccess.list(queryLoader, wrapSession(session, generatedKeys, returningColumns, returningResult), queryParameters);
                success = true;
            } catch (QueryExecutionRequestException he) {
                LOG.severe("Could not execute the following SQL query: " + finalSql);
                throw new IllegalStateException(he);
            } catch(TypeMismatchException e) {
                LOG.severe("Could not execute the following SQL query: " + finalSql);
                throw new IllegalArgumentException(e);
            } catch (HibernateException he) {
                LOG.severe("Could not execute the following SQL query: " + finalSql);
                throw hibernateAccess.convert(em, he);
            } finally {
                hibernateAccess.afterTransaction(session, success);
            }
            /*
             * NATIVE LIST END
             */
            
            returningResult.setResultList(results);
            return returningResult;
        } catch (Exception e1) {
            throw new RuntimeException(e1);
        }
    }

    private static String[][] getReturningColumns(boolean caseInsensitive, String exampleQuerySql) {
        int fromIndex = exampleQuerySql.indexOf("from");
        int selectIndex = exampleQuerySql.indexOf("select");
        String[] selectItems = splitSelectItems(exampleQuerySql.subSequence(selectIndex + "select".length() + 1, fromIndex));
        String[][] returningColumns = new String[selectItems.length][2];
        
        for (int i = 0; i < selectItems.length; i++) {
            String selectItemWithAlias = selectItems[i].substring(selectItems[i].lastIndexOf('.') + 1);
            if (caseInsensitive) {
            	returningColumns[i][0] = selectItemWithAlias.substring(0, selectItemWithAlias.indexOf(' ')).toLowerCase();
            } else {
            	returningColumns[i][0] = selectItemWithAlias.substring(0, selectItemWithAlias.indexOf(' '));
            }
            returningColumns[i][1] = selectItemWithAlias.substring(selectItemWithAlias.lastIndexOf(' ') + 1);
        }
        
        return returningColumns;
    }
    
    private static String[] splitSelectItems(CharSequence itemsString) {
        List selectItems = new ArrayList();
        StringBuilder sb = new StringBuilder();
        int parenthesis = 0;
        boolean text = false;
        
        int i = 0;
        int length = itemsString.length();
        while (i < length) {
            char c = itemsString.charAt(i);
            
            if (text) {
                if (c == '(') {
                    parenthesis++;
                } else if (c == ')') {
                    parenthesis--;
                } else if (parenthesis == 0 && c == ',') {
                    selectItems.add(trim(sb));
                    sb.setLength(0);
                    text = false;
                    
                    i++;
                    continue;
                }
                
                sb.append(c);
            } else {
                if (Character.isWhitespace(c)) {
                    // skip whitespace
                } else {
                    sb.append(c);
                    text = true;
                }
            }
            
            i++;
        }
        
        if (text) {
            selectItems.add(trim(sb));
        }
        
        return selectItems.toArray(new String[selectItems.size()]);
    }

    private static String trim(StringBuilder sb) {
        int i = sb.length() - 1;
        while (i >= 0) {
            if (!Character.isWhitespace(sb.charAt(i))) {
                break;
            } else {
                i--;
            }
        }
        
        return sb.substring(0, i + 1);
    }

    private  Iterable listeners(SessionFactoryImplementor factory, EventType type) {
        return factory.getServiceRegistry().getService(EventListenerRegistry.class).getEventListenerGroup(type).listeners();
    }
    
    private QueryParamEntry createQueryParameters(EntityManager em, List participatingQueries) {
        List parameterSpecifications = new ArrayList();
        
        List types = new ArrayList();
        List values = new ArrayList();
        Map namedParams = new LinkedHashMap();
        Serializable collectionKey = null;
        LockOptions lockOptions = new LockOptions();
        RowSelection rowSelection = new RowSelection();
        boolean readOnly = false; // TODO: readonly?
        boolean cacheable = false; // TODO: cacheable?
        String cacheRegion = null;
        String comment = null;
        List queryHints = null;

        for (QueryParamEntry queryParamEntry : getQueryParamEntries(em, participatingQueries)) {
            QueryParameters participatingQueryParameters = queryParamEntry.queryParameters;
            // Merge parameters
            Collections.addAll(types, participatingQueryParameters.getPositionalParameterTypes());
            Collections.addAll(values, participatingQueryParameters.getPositionalParameterValues());
            namedParams.putAll(participatingQueryParameters.getNamedParameters());
            parameterSpecifications.addAll(queryParamEntry.specifications);

            // Merge row selections
            if (participatingQueryParameters.hasRowSelection()) {
                RowSelection original = queryParamEntry.queryParameters.getRowSelection();
                // Check for defaults

                /***************************************************************************
                 * TODO: Either we do it like this, or let these values be passed in separately
                 **************************************************************************/

                if (rowSelection.getFirstRow() == null || rowSelection.getFirstRow() < 1) {
                    rowSelection.setFirstRow(original.getFirstRow());
                } else if (original.getFirstRow() != null && original.getFirstRow() > 0 && !original.getFirstRow().equals(rowSelection.getFirstRow())) {
                    throw new IllegalStateException("Multiple row selections not allowed!");
                }
                if (rowSelection.getMaxRows() == null || rowSelection.getMaxRows() == Integer.MAX_VALUE) {
                    rowSelection.setMaxRows(original.getMaxRows());
                } else if (original.getMaxRows() != null && original.getMaxRows() != Integer.MAX_VALUE && !original.getMaxRows().equals(rowSelection.getMaxRows())) {
                    throw new IllegalStateException("Multiple row selections not allowed!");
                }


                if (rowSelection.getFetchSize() == null) {
                    rowSelection.setFetchSize(original.getFetchSize());
                } else if (original.getFetchSize() != null && !original.getFetchSize().equals(rowSelection.getFetchSize())) {
                    throw new IllegalStateException("Multiple row selections not allowed!");
                }
                if (rowSelection.getTimeout() == null) {
                    rowSelection.setTimeout(original.getTimeout());
                } else if (original.getTimeout() != null && !original.getTimeout().equals(rowSelection.getTimeout())) {
                    throw new IllegalStateException("Multiple row selections not allowed!");
                }
            }

            // Merge lock options
            LockOptions originalLockOptions = participatingQueryParameters.getLockOptions();
            if (originalLockOptions.getScope()) {
                lockOptions.setScope(true);
            }
            if (originalLockOptions.getLockMode() != LockMode.NONE) {
                if (lockOptions.getLockMode() != LockMode.NONE && lockOptions.getLockMode() != originalLockOptions.getLockMode()) {
                    throw new IllegalStateException("Multiple different lock modes!");
                }
                lockOptions.setLockMode(originalLockOptions.getLockMode());
            }
            if (originalLockOptions.getTimeOut() != -1) {
                if (lockOptions.getTimeOut() != -1 && lockOptions.getTimeOut() != originalLockOptions.getTimeOut()) {
                    throw new IllegalStateException("Multiple different lock timeouts!");
                }
                lockOptions.setTimeOut(originalLockOptions.getTimeOut());
            }
            @SuppressWarnings("unchecked")
            Iterator> aliasLockIter = participatingQueryParameters.getLockOptions().getAliasLockIterator();
            while (aliasLockIter.hasNext()) {
                Map.Entry entry = aliasLockIter.next();
                lockOptions.setAliasSpecificLockMode(entry.getKey(), entry.getValue());
            }
        }
        
        QueryParameters queryParameters = hibernateAccess.createQueryParameters(
                  types.toArray(new Type[types.size()]),
                  values.toArray(new Object[values.size()]),
                  namedParams,
                  lockOptions,
                  rowSelection,
                  true,
                  readOnly,
                  cacheable,
                  cacheRegion,
                  comment,
                  queryHints,
                  collectionKey == null ? null : new Serializable[] { collectionKey }
        );
        
        return new QueryParamEntry(queryParameters, parameterSpecifications);
    }

    private SessionImplementor wrapSession(SessionImplementor session, boolean generatedKeys, String[][] columns, HibernateReturningResult returningResult) {
        // We do all this wrapping to change the StatementPreparer that is returned by the JdbcCoordinator
        // Instead of calling executeQuery, we delegate to executeUpdate and then return the generated keys in the prepared statement wrapper that we apply
        return hibernateAccess.wrapSession(session, generatedKeys, columns, returningResult);
    }
    
    private List getQueryParamEntries(EntityManager em, List queries) {
        SessionImplementor session = em.unwrap(SessionImplementor.class);
        SessionFactoryImplementor sfi = session.getFactory();
        List result = new ArrayList(queries.size());
        Deque queryQueue = new LinkedList(queries);
        
        while (queryQueue.size() > 0) {
            Query q = queryQueue.remove();
            if (q instanceof CteQueryWrapper) {
                List participatingQueries = ((CteQueryWrapper) q).getParticipatingQueries();
                for (int i = participatingQueries.size() - 1; i > -1; i--) {
                    queryQueue.addFirst(participatingQueries.get(i));
                }
                continue;
            }
            
            org.hibernate.Query hibernateQuery = q.unwrap(org.hibernate.Query.class);

            Map namedParams = new HashMap(hibernateAccess.getNamedParams(hibernateQuery));
            String queryString = hibernateAccess.expandParameterLists(session, hibernateQuery, namedParams);
            
            HQLQueryPlan queryPlan = sfi.getQueryPlanCache().getHQLQueryPlan(queryString, false, Collections.EMPTY_MAP);

            if (queryPlan.getTranslators().length > 1) {
                throw new IllegalArgumentException("No support for multiple translators yet!");
            }
            QueryTranslator queryTranslator = queryPlan.getTranslators()[0];
            QueryParameters queryParameters;
            List specifications;
            
            try {
                queryParameters = hibernateAccess.getQueryParameters(hibernateQuery, namedParams);
                specifications = getField(queryTranslator, "collectedParameterSpecifications");
                
                // This only happens for modification queries
                if (specifications == null) {
                    BasicExecutor executor = getStatementExecutor(queryTranslator);
                    specifications = getField(executor, "parameterSpecifications");
                }
            } catch (Exception e1) {
                throw new RuntimeException(e1);
            }
            
            result.add(new QueryParamEntry(queryParameters, specifications));
        }
        
        return result;
    }
    
    private BasicExecutor getStatementExecutor(QueryTranslator queryTranslator) {
        return getField(queryTranslator, "statementExecutor");
    }
    
    private QueryTranslator prepareQueryPlan(HQLQueryPlan queryPlan, List queryParameterSpecifications, String finalSql, SessionImplementor session, Query lastQuery, boolean isModification, DbmsDialect dbmsDialect) {
        try {
            if (queryPlan.getTranslators().length > 1) {
                throw new IllegalArgumentException("No support for multiple translators yet!");
            }

            QueryTranslator queryTranslator = queryPlan.getTranslators()[0];
            // Override the sql in the query plan
            setField(queryTranslator, "sql", finalSql);

            QueryLoader queryLoader = getField(queryTranslator, "queryLoader");
            // INSERT statement does not have a query loader
            if (queryLoader != null) {
                setField(queryLoader, "factory", hibernateAccess.wrapSessionFactory(queryLoader.getFactory(), dbmsDialect));
            }

            // Modification queries keep the sql in the executor
            StatementExecutor executor = null;
            
            Field statementExectuor = null;
            boolean madeAccessible = false;
            
            try {
                statementExectuor = ReflectionUtils.getField(queryTranslator.getClass(), "statementExecutor");
                madeAccessible = !statementExectuor.isAccessible();
                
                if (madeAccessible) {
                    statementExectuor.setAccessible(true);
                }
                
                executor = (StatementExecutor) statementExectuor.get(queryTranslator);
                
                if (executor == null && isModification) {
                    // We have to set an executor
                    org.hibernate.Query lastHibernateQuery = lastQuery.unwrap(org.hibernate.Query.class);

                    Map namedParams = new HashMap(hibernateAccess.getNamedParams(lastHibernateQuery));
                    String queryString = hibernateAccess.expandParameterLists(session, lastHibernateQuery, namedParams);
                    
                    // Extract the executor from the last query which is the actual main query
                    HQLQueryPlan lastQueryPlan = session.getFactory().getQueryPlanCache().getHQLQueryPlan(queryString, false, Collections.EMPTY_MAP);
                    if (lastQueryPlan.getTranslators().length > 1) {
                        throw new IllegalArgumentException("No support for multiple translators yet!");
                    }
                    QueryTranslator lastQueryTranslator = lastQueryPlan.getTranslators()[0];
                    executor = (StatementExecutor) statementExectuor.get(lastQueryTranslator);
                    // Now we use this executor for our example query
                    statementExectuor.set(queryTranslator, executor);
                }
            } finally {
                if (madeAccessible) {
                    statementExectuor.setAccessible(false);
                }
            }
            
            if (executor != null) {
                setField(executor, "sql", finalSql);
                setField(executor, BasicExecutor.class, "parameterSpecifications", queryParameterSpecifications);
                
                if (executor instanceof DeleteExecutor && dbmsDialect.supportsWithClauseInModificationQuery()) {
                    setField(executor, "deletes", new ArrayList());
                }
            }
            
            // Prepare queryTranslator for aggregated parameters
            ParameterTranslations translations = new ParameterTranslationsImpl(queryParameterSpecifications);
            setField(queryTranslator, "paramTranslations", translations);
            setField(queryTranslator, "collectedParameterSpecifications", queryParameterSpecifications);

            return queryTranslator;
        } catch (Exception e1) {
            throw new RuntimeException(e1);
        }
    }
    
    private CacheEntry getQueryPlan(SessionFactoryImplementor sfi, Query query, QueryPlanCacheKey cacheKey) {
        BoundedConcurrentHashMap queryPlanCache = getQueryPlanCache(sfi);
        HQLQueryPlan queryPlan = queryPlanCache.get(cacheKey);
        boolean fromCache = true;
        if (queryPlan == null) {
            fromCache = false;
            queryPlan = createQueryPlan(sfi, query);
        }
        
        return new CacheEntry(queryPlan, fromCache);
    }
    
    private HQLQueryPlan putQueryPlanIfAbsent(SessionFactoryImplementor sfi, QueryPlanCacheKey cacheKey, HQLQueryPlan queryPlan) {
        BoundedConcurrentHashMap queryPlanCache = getQueryPlanCache(sfi);
        HQLQueryPlan oldQueryPlan = queryPlanCache.putIfAbsent(cacheKey, queryPlan);
        if (oldQueryPlan != null) {
            queryPlan = oldQueryPlan;
        }
        
        return queryPlan;
    }
    
    private HQLQueryPlan createQueryPlan(SessionFactoryImplementor sfi, Query query) {
        org.hibernate.Query hibernateQuery = query.unwrap(org.hibernate.Query.class);
        String queryString = hibernateQuery.getQueryString();
        return new HQLQueryPlan(queryString, false, Collections.EMPTY_MAP, sfi);
    }
    
    private BoundedConcurrentHashMap getQueryPlanCache(SessionFactoryImplementor sfi) {
        BoundedConcurrentHashMap queryPlanCache = queryPlanCachesCache.get(sfi);
        if (queryPlanCache == null) {
            queryPlanCache = new BoundedConcurrentHashMap(QueryPlanCache.DEFAULT_QUERY_PLAN_MAX_COUNT, 20, BoundedConcurrentHashMap.Eviction.LIRS);
            BoundedConcurrentHashMap oldQueryPlanCache = queryPlanCachesCache.putIfAbsent(sfi, queryPlanCache);
            if (oldQueryPlanCache != null) {
                queryPlanCache = oldQueryPlanCache;
            }
        }
        
        return queryPlanCache;
    }

    private QueryPlanCacheKey createCacheKey(List queries) {
        return createCacheKey(queries, null, null);
    }
    
    private QueryPlanCacheKey createCacheKey(List queries, Integer firstResult, Integer maxResults) {
        List parts = new ArrayList(queries.size());
        addAll(queries, parts);
        return new QueryPlanCacheKey(parts, firstResult, maxResults);
    }
    
    private void addAll(List queries, List parts) {
        for (int i = 0; i< queries.size(); i++) {
            Query query = queries.get(i);
            
            if (query instanceof CteQueryWrapper) {
                addAll(((CteQueryWrapper) query).getParticipatingQueries(), parts);
            } else {
                parts.add(query.unwrap(org.hibernate.Query.class).getQueryString());
            }
        }
    }
    
    private static class QueryParamEntry {
        final QueryParameters queryParameters;
        final List specifications;
        
        public QueryParamEntry(QueryParameters queryParameters, List specifications) {
            this.queryParameters = queryParameters;
            this.specifications = specifications;
        }
    }
    
    private static class QueryPlanCacheKey {
        final List cacheKeyParts;
        final Integer firstResult;
        final Integer maxResults;

        public QueryPlanCacheKey(List cacheKeyParts) {
            this.cacheKeyParts = cacheKeyParts;
            this.firstResult = null;
            this.maxResults = null;
        }

        public QueryPlanCacheKey(List cacheKeyParts, Integer firstResult, Integer maxResults) {
            this.cacheKeyParts = cacheKeyParts;
            this.firstResult = firstResult;
            this.maxResults = maxResults;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (!(o instanceof QueryPlanCacheKey)) return false;

            QueryPlanCacheKey that = (QueryPlanCacheKey) o;

            if (!cacheKeyParts.equals(that.cacheKeyParts)) return false;
            if (firstResult != null ? !firstResult.equals(that.firstResult) : that.firstResult != null) return false;
            return maxResults != null ? maxResults.equals(that.maxResults) : that.maxResults == null;

        }

        @Override
        public int hashCode() {
            int result = cacheKeyParts.hashCode();
            result = 31 * result + (firstResult != null ? firstResult.hashCode() : 0);
            result = 31 * result + (maxResults != null ? maxResults.hashCode() : 0);
            return result;
        }
    }
    
    private static class CacheEntry {
        
        private final T value;
        private final boolean fromCache;
        
        public CacheEntry(T value, boolean fromCache) {
            this.value = value;
            this.fromCache = fromCache;
        }

        public T getValue() {
            return value;
        }

        public boolean isFromCache() {
            return fromCache;
        }
    }
    
    @SuppressWarnings("unchecked")
    private  T getField(Object object, String field) {
        boolean madeAccessible = false;
        Field f = null;
        try {
            f = ReflectionUtils.getField(object.getClass(), field);
            madeAccessible = !f.isAccessible();
            
            if (madeAccessible) {
                f.setAccessible(true);
            }
            return (T) f.get(object);
        } catch (Exception e1) {
            throw new RuntimeException(e1);
        } finally {
            if (madeAccessible) {
                f.setAccessible(false);
            }
        }
    }

    private void setField(Object object, String field, Object value) {
        setField(object, object.getClass(), field, value);
    }
    
    private void setField(Object object, Class clazz, String field, Object value) {
        boolean madeAccessible = false;
        Field f = null;
        try {
            f = ReflectionUtils.getField(clazz, field);
            madeAccessible = !f.isAccessible();
            
            if (madeAccessible) {
                f.setAccessible(true);
            }
            
            f.set(object, value);
        } catch (Exception e1) {
            throw new RuntimeException(e1);
        } finally {
            if (madeAccessible) {
                f.setAccessible(false);
            }
        }
    }

}