PageRenderTime 43ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/atlassian-hibernate5-extras/src/main/java/com/atlassian/hibernate/extras/type/GenericEnumUserType.java

https://bitbucket.org/atlassian/atlassian-hibernate-extras
Java | 252 lines | 146 code | 33 blank | 73 comment | 9 complexity | f789c78da9ce085dee34df8b082ab0f5 MD5 | raw file
Possible License(s): LGPL-2.1
  1. package com.atlassian.hibernate.extras.type;
  2. import org.hibernate.HibernateException;
  3. import org.hibernate.engine.spi.SessionImplementor;
  4. import org.hibernate.type.BasicTypeRegistry;
  5. import org.hibernate.type.SingleColumnType;
  6. import org.hibernate.usertype.EnhancedUserType;
  7. import org.hibernate.usertype.ParameterizedType;
  8. import java.io.ObjectInputStream;
  9. import java.io.Serializable;
  10. import java.lang.reflect.Method;
  11. import java.sql.PreparedStatement;
  12. import java.sql.ResultSet;
  13. import java.sql.SQLException;
  14. import java.util.Properties;
  15. /**
  16. * Implements a generic enum user type identified/represented by a single identifier/column.
  17. * <p>
  18. * <ul>
  19. * <li>The enum type being represented by the certain user type must be set by using the 'enumClass' property.</li>
  20. * <li>The identifier representing a enum value is retrieved by the identifierMethod. The name of the identifier method
  21. * can be specified by the 'identifierMethod' property and by default the getId() method is used.</li>
  22. * <li>The identifier type is automatically determined by the return-type of the identifierMethod.</li>
  23. * <li>The valueOfMethod is the name of the static factory method returning the enumeration object being represented by
  24. * the given identifier. The valueOfMethod's name can be specified by setting the 'valueOfMethod' property. The default
  25. * valueOfMethod's name is 'fromId'.</li>
  26. * </ul>
  27. * <p>
  28. * Example of an enum type represented by an int value:
  29. * <pre><code>
  30. * package com.atlassian.demo;
  31. *
  32. * public enum SimpleNumber {
  33. * UNKNOWN(-1),
  34. * ZERO(0),
  35. * ONE(1),
  36. * TWO(2),
  37. * THREE(3);
  38. *
  39. * public int getId() {
  40. * return value;
  41. * }
  42. *
  43. * public SimpleNumber fromId(int value) {
  44. * switch(value) {
  45. * case 0: return ZERO;
  46. * case 1: return ONE;
  47. * case 2: return TWO;
  48. * case 3: return THREE;
  49. * default: return UNKNOWN;
  50. * }
  51. * }
  52. * }
  53. * </code></pre>
  54. * <p>
  55. * Using JPA, the mapping would look like this:
  56. * <pre><code>
  57. * &#064;Type(type = "com.atlassian.stash.internal.hibernate.GenericEnumUserType", parameters = {
  58. * &#064;Parameter(name = "enumClass", value = "com.atlassian.demo.SimpleNumber"),
  59. * &#064;Parameter(name = "identifierMethod", value = "getId"),
  60. * &#064;Parameter(name = "valueOfMethod", value = "fromId")})
  61. * private SimpleNumber randomNumber;
  62. * </code></pre>
  63. * <p>
  64. * In this example, the properties for the {@code GenericEnumUserType} are fully specified to make the example more
  65. * clear on the type's usage. However, because the identifier and valueOf methods follow standard naming, properties
  66. * for them may be omitted from the mapping to reduce configuration.
  67. *
  68. * @since 6.1
  69. */
  70. @SuppressWarnings("unused")
  71. public class GenericEnumUserType implements EnhancedUserType, ParameterizedType, Serializable {
  72. private static final String DEFAULT_IDENTIFIER_METHOD_NAME = "getId";
  73. private static final String DEFAULT_VALUE_OF_METHOD_NAME = "fromId";
  74. private static final Class[] NULL_CLASS_VARARG = null;
  75. private static final Object[] NULL_OBJECT_VARARG = null;
  76. private static final char SINGLE_QUOTE = '\'';
  77. private Class<? extends Enum> enumClass;
  78. private Method identifierMethod;
  79. private int[] sqlTypes;
  80. private SingleColumnType<Object> type;
  81. private Method valueOfMethod;
  82. public Object assemble(Serializable cached, Object owner) throws HibernateException {
  83. return cached;
  84. }
  85. public Object deepCopy(Object value) throws HibernateException {
  86. return value;
  87. }
  88. public Serializable disassemble(Object value) throws HibernateException {
  89. return (Serializable) value;
  90. }
  91. public boolean equals(Object x, Object y) throws HibernateException {
  92. return x == y;
  93. }
  94. public Object fromXMLString(String xmlValue) {
  95. return Enum.valueOf(enumClass, xmlValue);
  96. }
  97. public int hashCode(Object x) throws HibernateException {
  98. return x.hashCode();
  99. }
  100. public boolean isMutable() {
  101. return false;
  102. }
  103. public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner)
  104. throws HibernateException, SQLException {
  105. Object identifier = type.nullSafeGet(rs, names[0], session);
  106. if (identifier == null || rs.wasNull()) {
  107. return null;
  108. }
  109. try {
  110. return valueOfMethod.invoke(enumClass, identifier);
  111. } catch (Exception exception) {
  112. String msg = "Exception while invoking valueOfMethod [" + valueOfMethod.getName() +
  113. "] of Enum class [" + enumClass.getName() + "] with argument of type [" +
  114. identifier.getClass().getName() + "], value=[" + identifier + "]";
  115. throw new HibernateException(msg, exception);
  116. }
  117. }
  118. public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session)
  119. throws HibernateException, SQLException {
  120. if (value == null) {
  121. st.setNull(index, sqlTypes[0]);
  122. } else {
  123. try {
  124. Object identifier = identifierMethod.invoke(value, NULL_OBJECT_VARARG);
  125. type.set(st, identifier, index, session);
  126. } catch (Exception exception) {
  127. String msg = "Exception while invoking identifierMethod [" + identifierMethod.getName() +
  128. "] of Enum class [" + enumClass.getName() +
  129. "] with argument of type [" + value.getClass().getName() + "], value=[" + value + "]";
  130. throw new HibernateException(msg, exception);
  131. }
  132. }
  133. }
  134. public String objectToSQLString(Object value) {
  135. return SINGLE_QUOTE + ((Enum) value).name() + SINGLE_QUOTE;
  136. }
  137. public Object replace(Object original, Object target, Object owner) throws HibernateException {
  138. return original;
  139. }
  140. public Class returnedClass() {
  141. return enumClass;
  142. }
  143. public void setParameterValues(Properties parameters) {
  144. String enumClassName = parameters.getProperty("enumClass");
  145. String identifierMethodName = parameters.getProperty("identifierMethod", DEFAULT_IDENTIFIER_METHOD_NAME);
  146. String valueOfMethodName = parameters.getProperty("valueOfMethod", DEFAULT_VALUE_OF_METHOD_NAME);
  147. initialize(enumClassName, identifierMethodName, valueOfMethodName);
  148. }
  149. public int[] sqlTypes() {
  150. return sqlTypes;
  151. }
  152. public String toXMLString(Object value) {
  153. return ((Enum<?>) value).name();
  154. }
  155. @SuppressWarnings("unchecked")
  156. private void initialize(String enumClassName, String identifierMethodName, String valueOfMethodName) {
  157. try {
  158. enumClass = Class.forName(enumClassName).asSubclass(Enum.class);
  159. } catch (ClassNotFoundException exception) {
  160. throw new HibernateException("Enum class not found", exception);
  161. }
  162. try {
  163. identifierMethod = enumClass.getMethod(identifierMethodName, NULL_CLASS_VARARG);
  164. } catch (Exception exception) {
  165. throw new HibernateException("Failed to obtain identifier method", exception);
  166. }
  167. Class<?> identifierType = identifierMethod.getReturnType();
  168. try {
  169. valueOfMethod = enumClass.getMethod(valueOfMethodName, identifierType);
  170. } catch (Exception exception) {
  171. throw new HibernateException("Failed to obtain valueOf method", exception);
  172. }
  173. //TODO: We really shouldn't be instantiating this, but I don't know how to get the SessionImplementor here
  174. BasicTypeRegistry registry = new BasicTypeRegistry();
  175. type = (SingleColumnType<Object>) registry.getRegisteredType(identifierType.getName());
  176. if (type == null) {
  177. throw new HibernateException("Unsupported identifier type " + identifierType.getName());
  178. }
  179. sqlTypes = new int[]{type.sqlType()};
  180. }
  181. /**
  182. * Prevents attempts to deserialize the {@code GenericEnumUserType} directly, as {@link SerializationProxy}
  183. * should have been written in its place.
  184. *
  185. * @param stream ignored
  186. */
  187. private void readObject(ObjectInputStream stream) {
  188. throw new UnsupportedOperationException(getClass().getName() + " cannot be deserialized directly");
  189. }
  190. /**
  191. * Replaces this {@code GenericEnumUserType} (which is not really serializable due to the {@code Method} fields)
  192. * with a simple {@link SerializationProxy} containing the names for the enum class, the identifier method and
  193. * the valueOf method.
  194. *
  195. * @return a new {@link SerializationProxy} to be serialized in this type's place
  196. */
  197. private Object writeReplace() {
  198. return new SerializationProxy(enumClass.getName(), identifierMethod.getName(), valueOfMethod.getName());
  199. }
  200. /**
  201. * Simple proxy to serialize in place of the more complicated {@link GenericEnumUserType}. This proxy contains
  202. * all the data necessary to recreate/reinitialize the full {@link GenericEnumUserType}.
  203. */
  204. private static class SerializationProxy implements Serializable {
  205. private final String enumClassName;
  206. private final String identifierMethodName;
  207. private final String valueOfMethodName;
  208. private SerializationProxy(String enumClassName, String identifierMethodName, String valueOfMethodName) {
  209. this.enumClassName = enumClassName;
  210. this.identifierMethodName = identifierMethodName;
  211. this.valueOfMethodName = valueOfMethodName;
  212. }
  213. private Object readResolve() {
  214. GenericEnumUserType type = new GenericEnumUserType();
  215. type.initialize(enumClassName, identifierMethodName, valueOfMethodName);
  216. return type;
  217. }
  218. }
  219. }