@@ -27,6 +27,7 @@ import org.sonar.batch.MavenPluginExecutor; | |||
import org.sonar.batch.ServerMetadata; | |||
import org.sonar.batch.config.BatchSettings; | |||
import org.sonar.batch.config.BatchSettingsEnhancer; | |||
import org.sonar.core.persistence.DatabaseVersion; | |||
import org.sonar.jpa.session.DatabaseSessionProvider; | |||
import org.sonar.jpa.session.DefaultDatabaseConnector; | |||
import org.sonar.jpa.session.ThreadLocalDatabaseSessionFactory; | |||
@@ -64,14 +65,20 @@ public class BootstrapModule extends Module { | |||
// set as the current context classloader for hibernate, else it does not find the JDBC driver. | |||
Thread.currentThread().setContextClassLoader(bootstrapClassLoader); | |||
// mybatis | |||
addCoreSingleton(BatchDatabase.class); | |||
addCoreSingleton(MyBatis.class); | |||
addCoreSingleton(DefaultDatabaseConnector.class); | |||
addCoreSingleton(ThreadLocalDatabaseSessionFactory.class); | |||
addCoreSingleton(DatabaseVersion.class); | |||
addCoreSingleton(DatabaseBatchCompatibility.class); | |||
for (Class daoClass : DaoUtils.getDaoClasses()) { | |||
addCoreSingleton(daoClass); | |||
} | |||
// hibernate | |||
addCoreSingleton(DefaultDatabaseConnector.class); | |||
addCoreSingleton(ThreadLocalDatabaseSessionFactory.class); | |||
addAdapter(new DatabaseSessionProvider()); | |||
for (Object component : boostrapperComponents) { | |||
addCoreSingleton(component); | |||
} |
@@ -0,0 +1,54 @@ | |||
/* | |||
* Sonar, open source software quality management tool. | |||
* Copyright (C) 2008-2012 SonarSource | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* Sonar is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* Sonar is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public | |||
* License along with Sonar; if not, write to the Free Software | |||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 | |||
*/ | |||
package org.sonar.batch.bootstrap; | |||
import org.sonar.api.BatchComponent; | |||
import org.sonar.api.platform.Server; | |||
import org.sonar.core.persistence.BadDatabaseVersion; | |||
import org.sonar.core.persistence.DatabaseVersion; | |||
/** | |||
* Detects if database is not up-to-date with the version required by the batch. | |||
*/ | |||
public class DatabaseBatchCompatibility implements BatchComponent { | |||
private DatabaseVersion version; | |||
private Server server; | |||
public DatabaseBatchCompatibility(DatabaseVersion version, Server server) { | |||
this.version = version; | |||
this.server = server; | |||
} | |||
public void start() { | |||
DatabaseVersion.Status status = version.getStatus(); | |||
if (status == DatabaseVersion.Status.REQUIRES_DOWNGRADE) { | |||
throw new BadDatabaseVersion("Database relates to a more recent version of Sonar. Please check your settings (JDBC settings, version of Maven plugin)"); | |||
} | |||
if (status == DatabaseVersion.Status.REQUIRES_UPGRADE) { | |||
throw new BadDatabaseVersion("Database must be upgraded. Please browse " + server.getURL() + "/setup"); | |||
} | |||
if (status != DatabaseVersion.Status.UP_TO_DATE) { | |||
// Support other future values | |||
throw new BadDatabaseVersion("Unknown database status: " + status); | |||
} | |||
} | |||
} |
@@ -0,0 +1,57 @@ | |||
/* | |||
* Sonar, open source software quality management tool. | |||
* Copyright (C) 2008-2012 SonarSource | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* Sonar is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* Sonar is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public | |||
* License along with Sonar; if not, write to the Free Software | |||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 | |||
*/ | |||
package org.sonar.batch.bootstrap; | |||
import org.junit.Test; | |||
import org.sonar.api.config.Settings; | |||
import org.sonar.api.platform.Server; | |||
import org.sonar.batch.ServerMetadata; | |||
import org.sonar.core.persistence.BadDatabaseVersion; | |||
import org.sonar.core.persistence.DatabaseVersion; | |||
import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.when; | |||
public class DatabaseBatchCompatibilityTest { | |||
private Server server = new ServerMetadata(new Settings()); | |||
@Test(expected = BadDatabaseVersion.class) | |||
public void shouldFailIfRequiresDowngrade() { | |||
DatabaseVersion version = mock(DatabaseVersion.class); | |||
when(version.getStatus()).thenReturn(DatabaseVersion.Status.REQUIRES_DOWNGRADE); | |||
new DatabaseBatchCompatibility(version, server).start(); | |||
} | |||
@Test(expected = BadDatabaseVersion.class) | |||
public void shouldFailIfRequiresUpgrade() { | |||
DatabaseVersion version = mock(DatabaseVersion.class); | |||
when(version.getStatus()).thenReturn(DatabaseVersion.Status.REQUIRES_UPGRADE); | |||
new DatabaseBatchCompatibility(version, server).start(); | |||
} | |||
@Test | |||
public void shouldDoNothingIfUpToDate() { | |||
DatabaseVersion version = mock(DatabaseVersion.class); | |||
when(version.getStatus()).thenReturn(DatabaseVersion.Status.UP_TO_DATE); | |||
new DatabaseBatchCompatibility(version, server).start(); | |||
// no error | |||
} | |||
} |
@@ -0,0 +1,31 @@ | |||
/* | |||
* Sonar, open source software quality management tool. | |||
* Copyright (C) 2008-2012 SonarSource | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* Sonar is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* Sonar is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public | |||
* License along with Sonar; if not, write to the Free Software | |||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 | |||
*/ | |||
package org.sonar.core.persistence; | |||
public final class BadDatabaseVersion extends RuntimeException { | |||
public BadDatabaseVersion(String s) { | |||
super(s); | |||
} | |||
@Override | |||
public Throwable fillInStackTrace() { | |||
return this; | |||
} | |||
} |
@@ -26,7 +26,8 @@ import org.sonar.api.ServerComponent; | |||
import java.sql.Connection; | |||
/** | |||
* Restore schema by executing DDL scripts. Only Derby database is supported. Other databases are created by Ruby on Rails migrations. | |||
* Restore schema by executing DDL scripts. Only Derby database is supported. | |||
* Other databases are created by Ruby on Rails migrations. | |||
* | |||
* @since 2.12 | |||
*/ |
@@ -0,0 +1,82 @@ | |||
/* | |||
* Sonar, open source software quality management tool. | |||
* Copyright (C) 2008-2012 SonarSource | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* Sonar is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* Sonar is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public | |||
* License along with Sonar; if not, write to the Free Software | |||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 | |||
*/ | |||
package org.sonar.core.persistence; | |||
import com.google.common.annotations.VisibleForTesting; | |||
import org.apache.ibatis.session.SqlSession; | |||
import org.sonar.api.BatchComponent; | |||
import org.sonar.api.ServerComponent; | |||
import java.util.Collections; | |||
import java.util.List; | |||
/** | |||
* @since 2.15 | |||
*/ | |||
public class DatabaseVersion implements BatchComponent, ServerComponent { | |||
public static final int LAST_VERSION = 263; | |||
public static enum Status { | |||
UP_TO_DATE, REQUIRES_UPGRADE, REQUIRES_DOWNGRADE | |||
} | |||
private MyBatis mybatis; | |||
public DatabaseVersion(MyBatis mybatis) { | |||
this.mybatis = mybatis; | |||
} | |||
public Integer getVersion() { | |||
SqlSession session = mybatis.openSession(); | |||
try { | |||
List<Integer> versions = session.getMapper(SchemaMigrationMapper.class).selectVersions(); | |||
if (!versions.isEmpty()) { | |||
Collections.sort(versions); | |||
return versions.get(versions.size() - 1); | |||
} | |||
return null; | |||
} catch (Exception e) { | |||
// The table SCHEMA_MIGRATIONS does not exist. | |||
// Ignore this exception -> it will be created by Ruby on Rails migrations. | |||
return null; | |||
} finally { | |||
MyBatis.closeQuietly(session); | |||
} | |||
} | |||
public Status getStatus() { | |||
return getStatus(getVersion(), LAST_VERSION); | |||
} | |||
@VisibleForTesting | |||
static Status getStatus(Integer currentVersion, int lastVersion) { | |||
Status status = Status.REQUIRES_UPGRADE; | |||
if (currentVersion != null) { | |||
if (currentVersion == lastVersion) { | |||
status = Status.UP_TO_DATE; | |||
} else if (currentVersion > lastVersion) { | |||
status = Status.REQUIRES_DOWNGRADE; | |||
} | |||
} | |||
return status; | |||
} | |||
} |
@@ -75,6 +75,7 @@ public class MyBatis implements BatchComponent, ServerComponent { | |||
loadAlias(conf, "ResourceIndex", ResourceIndexDto.class); | |||
loadAlias(conf, "Rule", RuleDto.class); | |||
loadAlias(conf, "Snapshot", SnapshotDto.class); | |||
loadAlias(conf, "SchemaMigration", SchemaMigrationDto.class); | |||
loadAlias(conf, "Widget", WidgetDto.class); | |||
loadAlias(conf, "WidgetProperty", WidgetPropertyDto.class); | |||
@@ -89,6 +90,7 @@ public class MyBatis implements BatchComponent, ServerComponent { | |||
loadMapper(conf, ReviewMapper.class); | |||
loadMapper(conf, ResourceIndexerMapper.class); | |||
loadMapper(conf, RuleMapper.class); | |||
loadMapper(conf, SchemaMigrationMapper.class); | |||
loadMapper(conf, WidgetMapper.class); | |||
loadMapper(conf, WidgetPropertyMapper.class); | |||
@@ -0,0 +1,32 @@ | |||
/* | |||
* Sonar, open source software quality management tool. | |||
* Copyright (C) 2008-2012 SonarSource | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* Sonar is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* Sonar is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public | |||
* License along with Sonar; if not, write to the Free Software | |||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 | |||
*/ | |||
package org.sonar.core.persistence; | |||
/** | |||
* Maps the table SCHEMA_MIGRATIONS that is fed by Ruby on Rails Migrations | |||
* @since 2.15 | |||
*/ | |||
public class SchemaMigrationDto { | |||
private String version;//NOSONAR this field is assigned by MyBatis | |||
public String getVersion() { | |||
return version; | |||
} | |||
} |
@@ -0,0 +1,26 @@ | |||
/* | |||
* Sonar, open source software quality management tool. | |||
* Copyright (C) 2008-2012 SonarSource | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* Sonar is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* Sonar is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public | |||
* License along with Sonar; if not, write to the Free Software | |||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 | |||
*/ | |||
package org.sonar.core.persistence; | |||
import java.util.List; | |||
public interface SchemaMigrationMapper { | |||
List<Integer> selectVersions(); | |||
} |
@@ -24,22 +24,10 @@ import org.apache.commons.lang.builder.ToStringStyle; | |||
import javax.persistence.*; | |||
import java.sql.Connection; | |||
import java.sql.ResultSet; | |||
import java.sql.SQLException; | |||
import java.sql.Statement; | |||
@Entity | |||
@Table(name = SchemaMigration.TABLE_NAME, uniqueConstraints = {@UniqueConstraint(columnNames = {"version"})}) | |||
@Table(name = "schema_migrations", uniqueConstraints = {@UniqueConstraint(columnNames = {"version"})}) | |||
public class SchemaMigration { | |||
public static final int VERSION_UNKNOWN = -1; | |||
public static final int LAST_VERSION = 263; | |||
public static final int VERSION_2_13 = 241; | |||
public static final String TABLE_NAME = "schema_migrations"; | |||
@Id | |||
@Column(name = "version", updatable = true) | |||
private String version; | |||
@@ -56,49 +44,6 @@ public class SchemaMigration { | |||
this.version = String.valueOf(i); | |||
} | |||
public static int getCurrentVersion(Connection connection) { | |||
Statement stmt = null; | |||
ResultSet rs = null; | |||
int version = VERSION_UNKNOWN; | |||
try { | |||
stmt = connection.createStatement(); | |||
rs = stmt.executeQuery("SELECT version FROM " + SchemaMigration.TABLE_NAME); | |||
while (rs.next()) { | |||
int i = Integer.parseInt(rs.getString(1)); | |||
if (i > version) { | |||
version = i; | |||
} | |||
} | |||
} catch (SQLException e) { | |||
// ignore | |||
} finally { | |||
close(rs); | |||
close(stmt); | |||
} | |||
return version; | |||
} | |||
private static void close(ResultSet rs) { | |||
if (rs != null) { | |||
try { | |||
rs.close(); | |||
} catch (SQLException e) { | |||
// why does close() throw a checked-exception ??? | |||
} | |||
} | |||
} | |||
private static void close(Statement st) { | |||
if (st != null) { | |||
try { | |||
st.close(); | |||
} catch (SQLException e) { | |||
// why does close() throw a checked-exception ??? | |||
} | |||
} | |||
} | |||
@Override | |||
public String toString() { | |||
return new ReflectionToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).toString(); |
@@ -21,16 +21,13 @@ package org.sonar.jpa.session; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
import org.sonar.api.utils.Logs; | |||
import org.sonar.core.persistence.Database; | |||
import org.sonar.core.persistence.DatabaseVersion; | |||
import org.sonar.core.persistence.dialect.Dialect; | |||
import org.sonar.jpa.entity.SchemaMigration; | |||
import javax.persistence.EntityManager; | |||
import javax.persistence.EntityManagerFactory; | |||
import javax.persistence.Persistence; | |||
import java.sql.Connection; | |||
import java.sql.SQLException; | |||
import java.util.Map; | |||
import java.util.Properties; | |||
@@ -39,45 +36,15 @@ public abstract class AbstractDatabaseConnector implements DatabaseConnector { | |||
protected Database database; | |||
private EntityManagerFactory factory = null; | |||
private int databaseVersion = SchemaMigration.VERSION_UNKNOWN; | |||
private boolean operational = false; | |||
private boolean started = false; | |||
protected AbstractDatabaseConnector(Database database) { | |||
this.database = database; | |||
} | |||
public String getDialectId() { | |||
return database.getDialect().getId(); | |||
} | |||
/** | |||
* Indicates if the connector is operational : database connection OK and schema version OK | |||
*/ | |||
public boolean isOperational() { | |||
return operational; | |||
} | |||
/** | |||
* Indicates if the connector is started : database connection OK and schema version OK or KO | |||
*/ | |||
protected boolean isStarted() { | |||
return started; | |||
} | |||
public void start() { | |||
if (!started) { | |||
testConnection(); | |||
started = true; | |||
} | |||
if (!operational) { | |||
boolean upToDate = upToDateSchemaVersion(); | |||
if (upToDate) { | |||
Logs.INFO.info("Initializing Hibernate"); | |||
factory = createEntityManagerFactory(); | |||
operational = true; | |||
} | |||
} | |||
LOG.info("Initializing Hibernate"); | |||
factory = createEntityManagerFactory(); | |||
} | |||
public void stop() { | |||
@@ -85,8 +52,6 @@ public abstract class AbstractDatabaseConnector implements DatabaseConnector { | |||
factory.close(); | |||
factory = null; | |||
} | |||
operational = false; | |||
started = false; | |||
database = null; | |||
} | |||
@@ -94,10 +59,6 @@ public abstract class AbstractDatabaseConnector implements DatabaseConnector { | |||
return factory; | |||
} | |||
protected void setEntityManagerFactory(EntityManagerFactory factory) { | |||
this.factory = factory; | |||
} | |||
protected EntityManagerFactory createEntityManagerFactory() { | |||
// other settings are stored into /META-INF/persistence.xml | |||
Properties props = database.getHibernateProperties(); | |||
@@ -117,54 +78,8 @@ public abstract class AbstractDatabaseConnector implements DatabaseConnector { | |||
return factory.createEntityManager(); | |||
} | |||
private String testConnection() throws DatabaseException { | |||
Connection connection = null; | |||
try { | |||
connection = getConnection(); | |||
return connection.getMetaData().getURL(); | |||
} catch (SQLException e) { | |||
throw new DatabaseException("Cannot open connection to database: " + e.getMessage(), e); | |||
} finally { | |||
close(connection); | |||
} | |||
} | |||
protected int loadVersion() { | |||
Connection connection = null; | |||
try { | |||
connection = getConnection(); | |||
return SchemaMigration.getCurrentVersion(connection); | |||
} catch (SQLException e) { | |||
// schema not created | |||
return 0; | |||
} finally { | |||
close(connection); | |||
} | |||
} | |||
private void close(Connection connection) { | |||
if (connection != null) { | |||
try { | |||
connection.close(); | |||
} catch (SQLException e) { | |||
// why does close() throw a checked-exception ??? | |||
} | |||
} | |||
} | |||
protected boolean upToDateSchemaVersion() { | |||
if (databaseVersion == SchemaMigration.LAST_VERSION) { | |||
return true; | |||
} | |||
databaseVersion = loadVersion(); | |||
return databaseVersion == SchemaMigration.LAST_VERSION; | |||
} | |||
public final int getDatabaseVersion() { | |||
return databaseVersion; | |||
throw new UnsupportedOperationException("Moved to " + DatabaseVersion.class.getCanonicalName()); | |||
} | |||
public final Dialect getDialect() { |
@@ -21,7 +21,6 @@ package org.sonar.jpa.session; | |||
import org.sonar.api.utils.SonarException; | |||
import org.sonar.core.persistence.Database; | |||
import org.sonar.jpa.entity.SchemaMigration; | |||
import java.sql.Connection; | |||
import java.sql.SQLException; | |||
@@ -32,24 +31,10 @@ public class DefaultDatabaseConnector extends AbstractDatabaseConnector { | |||
super(database); | |||
} | |||
@Override | |||
public boolean isOperational() { | |||
if (isStarted() && getDatabaseVersion() != SchemaMigration.LAST_VERSION) { | |||
// connector was started and connection OK but schema version was not OK | |||
// call start again to check if this is now ok (schema created by rails) | |||
start(); | |||
} | |||
return super.isOperational(); | |||
} | |||
@Override | |||
public void start() { | |||
if (!isStarted()) { | |||
createDatasource(); | |||
} | |||
if (!super.isOperational()) { | |||
super.start(); | |||
} | |||
createDatasource(); | |||
super.start(); | |||
} | |||
private void createDatasource() { |
@@ -20,6 +20,7 @@ | |||
package org.sonar.jpa.session; | |||
import org.sonar.core.persistence.Database; | |||
import org.sonar.core.persistence.DatabaseVersion; | |||
import org.sonar.jpa.entity.SchemaMigration; | |||
import javax.persistence.EntityManager; | |||
@@ -29,24 +30,12 @@ public class MemoryDatabaseConnector extends DefaultDatabaseConnector { | |||
public MemoryDatabaseConnector(Database database) { | |||
super(database); | |||
version = SchemaMigration.LAST_VERSION; | |||
} | |||
public MemoryDatabaseConnector(Database database, int version) { | |||
this(database); | |||
this.version = version; | |||
version = DatabaseVersion.LAST_VERSION; | |||
} | |||
@Override | |||
public void start() { | |||
try { | |||
super.start(); | |||
} catch (DatabaseException ex) { | |||
if (!isStarted()) { | |||
throw ex; | |||
} | |||
} | |||
setEntityManagerFactory(createEntityManagerFactory()); | |||
super.start(); | |||
setupSchemaVersion(version); | |||
} | |||
@@ -0,0 +1,11 @@ | |||
<?xml version="1.0" encoding="UTF-8" ?> | |||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> | |||
<mapper namespace="org.sonar.core.persistence.SchemaMigrationMapper"> | |||
<select id="selectVersions" resultType="int"> | |||
select * from schema_migrations | |||
</select> | |||
</mapper> | |||
@@ -0,0 +1,55 @@ | |||
/* | |||
* Sonar, open source software quality management tool. | |||
* Copyright (C) 2008-2012 SonarSource | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* Sonar is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* Sonar is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public | |||
* License along with Sonar; if not, write to the Free Software | |||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 | |||
*/ | |||
package org.sonar.core.persistence; | |||
import org.hamcrest.core.Is; | |||
import org.junit.Test; | |||
import static org.hamcrest.CoreMatchers.is; | |||
import static org.hamcrest.CoreMatchers.nullValue; | |||
import static org.junit.Assert.assertThat; | |||
public class DatabaseVersionTest extends DaoTestCase { | |||
@Test | |||
public void getVersion() { | |||
setupData("getVersion"); | |||
Integer version = new DatabaseVersion(getMyBatis()).getVersion(); | |||
assertThat(version, Is.is(123)); | |||
} | |||
@Test | |||
public void getVersion_no_rows() { | |||
setupData("getVersion_no_rows"); | |||
Integer version = new DatabaseVersion(getMyBatis()).getVersion(); | |||
assertThat(version, nullValue()); | |||
} | |||
@Test | |||
public void getStatus() { | |||
assertThat(DatabaseVersion.getStatus(null, 150), is(DatabaseVersion.Status.REQUIRES_UPGRADE)); | |||
assertThat(DatabaseVersion.getStatus(123, 150), is(DatabaseVersion.Status.REQUIRES_UPGRADE)); | |||
assertThat(DatabaseVersion.getStatus(150, 150), is(DatabaseVersion.Status.UP_TO_DATE)); | |||
assertThat(DatabaseVersion.getStatus(200, 150), is(DatabaseVersion.Status.REQUIRES_DOWNGRADE)); | |||
} | |||
} |
@@ -1,74 +0,0 @@ | |||
/* | |||
* Sonar, open source software quality management tool. | |||
* Copyright (C) 2008-2012 SonarSource | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* Sonar is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* Sonar is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public | |||
* License along with Sonar; if not, write to the Free Software | |||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 | |||
*/ | |||
package org.sonar.jpa.entity; | |||
import org.junit.Test; | |||
import org.sonar.core.persistence.Database; | |||
import org.sonar.core.persistence.InMemoryDatabase; | |||
import org.sonar.jpa.session.MemoryDatabaseConnector; | |||
import java.sql.Connection; | |||
import static org.junit.Assert.assertEquals; | |||
public class SchemaMigrationTest { | |||
@Test | |||
public void currentVersionShouldBeUnknownWhenSchemaIsEmpty() throws Exception { | |||
Database database = new InMemoryDatabase(); | |||
database.start(); | |||
MemoryDatabaseConnector connector = new MemoryDatabaseConnector(database, SchemaMigration.VERSION_UNKNOWN); | |||
connector.start(); | |||
Connection connection = null; | |||
try { | |||
connection = connector.getConnection(); | |||
assertEquals(SchemaMigration.VERSION_UNKNOWN, SchemaMigration.getCurrentVersion(connection)); | |||
} finally { | |||
if (connection != null) { | |||
connection.close(); | |||
} | |||
} | |||
connector.stop(); | |||
database.stop(); | |||
} | |||
@Test | |||
public void versionShouldBeLoadedFromSchemaMigrationsTable() throws Exception { | |||
Database database = new InMemoryDatabase(); | |||
database.start(); | |||
MemoryDatabaseConnector connector = new MemoryDatabaseConnector(database, 30); | |||
connector.start(); | |||
Connection connection = null; | |||
try { | |||
connection = connector.getConnection(); | |||
assertEquals(30, SchemaMigration.getCurrentVersion(connection)); | |||
} finally { | |||
if (connection != null) { | |||
connection.close(); | |||
} | |||
} | |||
connector.stop(); | |||
database.stop(); | |||
} | |||
} |
@@ -0,0 +1,8 @@ | |||
<dataset> | |||
<schema_migrations version="1"/> | |||
<schema_migrations version="2"/> | |||
<schema_migrations version="4"/> | |||
<schema_migrations version="123"/> | |||
<schema_migrations version="50"/> | |||
</dataset> |
@@ -0,0 +1 @@ | |||
<dataset></dataset> |
@@ -30,7 +30,7 @@ import org.apache.commons.lang.CharEncoding; | |||
import org.apache.commons.lang.StringUtils; | |||
import org.slf4j.LoggerFactory; | |||
import org.sonar.api.database.DatabaseSession; | |||
import org.sonar.jpa.entity.SchemaMigration; | |||
import org.sonar.core.persistence.DatabaseVersion; | |||
import javax.annotation.Nullable; | |||
import java.io.IOException; | |||
@@ -144,7 +144,7 @@ public class Backup { | |||
* Utils methods | |||
*/ | |||
protected int getVersion() { | |||
return SchemaMigration.LAST_VERSION; | |||
return DatabaseVersion.LAST_VERSION; | |||
} | |||
protected Date getCurrentDate() { |
@@ -0,0 +1,45 @@ | |||
/* | |||
* Sonar, open source software quality management tool. | |||
* Copyright (C) 2008-2012 SonarSource | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* Sonar is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* Sonar is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public | |||
* License along with Sonar; if not, write to the Free Software | |||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 | |||
*/ | |||
package org.sonar.server.platform; | |||
import org.slf4j.LoggerFactory; | |||
import org.sonar.api.ServerComponent; | |||
import org.sonar.core.persistence.BadDatabaseVersion; | |||
import org.sonar.core.persistence.DatabaseVersion; | |||
public class DatabaseServerCompatibility implements ServerComponent { | |||
private DatabaseVersion version; | |||
public DatabaseServerCompatibility(DatabaseVersion version) { | |||
this.version = version; | |||
} | |||
public void start() { | |||
DatabaseVersion.Status status = version.getStatus(); | |||
if (status== DatabaseVersion.Status.REQUIRES_DOWNGRADE) { | |||
throw new BadDatabaseVersion("Database relates to a more recent version of sonar. Please check your settings."); | |||
} | |||
if (status== DatabaseVersion.Status.REQUIRES_UPGRADE) { | |||
LoggerFactory.getLogger(DatabaseServerCompatibility.class).info("Database must be upgraded. Please browse /setup"); | |||
} | |||
} | |||
} |
@@ -22,8 +22,7 @@ package org.sonar.server.platform; | |||
import org.apache.commons.lang.builder.ReflectionToStringBuilder; | |||
import org.apache.commons.lang.builder.ToStringStyle; | |||
import org.sonar.api.platform.ServerUpgradeStatus; | |||
import org.sonar.jpa.entity.SchemaMigration; | |||
import org.sonar.jpa.session.DatabaseConnector; | |||
import org.sonar.core.persistence.DatabaseVersion; | |||
/** | |||
* @since 2.5 | |||
@@ -31,22 +30,23 @@ import org.sonar.jpa.session.DatabaseConnector; | |||
public final class DefaultServerUpgradeStatus implements ServerUpgradeStatus { | |||
private int initialDbVersion; | |||
private DatabaseConnector dbConnector; | |||
private DatabaseVersion dbVersion; | |||
public DefaultServerUpgradeStatus(DatabaseConnector dbConnector) { | |||
this.dbConnector = dbConnector; | |||
public DefaultServerUpgradeStatus(DatabaseVersion dbVersion) { | |||
this.dbVersion = dbVersion; | |||
} | |||
public void start() { | |||
this.initialDbVersion = dbConnector.getDatabaseVersion(); | |||
Integer v = dbVersion.getVersion(); | |||
this.initialDbVersion = (v != null ? v : -1); | |||
} | |||
public boolean isUpgraded() { | |||
return !isFreshInstall() &&(initialDbVersion < SchemaMigration.LAST_VERSION); | |||
return !isFreshInstall() && (initialDbVersion < DatabaseVersion.LAST_VERSION); | |||
} | |||
public boolean isFreshInstall() { | |||
return initialDbVersion <= 0; | |||
return initialDbVersion<0; | |||
} | |||
public int getInitialDbVersion() { |
@@ -40,10 +40,7 @@ import org.sonar.core.i18n.I18nManager; | |||
import org.sonar.core.i18n.RuleI18nManager; | |||
import org.sonar.core.metric.DefaultMetricFinder; | |||
import org.sonar.core.notification.DefaultNotificationManager; | |||
import org.sonar.core.persistence.DaoUtils; | |||
import org.sonar.core.persistence.DatabaseMigrator; | |||
import org.sonar.core.persistence.DefaultDatabase; | |||
import org.sonar.core.persistence.MyBatis; | |||
import org.sonar.core.persistence.*; | |||
import org.sonar.core.qualitymodel.DefaultModelFinder; | |||
import org.sonar.core.rule.DefaultRuleFinder; | |||
import org.sonar.core.user.DefaultUserFinder; | |||
@@ -108,9 +105,9 @@ public final class Platform { | |||
} | |||
public void start() { | |||
if (!started && isUpToDateDatabase()) { | |||
if (!started && getDatabaseStatus()== DatabaseVersion.Status.UP_TO_DATE) { | |||
try { | |||
TimeProfiler profiler = new TimeProfiler().start("Start services"); | |||
TimeProfiler profiler = new TimeProfiler().start("Start components"); | |||
startCoreComponents(); | |||
startServiceComponents(); | |||
executeStartupTasks(); | |||
@@ -133,22 +130,24 @@ public final class Platform { | |||
rootContainer.addSingleton(EmbeddedDatabaseFactory.class); | |||
rootContainer.addSingleton(DefaultDatabase.class); | |||
rootContainer.addSingleton(MyBatis.class); | |||
rootContainer.addSingleton(DefaultDatabaseConnector.class); | |||
rootContainer.addSingleton(DefaultServerUpgradeStatus.class); | |||
rootContainer.addSingleton(DatabaseServerCompatibility.class); | |||
rootContainer.addSingleton(DatabaseMigrator.class); | |||
rootContainer.addSingleton(DatabaseVersion.class); | |||
for (Class daoClass : DaoUtils.getDaoClasses()) { | |||
rootContainer.addSingleton(daoClass); | |||
} | |||
rootContainer.startComponents(); | |||
} | |||
private boolean isUpToDateDatabase() { | |||
DefaultDatabaseConnector databaseConnector = getContainer().getComponentByType(DefaultDatabaseConnector.class); | |||
return databaseConnector.isOperational(); | |||
private DatabaseVersion.Status getDatabaseStatus() { | |||
DatabaseVersion version = getContainer().getComponentByType(DatabaseVersion.class); | |||
return version.getStatus(); | |||
} | |||
private void startCoreComponents() { | |||
coreContainer = rootContainer.createChild(); | |||
coreContainer.addSingleton(DefaultDatabaseConnector.class); | |||
coreContainer.addSingleton(PluginDeployer.class); | |||
coreContainer.addSingleton(DefaultServerPluginRepository.class); | |||
coreContainer.addSingleton(ServerExtensionInstaller.class); |
@@ -5,7 +5,7 @@ HOW TO ADD A MIGRATION | |||
+ sonar-core/src/main/resources/org/sonar/core/persistence/schema-derby.ddl | |||
+ sonar-core/src/main/resources/org/sonar/core/persistence/rows-derby.sql : | |||
- add "INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('<THE MIGRATION ID>')" | |||
* Update the migration id defined in sonar-core/src/main/java/org/sonar/jpa/entity/SchemaMigration.java | |||
* Update the migration id defined in sonar-core/src/main/java/org/sonar/core/persistence/DatabaseVersion.java | |||
* If a table is added or removed, then edit sonar-core/src/main/java/org/sonar/core/persistence/DatabaseUtils.java | |||
@@ -0,0 +1,54 @@ | |||
/* | |||
* Sonar, open source software quality management tool. | |||
* Copyright (C) 2008-2012 SonarSource | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* Sonar is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* Sonar is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public | |||
* License along with Sonar; if not, write to the Free Software | |||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 | |||
*/ | |||
package org.sonar.server.platform; | |||
import org.junit.Test; | |||
import org.sonar.core.persistence.BadDatabaseVersion; | |||
import org.sonar.core.persistence.DatabaseVersion; | |||
import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.when; | |||
public class DatabaseServerCompatibilityTest { | |||
@Test(expected = BadDatabaseVersion.class) | |||
public void shouldFailIfRequiresDowngrade() { | |||
DatabaseVersion version = mock(DatabaseVersion.class); | |||
when(version.getStatus()).thenReturn(DatabaseVersion.Status.REQUIRES_DOWNGRADE); | |||
new DatabaseServerCompatibility(version).start(); | |||
} | |||
@Test | |||
public void shouldLogWarningIfRequiresUpgrade() { | |||
DatabaseVersion version = mock(DatabaseVersion.class); | |||
when(version.getStatus()).thenReturn(DatabaseVersion.Status.REQUIRES_UPGRADE); | |||
new DatabaseServerCompatibility(version).start(); | |||
// oh well... how to simply test logging ? | |||
// Let's assume that this test verifies that no error is raised. | |||
} | |||
@Test | |||
public void shouldDoNothingIfUpToDate() { | |||
DatabaseVersion version = mock(DatabaseVersion.class); | |||
when(version.getStatus()).thenReturn(DatabaseVersion.Status.UP_TO_DATE); | |||
new DatabaseServerCompatibility(version).start(); | |||
// no error | |||
} | |||
} |
@@ -20,8 +20,7 @@ | |||
package org.sonar.server.platform; | |||
import org.junit.Test; | |||
import org.sonar.jpa.entity.SchemaMigration; | |||
import org.sonar.jpa.session.DatabaseConnector; | |||
import org.sonar.core.persistence.DatabaseVersion; | |||
import static org.hamcrest.core.Is.is; | |||
import static org.junit.Assert.assertThat; | |||
@@ -32,10 +31,10 @@ public class DefaultServerUpgradeStatusTest { | |||
@Test | |||
public void shouldBeFreshInstallation() { | |||
DatabaseConnector connector = mock(DatabaseConnector.class); | |||
when(connector.getDatabaseVersion()).thenReturn(-1); | |||
DatabaseVersion dbVersion = mock(DatabaseVersion.class); | |||
when(dbVersion.getVersion()).thenReturn(null); | |||
DefaultServerUpgradeStatus status = new DefaultServerUpgradeStatus(connector); | |||
DefaultServerUpgradeStatus status = new DefaultServerUpgradeStatus(dbVersion); | |||
status.start(); | |||
assertThat(status.isFreshInstall(), is(true)); | |||
@@ -45,10 +44,10 @@ public class DefaultServerUpgradeStatusTest { | |||
@Test | |||
public void shouldBeUpgraded() { | |||
DatabaseConnector connector = mock(DatabaseConnector.class); | |||
when(connector.getDatabaseVersion()).thenReturn(50); | |||
DatabaseVersion dbVersion = mock(DatabaseVersion.class); | |||
when(dbVersion.getVersion()).thenReturn(50); | |||
DefaultServerUpgradeStatus status = new DefaultServerUpgradeStatus(connector); | |||
DefaultServerUpgradeStatus status = new DefaultServerUpgradeStatus(dbVersion); | |||
status.start(); | |||
assertThat(status.isFreshInstall(), is(false)); | |||
@@ -58,14 +57,14 @@ public class DefaultServerUpgradeStatusTest { | |||
@Test | |||
public void shouldNotBeUpgraded() { | |||
DatabaseConnector connector = mock(DatabaseConnector.class); | |||
when(connector.getDatabaseVersion()).thenReturn(SchemaMigration.LAST_VERSION); | |||
DatabaseVersion dbVersion = mock(DatabaseVersion.class); | |||
when(dbVersion.getVersion()).thenReturn(DatabaseVersion.LAST_VERSION); | |||
DefaultServerUpgradeStatus status = new DefaultServerUpgradeStatus(connector); | |||
DefaultServerUpgradeStatus status = new DefaultServerUpgradeStatus(dbVersion); | |||
status.start(); | |||
assertThat(status.isFreshInstall(), is(false)); | |||
assertThat(status.isUpgraded(), is(false)); | |||
assertThat(status.getInitialDbVersion(), is(SchemaMigration.LAST_VERSION)); | |||
assertThat(status.getInitialDbVersion(), is(DatabaseVersion.LAST_VERSION)); | |||
} | |||
} |
@@ -22,7 +22,7 @@ package org.sonar.server.startup; | |||
import org.junit.Test; | |||
import org.sonar.api.database.model.MeasureModel; | |||
import org.sonar.api.platform.ServerUpgradeStatus; | |||
import org.sonar.jpa.entity.SchemaMigration; | |||
import org.sonar.core.persistence.DatabaseVersion; | |||
import org.sonar.jpa.test.AbstractDbUnitTestCase; | |||
import java.sql.SQLException; | |||
@@ -98,7 +98,7 @@ public class DeleteDeprecatedMeasuresTest extends AbstractDbUnitTestCase { | |||
public void shouldNotDoPurgeOnStandardStartup() { | |||
ServerUpgradeStatus upgradeStatus = mock(ServerUpgradeStatus.class); | |||
when(upgradeStatus.isUpgraded()).thenReturn(false); | |||
when(upgradeStatus.getInitialDbVersion()).thenReturn(SchemaMigration.LAST_VERSION); | |||
when(upgradeStatus.getInitialDbVersion()).thenReturn(DatabaseVersion.LAST_VERSION); | |||
final DeleteDeprecatedMeasures purge = new DeleteDeprecatedMeasures(getSessionFactory(), upgradeStatus); | |||
assertThat(purge.mustDoPurge(), is(false)); |