package org.hibernate.id; import java.io.Serializable; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Properties; import org.hibernate.HibernateException; import org.hibernate.MappingException; import org.hibernate.dialect.Dialect; import org.hibernate.engine.SessionImplementor; import org.hibernate.exception.JDBCExceptionHelper; import org.hibernate.mapping.Table; import org.hibernate.type.StringType; import org.hibernate.type.Type; import org.hibernate.util.StringHelper; /** * FixedWidthStringGenerator *

* An IdentifierGenerator that returns a String representing a Number. *

*

* Based on org.hibernate.id.IncrementGenerator, counts from the maximum * primary key value at startup, but base-36 encodes the value, and left-pads with zeros * to produce a fixed-width id, * ranging from 1 to Long.MAX_VALUE ( "0000000000001" to "1y2p0ij32e8e7" ) * (13 chars) when configured with <param name="type">long</param>, *
* 1 to Integer.MAX_VALUE ("000001" to "zik0zj") (6 chars) when configured with * <param name="type">int</param>, *
* and * 1 to Short.MAX_VALUE ("001" to "pa7") (3 chars) when configured with * <param name="type">short</param> *

* The default number type is int. *

*

* Not safe for use in a cluster! *

*

* Mapping parameters supported, but not usually needed: tables, column. * (The tables parameter specifies a comma-separated list of table names.) *

* @author Rich Freedman */ public class FixedWidthStringIdGenerator implements IdentifierGenerator, Configurable { private static final String SHORT_PROTOTYPE = "000"; private static final String INT_PROTOTYPE = "000000"; private static final String LONG_PROTOTYPE = "0000000000000"; private String prototype = INT_PROTOTYPE; private long next; private String sql; private void setPrototype(String prototype) { this.prototype = prototype; } public synchronized Serializable generate(SessionImplementor session, Object object) throws HibernateException { if (sql != null) { getNext(session); } validateRange(next); return toString(next++); } public void configure(Type type, Properties params, Dialect dialect) throws MappingException { if(type instanceof StringType == false) { throw new MappingException("This generator only generates Strings"); } String tableList = params.getProperty("tables"); if (tableList == null) tableList = params.getProperty(PersistentIdentifierGenerator.TABLES); String[] tables = StringHelper.split(", ", tableList); String column = params.getProperty("column"); if (column == null) column = params.getProperty(PersistentIdentifierGenerator.PK); String schema = params.getProperty(PersistentIdentifierGenerator.SCHEMA); String catalog = params.getProperty(PersistentIdentifierGenerator.CATALOG); String numberType = params.getProperty("type", "int"); do { if("int".equals(numberType)) { setPrototype(INT_PROTOTYPE); break; } if("short".equals(numberType)) { setPrototype(SHORT_PROTOTYPE); break; } if("long".equals(numberType)) { setPrototype(LONG_PROTOTYPE); break; } throw new MappingException("Unsupported value for 'type' parameter: " + numberType + " valid values are int, short, and long"); } while(false); StringBuffer buf = new StringBuffer(); for (int i = 0; i < tables.length; i++) { if (tables.length > 1) { buf.append("select ").append(column).append(" from "); } buf.append(Table.qualify(catalog, schema, tables[i])); if (i < tables.length - 1) buf.append(" union "); } if (tables.length > 1) { buf.insert(0, "( ").append(" ) ids_"); column = "ids_." + column; } sql = "select max(" + column + ") from " + buf.toString(); } private void getNext(SessionImplementor session) throws HibernateException { try { PreparedStatement st = session.getBatcher().prepareSelectStatement(sql); try { ResultSet rs = st.executeQuery(); try { if (rs.next()) { String highValue = rs.getString(1); if(rs.wasNull()) { next = 1; } else { long temp = toLong(highValue); validateRange(temp); next = temp + 1; } } else { next = 1; } sql = null; } finally { rs.close(); } } finally { session.getBatcher().closeStatement(st); } } catch (SQLException sqle) { throw JDBCExceptionHelper.convert(session.getFactory().getSQLExceptionConverter(), sqle, "could not fetch initial value for increment generator", sql); } } private void validateRange(long value) throws HibernateException { if(prototype == SHORT_PROTOTYPE && value >= Short.MAX_VALUE) { throw new HibernateException("Short.MAX_VALUE exceeded"); } if(prototype == INT_PROTOTYPE && value >= Integer.MAX_VALUE) { throw new HibernateException("Integer.MAX_VALUE exceeded"); } if(prototype == LONG_PROTOTYPE && value >= Long.MAX_VALUE) { throw new HibernateException("Long.MAX_VALUE exceeded"); } } /* * Produces a fixed-width base-36 representation of the number */ private String toString(Number num) { StringBuilder buff = new StringBuilder().append(Long.toString(num.longValue(), 36)); pad(buff); return buff.toString(); } /* * decodes the string representation of the number */ private long toLong(String string) { return Long.parseLong(string, 36); } /* * pads the string to at least the length of the prototype */ private void pad(StringBuilder buff) { if(buff.length() < prototype.length()) { buff.insert(0, prototype, 0, prototype.length() - buff.length()); } } }