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.basic.DateConverter;
024import com.thoughtworks.xstream.core.util.QuickWriter;
025import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
026import com.thoughtworks.xstream.io.xml.PrettyPrintWriter;
027import com.thoughtworks.xstream.io.xml.XppDriver;
028import org.apache.commons.io.IOUtils;
029import org.apache.commons.lang.CharEncoding;
030import org.apache.commons.lang.StringUtils;
031import org.slf4j.LoggerFactory;
032import org.sonar.api.database.DatabaseSession;
033import org.sonar.core.persistence.DatabaseVersion;
034
035import javax.annotation.Nullable;
036import java.io.IOException;
037import java.io.InputStream;
038import java.io.Writer;
039import java.util.ArrayList;
040import java.util.Collection;
041import java.util.Date;
042import java.util.List;
043
044public class Backup {
045
046  private List<Backupable> backupables;
047  private DatabaseSession session;
048
049  protected static final String DATE_FORMAT = "yyyy-MM-dd";
050
051  protected Backup() {
052    backupables = new ArrayList<Backupable>();
053  }
054
055  public Backup(DatabaseSession session) {
056    this();
057    this.session = session;
058
059    backupables.add(new MetricsBackup(session));
060    backupables.add(new PropertiesBackup(session));
061    // Note that order is important, because profile can have reference to rule
062    backupables.add(new RulesBackup(session));
063    backupables.add(new ProfilesBackup(session));
064  }
065
066  /**
067   * For unit tests
068   */
069  Backup(List<Backupable> backupables) {
070    this();
071    this.backupables = backupables;
072  }
073
074  /*
075   * Export methods
076   */
077
078  public String exportXml() {
079    try {
080      startDb();
081      SonarConfig sonarConfig = new SonarConfig(getVersion(), getCurrentDate());
082      return exportXml(sonarConfig);
083    } finally {
084      stopDb();
085    }
086  }
087
088  protected String exportXml(SonarConfig sonarConfig) {
089    for (Backupable backupable : backupables) {
090      backupable.exportXml(sonarConfig);
091    }
092    String xml = getXmlFromSonarConfig(sonarConfig);
093    return addXmlHeader(xml);
094  }
095
096  protected String getXmlFromSonarConfig(SonarConfig sonarConfig) {
097    XStream xStream = getConfiguredXstream();
098    return xStream.toXML(sonarConfig);
099  }
100
101  private String addXmlHeader(String xml) {
102    return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n".concat(xml);
103  }
104
105  /*
106   * Import methods
107   */
108  public void importXml(String xml) {
109    try {
110      startDb();
111      doImportXml(xml);
112      LoggerFactory.getLogger(getClass()).info("Backup restored");
113    } finally {
114      stopDb();
115    }
116  }
117
118  void doImportXml(String xml) {
119    SonarConfig sonarConfig = getSonarConfigFromXml(xml);
120    importBackupablesXml(sonarConfig);
121  }
122
123  protected void importBackupablesXml(SonarConfig sonarConfig) {
124    for (Backupable backupable : backupables) {
125      backupable.importXml(sonarConfig);
126    }
127  }
128
129  protected SonarConfig getSonarConfigFromXml(String xml) {
130    try {
131      XStream xStream = getConfiguredXstream();
132      // Backward compatibility with old levels
133      xml = xml.replace("<level><![CDATA[ERROR]]></level>", "<level><![CDATA[MAJOR]]></level>");
134      xml = xml.replace("<level><![CDATA[WARNING]]></level>", "<level><![CDATA[INFO]]></level>");
135      InputStream inputStream = IOUtils.toInputStream(xml, CharEncoding.UTF_8);
136
137      return (SonarConfig) xStream.fromXML(inputStream);
138    } catch (IOException e) {
139      throw new RuntimeException("Can't read xml", e);
140    }
141  }
142
143  /*
144   * Utils methods
145   */
146  protected int getVersion() {
147    return DatabaseVersion.LAST_VERSION;
148  }
149
150  protected Date getCurrentDate() {
151    return new Date();
152  }
153
154  private XStream getConfiguredXstream() {
155    XStream xStream = new XStream(
156        new XppDriver() {
157          @Override
158          public HierarchicalStreamWriter createWriter(Writer out) {
159            return new PrettyPrintWriter(out) {
160              @Override
161              protected void writeText(QuickWriter writer, @Nullable String text) {
162                if (text != null) {
163                  writer.write("<![CDATA[");
164                  /*
165                  * See http://jira.codehaus.org/browse/SONAR-1605 According to XML specification (
166                  * http://www.w3.org/TR/REC-xml/#sec-cdata-sect ) CData section may contain everything except of sequence ']]>' so we will
167                  * split all occurrences of this sequence into two CDATA first one would contain ']]' and second '>'
168                  */
169                  text = StringUtils.replace(text, "]]>", "]]]]><![CDATA[>");
170                  writer.write(text);
171                  writer.write("]]>");
172                }
173              }
174            };
175          }
176        });
177
178    xStream.processAnnotations(SonarConfig.class);
179    xStream.addDefaultImplementation(ArrayList.class, Collection.class);
180    xStream.registerConverter(new DateConverter(DATE_FORMAT, new String[]{}));
181
182    for (Backupable backupable : backupables) {
183      backupable.configure(xStream);
184    }
185    return xStream;
186  }
187
188  private void startDb() {
189    session.start();
190  }
191
192  private void stopDb() {
193    session.stop();
194  }
195
196}