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.startup;
021
022import com.google.common.collect.Lists;
023import com.google.common.collect.Maps;
024import org.apache.commons.lang.StringUtils;
025import org.sonar.api.database.DatabaseSession;
026import org.sonar.api.rules.ActiveRuleParam;
027import org.sonar.api.rules.Rule;
028import org.sonar.api.rules.RuleParam;
029import org.sonar.api.rules.RuleRepository;
030import org.sonar.api.utils.Logs;
031import org.sonar.api.utils.SonarException;
032import org.sonar.api.utils.TimeProfiler;
033import org.sonar.core.i18n.RuleI18nManager;
034import org.sonar.jpa.session.DatabaseSessionFactory;
035
036import java.util.*;
037
038public final class RegisterRules {
039
040  private final DatabaseSessionFactory sessionFactory;
041  private final List<RuleRepository> repositories;
042  private final RuleI18nManager ruleI18nManager;
043
044  public RegisterRules(DatabaseSessionFactory sessionFactory, RuleRepository[] repos, RuleI18nManager ruleI18nManager) {
045    this.sessionFactory = sessionFactory;
046    this.repositories = Arrays.asList(repos);
047    this.ruleI18nManager = ruleI18nManager;
048  }
049
050  public RegisterRules(DatabaseSessionFactory sessionFactory, RuleI18nManager ruleI18nManager) {
051    this(sessionFactory, new RuleRepository[0], ruleI18nManager);
052  }
053
054  public void start() {
055    TimeProfiler profiler = new TimeProfiler();
056
057    DatabaseSession session = sessionFactory.getSession();
058    disableAllRules(session);
059    for (RuleRepository repository : repositories) {
060      profiler.start("Register rules [" + repository.getKey() + "/" + StringUtils.defaultString(repository.getLanguage(), "-") + "]");
061      registerRepository(repository, session);
062      profiler.stop();
063    }
064
065    profiler.start("Disable deprecated user rules");
066    disableDeprecatedUserRules(session);
067    profiler.stop();
068
069    session.commit();
070  }
071
072  private void disableDeprecatedUserRules(DatabaseSession session) {
073    List<Integer> deprecatedUserRuleIds = Lists.newLinkedList();
074    deprecatedUserRuleIds.addAll(session.createQuery(
075        "SELECT r.id FROM " + Rule.class.getSimpleName() +
076          " r WHERE r.parent IS NOT NULL AND NOT EXISTS(FROM " + Rule.class.getSimpleName() + " p WHERE r.parent=p)").getResultList());
077
078    deprecatedUserRuleIds.addAll(session.createQuery(
079        "SELECT r.id FROM " + Rule.class.getSimpleName() +
080          " r WHERE r.parent IS NOT NULL AND EXISTS(FROM " + Rule.class.getSimpleName() + " p WHERE r.parent=p and p.enabled=false)").getResultList());
081
082    for (Integer deprecatedUserRuleId : deprecatedUserRuleIds) {
083      Rule rule = session.getSingleResult(Rule.class, "id", deprecatedUserRuleId);
084      rule.setEnabled(false);
085      session.saveWithoutFlush(rule);
086    }
087
088  }
089
090  private void disableAllRules(DatabaseSession session) {
091    // the hardcoded repository "manual" is used for manual violations
092    session.createQuery("UPDATE " + Rule.class.getSimpleName() + " SET enabled=false WHERE parent IS NULL AND pluginName<>'manual'").executeUpdate();
093  }
094
095  private void registerRepository(RuleRepository repository, DatabaseSession session) {
096    Map<String, Rule> rulesByKey = Maps.newHashMap();
097    for (Rule rule : repository.createRules()) {
098      validateRule(rule, repository.getKey());
099      rule.setRepositoryKey(repository.getKey());
100      rulesByKey.put(rule.getKey(), rule);
101    }
102    Logs.INFO.info(rulesByKey.size() + " rules");
103
104    List<Rule> persistedRules = session.getResults(Rule.class, "pluginName", repository.getKey());
105    for (Rule persistedRule : persistedRules) {
106      Rule rule = rulesByKey.get(persistedRule.getKey());
107      if (rule != null) {
108        updateRule(persistedRule, rule, session);
109        rulesByKey.remove(rule.getKey());
110      }
111    }
112
113    saveNewRules(rulesByKey.values(), session);
114  }
115
116  private void validateRule(Rule rule, String repositoryKey) {
117    if (rule.getName() == null && ruleI18nManager.getName(repositoryKey, rule.getKey(), Locale.ENGLISH) == null) {
118      throw new SonarException("The following rule (repository: " + repositoryKey + ") must have a name: " + rule);
119    }
120    if (rule.getDescription() == null && ruleI18nManager.getDescription(repositoryKey, rule.getKey(), Locale.ENGLISH) == null) {
121      throw new SonarException("The following rule (repository: " + repositoryKey + ") must have a description: " + rule);
122    }
123  }
124
125  private void updateRule(Rule persistedRule, Rule rule, DatabaseSession session) {
126    persistedRule.setName(rule.getName());
127    persistedRule.setConfigKey(rule.getConfigKey());
128    persistedRule.setDescription(rule.getDescription());
129    persistedRule.setSeverity(rule.getSeverity());
130    persistedRule.setEnabled(true);
131    persistedRule.setCardinality(rule.getCardinality());
132
133    // delete deprecated params
134    deleteDeprecatedParameters(persistedRule, rule, session);
135
136    // add new params and update existing params
137    updateParameters(persistedRule, rule);
138
139    session.saveWithoutFlush(persistedRule);
140  }
141
142  private void updateParameters(Rule persistedRule, Rule rule) {
143    if (rule.getParams() != null) {
144      for (RuleParam param : rule.getParams()) {
145        RuleParam persistedParam = persistedRule.getParam(param.getKey());
146        if (persistedParam == null) {
147          persistedParam = persistedRule.createParameter(param.getKey());
148        }
149        persistedParam.setDescription(param.getDescription());
150        persistedParam.setType(param.getType());
151        persistedParam.setDefaultValue(param.getDefaultValue());
152      }
153    }
154  }
155
156  private void deleteDeprecatedParameters(Rule persistedRule, Rule rule, DatabaseSession session) {
157    if (persistedRule.getParams() != null && persistedRule.getParams().size() > 0) {
158      for (Iterator<RuleParam> it = persistedRule.getParams().iterator(); it.hasNext();) {
159        RuleParam persistedParam = it.next();
160        if (rule.getParam(persistedParam.getKey()) == null) {
161          it.remove();
162          session
163              .createQuery("delete from " + ActiveRuleParam.class.getSimpleName() + " where ruleParam=:param")
164              .setParameter("param", persistedParam)
165              .executeUpdate();
166        }
167      }
168    }
169  }
170
171  private void saveNewRules(Collection<Rule> rules, DatabaseSession session) {
172    for (Rule rule : rules) {
173      rule.setEnabled(true);
174      session.saveWithoutFlush(rule);
175    }
176  }
177}