All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.openjpa.jdbc.kernel.TableJDBCSeq Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.openjpa.jdbc.kernel;

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.concurrent.ConcurrentHashMap;

import jakarta.transaction.NotSupportedException;

import org.apache.openjpa.conf.OpenJPAConfiguration;
import org.apache.openjpa.jdbc.conf.JDBCConfiguration;
import org.apache.openjpa.jdbc.conf.JDBCConfigurationImpl;
import org.apache.openjpa.jdbc.identifier.DBIdentifier;
import org.apache.openjpa.jdbc.identifier.DBIdentifier.DBIdentifierType;
import org.apache.openjpa.jdbc.identifier.Normalizer;
import org.apache.openjpa.jdbc.identifier.QualifiedDBIdentifier;
import org.apache.openjpa.jdbc.meta.ClassMapping;
import org.apache.openjpa.jdbc.schema.Column;
import org.apache.openjpa.jdbc.schema.PrimaryKey;
import org.apache.openjpa.jdbc.schema.Schema;
import org.apache.openjpa.jdbc.schema.SchemaGroup;
import org.apache.openjpa.jdbc.schema.SchemaTool;
import org.apache.openjpa.jdbc.schema.Schemas;
import org.apache.openjpa.jdbc.schema.Table;
import org.apache.openjpa.jdbc.schema.Unique;
import org.apache.openjpa.jdbc.sql.DBDictionary;
import org.apache.openjpa.jdbc.sql.Row;
import org.apache.openjpa.jdbc.sql.SQLBuffer;
import org.apache.openjpa.lib.conf.Configurable;
import org.apache.openjpa.lib.conf.Configuration;
import org.apache.openjpa.lib.conf.Configurations;
import org.apache.openjpa.lib.identifier.IdentifierUtil;
import org.apache.openjpa.lib.log.Log;
import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.lib.util.Options;
import org.apache.openjpa.lib.util.StringUtil;
import org.apache.openjpa.meta.JavaTypes;
import org.apache.openjpa.util.InvalidStateException;
import org.apache.openjpa.util.UserException;


////////////////////////////////////////////////////////////
// NOTE: Do not change property names; see SequenceMetaData
// and SequenceMapping for standard property names.
////////////////////////////////////////////////////////////

/**
 * {@link JDBCSeq} implementation that uses a database table
 * for sequence number generation. This base implementation uses a single
 * row for a global sequence number.
 *
 * @author Abe White
 */
public class TableJDBCSeq extends AbstractJDBCSeq implements Configurable {

    public static final String ACTION_DROP = "drop";
    public static final String ACTION_ADD = "add";
    public static final String ACTION_GET = "get";
    public static final String ACTION_SET = "set";
    public static final String DEFAULT_TABLE = "OPENJPA_SEQUENCE_TABLE";

    private static final Localizer _loc = Localizer.forPackage
        (TableJDBCSeq.class);

    private transient JDBCConfiguration _conf = null;
    private transient Log _log = null;
    private int _alloc = 50;
    private int _intValue = 1;
    private final ConcurrentHashMap _stat = new ConcurrentHashMap<>();

    private DBIdentifier _table = DBIdentifier.newTable(DEFAULT_TABLE);
    private DBIdentifier _seqColumnName = DBIdentifier.newColumn("SEQUENCE_VALUE");
    private DBIdentifier _pkColumnName = DBIdentifier.newColumn("ID");
    private DBIdentifier[] _uniqueColumnNames;
    private DBIdentifier _uniqueConstraintName = DBIdentifier.NULL;

    private Column _seqColumn = null;
    private Column _pkColumn = null;

    /**
     * The sequence table name. Defaults to OPENJPA_SEQUENCE_TABLE.
     * By default, the table will be placed in the first schema listed in your
     * openjpa.jdbc.Schemas property, or in the default schema if
     * the property is not given. If you specify a table name in the form
     * <schema>.<table>, then the given schema
     * will be used.
     */
    public String getTable() {
        return _table.getName();
    }

