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