aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJulien HENRY <julien.henry@sonarsource.com>2017-08-03 17:41:13 +0200
committerJulien HENRY <julien.henry@sonarsource.com>2017-09-07 08:33:31 +0200
commitda4d725ebe9e870943405ba503c90331241db9a2 (patch)
tree332f7486ea3007b6d722e86a57a32cbed5c9f45c
parentcf6cbbb26136ef38430fab84ac8cc38ae974a0a6 (diff)
downloadsonarqube-da4d725ebe9e870943405ba503c90331241db9a2.tar.gz
sonarqube-da4d725ebe9e870943405ba503c90331241db9a2.zip
SONAR-9662 Store plugin hash + last modification date
-rw-r--r--server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java2
-rw-r--r--server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java1
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java2
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java8
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java4
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/plugin/PluginDao.java52
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/plugin/PluginDto.java105
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/plugin/PluginMapper.java38
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/plugin/package-info.java24
-rw-r--r--server/sonar-db-dao/src/main/resources/org/sonar/db/plugin/PluginMapper.xml60
-rw-r--r--server/sonar-db-dao/src/test/java/org/sonar/db/DaoModuleTest.java2
-rw-r--r--server/sonar-db-dao/src/test/java/org/sonar/db/plugin/PluginDaoTest.java116
-rw-r--r--server/sonar-db-dao/src/test/resources/org/sonar/db/plugin/PluginDaoTest/shared.xml18
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java4
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginRepository.java15
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/startup/GeneratePluginIndex.java39
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/startup/RegisterPlugins.java104
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/startup/GeneratePluginIndexTest.java30
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/startup/RegisterPluginsTest.java99
-rw-r--r--server/sonar-server/src/test/resources/org/sonar/server/startup/RegisterPluginsTest/insert_new_plugins-result.xml6
-rw-r--r--server/sonar-server/src/test/resources/org/sonar/server/startup/RegisterPluginsTest/insert_new_plugins.xml4
-rw-r--r--server/sonar-server/src/test/resources/org/sonar/server/startup/RegisterPluginsTest/update_only_changed_plugins-result.xml6
-rw-r--r--server/sonar-server/src/test/resources/org/sonar/server/startup/RegisterPluginsTest/update_only_changed_plugins.xml6
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>