Permalink
Browse files

Generate identity enum type and proxy factory dynamically

Changes to improve binary compatibility with Hibernate 5.2
grails/grails-data-mapping#820
  • Loading branch information...
graemerocher committed Nov 16, 2016
1 parent f644a70 commit cb81903f7bacb97e8838b69a1191a0906953c015
@@ -15,15 +15,9 @@
*/
package org.grails.orm.hibernate;
import grails.util.CollectionUtils;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.*;
import org.grails.orm.hibernate.support.ClosureEventTriggeringInterceptor;
import org.hibernate.boot.Metadata;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.event.service.spi.EventListenerGroup;
@@ -43,7 +37,7 @@ public EventListenerIntegrator(HibernateEventListeners hibernateEventListeners,
}
@SuppressWarnings("unchecked")
protected static final List<EventType<? extends Serializable>> TYPES = CollectionUtils.newList(
protected static final List<EventType<? extends Serializable>> TYPES = Arrays.asList(
EventType.AUTO_FLUSH,
EventType.MERGE,
EventType.PERSIST,
@@ -15,12 +15,10 @@
*/
package org.grails.orm.hibernate.cfg;
import grails.util.Holders;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.HibernateException;
import org.hibernate.MappingException;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.type.AbstractStandardBasicType;
import org.hibernate.type.TypeResolver;
import org.hibernate.usertype.ParameterizedType;
@@ -30,33 +28,32 @@
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;
/**
* Hibernate Usertype that enum values by their ID.
*
* @author Siegfried Puchbauer
* @author Graeme Rocher
*
* @since 1.1
*/
public class IdentityEnumType implements UserType, ParameterizedType, Serializable {
public abstract class AbstractIdentityEnumType implements UserType, ParameterizedType, Serializable {
private static final long serialVersionUID = -6625622185856547501L;
private static final Log LOG = LogFactory.getLog(IdentityEnumType.class);
private static final Log LOG = LogFactory.getLog(AbstractIdentityEnumType.class);
private static TypeResolver typeResolver = new TypeResolver();
public static final String ENUM_ID_ACCESSOR = "getId";
public static final String PARAM_ENUM_CLASS = "enumClass";
private static final Map<Class<? extends Enum<?>>, BidiEnumMap> ENUM_MAPPINGS = new HashMap<Class<? extends Enum<?>>, BidiEnumMap>();
private Class<? extends Enum<?>> enumClass;
private BidiEnumMap bidiMap;
private AbstractStandardBasicType<?> type;
private int[] sqlTypes;
protected Class<? extends Enum<?>> enumClass;
protected BidiEnumMap bidiMap;
protected AbstractStandardBasicType<?> type;
protected int[] sqlTypes;
public static BidiEnumMap getBidiEnumMap(Class<? extends Enum<?>> cls) throws IllegalAccessException, NoSuchMethodException, InvocationTargetException {
BidiEnumMap m = ENUM_MAPPINGS.get(cls);
@@ -74,14 +71,9 @@ public static BidiEnumMap getBidiEnumMap(Class<? extends Enum<?>> cls) throws Il
return m;
}
public static boolean isEnabled() {
Object disableConfigOption = Holders.getFlatConfig().get("grails.orm.enum.id.mapping");
return disableConfigOption == null || !(Boolean.FALSE.equals(disableConfigOption));
}
@SuppressWarnings("unchecked")
public static boolean supports(@SuppressWarnings("rawtypes") Class enumClass) {
if (!isEnabled()) return false;
if (enumClass.isEnum()) {
try {
Method idAccessor = enumClass.getMethod(ENUM_ID_ACCESSOR);
@@ -134,23 +126,6 @@ public int hashCode(Object o) throws HibernateException {
return o.hashCode();
}
public Object nullSafeGet(ResultSet resultSet, String[] names, SessionImplementor session, Object owner) throws SQLException {
Object id = type.nullSafeGet(resultSet, names[0], session);
if ((!resultSet.wasNull()) && id != null) {
return bidiMap.getEnumValue(id);
}
return null;
}
public void nullSafeSet(PreparedStatement pstmt, Object value, int idx, SessionImplementor session) throws SQLException {
if (value == null) {
pstmt.setNull(idx, sqlTypes[0]);
}
else {
type.nullSafeSet(pstmt, bidiMap.getKey(value), idx, session);
}
}
public Object deepCopy(Object o) throws HibernateException {
return o;
}
@@ -2036,19 +2036,17 @@ protected void bindEnumType(PersistentProperty property, Class<?> propertyType,
enumProperties.put(ENUM_CLASS_PROP, propertyType.getName());
String enumType = pc == null ? DEFAULT_ENUM_TYPE : pc.getEnumType();
if (enumType.equals(DEFAULT_ENUM_TYPE) && identityEnumTypeSupports(propertyType)) {
simpleValue.setTypeName("org.grails.orm.hibernate.cfg.IdentityEnumType");
} else {
simpleValue.setTypeName(ENUM_TYPE_CLASS);
if (enumType.equals(DEFAULT_ENUM_TYPE) || "string".equalsIgnoreCase(enumType)) {
enumProperties.put(EnumType.TYPE, String.valueOf(Types.VARCHAR));
enumProperties.put(EnumType.NAMED, Boolean.TRUE.toString());
}
else if (!"ordinal".equalsIgnoreCase(enumType)) {
LOG.warn("Invalid enumType specified when mapping property [" + property.getName() +
"] of class [" + owner.getName() +
"]. Using defaults instead.");
}
boolean isDefaultEnumType = enumType.equals(DEFAULT_ENUM_TYPE);
simpleValue.setTypeName(ENUM_TYPE_CLASS);
if (isDefaultEnumType || "string".equalsIgnoreCase(enumType)) {
enumProperties.put(EnumType.TYPE, String.valueOf(Types.VARCHAR));
enumProperties.put(EnumType.NAMED, Boolean.TRUE.toString());
}
else if("identity".equals(enumType)) {
simpleValue.setTypeName(HibernateUtils.buildIdentityEnumTypeFactory().getName());
}
else if (!"ordinal".equalsIgnoreCase(enumType)) {
simpleValue.setTypeName(enumType);
}
simpleValue.setTypeParameters(enumProperties);
}
@@ -3172,9 +3170,6 @@ protected void handleUniqueConstraint(PersistentProperty property, Column column
}
protected boolean identityEnumTypeSupports(Class<?> propertyType) {
return IdentityEnumType.supports(propertyType);
}
protected boolean isNotEmpty(String s) {
return GrailsHibernateUtil.isNotEmpty(s);
@@ -19,18 +19,16 @@
import groovy.lang.MetaClass;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.grails.datastore.mapping.config.Entity;
import org.grails.datastore.mapping.model.PersistentEntity;
import org.grails.datastore.mapping.model.PersistentProperty;
import org.grails.datastore.mapping.model.config.GormProperties;
import org.grails.datastore.mapping.model.types.Association;
import org.grails.datastore.mapping.model.types.Embedded;
import org.grails.datastore.mapping.reflect.ClassUtils;
import org.grails.orm.hibernate.AbstractHibernateDatastore;
import org.grails.orm.hibernate.HibernateDatastore;
import org.grails.orm.hibernate.datasource.MultipleDataSourceSupport;
import org.grails.orm.hibernate.proxy.GroovyAwareJavassistProxyFactory;
import org.grails.orm.hibernate.proxy.HibernateProxyHandler;
import org.grails.orm.hibernate.proxy.ProxyFactorySupport;
import org.grails.orm.hibernate.support.HibernateRuntimeUtils;
import org.hibernate.*;
import org.hibernate.criterion.Order;
@@ -41,6 +39,7 @@
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.proxy.ProxyFactory;
import org.hibernate.type.CompositeType;
import org.hibernate.type.Type;
import org.springframework.core.convert.ConversionService;
@@ -405,11 +404,17 @@ public static boolean isInitialized(Object obj, String associationName) {
return proxyHandler.isInitialized(obj, associationName);
}
public static GroovyAwareJavassistProxyFactory buildProxyFactory(PersistentClass persistentClass) {
GroovyAwareJavassistProxyFactory proxyFactory = new GroovyAwareJavassistProxyFactory();
/**
* Constructs a proxy factory instance
*
* @param persistentClass The persistent class
* @return The factory
*/
public static ProxyFactory buildProxyFactory(PersistentClass persistentClass) {
ProxyFactory proxyFactory = ProxyFactorySupport.createProxyFactory();
@SuppressWarnings("unchecked")
Set<Class<HibernateProxy>> proxyInterfaces = new HashSet<Class<HibernateProxy>>();
Set<Class> proxyInterfaces = new HashSet<>();
proxyInterfaces.add(HibernateProxy.class);
final Class<?> javaClass = persistentClass.getMappedClass();
@@ -17,11 +17,17 @@ package org.grails.orm.hibernate.cfg
import groovy.transform.CompileStatic
import groovy.transform.TypeCheckingMode
import javassist.ClassPool
import javassist.CtClass
import javassist.CtMethod
import org.grails.datastore.mapping.core.exceptions.ConfigurationException
import org.grails.datastore.mapping.model.PersistentEntity
import org.grails.datastore.mapping.model.PersistentProperty
import org.grails.datastore.mapping.reflect.ClassPropertyFetcher
import org.grails.datastore.mapping.reflect.NameUtils
import org.grails.orm.hibernate.proxy.AbstractGroovyAwareJavassistProxyFactory
import org.hibernate.proxy.HibernateProxy
import org.hibernate.type.EnumType
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.beans.PropertyAccessorFactory
@@ -31,6 +37,63 @@ class HibernateUtils {
static final Logger LOG = LoggerFactory.getLogger(HibernateUtils)
private static Class identityEnumTypeFactory
/**
* Build the identity enum type factory. We dynamically implement the UserType interface to avoid the differences between different Hibernate versions
*/
static Class buildIdentityEnumTypeFactory() {
if(identityEnumTypeFactory == null) {
try {
def pool = ClassPool.default
final CtClass superClass = pool.get(AbstractIdentityEnumType.name);
def clz = pool.makeClass("${AbstractIdentityEnumType.package.name}.IdentityEnumType", superClass)
def superClassMethods = superClass.getMethods()
CtMethod nullSafeGetMethod = superClassMethods.find { CtMethod m -> m.name == 'nullSafeGet' }
def parameterTypes = nullSafeGetMethod.getParameterTypes()
CtMethod newNullSafeGetMethod = new CtMethod(nullSafeGetMethod.getReturnType(), nullSafeGetMethod.name, parameterTypes, clz)
newNullSafeGetMethod.setBody(
'''{
Object id = type.nullSafeGet($1, $2[0], $3);
if ((!$1.wasNull()) && id != null) {
return bidiMap.getEnumValue(id);
}
return null;
}'''
)
clz.addMethod(
newNullSafeGetMethod
)
CtMethod nullSafeSetMethod = superClassMethods.find { CtMethod m -> m.name == 'nullSafeSet' }
def nullSafeSetParams = nullSafeSetMethod.getParameterTypes()
CtMethod newNullSafeSetMethod = new CtMethod(nullSafeSetMethod.getReturnType(), nullSafeSetMethod.name, nullSafeSetParams, clz)
newNullSafeSetMethod.setBody(
'''{
if ($2 == null) {
$1.setNull($3, sqlTypes[0]);
}
else {
type.nullSafeSet($1, bidiMap.getKey($2), $3, $4);
}
}'''
)
clz.addMethod(
newNullSafeSetMethod
)
identityEnumTypeFactory = clz.toClass()
} catch (Throwable e) {
throw new ConfigurationException("Unable to build identity enum type factory, probably due to a classpath conflict. Check the version of Hibernate on the path is correct (should be Hibernate 5+): ${e.message}", e)
}
}
return identityEnumTypeFactory
}
/**
* Overrides a getter on a property that is a Hibernate proxy in order to make sure the initialized object is returned hence avoiding Hibernate proxy hell.
*/
@@ -0,0 +1,36 @@
package org.grails.orm.hibernate.proxy;
import org.hibernate.HibernateException;
import org.hibernate.proxy.ProxyFactory;
import org.hibernate.type.CompositeType;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Set;
/**
* Abstract implementation of the ProxyFactory interface
*
* @author Graeme Rocher
* @since 6.1
*/
public abstract class AbstractGroovyAwareJavassistProxyFactory implements ProxyFactory, Serializable {
private static final long serialVersionUID = 8959336753472691947L;
protected static final Class<?>[] NO_CLASSES = {};
protected Class<?> persistentClass;
protected String entityName;
protected Class<?>[] interfaces;
protected Method getIdentifierMethod;
protected Method setIdentifierMethod;
protected CompositeType componentIdType;
@Override
public void postInstantiate(String entityName, Class persistentClass, Set<Class> interfaces, Method getIdentifierMethod, Method setIdentifierMethod, CompositeType componentIdType) throws HibernateException {
this.entityName = entityName;
this.persistentClass = persistentClass;
this.interfaces = interfaces.toArray(NO_CLASSES);
this.getIdentifierMethod = getIdentifierMethod;
this.setIdentifierMethod = setIdentifierMethod;
this.componentIdType = componentIdType;
}
}
Oops, something went wrong.

0 comments on commit cb81903

Please sign in to comment.