001 /* 002 * Sonar, open source software quality management tool. 003 * Copyright (C) 2008-2011 SonarSource 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.core.persistence; 021 022 import org.apache.commons.dbcp.BasicDataSource; 023 import org.apache.commons.dbcp.BasicDataSourceFactory; 024 import org.apache.commons.lang.StringUtils; 025 import org.hibernate.cfg.Environment; 026 import org.slf4j.Logger; 027 import org.slf4j.LoggerFactory; 028 import org.sonar.api.config.Settings; 029 import org.sonar.api.database.DatabaseProperties; 030 import org.sonar.core.persistence.dialect.*; 031 import org.sonar.jpa.session.CustomHibernateConnectionProvider; 032 033 import javax.sql.DataSource; 034 import java.sql.SQLException; 035 import java.util.Arrays; 036 import java.util.List; 037 import java.util.Map; 038 import java.util.Properties; 039 040 /** 041 * @since 2.12 042 */ 043 public class DefaultDatabase implements Database { 044 045 private static final Logger LOG = LoggerFactory.getLogger(Database.class); 046 047 private Settings settings; 048 private BasicDataSource datasource; 049 private Dialect dialect; 050 private Properties properties; 051 052 public DefaultDatabase(Settings settings) { 053 this.settings = settings; 054 } 055 056 public final DefaultDatabase start() { 057 try { 058 doBeforeStart(); 059 initSettings(); 060 initDatasource(); 061 return this; 062 063 } catch (Exception e) { 064 throw new IllegalStateException("Fail to connect to database", e); 065 } 066 } 067 068 void initSettings() { 069 initProperties(); 070 initDialect(); 071 initSchema(); 072 } 073 074 private void initProperties() { 075 properties = new Properties(); 076 completeProperties(settings, properties, "sonar.jdbc."); 077 completeProperties(settings, properties, "sonar.hibernate."); 078 completeDefaultProperties(properties); 079 doCompleteProperties(properties); 080 } 081 082 private void initDialect() { 083 dialect = DialectUtils.find(properties.getProperty("sonar.jdbc.dialect"), properties.getProperty("sonar.jdbc.url")); 084 if (dialect == null) { 085 throw new IllegalStateException("Can not guess the JDBC dialect. Please check the property sonar.jdbc.url."); 086 } 087 if (Derby.ID.equals(dialect.getId())) { 088 LoggerFactory.getLogger(DefaultDatabase.class).warn("Derby database should be used for evaluation purpose only"); 089 } 090 if (!properties.containsKey("sonar.jdbc.driverClassName")) { 091 properties.setProperty("sonar.jdbc.driverClassName", dialect.getDefaultDriverClassName()); 092 } 093 } 094 095 private void initSchema() { 096 String deprecatedSchema = null; 097 if (PostgreSql.ID.equals(dialect.getId())) { 098 // this property has been deprecated in version 2.13 099 deprecatedSchema = getSchemaPropertyValue(properties, "sonar.jdbc.postgreSearchPath"); 100 101 } else if (Oracle.ID.equals(dialect.getId())) { 102 // this property has been deprecated in version 2.13 103 deprecatedSchema = getSchemaPropertyValue(properties, "sonar.hibernate.default_schema"); 104 } 105 if (StringUtils.isNotBlank(deprecatedSchema)) { 106 properties.setProperty("sonar.jdbc.schema", deprecatedSchema); 107 } 108 } 109 110 private void initDatasource() throws Exception {//NOSONAR this exception is thrown by BasicDataSourceFactory 111 // but it's correctly caught by start() 112 113 LOG.info("Create JDBC datasource"); 114 datasource = (BasicDataSource) BasicDataSourceFactory.createDataSource(extractCommonsDbcpProperties(properties)); 115 116 String initStatement = dialect.getConnectionInitStatement(getSchema()); 117 if (StringUtils.isNotBlank(initStatement)) { 118 datasource.setConnectionInitSqls(Arrays.asList(initStatement)); 119 } 120 } 121 122 protected void doBeforeStart() { 123 } 124 125 public final DefaultDatabase stop() { 126 doBeforeStop(); 127 if (datasource != null) { 128 try { 129 datasource.close(); 130 } catch (SQLException e) { 131 throw new IllegalStateException("Fail to stop JDBC connection pool", e); 132 } 133 } 134 return this; 135 } 136 137 protected void doBeforeStop() { 138 139 } 140 141 public final Dialect getDialect() { 142 return dialect; 143 } 144 145 public final String getSchema() { 146 return properties.getProperty("sonar.jdbc.schema"); 147 } 148 149 public Properties getHibernateProperties() { 150 Properties props = new Properties(); 151 152 List<String> hibernateKeys = settings.getKeysStartingWith("sonar.hibernate."); 153 for (String hibernateKey : hibernateKeys) { 154 props.put(StringUtils.removeStart(hibernateKey, "sonar."), settings.getString(hibernateKey)); 155 } 156 props.put(Environment.DIALECT, getDialect().getHibernateDialectClass().getName()); 157 props.put("hibernate.generate_statistics", settings.getBoolean(DatabaseProperties.PROP_HIBERNATE_GENERATE_STATISTICS)); 158 props.put("hibernate.hbm2ddl.auto", "validate"); 159 props.put(Environment.CONNECTION_PROVIDER, CustomHibernateConnectionProvider.class.getName()); 160 161 String schema = getSchema(); 162 if (StringUtils.isNotBlank(schema)) { 163 props.put("hibernate.default_schema", schema); 164 } 165 return props; 166 } 167 168 public final DataSource getDataSource() { 169 return datasource; 170 } 171 172 public final Properties getProperties() { 173 return properties; 174 } 175 176 protected void doCompleteProperties(Properties properties) { 177 178 } 179 180 static void completeProperties(Settings settings, Properties properties, String prefix) { 181 List<String> jdbcKeys = settings.getKeysStartingWith(prefix); 182 for (String jdbcKey : jdbcKeys) { 183 String value = settings.getString(jdbcKey); 184 properties.setProperty(jdbcKey, value); 185 } 186 } 187 188 static Properties extractCommonsDbcpProperties(Properties properties) { 189 Properties result = new Properties(); 190 for (Map.Entry<Object, Object> entry : properties.entrySet()) { 191 String key = (String) entry.getKey(); 192 if (StringUtils.startsWith(key, "sonar.jdbc.")) { 193 result.setProperty(StringUtils.removeStart(key, "sonar.jdbc."), (String) entry.getValue()); 194 } 195 } 196 197 // This property is required by the Ruby Oracle enhanced adapter. 198 // It directly uses the Connection implementation provided by the Oracle driver 199 result.setProperty("accessToUnderlyingConnectionAllowed", "true"); 200 return result; 201 } 202 203 static String getSchemaPropertyValue(Properties props, String deprecatedKey) { 204 String value = props.getProperty("sonar.jdbc.schema"); 205 if (StringUtils.isBlank(value) && deprecatedKey != null) { 206 value = props.getProperty(deprecatedKey); 207 } 208 return StringUtils.isNotBlank(value) ? value : null; 209 } 210 211 private static void completeDefaultProperties(Properties props) { 212 completeDefaultProperty(props, DatabaseProperties.PROP_DRIVER, props.getProperty(DatabaseProperties.PROP_DRIVER_DEPRECATED)); 213 completeDefaultProperty(props, DatabaseProperties.PROP_URL, DatabaseProperties.PROP_URL_DEFAULT_VALUE); 214 completeDefaultProperty(props, DatabaseProperties.PROP_USER, props.getProperty(DatabaseProperties.PROP_USER_DEPRECATED, DatabaseProperties.PROP_USER_DEFAULT_VALUE)); 215 completeDefaultProperty(props, DatabaseProperties.PROP_PASSWORD, DatabaseProperties.PROP_PASSWORD_DEFAULT_VALUE); 216 completeDefaultProperty(props, DatabaseProperties.PROP_HIBERNATE_HBM2DLL, "validate"); 217 } 218 219 private static void completeDefaultProperty(Properties props, String key, String defaultValue) { 220 if (props.getProperty(key) == null && defaultValue != null) { 221 props.setProperty(key, defaultValue); 222 } 223 } 224 }