com.googlecode.fascinator.sequences.SequenceService.java Source code

Java tutorial

Introduction

Here is the source code for com.googlecode.fascinator.sequences.SequenceService.java

Source

/*
 * 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);

    }

}