package ome.tools.hibernate; import java.io.Serializable; import java.lang.reflect.Field; import java.sql.Array; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Types; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Properties; import org.hibernate.HibernateException; import org.hibernate.usertype.ParameterizedType; import org.hibernate.usertype.UserType; /** * Hibernate type to store Lists of primitives using SQL ARRAY. * * @author Sylvain * * References : http://forum.hibernate.org/viewtopic.php?t=946973 * http://archives.postgresql.org/pgsql-jdbc/2003-02/msg00141.php */ public abstract class ListAsSQLArrayUserType<T> implements UserType, ParameterizedType { public interface ArrayFactory { Array BOOLEAN(Connection conn, List<Boolean> value) throws SQLException; Array DATE(Connection conn, List<Date> value) throws SQLException; Array DOUBLE(Connection conn, List<Double> value) throws SQLException; Array FLOAT(Connection conn, List<Float> value) throws SQLException; Array INTEGER(Connection conn, List<Integer> value) throws SQLException; Array STRING(Connection conn, List<String> value) throws SQLException; Array STRING2(Connection conn, List<String[]> value) throws SQLException; } private static final int SQL_TYPE = Types.ARRAY; private static final int[] SQL_TYPES = { SQL_TYPE }; private /*final*/ String profile; protected ArrayFactory factory; public void setParameterValues(Properties parameters) { profile = parameters.getProperty("profile"); try { Class FACTORY = Class.forName("ome.tools.hibernate." + profile.toUpperCase()); Field field = FACTORY.getField("ARRAY_FACTORY"); factory = (ArrayFactory) field.get(null); } catch (ClassNotFoundException e) { factory = SqlArray.FACTORY; // DEFAULT } catch (Exception e) { throw new RuntimeException("Failed to acquire factory for profile " + profile, e); } } abstract protected Array getDataAsArray(Connection conn, Object value) throws SQLException; abstract protected List<T> getDataFromArray(Object primitivesArray); /** * To use, define : hibernate.property * type="ome.tools.hibernate.ListAsSQLArrayUserType$BOOLEAN" * hibernate.column name="fieldName" sql-type="bool[]" */ public static class BOOLEAN extends ListAsSQLArrayUserType<Boolean> { @Override @SuppressWarnings("unchecked") protected Array getDataAsArray(Connection conn, Object value) throws SQLException { return factory.BOOLEAN(conn, (List<Boolean>) value); } @Override protected List<Boolean> getDataFromArray(Object array) { boolean[] booleans = (boolean[]) array; ArrayList<Boolean> result = new ArrayList<Boolean>(booleans.length); for (boolean b : booleans) result.add(b); return result; } } /** * To use, define : hibernate.property * type="ome.tools.hibernate.ListAsSQLArrayUserType$INTEGER" * hibernate.column name="fieldName" sql-type="int[]" */ public static class INTEGER extends ListAsSQLArrayUserType<Integer> { @Override @SuppressWarnings("unchecked") protected Array getDataAsArray(Connection conn, Object value) throws SQLException { return factory.INTEGER(conn, (List<Integer>) value); } @Override protected List<Integer> getDataFromArray(Object array) { int[] ints = (int[]) array; ArrayList<Integer> result = new ArrayList<Integer>(ints.length); for (int i : ints) result.add(i); return result; } } /** * To use, define : hibernate.property * type="ome.tools.hibernate.ListAsSQLArrayUserType$FLOAT" * hibernate.column name="fieldName" sql-type="real[]" */ public static class FLOAT extends ListAsSQLArrayUserType<Float> { @Override @SuppressWarnings("unchecked") protected Array getDataAsArray(Connection conn, Object value) throws SQLException { return factory.FLOAT(conn, (List<Float>) value); } @Override protected List<Float> getDataFromArray(Object array) { float[] floats = (float[]) array; ArrayList<Float> result = new ArrayList<Float>(floats.length); for (float f : floats) result.add(f); return result; } } /** * To use, define : hibernate.property * type="ome.tools.hibernate.ListAsSQLArrayUserType$DOUBLE" * hibernate.column name="fieldName" sql-type="float8[]" */ public static class DOUBLE extends ListAsSQLArrayUserType<Double> { @Override @SuppressWarnings("unchecked") protected Array getDataAsArray(Connection conn, Object value) throws SQLException { return factory.DOUBLE(conn, (List<Double>) value); } @Override protected List<Double> getDataFromArray(Object array) { double[] doubles = (double[]) array; ArrayList<Double> result = new ArrayList<Double>(doubles.length); for (double d : doubles) result.add(d); return result; } } /** * To use, define : hibernate.property * type="ome.tools.hibernate.ListAsSQLArrayUserType$STRING" * hibernate.column name="fieldName" sql-type="text[]" */ public static class STRING extends ListAsSQLArrayUserType<String> { @Override @SuppressWarnings("unchecked") protected Array getDataAsArray(Connection conn, Object value) throws SQLException { return factory.STRING(conn, (List<String>) value); } @Override protected List<String> getDataFromArray(Object array) { String[] strings = (String[]) array; ArrayList<String> result = new ArrayList<String>(strings.length); for (String s : strings) result.add(s); return result; } } /** * To use, define : hibernate.property * type="ome.tools.hibernate.ListAsSQLArrayUserType$STRING2" * hibernate.column name="fieldName" sql-type="text[]" * * Added by Josh */ public static class STRING2 extends ListAsSQLArrayUserType<String[]> { @Override @SuppressWarnings("unchecked") protected Array getDataAsArray(Connection conn, Object value) throws SQLException { return factory.STRING2(conn, (List<String[]>) value); } @Override protected List<String[]> getDataFromArray(Object array) { if (String[][].class.isAssignableFrom(array.getClass())) { String[][] strings = (String[][]) array; ArrayList<String[]> result = new ArrayList<String[]>(strings.length); for (String[] s : strings) result.add(s); return result; } else { // ticket:2290 if (String[].class.isAssignableFrom(array.getClass())) { String[] strings = (String[]) array; if (strings.length == 0) { // ok. String[0][] got changed to String[0] return new ArrayList<String[]>(0); } } throw new RuntimeException("ticket:2290 - bad array type: " + array); } } } /** * To use, define : hibernate.property * type="ome.tools.hibernate.ListAsSQLArrayUserType$DATE" * hibernate.column name="fieldName" sql-type="timestamp[]" */ public static class DATE extends ListAsSQLArrayUserType<Date> { @Override @SuppressWarnings("unchecked") protected Array getDataAsArray(Connection conn, Object value) throws SQLException { return factory.DATE(conn, (List<Date>) value); } @Override protected List<Date> getDataFromArray(Object array) { Date[] dates = (Date[]) array; ArrayList<Date> result = new ArrayList<Date>(dates.length); for (Date d : dates) result.add(d); return result; } } /** * Warning, this one is special. You have to define a class that extends * ENUM_LIST<E> and that has a no arguments constructor. For example : * class MyEnumsList extends ENUM_LIST&<MyEnumType> { public * MyEnumList(){ super( MyEnum.values() ); } } Then, define : * hibernate.property type="com.myPackage.MyEnumsList" hibernate.column * name="fieldName" sql-type="int[]" */ public static class ENUM<E extends Enum<E>> extends ListAsSQLArrayUserType<E> { private E[] theEnumValues; /** * @param theEnumValues * The values of enum (by invoking .values()). */ protected ENUM(E[] theEnumValues) { this.theEnumValues = theEnumValues; } @Override @SuppressWarnings("unchecked") protected Array getDataAsArray(Connection conn, Object value) throws SQLException { List<E> enums = (List<E>) value; List<Integer> integers = new ArrayList<Integer>(enums.size()); for (E theEnum : enums) integers.add(theEnum.ordinal()); return factory.INTEGER(conn, integers); } @Override protected List<E> getDataFromArray(Object array) { int[] ints = (int[]) array; ArrayList<E> result = new ArrayList<E>(ints.length); for (int val : ints) { for (int i = 0; i < theEnumValues.length; i++) { if (theEnumValues[i].ordinal() == val) { result.add(theEnumValues[i]); break; } } } if (result.size() != ints.length) throw new RuntimeException("Error attempting to convert " + array + " into an array of enums (" + theEnumValues + ")."); return result; } } public Class returnedClass() { return List.class; } public int[] sqlTypes() { return SQL_TYPES; } public Object deepCopy(Object value) { return value; } public boolean isMutable() { return true; } public Object nullSafeGet(ResultSet resultSet, String[] names, Object owner) throws HibernateException, SQLException { Array sqlArray = resultSet.getArray(names[0]); if (resultSet.wasNull()) return null; return getDataFromArray(sqlArray.getArray()); } public void nullSafeSet(PreparedStatement preparedStatement, Object value, int index) throws HibernateException, SQLException { if (null == value) preparedStatement.setNull(index, SQL_TYPE); else preparedStatement.setArray(index, getDataAsArray(preparedStatement.getConnection(), value)); } public int hashCode(Object x) throws HibernateException { return x.hashCode(); } public boolean equals(Object x, Object y) throws HibernateException { if (x == y) return true; if (null == x || null == y) return false; Class<?> javaClass = returnedClass(); if (!javaClass.equals(x.getClass()) || !javaClass.equals(y.getClass())) return false; return x.equals(y); } public Object assemble(Serializable cached, Object owner) throws HibernateException { return cached; } public Serializable disassemble(Object value) throws HibernateException { return (Serializable) value; } public Object replace(Object original, Object target, Object owner) throws HibernateException { return original; } }