001 /* 002 * Sonar, open source software quality management tool. 003 * Copyright (C) 2009 SonarSource SA 004 * mailto:contact AT sonarsource DOT com 005 * 006 * Sonar is free software; you can redistribute it and/or 007 * modify it under the terms of the GNU Lesser General Public 008 * License as published by the Free Software Foundation; either 009 * version 3 of the License, or (at your option) any later version. 010 * 011 * Sonar is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 * Lesser General Public License for more details. 015 * 016 * You should have received a copy of the GNU Lesser General Public 017 * License along with Sonar; if not, write to the Free Software 018 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 019 */ 020 package org.sonar.api.database; 021 022 import org.apache.commons.configuration.Configuration; 023 import org.hibernate.dialect.DB2Dialect; 024 import org.hibernate.dialect.HSQLDialect; 025 import org.slf4j.Logger; 026 import org.slf4j.LoggerFactory; 027 import org.sonar.api.database.dialect.*; 028 029 import java.sql.Connection; 030 import java.sql.SQLException; 031 import java.util.Properties; 032 import javax.persistence.EntityManager; 033 import javax.persistence.EntityManagerFactory; 034 import javax.persistence.Persistence; 035 import javax.persistence.PersistenceException; 036 037 public abstract class AbstractDatabaseConnector implements DatabaseConnector { 038 protected static final Logger LOG_SQL = LoggerFactory.getLogger("org.hibernate.SQL"); 039 protected static final Logger LOG_STATISTICS = LoggerFactory.getLogger("org.sonar.DBSTATISTICS"); 040 041 private Configuration configuration = null; 042 private EntityManagerFactory factory = null; 043 private int databaseVersion = SchemaMigration.VERSION_UNKNOWN; 044 private boolean operational = false; 045 private boolean started = false; 046 private boolean startsFailIfSchemaOutdated; 047 private Integer transactionIsolation = null; 048 049 protected AbstractDatabaseConnector(Configuration configuration, boolean startsFailIfSchemaOutdated) { 050 this.configuration = configuration; 051 this.startsFailIfSchemaOutdated = startsFailIfSchemaOutdated; 052 } 053 054 protected AbstractDatabaseConnector() { 055 } 056 057 public Configuration getConfiguration() { 058 return configuration; 059 } 060 061 public void setConfiguration(Configuration configuration) { 062 this.configuration = configuration; 063 } 064 065 /** 066 * Indicates if the connector is operational : database connection OK and schema version OK 067 */ 068 public boolean isOperational() { 069 return operational; 070 } 071 072 /** 073 * Indicates if the connector is started : database connection OK and schema version OK or KO 074 */ 075 protected boolean isStarted() { 076 return started; 077 } 078 079 /** 080 * Get the JDBC transaction isolation defined by the configuration 081 * 082 * @return JDBC transaction isolation 083 */ 084 public Integer getTransactionIsolation() { 085 return transactionIsolation; 086 } 087 088 public void start() { 089 if (!started) { 090 transactionIsolation = configuration.getInteger(DatabaseProperties.PROP_ISOLATION, null /* use driver default setting */); 091 testConnection(); 092 started = true; 093 } 094 if (!operational) { 095 boolean upToDate = upToDateSchemaVersion(); 096 if (!upToDate && startsFailIfSchemaOutdated) { 097 throw new DatabaseException(databaseVersion, SchemaMigration.LAST_VERSION); 098 } 099 if (upToDate) { 100 factory = createEntityManagerFactory(); 101 operational = true; 102 } 103 } 104 } 105 106 public void stop() { 107 if (factory != null && factory.isOpen()) { 108 factory.close(); 109 factory = null; 110 } 111 operational = false; 112 started = false; 113 } 114 115 public abstract void setupEntityManagerFactory(Properties factoryProps); 116 117 public EntityManagerFactory getEntityManagerFactory() { 118 return factory; 119 } 120 121 protected void setEntityManagerFactory(EntityManagerFactory factory) { 122 this.factory = factory; 123 } 124 125 protected EntityManagerFactory createEntityManagerFactory() { 126 // other settings are stored into /META-INF/persistence.xml 127 Properties props = new Properties(); 128 if (transactionIsolation != null) { 129 props.put("hibernate.connection.isolation", Integer.toString(transactionIsolation)); 130 } 131 props.put("hibernate.hbm2ddl.auto", getConfiguration().getString(DatabaseProperties.PROP_HIBERNATE_HBM2DLL, "validate")); 132 props.put("hibernate.dialect", getDialectClass(getDialect())); 133 134 props.put("hibernate.generate_statistics", Boolean.valueOf(LOG_STATISTICS.isInfoEnabled()).toString()); 135 props.put("hibernate.show_sql", Boolean.valueOf(LOG_SQL.isInfoEnabled()).toString()); 136 137 // custom impl setup 138 setupEntityManagerFactory(props); 139 return Persistence.createEntityManagerFactory("sonar", props); 140 } 141 142 public EntityManager createEntityManager() { 143 EntityManager manager = factory.createEntityManager(); 144 if (LOG_STATISTICS.isInfoEnabled()) { 145 manager = new StatisticsEntityManager(factory, manager, LOG_STATISTICS); 146 } 147 return manager; 148 } 149 150 private void testConnection() throws DatabaseException { 151 Connection connection = null; 152 try { 153 connection = getConnection(); 154 } catch (SQLException e) { 155 throw new DatabaseException("Cannot open connection to database: " + e.getMessage(), e); 156 } finally { 157 silentlyCloseConnection(connection); 158 } 159 } 160 161 protected int loadVersion() { 162 Connection connection = null; 163 try { 164 connection = getConnection(); 165 return SchemaMigration.getCurrentVersion(connection); 166 } catch (SQLException e) { 167 // schema not created 168 return 0; 169 } finally { 170 silentlyCloseConnection(connection); 171 } 172 } 173 174 private void silentlyCloseConnection(Connection connection) { 175 if (connection != null) { 176 try { 177 connection.close(); 178 } catch (SQLException e) { 179 } 180 } 181 } 182 183 private boolean upToDateSchemaVersion() { 184 if (databaseVersion == SchemaMigration.LAST_VERSION) { 185 return true; 186 } 187 databaseVersion = loadVersion(); 188 return databaseVersion == SchemaMigration.LAST_VERSION; 189 } 190 191 protected int getDatabaseVersion() { 192 return databaseVersion; 193 } 194 195 public String getDialect() { 196 String dialect = configuration.getString("sonar.jdbc.dialect"); 197 if (dialect == null) { 198 Connection connection = null; 199 try { 200 connection = getConnection(); 201 dialect = getDialectFromJdbcUrl(connection.getMetaData().getURL()); 202 203 } catch (SQLException e) { 204 throw new PersistenceException("can not autodetect the dialect", e); 205 206 } finally { 207 silentlyCloseConnection(connection); 208 } 209 } 210 return dialect; 211 } 212 213 public String getDialectClass(String dialect) { 214 String dialectClass = configuration.getString(DatabaseProperties.PROP_DIALECT_CLASS); 215 if (dialectClass == null) { 216 dialectClass = getHibernateDialectClassName(dialect); 217 } 218 return dialectClass; 219 } 220 221 private String getDialectFromJdbcUrl(String url) { 222 if (url.toLowerCase().startsWith("jdbc:db2:")) { 223 return DatabaseProperties.DIALECT_DB2; 224 } 225 if (url.toLowerCase().startsWith("jdbc:derby:")) { 226 return DatabaseProperties.DIALECT_DERBY; 227 } 228 if (url.toLowerCase().startsWith("jdbc:hsqldb:")) { 229 return DatabaseProperties.DIALECT_HSQLDB; 230 } 231 if (url.toLowerCase().startsWith("jdbc:microsoft:sqlserver:") || 232 url.toLowerCase().startsWith("jdbc:jtds:sqlserver:")) { 233 return DatabaseProperties.DIALECT_MSSQL; 234 } 235 if (url.toLowerCase().startsWith("jdbc:mysql:")) { 236 return DatabaseProperties.DIALECT_MYSQL; 237 } 238 if (url.toLowerCase().startsWith("jdbc:oracle:")) { 239 return DatabaseProperties.DIALECT_ORACLE; 240 } 241 if (url.toLowerCase().startsWith("jdbc:postgresql:")) { 242 return DatabaseProperties.DIALECT_POSTGRESQL; 243 } 244 return null; 245 } 246 247 private String getHibernateDialectClassName(String dialect) { 248 if (DatabaseProperties.DIALECT_DB2.equals(dialect)) { 249 return DB2Dialect.class.getName(); 250 } 251 if (DatabaseProperties.DIALECT_DERBY.equals(dialect)) { 252 return DerbyWithDecimalDialect.class.getName(); 253 } 254 if (DatabaseProperties.DIALECT_HSQLDB.equals(dialect)) { 255 return HSQLDialect.class.getName(); 256 } 257 if (DatabaseProperties.DIALECT_MSSQL.equals(dialect)) { 258 return MsSqlDialect.class.getName(); 259 } 260 if (DatabaseProperties.DIALECT_MYSQL.equals(dialect)) { 261 return MySqlWithDecimalDialect.class.getName(); 262 } 263 if (DatabaseProperties.DIALECT_ORACLE.equals(dialect)) { 264 return Oracle10gWithDecimalDialect.class.getName(); 265 } 266 if (DatabaseProperties.DIALECT_POSTGRESQL.equals(dialect)) { 267 return PostgreSQLWithDecimalDialect.class.getName(); 268 } 269 return null; 270 } 271 }