org.hyperic.hibernate.id.HQMultipleHiLoPerTableGenerator.java Source code

Java tutorial

Introduction

Here is the source code for org.hyperic.hibernate.id.HQMultipleHiLoPerTableGenerator.java

Source

/**
 * NOTE: This copyright does *not* cover user programs that use HQ
 * program services by normal system calls through the application
 * program interfaces provided as part of the Hyperic Plug-in Development
 * Kit or the Hyperic Client Development Kit - this is merely considered
 * normal use of the program, and does *not* fall under the heading of
 *  "derived work".
 *
 *  Copyright (C) [2009-2010], VMware, Inc.
 *  This file is part of HQ.
 *
 *  HQ is free software; you can redistribute it and/or modify
 *  it under the terms version 2 of the GNU General Public License as
 *  published by the Free Software Foundation. This program 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 General Public License for more
 *  details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 *  USA.
 *
 */

//$Id: MultipleHiLoPerTableGenerator.java 9720 2006-03-31 00:11:54Z epbernard $
package org.hyperic.hibernate.id;

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.LinkedList;
import java.util.Properties;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.MappingException;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.SessionImplementor;
import org.hibernate.engine.TransactionHelper;
import org.hibernate.exception.JDBCExceptionHelper;
import org.hibernate.id.Configurable;
import org.hibernate.id.IdentifierGeneratorFactory;
import org.hibernate.id.PersistentIdentifierGenerator;
import org.hibernate.mapping.Table;
import org.hibernate.type.Type;
import org.hibernate.util.PropertiesHelper;
import org.hyperic.hq.context.Bootstrap;
import org.hyperic.util.jdbc.DBUtil;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;

/**
 * 
 * A hilo <tt>IdentifierGenerator</tt> that returns a <tt>Long</tt>, constructed
 * using a hi/lo algorithm. The hi value MUST be fetched in a seperate
 * transaction to the <tt>Session</tt> transaction so the generator must be able
 * to obtain a new connection and commit it. If running in a Spring-managed
 * transaction, the TransactionTemplate will be used to suspend the existing
 * transaction and create a new one. Else, an attempt will be made to open a new
 * JDBC connection to do the work.
 * 
 * A hilo <tt>IdentifierGenerator</tt> that uses a database table to store the
 * last generated values. A table can contains several hi values. They are
 * distinct from each other through a key
 * <p/>
 * <p>
 * This implementation is not compliant with a user connection
 * </p>
 * <p/>
 * 
 * <p>
 * Allowed parameters (all of them are optional):
 * </p>
 * <ul>
 * <li>table: table name (default <tt>hibernate_sequences</tt>)</li>
 * <li>primary_key_column: key column name (default <tt>sequence_name</tt>)</li>
 * <li>value_column: hi value column name(default
 * <tt>sequence_next_hi_value</tt>)</li>
 * <li>primary_key_value: key value for the current entity (default to the
 * entity's primary table name)</li>
 * <li>primary_key_length: length of the key column in DB represented as a
 * varchar (default to 255)</li>
 * <li>max_lo: max low value before increasing hi (default to Short.MAX_VALUE)</li>
 * </ul>
 * 
 * @author Emmanuel Bernard
 * @author <a href="mailto:kr@hbt.de">Klaus Richarz</a>.
 */
