/*
* Copyright 2014 - 2017 Blazebit.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.blazebit.persistence.impl.hibernate;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Proxy;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import com.blazebit.persistence.spi.DbmsDialect;
import com.blazebit.reflection.ReflectionUtils;
import org.hibernate.HibernateException;
import org.hibernate.LockOptions;
import org.hibernate.Query;
import org.hibernate.dialect.Dialect;
import org.hibernate.ejb.HibernateEntityManagerImplementor;
import org.hibernate.engine.jdbc.spi.JdbcCoordinator;
import org.hibernate.engine.query.spi.HQLQueryPlan;
import org.hibernate.engine.query.spi.ParameterMetadata;
import org.hibernate.engine.spi.QueryParameters;
import org.hibernate.engine.spi.RowSelection;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.engine.spi.TypedValue;
import org.hibernate.engine.transaction.spi.TransactionCoordinator;
import org.hibernate.event.spi.EventSource;
import com.blazebit.apt.service.ServiceProvider;
import org.hibernate.hql.internal.classic.ParserHelper;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.loader.hql.QueryLoader;
import org.hibernate.type.Type;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceException;
@ServiceProvider(HibernateAccess.class)
public class Hibernate4Access implements HibernateAccess {
private static final Logger LOG = Logger.getLogger(HibernateExtendedQuerySupport.class.getName());
@Override
public SessionImplementor wrapSession(SessionImplementor session, DbmsDialect dbmsDialect, String[][] columns, int[] returningSqlTypes, HibernateReturningResult<?> returningResult) {
TransactionCoordinator transactionCoordinator = session.getTransactionCoordinator();
JdbcCoordinator jdbcCoordinator = transactionCoordinator.getJdbcCoordinator();
Object jdbcCoordinatorProxy = Proxy.newProxyInstance(jdbcCoordinator.getClass().getClassLoader(), new Class[]{ JdbcCoordinator.class }, new JdbcCoordinatorInvocationHandler(jdbcCoordinator, session.getFactory(), dbmsDialect, columns, returningSqlTypes, returningResult));
Object transactionCoordinatorProxy = Proxy.newProxyInstance(transactionCoordinator.getClass().getClassLoader(), new Class[]{ TransactionCoordinator.class }, new Hibernate4TransactionCoordinatorInvocationHandler(transactionCoordinator, jdbcCoordinatorProxy));
Object sessionProxy = Proxy.newProxyInstance(session.getClass().getClassLoader(), new Class[]{ SessionImplementor.class, EventSource.class }, new Hibernate4SessionInvocationHandler(session, transactionCoordinatorProxy));
return (SessionImplementor) sessionProxy;
}
public SessionFactoryImplementor wrapSessionFactory(SessionFactoryImplementor sessionFactory, DbmsDialect dbmsDialect) {
Object dialectProxy = new Hibernate4LimitHandlingDialect(sessionFactory.getDialect(), dbmsDialect);
Object sessionFactoryProxy = Proxy.newProxyInstance(sessionFactory.getClass().getClassLoader(), new Class[]{ SessionFactoryImplementor.class }, new Hibernate4SessionFactoryInvocationHandler(sessionFactory, dialectProxy));
return (SessionFactoryImplementor) sessionFactoryProxy;
}
@Override
public void checkTransactionSynchStatus(SessionImplementor session) {
TransactionCoordinator coordinator = session.getTransactionCoordinator();
coordinator.pulse();
coordinator.getSynchronizationCallbackCoordinator().processAnyDelayedAfterCompletion();
}
@Override
public void afterTransaction(SessionImplementor session, boolean success) {
TransactionCoordinator coordinator = session.getTransactionCoordinator();
if (!session.isTransactionInProgress() ) {
coordinator.getJdbcCoordinator().afterTransaction();
}
coordinator.getSynchronizationCallbackCoordinator().processAnyDelayedAfterCompletion();
}
@Override
@SuppressWarnings({ "unchecked" })
public List<Object[]> list(QueryLoader queryLoader, SessionImplementor sessionImplementor, QueryParameters queryParameters) {
return queryLoader.list(sessionImplementor, queryParameters);
}
@Override
public List<Object> performList(HQLQueryPlan queryPlan, SessionImplementor sessionImplementor, QueryParameters queryParameters) {
return queryPlan.performList(queryParameters, sessionImplementor);
}
@Override
public int performExecuteUpdate(HQLQueryPlan queryPlan, SessionImplementor sessionImplementor, QueryParameters queryParameters) {
return queryPlan.performExecuteUpdate(queryParameters, sessionImplementor);
}
@Override
public QueryParameters getQueryParameters(Query hibernateQuery, Map<String, TypedValue> namedParams) {
return ((org.hibernate.internal.AbstractQueryImpl) hibernateQuery).getQueryParameters(namedParams);
}
@Override
public Map<String, TypedValue> getNamedParams(Query hibernateQuery) {
return getField(hibernateQuery, "namedParameters");
}
@Override
public String expandParameterLists(SessionImplementor session, org.hibernate.Query hibernateQuery, Map<String, TypedValue> namedParamsCopy) {
String query = hibernateQuery.getQueryString();
ParameterMetadata parameterMetadata = getParameterMetadata(hibernateQuery);
Iterator<Map.Entry<String, TypedValue>> iter = getNamedParamLists(hibernateQuery).entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String, TypedValue> me = iter.next();
query = expandParameterList(session, parameterMetadata, query, (String) me.getKey(), (TypedValue) me.getValue(), namedParamsCopy);
}
return query;
}
private ParameterMetadata getParameterMetadata(org.hibernate.Query hibernateQuery) {
return getField(hibernateQuery, "parameterMetadata");
}
private Map<String, TypedValue> getNamedParamLists(org.hibernate.Query hibernateQuery) {
return getField(hibernateQuery, "namedParameterLists");
}
private String expandParameterList(SessionImplementor session, ParameterMetadata parameterMetadata, String query, String name, TypedValue typedList, Map<String, TypedValue> namedParamsCopy) {
Collection<?> vals = (Collection<?>) typedList.getValue();
// HHH-1123
// Some DBs limit number of IN expressions. For now, warn...
final Dialect dialect = session.getFactory().getDialect();
final int inExprLimit = dialect.getInExpressionCountLimit();
if (inExprLimit > 0 && vals.size() > inExprLimit) {
LOG.warning(String.format("Dialect [%s] limits the number of elements in an IN predicate to %s entries. " +
"However, the given parameter list [%s] contained %s entries, which will likely cause failures " +
"to execute the query in the database", dialect.getClass().getName(), inExprLimit, name, vals.size()));
}
Type type = typedList.getType();
boolean isJpaPositionalParam = parameterMetadata.getNamedParameterDescriptor(name).isJpaStyle();
String paramPrefix = isJpaPositionalParam ? "?" : ParserHelper.HQL_VARIABLE_PREFIX;
String placeholder =
new StringBuilder(paramPrefix.length() + name.length())
.append(paramPrefix).append(name)
.toString();
if (query == null) {
return query;
}
int loc = query.indexOf(placeholder);
if (loc < 0) {
return query;
}
String beforePlaceholder = query.substring(0, loc);
String afterPlaceholder = query.substring(loc + placeholder.length());
// check if placeholder is already immediately enclosed in parentheses
// (ignoring whitespace)
boolean isEnclosedInParens =
StringHelper.getLastNonWhitespaceCharacter(beforePlaceholder) == '(' &&
StringHelper.getFirstNonWhitespaceCharacter(afterPlaceholder) == ')';
if (vals.size() == 1 && isEnclosedInParens) {
// short-circuit for performance when only 1 value and the
// placeholder is already enclosed in parentheses...
namedParamsCopy.put(name, new TypedValue(type, vals.iterator().next()));
return query;
}
StringBuilder list = new StringBuilder(16);
Iterator<?> iter = vals.iterator();
int i = 0;
while (iter.hasNext()) {
// Variable 'name' can represent a number or contain digit at the end. Surrounding it with
// characters to avoid ambiguous definition after concatenating value of 'i' counter.
String alias = (isJpaPositionalParam ? 'x' + name : name) + '_' + i++ + '_';
if (namedParamsCopy.put(alias, new TypedValue(type, iter.next())) != null) {
throw new HibernateException("Repeated usage of alias '" + alias + "' while expanding list parameter.");
}
list.append(ParserHelper.HQL_VARIABLE_PREFIX).append(alias);
if (iter.hasNext()) {
list.append(", ");
}
}
return StringHelper.replace(
beforePlaceholder,
afterPlaceholder,
placeholder.toString(),
list.toString(),
true,
true
);
}
@SuppressWarnings("unchecked")
private <T> 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 HibernateEntityManagerImplementor getEntityManager(EntityManager em) {
return (HibernateEntityManagerImplementor) em.unwrap(EntityManager.class);
}
@Override
public RuntimeException convert(EntityManager em, HibernateException e) {
return getEntityManager(em).convert(e);
}
@Override
public void handlePersistenceException(EntityManager em, PersistenceException e) {
getEntityManager(em).handlePersistenceException(e);
}
@Override
public void throwPersistenceException(EntityManager em, HibernateException e) {
getEntityManager(em).throwPersistenceException(e);
}
@Override
public QueryParameters createQueryParameters(
final Type[] positionalParameterTypes,
final Object[] positionalParameterValues,
final Map<String,TypedValue> namedParameters,
final LockOptions lockOptions,
final RowSelection rowSelection,
final boolean isReadOnlyInitialized,
final boolean readOnly,
final boolean cacheable,
final String cacheRegion,
//final boolean forceCacheRefresh,
final String comment,
final List<String> queryHints,
final Serializable[] collectionKeys) {
return new QueryParameters(
positionalParameterTypes,
positionalParameterValues,
namedParameters,
lockOptions,
rowSelection,
isReadOnlyInitialized,
readOnly,
cacheable,
cacheRegion,
comment,
collectionKeys,
null
);
}
}