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