package org.jumpmind.db.platform; /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ import java.lang.reflect.Constructor; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.HashMap; import java.util.Map; import javax.sql.DataSource; import org.apache.commons.lang.StringUtils; import org.h2.util.JdbcUtils; import org.jumpmind.db.platform.ase.AseDatabasePlatform; import org.jumpmind.db.platform.db2.Db2As400DatabasePlatform; import org.jumpmind.db.platform.db2.Db2DatabasePlatform; import org.jumpmind.db.platform.db2.Db2zOsDatabasePlatform; import org.jumpmind.db.platform.derby.DerbyDatabasePlatform; import org.jumpmind.db.platform.firebird.FirebirdDatabasePlatform; import org.jumpmind.db.platform.firebird.FirebirdDialect1DatabasePlatform; import org.jumpmind.db.platform.greenplum.GreenplumPlatform; import org.jumpmind.db.platform.h2.H2DatabasePlatform; import org.jumpmind.db.platform.hsqldb.HsqlDbDatabasePlatform; import org.jumpmind.db.platform.hsqldb2.HsqlDb2DatabasePlatform; import org.jumpmind.db.platform.informix.InformixDatabasePlatform; import org.jumpmind.db.platform.interbase.InterbaseDatabasePlatform; import org.jumpmind.db.platform.mariadb.MariaDBDatabasePlatform; import org.jumpmind.db.platform.mssql.MsSql2000DatabasePlatform; import org.jumpmind.db.platform.mssql.MsSql2005DatabasePlatform; import org.jumpmind.db.platform.mssql.MsSql2008DatabasePlatform; import org.jumpmind.db.platform.mysql.MySqlDatabasePlatform; import org.jumpmind.db.platform.oracle.OracleDatabasePlatform; import org.jumpmind.db.platform.postgresql.PostgreSqlDatabasePlatform; import org.jumpmind.db.platform.redshift.RedshiftDatabasePlatform; import org.jumpmind.db.platform.sqlanywhere.SqlAnywhereDatabasePlatform; import org.jumpmind.db.platform.sqlite.SqliteDatabasePlatform; import org.jumpmind.db.sql.SqlException; import org.jumpmind.db.sql.SqlTemplateSettings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /* * A factory of {@link IDatabasePlatform} instances based on a case * insensitive database name. Note that this is a convenience class as the platforms * can also simply be created via their constructors. */ public class JdbcDatabasePlatformFactory { /* The database name -> platform map. */ private static Map<String, Class<? extends IDatabasePlatform>> platforms = new HashMap<String, Class<? extends IDatabasePlatform>>(); /* * Maps the sub-protocl part of a jdbc connection url to a OJB platform * name. */ private static Map<String, Class<? extends IDatabasePlatform>> jdbcSubProtocolToPlatform = new HashMap<String, Class<? extends IDatabasePlatform>>(); private static final Logger log = LoggerFactory.getLogger(JdbcDatabasePlatformFactory.class); static { addPlatform(platforms, "H2", H2DatabasePlatform.class); addPlatform(platforms, "H21", H2DatabasePlatform.class); addPlatform(platforms, "Informix Dynamic Server11", InformixDatabasePlatform.class); addPlatform(platforms, "Informix Dynamic Server", InformixDatabasePlatform.class); addPlatform(platforms, "Apache Derby", DerbyDatabasePlatform.class); addPlatform(platforms, "Firebird", FirebirdDatabasePlatform.class); addPlatform(platforms, DatabaseNamesConstants.GREENPLUM, GreenplumPlatform.class); addPlatform(platforms, DatabaseNamesConstants.FIREBIRD_DIALECT1, FirebirdDialect1DatabasePlatform.class); addPlatform(platforms, "HsqlDb", HsqlDbDatabasePlatform.class); addPlatform(platforms, "HSQL Database Engine2", HsqlDb2DatabasePlatform.class); addPlatform(platforms, "Interbase", InterbaseDatabasePlatform.class); addPlatform(platforms, "MariaDB", MariaDBDatabasePlatform.class); addPlatform(platforms, "microsoft sql server8", MsSql2000DatabasePlatform.class); addPlatform(platforms, "microsoft sql server9", MsSql2005DatabasePlatform.class); addPlatform(platforms, "microsoft sql server10", MsSql2008DatabasePlatform.class); addPlatform(platforms, "microsoft sql server11", MsSql2008DatabasePlatform.class); addPlatform(platforms, "microsoft sql server", MsSql2008DatabasePlatform.class); addPlatform(platforms, "MySQL", MySqlDatabasePlatform.class); addPlatform(platforms, "Oracle", OracleDatabasePlatform.class); addPlatform(platforms, "PostgreSql", PostgreSqlDatabasePlatform.class); addPlatform(platforms, "Adaptive Server Enterprise", AseDatabasePlatform.class); addPlatform(platforms, "Adaptive Server Anywhere", SqlAnywhereDatabasePlatform.class); addPlatform(platforms, "SQL Anywhere", SqlAnywhereDatabasePlatform.class); addPlatform(platforms, "DB2", Db2DatabasePlatform.class); addPlatform(platforms, DatabaseNamesConstants.DB2ZOS, Db2zOsDatabasePlatform.class); addPlatform(platforms, DatabaseNamesConstants.DB2AS400, Db2As400DatabasePlatform.class); addPlatform(platforms, "SQLite", SqliteDatabasePlatform.class); addPlatform(platforms, DatabaseNamesConstants.REDSHIFT, RedshiftDatabasePlatform.class); jdbcSubProtocolToPlatform.put(Db2DatabasePlatform.JDBC_SUBPROTOCOL, Db2DatabasePlatform.class); jdbcSubProtocolToPlatform.put(DerbyDatabasePlatform.JDBC_SUBPROTOCOL, DerbyDatabasePlatform.class); jdbcSubProtocolToPlatform.put(FirebirdDatabasePlatform.JDBC_SUBPROTOCOL, FirebirdDatabasePlatform.class); jdbcSubProtocolToPlatform.put(HsqlDbDatabasePlatform.JDBC_SUBPROTOCOL, HsqlDbDatabasePlatform.class); jdbcSubProtocolToPlatform.put(InterbaseDatabasePlatform.JDBC_SUBPROTOCOL, InterbaseDatabasePlatform.class); jdbcSubProtocolToPlatform.put(MsSql2000DatabasePlatform.JDBC_SUBPROTOCOL, MsSql2000DatabasePlatform.class); jdbcSubProtocolToPlatform.put(MsSql2005DatabasePlatform.JDBC_SUBPROTOCOL, MsSql2005DatabasePlatform.class); jdbcSubProtocolToPlatform.put(MsSql2008DatabasePlatform.JDBC_SUBPROTOCOL, MsSql2008DatabasePlatform.class); jdbcSubProtocolToPlatform.put(MySqlDatabasePlatform.JDBC_SUBPROTOCOL, MySqlDatabasePlatform.class); jdbcSubProtocolToPlatform.put(OracleDatabasePlatform.JDBC_SUBPROTOCOL_THIN, OracleDatabasePlatform.class); jdbcSubProtocolToPlatform.put(OracleDatabasePlatform.JDBC_SUBPROTOCOL_OCI8, OracleDatabasePlatform.class); jdbcSubProtocolToPlatform.put(OracleDatabasePlatform.JDBC_SUBPROTOCOL_THIN_OLD, OracleDatabasePlatform.class); jdbcSubProtocolToPlatform.put(PostgreSqlDatabasePlatform.JDBC_SUBPROTOCOL, PostgreSqlDatabasePlatform.class); jdbcSubProtocolToPlatform.put(AseDatabasePlatform.JDBC_SUBPROTOCOL, AseDatabasePlatform.class); jdbcSubProtocolToPlatform.put(FirebirdDatabasePlatform.JDBC_SUBPROTOCOL, FirebirdDatabasePlatform.class); } /* * Creates a new platform for the specified database. Note that this method installs * the data source in the returned platform instance. * * @param dataSource The data source for the database * @param log The logger that the platform should use * * @return The platform or <code>null</code> if the database is not * supported */ public static synchronized IDatabasePlatform createNewPlatformInstance(DataSource dataSource, SqlTemplateSettings settings, boolean delimitedIdentifierMode) throws DdlException { // connects to the database and uses actual metadata info to get db name // and version to determine platform String[] nameVersion = determineDatabaseNameVersionSubprotocol(dataSource); Class<? extends IDatabasePlatform> clazz = findPlatformClass(nameVersion); try { Constructor<? extends IDatabasePlatform> construtor = clazz.getConstructor(DataSource.class, SqlTemplateSettings.class); IDatabasePlatform platform = construtor.newInstance(dataSource, settings); log.info("The IDatabasePlatform being used is " + platform.getClass().getCanonicalName()); platform.getDdlBuilder().setDelimitedIdentifierModeOn(delimitedIdentifierMode); return platform; } catch (Exception e) { throw new DdlException("Could not create a platform of type " + nameVersion[0], e); } } protected static synchronized Class<? extends IDatabasePlatform> findPlatformClass( String[] nameVersion) throws DdlException { Class<? extends IDatabasePlatform> platformClass = platforms.get(String.format("%s%s", nameVersion[0], nameVersion[1]).toLowerCase()); if (platformClass == null) { platformClass = platforms.get(nameVersion[0].toLowerCase()); } if (platformClass == null) { platformClass = jdbcSubProtocolToPlatform.get(nameVersion[2]); } if (platformClass == null) { throw new DdlException("Could not find platform for database " + nameVersion[0]); } else { return platformClass; } } protected static String[] determineDatabaseNameVersionSubprotocol(DataSource dataSource) { Connection connection = null; String[] nameVersion = new String[3]; try { connection = dataSource.getConnection(); DatabaseMetaData metaData = connection.getMetaData(); nameVersion[0] = metaData.getDatabaseProductName(); nameVersion[1] = Integer.toString(metaData.getDatabaseMajorVersion()); final String PREFIX = "jdbc:"; String url = metaData.getURL(); if (StringUtils.isNotBlank(url) && url.length() > PREFIX.length()) { url = url.substring(PREFIX.length()); if (url.indexOf(":") > 0) { url = url.substring(0, url.indexOf(":")); } } nameVersion[2] = url; /* * if the productName is PostgreSQL, it could be either PostgreSQL * or Greenplum */ /* query the metadata to determine which one it is */ if (nameVersion[0].equalsIgnoreCase("PostgreSql")) { if (isGreenplumDatabase(connection)) { nameVersion[0] = DatabaseNamesConstants.GREENPLUM; nameVersion[1] = Integer.toString(getGreenplumVersion(connection)); } else if (isRedshiftDatabase(connection)) { nameVersion[0] = DatabaseNamesConstants.REDSHIFT; } } /* * if the productName is MySQL, it could be either MysSQL or MariaDB * query the metadata to determine which one it is */ if (nameVersion[0].equalsIgnoreCase(DatabaseNamesConstants.MYSQL)) { if (isMariaDBDatabase(connection)) { nameVersion[0] = DatabaseNamesConstants.MARIADB; } } if (nameVersion[0].toLowerCase().indexOf(DatabaseNamesConstants.DB2) != -1) { if (nameVersion[0].toUpperCase().indexOf("Z") != -1) { nameVersion[0] = DatabaseNamesConstants.DB2ZOS; } else if (nameVersion[0].indexOf("400") != -1) { nameVersion[0] = DatabaseNamesConstants.DB2AS400; } else { nameVersion[0] = DatabaseNamesConstants.DB2; } } if (nameVersion[0].equalsIgnoreCase("AS") && nameVersion[2].equalsIgnoreCase("db2")) { nameVersion[0] = DatabaseNamesConstants.DB2AS400; } if (nameVersion[0].toLowerCase().startsWith(DatabaseNamesConstants.FIREBIRD)) { if (isFirebirdDialect1(connection)) { nameVersion[0] = DatabaseNamesConstants.FIREBIRD_DIALECT1; } } log.info("Detected database '" + nameVersion[0] + "', version '" + nameVersion[1] + "', protocol '" + nameVersion[2] + "'"); return nameVersion; } catch (SQLException ex) { throw new SqlException("Error while reading the database metadata: " + ex.getMessage(), ex); } finally { if (connection != null) { try { connection.close(); } catch (SQLException ex) { // we ignore this one } } } } private static boolean isGreenplumDatabase(Connection connection) { Statement stmt = null; ResultSet rs = null; String productName = null; boolean isGreenplum = false; try { stmt = connection.createStatement(); rs = stmt.executeQuery(GreenplumPlatform.SQL_GET_GREENPLUM_NAME); while (rs.next()) { productName = rs.getString(1); } if (productName != null && productName.equalsIgnoreCase("Greenplum")) { isGreenplum = true; } } catch (SQLException ex) { // ignore the exception, if it is caught, then this is most likely // not // a greenplum database } finally { try { if (rs != null) { rs.close(); } if (stmt != null) { stmt.close(); } } catch (SQLException ex) { } } return isGreenplum; } private static boolean isRedshiftDatabase(Connection connection) { boolean isRedshift = false; try { DatabaseMetaData dmd = connection.getMetaData(); dmd.getMaxColumnsInIndex(); } catch (SQLException ex) { if (ex.getSQLState().equals("99999")) { isRedshift = true; } } return isRedshift; } private static boolean isFirebirdDialect1(Connection connection) { boolean isDialect1 = false; Statement stmt = null; ResultSet rs = null; try { stmt = connection.createStatement(); rs = stmt.executeQuery("select current_time from rdb$database"); rs.next(); } catch (SQLException ex) { isDialect1 = true; try { JdbcUtils.closeSilently(rs); rs = stmt.executeQuery("select cast(1 as numeric(10,0)) from rdb$database"); rs.next(); } catch (SQLException e) { log.error("The client sql dialect does not match the database, which is not a supported mode. You must add ?sql_dialect=1 to the end of the JDBC URL."); } } finally { JdbcUtils.closeSilently(rs); JdbcUtils.closeSilently(stmt); } return isDialect1; } private static boolean isMariaDBDatabase(Connection connection) { Statement stmt = null; ResultSet rs = null; String productName = null; boolean isMariaDB = false; try { stmt = connection.createStatement(); rs = stmt.executeQuery(MariaDBDatabasePlatform.SQL_GET_MARIADB_NAME); while (rs.next()) { productName = rs.getString(1); } if (productName != null && StringUtils.containsIgnoreCase(productName, DatabaseNamesConstants.MARIADB)) { isMariaDB = true; } } catch (SQLException ex) { // ignore the exception, if it is caught, then this is most likely // not a mariadb database } finally { try { if (rs != null) { rs.close(); } if (stmt != null) { stmt.close(); } } catch (SQLException ex) { } } return isMariaDB; } private static int getGreenplumVersion(Connection connection) { Statement stmt = null; ResultSet rs = null; String versionName = null; int productVersion = 0; try { stmt = connection.createStatement(); rs = stmt.executeQuery(GreenplumPlatform.SQL_GET_GREENPLUM_VERSION); while (rs.next()) { versionName = rs.getString(1); } // take up to the first "." for version number if (versionName.indexOf('.') != -1) { versionName = versionName.substring(0, versionName.indexOf('.')); } try { productVersion = Integer.parseInt(versionName); } catch (NumberFormatException ex) { // if we can't convert this to a version number, leave it 0 } } catch (SQLException ex) { // ignore the exception, if it is caught, then this is most likely // not // a greenplum database } finally { try { rs.close(); stmt.close(); } catch (SQLException ex) { } } return productVersion; } public static String getDatabaseProductVersion(DataSource dataSource) { Connection connection = null; try { connection = dataSource.getConnection(); DatabaseMetaData metaData = connection.getMetaData(); return metaData.getDatabaseProductVersion(); } catch (SQLException ex) { throw new SqlException("Error while reading the database metadata: " + ex.getMessage(), ex); } finally { if (connection != null) { try { connection.close(); } catch (SQLException ex) { // we ignore this one } } } } public static int getDatabaseMajorVersion(DataSource dataSource) { Connection connection = null; try { connection = dataSource.getConnection(); DatabaseMetaData metaData = connection.getMetaData(); return metaData.getDatabaseMajorVersion(); } catch (SQLException ex) { throw new SqlException("Error while reading the database metadata: " + ex.getMessage(), ex); } finally { if (connection != null) { try { connection.close(); } catch (SQLException ex) { // we ignore this one } } } } public static int getDatabaseMinorVersion(DataSource dataSource) { Connection connection = null; try { connection = dataSource.getConnection(); DatabaseMetaData metaData = connection.getMetaData(); return metaData.getDatabaseMinorVersion(); } catch (SQLException ex) { throw new SqlException("Error while reading the database metadata: " + ex.getMessage(), ex); } finally { if (connection != null) { try { connection.close(); } catch (SQLException ex) { // we ignore this one } } } } private static synchronized void addPlatform( Map<String, Class<? extends IDatabasePlatform>> platformMap, String platformName, Class<? extends IDatabasePlatform> platformClass) { if (!IDatabasePlatform.class.isAssignableFrom(platformClass)) { throw new IllegalArgumentException("Cannot register class " + platformClass.getName() + " because it does not implement the " + IDatabasePlatform.class.getName() + " interface"); } platformMap.put(platformName.toLowerCase(), platformClass); } }