    /**
     * The sequence table name. Defaults to OPENJPA_SEQUENCE_TABLE.
     * By default, the table will be placed in the first schema listed in your
     * openjpa.jdbc.Schemas property, or in the default schema if
     * the property is not given. If you specify a table name in the form
     * <schema>.<table>, then the given schema
     * will be used.
     */
    public void setTable(String name) {
        // Split the name into its individual parts
        String[] names = Normalizer.splitName(name);
        // Join the name back together.  This will delimit as appropriate.
        _table = DBIdentifier.newTable(Normalizer.joinNames(names));
    }

    /**
     * @deprecated Use {@link #setTable}. Retained for
     * backwards-compatibility	with auto-configuration.
     */
    @Deprecated
    public void setTableName(String name) {
        setTable(name);
    }

    /**
     * The name of the column that holds the sequence value. Defaults
     * to SEQUENCE_VALUE.
     */
    public String getSequenceColumn() {
        return _seqColumnName.getName();
    }

    /**
     * The name of the column that holds the sequence value. Defaults
     * to SEQUENCE_VALUE.
     */
    public void setSequenceColumn(String sequenceColumn) {
        _seqColumnName = DBIdentifier.newColumn(sequenceColumn);
    }

    /**
     * The name of the table's primary key column. Defaults to
     * ID.
     */
    public String getPrimaryKeyColumn() {
        return _pkColumnName.getName();
    }

    public DBIdentifier getPrimaryKeyColumnIdentifier() {
        return _pkColumnName;
    }

    /**
     * The name of the table's primary key column. Defaults to
     * ID.
     */
    public void setPrimaryKeyColumn(String primaryKeyColumn) {
        _pkColumnName = DBIdentifier.newColumn(primaryKeyColumn, _conf.getDBDictionaryInstance().delimitAll());
    }

    /**
     * Return the number of sequences to allocate for each update of the
     * sequence table. Sequence numbers will be grabbed in blocks of this
     * value to reduce the number of transactions that must be performed on
     * the sequence table.
     */
    public int getAllocate() {
        return _alloc;
    }

    /**
     * Return the number of sequences to allocate for each update of the
     * sequence table. Sequence numbers will be grabbed in blocks of this
     * value to reduce the number of transactions that must be performed on
     * the sequence table.
     */
    public void setAllocate(int alloc) {
        _alloc = alloc;
    }

    /**
     * Return the number as the initial number for the
     * GeneratedValue.TABLE strategy to start with.
     * @return an initial number
     */
    public int getInitialValue() {
        return _intValue;
    }

    /**
     * Set the initial number in the table for the GeneratedValue.TABLE
     * strategy to use as initial number.
     * @param intValue. The initial number
     */
    public void setInitialValue(int intValue) {
        _intValue = intValue;
    }

    /**
     * Sets the names of the columns on which a unique constraint is set.
     * @param columnsNames are passed as a single String concatenated with
     * a '|' character. This method parses it back to array of Strings.
     */
    public void setUniqueColumns(String columnNames) {
    	_uniqueColumnNames = (StringUtil.isEmpty(columnNames))
    		? null : DBIdentifier.split(columnNames, DBIdentifierType.COLUMN, IdentifierUtil.BAR);
    }

    public String getUniqueColumns() {
    	return Normalizer.joinNames(DBIdentifier.toStringArray(_uniqueColumnNames), IdentifierUtil.BAR);
    }

    /**
     * @deprecated Use {@link #setAllocate}. Retained for backwards
     * compatibility of auto-configuration.
     */
    @Deprecated
    public void setIncrement(int inc) {
        setAllocate(inc);
    }

    @Override
    public JDBCConfiguration getConfiguration() {
        return _conf;
    }

