PageRenderTime 34ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

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

https://bitbucket.org/atlassian/atlassian-hibernate-extras
Java | 267 lines | 161 code | 33 blank | 73 comment | 9 complexity | 16cc99b828a7089eded8a0508967303a 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.SharedSessionContractImplementor;
  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. @Override
  83. public Object assemble(Serializable cached, Object owner) throws HibernateException {
  84. return cached;
  85. }
  86. @Override
  87. public Object deepCopy(Object value) throws HibernateException {
  88. return value;
  89. }
  90. @Override
  91. public Serializable disassemble(Object value) throws HibernateException {
  92. return (Serializable) value;
  93. }
  94. @Override
  95. public boolean equals(Object x, Object y) throws HibernateException {
  96. return x == y;
  97. }
  98. @Override
  99. public Object fromXMLString(String xmlValue) {
  100. return Enum.valueOf(enumClass, xmlValue);
  101. }
  102. @Override
  103. public int hashCode(Object x) throws HibernateException {
  104. return x.hashCode();
  105. }
  106. @Override
  107. public boolean isMutable() {
  108. return false;
  109. }
  110. @Override
  111. public Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner)
  112. throws HibernateException, SQLException {
  113. Object identifier = type.nullSafeGet(rs, names[0], session);
  114. if (identifier == null || rs.wasNull()) {
  115. return null;
  116. }
  117. try {
  118. return valueOfMethod.invoke(enumClass, identifier);
  119. } catch (Exception exception) {
  120. String msg = "Exception while invoking valueOfMethod [" + valueOfMethod.getName() +
  121. "] of Enum class [" + enumClass.getName() + "] with argument of type [" +
  122. identifier.getClass().getName() + "], value=[" + identifier + "]";
  123. throw new HibernateException(msg, exception);
  124. }
  125. }
  126. @Override
  127. public void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session)
  128. throws HibernateException, SQLException {
  129. if (value == null) {
  130. st.setNull(index, sqlTypes[0]);
  131. } else {
  132. try {
  133. Object identifier = identifierMethod.invoke(value, NULL_OBJECT_VARARG);
  134. type.set(st, identifier, index, session);
  135. } catch (Exception exception) {
  136. String msg = "Exception while invoking identifierMethod [" + identifierMethod.getName() +
  137. "] of Enum class [" + enumClass.getName() +
  138. "] with argument of type [" + value.getClass().getName() + "], value=[" + value + "]";
  139. throw new HibernateException(msg, exception);
  140. }
  141. }
  142. }
  143. @Override
  144. public String objectToSQLString(Object value) {
  145. return SINGLE_QUOTE + ((Enum) value).name() + SINGLE_QUOTE;
  146. }
  147. @Override
  148. public Object replace(Object original, Object target, Object owner) throws HibernateException {
  149. return original;
  150. }
  151. @Override
  152. public Class returnedClass() {
  153. return enumClass;
  154. }
  155. @Override
  156. public void setParameterValues(Properties parameters) {
  157. String enumClassName = parameters.getProperty("enumClass");
  158. String identifierMethodName = parameters.getProperty("identifierMethod", DEFAULT_IDENTIFIER_METHOD_NAME);
  159. String valueOfMethodName = parameters.getProperty("valueOfMethod", DEFAULT_VALUE_OF_METHOD_NAME);
  160. initialize(enumClassName, identifierMethodName, valueOfMethodName);
  161. }
  162. @Override
  163. public int[] sqlTypes() {
  164. return sqlTypes;
  165. }
  166. @Override
  167. public String toXMLString(Object value) {
  168. return ((Enum<?>) value).name();
  169. }
  170. @SuppressWarnings("unchecked")
  171. private void initialize(String enumClassName, String identifierMethodName, String valueOfMethodName) {
  172. try {
  173. enumClass = Class.forName(enumClassName).asSubclass(Enum.class);
  174. } catch (ClassNotFoundException exception) {
  175. throw new HibernateException("Enum class not found", exception);
  176. }
  177. try {
  178. identifierMethod = enumClass.getMethod(identifierMethodName, NULL_CLASS_VARARG);
  179. } catch (Exception exception) {
  180. throw new HibernateException("Failed to obtain identifier method", exception);
  181. }
  182. Class<?> identifierType = identifierMethod.getReturnType();
  183. try {
  184. valueOfMethod = enumClass.getMethod(valueOfMethodName, identifierType);
  185. } catch (Exception exception) {
  186. throw new HibernateException("Failed to obtain valueOf method", exception);
  187. }
  188. //TODO: We really shouldn't be instantiating this, but I don't know how to get the SessionImplementor here
  189. BasicTypeRegistry registry = new BasicTypeRegistry();
  190. type = (SingleColumnType<Object>) registry.getRegisteredType(identifierType.getName());
  191. if (type == null) {
  192. throw new HibernateException("Unsupported identifier type " + identifierType.getName());
  193. }
  194. sqlTypes = new int[]{type.sqlType()};
  195. }
  196. /**
  197. * Prevents attempts to deserialize the {@code GenericEnumUserType} directly, as {@link SerializationProxy}
  198. * should have been written in its place.
  199. *
  200. * @param stream ignored
  201. */
  202. private void readObject(ObjectInputStream stream) {
  203. throw new UnsupportedOperationException(getClass().getName() + " cannot be deserialized directly");
  204. }
  205. /**
  206. * Replaces this {@code GenericEnumUserType} (which is not really serializable due to the {@code Method} fields)
  207. * with a simple {@link SerializationProxy} containing the names for the enum class, the identifier method and
  208. * the valueOf method.
  209. *
  210. * @return a new {@link SerializationProxy} to be serialized in this type's place
  211. */
  212. private Object writeReplace() {
  213. return new SerializationProxy(enumClass.getName(), identifierMethod.getName(), valueOfMethod.getName());
  214. }
  215. /**
  216. * Simple proxy to serialize in place of the more complicated {@link GenericEnumUserType}. This proxy contains
  217. * all the data necessary to recreate/reinitialize the full {@link GenericEnumUserType}.
  218. */
  219. private static class SerializationProxy implements Serializable {
  220. private final String enumClassName;
  221. private final String identifierMethodName;
  222. private final String valueOfMethodName;
  223. private SerializationProxy(String enumClassName, String identifierMethodName, String valueOfMethodName) {
  224. this.enumClassName = enumClassName;
  225. this.identifierMethodName = identifierMethodName;
  226. this.valueOfMethodName = valueOfMethodName;
  227. }
  228. private Object readResolve() {
  229. GenericEnumUserType type = new GenericEnumUserType();
  230. type.initialize(enumClassName, identifierMethodName, valueOfMethodName);
  231. return type;
  232. }
  233. }
  234. }