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    }