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 }