/* * Copyright 2000-2013 Enonic AS * http://www.enonic.com/license */ package com.enonic.cms.store.hibernate.id; import java.io.Serializable; import java.lang.reflect.Constructor; import java.sql.Connection; 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.engine.TransactionHelper; import org.hibernate.id.Configurable; import org.hibernate.id.IdentifierGenerationException; import org.hibernate.id.PersistentIdentifierGenerator; import org.hibernate.type.Type; import org.hibernate.util.PropertiesHelper; import org.hibernate.util.ReflectHelper; /** * Class for generating keys for our "user typed" identifiers. Specify table and idClassName, where idClassName is the actual domain class * to instantiate passing an Integer to the constructor. */ public class IntegerBasedCustomIdentifierGenerator extends TransactionHelper implements PersistentIdentifierGenerator, Configurable { private static final String SELECT_LASTKEY = "SELECT key_llastkey FROM tkey WHERE key_sTableName = ?"; private static final String UPDATE_LASTKEY = "UPDATE tKey SET key_lLastKey = ( key_lLastKey + ? ) WHERE key_sTableName = ?"; private static final String INSERT_NEWKEY = "INSERT INTO tKey ( key_sTableName, key_lLastKey ) VALUES ( ?, ? )"; private static final String TABLE = "table"; private static final String ID_CLASS_NAME = "idClassName"; private String tableName; private Class idClass; public String[] sqlCreateStrings( Dialect dialect ) throws HibernateException { StringBuffer sql = new StringBuffer(); sql.append( "CREATE TABLE tKey (" ); sql.append( " key_sTableName varchar(18) not null" ); sql.append( ",key_lLastKey integer not null" ); sql.append( ",primary key (key_sTableName)" ); sql.append( " )" ); return new String[]{sql.toString()}; } public String[] sqlDropStrings( Dialect dialect ) throws HibernateException { StringBuffer sql = new StringBuffer(); sql.append( "DROP TABLE tKey" ); return new String[]{sql.toString()}; } public Object generatorKey() { return this.getClass().getName(); } public void configure( Type type, Properties params, Dialect d ) throws MappingException { idClass = parseClass( PropertiesHelper.getString( ID_CLASS_NAME, params, null ) ); tableName = PropertiesHelper.getString( TABLE, params, null ); if ( tableName == null ) { throw new IllegalArgumentException( "Property '" + TABLE + "' is not set" ); } tableName = tableName.toLowerCase(); } private Class parseClass( String className ) { try { return ReflectHelper.classForName( className ); } catch ( ClassNotFoundException e ) { throw new MappingException( "Failed to parse class: " + className, e ); } } public Serializable generate( SessionImplementor session, Object object ) throws HibernateException { return doWorkInNewTransaction( session ); } protected Serializable doWorkInCurrentTransaction( Connection conn, String sql ) throws SQLException { try { updateNextKey( conn, tableName, 1 ); } catch ( KeyRowNotFoundException e ) { // insert row for tableName and try one more time insertNewKey( conn, tableName ); updateNextKey( conn, tableName, 1 ); } Integer nextKey = selectLastKey( conn, tableName ); return convertToUserType( nextKey ); } private Serializable convertToUserType( Integer value ) { try { Constructor constructor = idClass.getConstructor( new Class[]{Integer.class} ); return (Serializable) constructor.newInstance( value ); } catch ( Exception e ) { throw new RuntimeException( "Failed to instantiate (" + value + "). " + idClass + " probably do not have a constructor that takes only one Integer.", e ); } } private void updateNextKey( Connection conn, String tableName, int steps ) throws SQLException, KeyRowNotFoundException { PreparedStatement ps = conn.prepareStatement( UPDATE_LASTKEY ); try { ps.setInt( 1, steps ); ps.setString( 2, tableName ); int result = ps.executeUpdate(); if ( result < 1 ) { throw new KeyRowNotFoundException(); } } finally { closeStatement( ps ); } } private void insertNewKey( Connection conn, String tableName ) throws SQLException { PreparedStatement ps = conn.prepareStatement( INSERT_NEWKEY ); try { ps.setString( 1, tableName ); ps.setInt( 2, 0 ); ps.executeUpdate(); } finally { closeStatement( ps ); } } private Integer selectLastKey( Connection conn, String tableName ) throws SQLException { PreparedStatement ps = conn.prepareStatement( SELECT_LASTKEY ); try { ps.setString( 1, tableName ); ResultSet rs = ps.executeQuery(); if ( !rs.next() ) { String msg = "Failed to read next key value for table '" + tableName + "', no row."; throw new IdentifierGenerationException( msg ); } Integer lastKey = rs.getInt( 1 ); if ( rs.wasNull() ) { String msg = "Failed to read next key value for table '" + tableName + "', key_llastkey was null."; throw new IdentifierGenerationException( msg ); } closeResultSet( rs ); return lastKey; } finally { closeStatement( ps ); } } private void closeResultSet( ResultSet rs ) throws SQLException { if ( rs != null ) { rs.close(); } } private void closeStatement( PreparedStatement ps ) throws SQLException { if ( ps != null ) { ps.close(); } } private class KeyRowNotFoundException extends RuntimeException { } }