
com.googlecode.fascinator.sequences.SequenceService Maven / Gradle / Ivy
The newest version!
/*
* The Fascinator - Sequence Service
* Copyright (C) 2008-2010 University of Southern Queensland
* Copyright (C) 2012 Queensland Cyber Infrastructure Foundation (http://www.qcif.edu.au/)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package com.googlecode.fascinator.sequences;
import java.io.File;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import javax.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import com.googlecode.fascinator.api.access.AccessControlException;
import com.googlecode.fascinator.common.JsonSimple;
import com.googlecode.fascinator.common.JsonSimpleConfig;
@Component(value = "sequenceService")
public class SequenceService {
/** Logging */
private final Logger log = LoggerFactory
.getLogger(SequenceService.class);
/** JDBC Driver */
private static String DERBY_DRIVER = "org.apache.derby.jdbc.EmbeddedDriver";
/** Connection string prefix */
private static String DERBY_PROTOCOL = "jdbc:derby:";
/** Sequence database name */
private static String SEQUENCE_DATABASE = "sequence";
/** Records table */
private static String SEQUENCE_TABLE = "sequences";
/** Database home directory */
private String derbyHome;
/** Database connection */
private Connection connection;
/**
* Initialization of Sequences Service
*
* @throws IOException
* @throws SQLException
*
*/
@PostConstruct
public void init() throws IOException, SQLException {
JsonSimpleConfig config = new JsonSimpleConfig();
init(config);
}
public void init(JsonSimple config) throws IOException, SQLException {
// Find data directory
derbyHome = config.getString(null, "database-service", "derbyHome");
String oldHome = System.getProperty("derby.system.home");
// Derby's data directory has already been configured
if (oldHome != null) {
if (derbyHome != null) {
// Use the existing one, but throw a warning
log.warn("Using previously specified data directory:"
+ " '{}', provided value has been ignored: '{}'",
oldHome, derbyHome);
} else {
// This is ok, no configuration conflicts
log.info("Using existing data directory: '{}'", oldHome);
}
// We don't have one, config MUST have one
} else {
if (derbyHome == null) {
log.error("No database home directory configured!");
return;
} else {
// Establish its validity and existance, create if necessary
File file = new File(derbyHome);
if (file.exists()) {
if (!file.isDirectory()) {
throw new IOException("Database home '"
+ derbyHome + "' is not a directory!");
}
} else {
file.mkdirs();
if (!file.exists()) {
throw new IOException("Database home '"
+ derbyHome
+ "' does not exist and could not be created!");
}
}
System.setProperty("derby.system.home", derbyHome);
}
}
// Database prep work
try {
checkTable(SEQUENCE_TABLE);
} catch (SQLException ex) {
log.error("Error during database preparation:", ex);
throw new SQLException(
"Error during database preparation:", ex);
}
log.debug("Derby security database online!");
}
private Connection connection() throws SQLException {
if (connection == null || !connection.isValid(1)) {
// At least try to close if not null... even though its not valid
if (connection != null) {
log.error("!!! Database connection has failed, recreating.");
try {
connection.close();
} catch (SQLException ex) {
log.error("Error closing invalid connection, ignoring: {}",
ex.getMessage());
}
}
// Open a new connection
Properties props = new Properties();
// Load the JDBC driver
try {
Class.forName(DERBY_DRIVER).newInstance();
} catch (Exception ex) {
log.error("Driver load failed: ", ex);
throw new SQLException("Driver load failed: ", ex);
}
// Establish a database connection
connection = DriverManager.getConnection(DERBY_PROTOCOL
+ SEQUENCE_DATABASE + ";create=true", props);
}
return connection;
}
/**
* Shuts down the plugin
*
* @throws AccessControlException
* if there was an error during shutdown
*/
public void shutdown() throws SQLException {
// Derby can only be shutdown from one thread,
// we'll catch errors from the rest.
String threadedShutdownMessage = DERBY_DRIVER
+ " is not registered with the JDBC driver manager";
try {
// Tell the database to close
DriverManager.getConnection(DERBY_PROTOCOL + ";shutdown=true");
// Shutdown just this database (but not the engine)
// DriverManager.getConnection(DERBY_PROTOCOL + SECURITY_DATABASE +
// ";shutdown=true");
} catch (SQLException ex) {
// These test values are used if the engine is NOT shutdown
// if (ex.getErrorCode() == 45000 &&
// ex.getSQLState().equals("08006")) {
// Valid response
if (ex.getErrorCode() == 50000 && ex.getSQLState().equals("XJ015")) {
// Error response
} else {
// Make sure we ignore simple thread issues
if (!ex.getMessage().equals(threadedShutdownMessage)) {
log.error("Error during database shutdown:", ex);
throw new SQLException(
"Error during database shutdown:", ex);
}
}
} finally {
try {
// Close our connection
if (connection != null) {
connection.close();
connection = null;
}
} catch (SQLException ex) {
log.error("Error closing connection:", ex);
}
}
}
/**
* Check for the existence of a table and arrange for its creation if not
* found.
*
* @param table
* The table to look for and create.
* @throws SQLException
* if there was an error.
*/
private void checkTable(String table) throws SQLException {
boolean tableFound = findTable(table);
// Create the table if we couldn't find it
if (!tableFound) {
log.debug("Table '{}' not found, creating now!", table);
createTable(table);
// Double check it was created
if (!findTable(table)) {
log.error("Unknown error creating table '{}'", table);
throw new SQLException("Could not find or create table '"
+ table + "'");
}
}
}
/**
* Check if the given table exists in the database.
*
* @param table
* The table to look for
* @return boolean flag if the table was found or not
* @throws SQLException
* if there was an error accessing the database
*/
private boolean findTable(String table) throws SQLException {
boolean tableFound = false;
DatabaseMetaData meta = connection().getMetaData();
ResultSet result = (ResultSet) meta.getTables(null, null, null, null);
while (result.next() && !tableFound) {
if (result.getString("TABLE_NAME").equalsIgnoreCase(table)) {
tableFound = true;
}
}
close(result);
return tableFound;
}
/**
* Create the given table in the database.
*
* @param table
* The table to create
* @throws SQLException
* if there was an error during creation, or an unknown table
* was specified.
*/
private void createTable(String table) throws SQLException {
if (table.equals(SEQUENCE_TABLE)) {
Statement sql = connection().createStatement();
sql.execute("CREATE TABLE " + SEQUENCE_TABLE
+ "(sequence_name VARCHAR(255) NOT NULL, "
+ "counter INTEGER NOT NULL,"
+ "PRIMARY KEY (sequence_name))");
close(sql);
return;
}
throw new SQLException("Unknown table '" + table + "' requested!");
}
/**
* Attempt to close a ResultSet. Basic wrapper for exception catching and
* logging
*
* @param resultSet
* The ResultSet to try and close.
*/
private void close(ResultSet resultSet) {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException ex) {
log.error("Error closing result set: ", ex);
}
}
resultSet = null;
}
/**
* Attempt to close a Statement. Basic wrapper for exception catching and
* logging
*
* @param statement
* The Statement to try and close.
*/
private void close(Statement statement) {
if (statement != null) {
try {
statement.close();
} catch (SQLException ex) {
log.error("Error closing statement: ", ex);
}
}
statement = null;
}
public synchronized Integer getSequence(String sequenceName) throws SQLException {
Integer sequenceCount = null;
PreparedStatement sql = connection().prepareStatement(
"SELECT * FROM " + SEQUENCE_TABLE + " WHERE sequence_name = ?");
// Prepare and execute
sql.setString(1, sequenceName);
ResultSet result = sql.executeQuery();
// Build response
while (result.next()) {
sequenceCount = result.getInt("counter");
}
close(result);
close(sql);
if(sequenceCount == null) {
sequenceCount = 1;
createNewSequence(sequenceName);
}
incrementSequence(sequenceName, sequenceCount+1);
return sequenceCount;
}
private void incrementSequence(String sequenceName, Integer sequenceCount) throws SQLException {
PreparedStatement sql = connection().prepareStatement(
"UPDATE " + SEQUENCE_TABLE + " SET counter = ? WHERE sequence_name = ?");
// Prepare and execute
sql.setInt(1, sequenceCount);
sql.setString(2, sequenceName);
sql.executeUpdate();
close(sql);
}
private void createNewSequence(String sequenceName) throws SQLException {
PreparedStatement sql = connection().prepareStatement(
"INSERT INTO " + SEQUENCE_TABLE + " VALUES (?, ?)");
// Prepare and execute
sql.setString(1, sequenceName);
sql.setInt(2, 1);
sql.executeUpdate();
close(sql);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy