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