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.thoughtworks.xstream.XStream;
023import com.thoughtworks.xstream.converters.Converter;
024import com.thoughtworks.xstream.converters.MarshallingContext;
025import com.thoughtworks.xstream.converters.UnmarshallingContext;
026import com.thoughtworks.xstream.io.HierarchicalStreamReader;
027import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
028import org.apache.commons.collections.CollectionUtils;
029import org.slf4j.LoggerFactory;
030import org.sonar.api.database.DatabaseSession;
031import org.sonar.api.measures.Metric;
032import org.sonar.api.profiles.Alert;
033import org.sonar.api.profiles.RulesProfile;
034import org.sonar.api.rules.*;
035import org.sonar.jpa.dao.RulesDao;
036
037import java.util.*;
038
039public class ProfilesBackup implements Backupable {
040
041  private Collection<RulesProfile> profiles;
042  private DatabaseSession session;
043
044  public ProfilesBackup(DatabaseSession session) {
045    this.session = session;
046  }
047
048  /**
049   * for unit tests
050   */
051  ProfilesBackup(Collection<RulesProfile> profiles) {
052    this.profiles = profiles;
053  }
054
055  public void configure(XStream xStream) {
056    xStream.alias("profile", RulesProfile.class);
057    xStream.alias("alert", Alert.class);
058    xStream.alias("active-rule", ActiveRule.class);
059    xStream.aliasField("active-rules", RulesProfile.class, "activeRules");
060    xStream.aliasField("default-profile", RulesProfile.class, "defaultProfile");
061    xStream.omitField(RulesProfile.class, "id");
062    xStream.omitField(RulesProfile.class, "projects");
063    xStream.registerConverter(getActiveRuleConverter());
064    xStream.registerConverter(getAlertsConverter());
065  }
066
067  public void exportXml(SonarConfig sonarConfig) {
068    this.profiles = (this.profiles == null ? session.getResults(RulesProfile.class) : this.profiles);
069    // the profiles objects must be cloned to avoid issues CGLib
070    List<RulesProfile> cloned = new ArrayList<RulesProfile>();
071    for (RulesProfile profile : this.profiles) {
072      cloned.add((RulesProfile) profile.clone());
073    }
074
075    sonarConfig.setProfiles(cloned);
076  }
077
078  public void importXml(SonarConfig sonarConfig) {
079    if (CollectionUtils.isNotEmpty(sonarConfig.getProfiles())) {
080      LoggerFactory.getLogger(getClass()).info("Delete profiles");
081      ProfilesManager profilesManager = new ProfilesManager(session, null);
082      profilesManager.deleteAllProfiles();
083
084      RulesDao rulesDao = new RulesDao(session);
085      for (RulesProfile profile : sonarConfig.getProfiles()) {
086        LoggerFactory.getLogger(getClass()).info("Restore profile " + profile.getName());
087        importProfile(rulesDao, profile);
088      }
089    }
090  }
091
092  public void importProfile(RulesDao rulesDao, RulesProfile toImport) {
093    if (toImport.getEnabled() == null) {
094      // backward-compatibility with versions < 2.6. The field "enabled" did not exist. Default value is true.
095      toImport.setEnabled(true);
096    }
097    if (toImport.getVersion() == 0) {
098      // backward-compatibility with versions < 2.9. The field "version" did not exist. Default value is 1.
099      toImport.setVersion(1);
100    }
101    if (toImport.getUsed() == null) {
102      // backward-compatibility with versions < 2.9. The field "used_profile" did not exist. Default value is false.
103      toImport.setUsed(false);
104    }
105    importActiveRules(rulesDao, toImport);
106    importAlerts(toImport);
107    session.save(toImport);
108  }
109
110  private void importAlerts(RulesProfile profile) {
111    if (profile.getAlerts() != null) {
112      for (Iterator<Alert> ia = profile.getAlerts().iterator(); ia.hasNext();) {
113        Alert alert = ia.next();
114        Metric unMarshalledMetric = alert.getMetric();
115        String validKey = unMarshalledMetric.getKey();
116        Metric matchingMetricInDb = session.getSingleResult(Metric.class, "key", validKey);
117        if (matchingMetricInDb == null) {
118          LoggerFactory.getLogger(getClass()).error("Unable to find metric " + validKey);
119          ia.remove();
120          continue;
121        }
122        alert.setMetric(matchingMetricInDb);
123        alert.setRulesProfile(profile);
124      }
125    }
126  }
127
128  private void importActiveRules(RulesDao rulesDao, RulesProfile profile) {
129    for (Iterator<ActiveRule> iar = profile.getActiveRules(true).iterator(); iar.hasNext();) {
130      ActiveRule activeRule = iar.next();
131      Rule unMarshalledRule = activeRule.getRule();
132      Rule matchingRuleInDb = rulesDao.getRuleByKey(unMarshalledRule.getRepositoryKey(), unMarshalledRule.getKey());
133      if (matchingRuleInDb == null) {
134        LoggerFactory.getLogger(getClass()).error(
135            "Unable to find active rule " + unMarshalledRule.getRepositoryKey() + ":" + unMarshalledRule.getKey());
136        iar.remove();
137        continue;
138      }
139      activeRule.setRule(matchingRuleInDb);
140      activeRule.setRulesProfile(profile);
141      activeRule.getActiveRuleParams();
142      for (Iterator<ActiveRuleParam> irp = activeRule.getActiveRuleParams().iterator(); irp.hasNext();) {
143        ActiveRuleParam activeRuleParam = irp.next();
144        RuleParam unMarshalledRP = activeRuleParam.getRuleParam();
145        RuleParam matchingRPInDb = rulesDao.getRuleParam(matchingRuleInDb, unMarshalledRP.getKey());
146        if (matchingRPInDb == null) {
147          LoggerFactory.getLogger(getClass()).error("Unable to find active rule parameter " + unMarshalledRP.getKey());
148          irp.remove();
149          continue;
150        }
151        activeRuleParam.setActiveRule(activeRule);
152        activeRuleParam.setRuleParam(matchingRPInDb);
153      }
154    }
155  }
156
157  private Converter getAlertsConverter() {
158    return new Converter() {
159
160      public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
161        Alert alert = (Alert) source;
162        writeNode(writer, "operator", alert.getOperator());
163        writeNode(writer, "value-error", alert.getValueError());
164        writeNode(writer, "value-warning", alert.getValueWarning());
165        writeNode(writer, "metric-key", alert.getMetric().getKey());
166      }
167
168      public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
169        Map<String, String> values = readNode(reader);
170        return new Alert(null, new Metric(values.get("metric-key")), values.get("operator"), values.get("value-error"),
171            values.get("value-warning"));
172      }
173
174      public boolean canConvert(Class type) {
175        return type.equals(Alert.class);
176      }
177    };
178  }
179
180  private Converter getActiveRuleConverter() {
181    return new Converter() {
182
183      public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
184        ActiveRule rule = (ActiveRule) source;
185        writeNode(writer, "key", rule.getRule().getKey());
186        writeNode(writer, "plugin", rule.getRule().getRepositoryKey());
187        writeNode(writer, "level", rule.getSeverity().name());
188        writeNode(writer, "inheritance", rule.getInheritance());
189
190        if (!rule.getActiveRuleParams().isEmpty()) {
191          writer.startNode("params");
192          for (ActiveRuleParam activeRuleParam : rule.getActiveRuleParams()) {
193            writer.startNode("param");
194            writeNode(writer, "key", activeRuleParam.getRuleParam().getKey());
195            writeNode(writer, "value", activeRuleParam.getValue());
196            writer.endNode();
197          }
198          writer.endNode();
199        }
200      }
201
202      public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
203        Map<String, String> valuesRule = new HashMap<String, String>();
204        List<ActiveRuleParam> params = new ArrayList<ActiveRuleParam>();
205        while (reader.hasMoreChildren()) {
206          reader.moveDown();
207          valuesRule.put(reader.getNodeName(), reader.getValue());
208          if ("params".equals(reader.getNodeName())) {
209            while (reader.hasMoreChildren()) {
210              reader.moveDown();
211              Map<String, String> valuesParam = readNode(reader);
212              ActiveRuleParam activeRuleParam = new ActiveRuleParam(null, new RuleParam(null, valuesParam.get("key"), null, null),
213                  valuesParam.get("value"));
214              params.add(activeRuleParam);
215              reader.moveUp();
216            }
217          }
218          reader.moveUp();
219        }
220
221        ActiveRule activeRule = new ActiveRule(null, new Rule(valuesRule.get("plugin"), valuesRule.get("key")), RulePriority
222            .valueOf(valuesRule.get("level")));
223        activeRule.setActiveRuleParams(params);
224        if (valuesRule.containsKey("inheritance")) {
225          activeRule.setInheritance(valuesRule.get("inheritance"));
226        }
227        return activeRule;
228      }
229
230      public boolean canConvert(Class type) {
231        return type.equals(ActiveRule.class);
232      }
233    };
234  }
235
236  private void writeNode(HierarchicalStreamWriter writer, String name, String value) {
237    if (value != null) {
238      writer.startNode(name);
239      writer.setValue(value);
240      writer.endNode();
241    }
242  }
243
244  private Map<String, String> readNode(HierarchicalStreamReader reader) {
245    Map<String, String> values = new HashMap<String, String>();
246    while (reader.hasMoreChildren()) {
247      reader.moveDown();
248      values.put(reader.getNodeName(), reader.getValue());
249      reader.moveUp();
250    }
251    return values;
252  }
253
254}