org.jboss.ejb3.entity.JTATableIdGenerator.java Source code

Java tutorial

Introduction

Here is the source code for org.jboss.ejb3.entity.JTATableIdGenerator.java

Source

/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2006, Red Hat Middleware LLC, and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.jboss.ejb3.entity;

import java.io.Serializable;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Properties;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.SessionImplementor;
import org.hibernate.exception.JDBCExceptionHelper;
import org.hibernate.id.Configurable;
import org.hibernate.id.IdentifierGenerationException;
import org.hibernate.id.IdentifierGeneratorFactory;
import org.hibernate.id.PersistentIdentifierGenerator;
import org.hibernate.transaction.JBossTransactionManagerLookup;
import org.hibernate.transaction.TransactionManagerLookup;
import org.hibernate.type.Type;
import org.hibernate.util.PropertiesHelper;

/**
 * A hilo <tt>IdentifierGenerator</tt> that uses a database
 * table to store the last generated value.
 * <p/>
 * <p/>
 * This implementation is solely for use inside JBoss using JTA for transactions.
 * </p>
 * <p/>
 * TODO implement sequence allocation
 *
 * @author <a href="mailto:kr@hbt.de">Klaus Richarz</a>.
 * @version <tt>$Revision: 57207 $</tt>
 * @see org.hibernate.id.TableGenerator
 * @see javax.persistence.TableGenerator
 */
public class JTATableIdGenerator implements PersistentIdentifierGenerator, Configurable {
    /* COLUMN and TABLE should be renamed but it would break the public API */
    /**
     * The column parameter
     */
    public static final String COLUMN = "column";

    /**
     * Default column name
     */
    public static final String DEFAULT_COLUMN_NAME = "next_hi";

    /**
     * The table parameter
     */
    public static final String TABLE = "table";

    /**
     * Default table name
     */
    public static final String DEFAULT_TABLE_NAME = "next_hi";

    /**
     * The allocation-size parameter
     */
    public static final String ALLOCATION_SIZE = "allocationSize";

    /**
     * Default allocation-size
     */
    public static final int DEFAULT_ALLOCATION_SIZE = 20;

    /**
     * logger for JTATableGenerator
     */
    private static final Log log = LogFactory.getLog(JTATableIdGenerator.class);

    /**
     * Holds the name where this generator gets its sequence from
     */
    private String tableName;

    /**
     * Holds the name ofthe column where the next sequence value is stored
     */
    private String columnName;

    /**
     * Holds the sql query to retrieve the next high value
     */
    private String query;

    /**
     * Holds the sql query to increment the sequence
     */
    private String update;

    /**
     * Holds the transaction manager lookup object
     */
    private TransactionManagerLookup transactionManagerLookup;

    /**
     * Holds the class type for the sequence value returned by generate()
     */
    private Class returnClass;

    /**
     * Holds the size for the sequence increment. The allocated sequences are managed in memory
     * and may be lost if the system stops.
     */
    private int allocationSize;

    public void configure(Type type, Properties params, Dialect dialect) {
        this.tableName = PropertiesHelper.getString(TABLE, params, DEFAULT_TABLE_NAME);
        this.columnName = PropertiesHelper.getString(COLUMN, params, DEFAULT_COLUMN_NAME);
        this.allocationSize = PropertiesHelper.getInt(ALLOCATION_SIZE, params, DEFAULT_ALLOCATION_SIZE);
        String schemaName = params.getProperty(SCHEMA);
        String catalogName = params.getProperty(CATALOG);

        if (true)
            throw new RuntimeException("DOES ANYBODY USE THIS?  It IS CURRENTLY BROKEN");

        /*
        getSchemaSeparator does not exist in hibernate anymore since 3.1 release
            
        // prepare table name
        if (tableName.indexOf(dialect.getSchemaSeparator()) < 0)
        {
           tableName = Table.qualify(catalogName, schemaName, tableName, dialect.getSchemaSeparator());
        }
        */

        // prepare SQL statements
        query = "select " + columnName + " from " + dialect.appendLockHint(LockMode.UPGRADE, tableName)
                + dialect.getForUpdateString();
        update = "update " + tableName + " set " + columnName + " = ? where " + columnName + " = ?";

        // set up transaction manager lookup
        // only JBoss transaction manager is supported
        transactionManagerLookup = new JBossTransactionManagerLookup();

        // set the sequence type that should be returned
        returnClass = type.getReturnedClass();

        // debug chosen configuration
        if (log.isDebugEnabled()) {
            log.debug("configuring id generator: " + this.getClass().getName());
            log.debug("tableName=" + tableName);
            log.debug("columnName=" + columnName);
            log.debug("allocationSize=" + allocationSize);
            log.debug("query=" + query);
            log.debug("update=" + update);
            log.debug("returnClass=" + returnClass);
        }
    }

