/*
* #!
* Ontopia Engine
* #-
* Copyright (C) 2001 - 2013 The Ontopia Project
* #-
* Licensed 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 net.ontopia.persistence.proxy;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Map;
import net.ontopia.utils.OntopiaRuntimeException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* INTERNAL: A key generator using the HIGH/LOW key generator
* algorithm. It maintains the current counters in a counter
* table. The key generator is able to preallocate a number of
* identities which it hand out without having to go the database for
* every new identity needed. It is used by the RDBMS proxy
* implementation.
*/
public final class HighLowKeyGenerator implements KeyGeneratorIF {
// Define a logging category.
static Logger log = LoggerFactory.getLogger(HighLowKeyGenerator.class.getName());
protected ConnectionFactoryIF connfactory;
protected String table;
protected String keycol;
protected String valcol;
protected int grabsize;
protected String global_entry;
protected String database;
protected Map<String, String> properties;
protected long value;
protected long max_value;
public HighLowKeyGenerator(ConnectionFactoryIF connfactory,
String table, String keycol, String valcol,
String global_entry, int grabsize, String database, Map<String, String> properties) {
this.connfactory = connfactory;
// NOTE: should probably just use a Properties/Map object.
this.table = table;
this.keycol = keycol;
this.valcol = valcol;
this.global_entry = global_entry;
this.grabsize = grabsize;
this.database = database;
this.properties = properties;
// Initialize counters so that they get set when first accessed
value = -1;
max_value = -1;
}
public synchronized IdentityIF generateKey(Class<?> type) {
// If we've used up the reserved interval fetch a new one from the database.
if (value >= max_value)
return new LongIdentity(type, incrementInDatabase(type));
else
// Increment and return
return new LongIdentity(type, ++value);
}
/**
* INTERNAL: Sends a request to the database to retrieve the current
* counter value. The counter value row is then locked, the counter
* incremented and the new value stored.
*/
protected long incrementInDatabase(Object type) {
// Read current counter value from key generator table.
long current_value;
long new_value;
String entry;
if (global_entry != null)
entry = global_entry;
else
//! entry = type.getName();
throw new UnsupportedOperationException("Named key generators are not yet supported.");
// Get key generator row locking keyword (e.g. 'for update')
String lkw = properties.get("net.ontopia.topicmaps.impl.rdbms.HighLowKeyGenerator.SelectSuffix");
// The row should normally be should locked while updating, but
// not all databases support this.
String sql_select;
if (lkw == null && (database.equals("sqlserver"))) {
sql_select = "select " + valcol + " from " + table + " with (XLOCK) where " + keycol + " = ?";
} else {
if (lkw == null) {
if (database.equals("sapdb"))
lkw = "with lock";
else
lkw = "for update";
}
sql_select = "select " + valcol + " from " + table + " where " + keycol + " = ? " + lkw;
}
if (log.isDebugEnabled())
log.debug("KeyGenerator: retrieving: " + sql_select);
// Request new database connection
Connection conn = null;
try {
conn = connfactory.requestConnection();
PreparedStatement stm1 = conn.prepareStatement(sql_select);
try {
stm1.setString(1, entry);
ResultSet rs = stm1.executeQuery();
if (!rs.next())
throw new OntopiaRuntimeException("HIGH/LOW key generator table '" + table +
"' not initialized (no rows).");
// Get value from result set and close it.
current_value = rs.getLong(1);
rs.close();
} finally {
stm1.close();
}
// Increment current value in the database
new_value = current_value + grabsize;
String sql_update = "update " + table + " set " + valcol + " = ? where " + keycol + " = ?";
if (log.isDebugEnabled())
log.debug("KeyGenerator: incrementing: " + sql_update);
PreparedStatement stm2 = conn.prepareStatement(sql_update);
try {
stm2.setLong(1, new_value);
stm2.setString(2, entry);
stm2.executeUpdate();
} finally {
stm2.close();
}
// commit transaction
conn.commit();
} catch (SQLException e) {
try {
if (conn != null) conn.rollback();
} catch (SQLException e2) {
// Ignore, we're already in trouble.
}
throw new OntopiaRuntimeException(e);
} finally {
if (conn != null) {
try {
// Close/release connection
conn.close();
} catch (Exception e) {
throw new OntopiaRuntimeException(e);
}
}
}
// Set counters
value = current_value + 1;
max_value = new_value;
return value;
}
}