    @Override
    public void setConfiguration(Configuration conf) {
        _conf = (JDBCConfiguration) conf;
        _log = _conf.getLog(OpenJPAConfiguration.LOG_RUNTIME);
    }

    @Override
    public void startConfiguration() {
    }

    @Override
    public void endConfiguration() {
        buildTable();
    }


    @Override
    public void addSchema(ClassMapping mapping, SchemaGroup group) {
        // Since the table is created by openjpa internally
        // we can create the table for each schema within the PU
        // in here.

        Schema[] schemas = group.getSchemas();
        for (Schema value : schemas) {
            QualifiedDBIdentifier path = QualifiedDBIdentifier.getPath(_table);
            DBIdentifier schemaName = path.getSchemaName();
            if (DBIdentifier.isEmpty(schemaName)) {
                schemaName = Schemas.getNewTableSchemaIdentifier(_conf);
            }
            if (DBIdentifier.isNull(schemaName)) {
                schemaName = value.getIdentifier();
            }

            // create table in this group
            Schema schema = group.getSchema(schemaName);
            if (schema == null) {
                schema = group.addSchema(schemaName);
            }

            Table copy = schema.importTable(_pkColumn.getTable());
            // importTable() does not import unique constraints
            Unique[] uniques = _pkColumn.getTable().getUniques();
            for (Unique u : uniques) {
                copy.importUnique(u);
            }
            // we need to reset the table name in the column with the
            // fully qualified name for matching the table name from the
            // Column.
            _pkColumn.resetTableIdentifier(QualifiedDBIdentifier.newPath(schemaName, _pkColumn.getTableIdentifier()));
            // some databases require to create an index for the sequence table
            _conf.getDBDictionaryInstance().createIndexIfNecessary(schema,
                    _table, _pkColumn);
        }
    }

    @Override
    protected Object nextInternal(JDBCStore store, ClassMapping mapping) throws Exception {
        // if needed, grab the next handful of ids
        Status stat = getStatus(mapping);
        if (stat == null)
            throw new InvalidStateException(_loc.get("bad-seq-type",
                getClass(), mapping));

        while (true) {
            synchronized (stat) {
                // make sure seq is at least 1, since autoassigned ids of 0 can
                // conflict with uninitialized values
                stat.seq = Math.max(stat.seq, 1);
                if (stat.seq < stat.max)
                    return stat.seq++;
                allocateSequence(store, mapping, stat, _alloc, true);
            }
        }
    }

    @Override
    protected Object currentInternal(JDBCStore store, ClassMapping mapping)
        throws Exception {
        if (current == null) {
            CurrentSequenceRunnable runnable =
                new CurrentSequenceRunnable(store, mapping);
            try {
                if (suspendInJTA()) {
                    // NotSupportedException is wrapped in a StoreException by
                    // the caller.
                    _conf.getManagedRuntimeInstance().doNonTransactionalWork(
                            runnable);
                } else {
                    runnable.run();
                }
            } catch (RuntimeException re) {
                throw (Exception) (re.getCause() == null ? re : re.getCause());
            }
        }
        return super.currentInternal(store, mapping);
    }

    @Override
    protected void allocateInternal(int count, JDBCStore store,
        ClassMapping mapping)
        throws SQLException {
        Status stat = getStatus(mapping);
        if (stat == null)
            return;

        while (true) {
            int available;
            synchronized (stat) {
                available = (int) (stat.max - stat.seq);
                if (available >= count)
                    return;
            }
            allocateSequence(store, mapping, stat, count - available, false);
        }
    }

    /**
     * Return the appropriate status object for the given class, or null
     * if cannot handle the given class. The mapping may be null.
     */
    protected Status getStatus(ClassMapping mapping) {
        Status status = _stat.get(mapping);
        if (status == null){
            status = new Status();
            Status tStatus = _stat.putIfAbsent(mapping, status);
            // This can happen if another thread calls .put(..) sometime after our call to get. Return
            // the value from the putIfAbsent call as that is truly in the map.
            if (tStatus != null) {
                return tStatus;
            }
        }
        return status;
    }

