/* * RHQ Management Platform * Copyright (C) 2005-2008 Red Hat, Inc. * All rights reserved. * * 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 version 2 of the License. * * 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., 675 Mass Ave, Cambridge, MA 02139, USA. */ package org.rhq.core.db; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.util.Hashtable; import java.util.Map; import javax.naming.Context; import javax.sql.DataSource; import mazz.i18n.Logger; /** * A factory class that creates {@link DatabaseType} objects based on the database being connected to. This should be * the only class (or at least one of the very few) that actually has database-specific knowledge coded into it. */ public class DatabaseTypeFactory { private static final Logger LOG = DbUtilsI18NFactory.getLogger(DatabaseTypeFactory.class); /** * Maps the database type that is associated with a particular connection type. */ private static final Map<String, Class<? extends DatabaseType>> DATABASE_TYPES = new Hashtable<String, Class<? extends DatabaseType>>(); /** * This provides a map that keys JDBC URL protocols with their corresponding <code>java.sql.Driver</code> classes. */ private static final Map<String, String> DB_URL_DRIVER_MAP; /** * This can be set to store the defaultDatabaseType. Typically set it application initialization to * provide a quick way to get the (typically) unchanging db vendor setting. */ private static DatabaseType defaultDatabaseType = null; static { DB_URL_DRIVER_MAP = new Hashtable<String, String>(); DB_URL_DRIVER_MAP.put("jdbc:postgresql:", "org.postgresql.Driver"); DB_URL_DRIVER_MAP.put("jdbc:oracle:thin:@", "oracle.jdbc.driver.OracleDriver"); DB_URL_DRIVER_MAP.put("jdbc:oracle:oci8:", "oracle.jdbc.driver.OracleDriver"); DB_URL_DRIVER_MAP.put("jdbc:h2:", "org.h2.Driver"); DB_URL_DRIVER_MAP.put("jdbc:jtds:sqlserver", "net.sourceforge.jtds.jdbc.Driver"); } /** * Prevents instantiation. */ private DatabaseTypeFactory() { // don't instantiate us from external } /** * By default, this factory will remember what {@link DatabaseType} is associated with * a specific connection class type. However, if you change the JDBC URL, its possible * that this will also change the database type the connection class type should be * associated with (i.e. the URL may point to a different version of the same vendor's * database). If you change the JDBC URL, you should call this method to clear the * cache, thus enabling the factory to re-check the connection metadata to associate * a new database type with the connection class type. */ public static void clearDatabaseTypeCache() { DATABASE_TYPES.clear(); } /** * Convenience method that gives you the {@link DatabaseType} given a context and datasource. This is a combination * of {@link #getDatabaseType(Context, String)} and {@link #getDatabaseType(Connection)}. * * @param context the context where the datasource can be found * @param datasource the name of the datasource within the given context * * @return the {@link DatabaseType} of the type of database behind the data source * * @throws Exception if cannot find the datasource in the given context or the connection cannot be created or if * cannot determine what database the connection is connected to */ public static DatabaseType getDatabaseType(Context context, String datasource) throws Exception { Connection c = getConnection(context, datasource); try { return getDatabaseType(c); } finally { try { c.close(); } catch (Exception e) { } } } /** * Given a context and a JNDI datasource name, this will lookup within that context to find the named datasource. A * connection to that datasource will be created and returned - with the intent that the connection will be passed * to {@link #getDatabaseType(Connection)} to create a {@link DatabaseType}. * * @param context the context where the datasource can be found * @param datasource the name of the datasource within the given context * * @return a connection to the given datasource * * @throws Exception if cannot find the datasource in the given context or the connection cannot be created */ public static Connection getConnection(Context context, String datasource) throws Exception { DataSource ds = (DataSource) context.lookup(datasource); return ds.getConnection(); } /** * Given a <code>Connection</code> object, this method returns its associated {@link DatabaseType}. * * @param conn * * @return the {@link DatabaseType} that the connection is connected to * * @throws Exception if cannot determine what database the connection is connected to */ public static DatabaseType getDatabaseType(Connection conn) throws Exception { String conn_class = conn.getClass().getName(); Class<? extends DatabaseType> database_type_class = DATABASE_TYPES.get(conn_class); if (database_type_class == null) { DatabaseMetaData db_metadata = conn.getMetaData(); String db_name = db_metadata.getDatabaseProductName().toLowerCase(); String db_version = db_metadata.getDatabaseProductVersion().toLowerCase(); LOG.debug(DbUtilsI18NResourceKeys.DB_CONNECTION_METADATA, db_name, db_version); if (db_name.indexOf("postgresql") != -1) { if (db_version.startsWith("7.")) { database_type_class = Postgresql7DatabaseType.class; } else if (db_version.startsWith("9.0")) { database_type_class = Postgresql90DatabaseType.class; } else if (db_version.startsWith("9.")) { // 9.1, 9.2, 9.x... database_type_class = Postgresql91DatabaseType.class; } else if (db_version.startsWith("8.4")) { database_type_class = Postgresql84DatabaseType.class; } else if (db_version.startsWith("8.3")) { database_type_class = Postgresql83DatabaseType.class; } else if (db_version.startsWith("8.2")) { database_type_class = Postgresql82DatabaseType.class; } else if (db_version.startsWith("8.1")) { database_type_class = Postgresql81DatabaseType.class; } else if (db_version.startsWith("8.")) { database_type_class = Postgresql8DatabaseType.class; } } else if (db_name.indexOf("oracle") != -1) { if (db_version.startsWith("oracle8")) { database_type_class = Oracle8DatabaseType.class; } else if (db_version.startsWith("oracle9")) { database_type_class = Oracle9DatabaseType.class; } else if (db_version.startsWith("oracle database 10g")) { database_type_class = Oracle10DatabaseType.class; } else if (db_version.startsWith("oracle database 11g")) { database_type_class = Oracle11DatabaseType.class; } else if (db_version.startsWith("oracle database 12c")) { database_type_class = Oracle12DatabaseType.class; } } else if (db_name.indexOf("h2") != -1) { if (db_version.startsWith("1.1")) { database_type_class = H2v11DatabaseType.class; } else if (db_version.startsWith("1.2")) { database_type_class = H2v12DatabaseType.class; } } else if (db_name.indexOf("sql server") != -1) { if (db_version.startsWith("09.00") || db_version.startsWith("9.00")) { // SQL Server 2005 database_type_class = SQLServer2005DatabaseType.class; } else if (db_version.startsWith("10.00")) { // SQL Server 2008 database_type_class = SQLServer2008DatabaseType.class; } } if (database_type_class == null) { throw new Exception(DbUtilsI18NFactory.getMsg().getMsg(DbUtilsI18NResourceKeys.UNKNOWN_DATABASE, db_name, db_version)); } DATABASE_TYPES.put(conn_class, database_type_class); } return database_type_class.newInstance(); } /** * This is the getter of a get/set convenience mechanism for storing and retrieving the active database type. * Not a true singleton but typically the value will not change as the underlying db is typically * not going to change at runtime. * * @return The current DatabaseType or null if not yet set. */ public static DatabaseType getDefaultDatabaseType() { return DatabaseTypeFactory.defaultDatabaseType; } /** * This is the setter of a get/set convenience mechanism for storing and retrieving the active database type. * Typically called one time when the dbType is established. Not a true singleton but typically the value will * not change as the underlying db is typically not going to change. */ public static void setDefaultDatabaseType(DatabaseType databaseType) { DatabaseTypeFactory.defaultDatabaseType = databaseType; } /** * Given a JDBC URL, this will attempt to load in the JDBC driver class that will be needed to connect to the * database via that URL and will return that driver class name. If the driver class could not be loaded, <code> * null</code> will be returned - in which case attempting to connect to the database with the given URL will * probably fail. * * @param jdbc_url a connection URL to the database * * @return the driver class that was attempted to have been loaded */ public static String loadJdbcDriver(String jdbc_url) { String ret_driver_class = null; for (Map.Entry<String, String> url_driver_entry : DB_URL_DRIVER_MAP.entrySet()) { String url_prefix = url_driver_entry.getKey(); if (jdbc_url.startsWith(url_prefix)) { String driver_class_to_load = url_driver_entry.getValue(); try { Class.forName(driver_class_to_load); ret_driver_class = driver_class_to_load; } catch (ClassNotFoundException e) { LOG.warn(DbUtilsI18NResourceKeys.CANNOT_LOAD_JDBC_DRIVER, driver_class_to_load, jdbc_url); } break; } } return ret_driver_class; } /** * Is the database PostgreSQL? * * @param c the connection to the database * * @return <code>true</code> if the connection is talking to a Postgres database * * @throws Exception */ public static boolean isPostgres(Connection c) throws Exception { DatabaseType type = getDatabaseType(c); return isPostgres(type); } /** * Determines if the given type refers to a Postgres database. * * @param type * * @return <code>true</code> if the type is a Postgres database */ public static boolean isPostgres(DatabaseType type) { return (type instanceof PostgresqlDatabaseType); } /** * Is the database Oracle? * * @param c the connection to the database * * @return <code>true</code> if the connection is talking to an Oracle database * * @throws Exception */ public static boolean isOracle(Connection c) throws Exception { DatabaseType type = getDatabaseType(c); return isOracle(type); } /** * Determines if the given type refers to an Oracle database. * * @param type * * @return <code>true</code> if the type is an Oracle database */ public static boolean isOracle(DatabaseType type) { return (type instanceof OracleDatabaseType); } /** * Is the database H2? * * @param c the connection to the database * * @return <code>true</code> if the connection is talking to an H2 database * * @throws Exception */ public static boolean isH2(Connection c) throws Exception { DatabaseType type = getDatabaseType(c); return isH2(type); } /** * Determines if the given type refers to an H2 database. * * @param type * * @return <code>true</code> if the type is an H2 database */ public static boolean isH2(DatabaseType type) { return (type instanceof H2DatabaseType); } /** * Is the database SQL Server? * * @param c the connection to the database * * @return <code>true</code> if the connection is talking to an SQL Server database * * @throws Exception */ public static boolean isSQLServer(Connection c) throws Exception { DatabaseType type = getDatabaseType(c); return isSQLServer(type); } /** * Determines if the given type refers to an SQL Server database. * * @param type * * @return <code>true</code> if the type is an SQL Server database */ public static boolean isSQLServer(DatabaseType type) { return (type instanceof SQLServerDatabaseType); } }