public class HQMultipleHiLoPerTableGenerator extends TransactionHelper
        implements PersistentIdentifierGenerator, Configurable {

    private static final Log log = LogFactory.getLog(HQMultipleHiLoPerTableGenerator.class);

    private LinkedList seqs = new LinkedList();

    public static final String ID_TABLE = "table";
    public static final String PK_COLUMN_NAME = "primary_key_column";
    public static final String PK_VALUE_NAME = "primary_key_value";
    public static final String VALUE_COLUMN_NAME = "value_column";
    public static final String PK_LENGTH_NAME = "primary_key_length";
    public static final String INITIAL_HI = "initial_hi";
    public static final int DEFAULT_INITIAL_HI = 0;

    private static final int DEFAULT_PK_LENGTH = 255;
    public static final String DEFAULT_TABLE = "hibernate_sequences";
    private static final String DEFAULT_PK_COLUMN = "sequence_name";
    private static final String DEFAULT_VALUE_COLUMN = "sequence_next_hi_value";

    private String tableName;
    private String pkColumnName;
    private String valueColumnName;
    private String query;
    private String insert;
    private String update;
    private String keyValue;

    // hilo params
    public static final String MAX_LO = "max_lo";

    private long hi;
    private int lo;
    private int maxLo;
    private int initialHi;
    private Class returnClass;
    private int keySize;

    public String[] sqlCreateStrings(Dialect dialect) throws HibernateException {
        return new String[] { new StringBuffer().append("create table ").append(tableName).append(" ( ")
                .append(pkColumnName).append(" ").append(dialect.getTypeName(Types.VARCHAR, keySize, 0, 0))
                .append(",  ").append(valueColumnName).append(" ").append(dialect.getTypeName(Types.INTEGER))
                .append(", ").append("primary key (" + pkColumnName + ") ").append(" ) ").toString() };
    }

    public String[] sqlDropStrings(Dialect dialect) throws HibernateException {
        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;
    }

    protected Serializable doWorkInCurrentTransaction(Connection conn, String sql) throws SQLException {
        return updateSequence(conn);
    }

    private Integer updateSequence(Connection connection) throws SQLException {
        int result;
        int rows;
        do {
            // The loop ensures atomicity of the
            // select + update even for no transaction
            // or read committed isolation level

            // sql = query;
            log.debug(query);
            PreparedStatement qps = connection.prepareStatement(query);
            PreparedStatement ips = null;
            try {
                // qps.setString(1, key);
                ResultSet rs = qps.executeQuery();
                boolean isInitialized = rs.next();
                if (!isInitialized) {
                    result = 0;
                    ips = connection.prepareStatement(insert);
                    // ips.setString(1, key);
                    ips.setInt(1, initialHi);
                    ips.execute();
                } else {
                    result = rs.getInt(1);
                }
                rs.close();
            } catch (SQLException sqle) {
                log.error("could not read or init a hi value", sqle);
                throw sqle;
            } finally {
                if (ips != null) {
                    ips.close();
                }
                qps.close();
            }

            // sql = update;
            PreparedStatement ups = connection.prepareStatement(update);
            try {
                ups.setInt(1, result + 1);
                ups.setInt(2, result);
                // ups.setString( 3, key );
                rows = ups.executeUpdate();
            } catch (SQLException sqle) {
                log.error("could not update hi value in: " + tableName, sqle);
                throw sqle;
            } finally {
                ups.close();
            }
        } while (rows == 0);
        return new Integer(result);
    }

    public synchronized Serializable generate(SessionImplementor session, Object obj) throws HibernateException {
        Number num;
        do {
            num = _generate(session, obj);
        } while (!numberIsValid(num));
        return num;
    }

    private int executeInNewTransaction(TransactionTemplate transactionTemplate, final SessionImplementor session) {
        if (transactionTemplate != null) {
            return transactionTemplate.execute(new TransactionCallback<Integer>() {
                //We are in a Spring managed environment
                public Integer doInTransaction(TransactionStatus status) {
                    try {
                        return updateSequence(Bootstrap.getBean(DBUtil.class).getConnection());
                    } catch (SQLException sqle) {
                        throw JDBCExceptionHelper.convert(session.getFactory().getSQLExceptionConverter(), sqle,
                                "could not get or update next value", null);
                    }
                }
            });
        } else {
            //Use Hibernate's JDBC delegation
            return (Integer) doWorkInNewTransaction(session);
        }
    }

    private synchronized Number _generate(final SessionImplementor session, Object obj) throws HibernateException {
        TransactionTemplate transactionTemplate = null;
        if (Bootstrap.hasAppContext()) {
            // We are running in Spring-managed server environment. Suspend the
            // existing Spring-managed transaction and work in a new one.
            transactionTemplate = new TransactionTemplate(
                    (PlatformTransactionManager) Bootstrap.getBean("transactionManager"));
            transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);

        }
        if (maxLo < 1) {
            // keep the behavior consistent even for boundary usages
            int val = executeInNewTransaction(transactionTemplate, session);

            if (val == 0) {
                val = executeInNewTransaction(transactionTemplate, session);
            }
            Number num = IdentifierGeneratorFactory.createNumber(val, returnClass);
            if (log.isTraceEnabled()) {
                log.trace(this + " created seq: " + keyValue + " / " + num);
            }
            return num;
        } else if (lo > maxLo) {
            int hival = executeInNewTransaction(transactionTemplate, session);
            lo = (hival == 0) ? 1 : 0;
            hi = hival * (maxLo + 1);
            log.debug("new hi value: " + hival);
        }
        Number num = IdentifierGeneratorFactory.createNumber(hi + lo++, returnClass);
        if (log.isTraceEnabled()) {
            log.trace(this + " created seq: " + keyValue + " / " + num);
        }
        return num;
    }

    private synchronized boolean numberIsValid(Number num) {
        if (seqs.contains(num)) {
            log.warn("sequence generator generated sequence " + keyValue + "/" + num
                    + " which is a duplicate sequence, retrying");
            return false;
        }
        seqs.add(num);
        while (seqs.size() >= 10) {
            seqs.removeLast();
        }
        return true;
    }

    public void configure(Type type, Properties params, Dialect dialect) throws MappingException {
        tableName = PropertiesHelper.getString(ID_TABLE, params, DEFAULT_TABLE);
        pkColumnName = PropertiesHelper.getString(PK_COLUMN_NAME, params, DEFAULT_PK_COLUMN);
        valueColumnName = PropertiesHelper.getString(VALUE_COLUMN_NAME, params, DEFAULT_VALUE_COLUMN);
        initialHi = PropertiesHelper.getInt(INITIAL_HI, params, DEFAULT_INITIAL_HI);
        String schemaName = params.getProperty(SCHEMA);
        String catalogName = params.getProperty(CATALOG);
        keySize = PropertiesHelper.getInt(PK_LENGTH_NAME, params, DEFAULT_PK_LENGTH);
        keyValue = PropertiesHelper.getString(PK_VALUE_NAME, params, params.getProperty(TABLE));

        if (tableName.indexOf('.') < 0) {
            tableName = Table.qualify(catalogName, schemaName, tableName);
        }

        query = "select " + valueColumnName + " from " + dialect.appendLockHint(LockMode.UPGRADE, tableName)
                + " where " + pkColumnName + " = '" + keyValue + "'" + dialect.getForUpdateString();

        update = "update " + tableName + " set " + valueColumnName + " = ? where " + valueColumnName + " = ? and "
                + pkColumnName + " = '" + keyValue + "'";

        insert = "insert into " + tableName + "(" + pkColumnName + ", " + valueColumnName + ") " + "values('"
                + keyValue + "', ?)";

        // hilo config
        maxLo = PropertiesHelper.getInt(MAX_LO, params, Short.MAX_VALUE);
        lo = maxLo + 1; // so we "clock over" on the first invocation
        returnClass = type.getReturnedClass();

    }
}