Package org.fcrepo.server.utilities

Source Code of org.fcrepo.server.utilities.SQLUtilityImpl

/* The contents of this file are subject to the license and copyright terms
* detailed in the license directory at the root of the source tree (also
* available online at http://fedora-commons.org/license/).
*/

package org.fcrepo.server.utilities;

import java.io.IOException;
import java.io.InputStream;

import java.sql.Clob;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.fcrepo.server.config.DatastoreConfiguration;
import org.fcrepo.server.config.Parameter;
import org.fcrepo.server.errors.InconsistentTableSpecException;
import org.fcrepo.server.storage.ConnectionPool;
import org.fcrepo.server.utilities.rebuild.SQLRebuilder;


/**
* This package-level class contains the methods that do much of the work for
* {@link SQLUtility}, which acts as a public facade around an instance of this
* class. This two-level structure allows the unit tests to mock out the
* functionality of {@link SQLUtility}. The worker methods are declared
* "protected" so they can be overridden by a mock class.
*/
class SQLUtilityImpl
        extends SQLUtility {

    private static final Logger logger =
            LoggerFactory.getLogger(SQLUtilityImpl.class);
   
    public static final String GET_MOST_RECENT_REBUILD =
            "SELECT rebuildDate FROM fcrepoRebuildStatus ORDER BY rebuildDate DESC";
   
    public static final String GET_REBUILD_STATUS =
            "SELECT complete FROM fcrepoRebuildStatus WHERE rebuildDate=?";
       

    @Override
    protected ConnectionPool i_getConnectionPool(DatastoreConfiguration cpDC)
            throws SQLException {
        String cpUsername = cpDC.getParameter("dbUsername",Parameter.class).getValue();
        String cpPassword = cpDC.getParameter("dbPassword",Parameter.class).getValue();
        String cpURL = cpDC.getParameter("jdbcURL",Parameter.class).getValue();
        String cpDriver = cpDC.getParameter("jdbcDriverClass",Parameter.class).getValue();
        String cpDDLConverter = cpDC.getParameter("ddlConverter",Parameter.class).getValue();
        int cpMaxActive =
                Integer.parseInt(cpDC.getParameter("maxActive",Parameter.class).getValue());
        int cpMaxIdle =
                Integer.parseInt(cpDC.getParameter("maxIdle",Parameter.class).getValue());
        long cpMaxWait =
                Long.parseLong(cpDC.getParameter("maxWait",Parameter.class).getValue());
        int cpMinIdle =
                Integer.parseInt(cpDC.getParameter("minIdle",Parameter.class).getValue());
        long cpMinEvictableIdleTimeMillis =
                Long.parseLong(cpDC.getParameter("minEvictableIdleTimeMillis",Parameter.class)
                        .getValue());
        int cpNumTestsPerEvictionRun =
                Integer.parseInt(cpDC.getParameter("numTestsPerEvictionRun",Parameter.class)
                        .getValue());
        long cpTimeBetweenEvictionRunsMillis =
                Long.parseLong(cpDC
                        .getParameter("timeBetweenEvictionRunsMillis",Parameter.class)
                        .getValue());
        String cpValidationQuery = null;
        if (cpDC.getParameter("validationQuery") != null) {
            cpValidationQuery = cpDC.getParameter("validationQuery",Parameter.class).getValue();
        }
        boolean cpTestOnBorrow =
                Boolean.parseBoolean(cpDC.getParameter("testOnBorrow",Parameter.class)
                        .getValue());
        boolean cpTestOnReturn =
                Boolean.parseBoolean(cpDC.getParameter("testOnReturn",Parameter.class)
                        .getValue());
        boolean cpTestWhileIdle =
                Boolean.parseBoolean(cpDC.getParameter("testWhileIdle",Parameter.class)
                        .getValue());
        byte cpWhenExhaustedAction =
                Byte.parseByte(cpDC.getParameter("whenExhaustedAction",Parameter.class)
                        .getValue());

        DDLConverter ddlConverter = null;
        if (cpDDLConverter != null) {
            try {
                ddlConverter =
                        (DDLConverter) Class.forName(cpDDLConverter)
                                .newInstance();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
        return new ConnectionPool(cpDriver,
                                  cpURL,
                                  cpUsername,
                                  cpPassword,
                                  ddlConverter,
                                  cpMaxActive,
                                  cpMaxIdle,
                                  cpMaxWait,
                                  cpMinIdle,
                                  cpMinEvictableIdleTimeMillis,
                                  cpNumTestsPerEvictionRun,
                                  cpTimeBetweenEvictionRunsMillis,
                                  cpValidationQuery,
                                  cpTestOnBorrow,
                                  cpTestOnReturn,
                                  cpTestWhileIdle,
                                  cpWhenExhaustedAction);
    }

    /**
     * Adds or replaces a row in the given table.
     *
     * @param conn
     *        the connection to use
     * @param table
     *        the name of the table
     * @param columns
     *        the names of the columns whose values we're setting.
     * @param values
     *        associated values
     * @param uniqueColumn
     *        which column name is unique? The value of this column will be used
     *        in the where clause. It must be a column which is not numeric.
     * @param numeric
     *        for each associated column, is it numeric? if null, all columns
     *        are assumed to be strings.
     */
    @Override
    protected void i_replaceInto(Connection conn,
                                 String table,
                                 String[] columns,
                                 String[] values,
                                 String uniqueColumn,
                                 boolean[] numeric) throws SQLException {
        if (!i_updateRow(conn, table, columns, values, uniqueColumn, numeric)) {
            i_addRow(conn, table, columns, values, numeric);
        }
    }

    /**
     * Updates an existing row.
     *
     * @return false if the row did not previously exist and therefore was not
     *         updated.
     */
    @Override
    protected boolean i_updateRow(Connection conn,
                                  String table,
                                  String[] columns,
                                  String[] values,
                                  String uniqueColumn,
                                  boolean[] numeric) throws SQLException {

        // prepare update statement
        StringBuffer sql = new StringBuffer();
        sql.append("UPDATE " + table + " SET ");
        boolean needComma = false;
        for (int i = 0; i < columns.length; i++) {
            if (!columns[i].equals(uniqueColumn)) {
                if (needComma) {
                    sql.append(", ");
                } else {
                    needComma = true;
                }
                sql.append(columns[i] + " = ");
                if (values[i] == null) {
                    sql.append("NULL");
                } else {
                    sql.append("?");
                }
            }
        }
        sql.append(" WHERE " + uniqueColumn + " = ?");
        logger.debug("About to execute: " + sql.toString());
        PreparedStatement stmt = conn.prepareStatement(sql.toString());

        try {
            // populate values
            int varIndex = 0;
            for (int i = 0; i < columns.length; i++) {
                if (!columns[i].equals(uniqueColumn) && values[i] != null) {
                    varIndex++;
                    if (numeric != null && numeric[i]) {
                        setNumeric(stmt, varIndex, columns[i], values[i]);
                    } else {
                        stmt.setString(varIndex, values[i]);
                    }
                }
            }
            varIndex++;
            stmt
                    .setString(varIndex, getSelector(columns,
                                                     values,
                                                     uniqueColumn));

            // execute and return true if existing row was updated
            return stmt.executeUpdate() > 0;

        } finally {
            closeStatement(stmt);
        }
    }

    /**
     * Adds a new row.
     *
     * @throws SQLException
     *         if the row could not be added.
     */
    @Override
    protected void i_addRow(Connection conn,
                            String table,
                            String[] columns,
                            String[] values,
                            boolean[] numeric) throws SQLException {

        // prepare insert statement
        StringBuffer sql = new StringBuffer();
        sql.append("INSERT INTO " + table + " (");
        for (int i = 0; i < columns.length; i++) {
            if (i > 0) {
                sql.append(", ");
            }
            sql.append(columns[i]);
        }
        sql.append(") VALUES (");
        for (int i = 0; i < columns.length; i++) {
            if (i > 0) {
                sql.append(", ");
            }
            if (values[i] == null) {
                sql.append("NULL");
            } else {
                sql.append("?");
            }
        }
        sql.append(")");
        logger.debug("About to execute: " + sql.toString());
        PreparedStatement stmt = conn.prepareStatement(sql.toString());

        try {
            // populate values
            int varIndex = 0;
            for (int i = 0; i < values.length; i++) {
                if (values[i] != null) {
                    varIndex++;
                    if (numeric != null && numeric[i]) {
                        setNumeric(stmt, varIndex, columns[i], values[i]);
                    } else {
                        stmt.setString(varIndex, values[i]);
                    }
                }
            }

            // execute
            stmt.executeUpdate();

        } finally {
            closeStatement(stmt);
        }

    }

    @Override
    protected void i_createNonExistingTables(ConnectionPool cPool,
                                             InputStream dbSpec)
            throws IOException, InconsistentTableSpecException, SQLException {
        List<TableSpec> nonExisting = null;
        Connection conn = null;
        try {
            conn = cPool.getReadOnlyConnection();
            nonExisting =
                    i_getNonExistingTables(conn, TableSpec
                            .getTableSpecs(dbSpec));
        } finally {
            if (conn != null) {
                cPool.free(conn);
            }
        }
        if (nonExisting.size() > 0) {
            TableCreatingConnection tcConn = null;
            try {
                tcConn = cPool.getTableCreatingConnection();
                if (tcConn == null) {
                    throw new SQLException("Unable to construct CREATE TABLE "
                            + "statement(s) because there is no DDLConverter "
                            + "registered for this connection type.");
                }
                i_createTables(tcConn, nonExisting);
            } finally {
                if (tcConn != null) {
                    cPool.free(tcConn);
                }
            }
        }
    }

    @Override
    protected List<TableSpec> i_getNonExistingTables(Connection conn,
                                                     List<TableSpec> tSpecs)
            throws SQLException {

        ArrayList<TableSpec> nonExisting = new ArrayList<TableSpec>();
        DatabaseMetaData dbMeta = conn.getMetaData();
        Iterator<TableSpec> tSpecIter = tSpecs.iterator();
        ResultSet r = null;
        // Get a list of tables that don't exist, if any
        try {
            r = dbMeta.getTables(null, null, "%", null);
            HashSet<String> existingTableSet = new HashSet<String>();
            while (r.next()) {
                existingTableSet.add(r.getString("TABLE_NAME").toLowerCase());
            }
            r.close();
            r = null;
            while (tSpecIter.hasNext()) {
                TableSpec spec = tSpecIter.next();
                if (!existingTableSet.contains(spec.getName().toLowerCase())) {
                    nonExisting.add(spec);
                }
            }
        } catch (SQLException sqle) {
            throw new SQLException(sqle.getMessage());
        } finally {
            try {
                if (r != null) {
                    r.close();
                }
            } catch (SQLException sqle2) {
                throw sqle2;
            } finally {
                r = null;
            }
        }
        return nonExisting;
    }

    @Override
    protected void i_createTables(TableCreatingConnection tcConn,
                                  List<TableSpec> tSpecs) throws SQLException {
        Iterator<TableSpec> nii = tSpecs.iterator();
        while (nii.hasNext()) {
            TableSpec spec = nii.next();
            if (logger.isInfoEnabled()) {
                StringBuffer sqlCmds = new StringBuffer();
                Iterator<String> iter =
                        tcConn.getDDLConverter().getDDL(spec).iterator();
                while (iter.hasNext()) {
                    sqlCmds.append("\n");
                    sqlCmds.append(iter.next());
                    sqlCmds.append(";");
                }
                logger.info("Creating new " + "table '" + spec.getName()
                        + "' with command(s): " + sqlCmds.toString());
            }
            tcConn.createTable(spec);
        }
    }

    void closeStatement(Statement stmt) {
        if (stmt != null) {
            try {
                stmt.close();
            } catch (SQLException e) {
                logger.warn("Unable to close statement", e);
            }
        }
    }

    /**
     * Get a long string, which could be a TEXT or CLOB type. (CLOBs require
     * special handling -- this method normalizes the reading of them)
     */
    @Override
    protected String i_getLongString(ResultSet rs, int pos) throws SQLException {
        String s = rs.getString(pos);
        if (s != null) {
            // It's a String-based datatype, so just return it.
            return s;
        } else {
            // It may be a CLOB. If so, return the contents as a String.
            try {
                Clob c = rs.getClob(pos);
                return c.getSubString(1, (int) c.length());
            } catch (Throwable th) {
                th.printStackTrace();
                return null;
            }
        }
    }

    /**
     * Sets a numeric value in the prepared statement. Parsing the string is
     * attempted as an int, then a long, and if that fails, a SQLException is
     * thrown.
     */
    private void setNumeric(PreparedStatement stmt,
                            int varIndex,
                            String columnName,
                            String value) throws SQLException {
        try {
            stmt.setInt(varIndex, Integer.parseInt(value));
        } catch (NumberFormatException e) {
            try {
                stmt.setLong(varIndex, Long.parseLong(value));
            } catch (NumberFormatException e2) {
                throw new SQLException("Value specified for " + columnName
                        + ", '" + value + "' was"
                        + " specified as numeric, but is not");
            }
        }
    }

    /**
     * Gets the value in the given array whose associated column name matches
     * the given uniqueColumn name.
     *
     * @throws SQLException
     *         if the uniqueColumn doesn't exist in the given column array.
     */
    private String getSelector(String[] columns,
                               String[] values,
                               String uniqueColumn) throws SQLException {
        String selector = null;
        for (int i = 0; i < columns.length; i++) {
            if (columns[i].equals(uniqueColumn)) {
                selector = values[i];
            }
        }
        if (selector != null) {
            return selector;
        } else {
            throw new SQLException("Unique column does not exist in given "
                    + "column array");
        }
    }

    @Override
    protected long i_getMostRecentRebuild(Connection conn) throws SQLException {
        PreparedStatement stmt = conn.prepareStatement(GET_MOST_RECENT_REBUILD);
        ResultSet rs = null;
        long mostRecent = -1;
        try {
            rs = stmt.executeQuery();
            if (rs.next()) {
                mostRecent = rs.getLong(1);
            }
        } finally {
            if (rs != null) rs.close();
            stmt.close();
        }
        return mostRecent;
    }

    @Override
    protected boolean i_getRebuildStatus(Connection conn, long rebuildDate)
            throws SQLException {
        PreparedStatement stmt = conn.prepareStatement(GET_REBUILD_STATUS);
        ResultSet rs = null;
        boolean status = false;
        try {
            stmt.setLong(1, rebuildDate);
            rs = stmt.executeQuery();
            if (rs.next()) {
                status = rs.getBoolean(1);
            }
           
        } finally {
            if (rs != null) rs.close();
            stmt.close();
        }
        return status;
    }

    @Override
    protected void i_recordSuccessfulRebuild(Connection conn, long rebuildDate)
            throws SQLException {
        PreparedStatement stmt = conn.prepareStatement(SQLRebuilder.CREATE_REBUILD_STATUS);
        stmt.setBoolean(1, true);
        stmt.setLong(2, rebuildDate);
        stmt.execute();
    }

}
TOP

Related Classes of org.fcrepo.server.utilities.SQLUtilityImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.