001 /*
002 * Sonar, open source software quality management tool.
003 * Copyright (C) 2008-2012 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 }