From d4baa0caf3fe490cbc72c445a17aa82f649d8faa Mon Sep 17 00:00:00 2001 From: =?utf8?q?S=C3=A9bastien=20Lesaint?= Date: Mon, 3 Dec 2018 11:24:13 +0100 Subject: [PATCH] SONARCLOUD-192 core extension can now declare MyBatis mapper and alias --- .../java/org/sonar/db/AbstractDbTester.java | 4 ++ .../test/java/org/sonar/db/CoreTestDb.java | 41 +++++++++------ .../src/main/java/org/sonar/db/MyBatis.java | 15 +++++- .../org/sonar/db/MyBatisConfExtension.java | 36 +++++++++++++ .../src/test/java/org/sonar/db/DbTester.java | 51 +++++++++++++++++-- .../src/test/java/org/sonar/db/TestDb.java | 44 +++++++++++----- .../ws/ChangeParentActionTest.java | 2 +- 7 files changed, 158 insertions(+), 35 deletions(-) create mode 100644 server/sonar-db-dao/src/main/java/org/sonar/db/MyBatisConfExtension.java diff --git a/server/sonar-db-core/src/test/java/org/sonar/db/AbstractDbTester.java b/server/sonar-db-core/src/test/java/org/sonar/db/AbstractDbTester.java index 703bf316975..33229b0a43f 100644 --- a/server/sonar-db-core/src/test/java/org/sonar/db/AbstractDbTester.java +++ b/server/sonar-db-core/src/test/java/org/sonar/db/AbstractDbTester.java @@ -84,6 +84,10 @@ public class AbstractDbTester extends ExternalResource { this.db = db; } + public T getDb() { + return db; + } + public void executeUpdateSql(String sql, Object... params) { try (Connection connection = getConnection()) { new QueryRunner().update(connection, sql, params); diff --git a/server/sonar-db-core/src/test/java/org/sonar/db/CoreTestDb.java b/server/sonar-db-core/src/test/java/org/sonar/db/CoreTestDb.java index 6b963d27e31..fe7852f5a65 100644 --- a/server/sonar-db-core/src/test/java/org/sonar/db/CoreTestDb.java +++ b/server/sonar-db-core/src/test/java/org/sonar/db/CoreTestDb.java @@ -26,6 +26,7 @@ import java.net.URI; import java.sql.SQLException; import java.util.Map; import java.util.Properties; +import java.util.function.BiConsumer; import javax.annotation.Nullable; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.io.FileUtils; @@ -61,17 +62,18 @@ class CoreTestDb { private IDatabaseTester tester; private boolean isDefault; - static CoreTestDb create(@Nullable String schemaPath) { - if (schemaPath == null) { - if (DEFAULT == null) { - DEFAULT = new CoreTestDb(null); - } - return DEFAULT; - } - return new CoreTestDb(schemaPath); + protected CoreTestDb() { + // use static factory method + } + + protected CoreTestDb(CoreTestDb base) { + this.db = base.db; + this.isDefault = base.isDefault; + this.commands = base.commands; + this.tester = base.tester; } - CoreTestDb(@Nullable String schemaPath) { + CoreTestDb init(@Nullable String schemaPath, BiConsumer extendedStart) { if (db == null) { Settings settings = new MapSettings().addProperties(System.getProperties()); if (isNotEmpty(settings.getString("orchestrator.configUrl"))) { @@ -102,16 +104,23 @@ class CoreTestDb { commands = DatabaseCommands.forDialect(db.getDialect()); tester = new DataSourceDatabaseTester(db.getDataSource(), commands.useLoginAsSchema() ? login : null); - extendStart(db); + extendedStart.accept(db, true); + } else { + extendedStart.accept(db, false); } + return this; } - /** - * to be overridden by subclasses to extend what's done when db is created - * @param db - */ - protected void extendStart(Database db) { - // nothing done here + static CoreTestDb create(@Nullable String schemaPath) { + if (schemaPath == null) { + if (DEFAULT == null) { + DEFAULT = new CoreTestDb().init(null, (db, created) -> { + }); + } + return DEFAULT; + } + return new CoreTestDb().init(schemaPath, (db, created) -> { + }); } public void start() { diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java b/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java index 1172e62b86d..8316c4f74b9 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java @@ -23,6 +23,10 @@ import com.google.common.annotations.VisibleForTesting; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import javax.annotation.Nullable; import org.apache.ibatis.logging.LogFactory; import org.apache.ibatis.session.ExecutorType; import org.apache.ibatis.session.SqlSession; @@ -139,11 +143,16 @@ import org.sonar.db.webhook.WebhookDeliveryMapper; import org.sonar.db.webhook.WebhookMapper; public class MyBatis implements Startable { - + private final List confExtensions; private final Database database; private SqlSessionFactory sessionFactory; public MyBatis(Database database) { + this(database, null); + } + + public MyBatis(Database database, @Nullable MyBatisConfExtension[] confExtensions) { + this.confExtensions = confExtensions == null ? Collections.emptyList() : Arrays.asList(confExtensions); this.database = database; } @@ -202,6 +211,7 @@ public class MyBatis implements Startable { confBuilder.loadAlias("User", UserDto.class); confBuilder.loadAlias("UuidWithProjectUuid", UuidWithProjectUuidDto.class); confBuilder.loadAlias("ViewsSnapshot", ViewsSnapshotDto.class); + confExtensions.forEach(ext -> ext.loadAliases(confBuilder::loadAlias)); // keep them sorted alphabetically Class[] mappers = { @@ -268,6 +278,9 @@ public class MyBatis implements Startable { WebhookDeliveryMapper.class }; confBuilder.loadMappers(mappers); + confExtensions.stream() + .flatMap(MyBatisConfExtension::getMapperClasses) + .forEach(confBuilder::loadMapper); sessionFactory = new SqlSessionFactoryBuilder().build(confBuilder.build()); } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatisConfExtension.java b/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatisConfExtension.java new file mode 100644 index 00000000000..3d755724284 --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatisConfExtension.java @@ -0,0 +1,36 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.db; + +import java.util.stream.Stream; +import org.sonar.core.extension.PlatformLevel; + +@PlatformLevel(1) +public interface MyBatisConfExtension { + default void loadAliases(LoadAliasContext context) { + // no alias to load + } + + interface LoadAliasContext { + void loadAlias(String alias, Class dtoClass); + } + + Stream> getMapperClasses(); +} diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/DbTester.java b/server/sonar-db-dao/src/test/java/org/sonar/db/DbTester.java index 82194a971a4..41a7d21036b 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/DbTester.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/DbTester.java @@ -21,8 +21,11 @@ package org.sonar.db; import java.sql.Connection; import java.sql.SQLException; +import java.util.Arrays; +import java.util.Comparator; import java.util.List; import java.util.Map; +import java.util.stream.Stream; import javax.annotation.Nullable; import org.apache.commons.dbcp2.BasicDataSource; import org.apache.commons.lang.StringUtils; @@ -90,8 +93,8 @@ public class DbTester extends AbstractDbTester { private final WebhookDeliveryDbTester webhookDeliveryDbTester; private final AlmDbTester almDbTester; - public DbTester(System2 system2, @Nullable String schemaPath) { - super(TestDb.create(schemaPath)); + private DbTester(System2 system2, @Nullable String schemaPath, MyBatisConfExtension... confExtensions) { + super(TestDb.create(schemaPath, confExtensions)); this.system2 = system2; initDbClient(); @@ -125,6 +128,14 @@ public class DbTester extends AbstractDbTester { return new DbTester(system2, null); } + public static DbTester createWithExtensionMappers(Class firstMapperClass, Class... otherMapperClasses) { + return new DbTester(System2.INSTANCE, null, new DbTesterMyBatisConfExtension(firstMapperClass, otherMapperClasses)); + } + + public static DbTester createWithExtensionMappers(System2 system2, Class firstMapperClass, Class... otherMapperClasses) { + return new DbTester(system2, null, new DbTesterMyBatisConfExtension(firstMapperClass, otherMapperClasses)); + } + public static DbTester createForSchema(System2 system2, Class testClass, String filename) { String path = StringUtils.replaceChars(testClass.getCanonicalName(), '.', '/'); String schemaPath = path + "/" + filename; @@ -257,7 +268,7 @@ public class DbTester extends AbstractDbTester { return pluginDbTester; } - public WebhookDbTester webhooks(){ + public WebhookDbTester webhooks() { return webhookDbTester; } @@ -355,4 +366,38 @@ public class DbTester extends AbstractDbTester { } } + private static class DbTesterMyBatisConfExtension implements MyBatisConfExtension { + // do not replace with a lambda to allow cache of MyBatis instances in TestDb to work + private final Class[] mapperClasses; + + public DbTesterMyBatisConfExtension(Class firstMapperClass, Class... otherMapperClasses) { + this.mapperClasses = Stream.concat( + Stream.of(firstMapperClass), + Arrays.stream(otherMapperClasses)) + .sorted(Comparator.comparing(Class::getName)) + .toArray(Class[]::new); + } + + @Override + public Stream> getMapperClasses() { + return Arrays.stream(mapperClasses); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + DbTesterMyBatisConfExtension that = (DbTesterMyBatisConfExtension) o; + return Arrays.equals(mapperClasses, that.mapperClasses); + } + + @Override + public int hashCode() { + return Arrays.hashCode(mapperClasses); + } + } } diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/TestDb.java b/server/sonar-db-dao/src/test/java/org/sonar/db/TestDb.java index d8c4f250205..cd877df8bee 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/TestDb.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/TestDb.java @@ -19,32 +19,48 @@ */ package org.sonar.db; +import java.util.HashMap; +import java.util.Map; import javax.annotation.Nullable; class TestDb extends CoreTestDb { - private static TestDb SINGLETON; + private static TestDb defaultSchemaBaseTestDb; + // instantiating MyBatis objects is costly => we cache them for default schema + private static final Map defaultSchemaTestDbsWithExtensions = new HashMap<>(); private MyBatis myBatis; - private TestDb(@Nullable String schemaPath) { - super(schemaPath); + private TestDb(@Nullable String schemaPath, MyBatisConfExtension... confExtensions) { + super(); + init(schemaPath, (db, created) -> myBatis = newMyBatis(db, confExtensions)); } - static TestDb create(@Nullable String schemaPath) { + private TestDb(TestDb base, MyBatis myBatis) { + super(base); + this.myBatis = myBatis; + } + + private static MyBatis newMyBatis(Database db, MyBatisConfExtension[] extensions) { + MyBatis newMyBatis = new MyBatis(db, extensions); + newMyBatis.start(); + return newMyBatis; + } + + static TestDb create(@Nullable String schemaPath, MyBatisConfExtension... confExtensions) { + MyBatisConfExtension[] extensionArray = confExtensions == null || confExtensions.length == 0 ? null : confExtensions; if (schemaPath == null) { - if (SINGLETON == null) { - SINGLETON = new TestDb(null); + if (defaultSchemaBaseTestDb == null) { + defaultSchemaBaseTestDb = new TestDb((String) null); } - return SINGLETON; + if (extensionArray != null) { + return defaultSchemaTestDbsWithExtensions.computeIfAbsent( + extensionArray, + extensions -> new TestDb(defaultSchemaBaseTestDb, newMyBatis(defaultSchemaBaseTestDb.getDatabase(), extensions))); + } + return defaultSchemaBaseTestDb; } - return new TestDb(schemaPath); - } - - @Override - protected void extendStart(Database db) { - myBatis = new MyBatis(db); - myBatis.start(); + return new TestDb(schemaPath, confExtensions); } MyBatis getMyBatis() { diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/ChangeParentActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/ChangeParentActionTest.java index 707212e283a..d637737c8da 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/ChangeParentActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/ChangeParentActionTest.java @@ -78,7 +78,7 @@ import static org.sonarqube.ws.client.qualityprofile.QualityProfileWsParameters. public class ChangeParentActionTest { @Rule - public DbTester db = new DbTester(System2.INSTANCE, null); + public DbTester db = DbTester.create(System2.INSTANCE); @Rule public EsTester es = EsTester.create(); @Rule -- 2.39.5