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 com.google.common.collect.Lists;
023import com.thoughtworks.xstream.XStream;
024import com.thoughtworks.xstream.converters.Converter;
025import com.thoughtworks.xstream.converters.MarshallingContext;
026import com.thoughtworks.xstream.converters.UnmarshallingContext;
027import com.thoughtworks.xstream.io.HierarchicalStreamReader;
028import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
029import org.apache.commons.collections.CollectionUtils;
030import org.slf4j.LoggerFactory;
031import org.sonar.api.database.DatabaseSession;
032import org.sonar.api.rules.Rule;
033import org.sonar.api.rules.RuleParam;
034import org.sonar.api.rules.RulePriority;
035import org.sonar.jpa.dao.RulesDao;
036
037import java.util.*;
038
039public class RulesBackup implements Backupable {
040  private Collection<Rule> rules;
041  private RulesDao rulesDao;
042  private DatabaseSession session;
043
044  public RulesBackup(DatabaseSession session) {
045    this.rulesDao = new RulesDao(session);
046    this.session = session;
047  }
048
049  /**
050   * For tests.
051   */
052  RulesBackup(Collection<Rule> rules) {
053    this.rules = rules;
054  }
055
056  public void exportXml(SonarConfig sonarConfig) {
057    if (rules == null) {
058      rules = getUserRules();
059    }
060    sonarConfig.setRules(rules);
061  }
062
063  public void importXml(SonarConfig sonarConfig) {
064    disableUserRules();
065    if (CollectionUtils.isNotEmpty(sonarConfig.getRules())) {
066      registerUserRules(sonarConfig.getRules());
067    }
068  }
069
070  private List<Rule> getUserRules() {
071    List<Rule> userRules = Lists.newArrayList();
072    for (Rule rule : rulesDao.getRules()) {
073      if (rule.getParent() != null) {
074        userRules.add(rule);
075      }
076    }
077    return userRules;
078  }
079
080  private void disableUserRules() {
081    LoggerFactory.getLogger(getClass()).info("Disable rules created by user");
082    for (Rule rule : getUserRules()) {
083      rule.setEnabled(false);
084      session.save(rule);
085    }
086  }
087
088  private void registerUserRules(Collection<Rule> rules) {
089    LoggerFactory.getLogger(getClass()).info("Restore rules");
090    for (Rule rule : rules) {
091      Rule parent = rule.getParent();
092      Rule matchingParentRuleInDb = rulesDao.getRuleByKey(parent.getRepositoryKey(), parent.getKey());
093      if (matchingParentRuleInDb == null) {
094        LoggerFactory.getLogger(getClass()).error("Unable to find parent rule " + parent.getRepositoryKey() + ":" + parent.getKey());
095        continue;
096      }
097
098      for (Iterator<RuleParam> irp = rule.getParams().iterator(); irp.hasNext();) {
099        RuleParam param = irp.next();
100        RuleParam matchingRPInDb = rulesDao.getRuleParam(matchingParentRuleInDb, param.getKey());
101        if (matchingRPInDb == null) {
102          LoggerFactory.getLogger(getClass()).error("Unable to find rule parameter in parent " + param.getKey());
103          irp.remove();
104        }
105      }
106
107      rule.setParent(matchingParentRuleInDb);
108      Rule matchingRuleInDb = session.getSingleResult(Rule.class,
109          "pluginName", rule.getRepositoryKey(),
110          "key", rule.getKey());
111      if (matchingRuleInDb != null) {
112        // merge
113        matchingRuleInDb.setParent(matchingParentRuleInDb);
114        matchingRuleInDb.setConfigKey(rule.getConfigKey());
115        matchingRuleInDb.setName(rule.getName());
116        matchingRuleInDb.setDescription(rule.getDescription());
117        matchingRuleInDb.setSeverity(rule.getSeverity());
118        matchingRuleInDb.setParams(rule.getParams());
119        matchingRuleInDb.setEnabled(true);
120        session.save(matchingRuleInDb);
121      } else {
122        rule.setEnabled(true);
123        session.save(rule);
124      }
125    }
126  }
127
128  public void configure(XStream xStream) {
129    xStream.alias("rule", Rule.class);
130    xStream.registerConverter(new Converter() {
131      public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
132        Rule rule = (Rule) source;
133        writeNode(writer, "parentRepositoryKey", rule.getParent().getRepositoryKey());
134        writeNode(writer, "parentKey", rule.getParent().getKey());
135        writeNode(writer, "repositoryKey", rule.getRepositoryKey());
136        writeNode(writer, "key", rule.getKey());
137        writeNode(writer, "configKey", rule.getConfigKey());
138        writeNode(writer, "level", rule.getSeverity().name());
139        writeNode(writer, "name", rule.getName());
140        writeNode(writer, "description", rule.getDescription());
141
142        if (!rule.getParams().isEmpty()) {
143          writer.startNode("params");
144          for (RuleParam ruleParam : rule.getParams()) {
145            writer.startNode("param");
146            writeNode(writer, "key", ruleParam.getKey());
147            writeNode(writer, "value", ruleParam.getDefaultValue());
148            writer.endNode();
149          }
150          writer.endNode();
151        }
152      }
153
154      public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
155        Rule rule = Rule.create();
156
157        Map<String, String> valuesRule = new HashMap<String, String>();
158        while (reader.hasMoreChildren()) {
159          reader.moveDown();
160          valuesRule.put(reader.getNodeName(), reader.getValue());
161          if ("params".equals(reader.getNodeName())) {
162            while (reader.hasMoreChildren()) {
163              reader.moveDown();
164              Map<String, String> valuesParam = readNode(reader);
165              rule.createParameter()
166                  .setKey(valuesParam.get("key"))
167                  .setDefaultValue(valuesParam.get("value"));
168              reader.moveUp();
169            }
170          }
171          reader.moveUp();
172        }
173
174        Rule parent = Rule.create()
175            .setRepositoryKey(valuesRule.get("parentRepositoryKey"))
176            .setKey(valuesRule.get("parentKey"));
177        rule.setParent(parent)
178            .setRepositoryKey(valuesRule.get("repositoryKey"))
179            .setKey(valuesRule.get("key"))
180            .setConfigKey(valuesRule.get("configKey"))
181            .setName(valuesRule.get("name"))
182            .setDescription(valuesRule.get("description"))
183            .setSeverity(RulePriority.valueOf(valuesRule.get("level")));
184        return rule;
185      }
186
187      public boolean canConvert(Class type) {
188        return Rule.class.equals(type);
189      }
190    });
191  }
192
193  private void writeNode(HierarchicalStreamWriter writer, String name, String value) {
194    writer.startNode(name);
195    writer.setValue(value);
196    writer.endNode();
197  }
198
199  private Map<String, String> readNode(HierarchicalStreamReader reader) {
200    Map<String, String> values = new HashMap<String, String>();
201    while (reader.hasMoreChildren()) {
202      reader.moveDown();
203      values.put(reader.getNodeName(), reader.getValue());
204      reader.moveUp();
205    }
206    return values;
207  }
208}