diff options
author | David Gageot <david@gageot.net> | 2012-10-18 17:47:19 +0200 |
---|---|---|
committer | David Gageot <david@gageot.net> | 2012-10-23 11:48:21 +0200 |
commit | a505a98b4d1ad33c80b939ae6fed12043e635ded (patch) | |
tree | 939b5de8fce6a3f5e3ad150c3ca814c429d462b6 /sonar-server | |
parent | 9f509a9600f0dafb62d8c9787d94a910762de385 (diff) | |
download | sonarqube-a505a98b4d1ad33c80b939ae6fed12043e635ded.tar.gz sonarqube-a505a98b4d1ad33c80b939ae6fed12043e635ded.zip |
SONAR-3895 Local mode
Diffstat (limited to 'sonar-server')
7 files changed, 326 insertions, 50 deletions
diff --git a/sonar-server/pom.xml b/sonar-server/pom.xml index 6a6b4624d31..a3b6e9e349b 100644 --- a/sonar-server/pom.xml +++ b/sonar-server/pom.xml @@ -69,6 +69,11 @@ <artifactId>commons-configuration</artifactId> </dependency> <dependency> + <groupId>mysql</groupId> + <artifactId>mysql-connector-java</artifactId> + <version>5.1.18</version> + </dependency> + <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> </dependency> diff --git a/sonar-server/src/main/java/org/sonar/server/database/LocalDatabaseFactory.java b/sonar-server/src/main/java/org/sonar/server/database/LocalDatabaseFactory.java new file mode 100644 index 00000000000..7a08c7e17ac --- /dev/null +++ b/sonar-server/src/main/java/org/sonar/server/database/LocalDatabaseFactory.java @@ -0,0 +1,199 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2012 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.server.database; + +import org.apache.commons.dbcp.BasicDataSource; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.ServerComponent; +import org.sonar.api.utils.SonarException; +import org.sonar.core.persistence.Database; +import org.sonar.core.persistence.DdlUtils; + +import javax.sql.DataSource; + +import java.io.File; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +public class LocalDatabaseFactory implements ServerComponent { + private static final Logger LOG = LoggerFactory.getLogger(LocalDatabaseFactory.class); + + private static final String H2_DIALECT = "h2"; + private static final String H2_DRIVER = "org.h2.Driver"; + private static final String H2_URL = "jdbc:h2:"; + private static final String H2_USER = "sonar"; + private static final String H2_PASSWORD = "sonar"; + + private Database database; + + public LocalDatabaseFactory(Database database) { + this.database = database; + } + + public String createDatabaseForLocalMode() throws SQLException { + String name = "/tmp/" + System.nanoTime(); + + DataSource source = database.getDataSource(); + BasicDataSource destination = dataSource(H2_DRIVER, H2_USER, H2_PASSWORD, H2_URL + name); + + create(destination, H2_DIALECT); + + copyTable(source, destination, "PROPERTIES", "SELECT * FROM PROPERTIES WHERE (USER_ID IS NULL) AND (RESOURCE_ID IS NULL)"); + copyTable(source, destination, "RULES_PROFILES", "SELECT * FROM RULES_PROFILES"); + copyTable(source, destination, "RULES", "SELECT * FROM RULES"); + copyTable(source, destination, "RULES_PARAMETERS", "SELECT * FROM RULES_PARAMETERS"); + copyTable(source, destination, "ACTIVE_RULES", "SELECT * FROM ACTIVE_RULES"); + copyTable(source, destination, "ACTIVE_RULE_PARAMETERS", "SELECT * FROM ACTIVE_RULE_PARAMETERS"); + copyTable(source, destination, "METRICS", "SELECT * FROM METRICS"); + + destination.close(); + + return new File(name + ".h2.db").getAbsolutePath(); + } + + private void copyTable(DataSource source, DataSource dest, String table, String query) throws SQLException { + LOG.info("Copy table " + table); + + int colCount = getColumnCount(source, table); + + truncate(dest, table); + + Connection sourceConnection = null; + Statement sourceStatement = null; + ResultSet sourceResultSet = null; + Connection destConnection = null; + ResultSet destResultSet = null; + try { + sourceConnection = source.getConnection(); + sourceStatement = sourceConnection.createStatement(); + sourceResultSet = sourceStatement.executeQuery(query); + + destConnection = dest.getConnection(); + destConnection.setAutoCommit(false); + + PreparedStatement destStatement = destConnection.prepareStatement("INSERT INTO " + table + " VALUES(" + StringUtils.repeat("?", ",", colCount) + ")"); + while (sourceResultSet.next()) { + for (int col = 1; col <= colCount; col++) { + Object value = sourceResultSet.getObject(col); + destStatement.setObject(col, value); + } + destStatement.addBatch(); + } + + destStatement.executeBatch(); + destConnection.commit(); + destStatement.close(); + } finally { + closeQuietly(destResultSet); + closeQuietly(destConnection); + closeQuietly(sourceResultSet); + closeQuietly(sourceStatement); + closeQuietly(sourceConnection); + } + } + + private int getColumnCount(DataSource dataSource, String table) throws SQLException { + Connection connection = null; + ResultSet metaData = null; + try { + connection = dataSource.getConnection(); + metaData = connection.getMetaData().getColumns(null, null, table, null); + + int nbColumns = 0; + while (metaData.next()) { + nbColumns++; + } + + return nbColumns; + } finally { + closeQuietly(metaData); + closeQuietly(connection); + } + } + + private void truncate(DataSource dataSource, String table) throws SQLException { + Connection connection = null; + Statement statement = null; + try { + connection = dataSource.getConnection(); + statement = connection.createStatement(); + statement.executeUpdate("TRUNCATE TABLE " + table); + } finally { + closeQuietly(statement); + closeQuietly(connection); + } + } + + private BasicDataSource dataSource(String driver, String user, String password, String url) { + BasicDataSource dataSource = new BasicDataSource(); + dataSource.setDriverClassName(driver); + dataSource.setUsername(user); + dataSource.setPassword(password); + dataSource.setUrl(url); + return dataSource; + } + + public void create(DataSource dataSource, String dialect) throws SQLException { + Connection connection = null; + try { + connection = dataSource.getConnection(); + DdlUtils.createSchema(connection, dialect); + } catch (SQLException e) { + throw new SonarException("Fail to create local database schema", e); + } finally { + closeQuietly(connection); + } + } + + private void closeQuietly(Connection connection) { + if (connection != null) { + try { + connection.close(); + } catch (SQLException e) { + // ignore + } + } + } + + private void closeQuietly(Statement statement) { + if (statement != null) { + try { + statement.close(); + } catch (SQLException e) { + // ignore + } + } + } + + private void closeQuietly(ResultSet resultSet) { + if (resultSet != null) { + try { + resultSet.close(); + } catch (SQLException e) { + // ignore + } + } + } +} diff --git a/sonar-server/src/main/java/org/sonar/server/platform/Platform.java b/sonar-server/src/main/java/org/sonar/server/platform/Platform.java index 83ae16377ae..88b9ae05c03 100644 --- a/sonar-server/src/main/java/org/sonar/server/platform/Platform.java +++ b/sonar-server/src/main/java/org/sonar/server/platform/Platform.java @@ -47,7 +47,11 @@ import org.sonar.core.measure.MeasureFilterEngine; import org.sonar.core.measure.MeasureFilterExecutor; import org.sonar.core.metric.DefaultMetricFinder; import org.sonar.core.notification.DefaultNotificationManager; -import org.sonar.core.persistence.*; +import org.sonar.core.persistence.DaoUtils; +import org.sonar.core.persistence.DatabaseMigrator; +import org.sonar.core.persistence.DatabaseVersion; +import org.sonar.core.persistence.DefaultDatabase; +import org.sonar.core.persistence.MyBatis; import org.sonar.core.qualitymodel.DefaultModelFinder; import org.sonar.core.rule.DefaultRuleFinder; import org.sonar.core.user.DefaultUserFinder; @@ -64,14 +68,35 @@ import org.sonar.server.charts.ChartFactory; import org.sonar.server.configuration.Backup; import org.sonar.server.configuration.ProfilesManager; import org.sonar.server.database.EmbeddedDatabaseFactory; +import org.sonar.server.database.LocalDatabaseFactory; import org.sonar.server.notifications.NotificationService; import org.sonar.server.notifications.reviews.ReviewsNotificationManager; -import org.sonar.server.plugins.*; +import org.sonar.server.plugins.ApplicationDeployer; +import org.sonar.server.plugins.DefaultServerPluginRepository; +import org.sonar.server.plugins.PluginDeployer; +import org.sonar.server.plugins.PluginDownloader; +import org.sonar.server.plugins.ServerExtensionInstaller; +import org.sonar.server.plugins.UpdateCenterClient; +import org.sonar.server.plugins.UpdateCenterMatrixFactory; import org.sonar.server.qualitymodel.DefaultModelManager; import org.sonar.server.rules.ProfilesConsole; import org.sonar.server.rules.RulesConsole; -import org.sonar.server.startup.*; -import org.sonar.server.ui.*; +import org.sonar.server.startup.DeleteDeprecatedMeasures; +import org.sonar.server.startup.GeneratePluginIndex; +import org.sonar.server.startup.GwtPublisher; +import org.sonar.server.startup.JdbcDriverDeployer; +import org.sonar.server.startup.RegisterMetrics; +import org.sonar.server.startup.RegisterNewDashboards; +import org.sonar.server.startup.RegisterNewFilters; +import org.sonar.server.startup.RegisterNewProfiles; +import org.sonar.server.startup.RegisterQualityModels; +import org.sonar.server.startup.RegisterRules; +import org.sonar.server.startup.ServerMetadataPersister; +import org.sonar.server.ui.CodeColorizers; +import org.sonar.server.ui.JRubyI18n; +import org.sonar.server.ui.PageDecorations; +import org.sonar.server.ui.SecurityRealmFactory; +import org.sonar.server.ui.Views; import javax.servlet.ServletContext; @@ -155,6 +180,7 @@ public final class Platform { rootContainer.addSingleton(I18nManager.class); rootContainer.addSingleton(RuleI18nManager.class); rootContainer.addSingleton(GwtI18n.class); + rootContainer.addSingleton(LocalDatabaseFactory.class); rootContainer.startComponents(); } @@ -223,6 +249,7 @@ public final class Platform { servicesContainer.addSingleton(MeasureFilterDecoder.class); servicesContainer.addSingleton(MeasureFilterExecutor.class); servicesContainer.addSingleton(MeasureFilterEngine.class); + servicesContainer.addSingleton(LocalDatabaseFactory.class); // Notifications servicesContainer.addSingleton(EmailSettings.class); diff --git a/sonar-server/src/main/webapp/WEB-INF/app/controllers/api/synchro_controller.rb b/sonar-server/src/main/webapp/WEB-INF/app/controllers/api/synchro_controller.rb new file mode 100644 index 00000000000..d3cd927e6bf --- /dev/null +++ b/sonar-server/src/main/webapp/WEB-INF/app/controllers/api/synchro_controller.rb @@ -0,0 +1,37 @@ +# +# Sonar, entreprise quality control tool. +# Copyright (C) 2008-2012 SonarSource +# mailto:contact AT sonarsource DOT com +# +# Sonar 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. +# +# Sonar 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 Sonar; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 +# + +require "json" + +class Api::SynchroController < Api::ApiController + + # curl http://localhost:9000/api/synchro -v + def index + database_factory = java_facade.getCoreComponentByClassname('org.sonar.server.database.LocalDatabaseFactory') + + path = database_factory.createDatabaseForLocalMode() + + hash = {:path => path} + + respond_to do |format| + format.json { render :json => jsonp(hash) } + end + end +end diff --git a/sonar-server/src/main/webapp/WEB-INF/app/models/active_rule.rb b/sonar-server/src/main/webapp/WEB-INF/app/models/active_rule.rb index f2613920847..b7258fa7509 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/models/active_rule.rb +++ b/sonar-server/src/main/webapp/WEB-INF/app/models/active_rule.rb @@ -43,11 +43,11 @@ class ActiveRule < ActiveRecord::Base def warning? Sonar::RulePriority::minor?(failure_level) end - + def info? - Sonar::RulePriority::info?(failure_level) + Sonar::RulePriority::info?(failure_level) end - + def minor? Sonar::RulePriority::minor?(failure_level) end @@ -55,11 +55,11 @@ class ActiveRule < ActiveRecord::Base def major? Sonar::RulePriority::major?(failure_level) end - + def critical? Sonar::RulePriority::critical?(failure_level) end - + def blocker? Sonar::RulePriority::blocker?(failure_level) end @@ -98,8 +98,8 @@ class ActiveRule < ActiveRecord::Base new_active_rule = ActiveRule.new(:rule => rule, :failure_level => failure_level) self.active_rule_parameters.each do |active_rule_parameter| new_active_rule.active_rule_parameters << active_rule_parameter.copy - end - new_active_rule + end + new_active_rule end def inherited? diff --git a/sonar-server/src/main/webapp/WEB-INF/app/models/profile.rb b/sonar-server/src/main/webapp/WEB-INF/app/models/profile.rb index a9eb887503f..57012bf4ae1 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/models/profile.rb +++ b/sonar-server/src/main/webapp/WEB-INF/app/models/profile.rb @@ -98,9 +98,9 @@ class Profile < ActiveRecord::Base def count_overriding_rules @count_overriding_rules||= - begin - active_rules.count(:conditions => ['inheritance=?', 'OVERRIDES']) - end + begin + active_rules.count(:conditions => ['inheritance=?', 'OVERRIDES']) + end end def inherited? @@ -109,13 +109,13 @@ class Profile < ActiveRecord::Base def parent @parent||= - begin - if parent_name.present? - Profile.find(:first, :conditions => ['language=? and name=?', language, parent_name]) - else - nil - end + begin + if parent_name.present? + Profile.find(:first, :conditions => ['language=? and name=?', language, parent_name]) + else + nil end + end end def children @@ -131,14 +131,14 @@ class Profile < ActiveRecord::Base def ancestors @ancestors ||= - begin - array=[] - if parent - array<<parent - array.concat(parent.ancestors) - end - array + begin + array=[] + if parent + array<<parent + array.concat(parent.ancestors) end + array + end end def import_configuration(importer_key, file) @@ -186,11 +186,11 @@ class Profile < ActiveRecord::Base def projects @projects ||= - begin - Project.find(:all, - :joins => 'LEFT JOIN properties ON properties.resource_id = projects.id', - :conditions => ['properties.resource_id is not null and properties.prop_key=? and properties.text_value like ?', "sonar.profile.#{language}", name]) - end + begin + Project.find(:all, + :joins => 'LEFT JOIN properties ON properties.resource_id = projects.id', + :conditions => ['properties.resource_id is not null and properties.prop_key=? and properties.text_value like ?', "sonar.profile.#{language}", name]) + end end def sorted_projects @@ -207,6 +207,15 @@ class Profile < ActiveRecord::Base @projects = nil end + def to_hash_json + { + :name => name, + :language => language, + :version => version, + :rules => active_rules.map { |active| active.rule.to_hash_json(self, active) } + } + end + def self.reset_default_profile_for_project_id(lang, project_id) Property.clear("sonar.profile.#{lang}", project_id) end diff --git a/sonar-server/src/main/webapp/WEB-INF/app/models/rule.rb b/sonar-server/src/main/webapp/WEB-INF/app/models/rule.rb index 8b55ca33938..bba01c56a70 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/models/rule.rb +++ b/sonar-server/src/main/webapp/WEB-INF/app/models/rule.rb @@ -94,11 +94,11 @@ class Rule < ActiveRecord::Base def description @l10n_description ||= - begin - result = Java::OrgSonarServerUi::JRubyFacade.getInstance().getRuleDescription(I18n.locale, repository_key, plugin_rule_key) - result = read_attribute(:description) unless result - result - end + begin + result = Java::OrgSonarServerUi::JRubyFacade.getInstance().getRuleDescription(I18n.locale, repository_key, plugin_rule_key) + result = read_attribute(:description) unless result + result + end end def description=(value) @@ -139,7 +139,7 @@ class Rule < ActiveRecord::Base def self.manual_rules rules = Rule.find(:all, :conditions => ['enabled=? and plugin_name=?', true, MANUAL_REPOSITORY_KEY]) - Api::Utils.insensitive_sort(rules) {|rule| rule.name} + Api::Utils.insensitive_sort(rules) { |rule| rule.name } end def self.manual_rule(id) @@ -154,7 +154,7 @@ class Rule < ActiveRecord::Base rule = Rule.find(:first, :conditions => {:enabled => true, :plugin_name => MANUAL_REPOSITORY_KEY, :plugin_rule_key => key}) if rule==nil && create_if_not_found description = options[:description] || Api::Utils.message('manual_rules.should_provide_real_description') - rule = Rule.create!(:enabled => true, :plugin_name => MANUAL_REPOSITORY_KEY, :plugin_rule_key => key, + rule = Rule.create!(:enabled => true, :plugin_name => MANUAL_REPOSITORY_KEY, :plugin_rule_key => key, :name => rule_id_or_name, :description => description) end end @@ -166,23 +166,22 @@ class Rule < ActiveRecord::Base checksum = nil level = Sonar::RulePriority.id(options['severity']||Severity::MAJOR) RuleFailure.create!( - :snapshot => resource.last_snapshot, - :rule => self, - :failure_level => level, - :message => options['message'], - :cost => (options['cost'] ? options['cost'].to_f : nil), - :switched_off => false, - :line => line, - :checksum => checksum) + :snapshot => resource.last_snapshot, + :rule => self, + :failure_level => level, + :message => options['message'], + :cost => (options['cost'] ? options['cost'].to_f : nil), + :switched_off => false, + :line => line, + :checksum => checksum) end - def to_hash_json(profile) + def to_hash_json(profile, active_rule = nil) json = {'title' => name, 'key' => key, 'plugin' => plugin_name, 'config_key' => config_key} json['description'] = description if description - active_rule = nil if profile - active_rule = profile.active_by_rule_id(id) + active_rule = active_rule || profile.active_by_rule_id(id) if active_rule json['priority'] = active_rule.priority_text json['status'] = 'ACTIVE' |