From d79f46b3733b44ca62a1f92c576d68ddb28ec368 Mon Sep 17 00:00:00 2001 From: Godin Date: Fri, 17 Dec 2010 00:05:04 +0000 Subject: [PATCH] * SONAR-2048: Add an option to define parent in quality profile settings * SONAR-1722: Provide a simple inheritance mechanism on Quality Profiles ** current implementation is some kind of synchronization between profiles ** only one level of inheritance supported, this constraint exists on UI side and not handled on Java side ** inherited rule can't be modified --- .../org/sonar/api/profiles/RulesProfile.java | 28 +++++- .../java/org/sonar/api/rules/ActiveRule.java | 19 ++++ .../server/configuration/ProfilesManager.java | 92 ++++++++++++++++++- .../java/org/sonar/server/ui/JRubyFacade.java | 13 ++- .../rules_configuration_controller.rb | 33 ++++++- .../main/webapp/WEB-INF/app/models/profile.rb | 2 + .../WEB-INF/app/models/rules_profile.rb | 4 +- .../views/rules_configuration/_rule.html.erb | 6 +- .../rules_configuration/_rule_param.html.erb | 3 +- .../views/rules_configuration/index.html.erb | 15 +++ .../configuration/InheritedProfilesTest.java | 50 ++++++++++ .../configuration/ProfilesManagerTest.java | 19 ++-- .../shouldActivateInChildren-result.xml | 18 ++++ .../shouldActivateInChildren.xml | 15 +++ .../shouldChangeParent-result.xml | 21 +++++ .../shouldChangeParent.xml | 21 +++++ .../shouldDeactivateInChildren-result.xml | 12 +++ .../shouldDeactivateInChildren.xml | 14 +++ .../shouldRemoveParent-result.xml | 12 +++ .../shouldRemoveParent.xml | 14 +++ .../shouldSetParent-result.xml | 14 +++ .../InheritedProfilesTest/shouldSetParent.xml | 14 +++ 22 files changed, 420 insertions(+), 19 deletions(-) create mode 100644 sonar-server/src/test/java/org/sonar/server/configuration/InheritedProfilesTest.java create mode 100644 sonar-server/src/test/resources/org/sonar/server/configuration/InheritedProfilesTest/shouldActivateInChildren-result.xml create mode 100644 sonar-server/src/test/resources/org/sonar/server/configuration/InheritedProfilesTest/shouldActivateInChildren.xml create mode 100644 sonar-server/src/test/resources/org/sonar/server/configuration/InheritedProfilesTest/shouldChangeParent-result.xml create mode 100644 sonar-server/src/test/resources/org/sonar/server/configuration/InheritedProfilesTest/shouldChangeParent.xml create mode 100644 sonar-server/src/test/resources/org/sonar/server/configuration/InheritedProfilesTest/shouldDeactivateInChildren-result.xml create mode 100644 sonar-server/src/test/resources/org/sonar/server/configuration/InheritedProfilesTest/shouldDeactivateInChildren.xml create mode 100644 sonar-server/src/test/resources/org/sonar/server/configuration/InheritedProfilesTest/shouldRemoveParent-result.xml create mode 100644 sonar-server/src/test/resources/org/sonar/server/configuration/InheritedProfilesTest/shouldRemoveParent.xml create mode 100644 sonar-server/src/test/resources/org/sonar/server/configuration/InheritedProfilesTest/shouldSetParent-result.xml create mode 100644 sonar-server/src/test/resources/org/sonar/server/configuration/InheritedProfilesTest/shouldSetParent.xml 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 292d6c9d23f..06fe96c829c 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 @@ -82,9 +82,12 @@ public class RulesProfile implements Cloneable { @OneToMany(mappedBy = "rulesProfile", fetch = FetchType.LAZY) private List projects = new ArrayList(); - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "parent_id", updatable = true, nullable = true) - private RulesProfile parentProfile; + // @ManyToOne(fetch = FetchType.LAZY) + // @JoinColumn(name = "parent_id", updatable = true, nullable = true) + // private RulesProfile parentProfile; + + @Column(name = "parent_id", updatable = true, nullable = true) + private Integer parentId; /** * @deprecated use the factory method create() @@ -193,6 +196,24 @@ public class RulesProfile implements Cloneable { return this; } + /** + * For internal use only. + * + * @since 2.5 + */ + public Integer getParentId() { + return parentId; + } + + /** + * For internal use only. + * + * @since 2.5 + */ + public void setParentId(Integer parentId) { + this.parentId = parentId; + } + /** * @return the list of alerts defined in the profile */ @@ -311,6 +332,7 @@ public class RulesProfile implements Cloneable { RulesProfile clone = RulesProfile.create(getName(), getLanguage()); clone.setDefaultProfile(getDefaultProfile()); clone.setProvided(getProvided()); + clone.setParentId(getParentId()); if (CollectionUtils.isNotEmpty(getActiveRules())) { clone.setActiveRules(new ArrayList(CollectionUtils.collect(getActiveRules(), new Transformer() { public Object transform(Object input) { diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/rules/ActiveRule.java b/sonar-plugin-api/src/main/java/org/sonar/api/rules/ActiveRule.java index f6f8b0fef30..a53c3f01c79 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/rules/ActiveRule.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/rules/ActiveRule.java @@ -89,6 +89,24 @@ public class ActiveRule implements Cloneable { return id; } + /** + * For internal use only. + * + * @since 2.5 + */ + public boolean isInherited() { + return inherited == null ? false : inherited; + } + + /** + * For internal use only. + * + * @since 2.5 + */ + public void setInherited(boolean inherited) { + this.inherited = inherited; + } + /** * @deprecated visibility should be decreased to protected or package */ @@ -243,6 +261,7 @@ public class ActiveRule implements Cloneable { @Override public Object clone() { ActiveRule clone = new ActiveRule(getRulesProfile(), getRule(), getSeverity()); + clone.setInherited(isInherited()); if (CollectionUtils.isNotEmpty(getActiveRuleParams())) { clone.setActiveRuleParams(new ArrayList(CollectionUtils.collect(getActiveRuleParams(), new Transformer() { public Object transform(Object input) { diff --git a/sonar-server/src/main/java/org/sonar/server/configuration/ProfilesManager.java b/sonar-server/src/main/java/org/sonar/server/configuration/ProfilesManager.java index aea09e1cf01..e92496bfd6f 100644 --- a/sonar-server/src/main/java/org/sonar/server/configuration/ProfilesManager.java +++ b/sonar-server/src/main/java/org/sonar/server/configuration/ProfilesManager.java @@ -23,7 +23,10 @@ import org.sonar.api.Plugins; import org.sonar.api.database.DatabaseSession; import org.sonar.api.database.model.ResourceModel; import org.sonar.api.profiles.RulesProfile; +import org.sonar.api.rules.ActiveRule; +import org.sonar.api.rules.ActiveRuleParam; import org.sonar.api.rules.DefaultRulesManager; +import org.sonar.api.rules.Rule; import org.sonar.jpa.dao.BaseDao; import org.sonar.jpa.dao.RulesDao; @@ -54,6 +57,7 @@ public class ProfilesManager extends BaseDao { } public void deleteProfile(int profileId) { + // TODO should support deletion of profile with children RulesProfile profile = getSession().getEntity(RulesProfile.class, profileId); if (profile != null && !profile.getProvided()) { String hql = "UPDATE " + ResourceModel.class.getSimpleName() + " o SET o.rulesProfile=null WHERE o.rulesProfile=:rulesProfile"; @@ -64,7 +68,8 @@ public class ProfilesManager extends BaseDao { } public void deleteAllProfiles() { - getSession().createQuery("UPDATE " + ResourceModel.class.getSimpleName() + " o SET o.rulesProfile = null WHERE o.rulesProfile IS NOT NULL").executeUpdate(); + String hql = "UPDATE " + ResourceModel.class.getSimpleName() + " o SET o.rulesProfile = null WHERE o.rulesProfile IS NOT NULL"; + getSession().createQuery(hql).executeUpdate(); List profiles = getSession().createQuery("FROM " + RulesProfile.class.getSimpleName()).getResultList(); for (Object profile : profiles) { getSession().removeWithoutFlush(profile); @@ -72,4 +77,89 @@ public class ProfilesManager extends BaseDao { getSession().commit(); } + // Managing inheritance of profiles + // Only one level of inheritance supported + + public void changeParentProfile(Integer profileId, Integer parentId) { + RulesProfile profile = getSession().getEntity(RulesProfile.class, profileId); + if (profile != null && !profile.getProvided() && profileId != parentId) { + RulesProfile oldParent = getProfile(profile.getParentId()); + RulesProfile newParent = getProfile(parentId); + // Deactivate all inherited rules + if (oldParent != null) { + for (ActiveRule activeRule : oldParent.getActiveRules()) { + deactivate(profile, activeRule.getRule()); + } + } + // Activate all inherited rules + if (newParent != null) { + for (ActiveRule activeRule : newParent.getActiveRules()) { + activate(profile, activeRule); + } + } + profile.setParentId(parentId); + getSession().saveWithoutFlush(profile); + getSession().commit(); + } + } + + /** + * Rule was activated/changed in parent profile. + */ + public void activatedOrChanged(int parentProfileId, int activeRuleId) { + List children = getChildren(parentProfileId); + ActiveRule parentActiveRule = getSession().getEntity(ActiveRule.class, activeRuleId); + for (RulesProfile child : children) { + activate(child, parentActiveRule); + } + getSession().commit(); + } + + /** + * Rule was deactivated in parent profile. + */ + public void deactivated(int parentProfileId, int ruleId) { + List children = getChildren(parentProfileId); + Rule rule = getSession().getEntity(Rule.class, ruleId); + for (RulesProfile child : children) { + deactivate(child, rule); + } + getSession().commit(); + } + + private void activate(RulesProfile profile, ActiveRule parentActiveRule) { + ActiveRule activeRule = profile.getActiveRule(parentActiveRule.getRule()); + if (activeRule != null) { + removeActiveRule(profile, activeRule); + } + activeRule = (ActiveRule) parentActiveRule.clone(); + // TODO Godin: it means that we have bug in ActiveRule.clone() + for (ActiveRuleParam param : activeRule.getActiveRuleParams()) { + param.setActiveRule(activeRule); + } + activeRule.setRulesProfile(profile); + activeRule.setInherited(true); + profile.getActiveRules().add(activeRule); + } + + private void deactivate(RulesProfile profile, Rule rule) { + ActiveRule activeRule = profile.getActiveRule(rule); + if (activeRule != null) { + removeActiveRule(profile, activeRule); + } + } + + private List getChildren(int parentId) { + return getSession().getResults(RulesProfile.class, "parentId", parentId, "provided", false); + } + + private void removeActiveRule(RulesProfile profile, ActiveRule activeRule) { + profile.getActiveRules().remove(activeRule); + getSession().removeWithoutFlush(activeRule); + } + + private RulesProfile getProfile(Integer id) { + return id == null ? null : getSession().getEntity(RulesProfile.class, id); + } + } diff --git a/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java b/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java index d6681e35148..a4749ef623b 100644 --- a/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java +++ b/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java @@ -122,7 +122,6 @@ public final class JRubyFacade implements ServerComponent { public ViewProxy getWidget(String id) { return getContainer().getComponent(Views.class).getWidget(id); } - public List> getPages(String section, String resourceScope, String resourceQualifier, String resourceLanguage) { return getContainer().getComponent(Views.class).getPages(section, resourceScope, resourceQualifier, resourceLanguage); @@ -198,6 +197,18 @@ public final class JRubyFacade implements ServerComponent { getProfilesManager().deleteProfile((int) profileId); } + public void changeParentProfile(int profileId, Integer parentId) { + getProfilesManager().changeParentProfile(profileId, parentId); + } + + public void ruleActivatedOrChanged(int parentProfileId, int activeRuleId) { + getProfilesManager().activatedOrChanged(parentProfileId, activeRuleId); + } + + public void ruleDeactivated(int parentProfileId, int ruleId) { + getProfilesManager().deactivated(parentProfileId, ruleId); + } + public List