    /**
     * Add the primary key column to the given table and return it.
     */
    protected Column addPrimaryKeyColumn(Table table) {
        DBDictionary dict = _conf.getDBDictionaryInstance();
        Column pkColumn = table.addColumn(dict.getValidColumnName(getPrimaryKeyColumnIdentifier(), table));
        pkColumn.setType(dict.getPreferredType(Types.TINYINT));
        pkColumn.setJavaType(JavaTypes.INT);
        return pkColumn;
    }

    /**
     * Return the primary key value for the sequence table for the given class.
     */
    protected Object getPrimaryKey(ClassMapping mapping) {
        return 0;
    }

    /**
     * Creates the object-level representation of the sequence table.
     */
    private void buildTable() {
        DBIdentifier tableName = DBIdentifier.NULL;
        DBIdentifier schemaName = DBIdentifier.NULL;
        QualifiedDBIdentifier path = QualifiedDBIdentifier.getPath(_table);
        if (!DBIdentifier.isEmpty(path.getSchemaName())) {
            schemaName = path.getSchemaName();
            tableName = path.getUnqualifiedName();
        }
        else {
            tableName = _table;
        }

        if (DBIdentifier.isEmpty(schemaName)) {
            schemaName = Schemas.getNewTableSchemaIdentifier(_conf);
        }

        SchemaGroup group = new SchemaGroup();
        Schema schema = group.addSchema(schemaName);

        Table table = schema.addTable(tableName);
        _pkColumn = addPrimaryKeyColumn(table);
        PrimaryKey pk = table.addPrimaryKey();
        pk.addColumn(_pkColumn);

        DBDictionary dict = _conf.getDBDictionaryInstance();
        DBIdentifier _delimitedSeqColumnName = dict.delimitAll() ?
                DBIdentifier.newColumn(this._seqColumnName.getName(), true) : this._seqColumnName;
        _seqColumn = table.addColumn(dict.getValidColumnName
            (_delimitedSeqColumnName, table));
        _seqColumn.setType(dict.getPreferredType(Types.BIGINT));
        _seqColumn.setJavaType(JavaTypes.LONG);

        if (_uniqueColumnNames != null) {
            DBIdentifier uniqueName = _uniqueConstraintName;
            if (DBIdentifier.isEmpty(uniqueName)) {
                uniqueName = dict.getValidUniqueName(DBIdentifier.newConstraint("UNQ"), table);
            }
    		Unique u = table.addUnique(uniqueName);
    		for (DBIdentifier columnName : _uniqueColumnNames) {
    			if (!table.containsColumn(columnName, _conf.getDBDictionaryInstance()))
                    throw new UserException(_loc.get("unique-missing-column",
                            columnName, table.getIdentifier(),
                            table.getColumnNames()));
    			Column col = table.getColumn(columnName);
    			u.addColumn(col);
    		}
        }

    }

    /**
     * Updates the max available sequence value.
     */
    private void allocateSequence(JDBCStore store, ClassMapping mapping,
            Status stat, int alloc, boolean updateStatSeq) throws SQLException {
        Runnable runnable =
            new AllocateSequenceRunnable(
                    store, mapping, stat, alloc, updateStatSeq);
        try {
            if (suspendInJTA()) {
                // NotSupportedException is wrapped in a StoreException by
                // the caller.
                try {
                _conf.getManagedRuntimeInstance().doNonTransactionalWork(
                        runnable);
                }
                catch(NotSupportedException nse) {
                    SQLException sqlEx = new SQLException(
                            nse.getLocalizedMessage(), nse);
                    throw sqlEx;
                }
            } else {
                runnable.run();
            }
        } catch (RuntimeException re) {
            Throwable e = re.getCause();
            if(e instanceof SQLException )
                throw (SQLException) e;
            else
                throw re;
        }
    }

    /**
     * Inserts the initial sequence column into the database.
     *
     * @param mapping
     *            ClassMapping for the class whose sequence column will be
     *            updated
     * @param conn
     *            Connection used issue SQL statements.
     */
    private void insertSequence(ClassMapping mapping, Connection conn)
        throws SQLException {

        if (_log.isTraceEnabled())
            _log.trace(_loc.get("insert-seq"));

        Object pk = getPrimaryKey(mapping);
        if (pk == null)
            throw new InvalidStateException(_loc.get("bad-seq-type",
                getClass(), mapping));

        DBDictionary dict = _conf.getDBDictionaryInstance();
        DBIdentifier tableName = resolveTableIdentifier(mapping, _pkColumn.getTable());
        SQLBuffer insert = new SQLBuffer(dict).append("INSERT INTO ").
            append(tableName).append(" (").
            append(_pkColumn).append(", ").append(_seqColumn).
            append(") VALUES (").
            appendValue(pk, _pkColumn).append(", ").
            appendValue(_intValue, _seqColumn).append(")");

        boolean wasAuto = conn.getAutoCommit();
        if (!wasAuto && !suspendInJTA())
            conn.setAutoCommit(true);

        PreparedStatement stmnt = null;
        try {
            stmnt = prepareStatement(conn, insert);
            dict.setTimeouts(stmnt, _conf, true);
            executeUpdate(_conf, conn, stmnt, insert, Row.ACTION_INSERT);
        } finally {
            if (stmnt != null)
                try { stmnt.close(); } catch (SQLException se) {}
            if (!wasAuto && !suspendInJTA())
                conn.setAutoCommit(false);
        }
    }

    /**
     * Get the current sequence value.
     *
     * @param mapping
     *            ClassMapping of the entity whose sequence value will be
     *            obtained.
     * @param conn
     *            Connection used issue SQL statements.
     *
     * @return The current sequence value, or SEQUENCE_NOT_FOUND
     *         if the sequence could not be found.
     */
    protected long getSequence(ClassMapping mapping, Connection conn)
        throws SQLException {
        if (_log.isTraceEnabled())
            _log.trace(_loc.get("get-seq"));

        Object pk = getPrimaryKey(mapping);
        if (pk == null)
            return -1;

        DBDictionary dict = _conf.getDBDictionaryInstance();
        SQLBuffer sel = new SQLBuffer(dict).append(_seqColumn);
        SQLBuffer where = new SQLBuffer(dict).append(_pkColumn).append(" = ").
            appendValue(pk, _pkColumn);
        DBIdentifier tableName = resolveTableIdentifier(mapping, _seqColumn.getTable());
        SQLBuffer tables = new SQLBuffer(dict).append(tableName);

        SQLBuffer select = dict.toSelect(sel, null, tables, where, null, null,
                null, false, dict.supportsSelectForUpdate, 0, Long.MAX_VALUE,
                false, true);

        PreparedStatement stmnt = null;
        ResultSet rs = null;
        try {
            stmnt = prepareStatement(conn, select);
            dict.setTimeouts(stmnt, _conf, false);
            rs = executeQuery(_conf, conn, stmnt, select);
            return getSequence(rs, dict);
        } finally {
            if (rs != null)
                try { rs.close(); } catch (SQLException se) {}
            if (stmnt != null)
                try { stmnt.close(); } catch (SQLException se) {}
        }
    }

