| /* |
| * 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.sql.Connection; |
| import java.sql.PreparedStatement; |
| import java.sql.ResultSet; |
| import java.sql.SQLException; |
| import java.text.MessageFormat; |
| |
| 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.QualifiedDBIdentifier; |
| import org.apache.openjpa.jdbc.meta.ClassMapping; |
| 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.Sequence; |
| import org.apache.openjpa.jdbc.sql.DBDictionary; |
| 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.log.Log; |
| import org.apache.openjpa.lib.util.Localizer; |
| import org.apache.openjpa.lib.util.Options; |
| import org.apache.openjpa.util.MetaDataException; |
| 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 sequence |
| * to generate numbers. |
| * Supports allocation (caching). In order for allocation to work properly, the database sequence must be defined |
| * with INCREMENT BY value equal to allocate * increment. |
| * |
| * @see JDBCSeq |
| * @see AbstractJDBCSeq |
| */ |
| public class NativeJDBCSeq |
| 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"; |
| |
| private static Localizer _loc = Localizer.forPackage(NativeJDBCSeq.class); |
| |
| private JDBCConfiguration _conf = null; |
| private DBIdentifier _seqName = DBIdentifier.newSequence("OPENJPA_SEQUENCE"); |
| private int _increment = 1; |
| private int _initial = 1; |
| private int _allocate = 50; |
| private Sequence _seq = null; |
| private String _select = null; |
| private long _nextValue = 0; |
| private long _maxValue = -1; |
| |
| private DBIdentifier _schema = DBIdentifier.NULL; |
| |
| private boolean alterIncrementBy = false; |
| private boolean alreadyLoggedAlterSeqFailure = false; |
| private boolean alreadyLoggedAlterSeqDisabled = false; |
| |
| /** |
| * The sequence name. Defaults to <code>OPENJPA_SEQUENCE</code>. |
| */ |
| // @GETTER |
| public String getSequence() { |
| return _seqName.getName(); |
| } |
| |
| /** |
| * The sequence name. Defaults to <code>OPENJPA_SEQUENCE</code>. |
| */ |
| public void setSequence(String seqName) { |
| _seqName = DBIdentifier.newSequence(seqName); |
| } |
| |
| /** |
| * @see Sequence#getInitialValue |
| */ |
| public int getInitialValue() { |
| return _initial; |
| } |
| |
| /** |
| * @see Sequence#setInitialValue |
| */ |
| public void setInitialValue(int initial) { |
| _initial = initial; |
| } |
| |
| /** |
| * @see Sequence#getAllocate |
| */ |
| public int getAllocate() { |
| return _allocate; |
| } |
| |
| /** |
| * @see Sequence#setAllocate |
| */ |
| public void setAllocate(int allocate) { |
| _allocate = allocate; |
| } |
| |
| /** |
| * @see Sequence#getIncrement |
| */ |
| public int getIncrement() { |
| return _increment; |
| } |
| |
| /** |
| * @see Sequence#setIncrement |
| */ |
| public void setIncrement(int increment) { |
| _increment = increment; |
| } |
| |
| @Override |
| public void addSchema(ClassMapping mapping, SchemaGroup group) { |
| // sequence already exists? |
| QualifiedDBIdentifier path = QualifiedDBIdentifier.getPath(_seqName); |
| if (group.isKnownSequence(path)) |
| return; |
| |
| DBIdentifier schemaName = getSchemaIdentifier(); |
| if (DBIdentifier.isEmpty(schemaName)) { |
| schemaName = path.getSchemaName(); |
| if (DBIdentifier.isEmpty(schemaName)) |
| schemaName = Schemas.getNewTableSchemaIdentifier(_conf); |
| } |
| |
| // create table in this group |
| Schema schema = group.getSchema(schemaName); |
| if (schema == null) |
| schema = group.addSchema(schemaName); |
| schema.importSequence(_seq); |
| } |
| |
| @Override |
| public JDBCConfiguration getConfiguration() { |
| return _conf; |
| } |
| |
| @Override |
| public void setConfiguration(Configuration conf) { |
| _conf = (JDBCConfiguration) conf; |
| } |
| |
| @Override |
| public void startConfiguration() { |
| } |
| |
| @Override |
| public void endConfiguration() { |
| buildSequence(); |
| |
| DBDictionary dict = _conf.getDBDictionaryInstance(); |
| String format = dict.nextSequenceQuery; |
| if (format == null) { |
| throw new MetaDataException(_loc.get("no-seq-sql", _seqName)); |
| } |
| |
| String name = dict.getFullName(_seq); |
| // Increment step is needed for Firebird which uses non-standard sequence fetch syntax. |
| // Use String.valueOf to get rid of possible locale-specific number formatting. |
| _select = MessageFormat.format(format, new Object[]{name, String.valueOf(_allocate * _increment)}); |
| |
| type = dict.nativeSequenceType; |
| } |
| |
| @Override |
| protected synchronized Object nextInternal(JDBCStore store, ClassMapping mapping) |
| throws SQLException { |
| if (!alterIncrementBy) { |
| allocateInternal(0, store, mapping); |
| alterIncrementBy = true; |
| } |
| if (_nextValue >= _maxValue) { |
| allocateInternal(0, store, mapping); |
| } |
| long result = _nextValue; |
| _nextValue += _increment; |
| return result; |
| } |
| |
| /** |
| * Allocate additional sequence values. |
| * @param additional ignored - the allocation size is fixed and determined by allocate and increment properties. |
| * @param store used to obtain connection |
| * @param mapping ignored |
| */ |
| @Override |
| protected synchronized void allocateInternal(int additional, JDBCStore store, ClassMapping mapping) |
| throws SQLException { |
| Connection conn = getConnection(store); |
| try { |
| if (!alterIncrementBy) { |
| DBDictionary dict = _conf.getDBDictionaryInstance(); |
| if (!dict.disableAlterSeqenceIncrementBy) { |
| // If this fails, we will warn the user at most one time and set _allocated and _increment to 1 so |
| // as to not potentially insert records ahead of what the database thinks is the next sequence |
| // value. |
| if (updateSql(conn, dict.getAlterSequenceSQL(_seq)) == -1) { |
| if (!alreadyLoggedAlterSeqFailure) { |
| Log log = _conf.getLog(OpenJPAConfiguration.LOG_RUNTIME); |
| if (log.isWarnEnabled()) { |
| log.warn(_loc.get("fallback-no-seq-cache", _seqName)); |
| } |
| } |
| alreadyLoggedAlterSeqFailure = true; |
| _allocate = 1; |
| } |
| } else { |
| if (!alreadyLoggedAlterSeqDisabled) { |
| Log log = _conf.getLog(OpenJPAConfiguration.LOG_RUNTIME); |
| if (log.isWarnEnabled()) { |
| log.warn(_loc.get("alter-seq-disabled", _seqName)); |
| } |
| } |
| |
| alreadyLoggedAlterSeqDisabled = true; |
| } |
| } |
| _nextValue = getSequence(conn); |
| _maxValue = _nextValue + _allocate * _increment; |
| } finally { |
| closeConnection(conn); |
| } |
| } |
| |
| /** |
| * Creates the sequence object. |
| */ |
| private void buildSequence() { |
| QualifiedDBIdentifier path = QualifiedDBIdentifier.getPath(_seqName); |
| DBIdentifier seqName = path.getIdentifier(); |
| // JPA 2 added schema as a configurable attribute on |
| // sequence generator. OpenJPA <= 1.x allowed this via |
| // schema.sequence on the sequence name. Specifying a schema |
| // name on the annotation or in the orm will override the old |
| // behavior. |
| DBIdentifier schemaName = _schema; |
| if (DBIdentifier.isEmpty(schemaName)) { |
| schemaName = path.getSchemaName(); |
| if (DBIdentifier.isEmpty(schemaName)) |
| schemaName = Schemas.getNewTableSchemaIdentifier(_conf); |
| } |
| |
| // build the sequence in one of the designated schemas |
| SchemaGroup group = new SchemaGroup(); |
| Schema schema = group.addSchema(schemaName); |
| |
| _seq = schema.addSequence(seqName); |
| _seq.setInitialValue(_initial); |
| _seq.setIncrement(_increment); |
| _seq.setAllocate(_allocate); |
| } |
| |
| /** |
| * Creates the sequence in the DB. |
| */ |
| public void refreshSequence() |
| throws SQLException { |
| Log log = _conf.getLog(OpenJPAConfiguration.LOG_RUNTIME); |
| if (log.isInfoEnabled()) |
| log.info(_loc.get("make-native-seq")); |
| |
| // create the sequence |
| SchemaTool tool = new SchemaTool(_conf); |
| tool.setIgnoreErrors(true); |
| tool.createSequence(_seq); |
| } |
| |
| /** |
| * Drops the sequence in the DB. |
| */ |
| public void dropSequence() |
| throws SQLException { |
| Log log = _conf.getLog(OpenJPAConfiguration.LOG_RUNTIME); |
| if (log.isInfoEnabled()) |
| log.info(_loc.get("drop-native-seq")); |
| |
| // drop the table |
| SchemaTool tool = new SchemaTool(_conf); |
| tool.setIgnoreErrors(true); |
| tool.dropSequence(_seq); |
| } |
| |
| /** |
| * Return the next sequence value. |
| */ |
| private long getSequence(Connection conn) |
| throws SQLException { |
| DBDictionary dict = _conf.getDBDictionaryInstance(); |
| PreparedStatement stmnt = null; |
| ResultSet rs = null; |
| try { |
| stmnt = conn.prepareStatement(_select); |
| dict.setTimeouts(stmnt, _conf, false); |
| rs = stmnt.executeQuery(); |
| if (rs.next()) |
| return rs.getLong(1); |
| |
| // no row !? |
| throw new UserException(_loc.get("invalid-seq-sql", _select)); |
| } finally { |
| // clean up our resources |
| if (rs != null) |
| try { rs.close(); } catch (SQLException se) {} |
| if (stmnt != null) |
| try { stmnt.close(); } catch (SQLException se) {} |
| } |
| } |
| |
| private int updateSql(Connection conn, String sql) throws SQLException { |
| DBDictionary dict = _conf.getDBDictionaryInstance(); |
| PreparedStatement stmnt = null; |
| int rc = -1; |
| try { |
| stmnt = conn.prepareStatement(sql); |
| dict.setTimeouts(stmnt, _conf, false); |
| rc = stmnt.executeUpdate(); |
| } catch (Exception e) { |
| // tolerate exception when attempting to alter increment, |
| // however, caller should check rc and not cache sequence values if rc != -1. |
| } finally { |
| // clean up our resources |
| if (stmnt != null) { |
| try { |
| stmnt.close(); |
| } catch (SQLException se) { |
| } |
| } |
| } |
| return rc; |
| } |
| |
| ///////// |
| // Main |
| ///////// |
| |
| /** |
| * Usage: java org.apache.openjpa.jdbc.schema.NativeJDBCSequence [option]* |
| * -action/-a <add | drop | get> |
| * Where the following options are recognized. |
| * <ul> |
| * <li><i>-properties/-p <properties file or resource></i>: The |
| * path or resource name of an OpenJPA properties file containing |
| * information such as connection data as |
| * outlined in {@link JDBCConfiguration}. Optional.</li> |
| * <li><i>-<property name> <property value></i>: All bean |
| * properties of the OpenJPA {@link JDBCConfiguration} can be set by |
| * using their names and supplying a value.</li> |
| * </ul> |
| * The various actions are as follows. |
| * <ul> |
| * <li><i>add</i>: Create the sequence.</li> |
| * <li><i>drop</i>: Drop the sequence.</li> |
| * <li><i>get</i>: Print the next sequence value.</li> |
| * </ul> |
| */ |
| 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 NativeJDBCSeq.run(conf, arguments, opts); |
| } finally { |
| conf.close(); |
| } |
| } |
| }); |
| if (!ret) { |
| // START - ALLOW PRINT STATEMENTS |
| System.out.println(_loc.get("native-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. Returns false if an invalid option was given. |
| */ |
| public static boolean run(JDBCConfiguration conf, String[] args, |
| String action) |
| throws Exception { |
| if (args.length != 0) |
| return false; |
| |
| NativeJDBCSeq seq = new NativeJDBCSeq(); |
| String props = Configurations.getProperties(conf.getSequence()); |
| Configurations.configureInstance(seq, conf, props); |
| |
| if (ACTION_DROP.equals(action)) |
| seq.dropSequence(); |
| else if (ACTION_ADD.equals(action)) |
| seq.refreshSequence(); |
| else if (ACTION_GET.equals(action)) { |
| Connection conn = conf.getDataSource2(null).getConnection(); |
| try { |
| long cur = seq.getSequence(conn); |
| // START - ALLOW PRINT STATEMENTS |
| System.out.println(cur); |
| // STOP - ALLOW PRINT STATEMENTS |
| } finally { |
| try { conn.close(); } catch (SQLException se) {} |
| } |
| } else |
| return false; |
| return true; |
| } |
| |
| /** |
| * @deprecated |
| */ |
| @Deprecated |
| public void setSchema(String schema) { |
| _schema = DBIdentifier.newSchema(schema); |
| } |
| |
| /** |
| * @deprecated |
| */ |
| @Deprecated |
| public String getSchema() { |
| return _schema.getName(); |
| } |
| |
| public DBIdentifier getSchemaIdentifier() { |
| return _schema; |
| } |
| |
| } |