Browse Source

SONAR-3292 improve messages when database is not up-to-date

tags/3.0
Simon Brandhof 12 years ago
parent
commit
0a608327b2
26 changed files with 514 additions and 290 deletions
  1. 9
    2
      sonar-batch/src/main/java/org/sonar/batch/bootstrap/BootstrapModule.java
  2. 54
    0
      sonar-batch/src/main/java/org/sonar/batch/bootstrap/DatabaseBatchCompatibility.java
  3. 57
    0
      sonar-batch/src/test/java/org/sonar/batch/bootstrap/DatabaseBatchCompatibilityTest.java
  4. 31
    0
      sonar-core/src/main/java/org/sonar/core/persistence/BadDatabaseVersion.java
  5. 2
    1
      sonar-core/src/main/java/org/sonar/core/persistence/DatabaseMigrator.java
  6. 82
    0
      sonar-core/src/main/java/org/sonar/core/persistence/DatabaseVersion.java
  7. 2
    0
      sonar-core/src/main/java/org/sonar/core/persistence/MyBatis.java
  8. 32
    0
      sonar-core/src/main/java/org/sonar/core/persistence/SchemaMigrationDto.java
  9. 26
    0
      sonar-core/src/main/java/org/sonar/core/persistence/SchemaMigrationMapper.java
  10. 1
    56
      sonar-core/src/main/java/org/sonar/jpa/entity/SchemaMigration.java
  11. 5
    90
      sonar-core/src/main/java/org/sonar/jpa/session/AbstractDatabaseConnector.java
  12. 2
    17
      sonar-core/src/main/java/org/sonar/jpa/session/DefaultDatabaseConnector.java
  13. 3
    14
      sonar-core/src/main/java/org/sonar/jpa/session/MemoryDatabaseConnector.java
  14. 11
    0
      sonar-core/src/main/resources/org/sonar/core/persistence/SchemaMigrationMapper.xml
  15. 55
    0
      sonar-core/src/test/java/org/sonar/core/persistence/DatabaseVersionTest.java
  16. 0
    74
      sonar-core/src/test/java/org/sonar/jpa/entity/SchemaMigrationTest.java
  17. 8
    0
      sonar-core/src/test/resources/org/sonar/core/persistence/DatabaseVersionTest/getVersion.xml
  18. 1
    0
      sonar-core/src/test/resources/org/sonar/core/persistence/DatabaseVersionTest/getVersion_no_rows.xml
  19. 2
    2
      sonar-server/src/main/java/org/sonar/server/configuration/Backup.java
  20. 45
    0
      sonar-server/src/main/java/org/sonar/server/platform/DatabaseServerCompatibility.java
  21. 8
    8
      sonar-server/src/main/java/org/sonar/server/platform/DefaultServerUpgradeStatus.java
  22. 9
    10
      sonar-server/src/main/java/org/sonar/server/platform/Platform.java
  23. 1
    1
      sonar-server/src/main/webapp/WEB-INF/db/migrate/README.txt
  24. 54
    0
      sonar-server/src/test/java/org/sonar/server/platform/DatabaseServerCompatibilityTest.java
  25. 12
    13
      sonar-server/src/test/java/org/sonar/server/platform/DefaultServerUpgradeStatusTest.java
  26. 2
    2
      sonar-server/src/test/java/org/sonar/server/startup/DeleteDeprecatedMeasuresTest.java

+ 9
- 2
sonar-batch/src/main/java/org/sonar/batch/bootstrap/BootstrapModule.java View File

@@ -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);
}

+ 54
- 0
sonar-batch/src/main/java/org/sonar/batch/bootstrap/DatabaseBatchCompatibility.java View File

@@ -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);
}
}

}

+ 57
- 0
sonar-batch/src/test/java/org/sonar/batch/bootstrap/DatabaseBatchCompatibilityTest.java View File

@@ -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
}
}

+ 31
- 0
sonar-core/src/main/java/org/sonar/core/persistence/BadDatabaseVersion.java View File

@@ -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;
}
}

+ 2
- 1
sonar-core/src/main/java/org/sonar/core/persistence/DatabaseMigrator.java View File

@@ -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
*/

+ 82
- 0
sonar-core/src/main/java/org/sonar/core/persistence/DatabaseVersion.java View File

@@ -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;
}
}

+ 2
- 0
sonar-core/src/main/java/org/sonar/core/persistence/MyBatis.java View File

@@ -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);


+ 32
- 0
sonar-core/src/main/java/org/sonar/core/persistence/SchemaMigrationDto.java View File

@@ -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;
}
}

+ 26
- 0
sonar-core/src/main/java/org/sonar/core/persistence/SchemaMigrationMapper.java View File

@@ -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();
}

+ 1
- 56
sonar-core/src/main/java/org/sonar/jpa/entity/SchemaMigration.java View File

@@ -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();

+ 5
- 90
sonar-core/src/main/java/org/sonar/jpa/session/AbstractDatabaseConnector.java View File

@@ -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() {

+ 2
- 17
sonar-core/src/main/java/org/sonar/jpa/session/DefaultDatabaseConnector.java View File

@@ -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() {

+ 3
- 14
sonar-core/src/main/java/org/sonar/jpa/session/MemoryDatabaseConnector.java View File

@@ -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);
}


+ 11
- 0
sonar-core/src/main/resources/org/sonar/core/persistence/SchemaMigrationMapper.xml View File

@@ -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>


+ 55
- 0
sonar-core/src/test/java/org/sonar/core/persistence/DatabaseVersionTest.java View File

@@ -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));
}
}

+ 0
- 74
sonar-core/src/test/java/org/sonar/jpa/entity/SchemaMigrationTest.java View File

@@ -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();
}
}

+ 8
- 0
sonar-core/src/test/resources/org/sonar/core/persistence/DatabaseVersionTest/getVersion.xml View File

@@ -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>

+ 1
- 0
sonar-core/src/test/resources/org/sonar/core/persistence/DatabaseVersionTest/getVersion_no_rows.xml View File

@@ -0,0 +1 @@
<dataset></dataset>

+ 2
- 2
sonar-server/src/main/java/org/sonar/server/configuration/Backup.java View File

@@ -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() {

+ 45
- 0
sonar-server/src/main/java/org/sonar/server/platform/DatabaseServerCompatibility.java View File

@@ -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");
}
}

}

+ 8
- 8
sonar-server/src/main/java/org/sonar/server/platform/DefaultServerUpgradeStatus.java View File

@@ -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() {

+ 9
- 10
sonar-server/src/main/java/org/sonar/server/platform/Platform.java View File

@@ -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);

+ 1
- 1
sonar-server/src/main/webapp/WEB-INF/db/migrate/README.txt View File

@@ -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



+ 54
- 0
sonar-server/src/test/java/org/sonar/server/platform/DatabaseServerCompatibilityTest.java View File

@@ -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
}
}

+ 12
- 13
sonar-server/src/test/java/org/sonar/server/platform/DefaultServerUpgradeStatusTest.java View File

@@ -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));
}
}

+ 2
- 2
sonar-server/src/test/java/org/sonar/server/startup/DeleteDeprecatedMeasuresTest.java View File

@@ -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));

Loading…
Cancel
Save