Skip to content

Instantly share code, notes, and snippets.

@gbadner
Created January 7, 2014 05:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gbadner/8294930 to your computer and use it in GitHub Desktop.
Save gbadner/8294930 to your computer and use it in GitHub Desktop.
EnumType conflict (HHH-7776)
<<<<<<< HEAD
public class EnumType implements EnhancedUserType, DynamicParameterizedType,LoggableUserType, Serializable {
=======
public class EnumType implements EnhancedUserType, DynamicParameterizedType, Serializable, StringRepresentableType {
>>>>>>> HHH-7776 - Consolidate to/from string handling for types in StringRepresentableType
private static final Logger LOG = Logger.getLogger( EnumType.class.getName() );
public static final String ENUM = "enumClass";
public static final String NAMED = "useNamed";
public static final String TYPE = "type";
private Class<? extends Enum> enumClass;
private EnumValueMapper enumValueMapper;
private int sqlType = Types.INTEGER; // before any guessing
@Override
public int[] sqlTypes() {
return new int[] { sqlType };
}
@Override
public Class<? extends Enum> returnedClass() {
return enumClass;
}
@Override
public boolean equals(Object x, Object y) throws HibernateException {
return x == y;
}
@Override
public int hashCode(Object x) throws HibernateException {
return x == null ? 0 : x.hashCode();
}
@Override
public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) throws SQLException {
if ( enumValueMapper == null ) {
resolveEnumValueMapper( rs, names[0] );
}
return enumValueMapper.getValue( rs, names );
}
private void resolveEnumValueMapper(ResultSet rs, String name) {
if ( enumValueMapper == null ) {
try {
resolveEnumValueMapper( rs.getMetaData().getColumnType( rs.findColumn( name ) ) );
}
catch (Exception e) {
// because some drivers do not implement this
LOG.debugf(
"JDBC driver threw exception calling java.sql.ResultSetMetaData.getColumnType; " +
"using fallback determination [%s] : %s",
enumClass.getName(),
e.getMessage()
);
// peek at the result value to guess type (this is legacy behavior)
try {
Object value = rs.getObject( name );
if ( Number.class.isInstance( value ) ) {
treatAsOrdinal();
}
else {
treatAsNamed();
}
}
catch (SQLException ignore) {
treatAsOrdinal();
}
}
}
}
private void resolveEnumValueMapper(int columnType) {
// fallback for cases where not enough parameter/parameterization information was passed in
if ( isOrdinal( columnType ) ) {
treatAsOrdinal();
}
else {
treatAsNamed();
}
}
@Override
public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) throws HibernateException, SQLException {
if ( enumValueMapper == null ) {
resolveEnumValueMapper( st, index );
}
enumValueMapper.setValue( st, (Enum) value, index );
}
private void resolveEnumValueMapper(PreparedStatement st, int index) {
if ( enumValueMapper == null ) {
try {
resolveEnumValueMapper( st.getParameterMetaData().getParameterType( index ) );
}
catch (Exception e) {
// because some drivers do not implement this
LOG.debugf(
"JDBC driver threw exception calling java.sql.ParameterMetaData#getParameterType; " +
"falling back to ordinal-based enum mapping [%s] : %s",
enumClass.getName(),
e.getMessage()
);
// Originally, this was simply treatAsOrdinal(). But, for DBs that do not implement the above, enums
// were treated as ordinal even when the *.hbm.xml explicitly define the type sqlCode. By default,
// this is essentially the same anyway, since sqlType is defaulted to Integer.
resolveEnumValueMapper( sqlType );
}
}
}
@Override
public Object deepCopy(Object value) throws HibernateException {
return value;
}
@Override
public boolean isMutable() {
return false;
}
@Override
public Serializable disassemble(Object value) throws HibernateException {
return ( Serializable ) value;
}
@Override
public Object assemble(Serializable cached, Object owner) throws HibernateException {
return cached;
}
@Override
public Object replace(Object original, Object target, Object owner) throws HibernateException {
return original;
}
@Override
public void setParameterValues(Properties parameters) {
final ParameterType reader = (ParameterType) parameters.get( PARAMETER_TYPE );
// IMPL NOTE : be protective about not setting enumValueMapper (i.e. calling treatAsNamed/treatAsOrdinal)
// in cases where we do not have enough information. In such cases we do additional checks
// as part of nullSafeGet/nullSafeSet to query against the JDBC metadata to make the determination.
if ( reader != null ) {
enumClass = reader.getReturnedClass().asSubclass( Enum.class );
final boolean isOrdinal;
final javax.persistence.EnumType enumType = getEnumType( reader );
if ( enumType == null ) {
isOrdinal = true;
}
else if ( javax.persistence.EnumType.ORDINAL.equals( enumType ) ) {
isOrdinal = true;
}
else if ( javax.persistence.EnumType.STRING.equals( enumType ) ) {
isOrdinal = false;
}
else {
throw new AssertionFailure( "Unknown EnumType: " + enumType );
}
if ( isOrdinal ) {
treatAsOrdinal();
}
else {
treatAsNamed();
}
sqlType = enumValueMapper.getSqlType();
}
else {
String enumClassName = (String) parameters.get( ENUM );
try {
enumClass = ReflectHelper.classForName( enumClassName, this.getClass() ).asSubclass( Enum.class );
}
catch ( ClassNotFoundException exception ) {
throw new HibernateException( "Enum class not found", exception );
}
final Object useNamedSetting = parameters.get( NAMED );
if ( useNamedSetting != null ) {
final boolean useNamed = ConfigurationHelper.getBoolean( NAMED, parameters );
if ( useNamed ) {
treatAsNamed();
}
else {
treatAsOrdinal();
}
sqlType = enumValueMapper.getSqlType();
}
}
final String type = (String) parameters.get( TYPE );
if ( type != null ) {
sqlType = Integer.decode( type );
}
}
private void treatAsOrdinal() {
if ( enumValueMapper == null || ! OrdinalEnumValueMapper.class.isInstance( enumValueMapper ) ) {
enumValueMapper = new OrdinalEnumValueMapper();
sqlType = enumValueMapper.getSqlType();
}
}
private void treatAsNamed() {
if ( enumValueMapper == null || ! NamedEnumValueMapper.class.isInstance( enumValueMapper ) ) {
enumValueMapper = new NamedEnumValueMapper();
sqlType = enumValueMapper.getSqlType();
}
}
private javax.persistence.EnumType getEnumType(ParameterType reader) {
javax.persistence.EnumType enumType = null;
if ( reader.isPrimaryKey() ) {
MapKeyEnumerated enumAnn = getAnnotation( reader.getAnnotationsMethod(), MapKeyEnumerated.class );
if ( enumAnn != null ) {
enumType = enumAnn.value();
}
}
else {
Enumerated enumAnn = getAnnotation( reader.getAnnotationsMethod(), Enumerated.class );
if ( enumAnn != null ) {
enumType = enumAnn.value();
}
}
return enumType;
}
private <T extends Annotation> T getAnnotation(Annotation[] annotations, Class<T> anClass) {
for ( Annotation annotation : annotations ) {
if ( anClass.isInstance( annotation ) ) {
return (T) annotation;
}
}
return null;
}
@Override
public String objectToSQLString(Object value) {
if ( enumValueMapper == null ) {
guessTypeOfEnumValueMapper( sqlType );
}
return enumValueMapper.objectToSQLString( (Enum) value );
}
@Override
public String toString(Object value) throws HibernateException {
if ( enumValueMapper == null ) {
guessTypeOfEnumValueMapper( sqlType );
}
return enumValueMapper.toString( ( Enum) value );
}
@Override
public Object fromStringValue(String string) throws HibernateException {
if ( enumValueMapper == null ) {
guessTypeOfEnumValueMapper( sqlType );
}
return enumValueMapper.fromString( string );
}
@Override
public String toLoggableString(Object value, SessionFactoryImplementor factory) {
if ( enumValueMapper != null ) {
return enumValueMapper.toXMLString( (Enum) value );
}
return value.toString();
}
private static interface EnumValueMapper extends Serializable {
public int getSqlType();
public Enum getValue(ResultSet rs, String[] names) throws SQLException;
public void setValue(PreparedStatement st, Enum value, int index) throws SQLException;
public String objectToSQLString(Enum value);
public String toString(Enum value);
public Enum fromString(String xml);
}
public abstract class EnumValueMapperSupport implements EnumValueMapper {
protected abstract Object extractJdbcValue(Enum value);
@Override
public void setValue(PreparedStatement st, Enum value, int index) throws SQLException {
final Object jdbcValue = value == null ? null : extractJdbcValue( value );
final boolean traceEnabled = LOG.isTraceEnabled();
if ( jdbcValue == null ) {
if ( traceEnabled ) {
LOG.trace(String.format("Binding null to parameter: [%s]", index));
}
st.setNull( index, getSqlType() );
return;
}
if ( traceEnabled ) {
LOG.trace(String.format("Binding [%s] to parameter: [%s]", jdbcValue, index));
}
st.setObject( index, jdbcValue, EnumType.this.sqlType );
}
}
private class OrdinalEnumValueMapper extends EnumValueMapperSupport implements EnumValueMapper, Serializable {
private transient Enum[] enumsByOrdinal;
@Override
public int getSqlType() {
return Types.INTEGER;
}
@Override
public Enum getValue(ResultSet rs, String[] names) throws SQLException {
final int ordinal = rs.getInt( names[0] );
final boolean traceEnabled = LOG.isTraceEnabled();
if ( rs.wasNull() ) {
if ( traceEnabled ) {
LOG.trace(String.format("Returning null as column [%s]", names[0]));
}
return null;
}
final Enum enumValue = fromOrdinal( ordinal );
if ( traceEnabled ) {
LOG.trace(String.format("Returning [%s] as column [%s]", enumValue, names[0]));
}
return enumValue;
}
private Enum fromOrdinal(int ordinal) {
final Enum[] enumsByOrdinal = enumsByOrdinal();
if ( ordinal < 0 || ordinal >= enumsByOrdinal.length ) {
throw new IllegalArgumentException(
String.format(
"Unknown ordinal value [%s] for enum class [%s]",
ordinal,
enumClass.getName()
)
);
}
return enumsByOrdinal[ordinal];
}
private Enum[] enumsByOrdinal() {
if ( enumsByOrdinal == null ) {
enumsByOrdinal = enumClass.getEnumConstants();
if ( enumsByOrdinal == null ) {
throw new HibernateException( "Failed to init enum values" );
}
}
return enumsByOrdinal;
}
@Override
public String objectToSQLString(Enum value) {
return toString( value );
}
@Override
public String toString(Enum value) {
return Integer.toString( value.ordinal() );
}
@Override
public Enum fromString(String xml) {
return fromOrdinal( Integer.parseInt( xml ) );
}
@Override
protected Object extractJdbcValue(Enum value) {
return value.ordinal();
}
}
private class NamedEnumValueMapper extends EnumValueMapperSupport implements EnumValueMapper, Serializable {
@Override
public int getSqlType() {
return Types.VARCHAR;
}
@Override
public Enum getValue(ResultSet rs, String[] names) throws SQLException {
final String value = rs.getString( names[0] );
final boolean traceEnabled = LOG.isTraceEnabled();
if ( rs.wasNull() ) {
if ( traceEnabled ) {
LOG.trace(String.format("Returning null as column [%s]", names[0]));
}
return null;
}
final Enum enumValue = fromName( value );
if ( traceEnabled ) {
LOG.trace(String.format("Returning [%s] as column [%s]", enumValue, names[0]));
}
return enumValue;
}
private Enum fromName(String name) {
try {
if(name == null) {
return null;
}
return Enum.valueOf( enumClass, name.trim() );
}
catch ( IllegalArgumentException iae ) {
throw new IllegalArgumentException(
String.format(
"Unknown name value [%s] for enum class [%s]",
name,
enumClass.getName()
)
);
}
}
@Override
public String objectToSQLString(Enum value) {
return '\'' + toString( value ) + '\'';
}
@Override
public String toString(Enum value) {
return value.name();
}
@Override
public Enum fromString(String xml) {
return fromName( xml );
}
@Override
protected Object extractJdbcValue(Enum value) {
return value.name();
}
}
public boolean isOrdinal() {
return isOrdinal( sqlType );
}
private boolean isOrdinal(int paramType) {
switch ( paramType ) {
case Types.INTEGER:
case Types.NUMERIC:
case Types.SMALLINT:
case Types.TINYINT:
case Types.BIGINT:
case Types.DECIMAL: //for Oracle Driver
case Types.DOUBLE: //for Oracle Driver
case Types.FLOAT: //for Oracle Driver
return true;
case Types.CHAR:
case Types.LONGVARCHAR:
case Types.VARCHAR:
return false;
default:
throw new HibernateException( "Unable to persist an Enum in a column of SQL Type: " + paramType );
}
}
}
@stliu
Copy link

stliu commented Jan 7, 2014

IIRC, we should make EnumType a LoggableUserType type.

I don't remember the details, but I guess i made this change to sync with master ( just checked from master branch )

and I made the change from _ StringRepresentableType _ to _ LoggableUserType_ at this c131734 commit

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment