1 //$Id: EnumType.java 14736 2008-06-04 14:23:42Z hardy.ferentschik $
2 package org.hibernate.type;
3
4 import java.io.IOException;
5 import java.io.ObjectInputStream;
6 import java.io.Serializable;
7 import java.lang.reflect.Method;
8 import java.sql.PreparedStatement;
9 import java.sql.ResultSet;
10 import java.sql.SQLException;
11 import java.sql.Types;
12 import java.util.HashMap;
13 import java.util.Map;
14 import java.util.Properties;
15
16 import org.hibernate.AssertionFailure;
17 import org.hibernate.HibernateException;
18 import org.hibernate.annotations.common.util.StringHelper;
19 import org.hibernate.usertype.EnhancedUserType;
20 import org.hibernate.usertype.ParameterizedType;
21 import org.hibernate.util.ReflectHelper;
22 import org.slf4j.Logger;
23 import org.slf4j.LoggerFactory;
24
25 /**
26 * Enum type mapper
27 * Try and find the appropriate SQL type depending on column metadata
28 *
29 * @author Emmanuel Bernard
30 */
31 //TODO implements readobject/writeobject to recalculate the enumclasses
32 public class EnumType implements EnhancedUserType, ParameterizedType, Serializable {
33 /**
34 * This is the old scheme where logging of parameter bindings and value extractions
35 * was controlled by the trace level enablement on the 'org.hibernate.type' package...
36 * <p/>
37 * Originally was cached such because of performance of looking up the logger each time
38 * in order to check the trace-enablement. Driving this via a central Log-specific class
39 * would alleviate that performance hit, and yet still allow more "normal" logging usage/config.
40 */
41 private static final boolean IS_VALUE_TRACING_ENABLED = LoggerFactory.getLogger( StringHelper.qualifier( Type.class.getName() ) ).isTraceEnabled();
42 private transient Logger log;
43
44 private Logger log() {
45 if ( log == null ) {
46 log = LoggerFactory.getLogger( getClass() );
47 }
48 return log;
49 }
50
51 public static final String ENUM = "enumClass";
52 public static final String SCHEMA = "schema";
53 public static final String CATALOG = "catalog";
54 public static final String TABLE = "table";
55 public static final String COLUMN = "column";
56 public static final String TYPE = "type";
57
58 private static Map<Class, Object[]> enumValues = new HashMap<Class, Object[]>();
59
60 private Class<? extends Enum> enumClass;
61 private String column;
62 private String table;
63 private String catalog;
64 private String schema;
65 private boolean guessed = false;
66 private int sqlType = Types.INTEGER; //before any guessing
67
68 public int[] sqlTypes() {
69 return new int[]{sqlType};
70 }
71
72 public Class returnedClass() {
73 return enumClass;
74 }
75
76 public boolean equals(Object x, Object y) throws HibernateException {
77 return x == y;
78 }
79
80 public int hashCode(Object x) throws HibernateException {
81 return x == null ? 0 : x.hashCode();
82 }
83
84 public Object nullSafeGet(ResultSet rs, String[] names, Object owner) throws HibernateException, SQLException {
85 Object object = rs.getObject( names[0] );
86 if ( rs.wasNull() ) {
87 if ( IS_VALUE_TRACING_ENABLED ) {
88 log().debug( "Returning null as column {}", names[0] );
89 }
90 return null;
91 }
92 if ( object instanceof Number ) {
93 Object[] values = enumValues.get( enumClass );
94 if ( values == null ) throw new AssertionFailure( "enumValues not preprocessed: " + enumClass );
95 int ordinal = ( (Number) object ).intValue();
96 if ( ordinal < 0 || ordinal >= values.length ) {
97 throw new IllegalArgumentException( "Unknown ordinal value for enum " + enumClass + ": " + ordinal );
98 }
99 if ( IS_VALUE_TRACING_ENABLED ) {
100 log().debug( "Returning '{}' as column {}", ordinal, names[0] );
101 }
102 return values[ordinal];
103 }
104 else {
105 String name = (String) object;
106 if ( IS_VALUE_TRACING_ENABLED ) {
107 log().debug( "Returning '{}' as column {}", name, names[0] );
108 }
109 try {
110 return Enum.valueOf( enumClass, name );
111 }
112 catch (IllegalArgumentException iae) {
113 throw new IllegalArgumentException( "Unknown name value for enum " + enumClass + ": " + name, iae );
114 }
115 }
116 }
117
118 public void nullSafeSet(PreparedStatement st, Object value, int index) throws HibernateException, SQLException {
119 //if (!guessed) guessType( st, index );
120 if ( value == null ) {
121 if ( IS_VALUE_TRACING_ENABLED ) log().debug( "Binding null to parameter: {}", index );
122 st.setNull( index, sqlType );
123 }
124 else {
125 boolean isOrdinal = isOrdinal( sqlType );
126 if ( isOrdinal ) {
127 int ordinal = ( (Enum) value ).ordinal();
128 if ( IS_VALUE_TRACING_ENABLED ) {
129 log().debug( "Binding '{}' to parameter: {}", ordinal, index );
130 }
131 st.setObject( index, Integer.valueOf( ordinal ), sqlType );
132 }
133 else {
134 String enumString = ( (Enum) value ).name();
135 if ( IS_VALUE_TRACING_ENABLED ) {
136 log().debug( "Binding '{}' to parameter: {}", enumString, index );
137 }
138 st.setObject( index, enumString, sqlType );
139 }
140 }
141 }
142
143 private boolean isOrdinal(int paramType) {
144 switch ( paramType ) {
145 case Types.INTEGER:
146 case Types.NUMERIC:
147 case Types.SMALLINT:
148 case Types.TINYINT:
149 case Types.BIGINT:
150 case Types.DECIMAL: //for Oracle Driver
151 case Types.DOUBLE: //for Oracle Driver
152 case Types.FLOAT: //for Oracle Driver
153 return true;
154 case Types.CHAR:
155 case Types.LONGVARCHAR:
156 case Types.VARCHAR:
157 return false;
158 default:
159 throw new HibernateException( "Unable to persist an Enum in a column of SQL Type: " + paramType );
160 }
161 }
162
163 public Object deepCopy(Object value) throws HibernateException {
164 return value;
165 }
166
167 public boolean isMutable() {
168 return false;
169 }
170
171 public Serializable disassemble(Object value) throws HibernateException {
172 return (Serializable) value;
173 }
174
175 public Object assemble(Serializable cached, Object owner) throws HibernateException {
176 return cached;
177 }
178
179 public Object replace(Object original, Object target, Object owner) throws HibernateException {
180 return original;
181 }
182
183 public void setParameterValues(Properties parameters) {
184 String enumClassName = parameters.getProperty( ENUM );
185 try {
186 enumClass = ReflectHelper.classForName( enumClassName, this.getClass() ).asSubclass( Enum.class );
187 }
188 catch (ClassNotFoundException exception) {
189 throw new HibernateException( "Enum class not found", exception );
190 }
191 //this is threadsafe to do it here, setParameterValues() is called sequencially
192 initEnumValue();
193 //nullify unnullified properties yuck!
194 schema = parameters.getProperty( SCHEMA );
195 if ( "".equals( schema ) ) schema = null;
196 catalog = parameters.getProperty( CATALOG );
197 if ( "".equals( catalog ) ) catalog = null;
198 table = parameters.getProperty( TABLE );
199 column = parameters.getProperty( COLUMN );
200 String type = parameters.getProperty( TYPE );
201 if ( type != null ) {
202 sqlType = Integer.decode( type ).intValue();
203 guessed = true;
204 }
205 }
206
207 private void initEnumValue() {
208 Object[] values = enumValues.get( enumClass );
209 if ( values == null ) {
210 try {
211 Method method = null;
212 method = enumClass.getDeclaredMethod( "values", new Class[0] );
213 values = (Object[]) method.invoke( null, new Object[0] );
214 enumValues.put( enumClass, values );
215 }
216 catch (Exception e) {
217 throw new HibernateException( "Error while accessing enum.values(): " + enumClass, e );
218 }
219 }
220 }
221
222 private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
223 //FIXME Hum, I think I break the thread safety here
224 ois.defaultReadObject();
225 initEnumValue();
226 }
227
228 public String objectToSQLString(Object value) {
229 boolean isOrdinal = isOrdinal( sqlType );
230 if ( isOrdinal ) {
231 int ordinal = ( (Enum) value ).ordinal();
232 return Integer.toString( ordinal );
233 }
234 else {
235 return '\'' + ( (Enum) value ).name() + '\'';
236 }
237 }
238
239 public String toXMLString(Object value) {
240 boolean isOrdinal = isOrdinal( sqlType );
241 if ( isOrdinal ) {
242 int ordinal = ( (Enum) value ).ordinal();
243 return Integer.toString( ordinal );
244 }
245 else {
246 return ( (Enum) value ).name();
247 }
248 }
249
250 public Object fromXMLString(String xmlValue) {
251 try {
252 int ordinal = Integer.parseInt( xmlValue );
253 Object[] values = enumValues.get( enumClass );
254 if ( values == null ) throw new AssertionFailure( "enumValues not preprocessed: " + enumClass );
255 if ( ordinal < 0 || ordinal >= values.length ) {
256 throw new IllegalArgumentException( "Unknown ordinal value for enum " + enumClass + ": " + ordinal );
257 }
258 return values[ordinal];
259 }
260 catch(NumberFormatException e) {
261 try {
262 return Enum.valueOf( enumClass, xmlValue );
263 }
264 catch (IllegalArgumentException iae) {
265 throw new IllegalArgumentException( "Unknown name value for enum " + enumClass + ": " + xmlValue, iae );
266 }
267 }
268 }
269 }