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.server.configuration;
021    
022    import org.apache.commons.lang.ObjectUtils;
023    import org.codehaus.plexus.util.StringUtils;
024    import org.sonar.api.database.DatabaseSession;
025    import org.sonar.api.database.model.ResourceModel;
026    import org.sonar.api.profiles.RulesProfile;
027    import org.sonar.api.rules.*;
028    import org.sonar.api.utils.ValidationMessages;
029    import org.sonar.jpa.dao.BaseDao;
030    import org.sonar.jpa.dao.RulesDao;
031    
032    import java.util.List;
033    
034    public class ProfilesManager extends BaseDao {
035    
036      private RulesDao rulesDao;
037    
038      public ProfilesManager(DatabaseSession session, RulesDao rulesDao) {
039        super(session);
040        this.rulesDao = rulesDao;
041      }
042    
043      public void renameProfile(int profileId, String newProfileName) {
044        RulesProfile profile = getSession().getSingleResult(RulesProfile.class, "id", profileId);
045        if (profile != null && !profile.getProvided()) {
046          String hql = "UPDATE " + RulesProfile.class.getSimpleName() + " o SET o.parentName=:newName  WHERE o.parentName=:oldName";
047          getSession().getEntityManager().createQuery(hql)
048              .setParameter("oldName", profile.getName())
049              .setParameter("newName", newProfileName)
050              .executeUpdate();
051          profile.setName(newProfileName);
052          getSession().save(profile);
053          getSession().commit();
054        }
055      }
056    
057      public void copyProfile(int profileId, String newProfileName) {
058        RulesProfile profile = getSession().getSingleResult(RulesProfile.class, "id", profileId);
059        RulesProfile toImport = (RulesProfile) profile.clone();
060        toImport.setName(newProfileName);
061        toImport.setDefaultProfile(false);
062        toImport.setProvided(false);
063        ProfilesBackup pb = new ProfilesBackup(getSession());
064        pb.importProfile(rulesDao, toImport);
065        getSession().commit();
066      }
067    
068      public void deleteProfile(int profileId) {
069        RulesProfile profile = getSession().getEntity(RulesProfile.class, profileId);
070        if (profile != null && !profile.getProvided() && getChildren(profile).isEmpty()) {
071          // Remove history of rule changes
072          String hqlDeleteRc = "DELETE " + ActiveRuleChange.class.getSimpleName() + " rc WHERE rc.rulesProfile=:rulesProfile";
073          getSession().createQuery(hqlDeleteRc).setParameter("rulesProfile", profile).executeUpdate();
074    
075          String hql = "UPDATE " + ResourceModel.class.getSimpleName() + " o SET o.rulesProfile=null WHERE o.rulesProfile=:rulesProfile";
076          getSession().createQuery(hql).setParameter("rulesProfile", profile).executeUpdate();
077          getSession().remove(profile);
078          getSession().commit();
079        }
080      }
081    
082      public void deleteAllProfiles() {
083        // Remove history of rule changes
084        String hqlDeleteRc = "DELETE " + ActiveRuleChange.class.getSimpleName() + " rc";
085        getSession().createQuery(hqlDeleteRc).executeUpdate();
086    
087        String hql = "UPDATE " + ResourceModel.class.getSimpleName() + " o SET o.rulesProfile = null WHERE o.rulesProfile IS NOT NULL";
088        getSession().createQuery(hql).executeUpdate();
089        List profiles = getSession().createQuery("FROM " + RulesProfile.class.getSimpleName()).getResultList();
090        for (Object profile : profiles) {
091          getSession().removeWithoutFlush(profile);
092        }
093        getSession().commit();
094      }
095    
096      // Managing inheritance of profiles
097    
098      public ValidationMessages changeParentProfile(Integer profileId, String parentName, String userName) {
099        ValidationMessages messages = ValidationMessages.create();
100        RulesProfile profile = getSession().getEntity(RulesProfile.class, profileId);
101        if (profile != null && !profile.getProvided()) {
102          RulesProfile oldParent = getParentProfile(profile);
103          RulesProfile newParent = getProfile(profile.getLanguage(), parentName);
104          if (isCycle(profile, newParent)) {
105            messages.addWarningText("Please do not select a child profile as parent.");
106            return messages;
107          }
108          // Deactivate all inherited rules
109          if (oldParent != null) {
110            for (ActiveRule activeRule : oldParent.getActiveRules()) {
111              deactivate(profile, activeRule.getRule(), userName);
112            }
113          }
114          // Activate all inherited rules
115          if (newParent != null) {
116            for (ActiveRule activeRule : newParent.getActiveRules()) {
117              activateOrChange(profile, activeRule, userName);
118            }
119          }
120          profile.setParentName(newParent == null ? null : newParent.getName());
121          getSession().saveWithoutFlush(profile);
122          getSession().commit();
123        }
124        return messages;
125      }
126    
127      /**
128       * Rule was activated
129       */
130      public void activated(int profileId, int activeRuleId, String userName) {
131        ActiveRule activeRule = getSession().getEntity(ActiveRule.class, activeRuleId);
132        RulesProfile profile = getSession().getEntity(RulesProfile.class, profileId);
133        ruleEnabled(profile, activeRule, userName);
134        // Notify child profiles
135        activatedOrChanged(profileId, activeRuleId, userName);
136      }
137    
138      /**
139       * Rule param was changed
140       */
141      public void ruleParamChanged(int profileId, int activeRuleId, String paramKey, String oldValue, String newValue, String userName) {
142        ActiveRule activeRule = getSession().getEntity(ActiveRule.class, activeRuleId);
143        RulesProfile profile = getSession().getEntity(RulesProfile.class, profileId);
144    
145        ruleParamChanged(profile, activeRule.getRule(), paramKey, oldValue, newValue, userName);
146    
147        // Notify child profiles
148        activatedOrChanged(profileId, activeRuleId, userName);
149      }
150    
151      /**
152       * Rule severity was changed
153       */
154      public void ruleSeverityChanged(int profileId, int activeRuleId, RulePriority oldSeverity, RulePriority newSeverity, String userName) {
155        ActiveRule activeRule = getSession().getEntity(ActiveRule.class, activeRuleId);
156        RulesProfile profile = getSession().getEntity(RulesProfile.class, profileId);
157    
158        ruleSeverityChanged(profile, activeRule.getRule(), oldSeverity, newSeverity, userName);
159    
160        // Notify child profiles
161        activatedOrChanged(profileId, activeRuleId, userName);
162      }
163    
164      /**
165       * Rule was activated/changed in parent profile.
166       */
167      private void activatedOrChanged(int parentProfileId, int activeRuleId, String userName) {
168        ActiveRule parentActiveRule = getSession().getEntity(ActiveRule.class, activeRuleId);
169        if (parentActiveRule.isInherited()) {
170          parentActiveRule.setInheritance(ActiveRule.OVERRIDES);
171          getSession().saveWithoutFlush(parentActiveRule);
172        }
173        for (RulesProfile child : getChildren(parentProfileId)) {
174          activateOrChange(child, parentActiveRule, userName);
175        }
176        getSession().commit();
177      }
178    
179      /**
180       * Rule was deactivated in parent profile.
181       */
182      public void deactivated(int parentProfileId, int deactivatedRuleId, String userName) {
183        ActiveRule parentActiveRule = getSession().getEntity(ActiveRule.class, deactivatedRuleId);
184        RulesProfile profile = getSession().getEntity(RulesProfile.class, parentProfileId);
185        ruleDisabled(profile, parentActiveRule, userName);
186        for (RulesProfile child : getChildren(parentProfileId)) {
187          deactivate(child, parentActiveRule.getRule(), userName);
188        }
189        getSession().commit();
190      }
191    
192      /**
193       * @return true, if setting <code>childProfile</code> as a child of <code>parentProfile</code> adds cycle
194       */
195      boolean isCycle(RulesProfile childProfile, RulesProfile parentProfile) {
196        while (parentProfile != null) {
197          if (childProfile.equals(parentProfile)) {
198            return true;
199          }
200          parentProfile = getParentProfile(parentProfile);
201        }
202        return false;
203      }
204    
205      public void revert(int profileId, int activeRuleId, String userName) {
206        RulesProfile profile = getSession().getEntity(RulesProfile.class, profileId);
207        ActiveRule oldActiveRule = getSession().getEntity(ActiveRule.class, activeRuleId);
208        if (oldActiveRule != null && oldActiveRule.doesOverride()) {
209          ActiveRule parentActiveRule = getParentProfile(profile).getActiveRule(oldActiveRule.getRule());
210          removeActiveRule(profile, oldActiveRule);
211          ActiveRule newActiveRule = (ActiveRule) parentActiveRule.clone();
212          newActiveRule.setRulesProfile(profile);
213          newActiveRule.setInheritance(ActiveRule.INHERITED);
214          profile.addActiveRule(newActiveRule);
215          getSession().saveWithoutFlush(newActiveRule);
216    
217          // Compute change
218          ruleChanged(profile, oldActiveRule, newActiveRule, userName);
219    
220          for (RulesProfile child : getChildren(profile)) {
221            activateOrChange(child, newActiveRule, userName);
222          }
223    
224          getSession().commit();
225        }
226      }
227    
228      private synchronized void incrementProfileVersionIfNeeded(RulesProfile profile) {
229        if (profile.getUsed()) {
230          profile.setVersion(profile.getVersion() + 1);
231          profile.setUsed(false);
232          getSession().saveWithoutFlush(profile);
233        }
234      }
235    
236      /**
237       * Deal with creation of ActiveRuleChange item when a rule param is changed on a profile
238       */
239      private void ruleParamChanged(RulesProfile profile, Rule rule, String paramKey, String oldValue, String newValue, String userName) {
240        incrementProfileVersionIfNeeded(profile);
241        ActiveRuleChange rc = new ActiveRuleChange(userName, profile, rule);
242        if (!StringUtils.equals(oldValue, newValue)) {
243          rc.setParameterChange(paramKey, oldValue, newValue);
244          getSession().saveWithoutFlush(rc);
245        }
246      }
247    
248      /**
249       * Deal with creation of ActiveRuleChange item when a rule severity is changed on a profile
250       */
251      private void ruleSeverityChanged(RulesProfile profile, Rule rule, RulePriority oldSeverity, RulePriority newSeverity, String userName) {
252        incrementProfileVersionIfNeeded(profile);
253        ActiveRuleChange rc = new ActiveRuleChange(userName, profile, rule);
254        if (!ObjectUtils.equals(oldSeverity, newSeverity)) {
255          rc.setOldSeverity(oldSeverity);
256          rc.setNewSeverity(newSeverity);
257          getSession().saveWithoutFlush(rc);
258        }
259      }
260    
261      /**
262       * Deal with creation of ActiveRuleChange item when a rule is changed (severity and/or param(s)) on a profile
263       */
264      private void ruleChanged(RulesProfile profile, ActiveRule oldActiveRule, ActiveRule newActiveRule, String userName) {
265        incrementProfileVersionIfNeeded(profile);
266        ActiveRuleChange rc = new ActiveRuleChange(userName, profile, newActiveRule.getRule());
267    
268        if (oldActiveRule.getSeverity() != newActiveRule.getSeverity()) {
269          rc.setOldSeverity(oldActiveRule.getSeverity());
270          rc.setNewSeverity(newActiveRule.getSeverity());
271        }
272        if (oldActiveRule.getRule().getParams() != null) {
273          for (RuleParam p : oldActiveRule.getRule().getParams()) {
274            String oldParam = oldActiveRule.getParameter(p.getKey());
275            String newParam = newActiveRule.getParameter(p.getKey());
276            if (!StringUtils.equals(oldParam, newParam)) {
277              rc.setParameterChange(p.getKey(), oldParam, newParam);
278            }
279          }
280        }
281    
282        getSession().saveWithoutFlush(rc);
283      }
284    
285      /**
286       * Deal with creation of ActiveRuleChange item when a rule is enabled on a profile
287       */
288      private void ruleEnabled(RulesProfile profile, ActiveRule newActiveRule, String userName) {
289        incrementProfileVersionIfNeeded(profile);
290        ActiveRuleChange rc = new ActiveRuleChange(userName, profile, newActiveRule.getRule());
291        rc.setEnabled(true);
292        rc.setNewSeverity(newActiveRule.getSeverity());
293        if (newActiveRule.getRule().getParams() != null) {
294          for (RuleParam p : newActiveRule.getRule().getParams()) {
295            String newParam = newActiveRule.getParameter(p.getKey());
296            if (newParam != null) {
297              rc.setParameterChange(p.getKey(), null, newParam);
298            }
299          }
300        }
301        getSession().saveWithoutFlush(rc);
302      }
303    
304      /**
305       * Deal with creation of ActiveRuleChange item when a rule is disabled on a profile
306       */
307      private void ruleDisabled(RulesProfile profile, ActiveRule disabledRule, String userName) {
308        incrementProfileVersionIfNeeded(profile);
309        ActiveRuleChange rc = new ActiveRuleChange(userName, profile, disabledRule.getRule());
310        rc.setEnabled(false);
311        rc.setOldSeverity(disabledRule.getSeverity());
312        if (disabledRule.getRule().getParams() != null) {
313          for (RuleParam p : disabledRule.getRule().getParams()) {
314            String oldParam = disabledRule.getParameter(p.getKey());
315            if (oldParam != null) {
316              rc.setParameterChange(p.getKey(), oldParam, null);
317            }
318          }
319        }
320        getSession().saveWithoutFlush(rc);
321      }
322    
323      private void activateOrChange(RulesProfile profile, ActiveRule parentActiveRule, String userName) {
324        ActiveRule oldActiveRule = profile.getActiveRule(parentActiveRule.getRule());
325        if (oldActiveRule != null) {
326          if (oldActiveRule.isInherited()) {
327            removeActiveRule(profile, oldActiveRule);
328          } else {
329            oldActiveRule.setInheritance(ActiveRule.OVERRIDES);
330            getSession().saveWithoutFlush(oldActiveRule);
331            return; // no need to change in children
332          }
333        }
334        ActiveRule newActiveRule = (ActiveRule) parentActiveRule.clone();
335        newActiveRule.setRulesProfile(profile);
336        newActiveRule.setInheritance(ActiveRule.INHERITED);
337        profile.addActiveRule(newActiveRule);
338        getSession().saveWithoutFlush(newActiveRule);
339    
340        if (oldActiveRule != null) {
341          ruleChanged(profile, oldActiveRule, newActiveRule, userName);
342        } else {
343          ruleEnabled(profile, newActiveRule, userName);
344        }
345    
346        for (RulesProfile child : getChildren(profile)) {
347          activateOrChange(child, newActiveRule, userName);
348        }
349      }
350    
351      private void deactivate(RulesProfile profile, Rule rule, String userName) {
352        ActiveRule activeRule = profile.getActiveRule(rule);
353        if (activeRule != null) {
354          if (activeRule.isInherited()) {
355            ruleDisabled(profile, activeRule, userName);
356            removeActiveRule(profile, activeRule);
357          } else {
358            activeRule.setInheritance(null);
359            getSession().saveWithoutFlush(activeRule);
360            return; // no need to change in children
361          }
362    
363          for (RulesProfile child : getChildren(profile)) {
364            deactivate(child, rule, userName);
365          }
366        }
367      }
368    
369      private List<RulesProfile> getChildren(int parentId) {
370        RulesProfile parent = getSession().getEntity(RulesProfile.class, parentId);
371        return getChildren(parent);
372      }
373    
374      private List<RulesProfile> getChildren(RulesProfile parent) {
375        return getSession().getResults(RulesProfile.class,
376            "language", parent.getLanguage(),
377            "parentName", parent.getName(),
378            "provided", false,
379            "enabled", true);
380      }
381    
382      private void removeActiveRule(RulesProfile profile, ActiveRule activeRule) {
383        profile.removeActiveRule(activeRule);
384        getSession().removeWithoutFlush(activeRule);
385      }
386    
387      RulesProfile getProfile(String language, String name) {
388        return getSession().getSingleResult(RulesProfile.class,
389            "language", language,
390            "name", name,
391            "enabled", true);
392      }
393    
394      RulesProfile getParentProfile(RulesProfile profile) {
395        if (profile.getParentName() == null) {
396          return null;
397        }
398        return getProfile(profile.getLanguage(), profile.getParentName());
399      }
400    
401    }