    /**
     * Grabs the next handful of sequence numbers.
     *
     * @return true if the sequence was updated, false if no sequence
     * row existed for this mapping
     */
    protected boolean setSequence(ClassMapping mapping, Status stat, int inc,
        boolean updateStatSeq, Connection conn)
        throws SQLException {
        if (_log.isTraceEnabled())
            _log.trace(_loc.get("update-seq"));

        Object pk = getPrimaryKey(mapping);
        if (pk == null)
            throw new InvalidStateException(_loc.get("bad-seq-type",
                getClass(), mapping));

        DBDictionary dict = _conf.getDBDictionaryInstance();
        SQLBuffer where = new SQLBuffer(dict).append(_pkColumn).append(" = ").
            appendValue(pk, _pkColumn);

        // loop until we have a successful atomic select/update sequence
        long cur = 0;
        PreparedStatement stmnt;
        ResultSet rs;
        SQLBuffer upd;
        for (int updates = 0; updates == 0;) {
            stmnt = null;
            rs = null;
            try {
                cur = getSequence(mapping, conn);
                if (cur == -1)
                    return false;

                // update the value
                upd = new SQLBuffer(dict);
                DBIdentifier tableName = resolveTableIdentifier(mapping,
                        _seqColumn.getTable());
                upd.append("UPDATE ").append(tableName).
                    append(" SET ").append(_seqColumn).append(" = ").
                    appendValue(cur + inc, _seqColumn).
                    append(" WHERE ").append(where).append(" AND ").
                    append(_seqColumn).append(" = ").
                    appendValue(cur, _seqColumn);

                stmnt = prepareStatement(conn, upd);
                dict.setTimeouts(stmnt, _conf, true);
                updates = executeUpdate(_conf, conn, stmnt, upd,
                        Row.ACTION_UPDATE);
            } finally {
                if (rs != null)
                    try { rs.close(); } catch (SQLException se) {}
                if (stmnt != null)
                    try { stmnt.close(); } catch (SQLException se) {}
            }
        }

        // setup new sequence range
        synchronized (stat) {
            if (updateStatSeq && stat.seq < cur)
                stat.seq = cur;
            if (stat.max < cur + inc)
                stat.max = cur + inc;
        }
        return true;
    }
    /**
     * Resolve a fully qualified table name
     *
     * @param class
     *            mapping to get the schema name
     * @deprecated
     */
    @Deprecated
    public String resolveTableName(ClassMapping mapping, Table table) {
        return resolveTableIdentifier(mapping, table).getName();
    }

    /**
     * Resolve a fully qualified table name
     *
     * @param class
     *            mapping to get the schema name
     */
    public DBIdentifier resolveTableIdentifier(ClassMapping mapping, Table table) {
        DBIdentifier sName = mapping.getTable().getSchemaIdentifier();
        DBIdentifier tableName = DBIdentifier.NULL;

        //OPENJPA-2650: Don't use a schema name if the user has requested,
        //via useSchemaName, to not use one.
        if (!_conf.getDBDictionaryInstance().useSchemaName){
            tableName = table.getIdentifier();
        } else if (DBIdentifier.isNull(sName)) {
            tableName = table.getFullIdentifier();
        } else if (!DBIdentifier.isNull(table.getSchemaIdentifier())) {
            tableName = table.getFullIdentifier();
        } else {
            tableName = QualifiedDBIdentifier.newPath(sName, table.getIdentifier());
        }
        return tableName;
    }


    /**
     * Creates the sequence table in the DB.
     */
    public void refreshTable()
        throws SQLException {
        if (_log.isInfoEnabled())
            _log.info(_loc.get("make-seq-table"));

        // create the table
        SchemaTool tool = new SchemaTool(_conf);
        tool.setIgnoreErrors(true);
        tool.createTable(_pkColumn.getTable());
    }

    /**
     * Drops the sequence table in the DB.
     */
    public void dropTable()
        throws SQLException {
        if (_log.isInfoEnabled())
            _log.info(_loc.get("drop-seq-table"));

        // drop the table
        SchemaTool tool = new SchemaTool(_conf);
        tool.setIgnoreErrors(true);
        tool.dropTable(_pkColumn.getTable());
    }

    /////////
    // Main
    /////////

