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.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.jpa.dialect.Dialect;
031    import org.sonar.jpa.dialect.DialectRepository;
032    import org.sonar.jpa.session.CustomHibernateConnectionProvider;
033    
034    import javax.sql.DataSource;
035    import java.sql.SQLException;
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    
051      public DefaultDatabase(Settings settings) {
052        this.settings = settings;
053      }
054    
055      public final DefaultDatabase start() {
056        try {
057          doBeforeStart();
058    
059          Properties properties = getProperties();
060          dialect = initDialect(properties);
061          datasource = initDatasource(properties);
062          return this;
063    
064        } catch (Exception e) {
065          throw new IllegalStateException("Fail to connect to database", e);
066        }
067      }
068    
069      BasicDataSource initDatasource(Properties properties) throws Exception {
070        LOG.info("Create JDBC datasource");
071        return (BasicDataSource) BasicDataSourceFactory.createDataSource(extractCommonsDbcpProperties(properties));
072      }
073    
074      Dialect initDialect(Properties properties) {
075        Dialect result = DialectRepository.find(properties.getProperty("sonar.jdbc.dialect"), properties.getProperty("sonar.jdbc.url"));
076        if (result != null && "derby".equals(result.getId())) {
077          LoggerFactory.getLogger(getClass()).warn("Derby database should be used for evaluation purpose only");
078        }
079        return result;
080      }
081    
082      protected void doBeforeStart() {
083      }
084    
085      public final DefaultDatabase stop() {
086        doBeforeStop();
087        if (datasource != null) {
088          try {
089            datasource.close();
090          } catch (SQLException e) {
091            throw new IllegalStateException("Fail to stop JDBC connection pool", e);
092          }
093        }
094        return this;
095      }
096    
097      protected void doBeforeStop() {
098    
099      }
100    
101      public final Dialect getDialect() {
102        return dialect;
103      }
104    
105      public Properties getHibernateProperties() {
106        Properties props = new Properties();
107    
108        List<String> hibernateKeys = settings.getKeysStartingWith("sonar.hibernate.");
109        for (String hibernateKey : hibernateKeys) {
110          props.put(StringUtils.removeStart(hibernateKey, "sonar."), settings.getString(hibernateKey));
111        }
112        props.put(Environment.DIALECT, getDialect().getHibernateDialectClass().getName());
113        props.put("hibernate.generate_statistics", settings.getBoolean(DatabaseProperties.PROP_HIBERNATE_GENERATE_STATISTICS));
114        props.put("hibernate.hbm2ddl.auto", "validate");
115        props.put(Environment.CONNECTION_PROVIDER, CustomHibernateConnectionProvider.class.getName());
116        return props;
117      }
118    
119      public final DataSource getDataSource() {
120        return datasource;
121      }
122    
123      public final Properties getProperties() {
124        Properties properties = new Properties();
125        completeProperties(settings, properties, "sonar.jdbc.");
126        completeProperties(settings, properties, "sonar.hibernate.");
127        completeDefaultProperties(properties);
128        doCompleteProperties(properties);
129        return properties;
130      }
131    
132      protected void doCompleteProperties(Properties properties) {
133    
134      }
135    
136      static void completeProperties(Settings settings, Properties properties, String prefix) {
137        List<String> jdbcKeys = settings.getKeysStartingWith(prefix);
138        for (String jdbcKey : jdbcKeys) {
139          String value = settings.getString(jdbcKey);
140          properties.setProperty(jdbcKey, value);
141        }
142      }
143    
144      static Properties extractCommonsDbcpProperties(Properties properties) {
145        Properties result = new Properties();
146        for (Map.Entry<Object, Object> entry : properties.entrySet()) {
147          String key = (String) entry.getKey();
148          if (StringUtils.startsWith(key, "sonar.jdbc.")) {
149            result.setProperty(StringUtils.removeStart(key, "sonar.jdbc."), (String) entry.getValue());
150          }
151        }
152    
153        // This property is required by the Ruby Oracle enhanced adapter.
154        // It directly uses the Connection implementation provided by the Oracle driver
155        result.setProperty("accessToUnderlyingConnectionAllowed", "true");
156        return result;
157      }
158    
159      private static void completeDefaultProperties(Properties props) {
160        completeDefaultProperty(props, DatabaseProperties.PROP_DRIVER, props.getProperty(DatabaseProperties.PROP_DRIVER_DEPRECATED, DatabaseProperties.PROP_DRIVER_DEFAULT_VALUE));
161        completeDefaultProperty(props, DatabaseProperties.PROP_URL, DatabaseProperties.PROP_URL_DEFAULT_VALUE);
162        completeDefaultProperty(props, DatabaseProperties.PROP_USER, props.getProperty(DatabaseProperties.PROP_USER_DEPRECATED, DatabaseProperties.PROP_USER_DEFAULT_VALUE));
163        completeDefaultProperty(props, DatabaseProperties.PROP_PASSWORD, DatabaseProperties.PROP_PASSWORD_DEFAULT_VALUE);
164        completeDefaultProperty(props, DatabaseProperties.PROP_HIBERNATE_HBM2DLL, "validate");
165      }
166    
167      private static void completeDefaultProperty(Properties props, String key, String defaultValue) {
168        if (props.getProperty(key) == null) {
169          props.setProperty(key, defaultValue);
170        }
171      }
172    }