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 */
020package org.sonar.core.persistence;
021
022import org.apache.commons.dbcp.BasicDataSource;
023import org.apache.commons.dbcp.BasicDataSourceFactory;
024import org.apache.commons.lang.StringUtils;
025import org.hibernate.cfg.Environment;
026import org.slf4j.Logger;
027import org.slf4j.LoggerFactory;
028import org.sonar.api.config.Settings;
029import org.sonar.api.database.DatabaseProperties;
030import org.sonar.core.persistence.dialect.*;
031import org.sonar.jpa.session.CustomHibernateConnectionProvider;
032
033import javax.sql.DataSource;
034import java.sql.SQLException;
035import java.util.Arrays;
036import java.util.List;
037import java.util.Map;
038import java.util.Properties;
039
040/**
041 * @since 2.12
042 */
043public 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}