    /**
     * Usage: java org.apache.openjpa.jdbc.schema.TableJDBCSequence [option]*
     * -action/-a <add | drop | get | set> [value]
     *  Where the following options are recognized.
     * 
    *
  • -properties/-p <properties file or resource>: The * path or resource name of a OpenJPA properties file containing * information such as the license key and connection data as * outlined in {@link JDBCConfiguration}. Optional.
  • *
  • -<property name> <property value>: All bean * properties of the OpenJPA {@link JDBCConfiguration} can be set by * using their names and supplying a value. For example: * -licenseKey adslfja83r3lkadf
  • *
* The various actions are as follows. *
    *
  • add: Create the sequence table.
  • *
  • drop: Drop the sequence table.
  • *
  • get: Print the current sequence value.
  • *
  • set: Set the sequence value.
  • *
*/ public static void main(String[] args) throws Exception { Options opts = new Options(); final String[] arguments = opts.setFromCmdLine(args); boolean ret = Configurations.runAgainstAllAnchors(opts, new Configurations.Runnable() { @Override public boolean run(Options opts) throws Exception { JDBCConfiguration conf = new JDBCConfigurationImpl(); try { return TableJDBCSeq.run(conf, arguments, opts); } finally { conf.close(); } } }); if (!ret) { // START - ALLOW PRINT STATEMENTS System.out.println(_loc.get("seq-usage")); // STOP - ALLOW PRINT STATEMENTS } } /** * Run the tool. Returns false if invalid options were given. */ public static boolean run(JDBCConfiguration conf, String[] args, Options opts) throws Exception { String action = opts.removeProperty("action", "a", null); Configurations.populateConfiguration(conf, opts); return run(conf, args, action); } /** * Run the tool. Return false if an invalid option was given. */ public static boolean run(JDBCConfiguration conf, String[] args, String action) throws Exception { if (args.length > 1 || (args.length != 0 && !ACTION_SET.equals(action))) return false; TableJDBCSeq seq = new TableJDBCSeq(); String props = Configurations.getProperties(conf.getSequence()); Configurations.configureInstance(seq, conf, props); if (ACTION_DROP.equals(action)) seq.dropTable(); else if (ACTION_ADD.equals(action)) seq.refreshTable(); else if (ACTION_GET.equals(action) || ACTION_SET.equals(action)) { Connection conn = conf.getDataSource2(null).getConnection(); try { long cur = seq.getSequence(null, conn); if (ACTION_GET.equals(action)) { // START - ALLOW PRINT STATEMENTS System.out.println(cur); // STOP - ALLOW PRINT STATEMENTS } else { long set; if (args.length > 0) set = Long.parseLong(args[0]); else set = cur + seq.getAllocate(); if (set < cur) set = cur; else { Status stat = seq.getStatus(null); seq.setSequence(null, stat, (int) (set - cur), true, conn); set = stat.seq; } // START - ALLOW PRINT STATEMENTS System.err.println(set); // STOP - ALLOW PRINT STATEMENTS } } catch (NumberFormatException nfe) { return false; } finally { try { conn.close(); } catch (SQLException se) {} } } else return false; return true; } /** * Helper struct to hold status information. */ protected static class Status implements Serializable { private static final long serialVersionUID = 1L; public long seq = 1L; public long max = 0L; } /** * This method is to provide override for non-JDBC or JDBC-like * implementation of preparing statement. */ protected PreparedStatement prepareStatement(Connection conn, SQLBuffer buf) throws SQLException { return buf.prepareStatement(conn); } /** * This method is to provide override for non-JDBC or JDBC-like * implementation of executing update. */ protected int executeUpdate(JDBCConfiguration conf, Connection conn, PreparedStatement stmnt, SQLBuffer buf, int opcode) throws SQLException { return stmnt.executeUpdate(); } /** * This method is to provide override for non-JDBC or JDBC-like * implementation of executing query. */ protected ResultSet executeQuery(JDBCConfiguration conf, Connection conn, PreparedStatement stmnt, SQLBuffer buf) throws SQLException { return stmnt.executeQuery(); } /** * This method is to provide override for non-JDBC or JDBC-like * implementation of getting sequence from the result set. */ protected long getSequence(ResultSet rs, DBDictionary dict) throws SQLException { if (rs == null || !rs.next()) return -1; return dict.getLong(rs, 1); } public void setUniqueConstraintName(String uniqueConstraintName) { _uniqueConstraintName = DBIdentifier.newConstraint(uniqueConstraintName); } public void setUniqueConstraintName(DBIdentifier uniqueConstraintName) { _uniqueConstraintName = uniqueConstraintName; } public String getUniqueConstraintName() { return _uniqueConstraintName.getName(); } public DBIdentifier getUniqueConstraintIdentifier() { return _uniqueConstraintName; } /** * AllocateSequenceRunnable is a runnable wrapper that will inserts the * initial sequence value into the database. */ protected class AllocateSequenceRunnable implements Runnable { JDBCStore store = null; ClassMapping mapping = null; Status stat = null; int alloc; boolean updateStatSeq; AllocateSequenceRunnable(JDBCStore store, ClassMapping mapping, Status stat, int alloc, boolean updateStatSeq) { this.store = store; this.mapping = mapping; this.stat = stat; this.alloc = alloc; this.updateStatSeq = updateStatSeq; } /** * This method actually obtains the current sequence value. * * @throws RuntimeException * any SQLExceptions that occur when obtaining the sequence * value are wrapped in a runtime exception to avoid * breaking the Runnable method signature. The caller can * obtain the "real" exception by calling getCause(). */ @Override public void run() throws RuntimeException { Connection conn = null; SQLException err = null; try { // Try to use the store's connection. conn = getConnection(store); boolean sequenceSet = setSequence(mapping, stat, alloc, updateStatSeq, conn); closeConnection(conn); if (!sequenceSet) { // insert a new sequence column. Prefer connection2 / non-jta-data-source when inserting a // sequence column regardless of Seq.type. conn = _conf.getDataSource2(store.getContext()).getConnection(); try { insertSequence(mapping, conn); } catch (SQLException e) { // it is possible another thread already got in and inserted this sequence. Try to keep going if (_log.isTraceEnabled()) { _log.trace( "Caught an exception while trying to insert sequence. Will try to reselect the " + "seqence. ", e); } } conn.close(); // now we should be able to update using the connection per // on the seq type. conn = getConnection(store); if (!setSequence(mapping, stat, alloc, updateStatSeq, conn)) { throw (err != null) ? err : new SQLException(_loc.get( "no-seq-row", mapping, _table).getMessage()); } closeConnection(conn); } } catch (SQLException e) { if (conn != null) { closeConnection(conn); } RuntimeException re = new RuntimeException(e.getMessage(), e); throw re; } } } /** * CurentSequenceRunnable is a runnable wrapper which obtains the current * sequence value from the database. */ protected class CurrentSequenceRunnable implements Runnable { private JDBCStore _store; private ClassMapping _mapping; CurrentSequenceRunnable(JDBCStore store, ClassMapping mapping) { _store = store; _mapping = mapping; } /** * This method actually obtains the current sequence value. * * @throws RuntimeException * any SQLExceptions that occur when obtaining the sequence * value are wrapped in a runtime exception to avoid * breaking the Runnable method signature. The caller can * obtain the "real" exception by calling getCause(). */ @Override public void run() throws RuntimeException { Connection conn = null; try { conn = getConnection(_store); long cur = getSequence(_mapping, conn); if (cur != -1 ) // USE the constant current = cur; } catch (SQLException sqle) { RuntimeException re = new RuntimeException(sqle.getMessage(), sqle); throw re; } finally { if (conn != null) { closeConnection(conn); } } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy