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 }