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.core.persistence.DatabaseVersion; 034 035 import javax.annotation.Nullable; 036 import java.io.IOException; 037 import java.io.InputStream; 038 import java.io.Writer; 039 import java.util.ArrayList; 040 import java.util.Collection; 041 import java.util.Date; 042 import java.util.List; 043 044 public 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 }