diff options
author | Simon Brandhof <simon.brandhof@gmail.com> | 2012-03-07 22:13:35 +0100 |
---|---|---|
committer | Simon Brandhof <simon.brandhof@gmail.com> | 2012-03-07 23:30:28 +0100 |
commit | 0a608327b220e0d89a380fa4499af5247e1e85d5 (patch) | |
tree | f2e9b8ca5253606ff1a6c03fbd08f2364268d55b | |
parent | 6cc1508a6207bf923da34659722e23e2e5c17599 (diff) | |
download | sonarqube-0a608327b220e0d89a380fa4499af5247e1e85d5.tar.gz sonarqube-0a608327b220e0d89a380fa4499af5247e1e85d5.zip |
SONAR-3292 improve messages when database is not up-to-date
26 files changed, 514 insertions, 290 deletions
diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BootstrapModule.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BootstrapModule.java index 9f480c8980f..8ca3fe2a087 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BootstrapModule.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BootstrapModule.java @@ -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); } diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/DatabaseBatchCompatibility.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/DatabaseBatchCompatibility.java new file mode 100644 index 00000000000..683bcda5d1d --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/DatabaseBatchCompatibility.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.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); + } + } + +} diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/DatabaseBatchCompatibilityTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/DatabaseBatchCompatibilityTest.java new file mode 100644 index 00000000000..2059313776a --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/DatabaseBatchCompatibilityTest.java @@ -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 + } +} diff --git a/sonar-core/src/main/java/org/sonar/core/persistence/BadDatabaseVersion.java b/sonar-core/src/main/java/org/sonar/core/persistence/BadDatabaseVersion.java new file mode 100644 index 00000000000..fee98c3f51d --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/persistence/BadDatabaseVersion.java @@ -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; + } +} diff --git a/sonar-core/src/main/java/org/sonar/core/persistence/DatabaseMigrator.java b/sonar-core/src/main/java/org/sonar/core/persistence/DatabaseMigrator.java index e2688a408ef..32b7ba37697 100644 --- a/sonar-core/src/main/java/org/sonar/core/persistence/DatabaseMigrator.java +++ b/sonar-core/src/main/java/org/sonar/core/persistence/DatabaseMigrator.java @@ -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 */ diff --git a/sonar-core/src/main/java/org/sonar/core/persistence/DatabaseVersion.java b/sonar-core/src/main/java/org/sonar/core/persistence/DatabaseVersion.java new file mode 100644 index 00000000000..942d41eb61f --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/persistence/DatabaseVersion.java @@ -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; + } +} diff --git a/sonar-core/src/main/java/org/sonar/core/persistence/MyBatis.java b/sonar-core/src/main/java/org/sonar/core/persistence/MyBatis.java index 073fda4048e..68a0b83adb1 100644 --- a/sonar-core/src/main/java/org/sonar/core/persistence/MyBatis.java +++ b/sonar-core/src/main/java/org/sonar/core/persistence/MyBatis.java @@ -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); diff --git a/sonar-core/src/main/java/org/sonar/core/persistence/SchemaMigrationDto.java b/sonar-core/src/main/java/org/sonar/core/persistence/SchemaMigrationDto.java new file mode 100644 index 00000000000..2d0256263a7 --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/persistence/SchemaMigrationDto.java @@ -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; + } +} diff --git a/sonar-core/src/main/java/org/sonar/core/persistence/SchemaMigrationMapper.java b/sonar-core/src/main/java/org/sonar/core/persistence/SchemaMigrationMapper.java new file mode 100644 index 00000000000..b86ae41e83b --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/persistence/SchemaMigrationMapper.java @@ -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(); +} diff --git a/sonar-core/src/main/java/org/sonar/jpa/entity/SchemaMigration.java b/sonar-core/src/main/java/org/sonar/jpa/entity/SchemaMigration.java index 8598d2a68e5..979104590be 100644 --- a/sonar-core/src/main/java/org/sonar/jpa/entity/SchemaMigration.java +++ b/sonar-core/src/main/java/org/sonar/jpa/entity/SchemaMigration.java @@ -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(); diff --git a/sonar-core/src/main/java/org/sonar/jpa/session/AbstractDatabaseConnector.java b/sonar-core/src/main/java/org/sonar/jpa/session/AbstractDatabaseConnector.java index 7edc135bb2b..a7d34a9c296 100644 --- a/sonar-core/src/main/java/org/sonar/jpa/session/AbstractDatabaseConnector.java +++ b/sonar-core/src/main/java/org/sonar/jpa/session/AbstractDatabaseConnector.java @@ -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() { diff --git a/sonar-core/src/main/java/org/sonar/jpa/session/DefaultDatabaseConnector.java b/sonar-core/src/main/java/org/sonar/jpa/session/DefaultDatabaseConnector.java index d55e0134f18..3c7a6a56579 100644 --- a/sonar-core/src/main/java/org/sonar/jpa/session/DefaultDatabaseConnector.java +++ b/sonar-core/src/main/java/org/sonar/jpa/session/DefaultDatabaseConnector.java @@ -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; @@ -33,23 +32,9 @@ public class DefaultDatabaseConnector extends AbstractDatabaseConnector { } @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() { diff --git a/sonar-core/src/main/java/org/sonar/jpa/session/MemoryDatabaseConnector.java b/sonar-core/src/main/java/org/sonar/jpa/session/MemoryDatabaseConnector.java index 1484a88f979..488898e7761 100644 --- a/sonar-core/src/main/java/org/sonar/jpa/session/MemoryDatabaseConnector.java +++ b/sonar-core/src/main/java/org/sonar/jpa/session/MemoryDatabaseConnector.java @@ -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); } diff --git a/sonar-core/src/main/resources/org/sonar/core/persistence/SchemaMigrationMapper.xml b/sonar-core/src/main/resources/org/sonar/core/persistence/SchemaMigrationMapper.xml new file mode 100644 index 00000000000..2181a80d067 --- /dev/null +++ b/sonar-core/src/main/resources/org/sonar/core/persistence/SchemaMigrationMapper.xml @@ -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> + diff --git a/sonar-core/src/test/java/org/sonar/core/persistence/DatabaseVersionTest.java b/sonar-core/src/test/java/org/sonar/core/persistence/DatabaseVersionTest.java new file mode 100644 index 00000000000..03d6033df55 --- /dev/null +++ b/sonar-core/src/test/java/org/sonar/core/persistence/DatabaseVersionTest.java @@ -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)); + } +} diff --git a/sonar-core/src/test/java/org/sonar/jpa/entity/SchemaMigrationTest.java b/sonar-core/src/test/java/org/sonar/jpa/entity/SchemaMigrationTest.java deleted file mode 100644 index cd2e5a875b1..00000000000 --- a/sonar-core/src/test/java/org/sonar/jpa/entity/SchemaMigrationTest.java +++ /dev/null @@ -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(); - } -} diff --git a/sonar-core/src/test/resources/org/sonar/core/persistence/DatabaseVersionTest/getVersion.xml b/sonar-core/src/test/resources/org/sonar/core/persistence/DatabaseVersionTest/getVersion.xml new file mode 100644 index 00000000000..867616dc080 --- /dev/null +++ b/sonar-core/src/test/resources/org/sonar/core/persistence/DatabaseVersionTest/getVersion.xml @@ -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> diff --git a/sonar-core/src/test/resources/org/sonar/core/persistence/DatabaseVersionTest/getVersion_no_rows.xml b/sonar-core/src/test/resources/org/sonar/core/persistence/DatabaseVersionTest/getVersion_no_rows.xml new file mode 100644 index 00000000000..5ed00ba028b --- /dev/null +++ b/sonar-core/src/test/resources/org/sonar/core/persistence/DatabaseVersionTest/getVersion_no_rows.xml @@ -0,0 +1 @@ +<dataset></dataset>
\ No newline at end of file diff --git a/sonar-server/src/main/java/org/sonar/server/configuration/Backup.java b/sonar-server/src/main/java/org/sonar/server/configuration/Backup.java index 8620bfd44ca..8b6583a959f 100644 --- a/sonar-server/src/main/java/org/sonar/server/configuration/Backup.java +++ b/sonar-server/src/main/java/org/sonar/server/configuration/Backup.java @@ -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() { diff --git a/sonar-server/src/main/java/org/sonar/server/platform/DatabaseServerCompatibility.java b/sonar-server/src/main/java/org/sonar/server/platform/DatabaseServerCompatibility.java new file mode 100644 index 00000000000..7a1a925f2a3 --- /dev/null +++ b/sonar-server/src/main/java/org/sonar/server/platform/DatabaseServerCompatibility.java @@ -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"); + } + } + +} diff --git a/sonar-server/src/main/java/org/sonar/server/platform/DefaultServerUpgradeStatus.java b/sonar-server/src/main/java/org/sonar/server/platform/DefaultServerUpgradeStatus.java index 64c47d5c880..3aebf626cb1 100644 --- a/sonar-server/src/main/java/org/sonar/server/platform/DefaultServerUpgradeStatus.java +++ b/sonar-server/src/main/java/org/sonar/server/platform/DefaultServerUpgradeStatus.java @@ -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() { diff --git a/sonar-server/src/main/java/org/sonar/server/platform/Platform.java b/sonar-server/src/main/java/org/sonar/server/platform/Platform.java index 13f29b6b079..ce7193fd5b0 100644 --- a/sonar-server/src/main/java/org/sonar/server/platform/Platform.java +++ b/sonar-server/src/main/java/org/sonar/server/platform/Platform.java @@ -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); diff --git a/sonar-server/src/main/webapp/WEB-INF/db/migrate/README.txt b/sonar-server/src/main/webapp/WEB-INF/db/migrate/README.txt index 1a2dcc99bc4..f1d93735e13 100644 --- a/sonar-server/src/main/webapp/WEB-INF/db/migrate/README.txt +++ b/sonar-server/src/main/webapp/WEB-INF/db/migrate/README.txt @@ -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 diff --git a/sonar-server/src/test/java/org/sonar/server/platform/DatabaseServerCompatibilityTest.java b/sonar-server/src/test/java/org/sonar/server/platform/DatabaseServerCompatibilityTest.java new file mode 100644 index 00000000000..7ff5a20bc00 --- /dev/null +++ b/sonar-server/src/test/java/org/sonar/server/platform/DatabaseServerCompatibilityTest.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 + } +} diff --git a/sonar-server/src/test/java/org/sonar/server/platform/DefaultServerUpgradeStatusTest.java b/sonar-server/src/test/java/org/sonar/server/platform/DefaultServerUpgradeStatusTest.java index dc271ffca8f..2b59007390c 100644 --- a/sonar-server/src/test/java/org/sonar/server/platform/DefaultServerUpgradeStatusTest.java +++ b/sonar-server/src/test/java/org/sonar/server/platform/DefaultServerUpgradeStatusTest.java @@ -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)); } } diff --git a/sonar-server/src/test/java/org/sonar/server/startup/DeleteDeprecatedMeasuresTest.java b/sonar-server/src/test/java/org/sonar/server/startup/DeleteDeprecatedMeasuresTest.java index f7a0a3e04a9..67479445563 100644 --- a/sonar-server/src/test/java/org/sonar/server/startup/DeleteDeprecatedMeasuresTest.java +++ b/sonar-server/src/test/java/org/sonar/server/startup/DeleteDeprecatedMeasuresTest.java @@ -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)); |