    public synchronized Serializable generate(SessionImplementor session, Object object) throws HibernateException {
        // get TransactionManager from JNDI
        // no JNDI properties provided -> we are in the container
        TransactionManager tm = transactionManagerLookup.getTransactionManager(new Properties());
        Transaction surroundingTransaction = null; // for resuming in finally block
        Connection conn = null; // for ressource cleanup
        String sql = null; // for exception
        try {
            long result; // holds the resulting sequence value

            // prepare a new transaction context for the generator
            surroundingTransaction = tm.suspend();
            if (log.isDebugEnabled()) {
                log.debug("surrounding tx suspended");
            }
            tm.begin();

            // get connection from managed environment
            conn = session.getBatcher().openConnection();

            // execute fetching of current sequence value
            sql = query;
            PreparedStatement qps = conn.prepareStatement(query);
            try {
                ResultSet rs = qps.executeQuery();
                if (!rs.next()) {
                    String err = "could not read sequence value - you need to populate the table: " + tableName;
                    log.error(err);
                    throw new IdentifierGenerationException(err);
                }
                result = rs.getLong(1);
                rs.close();
            } catch (SQLException sqle) {
                log.error("could not read a sequence value", sqle);
                throw sqle;
            } finally {
                qps.close();
            }

            // increment sequence value
            sql = update;
            long sequence = result + 1;
            PreparedStatement ups = conn.prepareStatement(update);
            try {
                ups.setLong(1, sequence);
                ups.setLong(2, result);
                ups.executeUpdate();
            } catch (SQLException sqle) {
                log.error("could not update sequence value in: " + tableName, sqle);
                throw sqle;
            } finally {
                ups.close();
            }

            // commit transaction to ensure updated sequence is not rolled back
            tm.commit();

            // transform sequence to the desired type and return the value
            Number typedSequence = IdentifierGeneratorFactory.createNumber(sequence, returnClass);
            if (log.isDebugEnabled()) {
                log.debug("generate() returned: " + typedSequence);
            }
            return typedSequence;
        } catch (SQLException sqle) {
            throw JDBCExceptionHelper.convert(session.getFactory().getSQLExceptionConverter(), sqle,
                    "could not get or update next value", sql);
        } catch (Exception e) {
            try {
                tm.rollback();
                throw new HibernateException(e);
            } catch (SystemException e1) {
                throw new HibernateException(e1);
            }
        } finally {
            if (conn != null)
                try {
                    conn.close();
                } catch (SQLException e) {
                    // ignore exception
                }
            // switch back to surrounding transaction context
            if (surroundingTransaction != null) {
                try {
                    tm.resume(surroundingTransaction);
                    if (log.isDebugEnabled()) {
                        log.debug("surrounding tx resumed");
                    }
                } catch (Exception e) {
                    throw new HibernateException(e);
                }
            }
        }
    }

    public String[] sqlCreateStrings(Dialect dialect) throws HibernateException {
        return new String[] {
                "create table " + tableName + " ( " + columnName + " " + dialect.getTypeName(Types.BIGINT) + " )",
                "insert into " + tableName + " values ( 0 )" };
    }

    public String[] sqlDropStrings(Dialect dialect) {
        //return "drop table " + tableName + dialect.getCascadeConstraintsString();
        StringBuffer sqlDropString = new StringBuffer().append("drop table ");
        if (dialect.supportsIfExistsBeforeTableName()) {
            sqlDropString.append("if exists ");
        }
        sqlDropString.append(tableName).append(dialect.getCascadeConstraintsString());
        if (dialect.supportsIfExistsAfterTableName()) {
            sqlDropString.append(" if exists");
        }
        return new String[] { sqlDropString.toString() };
    }

    public Object generatorKey() {
        return tableName;
    }
}