package nl.rug.lib.db; import org.apache.log4j.Logger; import java.sql.*; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Convenience methods for database access. * * @author Peter Fokkinga */ public final class DbUtils { public enum SupportedDatabase { ORACLE("oracle:thin"), POSTGRESQL("postgresql"), MYSQL("mysql"), SQLSERVER("inetdae7"); private final String jdbcDriver; private SupportedDatabase(String driver) { jdbcDriver = driver; } public String getJdbcDriver() { return jdbcDriver; } public static SupportedDatabase fromDriver(String driver) { return driver == null ? ORACLE : ORACLE.jdbcDriver.equals(driver) ? ORACLE : SQLSERVER.jdbcDriver.equals(driver) ? SQLSERVER : "mssql".equals(driver) ? SQLSERVER // legacy : valueOf(driver.toUpperCase()); } /** * Builds a connectstring for connecting to a database through JDBC. * * @param hostname the hostname of the database server * @param portNumber the port on which the database listens for new connections * @param dbInstance the name of the database to connect to * @return a JDBC connectstring, can be used when calling * {@link DbUtils#getConnection(DbUtils.SupportedDatabase, String, String, String)} */ public String getConnectString(String hostname, int portNumber, String dbInstance) { if (dbInstance == null) { throw new IllegalArgumentException("database cannot be NULL"); } StringBuilder sb = new StringBuilder("jdbc:"); sb.append(getJdbcDriver()).append(":"); if (this != SupportedDatabase.SQLSERVER) { sb.append("//"); } sb.append(hostname).append((portNumber > 0 ? ":" + portNumber : "")); String instance = null, db = dbInstance; if (dbInstance.contains("/")) { String[] parts = dbInstance.split("/"); instance = parts[0]; db = parts[1]; } if (this == DbUtils.SupportedDatabase.SQLSERVER && portNumber > 0) { sb.append("?database=").append(db); } else { // A bit of a hack really... this uses the Sqlbrowser process on the SQLserver // machine to lookup the portnumber for the named instance. // Note that you now cannot specify the database in the connectstring, // so you need to configure the "default database" for the user you // connect with in SQLserver/ sb.append("/").append(instance != null ? instance : dbInstance); } return sb.toString(); } } /** * Gets a connection to an external database. If it is an Oracle database * then the date format will be explicitly set to YYYY-MM-DD HH:MI:SS * * @param dbType one of the supported database types * @param connectString full JDBC connect string * @param username username for the connection * @param password password for the connection * @return a connection to the database * @throws SQLException when the connection could not be established */ public static Connection getConnection(SupportedDatabase dbType, String connectString, String username, String password) throws SQLException { Connection result; log4j.debug("getConnection(" + dbType.name() + ", " + connectString + ", " + username + ")"); try { switch (dbType) { case POSTGRESQL: Class.forName("org.postgresql.Driver"); result = DriverManager.getConnection(connectString, username, password); break; case ORACLE: Class.forName("oracle.jdbc.driver.OracleDriver"); Pattern p = Pattern.compile("^jdbc:" + dbType.getJdbcDriver() + "://([^:]+):(\\d+)/(.+)"); Matcher m = p.matcher(connectString); StringBuilder sb = new StringBuilder(); if (m.matches()) { sb.append("jdbc:").append(dbType.getJdbcDriver()).append(":@"); sb.append(m.group(1)).append(":").append(m.group(2)).append(":").append(m.group(3)); log4j.trace("Oracle connectstring = " + sb.toString()); } else { // connectstring did not have portnumber, use default Oracle portnumber (1521) p = Pattern.compile("^jdbc:" + dbType.getJdbcDriver() + "://([^/]+)/(.+)"); m = p.matcher(connectString); if (m.matches()) { sb.append("jdbc:").append(dbType.getJdbcDriver()).append(":@"); sb.append(m.group(1)).append(":1521:").append(m.group(2)); } else { log4j.error("could not parse connectstring for Oracle: " + connectString); sb.append(connectString); } } result = DriverManager.getConnection(sb.toString(), username, password); // use ISO date as default Statement stat = result.createStatement(); try { stat.executeUpdate("ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS'"); } finally { stat.close(); } break; case MYSQL: Class.forName("com.mysql.jdbc.Driver"); result = DriverManager.getConnection(connectString, username, password); break; case SQLSERVER: // this is the (commercial) Merlia driver from i-net software that Blackboard uses Class.forName("com.inet.tds.TdsDriver"); result = DriverManager.getConnection(connectString, username, password); break; default: throw new SQLException("database driver " + dbType + " is not supported"); } } catch (ClassNotFoundException e) { throw new SQLException("problem loading driver: " + e.getMessage()); } return result; } private static final Logger log4j = Logger.getLogger(DbUtils.class); /** * This class only contains static methods */ private DbUtils() { } /** * Sets NULL when the supplied integer is less than 1 (or NULL). * * @param ps the prepared statement to set a value in * @param pos the position of the placeholder to insert to * @param value the value to insert, may be NULL * @throws java.sql.SQLException when the placeholder could not be set to the value */ public static void setPositiveInteger(PreparedStatement ps, int pos, Integer value) throws SQLException { if (value == null || value < 1) { value = null; } setInteger(ps, pos, value); } /** * Sets NULL when the supplied integer is NULL * * @param ps the prepared statement to set a value in * @param pos the position of the placeholder to insert to * @param value the value to insert, may be NULL * @throws java.sql.SQLException when the placeholder could not be set to the value */ public static void setInteger(PreparedStatement ps, int pos, Integer value) throws SQLException { if (value == null) { ps.setNull(pos, java.sql.Types.INTEGER); } else { ps.setInt(pos, value); } } /** * Sets NULL when the supplied float is NULL * * @param ps the prepared statement to set a value in * @param pos the position of the placeholder to insert to * @param value the value to insert, may be NULL * @throws java.sql.SQLException when the placeholder could not be set to the value */ public static void setFloat(PreparedStatement ps, int pos, Float value) throws SQLException { if (value == null) { ps.setNull(pos, java.sql.Types.FLOAT); } else { ps.setFloat(pos, value); } } /** * Sets NULL when the supplied date is NULL * * @param ps the prepared statement to set a value in * @param pos the position of the placeholder to insert to * @param value the value to insert, may be NULL * @throws java.sql.SQLException when the placeholder could not be set to the value */ public static void setDate(PreparedStatement ps, int pos, java.util.Date value) throws SQLException { if (value == null) { ps.setNull(pos, java.sql.Types.DATE); } else { ps.setDate(pos, new java.sql.Date(value.getTime())); } } /** * Sets NULL when the supplied string is empty or contains only whitespace. * * @param ps the prepared statement to set a value in * @param pos the position of the placeholder to insert to * @param value the value to insert, may be NULL * @throws java.sql.SQLException when the placeholder could not be set to the value */ public static void setNonEmptyVarChar(PreparedStatement ps, int pos, String value) throws SQLException { if (value == null || value.trim().length() == 0) { value = null; } setVarChar(ps, pos, value); } /** * Sets NULL when the supplied string is NULL * * @param ps the prepared statement to set a value in * @param pos the position of the placeholder to insert to * @param value the value to insert, may be NULL * @throws java.sql.SQLException when the placeholder could not be set to the value */ public static void setVarChar(PreparedStatement ps, int pos, String value) throws SQLException { if (value == null) { ps.setNull(pos, java.sql.Types.VARCHAR); } else { ps.setString(pos, value); } } public static boolean isOracle(Connection c) throws SQLException { return "Oracle".equalsIgnoreCase(c.getMetaData().getDatabaseProductName()); } public static boolean isMSSQL(Connection c) throws SQLException { return "Microsoft SQL Server".equalsIgnoreCase(c.getMetaData().getDatabaseProductName()); } public static boolean isMySQL(Connection c) throws SQLException { return "mysql".equalsIgnoreCase(c.getMetaData().getDatabaseProductName()); } public static boolean isPostgreSQL(Connection c) throws SQLException { return "postgresql".equalsIgnoreCase(c.getMetaData().getDatabaseProductName()); } /** * Convenience method for closing PreparedStatements. Handles (ignores) * exceptions, including when the given statement is NULL. * * @param statements one or more PreparedStatement that must be closed * (this method supports a variable-length argument list) */ public static void closePreparedStatement(PreparedStatement... statements) { for (PreparedStatement ps : statements) { try { ps.close(); } catch (Exception e) { // this space intentionally left blank } } } /** * Convenience method for closing ResultSets. Handles (ignores) * exceptions, including when the given resultset is NULL. * * @param resultsets one or more ResultSet that must be closed * (this method supports a variable-length argument list) */ public static void closeResultSet(ResultSet... resultsets) { for (ResultSet rs : resultsets) { try { rs.close(); } catch (Exception e) { // this space intentionally left blank } } } }