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 }