001/*
002 * Sonar, open source software quality management tool.
003 * Copyright (C) 2008-2012 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 */
020package org.sonar.server.configuration;
021
022import org.apache.commons.lang.ObjectUtils;
023import org.codehaus.plexus.util.StringUtils;
024import org.sonar.api.database.DatabaseSession;
025import org.sonar.api.database.model.ResourceModel;
026import org.sonar.api.profiles.RulesProfile;
027import org.sonar.api.rules.*;
028import org.sonar.api.utils.ValidationMessages;
029import org.sonar.jpa.dao.BaseDao;
030import org.sonar.jpa.dao.RulesDao;
031
032import java.util.List;
033
034public 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}