diff options
author | Julien HENRY <julien.henry@sonarsource.com> | 2017-08-03 17:41:13 +0200 |
---|---|---|
committer | Julien HENRY <julien.henry@sonarsource.com> | 2017-09-07 08:33:31 +0200 |
commit | da4d725ebe9e870943405ba503c90331241db9a2 (patch) | |
tree | 332f7486ea3007b6d722e86a57a32cbed5c9f45c | |
parent | cf6cbbb26136ef38430fab84ac8cc38ae974a0a6 (diff) | |
download | sonarqube-da4d725ebe9e870943405ba503c90331241db9a2.tar.gz sonarqube-da4d725ebe9e870943405ba503c90331241db9a2.zip |
SONAR-9662 Store plugin hash + last modification date
23 files changed, 707 insertions, 38 deletions
diff --git a/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java b/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java index 0215ca1c134..782514d6502 100644 --- a/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java +++ b/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java @@ -146,7 +146,7 @@ public class ComputeEngineContainerImplTest { assertThat(picoContainer.getParent().getParent().getParent().getComponentAdapters()).hasSize( COMPONENTS_IN_LEVEL_1_AT_CONSTRUCTION + 25 // level 1 - + 47 // content of DaoModule + + 48 // content of DaoModule + 3 // content of EsSearchModule + 61 // content of CorePropertyDefinitions + 1 // StopFlagContainer diff --git a/server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java b/server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java index 873d38eb2d1..2f6d77bcb05 100644 --- a/server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java +++ b/server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java @@ -75,6 +75,7 @@ public final class SqTables { "perm_templates_users", "perm_templates_groups", "perm_tpl_characteristics", + "plugins", "projects", "project_links", "project_measures", diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java b/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java index bc5efc71ccb..44b741739d7 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java @@ -48,6 +48,7 @@ import org.sonar.db.permission.GroupPermissionDao; import org.sonar.db.permission.UserPermissionDao; import org.sonar.db.permission.template.PermissionTemplateCharacteristicDao; import org.sonar.db.permission.template.PermissionTemplateDao; +import org.sonar.db.plugin.PluginDao; import org.sonar.db.property.InternalPropertiesDao; import org.sonar.db.property.PropertiesDao; import org.sonar.db.purge.PurgeDao; @@ -105,6 +106,7 @@ public class DaoModule extends Module { OrganizationMemberDao.class, PermissionTemplateCharacteristicDao.class, PermissionTemplateDao.class, + PluginDao.class, ProjectQgateAssociationDao.class, PropertiesDao.class, PurgeDao.class, diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java b/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java index 5d31776cf6e..246bb15f559 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java @@ -47,6 +47,7 @@ import org.sonar.db.permission.GroupPermissionDao; import org.sonar.db.permission.UserPermissionDao; import org.sonar.db.permission.template.PermissionTemplateCharacteristicDao; import org.sonar.db.permission.template.PermissionTemplateDao; +import org.sonar.db.plugin.PluginDao; import org.sonar.db.property.InternalPropertiesDao; import org.sonar.db.property.PropertiesDao; import org.sonar.db.purge.PurgeDao; @@ -121,6 +122,7 @@ public class DbClient { private final WebhookDeliveryDao webhookDeliveryDao; private final DefaultQProfileDao defaultQProfileDao; private final EsQueueDao esQueueDao; + private final PluginDao pluginDao; public DbClient(Database database, MyBatis myBatis, DBSessions dbSessions, Dao... daos) { this.database = database; @@ -178,6 +180,7 @@ public class DbClient { webhookDeliveryDao = getDao(map, WebhookDeliveryDao.class); defaultQProfileDao = getDao(map, DefaultQProfileDao.class); esQueueDao = getDao(map, EsQueueDao.class); + pluginDao = getDao(map, PluginDao.class); } public DbSession openSession(boolean batch) { @@ -376,6 +379,10 @@ public class DbClient { return esQueueDao; } + public PluginDao pluginDao() { + return pluginDao; + } + protected <K extends Dao> K getDao(Map<Class, Dao> map, Class<K> clazz) { return (K) map.get(clazz); } @@ -384,4 +391,5 @@ public class DbClient { public MyBatis getMyBatis() { return myBatis; } + } 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 73f39eea73b..ee23994014b 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 @@ -81,6 +81,8 @@ import org.sonar.db.permission.template.PermissionTemplateDto; import org.sonar.db.permission.template.PermissionTemplateGroupDto; import org.sonar.db.permission.template.PermissionTemplateMapper; import org.sonar.db.permission.template.PermissionTemplateUserDto; +import org.sonar.db.plugin.PluginDto; +import org.sonar.db.plugin.PluginMapper; import org.sonar.db.property.InternalPropertiesMapper; import org.sonar.db.property.InternalPropertyDto; import org.sonar.db.property.PropertiesMapper; @@ -164,6 +166,7 @@ public class MyBatis implements Startable { confBuilder.loadAlias("PermissionTemplateGroup", PermissionTemplateGroupDto.class); confBuilder.loadAlias("PermissionTemplate", PermissionTemplateDto.class); confBuilder.loadAlias("PermissionTemplateUser", PermissionTemplateUserDto.class); + confBuilder.loadAlias("Plugin", PluginDto.class); confBuilder.loadAlias("ProjectQgateAssociation", ProjectQgateAssociationDto.class); confBuilder.loadAlias("PurgeableAnalysis", PurgeableAnalysisDto.class); confBuilder.loadAlias("QualityGateCondition", QualityGateConditionDto.class); @@ -216,6 +219,7 @@ public class MyBatis implements Startable { OrganizationMemberMapper.class, PermissionTemplateCharacteristicMapper.class, PermissionTemplateMapper.class, + PluginMapper.class, ProjectQgateAssociationMapper.class, PropertiesMapper.class, PurgeMapper.class, diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/plugin/PluginDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/plugin/PluginDao.java new file mode 100644 index 00000000000..68705f81a33 --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/plugin/PluginDao.java @@ -0,0 +1,52 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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.plugin; + +import java.util.List; +import java.util.Optional; +import org.sonar.db.Dao; +import org.sonar.db.DbSession; + +public class PluginDao implements Dao { + + public List<PluginDto> selectAll(DbSession dbSession) { + return mapper(dbSession).selectAll(); + } + + public Optional<PluginDto> selectByKey(DbSession dbSession, String key) { + return Optional.ofNullable(mapper(dbSession).selectByKey(key)); + } + + public void insert(DbSession dbSession, PluginDto dto) { + mapper(dbSession).insert(dto); + } + + public void update(DbSession dbSession, PluginDto dto) { + mapper(dbSession).update(dto); + } + + public void delete(DbSession dbSession, PluginDto dto) { + mapper(dbSession).delete(dto.getUuid()); + } + + private static PluginMapper mapper(DbSession dbSession) { + return dbSession.getMapper(PluginMapper.class); + } +} diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/plugin/PluginDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/plugin/PluginDto.java new file mode 100644 index 00000000000..13c512ff328 --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/plugin/PluginDto.java @@ -0,0 +1,105 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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.plugin; + +import javax.annotation.CheckForNull; +import org.apache.commons.lang.builder.ToStringBuilder; + +public class PluginDto { + /** Technical unique identifier, can't be null */ + private String uuid; + /** Plugin key, unique, can't be null */ + private String kee; + /** Base plugin key, can be null */ + private String basePluginKey; + /** JAR file MD5 checksum, can't be null */ + private String hash; + /** Time plugin was first installed */ + private long createdAt; + /** Time of last plugin update (=md5 change) */ + private long updatedAt; + + public String getUuid() { + return uuid; + } + + public PluginDto setUuid(String s) { + this.uuid = s; + return this; + } + + public String getKee() { + return kee; + } + + public PluginDto setKee(String s) { + this.kee = s; + return this; + } + + @CheckForNull + public String getBasePluginKey() { + return basePluginKey; + } + + public PluginDto setBasePluginKey(String s) { + this.basePluginKey = s; + return this; + } + + public String getHash() { + return hash; + } + + public PluginDto setHash(String s) { + this.hash = s; + return this; + } + + public long getCreatedAt() { + return createdAt; + } + + public PluginDto setCreatedAt(long l) { + this.createdAt = l; + return this; + } + + public long getUpdatedAt() { + return updatedAt; + } + + public PluginDto setUpdatedAt(long l) { + this.updatedAt = l; + return this; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("uuid", uuid) + .append("key", kee) + .append("basePluginKey", basePluginKey) + .append("jarMd5", hash) + .append("createdAt", createdAt) + .append("updatedAt", updatedAt) + .toString(); + } +} diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/plugin/PluginMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/plugin/PluginMapper.java new file mode 100644 index 00000000000..66dcd44d345 --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/plugin/PluginMapper.java @@ -0,0 +1,38 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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.plugin; + +import java.util.List; +import javax.annotation.CheckForNull; +import org.apache.ibatis.annotations.Param; + +public interface PluginMapper { + + List<PluginDto> selectAll(); + + @CheckForNull + PluginDto selectByKey(@Param("key") String key); + + void insert(PluginDto dto); + + void update(PluginDto dto); + + void delete(@Param("uuid") String uuid); +} diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/plugin/package-info.java b/server/sonar-db-dao/src/main/java/org/sonar/db/plugin/package-info.java new file mode 100644 index 00000000000..deefe58baeb --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/plugin/package-info.java @@ -0,0 +1,24 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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. + */ +@ParametersAreNonnullByDefault +package org.sonar.db.plugin; + +import javax.annotation.ParametersAreNonnullByDefault; + diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/plugin/PluginMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/plugin/PluginMapper.xml new file mode 100644 index 00000000000..a6fdb0c199a --- /dev/null +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/plugin/PluginMapper.xml @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="UTF-8" ?> + +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "mybatis-3-mapper.dtd"> + +<mapper namespace="org.sonar.db.plugin.PluginMapper"> + + <sql id="sqlColumns"> + uuid, + kee, + base_plugin_key as basePluginKey, + hash, + created_at as createdAt, + updated_at as updatedAt + </sql> + + <select id="selectByKey" parameterType="String" resultType="org.sonar.db.plugin.PluginDto"> + select + <include refid="sqlColumns" /> + from plugins + where kee = #{key,jdbcType=VARCHAR} + </select> + + <select id="selectAll" resultType="org.sonar.db.plugin.PluginDto"> + select <include refid="sqlColumns" /> + from plugins + </select> + + <insert id="insert" parameterType="org.sonar.db.plugin.PluginDto" useGeneratedKeys="false"> + insert into plugins ( + uuid, + kee, + base_plugin_key, + hash, + created_at, + updated_at + ) values ( + #{uuid,jdbcType=VARCHAR}, + #{kee,jdbcType=VARCHAR}, + #{basePluginKey,jdbcType=VARCHAR}, + #{hash,jdbcType=VARCHAR}, + #{createdAt,jdbcType=TIMESTAMP}, + #{updatedAt,jdbcType=TIMESTAMP} + ) + </insert> + + <update id="update" parameterType="org.sonar.db.plugin.PluginDto"> + update plugins set + base_plugin_key=#{basePluginKey,jdbcType=VARCHAR}, + hash=#{hash,jdbcType=VARCHAR}, + updated_at=#{updatedAt,jdbcType=BIGINT} + where + uuid=#{uuid,jdbcType=VARCHAR} + </update> + + <delete id="delete" parameterType="String"> + delete from plugins + where + uuid = #{uuid,jdbcType=VARCHAR} + </delete> +</mapper> diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/DaoModuleTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/DaoModuleTest.java index 2ffc454abd9..0fb6690bd21 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/DaoModuleTest.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/DaoModuleTest.java @@ -29,6 +29,6 @@ public class DaoModuleTest { public void verify_count_of_added_components() { ComponentContainer container = new ComponentContainer(); new DaoModule().configure(container); - assertThat(container.size()).isEqualTo(2 + 47); + assertThat(container.size()).isEqualTo(2 + 48); } } diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/plugin/PluginDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/plugin/PluginDaoTest.java new file mode 100644 index 00000000000..fda7a9581d1 --- /dev/null +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/plugin/PluginDaoTest.java @@ -0,0 +1,116 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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.plugin; + +import java.util.Optional; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.utils.System2; +import org.sonar.db.DbTester; + +import static org.assertj.core.api.Assertions.assertThat; + +public class PluginDaoTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + @Rule + public DbTester db = DbTester.create(System2.INSTANCE); + + private PluginDao underTest = db.getDbClient().pluginDao(); + + @Test + public void selectByKey() { + db.prepareDbUnit(getClass(), "shared.xml"); + + assertThat(underTest.selectByKey(db.getSession(), "java2")).isEmpty(); + + Optional<PluginDto> plugin = underTest.selectByKey(db.getSession(), "java"); + assertThat(plugin.isPresent()).isTrue(); + assertThat(plugin.get().getUuid()).isEqualTo("a"); + assertThat(plugin.get().getKee()).isEqualTo("java"); + assertThat(plugin.get().getBasePluginKey()).isNull(); + assertThat(plugin.get().getHash()).isEqualTo("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + assertThat(plugin.get().getCreatedAt()).isEqualTo(1500000000000L); + assertThat(plugin.get().getUpdatedAt()).isEqualTo(1600000000000L); + } + + @Test + public void selectAll() { + db.prepareDbUnit(getClass(), "shared.xml"); + + assertThat(underTest.selectAll(db.getSession())).hasSize(2); + } + + @Test + public void insert() { + db.prepareDbUnit(getClass(), "shared.xml"); + + underTest.insert(db.getSession(), new PluginDto() + .setUuid("c") + .setKee("javascript") + .setBasePluginKey("java") + .setHash("cccccccccccccccccccccccccccccccc") + .setCreatedAt(1L) + .setUpdatedAt(2L)); + + Optional<PluginDto> plugin = underTest.selectByKey(db.getSession(), "javascript"); + assertThat(plugin.isPresent()).isTrue(); + assertThat(plugin.get().getUuid()).isEqualTo("c"); + assertThat(plugin.get().getKee()).isEqualTo("javascript"); + assertThat(plugin.get().getBasePluginKey()).isEqualTo("java"); + assertThat(plugin.get().getHash()).isEqualTo("cccccccccccccccccccccccccccccccc"); + assertThat(plugin.get().getCreatedAt()).isEqualTo(1L); + assertThat(plugin.get().getUpdatedAt()).isEqualTo(2L); + } + + @Test + public void update() { + db.prepareDbUnit(getClass(), "shared.xml"); + + PluginDto plugin = underTest.selectByKey(db.getSession(), "java").get(); + + plugin.setBasePluginKey("foo"); + plugin.setHash("abc"); + plugin.setUpdatedAt(3L); + + underTest.update(db.getSession(), plugin); + + plugin = underTest.selectByKey(db.getSession(), "java").get(); + assertThat(plugin.getUuid()).isEqualTo("a"); + assertThat(plugin.getKee()).isEqualTo("java"); + assertThat(plugin.getBasePluginKey()).isEqualTo("foo"); + assertThat(plugin.getHash()).isEqualTo("abc"); + assertThat(plugin.getCreatedAt()).isEqualTo(1500000000000L); + assertThat(plugin.getUpdatedAt()).isEqualTo(3L); + } + + @Test + public void delete() { + db.prepareDbUnit(getClass(), "shared.xml"); + + underTest.delete(db.getSession(), new PluginDto() + .setUuid("a")); + + assertThat(underTest.selectAll(db.getSession())).hasSize(1); + } + +} diff --git a/server/sonar-db-dao/src/test/resources/org/sonar/db/plugin/PluginDaoTest/shared.xml b/server/sonar-db-dao/src/test/resources/org/sonar/db/plugin/PluginDaoTest/shared.xml new file mode 100644 index 00000000000..016bddf0d08 --- /dev/null +++ b/server/sonar-db-dao/src/test/resources/org/sonar/db/plugin/PluginDaoTest/shared.xml @@ -0,0 +1,18 @@ +<dataset> + + <plugins uuid="a" + kee="java" + base_plugin_key="[null]" + hash="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + created_at="1500000000000" + updated_at="1600000000000" + /> + + <plugins uuid="b" + kee="javacustom" + base_plugin_key="java" + hash="bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + created_at="1500000000000" + updated_at="1600000000000" + /> +</dataset> diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java index 77ae38c9666..ad0d0e8f73a 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java @@ -30,8 +30,8 @@ import org.sonar.server.qualityprofile.BuiltInQProfileInsertImpl; import org.sonar.server.qualityprofile.BuiltInQProfileLoader; import org.sonar.server.qualityprofile.BuiltInQProfileUpdateImpl; import org.sonar.server.qualityprofile.BuiltInQualityProfilesNotificationDispatcher; -import org.sonar.server.qualityprofile.BuiltInQualityProfilesUpdateListener; import org.sonar.server.qualityprofile.BuiltInQualityProfilesNotificationTemplate; +import org.sonar.server.qualityprofile.BuiltInQualityProfilesUpdateListener; import org.sonar.server.qualityprofile.RegisterQualityProfiles; import org.sonar.server.rule.RegisterRules; import org.sonar.server.rule.WebServerRuleFinder; @@ -41,6 +41,7 @@ import org.sonar.server.startup.GeneratePluginIndex; import org.sonar.server.startup.RegisterMetrics; import org.sonar.server.startup.RegisterPermissionTemplates; import org.sonar.server.startup.RenameDeprecatedPropertyKeys; +import org.sonar.server.startup.RegisterPlugins; import org.sonar.server.user.DoPrivileged; import org.sonar.server.user.ThreadLocalUserSession; @@ -52,6 +53,7 @@ public class PlatformLevelStartup extends PlatformLevel { @Override protected void configureLevel() { add(GeneratePluginIndex.class, + RegisterPlugins.class, ServerLifecycleNotifier.class, DefaultOrganizationEnforcer.class); diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginRepository.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginRepository.java index 4edc0f7768c..5b189972990 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginRepository.java +++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginRepository.java @@ -20,7 +20,6 @@ package org.sonar.server.plugins; import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; @@ -37,7 +36,6 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import javax.annotation.CheckForNull; -import javax.annotation.Nonnull; import org.apache.commons.io.FileUtils; import org.picocontainer.Startable; import org.sonar.api.Plugin; @@ -57,6 +55,7 @@ import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.Iterables.transform; import static com.google.common.collect.Lists.newArrayList; import static java.lang.String.format; +import static java.util.stream.Collectors.toList; import static org.apache.commons.io.FileUtils.copyFile; import static org.apache.commons.io.FileUtils.moveFile; import static org.apache.commons.io.FileUtils.moveFileToDirectory; @@ -340,7 +339,7 @@ public class ServerPluginRepository implements PluginRepository, Startable { } public List<String> getUninstalledPluginFilenames() { - return newArrayList(transform(listJarFiles(uninstalledPluginsDir()), FileToName.INSTANCE)); + return listJarFiles(uninstalledPluginsDir()).stream().map(File::getName).collect(toList()); } /** @@ -394,16 +393,6 @@ public class ServerPluginRepository implements PluginRepository, Startable { return pluginInfosByKeys.containsKey(key); } - private enum FileToName implements Function<File, String> { - INSTANCE; - - @Override - public String apply(@Nonnull File file) { - return file.getName(); - } - - } - /** * @return existing trash dir */ diff --git a/server/sonar-server/src/main/java/org/sonar/server/startup/GeneratePluginIndex.java b/server/sonar-server/src/main/java/org/sonar/server/startup/GeneratePluginIndex.java index a7563db981e..15527cc90c4 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/startup/GeneratePluginIndex.java +++ b/server/sonar-server/src/main/java/org/sonar/server/startup/GeneratePluginIndex.java @@ -26,16 +26,21 @@ import java.io.OutputStreamWriter; import java.io.Writer; import java.nio.charset.StandardCharsets; import org.apache.commons.io.FileUtils; -import org.apache.commons.io.IOUtils; import org.apache.commons.lang.CharUtils; +import org.picocontainer.Startable; import org.sonar.api.server.ServerSide; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.api.utils.log.Profiler; import org.sonar.core.platform.PluginInfo; import org.sonar.core.platform.PluginRepository; import org.sonar.core.platform.RemotePlugin; import org.sonar.server.platform.ServerFileSystem; @ServerSide -public final class GeneratePluginIndex { +public final class GeneratePluginIndex implements Startable { + + private static final Logger LOG = Loggers.get(GeneratePluginIndex.class); private final ServerFileSystem fileSystem; private final PluginRepository repository; @@ -45,22 +50,30 @@ public final class GeneratePluginIndex { this.repository = repository; } - public void start() throws IOException { + @Override + public void start() { + Profiler profiler = Profiler.create(LOG).startInfo("Generate scanner plugin index"); writeIndex(fileSystem.getPluginIndex()); + profiler.stopDebug(); + } + + @Override + public void stop() { + // Nothing to do } - void writeIndex(File indexFile) throws IOException { - FileUtils.forceMkdir(indexFile.getParentFile()); - Writer writer = new OutputStreamWriter(new FileOutputStream(indexFile), StandardCharsets.UTF_8); + void writeIndex(File indexFile) { try { - for (PluginInfo pluginInfo : repository.getPluginInfos()) { - writer.append(RemotePlugin.create(pluginInfo).marshal()); - writer.append(CharUtils.LF); + FileUtils.forceMkdir(indexFile.getParentFile()); + try (Writer writer = new OutputStreamWriter(new FileOutputStream(indexFile), StandardCharsets.UTF_8)) { + for (PluginInfo pluginInfo : repository.getPluginInfos()) { + writer.append(RemotePlugin.create(pluginInfo).marshal()); + writer.append(CharUtils.LF); + } + writer.flush(); } - writer.flush(); - - } finally { - IOUtils.closeQuietly(writer); + } catch (IOException e) { + throw new IllegalStateException("Unable to generate plugin index at " + indexFile, e); } } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/startup/RegisterPlugins.java b/server/sonar-server/src/main/java/org/sonar/server/startup/RegisterPlugins.java new file mode 100644 index 00000000000..58eb25bde36 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/startup/RegisterPlugins.java @@ -0,0 +1,104 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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.server.startup; + +import java.util.Collection; +import java.util.Map; +import java.util.stream.Collectors; +import org.picocontainer.Startable; +import org.sonar.api.utils.System2; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.api.utils.log.Profiler; +import org.sonar.core.platform.PluginInfo; +import org.sonar.core.platform.RemotePlugin; +import org.sonar.core.util.UuidFactory; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.plugin.PluginDto; +import org.sonar.server.plugins.ServerPluginRepository; + +import static java.util.function.Function.identity; + +/** + * Take care to update the 'plugins' table at startup. + */ +public class RegisterPlugins implements Startable { + + private static final Logger LOG = Loggers.get(RegisterPlugins.class); + + private final ServerPluginRepository repository; + private final DbClient dbClient; + private final UuidFactory uuidFactory; + private final System2 system; + + public RegisterPlugins(ServerPluginRepository repository, DbClient dbClient, UuidFactory uuidFactory, System2 system) { + this.repository = repository; + this.dbClient = dbClient; + this.uuidFactory = uuidFactory; + this.system = system; + } + + @Override + public void start() { + Profiler profiler = Profiler.create(LOG).startInfo("Register plugins"); + updateDB(repository.getPluginInfos()); + profiler.stopDebug(); + } + + @Override + public void stop() { + // Nothing to do + } + + private void updateDB(Collection<PluginInfo> pluginInfos) { + long now = system.now(); + try (DbSession dbSession = dbClient.openSession(false)) { + Map<String, PluginDto> allPreviousPluginsByKey = dbClient.pluginDao().selectAll(dbSession).stream() + .collect(Collectors.toMap(PluginDto::getKee, identity())); + for (PluginInfo pluginInfo : pluginInfos) { + RemotePlugin remotePlugin = RemotePlugin.create(pluginInfo); + String newJarMd5 = remotePlugin.file().getHash(); + PluginDto previousDto = allPreviousPluginsByKey.get(pluginInfo.getKey()); + if (previousDto == null) { + LOG.debug("Register new plugin {}", pluginInfo.getKey()); + PluginDto pluginDto = new PluginDto() + .setUuid(uuidFactory.create()) + .setKee(pluginInfo.getKey()) + .setBasePluginKey(pluginInfo.getBasePlugin()) + .setHash(newJarMd5) + .setCreatedAt(now) + .setUpdatedAt(now); + dbClient.pluginDao().insert(dbSession, pluginDto); + } else if (!previousDto.getHash().equals(newJarMd5)) { + LOG.debug("Update plugin {}", pluginInfo.getKey()); + previousDto + .setBasePluginKey(pluginInfo.getBasePlugin()) + .setHash(newJarMd5) + .setUpdatedAt(now); + dbClient.pluginDao().update(dbSession, previousDto); + } + // Don't remove uninstalled plugins, because corresponding rules and active rules are also not deleted + } + dbSession.commit(); + } + } + +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/startup/GeneratePluginIndexTest.java b/server/sonar-server/src/test/java/org/sonar/server/startup/GeneratePluginIndexTest.java index 30b46ffd912..1e9bafb9666 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/startup/GeneratePluginIndexTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/startup/GeneratePluginIndexTest.java @@ -24,7 +24,6 @@ import java.io.IOException; import java.util.Arrays; import java.util.List; import org.apache.commons.io.FileUtils; -import org.hamcrest.core.Is; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -33,8 +32,7 @@ import org.sonar.core.platform.PluginInfo; import org.sonar.core.platform.PluginRepository; import org.sonar.server.platform.ServerFileSystem; -import static org.junit.Assert.assertThat; -import static org.junit.matchers.JUnitMatchers.containsString; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -47,8 +45,8 @@ public class GeneratePluginIndexTest { private File index; @Before - public void createIndexFile() { - index = new File("target/test-tmp/GeneratePluginIndexTest/plugins.txt"); + public void createIndexFile() throws IOException { + index = temp.newFile(); when(fileSystem.getPluginIndex()).thenReturn(index); } @@ -59,12 +57,26 @@ public class GeneratePluginIndexTest { PluginInfo checkstyle = newInfo("checkstyle"); when(repository.getPluginInfos()).thenReturn(Arrays.asList(sqale, checkstyle)); - new GeneratePluginIndex(fileSystem, repository).start(); + GeneratePluginIndex underTest = new GeneratePluginIndex(fileSystem, repository); + underTest.start(); + underTest.stop(); // For coverage List<String> lines = FileUtils.readLines(index); - assertThat(lines.size(), Is.is(2)); - assertThat(lines.get(0), containsString("sqale")); - assertThat(lines.get(1), containsString("checkstyle")); + assertThat(lines).hasSize(2); + assertThat(lines.get(0)).contains("sqale"); + assertThat(lines.get(1)).contains("checkstyle"); + } + + @Test(expected = IllegalStateException.class) + public void shouldThrowWhenUnableToWrite() throws IOException { + File wrongParent = temp.newFile(); + wrongParent.createNewFile(); + File wrongIndex = new File(wrongParent, "index.txt"); + when(fileSystem.getPluginIndex()).thenReturn(wrongIndex); + + PluginRepository repository = mock(PluginRepository.class); + + new GeneratePluginIndex(fileSystem, repository).start(); } private PluginInfo newInfo(String pluginKey) throws IOException { diff --git a/server/sonar-server/src/test/java/org/sonar/server/startup/RegisterPluginsTest.java b/server/sonar-server/src/test/java/org/sonar/server/startup/RegisterPluginsTest.java new file mode 100644 index 00000000000..e0f13dc6c74 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/startup/RegisterPluginsTest.java @@ -0,0 +1,99 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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.server.startup; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import org.apache.commons.io.FileUtils; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.utils.System2; +import org.sonar.core.platform.PluginInfo; +import org.sonar.core.util.UuidFactory; +import org.sonar.db.DbClient; +import org.sonar.db.DbTester; +import org.sonar.server.plugins.ServerPluginRepository; + +import static java.util.Arrays.asList; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class RegisterPluginsTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Rule + public DbTester dbTester = DbTester.create(System2.INSTANCE); + + DbClient dbClient = dbTester.getDbClient(); + + private ServerPluginRepository serverPluginRepository; + private UuidFactory uuidFactory; + private System2 system2; + + @Before + public void prepare() { + serverPluginRepository = mock(ServerPluginRepository.class); + uuidFactory = mock(UuidFactory.class); + system2 = mock(System2.class); + when(system2.now()).thenReturn(12345L).thenThrow(new IllegalStateException("Should be called only once")); + } + + /** + * Insert new plugins + */ + @Test + public void insert_new_plugins() throws IOException { + dbTester.prepareDbUnit(getClass(), "insert_new_plugins.xml"); + + File fakeJavaJar = temp.newFile(); + FileUtils.write(fakeJavaJar, "fakejava", StandardCharsets.UTF_8); + File fakeJavaCustomJar = temp.newFile(); + FileUtils.write(fakeJavaCustomJar, "fakejavacustom", StandardCharsets.UTF_8); + when(serverPluginRepository.getPluginInfos()).thenReturn(asList(new PluginInfo("java").setJarFile(fakeJavaJar), + new PluginInfo("javacustom").setJarFile(fakeJavaCustomJar).setBasePlugin("java"))); + when(uuidFactory.create()).thenReturn("a").thenReturn("b").thenThrow(new IllegalStateException("Should be called only twice")); + RegisterPlugins register = new RegisterPlugins(serverPluginRepository, dbClient, uuidFactory, system2); + register.start(); + register.stop(); // For coverage + dbTester.assertDbUnit(getClass(), "insert_new_plugins-result.xml", "plugins"); + } + + /** + * Update existing plugins, only when checksum is different and don't remove uninstalled plugins + */ + @Test + public void update_only_changed_plugins() throws IOException { + dbTester.prepareDbUnit(getClass(), "update_only_changed_plugins.xml"); + + File fakeJavaCustomJar = temp.newFile(); + FileUtils.write(fakeJavaCustomJar, "fakejavacustomchanged", StandardCharsets.UTF_8); + when(serverPluginRepository.getPluginInfos()).thenReturn(asList(new PluginInfo("javacustom").setJarFile(fakeJavaCustomJar).setBasePlugin("java2"))); + + new RegisterPlugins(serverPluginRepository, dbClient, uuidFactory, system2).start(); + + dbTester.assertDbUnit(getClass(), "update_only_changed_plugins-result.xml", "plugins"); + } + +} diff --git a/server/sonar-server/src/test/resources/org/sonar/server/startup/RegisterPluginsTest/insert_new_plugins-result.xml b/server/sonar-server/src/test/resources/org/sonar/server/startup/RegisterPluginsTest/insert_new_plugins-result.xml new file mode 100644 index 00000000000..37b152ccac3 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/startup/RegisterPluginsTest/insert_new_plugins-result.xml @@ -0,0 +1,6 @@ +<dataset> + + <plugins uuid="a" kee="java" base_plugin_key="[null]" hash="bd451e47a1aa76e73da0359cef63dd63" created_at="12345" updated_at="12345"/> + <plugins uuid="b" kee="javacustom" base_plugin_key="java" hash="de9b2de3ddc0680904939686c0dba5be" created_at="12345" updated_at="12345"/> + +</dataset> diff --git a/server/sonar-server/src/test/resources/org/sonar/server/startup/RegisterPluginsTest/insert_new_plugins.xml b/server/sonar-server/src/test/resources/org/sonar/server/startup/RegisterPluginsTest/insert_new_plugins.xml new file mode 100644 index 00000000000..a1c54e4625a --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/startup/RegisterPluginsTest/insert_new_plugins.xml @@ -0,0 +1,4 @@ +<dataset> + + +</dataset> diff --git a/server/sonar-server/src/test/resources/org/sonar/server/startup/RegisterPluginsTest/update_only_changed_plugins-result.xml b/server/sonar-server/src/test/resources/org/sonar/server/startup/RegisterPluginsTest/update_only_changed_plugins-result.xml new file mode 100644 index 00000000000..037fb9aec93 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/startup/RegisterPluginsTest/update_only_changed_plugins-result.xml @@ -0,0 +1,6 @@ +<dataset> + + <plugins uuid="a" kee="java" base_plugin_key="[null]" hash="bd451e47a1aa76e73da0359cef63dd63" created_at="1" updated_at="1"/> + <plugins uuid="b" kee="javacustom" base_plugin_key="java2" hash="d22091cff5155e892cfe2f9dab51f811" created_at="1" updated_at="12345"/> + +</dataset> diff --git a/server/sonar-server/src/test/resources/org/sonar/server/startup/RegisterPluginsTest/update_only_changed_plugins.xml b/server/sonar-server/src/test/resources/org/sonar/server/startup/RegisterPluginsTest/update_only_changed_plugins.xml new file mode 100644 index 00000000000..b217fb7fa6d --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/startup/RegisterPluginsTest/update_only_changed_plugins.xml @@ -0,0 +1,6 @@ +<dataset> + + <plugins uuid="a" kee="java" base_plugin_key="[null]" hash="bd451e47a1aa76e73da0359cef63dd63" created_at="1" updated_at="1"/> + <plugins uuid="b" kee="javacustom" base_plugin_key="java" hash="de9b2de3ddc0680904939686c0dba5be" created_at="1" updated_at="1"/> + +</dataset> |