aboutsummaryrefslogtreecommitdiffstats
path: root/sonar-plugin-api
diff options
context:
space:
mode:
authorEvgeny Mandrikov <mandrikov@gmail.com>2011-05-27 02:07:33 +0400
committerEvgeny Mandrikov <mandrikov@gmail.com>2011-05-27 02:54:10 +0400
commit2dbed652688d87b303f7821ea619ed36ba654a19 (patch)
treecbf627ca9957741a70eb4dc55ca6210974986b32 /sonar-plugin-api
parent880d215a9fbafec25f7d6172b22e8dd0915a0c9a (diff)
downloadsonarqube-2dbed652688d87b303f7821ea619ed36ba654a19.tar.gz
sonarqube-2dbed652688d87b303f7821ea619ed36ba654a19.zip
SONAR-1922 Add a kind of version control for quality profiles
Apply patch, which was contributed by Julien Henry: * Following algorithm was implemented: Every profile starts with version=1 and used=false. As soon as there is an analysis of a project, the involved profile is set to used=true. Every modification to a quality profile (activation, deactivation or modification of rule) is logged in DB in dedicated tables. When a modification is done on a profile that is used=true, then version number is increased and profile is set to used=false. * Introduced new metric to store profile version, which was used during analysis. * If profile for project is different than the one used during previous analysis, then event would be created. * Introduced new tab 'changelog' for profiles. Following fixes were applied on original patch: * Index name limited to 30 characters in Oracle DB, so names were reduced. * Field ActiveRuleChange.profileVersion never read locally, because ruby read it directly from DB, so getter added. * Direction doesn't make sense for 'profile_version' metric, so was removed. * Fixed ProfileEventsSensor: it seems that TimeMachine not guarantee that the order of measures would be the same as in query, so we should perform two sequential queries. * Fixed handling of null values during migration.
Diffstat (limited to 'sonar-plugin-api')
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/batch/Event.java5
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/batch/TimeMachineQuery.java8
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java6
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/profiles/RulesProfile.java24
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/rules/ActiveRuleChange.java213
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/rules/ActiveRuleParamChange.java97
-rw-r--r--sonar-plugin-api/src/test/java/org/sonar/api/profiles/RulesProfileTest.java6
7 files changed, 359 insertions, 0 deletions
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/Event.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/Event.java
index b1a7f1e19b9..fa2263548d6 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/Event.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/Event.java
@@ -35,6 +35,7 @@ import javax.persistence.*;
public class Event extends BaseIdentifiable {
public static final String CATEGORY_VERSION = "Version";
public static final String CATEGORY_ALERT = "Alert";
+ public static final String CATEGORY_PROFILE = "Profile";
@Column(name = "name", updatable = true, nullable = true, length = 50)
private String name;
@@ -121,6 +122,10 @@ public class Event extends BaseIdentifiable {
return CATEGORY_VERSION.equalsIgnoreCase(category);
}
+ public boolean isProfileCategory() {
+ return CATEGORY_PROFILE.equalsIgnoreCase(category);
+ }
+
public Date getDate() {
return date;
}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/TimeMachineQuery.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/TimeMachineQuery.java
index 13f8818d0bc..20b0ee861c0 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/TimeMachineQuery.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/TimeMachineQuery.java
@@ -19,6 +19,8 @@
*/
package org.sonar.api.batch;
+import org.apache.commons.lang.builder.EqualsBuilder;
+
import com.google.common.collect.Lists;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.sonar.api.measures.Metric;
@@ -231,4 +233,10 @@ public class TimeMachineQuery {
.append("to", to)
.toString();
}
+
+ @Override
+ public boolean equals(Object obj) {
+ return EqualsBuilder.reflectionEquals(this, obj);
+ }
+
}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java b/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java
index 4e265bac406..2cf3d598b40 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java
@@ -1141,6 +1141,12 @@ public final class CoreMetrics {
.setDomain(DOMAIN_GENERAL)
.create();
+ public static final String PROFILE_VERSION_KEY = "profile_version";
+ public static final Metric PROFILE_VERSION = new Metric.Builder(PROFILE_VERSION_KEY, "Profile version", Metric.ValueType.INT)
+ .setDescription("Selected quality profile version")
+ .setQualitative(false)
+ .setDomain(DOMAIN_GENERAL)
+ .create();
public static List<Metric> metrics = Lists.newLinkedList();
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/profiles/RulesProfile.java b/sonar-plugin-api/src/main/java/org/sonar/api/profiles/RulesProfile.java
index 5b34326daf6..e7f61d92df6 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/profiles/RulesProfile.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/profiles/RulesProfile.java
@@ -64,6 +64,9 @@ public class RulesProfile implements Cloneable {
@Column(name = "name", updatable = true, nullable = false)
private String name;
+ @Column(name = "version", updatable = true, nullable = false)
+ private int version = 1;
+
@Column(name = "default_profile", updatable = true, nullable = false)
private Boolean defaultProfile = Boolean.FALSE;
@@ -73,6 +76,9 @@ public class RulesProfile implements Cloneable {
@Column(name = "enabled", updatable = true, nullable = false)
private Boolean enabled = Boolean.TRUE;
+ @Column(name = "used_profile", updatable = true, nullable = false)
+ private Boolean used = Boolean.FALSE;
+
@Column(name = "language", updatable = true, nullable = false)
private String language;
@@ -135,6 +141,24 @@ public class RulesProfile implements Cloneable {
this.name = s;
return this;
}
+
+ public int getVersion() {
+ return version;
+ }
+
+ public RulesProfile setVersion(int version) {
+ this.version = version;
+ return this;
+ }
+
+ public Boolean getUsed() {
+ return used;
+ }
+
+ public RulesProfile setUsed(Boolean used) {
+ this.used = used;
+ return this;
+ }
/**
* @return the list of active rules
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/rules/ActiveRuleChange.java b/sonar-plugin-api/src/main/java/org/sonar/api/rules/ActiveRuleChange.java
new file mode 100644
index 00000000000..a3b95213220
--- /dev/null
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/rules/ActiveRuleChange.java
@@ -0,0 +1,213 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 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.api.rules;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import javax.persistence.FetchType;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.sonar.api.database.BaseIdentifiable;
+import org.sonar.api.profiles.RulesProfile;
+
+/**
+ * A class to map a RuleChange to the hibernate model
+ *
+ * @since 2.9
+ */
+@Entity
+@Table(name = "active_rule_changes")
+public class ActiveRuleChange extends BaseIdentifiable {
+
+ @Column(name = "user_login", updatable = false, nullable = false)
+ private String modifierLogin;
+
+ @ManyToOne(fetch = FetchType.EAGER)
+ @JoinColumn(name = "profile_id", updatable = false, nullable = false)
+ private RulesProfile rulesProfile;
+
+ @Column(name = "profile_version", updatable = false, nullable = false)
+ private int profileVersion;
+
+ @ManyToOne(fetch = FetchType.EAGER)
+ @JoinColumn(name = "rule_id", updatable = false, nullable = false)
+ private Rule rule;
+
+ @Column(name = "change_date", updatable = false, nullable = false)
+ private Date date;
+
+ /**
+ * true means rule was enabled
+ * false means rule was disabled
+ * null means rule stay enabled (another param was changed)
+ */
+ @Column(name = "enabled")
+ private Boolean enabled;
+
+ @Column(name = "old_severity", updatable = false, nullable = true)
+ @Enumerated(EnumType.ORDINAL)
+ private RulePriority oldSeverity;
+
+ @Column(name = "new_severity", updatable = false, nullable = true)
+ @Enumerated(EnumType.ORDINAL)
+ private RulePriority newSeverity;
+
+ @OneToMany(mappedBy = "activeRuleChange", fetch = FetchType.LAZY, cascade = { CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REMOVE })
+ private List<ActiveRuleParamChange> activeRuleParamChanges = new ArrayList<ActiveRuleParamChange>();
+
+ public ActiveRuleChange(String modifierLogin, RulesProfile profile, Rule rule) {
+ this.modifierLogin = modifierLogin;
+ this.rulesProfile = profile;
+ this.profileVersion = profile.getVersion();
+ this.rule = rule;
+ this.date = Calendar.getInstance().getTime();
+ }
+
+ public Rule getRule() {
+ return rule;
+ }
+
+ public RulePriority getOldSeverity() {
+ return oldSeverity;
+ }
+
+ public void setOldSeverity(RulePriority oldSeverity) {
+ this.oldSeverity = oldSeverity;
+ }
+
+ public RulePriority getNewSeverity() {
+ return newSeverity;
+ }
+
+ public void setNewSeverity(RulePriority newSeverity) {
+ this.newSeverity = newSeverity;
+ }
+
+ public RulesProfile getRulesProfile() {
+ return rulesProfile;
+ }
+
+ public int getProfileVersion() {
+ return profileVersion;
+ }
+
+ public String getRepositoryKey() {
+ return rule.getRepositoryKey();
+ }
+
+ /**
+ * @return the config key the changed rule belongs to
+ */
+ public String getConfigKey() {
+ return rule.getConfigKey();
+ }
+
+ /**
+ * @return the key of the changed rule
+ */
+ public String getRuleKey() {
+ return rule.getKey();
+ }
+
+ public Boolean isEnabled() {
+ return enabled;
+ }
+
+ public void setEnabled(Boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ public List<ActiveRuleParamChange> getActiveRuleParamChanges() {
+ return activeRuleParamChanges;
+ }
+
+ public String getModifierLogin() {
+ return modifierLogin;
+ }
+
+ public ActiveRuleChange setParameterChange(String key, String oldValue, String newValue) {
+ RuleParam ruleParameter = rule.getParam(key);
+ if (ruleParameter != null) {
+ activeRuleParamChanges.add(new ActiveRuleParamChange(this, ruleParameter, oldValue, newValue));
+ }
+ return this;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (obj == this) {
+ return true;
+ }
+ if (obj.getClass() != getClass()) {
+ return false;
+ }
+ ActiveRuleChange rhs = (ActiveRuleChange) obj;
+ return new EqualsBuilder()
+ .appendSuper(super.equals(obj))
+ .append(modifierLogin, rhs.modifierLogin)
+ .append(rulesProfile, rhs.rulesProfile)
+ .append(rule, rhs.rule)
+ .append(date, rhs.date)
+ .append(enabled, rhs.enabled)
+ .append(newSeverity, rhs.newSeverity)
+ .isEquals();
+ }
+
+ @Override
+ public int hashCode() {
+ return new HashCodeBuilder(41, 33)
+ .append(modifierLogin)
+ .append(rulesProfile)
+ .append(rule)
+ .append(date)
+ .append(enabled)
+ .append(newSeverity)
+ .toHashCode();
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .append("id", getId())
+ .append("profile", rulesProfile)
+ .append("rule", rule)
+ .append("modifier", modifierLogin)
+ .append("changed at", date)
+ .append("enabled", enabled)
+ .append("new severity", newSeverity)
+ .toString();
+ }
+
+}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/rules/ActiveRuleParamChange.java b/sonar-plugin-api/src/main/java/org/sonar/api/rules/ActiveRuleParamChange.java
new file mode 100644
index 00000000000..be7d2cef003
--- /dev/null
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/rules/ActiveRuleParamChange.java
@@ -0,0 +1,97 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 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.api.rules;
+
+import org.sonar.api.database.BaseIdentifiable;
+
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+
+import javax.persistence.*;
+
+/**
+ * @since 2.9
+ */
+@Entity
+@Table(name = "active_rule_param_changes")
+public class ActiveRuleParamChange extends BaseIdentifiable {
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "active_rule_change_id")
+ private ActiveRuleChange activeRuleChange;
+
+ @ManyToOne(fetch = FetchType.LAZY, optional = true)
+ @JoinColumn(name = "rules_parameter_id")
+ private RuleParam ruleParam;
+
+ @Column(name = "old_value", updatable = false, nullable = true, length = 4000)
+ private String oldValue;
+
+ @Column(name = "new_value", updatable = false, nullable = true, length = 4000)
+ private String newValue;
+
+ ActiveRuleParamChange(ActiveRuleChange activeRuleChange, RuleParam ruleParam, String oldValue, String newValue) {
+ this.activeRuleChange = activeRuleChange;
+ this.ruleParam = ruleParam;
+ this.oldValue = oldValue;
+ this.newValue = newValue;
+ }
+
+ public ActiveRuleChange getActiveRuleChange() {
+ return activeRuleChange;
+ }
+
+ public RuleParam getRuleParam() {
+ return ruleParam;
+ }
+
+ public String getOldValue() {
+ return oldValue;
+ }
+
+ public String getNewValue() {
+ return newValue;
+ }
+
+ public String getKey() {
+ return ruleParam.getKey();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof ActiveRuleParamChange)) {
+ return false;
+ }
+ if (this == obj) {
+ return true;
+ }
+ ActiveRuleParamChange other = (ActiveRuleParamChange) obj;
+ return new EqualsBuilder()
+ .append(getId(), other.getId()).isEquals();
+ }
+
+ @Override
+ public int hashCode() {
+ return new HashCodeBuilder(17, 57)
+ .append(getId())
+ .toHashCode();
+ }
+
+}
diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/profiles/RulesProfileTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/profiles/RulesProfileTest.java
index 944805f866c..dd364486646 100644
--- a/sonar-plugin-api/src/test/java/org/sonar/api/profiles/RulesProfileTest.java
+++ b/sonar-plugin-api/src/test/java/org/sonar/api/profiles/RulesProfileTest.java
@@ -54,4 +54,10 @@ public class RulesProfileTest {
profile.activateRule(rule, RulePriority.MINOR);
assertThat(profile.getActiveRule("repo", "key1").getSeverity(), is(RulePriority.MINOR));
}
+
+ @Test
+ public void defaultVersionIs1() {
+ RulesProfile profile = RulesProfile.create();
+ assertThat(profile.getVersion(), is(1));
+ }
}