This is an automated email from the ASF dual-hosted git repository. borinquenkid pushed a commit to branch merge-hibernate6 in repository https://gitbox.apache.org/repos/asf/grails-core.git
commit 80dc4f4bd559b6cdb5eefe2638bd0eb0115c411a Author: Walter Duque de Estrada <[email protected]> AuthorDate: Sun Sep 7 21:53:14 2025 -0500 Created HibernateLegacyEnumType as a copy of the deprecated Hibernate EnumType --- .../orm/hibernate/HibernateLegacyEnumType.java | 345 +++++++++++++++++++++ .../orm/hibernate/cfg/GrailsDomainBinder.java | 2 +- .../cfg/domainbinding/EnumTypeBinder.java | 12 +- .../cfg/domainbinding/EnumTypeBinderSpec.groovy | 12 +- 4 files changed, 358 insertions(+), 13 deletions(-) diff --git a/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/HibernateLegacyEnumType.java b/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/HibernateLegacyEnumType.java new file mode 100644 index 0000000000..f5554bc4d2 --- /dev/null +++ b/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/HibernateLegacyEnumType.java @@ -0,0 +1,345 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>. + */ +package org.grails.orm.hibernate; + +import java.io.Serializable; +import java.lang.annotation.Annotation; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Properties; + +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.MapKeyEnumerated; + +import org.hibernate.AssertionFailure; +import org.hibernate.HibernateException; +import org.hibernate.annotations.Nationalized; +import org.hibernate.dialect.Dialect; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.internal.CoreLogging; +import org.hibernate.internal.util.ReflectHelper; +import org.hibernate.type.SqlTypes; +import org.hibernate.type.descriptor.java.EnumJavaType; +import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.descriptor.jdbc.JdbcType; +import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators; +import org.hibernate.type.spi.TypeConfiguration; +import org.hibernate.type.spi.TypeConfigurationAware; +import org.hibernate.usertype.DynamicParameterizedType; +import org.hibernate.usertype.EnhancedUserType; +import org.hibernate.usertype.LoggableUserType; + +import org.jboss.logging.Logger; + +import static jakarta.persistence.EnumType.ORDINAL; +import static jakarta.persistence.EnumType.STRING; +import static org.hibernate.internal.util.config.ConfigurationHelper.getBoolean; + +/** + * Value type mapper for enumerations. + * + * @author Emmanuel Bernard + * @author Hardy Ferentschik + * @author Steve Ebersole + * + */ +public class HibernateLegacyEnumType<T extends Enum<T>> + implements EnhancedUserType<T>, DynamicParameterizedType, LoggableUserType, TypeConfigurationAware, Serializable { + private static final Logger LOG = CoreLogging.logger( EnumType.class ); + + public static final String ENUM = "enumClass"; + public static final String NAMED = "useNamed"; + public static final String TYPE = "type"; + + private Class<T> enumClass; + + private boolean isOrdinal; + private JdbcType jdbcType; + private EnumJavaType<T> enumJavaType; + + private TypeConfiguration typeConfiguration; + + public HibernateLegacyEnumType() { + } + + public Class<T> getEnumClass() { + return enumClass; + } + + @Override + public JdbcType getJdbcType(TypeConfiguration typeConfiguration) { + return jdbcType; + } + + /** + * <p> + * An instance of this class is "configured" by a call to {@link #setParameterValues}, + * where configuration parameters are given as entries in a {@link Properties} object. + * There are two distinct ways an instance may be configured: + * <ul> + * <li>one for {@code hbm.xml}-based mapping, and + * <li>another for annotation-based or {@code orm.xml}-based mapping. + * </ul> + * <p> + * In the case of annotations or {@code orm.xml}, a {@link ParameterType} is passed to + * {@link #setParameterValues} under the key {@value #PARAMETER_TYPE}. + * <p> + * But in the case of {@code hbm.xml}, there are multiple parameters: + * <ul> + * <li> + * {@value #ENUM}, the name of the Java enumeration class. + * </li> + * <li> + * {@value #NAMED}, specifies if the enum should be mapped by name. + * Default is to map as ordinal. + * </li> + * <li> + * {@value #TYPE}, a JDBC type code (legacy alternative to {@value #NAMED}). + * </li> + * </ul> + */ + @Override + public void setParameterValues(Properties parameters) { + // IMPL NOTE: we handle 2 distinct cases here: + // 1) we are passed a ParameterType instance in the incoming Properties - generally + // speaking this indicates the annotation-binding case, and the passed ParameterType + // represents information about the attribute and annotation + // 2) we are not passed a ParameterType - generally this indicates a hbm.xml binding case. + final ParameterType reader = (ParameterType) parameters.get( PARAMETER_TYPE ); + + if ( parameters.containsKey( ENUM ) ) { + final String enumClassName = (String) parameters.get( ENUM ); + try { + enumClass = (Class<T>) ReflectHelper.classForName( enumClassName, this.getClass() ).asSubclass( Enum.class ); + } + catch ( ClassNotFoundException exception ) { + throw new HibernateException("Enum class not found: " + enumClassName, exception); + } + } + else if ( reader != null ) { + enumClass = (Class<T>) reader.getReturnedClass().asSubclass( Enum.class ); + } + + final JavaType<T> descriptor = typeConfiguration.getJavaTypeRegistry().getDescriptor( enumClass ); + enumJavaType = (EnumJavaType<T>) descriptor; + + if ( parameters.containsKey( TYPE ) ) { + int jdbcTypeCode = Integer.parseInt( (String) parameters.get( TYPE ) ); + jdbcType = typeConfiguration.getJdbcTypeRegistry().getDescriptor( jdbcTypeCode ); + isOrdinal = jdbcType.isInteger() + // Both, ENUM and NAMED_ENUM are treated like ordinal with respect to the ordering + || jdbcType.getDefaultSqlTypeCode() == SqlTypes.ENUM + || jdbcType.getDefaultSqlTypeCode() == SqlTypes.NAMED_ENUM; + } + else { + final LocalJdbcTypeIndicators indicators; + final Long columnLength = reader == null ? null : reader.getColumnLengths()[0]; + if ( parameters.containsKey (NAMED ) ) { + indicators = new LocalJdbcTypeIndicators( + // use ORDINAL as default for hbm.xml mappings + getBoolean( NAMED, parameters ) ? STRING : ORDINAL, + false, + columnLength + ); + } + else { + indicators = new LocalJdbcTypeIndicators( + getEnumType( reader ), + isNationalized( reader ), + columnLength + ); + } + jdbcType = descriptor.getRecommendedJdbcType( indicators ); + isOrdinal = indicators.getEnumeratedType() != STRING; + } + + if ( LOG.isDebugEnabled() ) { + LOG.debugf( + "Using %s-based conversion for Enum %s", + isOrdinal() ? "ORDINAL" : "NAMED", + enumClass.getName() + ); + } + } + + private jakarta.persistence.EnumType getEnumType(ParameterType reader) { + if ( reader != null ) { + if ( reader.isPrimaryKey() ) { + final MapKeyEnumerated enumAnn = getAnnotation( reader.getAnnotationsMethod(), MapKeyEnumerated.class ); + if ( enumAnn != null ) { + return enumAnn.value(); + } + } + final Enumerated enumAnn = getAnnotation( reader.getAnnotationsMethod(), Enumerated.class ); + if ( enumAnn != null ) { + return enumAnn.value(); + } + } + return ORDINAL; + } + + private boolean isNationalized(ParameterType reader) { + return typeConfiguration.getCurrentBaseSqlTypeIndicators().isNationalized() + || reader!=null && getAnnotation( reader.getAnnotationsMethod(), Nationalized.class ) != null; + } + + @SuppressWarnings("unchecked") + private <A extends Annotation> A getAnnotation(Annotation[] annotations, Class<A> annotationType) { + for ( Annotation annotation : annotations ) { + if ( annotationType.isInstance( annotation ) ) { + return (A) annotation; + } + } + return null; + } + + @Override + public int getSqlType() { + verifyConfigured(); + return jdbcType.getJdbcTypeCode(); + } + + @Override + public Class<T> returnedClass() { + return enumClass; + } + + @Override + public boolean equals(T x, T y) throws HibernateException { + return x == y; + } + + @Override + public int hashCode(T x) throws HibernateException { + return x == null ? 0 : x.hashCode(); + } + + @Override + public T nullSafeGet(ResultSet rs, int position, SharedSessionContractImplementor session, Object owner) throws SQLException { + verifyConfigured(); + return jdbcType.getExtractor( enumJavaType ).extract( rs, position, session ); + } + + private void verifyConfigured() { + if ( enumJavaType == null ) { + throw new AssertionFailure("EnumType (" + enumClass.getName() + ") not properly, fully configured"); + } + } + + @Override + public void nullSafeSet(PreparedStatement st, T value, int index, SharedSessionContractImplementor session) throws HibernateException, SQLException { + verifyConfigured(); + jdbcType.getBinder( enumJavaType ).bind( st, value, index, session ); + } + + @Override + public T deepCopy(T value) throws HibernateException { + return value; + } + + @Override + public boolean isMutable() { + return false; + } + + @Override + public Serializable disassemble(T value) throws HibernateException { + return value; + } + + @Override + public T assemble(Serializable cached, Object owner) throws HibernateException { + return (T) cached; + } + + @Override + public T replace(T original, T target, Object owner) throws HibernateException { + return original; + } + + @Override + public TypeConfiguration getTypeConfiguration() { + return typeConfiguration; + } + + @Override + public void setTypeConfiguration(TypeConfiguration typeConfiguration) { + this.typeConfiguration = typeConfiguration; + } + + @Override + public String toSqlLiteral(T value) { + verifyConfigured(); + return isOrdinal() + ? Integer.toString( value.ordinal() ) + : "'" + value.name() + "'"; + } + + @Override + public String toString(T value) { + verifyConfigured(); + return enumJavaType.toName( value ); + } + + @Override + public T fromStringValue(CharSequence sequence) { + verifyConfigured(); + return enumJavaType.fromName( sequence.toString() ); + } + + @Override @SuppressWarnings("unchecked") + public String toLoggableString(Object value, SessionFactoryImplementor factory) { + verifyConfigured(); + return enumJavaType.extractLoggableRepresentation( (T) value ); + } + + public boolean isOrdinal() { + verifyConfigured(); + return isOrdinal; + } + + private class LocalJdbcTypeIndicators implements JdbcTypeIndicators { + private final jakarta.persistence.EnumType enumType; + private final boolean nationalized; + private final Long columnLength; + + private LocalJdbcTypeIndicators(jakarta.persistence.EnumType enumType, boolean nationalized, Long columnLength) { + this.enumType = enumType; + this.nationalized = nationalized; + this.columnLength = columnLength; + } + + @Override + public TypeConfiguration getTypeConfiguration() { + return typeConfiguration; + } + + @Override + public jakarta.persistence.EnumType getEnumeratedType() { + return enumType != null ? enumType : typeConfiguration.getCurrentBaseSqlTypeIndicators().getEnumeratedType(); + } + + @Override + public boolean isNationalized() { + return nationalized; + } + + + @Override + public long getColumnLength() { + return columnLength == null ? NO_COLUMN_LENGTH : columnLength; + } + + @Override + public Dialect getDialect() { + return typeConfiguration.getCurrentBaseSqlTypeIndicators().getDialect(); + } + } +} diff --git a/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java b/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java index 02f0e183f7..4585678f47 100644 --- a/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java +++ b/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java @@ -130,7 +130,7 @@ public class GrailsDomainBinder implements MetadataContributor { public static final String CASCADE_NONE = "none"; private static final String BACKTICK = "`"; - public static final String ENUM_TYPE_CLASS = "org.hibernate.type.EnumType"; + public static final String ENUM_TYPE_CLASS = org.grails.orm.hibernate.HibernateLegacyEnumType.class.getName(); public static final String ENUM_CLASS_PROP = "enumClass"; private static final String ENUM_TYPE_PROP = "type"; public static final String DEFAULT_ENUM_TYPE = "default"; diff --git a/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/EnumTypeBinder.java b/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/EnumTypeBinder.java index 9b98e8c915..6a2de0ee0e 100644 --- a/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/EnumTypeBinder.java +++ b/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/EnumTypeBinder.java @@ -10,7 +10,7 @@ import org.hibernate.MappingException; import org.hibernate.mapping.Column; import org.hibernate.mapping.SimpleValue; import org.hibernate.mapping.Table; -import org.hibernate.type.EnumType; +import org.grails.orm.hibernate.HibernateLegacyEnumType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -51,13 +51,13 @@ public class EnumTypeBinder { } else { if (GrailsEnumType.DEFAULT.getType().equals(enumType) || GrailsEnumType.STRING.getType().equalsIgnoreCase(enumType)) { simpleValue.setTypeName(ENUM_TYPE_CLASS); - enumProperties.put(EnumType.TYPE, String.valueOf(Types.VARCHAR)); - enumProperties.put(EnumType.NAMED, Boolean.TRUE.toString()); + enumProperties.put(HibernateLegacyEnumType.TYPE, String.valueOf(Types.VARCHAR)); + enumProperties.put(HibernateLegacyEnumType.NAMED, Boolean.TRUE.toString()); } else if (GrailsEnumType.ORDINAL.getType().equalsIgnoreCase(enumType)) { simpleValue.setTypeName(ENUM_TYPE_CLASS); - enumProperties.put(EnumType.TYPE, String.valueOf(Types.INTEGER)); - enumProperties.put(EnumType.NAMED, Boolean.FALSE.toString()); - } else if (GrailsEnumType.ORDINAL.getType().equals(enumType)) { + enumProperties.put(HibernateLegacyEnumType.TYPE, String.valueOf(Types.INTEGER)); + enumProperties.put(HibernateLegacyEnumType.NAMED, Boolean.FALSE.toString()); + } else if (GrailsEnumType.IDENTITY.getType().equals(enumType)) { simpleValue.setTypeName(IdentityEnumType.class.getName()); } else { throw new MappingException("Invalid enum type [" + enumType + "]."); diff --git a/grails-data-hibernate6/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/EnumTypeBinderSpec.groovy b/grails-data-hibernate6/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/EnumTypeBinderSpec.groovy index 3a5d305dbc..be7041668e 100644 --- a/grails-data-hibernate6/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/EnumTypeBinderSpec.groovy +++ b/grails-data-hibernate6/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/EnumTypeBinderSpec.groovy @@ -9,7 +9,7 @@ import org.hibernate.mapping.BasicValue import org.hibernate.mapping.Column import org.hibernate.mapping.Selectable import org.hibernate.mapping.Table -import org.hibernate.type.EnumType +import org.grails.orm.hibernate.HibernateLegacyEnumType import org.hibernate.usertype.UserType import spock.lang.Subject import spock.lang.Unroll @@ -50,14 +50,14 @@ class EnumTypeBinderSpec extends HibernateGormDatastoreSpec { and: "the type parameters are configured correctly" def props = simpleValue.getTypeParameters() - (props.getProperty(EnumType.TYPE) == String.valueOf(expectedSqlType)) == typeExpected - (props.getProperty(EnumType.NAMED) == String.valueOf(namedExpected)) == namedIsExpected + (props.getProperty(HibernateLegacyEnumType.TYPE) == String.valueOf(expectedSqlType)) == typeExpected + (props.getProperty(HibernateLegacyEnumType.NAMED) == String.valueOf(namedExpected)) == namedIsExpected where: clazz | enumTypeMapping | expectedHibernateType | expectedSqlType | typeExpected | namedExpected | namedIsExpected | nullable - Person01| "default" | EnumType.class.getName() | Types.VARCHAR | true | true | true | false - Person02|"string" | EnumType.class.getName() | Types.VARCHAR | true | true | true | true - Person03|"ordinal" | EnumType.class.getName() | Types.INTEGER | true | false | true | true + Person01| "default" | HibernateLegacyEnumType.class.getName() | Types.VARCHAR | true | true | true | false + Person02|"string" | HibernateLegacyEnumType.class.getName() | Types.VARCHAR | true | true | true | true + Person03|"ordinal" | HibernateLegacyEnumType.class.getName() | Types.INTEGER | true | false | true | true Person04|"identity" | IdentityEnumType.class.getName() | null | false | null | false | false Person05|UserTypeEnumType | UserTypeEnumType.class.getName() | null | false | null | false | false }
