org.sonar.db.DefaultDatabase.java Source code

Java tutorial

Introduction

Here is the source code for org.sonar.db.DefaultDatabase.java

Source

/*
 * SonarQube
 * Copyright (C) 2009-2017 SonarSource SA
 * mailto:info AT sonarsource DOT com
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package org.sonar.db;

import ch.qos.logback.classic.Level;
import com.google.common.annotations.VisibleForTesting;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.commons.dbcp.BasicDataSource;
import org.apache.commons.dbcp.BasicDataSourceFactory;
import org.apache.commons.dbutils.DbUtils;
import org.apache.commons.lang.StringUtils;
import org.sonar.api.config.Settings;
import org.sonar.api.database.DatabaseProperties;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import org.sonar.db.dialect.Dialect;
import org.sonar.db.dialect.DialectUtils;
import org.sonar.db.profiling.NullConnectionInterceptor;
import org.sonar.db.profiling.ProfiledConnectionInterceptor;
import org.sonar.db.profiling.ProfiledDataSource;
import org.sonar.process.logging.LogbackHelper;

import static java.lang.String.format;

/**
 * @since 2.12
 */
public class DefaultDatabase implements Database {

    private static final Logger LOG = Loggers.get(Database.class);

    private static final String DEFAULT_URL = "jdbc:h2:tcp://localhost/sonar";
    private static final String SONAR_JDBC = "sonar.jdbc.";
    private static final String SONAR_JDBC_DIALECT = "sonar.jdbc.dialect";
    private static final String SONAR_JDBC_URL = "sonar.jdbc.url";

    private final LogbackHelper logbackHelper;
    private final Settings settings;
    private ProfiledDataSource datasource;
    private Dialect dialect;
    private Properties properties;

    public DefaultDatabase(LogbackHelper logbackHelper, Settings settings) {
        this.logbackHelper = logbackHelper;
        this.settings = settings;
    }

    @Override
    public void start() {
        try {
            initSettings();
            initDataSource();
            checkConnection();

        } catch (Exception e) {
            throw new IllegalStateException("Fail to connect to database", e);
        }
    }

    @VisibleForTesting
    void initSettings() {
        properties = new Properties();
        completeProperties(settings, properties, SONAR_JDBC);
        completeDefaultProperty(properties, DatabaseProperties.PROP_URL, DEFAULT_URL);
        doCompleteProperties(properties);

        dialect = DialectUtils.find(properties.getProperty(SONAR_JDBC_DIALECT),
                properties.getProperty(SONAR_JDBC_URL));
        properties.setProperty(DatabaseProperties.PROP_DRIVER, dialect.getDefaultDriverClassName());
    }

    private void initDataSource() throws Exception {
        // but it's correctly caught by start()
        LOG.info("Create JDBC data source for {}",
                properties.getProperty(DatabaseProperties.PROP_URL, DEFAULT_URL));
        BasicDataSource basicDataSource = (BasicDataSource) BasicDataSourceFactory
                .createDataSource(extractCommonsDbcpProperties(properties));
        datasource = new ProfiledDataSource(basicDataSource, NullConnectionInterceptor.INSTANCE);
        datasource.setConnectionInitSqls(dialect.getConnectionInitStatements());
        datasource.setValidationQuery(dialect.getValidationQuery());
        enableSqlLogging(datasource, logbackHelper.getLoggerLevel("sql") == Level.TRACE);
    }

    private void checkConnection() {
        Connection connection = null;
        try {
            connection = datasource.getConnection();
        } catch (SQLException e) {
            throw new IllegalStateException(
                    "Can not connect to database. Please check connectivity and settings (see the properties prefixed by 'sonar.jdbc.').",
                    e);
        } finally {
            DbUtils.closeQuietly(connection);
        }
    }

    @Override
    public void stop() {
        if (datasource != null) {
            try {
                datasource.close();
            } catch (SQLException e) {
                throw new IllegalStateException("Fail to stop JDBC connection pool", e);
            }
        }
    }

    @Override
    public final Dialect getDialect() {
        return dialect;
    }

    @Override
    public final DataSource getDataSource() {
        return datasource;
    }

    public final Properties getProperties() {
        return properties;
    }

    @Override
    public void enableSqlLogging(boolean enable) {
        enableSqlLogging(datasource, enable);
    }

    private static void enableSqlLogging(ProfiledDataSource ds, boolean enable) {
        ds.setConnectionInterceptor(
                enable ? ProfiledConnectionInterceptor.INSTANCE : NullConnectionInterceptor.INSTANCE);
    }

    /**
     * Override this method to add JDBC properties at runtime
     */
    protected void doCompleteProperties(Properties properties) {
        // open-close principle
    }

    private static void completeProperties(Settings settings, Properties properties, String prefix) {
        List<String> jdbcKeys = settings.getKeysStartingWith(prefix);
        for (String jdbcKey : jdbcKeys) {
            String value = settings.getString(jdbcKey);
            properties.setProperty(jdbcKey, value);
        }
    }

    @VisibleForTesting
    static Properties extractCommonsDbcpProperties(Properties properties) {
        Properties result = new Properties();
        for (Map.Entry<Object, Object> entry : properties.entrySet()) {
            String key = (String) entry.getKey();
            if (StringUtils.startsWith(key, SONAR_JDBC)) {
                result.setProperty(StringUtils.removeStart(key, SONAR_JDBC), (String) entry.getValue());
            }
        }
        return result;
    }

    private static void completeDefaultProperty(Properties props, String key, String defaultValue) {
        if (props.getProperty(key) == null) {
            props.setProperty(key, defaultValue);
        }
    }

    @Override
    public String toString() {
        return format("Database[%s]", properties != null ? properties.getProperty(SONAR_JDBC_URL) : "?");
    }
}