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 java.sql.Connection; 023 import java.sql.SQLException; 024 import java.util.Iterator; 025 import java.util.Map; 026 import java.util.Properties; 027 028 import javax.persistence.EntityManager; 029 import javax.persistence.EntityManagerFactory; 030 import javax.persistence.Persistence; 031 032 import org.apache.commons.configuration.Configuration; 033 import org.apache.commons.lang.StringUtils; 034 import org.slf4j.Logger; 035 import org.slf4j.LoggerFactory; 036 import org.sonar.api.database.dialect.Dialect; 037 import org.sonar.api.database.dialect.DialectRepository; 038 039 public abstract class AbstractDatabaseConnector implements DatabaseConnector { 040 protected static final Logger LOG_SQL = LoggerFactory.getLogger("org.hibernate.SQL"); 041 protected static final Logger LOG = LoggerFactory.getLogger(AbstractDatabaseConnector.class); 042 043 private Configuration configuration = null; 044 private EntityManagerFactory factory = null; 045 private int databaseVersion = SchemaMigration.VERSION_UNKNOWN; 046 private boolean operational = false; 047 private boolean started = false; 048 private boolean startsFailIfSchemaOutdated; 049 private Integer transactionIsolation = null; 050 private Dialect dialect = null; 051 052 protected AbstractDatabaseConnector(Configuration configuration, boolean startsFailIfSchemaOutdated) { 053 this.configuration = configuration; 054 this.startsFailIfSchemaOutdated = startsFailIfSchemaOutdated; 055 } 056 057 protected AbstractDatabaseConnector() { 058 } 059 060 public Configuration getConfiguration() { 061 return configuration; 062 } 063 064 public void setConfiguration(Configuration configuration) { 065 this.configuration = configuration; 066 } 067 068 /** 069 * Indicates if the connector is operational : database connection OK and schema version OK 070 */ 071 public boolean isOperational() { 072 return operational; 073 } 074 075 /** 076 * Indicates if the connector is started : database connection OK and schema version OK or KO 077 */ 078 protected boolean isStarted() { 079 return started; 080 } 081 082 /** 083 * Get the JDBC transaction isolation defined by the configuration 084 * 085 * @return JDBC transaction isolation 086 */ 087 public Integer getTransactionIsolation() { 088 return transactionIsolation; 089 } 090 091 public void start() { 092 if (!started) { 093 transactionIsolation = configuration.getInteger(DatabaseProperties.PROP_ISOLATION, null /* use driver default setting */); 094 String jdbcConnectionUrl = testConnection(); 095 dialect = DialectRepository.find(configuration.getString("sonar.jdbc.dialect"), jdbcConnectionUrl); 096 LoggerFactory.getLogger("org.sonar.INFO").info("Database dialect class " + dialect.getClass().getName()); 097 started = true; 098 } 099 if (!operational) { 100 boolean upToDate = upToDateSchemaVersion(); 101 if (!upToDate && startsFailIfSchemaOutdated) { 102 throw new DatabaseException(databaseVersion, SchemaMigration.LAST_VERSION); 103 } 104 if (upToDate) { 105 factory = createEntityManagerFactory(); 106 operational = true; 107 } 108 } 109 } 110 111 public void stop() { 112 if (factory != null && factory.isOpen()) { 113 factory.close(); 114 factory = null; 115 } 116 operational = false; 117 started = false; 118 } 119 120 public abstract void setupEntityManagerFactory(Properties factoryProps); 121 122 public EntityManagerFactory getEntityManagerFactory() { 123 return factory; 124 } 125 126 protected void setEntityManagerFactory(EntityManagerFactory factory) { 127 this.factory = factory; 128 } 129 130 protected EntityManagerFactory createEntityManagerFactory() { 131 // other settings are stored into /META-INF/persistence.xml 132 Properties props = getHibernateProperties(); 133 logHibernateSettings(props); 134 return Persistence.createEntityManagerFactory("sonar", props); 135 } 136 137 private void logHibernateSettings(Properties props) { 138 if (LOG.isDebugEnabled()) { 139 for (Map.Entry<Object, Object> entry : props.entrySet()) { 140 LOG.debug(entry.getKey() + ": " + entry.getValue()); 141 } 142 } 143 } 144 145 protected Properties getHibernateProperties() { 146 Properties props = new Properties(); 147 if (transactionIsolation != null) { 148 props.put("hibernate.connection.isolation", Integer.toString(transactionIsolation)); 149 } 150 props.put("hibernate.hbm2ddl.auto", getConfiguration().getString(DatabaseProperties.PROP_HIBERNATE_HBM2DLL, "validate")); 151 props.put("hibernate.dialect", getDialectClass()); 152 153 props.put("hibernate.generate_statistics", getConfiguration().getBoolean(DatabaseProperties.PROP_HIBERNATE_GENERATE_STATISTICS, false)); 154 props.put("hibernate.show_sql", Boolean.valueOf(LOG_SQL.isInfoEnabled()).toString()); 155 156 Configuration subset = getConfiguration().subset("sonar.hibernate"); 157 for (Iterator keys = subset.getKeys(); keys.hasNext();) { 158 String key = (String) keys.next(); 159 if (StringUtils.isNotBlank((String)subset.getProperty(key))) { 160 props.put("hibernate." + key, subset.getProperty(key)); 161 } 162 } 163 164 // custom impl setup 165 setupEntityManagerFactory(props); 166 167 168 return props; 169 } 170 171 public EntityManager createEntityManager() { 172 return factory.createEntityManager(); 173 } 174 175 private String testConnection() throws DatabaseException { 176 Connection connection = null; 177 try { 178 connection = getConnection(); 179 return connection.getMetaData().getURL(); 180 181 } catch (SQLException e) { 182 throw new DatabaseException("Cannot open connection to database: " + e.getMessage(), e); 183 184 } finally { 185 close(connection); 186 } 187 } 188 189 protected int loadVersion() { 190 Connection connection = null; 191 try { 192 connection = getConnection(); 193 return SchemaMigration.getCurrentVersion(connection); 194 195 } catch (SQLException e) { 196 // schema not created 197 return 0; 198 } finally { 199 close(connection); 200 } 201 } 202 203 private void close(Connection connection) { 204 if (connection != null) { 205 try { 206 connection.close(); 207 } catch (SQLException e) { 208 // why does close() throw a checked-exception ??? 209 } 210 } 211 } 212 213 protected boolean upToDateSchemaVersion() { 214 if (databaseVersion == SchemaMigration.LAST_VERSION) { 215 return true; 216 } 217 databaseVersion = loadVersion(); 218 return databaseVersion == SchemaMigration.LAST_VERSION; 219 } 220 221 protected int getDatabaseVersion() { 222 return databaseVersion; 223 } 224 225 public Dialect getDialect() { 226 return dialect; 227 } 228 229 public String getDialectClass() { 230 String dialectClass = configuration.getString(DatabaseProperties.PROP_DIALECT_CLASS); 231 if (dialectClass == null && dialect != null) { 232 dialectClass = dialect.getHibernateDialectClass().getName(); 233 } 234 return dialectClass; 235 } 236 237 }