diff options
author | Belen Pruvost <belen.pruvost@sonarsource.com> | 2022-07-28 16:55:41 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2022-07-28 20:02:56 +0000 |
commit | 7ed0c0a19b7a0da9c6c907cab9a57a4d723dd03e (patch) | |
tree | 243c1417f126cc6f954c9c3352cf4c7f6ad61794 | |
parent | ca7e8a8d9687f93d1e4ddad5748aee2d66f57252 (diff) | |
download | sonarqube-7ed0c0a19b7a0da9c6c907cab9a57a4d723dd03e.tar.gz sonarqube-7ed0c0a19b7a0da9c6c907cab9a57a4d723dd03e.zip |
SONAR-16647 - Move previous SSE events to DB queue
39 files changed, 414 insertions, 1175 deletions
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/pushevent/PushEventDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/pushevent/PushEventDto.java index 905eaf03b55..8024bf71bf0 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/pushevent/PushEventDto.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/pushevent/PushEventDto.java @@ -25,6 +25,7 @@ public class PushEventDto { private String uuid; private String name; private String projectUuid; + private String language; private byte[] payload; private Long createdAt; @@ -59,6 +60,16 @@ public class PushEventDto { return this; } + @CheckForNull + public String getLanguage() { + return language; + } + + public PushEventDto setLanguage(String language) { + this.language = language; + return this; + } + public byte[] getPayload() { return payload; } diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/pushevent/PushEventMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/pushevent/PushEventMapper.xml index 32af916bf01..be7720c9a50 100644 --- a/server/sonar-db-dao/src/main/resources/org/sonar/db/pushevent/PushEventMapper.xml +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/pushevent/PushEventMapper.xml @@ -6,6 +6,7 @@ pe.uuid as uuid, pe.name as name, pe.project_uuid as projectUuid, + pe.language, pe.payload as payload, pe.created_at as createdAt </sql> @@ -15,6 +16,7 @@ uuid, name, project_uuid, + language, payload, created_at ) @@ -22,6 +24,7 @@ #{uuid,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR}, #{projectUuid,jdbcType=VARCHAR}, + #{language,jdbcType=VARCHAR}, #{payload,jdbcType=BLOB}, #{createdAt,jdbcType=BIGINT} ) diff --git a/server/sonar-db-dao/src/schema/schema-sq.ddl b/server/sonar-db-dao/src/schema/schema-sq.ddl index f2294d241f7..148cd2ec489 100644 --- a/server/sonar-db-dao/src/schema/schema-sq.ddl +++ b/server/sonar-db-dao/src/schema/schema-sq.ddl @@ -745,7 +745,8 @@ CREATE TABLE "PUSH_EVENTS"( "NAME" CHARACTER VARYING(40) NOT NULL, "PROJECT_UUID" CHARACTER VARYING(40) NOT NULL, "PAYLOAD" BINARY LARGE OBJECT NOT NULL, - "CREATED_AT" BIGINT NOT NULL + "CREATED_AT" BIGINT NOT NULL, + "LANGUAGE" CHARACTER VARYING(20) ); ALTER TABLE "PUSH_EVENTS" ADD CONSTRAINT "PK_PUSH_EVENTS" PRIMARY KEY("UUID"); CREATE INDEX "IDX_PUSH_EVEN_CREA_UUID_PROJ" ON "PUSH_EVENTS"("CREATED_AT" NULLS FIRST, "UUID" NULLS FIRST, "PROJECT_UUID" NULLS FIRST); diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v96/AddLanguageColumnToPushEventsTable.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v96/AddLanguageColumnToPushEventsTable.java new file mode 100644 index 00000000000..79c51760a3d --- /dev/null +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v96/AddLanguageColumnToPushEventsTable.java @@ -0,0 +1,65 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 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.platform.db.migration.version.v96; + +import java.sql.Connection; +import java.sql.SQLException; +import org.sonar.db.Database; +import org.sonar.db.DatabaseUtils; +import org.sonar.server.platform.db.migration.def.ColumnDef; +import org.sonar.server.platform.db.migration.def.VarcharColumnDef; +import org.sonar.server.platform.db.migration.sql.AddColumnsBuilder; +import org.sonar.server.platform.db.migration.step.DdlChange; + +import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.newVarcharColumnDefBuilder; +import static org.sonar.server.platform.db.migration.version.v96.CreatePushEventsTable.PUSH_EVENTS_TABLE_NAME; + +public class AddLanguageColumnToPushEventsTable extends DdlChange { + + static final String COLUMN_LANGUAGE = "language"; + + public AddLanguageColumnToPushEventsTable(Database db) { + super(db); + } + + @Override + public void execute(Context context) throws SQLException { + try (Connection connection = getDatabase().getDataSource().getConnection()) { + createLanguageColumn(context, connection); + } + } + + private void createLanguageColumn(Context context, Connection connection) { + VarcharColumnDef contextKeyColumn = newVarcharColumnDefBuilder() + .setColumnName(COLUMN_LANGUAGE) + .setIsNullable(true) + .setLimit(20).build(); + createColumnIfNotExists(context, connection, contextKeyColumn); + } + + private void createColumnIfNotExists(Context context, Connection connection, ColumnDef columnDef) { + if (!DatabaseUtils.tableColumnExists(connection, PUSH_EVENTS_TABLE_NAME, columnDef.getName())) { + context.execute(new AddColumnsBuilder(getDialect(), PUSH_EVENTS_TABLE_NAME) + .addColumn(columnDef) + .build()); + } + } + +} diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v96/DbVersion96.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v96/DbVersion96.java index 6aedc224b67..aeb62b406b0 100644 --- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v96/DbVersion96.java +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v96/DbVersion96.java @@ -40,6 +40,7 @@ public class DbVersion96 implements DbVersion { .add(6509, "Drop column sonarlint_ad_seen in 'users'", DropSonarlintAdSeenColumnInUsersTable.class) .add(6510, "Create table 'push_events'", CreatePushEventsTable.class) .add(6511, "Create index 'idx_push_even_crea_uuid_proj' on 'push_events'", CreateIndexForPushEvents.class) + .add(6512, "Add column 'language' to 'push_events'", AddLanguageColumnToPushEventsTable.class) ; } } diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v96/AddLanguageColumnToPushEventsTableTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v96/AddLanguageColumnToPushEventsTableTest.java new file mode 100644 index 00000000000..24ff79c7cc8 --- /dev/null +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v96/AddLanguageColumnToPushEventsTableTest.java @@ -0,0 +1,56 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 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.platform.db.migration.version.v96; + +import java.sql.SQLException; +import java.sql.Types; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.db.CoreDbTester; + +import static org.sonar.db.CoreDbTester.createForSchema; +import static org.sonar.server.platform.db.migration.version.v96.AddLanguageColumnToPushEventsTable.COLUMN_LANGUAGE; +import static org.sonar.server.platform.db.migration.version.v96.CreatePushEventsTable.PUSH_EVENTS_TABLE_NAME; + +public class AddLanguageColumnToPushEventsTableTest { + @Rule + public final CoreDbTester db = createForSchema(AddLanguageColumnToPushEventsTableTest.class, "schema.sql"); + + private final AddLanguageColumnToPushEventsTable underTest = new AddLanguageColumnToPushEventsTable(db.database()); + + @Test + public void column_education_principles_should_be_added() throws SQLException { + db.assertColumnDoesNotExist(PUSH_EVENTS_TABLE_NAME, COLUMN_LANGUAGE); + + underTest.execute(); + + db.assertColumnDefinition(PUSH_EVENTS_TABLE_NAME, COLUMN_LANGUAGE, Types.VARCHAR, 20, true); + } + + @Test + public void migration_should_be_reentrant() throws SQLException { + db.assertColumnDoesNotExist(PUSH_EVENTS_TABLE_NAME, COLUMN_LANGUAGE); + + underTest.execute(); + underTest.execute(); + + db.assertColumnDefinition(PUSH_EVENTS_TABLE_NAME, COLUMN_LANGUAGE, Types.VARCHAR, 20, true); + } +} diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v96/AddLanguageColumnToPushEventsTableTest/schema.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v96/AddLanguageColumnToPushEventsTableTest/schema.sql new file mode 100644 index 00000000000..3ec86992073 --- /dev/null +++ b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v96/AddLanguageColumnToPushEventsTableTest/schema.sql @@ -0,0 +1,7 @@ +CREATE TABLE "PUSH_EVENTS"( + "UUID" CHARACTER VARYING(40) NOT NULL, + "PROJECT_UUID" CHARACTER VARYING(40) NOT NULL, + "PAYLOAD" CHARACTER LARGE OBJECT NOT NULL, + "CREATED_AT" BIGINT NOT NULL +); +ALTER TABLE "PUSH_EVENTS" ADD CONSTRAINT "PK_PUSH_EVENTS" PRIMARY KEY("UUID"); diff --git a/server/sonar-main/src/test/java/org/sonar/application/cluster/AppNodesClusterHostsConsistencyTest.java b/server/sonar-main/src/test/java/org/sonar/application/cluster/AppNodesClusterHostsConsistencyTest.java index 13581a4d364..40875de72c1 100644 --- a/server/sonar-main/src/test/java/org/sonar/application/cluster/AppNodesClusterHostsConsistencyTest.java +++ b/server/sonar-main/src/test/java/org/sonar/application/cluster/AppNodesClusterHostsConsistencyTest.java @@ -40,10 +40,6 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; import org.sonar.application.config.TestAppSettings; -import org.sonar.core.util.issue.IssueChangeListener; -import org.sonar.core.util.issue.IssueChangedEvent; -import org.sonar.core.util.rule.RuleActivationListener; -import org.sonar.core.util.rule.RuleSetChangedEvent; import org.sonar.process.cluster.hz.DistributedAnswer; import org.sonar.process.cluster.hz.DistributedCall; import org.sonar.process.cluster.hz.DistributedCallback; @@ -199,26 +195,6 @@ public class AppNodesClusterHostsConsistencyTest { } @Override - public void subscribeRuleActivationTopic(RuleActivationListener listener) { - - } - - @Override - public void publishEvent(RuleSetChangedEvent event) { - - } - - @Override - public void subscribeIssueChangeTopic(IssueChangeListener listener) { - - } - - @Override - public void publishEvent(IssueChangedEvent event) { - - } - - @Override public void close() { } diff --git a/server/sonar-process/src/main/java/org/sonar/process/cluster/hz/HazelcastMember.java b/server/sonar-process/src/main/java/org/sonar/process/cluster/hz/HazelcastMember.java index 833864b8e96..65f43bae707 100644 --- a/server/sonar-process/src/main/java/org/sonar/process/cluster/hz/HazelcastMember.java +++ b/server/sonar-process/src/main/java/org/sonar/process/cluster/hz/HazelcastMember.java @@ -26,10 +26,6 @@ import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.concurrent.locks.Lock; -import org.sonar.core.util.issue.IssueChangeListener; -import org.sonar.core.util.issue.IssueChangedEvent; -import org.sonar.core.util.rule.RuleActivationListener; -import org.sonar.core.util.rule.RuleSetChangedEvent; import org.sonar.process.ProcessId; public interface HazelcastMember extends AutoCloseable { @@ -110,14 +106,6 @@ public interface HazelcastMember extends AutoCloseable { */ <T> void callAsync(DistributedCall<T> callable, MemberSelector memberSelector, DistributedCallback<T> callback); - void subscribeRuleActivationTopic(RuleActivationListener listener); - - void publishEvent(RuleSetChangedEvent event); - - void subscribeIssueChangeTopic(IssueChangeListener listener); - - void publishEvent(IssueChangedEvent event); - @Override void close(); } diff --git a/server/sonar-process/src/main/java/org/sonar/process/cluster/hz/HazelcastMemberImpl.java b/server/sonar-process/src/main/java/org/sonar/process/cluster/hz/HazelcastMemberImpl.java index bb6a541087d..685910361af 100644 --- a/server/sonar-process/src/main/java/org/sonar/process/cluster/hz/HazelcastMemberImpl.java +++ b/server/sonar-process/src/main/java/org/sonar/process/cluster/hz/HazelcastMemberImpl.java @@ -27,8 +27,6 @@ import com.hazelcast.core.HazelcastInstanceNotActiveException; import com.hazelcast.core.IExecutorService; import com.hazelcast.core.MultiExecutionCallback; import com.hazelcast.cp.IAtomicReference; -import com.hazelcast.topic.ITopic; -import com.hazelcast.topic.MessageListener; import java.util.Map; import java.util.Set; import java.util.UUID; @@ -39,10 +37,6 @@ import java.util.concurrent.TimeoutException; import java.util.concurrent.locks.Lock; import java.util.stream.Collectors; import org.slf4j.LoggerFactory; -import org.sonar.core.util.issue.IssueChangeListener; -import org.sonar.core.util.issue.IssueChangedEvent; -import org.sonar.core.util.rule.RuleActivationListener; -import org.sonar.core.util.rule.RuleSetChangedEvent; class HazelcastMemberImpl implements HazelcastMember { @@ -132,30 +126,6 @@ class HazelcastMemberImpl implements HazelcastMember { } @Override - public void subscribeRuleActivationTopic(RuleActivationListener listener) { - ITopic<RuleSetChangedEvent> topic = hzInstance.getTopic("ruleActivated"); - MessageListener<RuleSetChangedEvent> hzListener = message -> listener.listen(message.getMessageObject()); - topic.addMessageListener(hzListener); - } - - @Override - public void publishEvent(RuleSetChangedEvent event) { - hzInstance.getTopic("ruleActivated").publish(event); - } - - @Override - public void subscribeIssueChangeTopic(IssueChangeListener listener) { - ITopic<IssueChangedEvent> topic = hzInstance.getTopic("issueChanged"); - MessageListener<IssueChangedEvent> hzListener = message -> listener.listen(message.getMessageObject()); - topic.addMessageListener(hzListener); - } - - @Override - public void publishEvent(IssueChangedEvent event) { - hzInstance.getTopic("issueChanged").publish(event); - } - - @Override public void close() { try { hzInstance.shutdown(); diff --git a/server/sonar-process/src/test/java/org/sonar/process/cluster/hz/HazelcastMemberImplTest.java b/server/sonar-process/src/test/java/org/sonar/process/cluster/hz/HazelcastMemberImplTest.java index 032143d68d3..cc3e712f63d 100644 --- a/server/sonar-process/src/test/java/org/sonar/process/cluster/hz/HazelcastMemberImplTest.java +++ b/server/sonar-process/src/test/java/org/sonar/process/cluster/hz/HazelcastMemberImplTest.java @@ -21,8 +21,6 @@ package org.sonar.process.cluster.hz; import com.hazelcast.cluster.Member; import com.hazelcast.cluster.memberselector.MemberSelectors; -import com.hazelcast.core.HazelcastInstance; -import com.hazelcast.topic.ITopic; import java.net.InetAddress; import java.util.Arrays; import java.util.List; @@ -36,17 +34,10 @@ import org.junit.Test; import org.junit.rules.DisableOnDebug; import org.junit.rules.TestRule; import org.junit.rules.Timeout; -import org.sonar.core.util.issue.IssueChangeListener; -import org.sonar.core.util.rule.RuleActivationListener; import org.sonar.process.NetworkUtilsImpl; import org.sonar.process.ProcessId; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; public class HazelcastMemberImplTest { @@ -115,32 +106,6 @@ public class HazelcastMemberImplTest { assertThat(failures.get(0)).hasMessageContaining("BOOM"); } - @Test - public void subscribeRuleActivationTopic_listenerAdded() { - RuleActivationListener listener = mock(RuleActivationListener.class); - HazelcastInstance hzInstance = mock(HazelcastInstance.class); - ITopic<Object> topic = mock(ITopic.class); - when(hzInstance.getTopic(any())).thenReturn(topic); - HazelcastMemberImpl underTest = new HazelcastMemberImpl(hzInstance); - - underTest.subscribeRuleActivationTopic(listener); - - verify(topic, times(1)).addMessageListener(any()); - } - - @Test - public void subscribeIssueChangeTopic_listenerAdded() { - IssueChangeListener listener = mock(IssueChangeListener.class); - HazelcastInstance hzInstance = mock(HazelcastInstance.class); - ITopic<Object> topic = mock(ITopic.class); - when(hzInstance.getTopic(any())).thenReturn(topic); - HazelcastMemberImpl underTest = new HazelcastMemberImpl(hzInstance); - - underTest.subscribeIssueChangeTopic(listener); - - verify(topic, times(1)).addMessageListener(any()); - } - private static HazelcastMember newHzMember(int port, int... otherPorts) { return new HazelcastMemberBuilder(JoinConfigurationType.TCP_IP) .setProcessId(ProcessId.COMPUTE_ENGINE) diff --git a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/issues/DistributedIssueChangeEventsDistributor.java b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/issues/DistributedIssueChangeEventsDistributor.java deleted file mode 100644 index ae5bbc7e5dc..00000000000 --- a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/issues/DistributedIssueChangeEventsDistributor.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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.pushapi.issues; - -import org.sonar.api.server.ServerSide; -import org.sonar.core.util.issue.IssueChangeListener; -import org.sonar.core.util.issue.IssueChangedEvent; -import org.sonar.process.cluster.hz.HazelcastMember; - -@ServerSide -public class DistributedIssueChangeEventsDistributor implements IssueChangeEventsDistributor { - - private HazelcastMember hazelcastMember; - - public DistributedIssueChangeEventsDistributor(HazelcastMember hazelcastMember) { - this.hazelcastMember = hazelcastMember; - } - - @Override - public void subscribe(IssueChangeListener listener) { - hazelcastMember.subscribeIssueChangeTopic(listener); - } - - @Override - public void pushEvent(IssueChangedEvent event) { - hazelcastMember.publishEvent(event); - } -} diff --git a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/issues/IssueChangeBroadcastUtils.java b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/issues/IssueChangeBroadcastUtils.java deleted file mode 100644 index b784f0eead5..00000000000 --- a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/issues/IssueChangeBroadcastUtils.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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.pushapi.issues; - -import java.util.Collections; -import java.util.List; -import java.util.Set; -import java.util.function.Predicate; -import org.json.JSONArray; -import org.json.JSONObject; -import org.sonar.core.util.issue.Issue; -import org.sonar.core.util.issue.IssueChangedEvent; -import org.sonar.server.pushapi.sonarlint.SonarLintClient; - -import static java.util.Arrays.asList; - -public class IssueChangeBroadcastUtils { - private IssueChangeBroadcastUtils() { - - } - - public static Predicate<SonarLintClient> getFilterForEvent(IssueChangedEvent issueChangedEvent) { - List<String> affectedProjects = asList(issueChangedEvent.getProjectKey()); - return client -> { - Set<String> clientProjectKeys = client.getClientProjectKeys(); - return !Collections.disjoint(clientProjectKeys, affectedProjects); - }; - } - - public static String getMessage(IssueChangedEvent issueChangedEvent) { - return "event: " + issueChangedEvent.getEvent() + "\n" - + "data: " + toJson(issueChangedEvent); - } - - private static String toJson(IssueChangedEvent issueChangedEvent) { - JSONObject data = new JSONObject(); - data.put("projectKey", issueChangedEvent.getProjectKey()); - - JSONArray issuesJson = new JSONArray(); - for (Issue issue : issueChangedEvent.getIssues()) { - issuesJson.put(toJson(issue)); - } - data.put("issues", issuesJson); - data.put("userSeverity", issueChangedEvent.getUserSeverity()); - data.put("userType", issueChangedEvent.getUserType()); - data.put("resolved", issueChangedEvent.getResolved()); - - return data.toString(); - } - - private static JSONObject toJson(Issue issue) { - JSONObject ruleJson = new JSONObject(); - ruleJson.put("issueKey", issue.getIssueKey()); - ruleJson.put("branchName", issue.getBranchName()); - return ruleJson; - } - -} diff --git a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/issues/IssueChangeEventServiceImpl.java b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/issues/IssueChangeEventServiceImpl.java index 54dc1c71ba1..2e0e7f9b223 100644 --- a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/issues/IssueChangeEventServiceImpl.java +++ b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/issues/IssueChangeEventServiceImpl.java @@ -19,6 +19,8 @@ */ package org.sonar.server.pushapi.issues; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; import java.util.Collection; import java.util.Map; import java.util.Map.Entry; @@ -31,9 +33,13 @@ import org.sonar.core.issue.DefaultIssue; import org.sonar.core.issue.FieldDiffs.Diff; import org.sonar.core.util.issue.Issue; import org.sonar.core.util.issue.IssueChangedEvent; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; import org.sonar.db.component.BranchDto; import org.sonar.db.component.ComponentDto; +import org.sonar.db.pushevent.PushEventDto; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.elasticsearch.common.Strings.isNullOrEmpty; import static org.sonar.api.issue.DefaultTransitions.CONFIRM; import static org.sonar.api.issue.DefaultTransitions.FALSE_POSITIVE; @@ -43,6 +49,9 @@ import static org.sonar.db.component.BranchType.BRANCH; @ServerSide public class IssueChangeEventServiceImpl implements IssueChangeEventService { + private static final Gson GSON = new GsonBuilder().create(); + + private static final String EVENT_NAME = "IssueChanged"; private static final String FALSE_POSITIVE_KEY = "FALSE-POSITIVE"; private static final String WONT_FIX_KEY = "WONTFIX"; @@ -50,10 +59,10 @@ public class IssueChangeEventServiceImpl implements IssueChangeEventService { private static final String SEVERITY_KEY = "severity"; private static final String TYPE_KEY = "type"; - private final IssueChangeEventsDistributor eventsDistributor; + private final DbClient dbClient; - public IssueChangeEventServiceImpl(IssueChangeEventsDistributor eventsDistributor) { - this.eventsDistributor = eventsDistributor; + public IssueChangeEventServiceImpl(DbClient dbClient) { + this.dbClient = dbClient; } @Override @@ -69,7 +78,8 @@ public class IssueChangeEventServiceImpl implements IssueChangeEventService { IssueChangedEvent event = new IssueChangedEvent(projectKey, new Issue[]{changedIssue}, resolved, severity, type); - eventsDistributor.pushEvent(event); + + persistEvent(event, issue.projectUuid()); } @Override @@ -96,7 +106,7 @@ public class IssueChangeEventServiceImpl implements IssueChangeEventService { IssueChangedEvent event = getIssueChangedEvent(projectKey, issuesInProject, issueChanges); if (event != null) { - eventsDistributor.pushEvent(event); + persistEvent(event, entry.getValue().projectUuid()); } } } @@ -151,4 +161,19 @@ public class IssueChangeEventServiceImpl implements IssueChangeEventService { return transitionOrStatus.equals(WONT_FIX) || transitionOrStatus.equals(FALSE_POSITIVE) || transitionOrStatus.equals(FALSE_POSITIVE_KEY) || transitionOrStatus.equals(WONT_FIX_KEY); } + + private void persistEvent(IssueChangedEvent event, String entry) { + try (DbSession dbSession = dbClient.openSession(false)) { + PushEventDto eventDto = new PushEventDto() + .setName(EVENT_NAME) + .setProjectUuid(entry) + .setPayload(serializeIssueToPushEvent(event)); + dbClient.pushEventDao().insert(dbSession, eventDto); + dbSession.commit(); + } + } + + private static byte[] serializeIssueToPushEvent(IssueChangedEvent event) { + return GSON.toJson(event).getBytes(UTF_8); + } } diff --git a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/issues/IssueChangeEventsDistributor.java b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/issues/IssueChangeEventsDistributor.java deleted file mode 100644 index 486cc87068f..00000000000 --- a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/issues/IssueChangeEventsDistributor.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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.pushapi.issues; - -import org.sonar.core.util.issue.IssueChangeListener; -import org.sonar.core.util.issue.IssueChangedEvent; - -public interface IssueChangeEventsDistributor { - - void subscribe(IssueChangeListener listener); - - void pushEvent(IssueChangedEvent event); -} diff --git a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/issues/StandaloneIssueChangeEventsDistributor.java b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/issues/StandaloneIssueChangeEventsDistributor.java deleted file mode 100644 index 53aa3f34054..00000000000 --- a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/issues/StandaloneIssueChangeEventsDistributor.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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.pushapi.issues; - -import java.util.ArrayList; -import java.util.List; -import org.sonar.api.server.ServerSide; -import org.sonar.core.util.issue.IssueChangeListener; -import org.sonar.core.util.issue.IssueChangedEvent; - -@ServerSide -public class StandaloneIssueChangeEventsDistributor implements IssueChangeEventsDistributor { - - private List<IssueChangeListener> listeners = new ArrayList<>(); - - @Override - public void subscribe(IssueChangeListener listener) { - listeners.add(listener); - } - - @Override - public void pushEvent(IssueChangedEvent event) { - listeners.forEach(l -> l.listen(event)); - } -} diff --git a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/qualityprofile/DistributedRuleActivatorEventsDistributor.java b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/qualityprofile/DistributedRuleActivatorEventsDistributor.java deleted file mode 100644 index a45ff9ce87f..00000000000 --- a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/qualityprofile/DistributedRuleActivatorEventsDistributor.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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.pushapi.qualityprofile; - -import org.sonar.api.server.ServerSide; -import org.sonar.core.util.rule.RuleActivationListener; -import org.sonar.core.util.rule.RuleSetChangedEvent; -import org.sonar.process.cluster.hz.HazelcastMember; - -@ServerSide -public class DistributedRuleActivatorEventsDistributor implements RuleActivatorEventsDistributor { - - private HazelcastMember hazelcastMember; - - public DistributedRuleActivatorEventsDistributor(HazelcastMember hazelcastMember) { - this.hazelcastMember = hazelcastMember; - } - - @Override - public void subscribe(RuleActivationListener listener) { - hazelcastMember.subscribeRuleActivationTopic(listener); - } - - @Override - public void pushEvent(RuleSetChangedEvent event) { - hazelcastMember.publishEvent(event); - } -} diff --git a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/qualityprofile/QualityProfileChangeEventServiceImpl.java b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/qualityprofile/QualityProfileChangeEventServiceImpl.java index 76fc0799986..bb4bd7a0461 100644 --- a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/qualityprofile/QualityProfileChangeEventServiceImpl.java +++ b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/qualityprofile/QualityProfileChangeEventServiceImpl.java @@ -19,8 +19,11 @@ */ package org.sonar.server.pushapi.qualityprofile; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -39,6 +42,7 @@ import org.sonar.core.util.rule.RuleSetChangedEvent; import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.project.ProjectDto; +import org.sonar.db.pushevent.PushEventDto; import org.sonar.db.qualityprofile.ActiveRuleDto; import org.sonar.db.qualityprofile.ActiveRuleParamDto; import org.sonar.db.qualityprofile.OrgActiveRuleDto; @@ -47,6 +51,7 @@ import org.sonar.db.qualityprofile.QProfileDto; import org.sonar.db.rule.RuleDto; import org.sonar.server.qualityprofile.ActiveRuleChange; +import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.function.Predicate.not; import static org.sonar.server.qualityprofile.ActiveRuleChange.Type.ACTIVATED; import static org.sonar.server.qualityprofile.ActiveRuleChange.Type.DEACTIVATED; @@ -54,17 +59,18 @@ import static org.sonar.server.qualityprofile.ActiveRuleChange.Type.UPDATED; @ServerSide public class QualityProfileChangeEventServiceImpl implements QualityProfileChangeEventService { + private static final Gson GSON = new GsonBuilder().create(); + private static final String EVENT_NAME = "RuleSetChanged"; private final DbClient dbClient; - private final RuleActivatorEventsDistributor eventsDistributor; - public QualityProfileChangeEventServiceImpl(DbClient dbClient, RuleActivatorEventsDistributor eventsDistributor) { + public QualityProfileChangeEventServiceImpl(DbClient dbClient) { this.dbClient = dbClient; - this.eventsDistributor = eventsDistributor; } @Override - public void publishRuleActivationToSonarLintClients(ProjectDto project, @Nullable QProfileDto activatedProfile, @Nullable QProfileDto deactivatedProfile) { + public void publishRuleActivationToSonarLintClients(ProjectDto project, @Nullable QProfileDto activatedProfile, + @Nullable QProfileDto deactivatedProfile) { List<RuleChange> activatedRules = new ArrayList<>(); Set<String> deactivatedRules = new HashSet<>(); @@ -81,9 +87,8 @@ public class QualityProfileChangeEventServiceImpl implements QualityProfileChang } String language = activatedProfile != null ? activatedProfile.getLanguage() : deactivatedProfile.getLanguage(); - RuleSetChangedEvent event = new RuleSetChangedEvent(new String[] {project.getKey()}, activatedRules.toArray(new RuleChange[0]), deactivatedRules.toArray(new String[0]), - language); - eventsDistributor.pushEvent(event); + + persistPushEvent(project.getKey(), activatedRules.toArray(new RuleChange[0]), deactivatedRules, language, project.getUuid()); } private List<RuleChange> createRuleChanges(@NotNull QProfileDto profileDto) { @@ -193,15 +198,29 @@ public class QualityProfileChangeEventServiceImpl implements QualityProfileChang .map(RuleKey::toString) .collect(Collectors.toSet()); - Set<String> projectKeys = getProjectKeys(profiles); + Map<String, String> projectKeyAndUuids = getProjectKeyAndUuids(profiles); if (activatedRules.isEmpty() && deactivatedRules.isEmpty()) { return; } - RuleSetChangedEvent event = new RuleSetChangedEvent(projectKeys.toArray(new String[0]), activatedRules.toArray(new RuleChange[0]), deactivatedRules.toArray(new String[0]), - language); - eventsDistributor.pushEvent(event); + for (Map.Entry<String, String> entry : projectKeyAndUuids.entrySet()) { + persistPushEvent(entry.getKey(), activatedRules.toArray(new RuleChange[0]), deactivatedRules, language, entry.getValue()); + } + } + + private void persistPushEvent(String projectKey, RuleChange[] activatedRules, Set<String> deactivatedRules, String language, String projectUuid) { + RuleSetChangedEvent event = new RuleSetChangedEvent(projectKey, activatedRules, deactivatedRules.toArray(new String[0])); + + try (DbSession dbSession = dbClient.openSession(false)) { + PushEventDto eventDto = new PushEventDto() + .setName(EVENT_NAME) + .setProjectUuid(projectUuid) + .setLanguage(language) + .setPayload(serializeIssueToPushEvent(event)); + dbClient.pushEventDao().insert(dbSession, eventDto); + dbSession.commit(); + } } private Optional<String> templateKey(ActiveRuleChange arc) { @@ -219,17 +238,20 @@ public class QualityProfileChangeEventServiceImpl implements QualityProfileChang return Optional.empty(); } - private Set<String> getProjectKeys(Collection<QProfileDto> profiles) { - Set<String> projectKeys = new HashSet<>(); + private Map<String, String> getProjectKeyAndUuids(Collection<QProfileDto> profiles) { + Map<String, String> projectKeyAndUuids = new HashMap<>(); try (DbSession dbSession = dbClient.openSession(false)) { for (QProfileDto profileDto : profiles) { List<ProjectQprofileAssociationDto> associationDtos = dbClient.qualityProfileDao().selectSelectedProjects(dbSession, profileDto, null); for (ProjectQprofileAssociationDto associationDto : associationDtos) { - projectKeys.add(associationDto.getProjectKey()); + projectKeyAndUuids.put(associationDto.getProjectKey(), associationDto.getProjectUuid()); } } - return projectKeys; + return projectKeyAndUuids; } } + private static byte[] serializeIssueToPushEvent(RuleSetChangedEvent event) { + return GSON.toJson(event).getBytes(UTF_8); + } } diff --git a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/qualityprofile/RuleActivatorEventsDistributor.java b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/qualityprofile/RuleActivatorEventsDistributor.java deleted file mode 100644 index 8914610b28a..00000000000 --- a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/qualityprofile/RuleActivatorEventsDistributor.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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.pushapi.qualityprofile; - -import org.sonar.core.util.rule.RuleActivationListener; -import org.sonar.core.util.rule.RuleSetChangedEvent; - -public interface RuleActivatorEventsDistributor { - - void subscribe(RuleActivationListener listener); - - void pushEvent(RuleSetChangedEvent event); -} diff --git a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/qualityprofile/RuleSetChangeBroadcastUtils.java b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/qualityprofile/RuleSetChangeBroadcastUtils.java deleted file mode 100644 index 159d67fa593..00000000000 --- a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/qualityprofile/RuleSetChangeBroadcastUtils.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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.pushapi.qualityprofile; - -import java.util.Collections; -import java.util.List; -import java.util.Set; -import java.util.function.Predicate; -import org.json.JSONArray; -import org.json.JSONObject; -import org.sonar.core.util.ParamChange; -import org.sonar.core.util.rule.RuleChange; -import org.sonar.core.util.rule.RuleSetChangedEvent; -import org.sonar.server.pushapi.sonarlint.SonarLintClient; - -import static java.util.Arrays.asList; - -public class RuleSetChangeBroadcastUtils { - private RuleSetChangeBroadcastUtils() { - } - - public static Predicate<SonarLintClient> getFilterForEvent(RuleSetChangedEvent ruleSetChangedEvent) { - List<String> affectedProjects = asList(ruleSetChangedEvent.getProjects()); - return client -> { - Set<String> clientProjectKeys = client.getClientProjectKeys(); - Set<String> languages = client.getLanguages(); - return !Collections.disjoint(clientProjectKeys, affectedProjects) && languages.contains(ruleSetChangedEvent.getLanguage()); - }; - } - - public static String getMessage(RuleSetChangedEvent ruleSetChangedEvent) { - return "event: " + ruleSetChangedEvent.getEvent() + "\n" - + "data: " + toJson(ruleSetChangedEvent); - } - - private static String toJson(RuleSetChangedEvent ruleSetChangedEvent) { - JSONObject data = new JSONObject(); - data.put("projects", ruleSetChangedEvent.getProjects()); - - JSONArray activatedRulesJson = new JSONArray(); - for (RuleChange rule : ruleSetChangedEvent.getActivatedRules()) { - activatedRulesJson.put(toJson(rule)); - } - data.put("activatedRules", activatedRulesJson); - - JSONArray deactivatedRulesJson = new JSONArray(); - for (String ruleKey : ruleSetChangedEvent.getDeactivatedRules()) { - deactivatedRulesJson.put(ruleKey); - } - data.put("deactivatedRules", deactivatedRulesJson); - - return data.toString(); - } - - private static JSONObject toJson(RuleChange rule) { - JSONObject ruleJson = new JSONObject(); - ruleJson.put("key", rule.getKey()); - ruleJson.put("language", rule.getLanguage()); - ruleJson.put("severity", rule.getSeverity()); - ruleJson.put("templateKey", rule.getTemplateKey()); - - JSONArray params = new JSONArray(); - for (ParamChange paramChange : rule.getParams()) { - params.put(toJson(paramChange)); - } - ruleJson.put("params", params); - return ruleJson; - } - - private static JSONObject toJson(ParamChange paramChange) { - JSONObject param = new JSONObject(); - param.put("key", paramChange.getKey()); - param.put("value", paramChange.getValue()); - return param; - } - -} diff --git a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/qualityprofile/StandaloneRuleActivatorEventsDistributor.java b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/qualityprofile/StandaloneRuleActivatorEventsDistributor.java deleted file mode 100644 index 64a5d15e10a..00000000000 --- a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/qualityprofile/StandaloneRuleActivatorEventsDistributor.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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.pushapi.qualityprofile; - -import java.util.ArrayList; -import java.util.List; -import org.sonar.api.server.ServerSide; -import org.sonar.core.util.rule.RuleActivationListener; -import org.sonar.core.util.rule.RuleSetChangedEvent; - -@ServerSide -public class StandaloneRuleActivatorEventsDistributor implements RuleActivatorEventsDistributor { - - private List<RuleActivationListener> listeners = new ArrayList<>(); - - @Override - public void subscribe(RuleActivationListener listener) { - listeners.add(listener); - } - - @Override - public void pushEvent(RuleSetChangedEvent event) { - listeners.forEach(l -> l.listen(event)); - } -} diff --git a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/scheduler/polling/PushEventPollScheduler.java b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/scheduler/polling/PushEventPollScheduler.java index 3f94d26d4f2..d7b0f9d01ab 100644 --- a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/scheduler/polling/PushEventPollScheduler.java +++ b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/scheduler/polling/PushEventPollScheduler.java @@ -49,6 +49,7 @@ public class PushEventPollScheduler implements Startable { private static final Logger LOG = Loggers.get(PushEventPollScheduler.class); private static final String INITIAL_DELAY_IN_SECONDS = "sonar.pushevents.polling.initial.delay"; + private static final String LAST_TIMESTAMP_IN_SECONDS = "sonar.pushevents.polling.last.timestamp"; private static final String PERIOD_IN_SECONDS = "sonar.pushevents.polling.period"; private static final String PAGE_SIZE = "sonar.pushevents.polling.page.size"; @@ -91,7 +92,7 @@ public class PushEventPollScheduler implements Startable { } if (lastPullTimestamp == null) { - lastPullTimestamp = system2.now(); + lastPullTimestamp = getLastPullTimestamp(); } var projectKeys = getClientsProjectKeys(clients); @@ -120,7 +121,8 @@ public class PushEventPollScheduler implements Startable { LOG.debug("Could not find key for project with uuid [{}]", pushEventDto.getProjectUuid()); return Optional.empty(); } - return Optional.of(new SonarLintPushEvent(pushEventDto.getName(), pushEventDto.getPayload(), resolvedProjectKey)); + return Optional.of(new SonarLintPushEvent(pushEventDto.getName(), pushEventDto.getPayload(), resolvedProjectKey, + pushEventDto.getLanguage())); } private static Set<String> getClientsProjectKeys(List<SonarLintClient> clients) { @@ -154,8 +156,12 @@ public class PushEventPollScheduler implements Startable { return config.getLong(PERIOD_IN_SECONDS).orElse(40L); } + public long getLastPullTimestamp() { + // execute every 40 seconds + return config.getLong(LAST_TIMESTAMP_IN_SECONDS).orElse(system2.now()); + } + public long getPageSize() { - // 20 events per 40 seconds return config.getLong(PAGE_SIZE).orElse(20L); } diff --git a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/sonarlint/SonarLintClientsRegistry.java b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/sonarlint/SonarLintClientsRegistry.java index 5905f91bb4a..ede8477eea3 100644 --- a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/sonarlint/SonarLintClientsRegistry.java +++ b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/sonarlint/SonarLintClientsRegistry.java @@ -24,62 +24,32 @@ import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; -import java.util.function.Predicate; import javax.servlet.AsyncEvent; import javax.servlet.AsyncListener; import org.sonar.api.server.ServerSide; import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; -import org.sonar.core.util.issue.IssueChangeListener; -import org.sonar.core.util.issue.IssueChangedEvent; -import org.sonar.core.util.rule.RuleActivationListener; -import org.sonar.core.util.rule.RuleSetChangedEvent; import org.sonar.server.exceptions.ForbiddenException; -import org.sonar.server.pushapi.issues.IssueChangeBroadcastUtils; -import org.sonar.server.pushapi.issues.IssueChangeEventsDistributor; -import org.sonar.server.pushapi.qualityprofile.RuleActivatorEventsDistributor; -import org.sonar.server.pushapi.qualityprofile.RuleSetChangeBroadcastUtils; @ServerSide -public class SonarLintClientsRegistry implements RuleActivationListener, IssueChangeListener { +public class SonarLintClientsRegistry { private static final Logger LOG = Loggers.get(SonarLintClientsRegistry.class); private final SonarLintClientPermissionsValidator sonarLintClientPermissionsValidator; private final List<SonarLintClient> clients = new CopyOnWriteArrayList<>(); - private final RuleActivatorEventsDistributor ruleEventsDistributor; - private final IssueChangeEventsDistributor issueChangeEventsDistributor; - private boolean registeredToEvents = false; - - public SonarLintClientsRegistry(IssueChangeEventsDistributor issueChangeEventsDistributor, - RuleActivatorEventsDistributor ruleActivatorEventsDistributor, SonarLintClientPermissionsValidator permissionsValidator) { - this.issueChangeEventsDistributor = issueChangeEventsDistributor; + public SonarLintClientsRegistry(SonarLintClientPermissionsValidator permissionsValidator) { this.sonarLintClientPermissionsValidator = permissionsValidator; - this.ruleEventsDistributor = ruleActivatorEventsDistributor; } public void registerClient(SonarLintClient sonarLintClient) { - ensureListeningToEvents(); clients.add(sonarLintClient); sonarLintClient.scheduleHeartbeat(); sonarLintClient.addListener(new SonarLintClientEventsListener(sonarLintClient)); LOG.debug("Registering new SonarLint client"); } - private synchronized void ensureListeningToEvents() { - if (registeredToEvents) { - return; - } - try { - ruleEventsDistributor.subscribe(this); - issueChangeEventsDistributor.subscribe(this); - registeredToEvents = true; - } catch (RuntimeException e) { - LOG.warn("Can not listen to rule activation or issue events for server push. Web Server might not have started fully yet.", e); - } - } - public void unregisterClient(SonarLintClient client) { client.close(); clients.remove(client); @@ -94,18 +64,8 @@ public class SonarLintClientsRegistry implements RuleActivationListener, IssueCh return clients.size(); } - @Override - public void listen(RuleSetChangedEvent ruleSetChangedEvent) { - broadcastMessage(ruleSetChangedEvent, RuleSetChangeBroadcastUtils.getFilterForEvent(ruleSetChangedEvent)); - } - - @Override - public void listen(IssueChangedEvent issueChangedEvent) { - broadcastMessage(issueChangedEvent, IssueChangeBroadcastUtils.getFilterForEvent(issueChangedEvent)); - } - public void broadcastMessage(SonarLintPushEvent event) { - clients.stream().filter(client -> client.getClientProjectKeys().contains(event.getProjectKey())) + clients.stream().filter(client -> isRelevantEvent(event, client)) .forEach(c -> { Set<String> clientProjectKeys = new HashSet<>(c.getClientProjectKeys()); clientProjectKeys.retainAll(Set.of(event.getProjectKey())); @@ -122,42 +82,9 @@ public class SonarLintClientsRegistry implements RuleActivationListener, IssueCh }); } - public void broadcastMessage(RuleSetChangedEvent event, Predicate<SonarLintClient> filter) { - clients.stream().filter(filter).forEach(c -> { - Set<String> projectKeysInterestingForClient = new HashSet<>(c.getClientProjectKeys()); - projectKeysInterestingForClient.retainAll(Set.of(event.getProjects())); - try { - sonarLintClientPermissionsValidator.validateUserCanReceivePushEventForProjects(c.getUserUuid(), projectKeysInterestingForClient); - RuleSetChangedEvent personalizedEvent = new RuleSetChangedEvent(projectKeysInterestingForClient.toArray(String[]::new), event.getActivatedRules(), - event.getDeactivatedRules(), event.getLanguage()); - String message = RuleSetChangeBroadcastUtils.getMessage(personalizedEvent); - c.writeAndFlush(message); - } catch (ForbiddenException forbiddenException) { - logClientUnauthenticated(forbiddenException); - unregisterClient(c); - } catch (IllegalStateException | IOException e) { - logUnexpectedError(e); - unregisterClient(c); - } - }); - } - - public void broadcastMessage(IssueChangedEvent event, Predicate<SonarLintClient> filter) { - clients.stream().filter(filter).forEach(c -> { - Set<String> projectKeysInterestingForClient = new HashSet<>(c.getClientProjectKeys()); - projectKeysInterestingForClient.retainAll(Set.of(event.getProjectKey())); - try { - sonarLintClientPermissionsValidator.validateUserCanReceivePushEventForProjects(c.getUserUuid(), projectKeysInterestingForClient); - String message = IssueChangeBroadcastUtils.getMessage(event); - c.writeAndFlush(message); - } catch (ForbiddenException forbiddenException) { - logClientUnauthenticated(forbiddenException); - unregisterClient(c); - } catch (IllegalStateException | IOException e) { - logUnexpectedError(e); - unregisterClient(c); - } - }); + private static boolean isRelevantEvent(SonarLintPushEvent event, SonarLintClient client) { + return client.getClientProjectKeys().contains(event.getProjectKey()) + && (!event.getName().equals("RuleSetChanged") || client.getLanguages().contains(event.getLanguage())); } private static void logUnexpectedError(Exception e) { diff --git a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/sonarlint/SonarLintPushEvent.java b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/sonarlint/SonarLintPushEvent.java index 5484fb17602..9e7ee98f044 100644 --- a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/sonarlint/SonarLintPushEvent.java +++ b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/sonarlint/SonarLintPushEvent.java @@ -19,6 +19,9 @@ */ package org.sonar.server.pushapi.sonarlint; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; + import static java.nio.charset.StandardCharsets.UTF_8; public class SonarLintPushEvent { @@ -26,17 +29,24 @@ public class SonarLintPushEvent { private final String name; private final byte[] data; private final String projectKey; + private final String language; - public SonarLintPushEvent(String name, byte[] data, String projectKey) { + public SonarLintPushEvent(String name, byte[] data, String projectKey, @Nullable String language) { this.name = name; this.data = data; this.projectKey = projectKey; + this.language = language; } public String getProjectKey() { return projectKey; } + @CheckForNull + public String getLanguage() { + return language; + } + public String getName() { return name; } diff --git a/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/issues/DistributedIssueChangeEventsDistributorTest.java b/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/issues/DistributedIssueChangeEventsDistributorTest.java deleted file mode 100644 index b08a831cb47..00000000000 --- a/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/issues/DistributedIssueChangeEventsDistributorTest.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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.pushapi.issues; - -import org.junit.Test; -import org.sonar.core.util.issue.IssueChangeListener; -import org.sonar.core.util.issue.IssueChangedEvent; -import org.sonar.process.cluster.hz.HazelcastMember; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -public class DistributedIssueChangeEventsDistributorTest { - HazelcastMember hazelcastMember = mock(HazelcastMember.class); - IssueChangeListener issueChangeListener = mock(IssueChangeListener.class); - IssueChangedEvent event = mock(IssueChangedEvent.class); - - public final DistributedIssueChangeEventsDistributor underTest = new DistributedIssueChangeEventsDistributor(hazelcastMember); - - @Test - public void subscribe_subscribesHazelCastMember() { - underTest.subscribe(issueChangeListener); - verify(hazelcastMember).subscribeIssueChangeTopic(issueChangeListener); - } - - @Test - public void pushEvent_publishesEvent() { - underTest.pushEvent(event); - verify(hazelcastMember).publishEvent(event); - } -} diff --git a/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/issues/IssueChangeBroadcastUtilsTest.java b/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/issues/IssueChangeBroadcastUtilsTest.java deleted file mode 100644 index 8e65c839b02..00000000000 --- a/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/issues/IssueChangeBroadcastUtilsTest.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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.pushapi.issues; - -import java.util.Set; -import java.util.function.Predicate; -import javax.servlet.AsyncContext; -import org.junit.Test; -import org.sonar.core.util.issue.Issue; -import org.sonar.core.util.issue.IssueChangedEvent; -import org.sonar.server.pushapi.sonarlint.SonarLintClient; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; - -public class IssueChangeBroadcastUtilsTest { - - private final String PROJECT_KEY = "projectKey"; - private final String USER_UUID = "userUUID"; - private final AsyncContext asyncContext = mock(AsyncContext.class); - - @Test - public void getsFilterForEvent() { - Issue[] issues = new Issue[]{ new Issue("issue-1", "branch-1")}; - IssueChangedEvent issueChangedEvent = new IssueChangedEvent(PROJECT_KEY, issues, true, "BLOCKER", "BUG"); - Predicate<SonarLintClient> predicate = IssueChangeBroadcastUtils.getFilterForEvent(issueChangedEvent); - assertThat(predicate.test(new SonarLintClient(asyncContext, Set.of(PROJECT_KEY), Set.of(), USER_UUID))).isTrue(); - assertThat(predicate.test(new SonarLintClient(asyncContext, Set.of(), Set.of(), USER_UUID))).isFalse(); - assertThat(predicate.test(new SonarLintClient(asyncContext, Set.of("another-project"), Set.of(), USER_UUID))).isFalse(); - } - - @Test - public void getsMessageForEvent() { - Issue[] issues = new Issue[]{ new Issue("issue-1", "branch-1")}; - IssueChangedEvent issueChangedEvent = new IssueChangedEvent(PROJECT_KEY, issues, true, "BLOCKER", "BUG"); - String message = IssueChangeBroadcastUtils.getMessage(issueChangedEvent); - - assertThat(message).isEqualTo("event: IssueChangedEvent\n" + - "data: {\"projectKey\":\""+ PROJECT_KEY+"\"," + - "\"userType\":\"BUG\"," + - "\"issues\":[{\"issueKey\":\"issue-1\",\"branchName\":\"branch-1\"}]," + - "\"userSeverity\":\"BLOCKER\"," + - "\"resolved\":true}"); - } -} diff --git a/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/issues/IssueChangeEventServiceImplTest.java b/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/issues/IssueChangeEventServiceImplTest.java index 91d543e2e73..d8749bc4752 100644 --- a/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/issues/IssueChangeEventServiceImplTest.java +++ b/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/issues/IssueChangeEventServiceImplTest.java @@ -19,34 +19,30 @@ */ package org.sonar.server.pushapi.issues; +import java.nio.charset.StandardCharsets; +import java.util.Deque; import java.util.HashMap; -import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import javax.annotation.Nullable; import org.junit.Rule; import org.junit.Test; -import org.mockito.ArgumentCaptor; import org.sonar.api.rules.RuleType; import org.sonar.core.issue.DefaultIssue; import org.sonar.core.issue.FieldDiffs; -import org.sonar.core.util.issue.IssueChangedEvent; import org.sonar.db.DbTester; import org.sonar.db.component.BranchDto; import org.sonar.db.component.BranchType; import org.sonar.db.component.ComponentDto; import org.sonar.db.issue.IssueDto; import org.sonar.db.project.ProjectDto; +import org.sonar.db.pushevent.PushEventDto; import org.sonar.db.rule.RuleDto; import org.sonarqube.ws.Common; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.tuple; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.sonar.api.issue.DefaultTransitions.CONFIRM; import static org.sonar.api.issue.DefaultTransitions.FALSE_POSITIVE; import static org.sonar.api.issue.DefaultTransitions.REOPEN; @@ -64,9 +60,7 @@ public class IssueChangeEventServiceImplTest { @Rule public DbTester db = DbTester.create(); - IssueChangeEventsDistributor eventsDistributor = mock(IssueChangeEventsDistributor.class); - - public final IssueChangeEventServiceImpl underTest = new IssueChangeEventServiceImpl(eventsDistributor); + public final IssueChangeEventServiceImpl underTest = new IssueChangeEventServiceImpl(db.getDbClient()); @Test public void distributeIssueChangeEvent_singleIssueChange_severityChange() { @@ -74,9 +68,9 @@ public class IssueChangeEventServiceImplTest { ProjectDto project = db.getDbClient().projectDao().selectByUuid(db.getSession(), componentDto.uuid()).get(); BranchDto branch = db.getDbClient().branchDao().selectByUuid(db.getSession(), project.getUuid()).get(); RuleDto rule = db.rules().insert(); - IssueDto issue = db.issues().insert(rule, project, componentDto, i-> i.setSeverity(MAJOR.name())); + IssueDto issue = db.issues().insert(rule, project, componentDto, i -> i.setSeverity(MAJOR.name())); - assertIssueDistribution(project, branch, issue, BLOCKER.name(), null, null, null, 1); + assertPushEventIsPersisted(project, branch, issue, BLOCKER.name(), null, null, null, 1); } @Test @@ -85,9 +79,9 @@ public class IssueChangeEventServiceImplTest { ProjectDto project = db.getDbClient().projectDao().selectByUuid(db.getSession(), componentDto.uuid()).get(); BranchDto branch = db.getDbClient().branchDao().selectByUuid(db.getSession(), project.getUuid()).get(); RuleDto rule = db.rules().insert(); - IssueDto issue = db.issues().insert(rule, project, componentDto, i-> i.setSeverity(MAJOR.name())); + IssueDto issue = db.issues().insert(rule, project, componentDto, i -> i.setSeverity(MAJOR.name())); - assertIssueDistribution(project, branch, issue, null, Common.RuleType.BUG.name(), null, null, 1); + assertPushEventIsPersisted(project, branch, issue, null, Common.RuleType.BUG.name(), null, null, 1); } @Test @@ -96,16 +90,16 @@ public class IssueChangeEventServiceImplTest { ProjectDto project = db.getDbClient().projectDao().selectByUuid(db.getSession(), componentDto.uuid()).get(); BranchDto branch = db.getDbClient().branchDao().selectByUuid(db.getSession(), project.getUuid()).get(); RuleDto rule = db.rules().insert(); - IssueDto issue = db.issues().insert(rule, project, componentDto, i-> i.setSeverity(MAJOR.name())); - - assertIssueDistribution(project, branch, issue, null, null, WONT_FIX, true, 1); - assertIssueDistribution(project, branch, issue, null, null, REOPEN, false, 2); - assertIssueDistribution(project, branch, issue, null, null, FALSE_POSITIVE, true, 3); - assertIssueDistribution(project, branch, issue, null, null, REOPEN, false, 4); - assertIssueDistribution(project, branch, issue, null, null, RESOLVE, false, 5); - assertIssueDistribution(project, branch, issue, null, null, REOPEN, false, 6); - assertNoIssueDistribution(project, branch, issue, null, null, CONFIRM); - assertNoIssueDistribution(project, branch, issue, null, null, UNCONFIRM); + IssueDto issue = db.issues().insert(rule, project, componentDto, i -> i.setSeverity(MAJOR.name())); + + assertPushEventIsPersisted(project, branch, issue, null, null, WONT_FIX, true, 1); + assertPushEventIsPersisted(project, branch, issue, null, null, REOPEN, false, 2); + assertPushEventIsPersisted(project, branch, issue, null, null, FALSE_POSITIVE, true, 3); + assertPushEventIsPersisted(project, branch, issue, null, null, REOPEN, false, 4); + assertPushEventIsPersisted(project, branch, issue, null, null, RESOLVE, false, 5); + assertPushEventIsPersisted(project, branch, issue, null, null, REOPEN, false, 6); + assertNoIssueDistribution(project, branch, issue, null, null, CONFIRM, 7); + assertNoIssueDistribution(project, branch, issue, null, null, UNCONFIRM, 8); } @Test @@ -114,9 +108,9 @@ public class IssueChangeEventServiceImplTest { ProjectDto project = db.getDbClient().projectDao().selectByUuid(db.getSession(), componentDto.uuid()).get(); BranchDto branch = db.getDbClient().branchDao().selectByUuid(db.getSession(), project.getUuid()).get(); RuleDto rule = db.rules().insert(); - IssueDto issue = db.issues().insert(rule, project, componentDto, i-> i.setSeverity(MAJOR.name())); + IssueDto issue = db.issues().insert(rule, project, componentDto, i -> i.setSeverity(MAJOR.name())); - assertIssueDistribution(project, branch, issue, BLOCKER.name(), Common.RuleType.BUG.name(), WONT_FIX, true, 1); + assertPushEventIsPersisted(project, branch, issue, BLOCKER.name(), Common.RuleType.BUG.name(), WONT_FIX, true, 1); } @Test @@ -126,17 +120,17 @@ public class IssueChangeEventServiceImplTest { ComponentDto componentDto1 = db.components().insertPublicProject(); ProjectDto project1 = db.getDbClient().projectDao().selectByUuid(db.getSession(), componentDto1.uuid()).get(); BranchDto branch1 = db.getDbClient().branchDao().selectByUuid(db.getSession(), project1.getUuid()).get(); - IssueDto issue1 = db.issues().insert(rule, project1, componentDto1, i-> i.setSeverity(MAJOR.name()).setType(RuleType.BUG)); + IssueDto issue1 = db.issues().insert(rule, project1, componentDto1, i -> i.setSeverity(MAJOR.name()).setType(RuleType.BUG)); ComponentDto componentDto2 = db.components().insertPublicProject(); ProjectDto project2 = db.getDbClient().projectDao().selectByUuid(db.getSession(), componentDto2.uuid()).get(); BranchDto branch2 = db.getDbClient().branchDao().selectByUuid(db.getSession(), project2.getUuid()).get(); - IssueDto issue2 = db.issues().insert(rule, project2, componentDto2, i-> i.setSeverity(MAJOR.name()).setType(RuleType.BUG)); + IssueDto issue2 = db.issues().insert(rule, project2, componentDto2, i -> i.setSeverity(MAJOR.name()).setType(RuleType.BUG)); ComponentDto componentDto3 = db.components().insertPublicProject(); ProjectDto project3 = db.getDbClient().projectDao().selectByUuid(db.getSession(), componentDto3.uuid()).get(); BranchDto branch3 = db.getDbClient().branchDao().selectByUuid(db.getSession(), project3.getUuid()).get(); - IssueDto issue3 = db.issues().insert(rule, project3, componentDto3, i-> i.setSeverity(MAJOR.name()).setType(RuleType.BUG)); + IssueDto issue3 = db.issues().insert(rule, project3, componentDto3, i -> i.setSeverity(MAJOR.name()).setType(RuleType.BUG)); DefaultIssue defaultIssue1 = issue1.toDefaultIssue().setCurrentChangeWithoutAddChange(new FieldDiffs() .setDiff("resolution", null, null) @@ -159,18 +153,35 @@ public class IssueChangeEventServiceImplTest { underTest.distributeIssueChangeEvent(issues, projectsByUuid, branchesByProjectUuid); - ArgumentCaptor<IssueChangedEvent> eventCaptor = ArgumentCaptor.forClass(IssueChangedEvent.class); - verify(eventsDistributor, times(2)).pushEvent(eventCaptor.capture()); + Deque<PushEventDto> issueChangedEvents = db.getDbClient().pushEventDao() + .selectChunkByProjectUuids(db.getSession(), Set.of(project1.getUuid(), project2.getUuid()), + 1l, null, 3); - List<IssueChangedEvent> issueChangedEvents = eventCaptor.getAllValues(); assertThat(issueChangedEvents).hasSize(2); assertThat(issueChangedEvents) - .extracting(IssueChangedEvent::getEvent, IssueChangedEvent::getProjectKey, - IssueChangedEvent::getUserSeverity, IssueChangedEvent::getUserType, IssueChangedEvent::getResolved) + .extracting(PushEventDto::getName, PushEventDto::getProjectUuid) .containsExactlyInAnyOrder( - tuple("IssueChangedEvent", project1.getKey(), CRITICAL.name(), CODE_SMELL.name(), false), - tuple("IssueChangedEvent", project2.getKey(), CRITICAL.name(), CODE_SMELL.name(), true)); + tuple("IssueChanged", project1.getUuid()), + tuple("IssueChanged", project2.getUuid())); + + Optional<PushEventDto> project1Event = issueChangedEvents.stream().filter(e -> e.getProjectUuid().equals(project1.getUuid())).findFirst(); + Optional<PushEventDto> project2Event = issueChangedEvents.stream().filter(e -> e.getProjectUuid().equals(project2.getUuid())).findFirst(); + + assertThat(project1Event).isPresent(); + assertThat(project2Event).isPresent(); + + String firstPayload = new String(project1Event.get().getPayload(), StandardCharsets.UTF_8); + assertThat(firstPayload) + .contains("\"userSeverity\":\"" + CRITICAL.name() + "\"", + "\"userType\":\"" + CODE_SMELL.name() + "\"", + "\"resolved\":" + false); + + String secondPayload = new String(project2Event.get().getPayload(), StandardCharsets.UTF_8); + assertThat(secondPayload) + .contains("\"userSeverity\":\"" + CRITICAL.name() + "\"", + "\"userType\":\"" + CODE_SMELL.name() + "\"", + "\"resolved\":" + true); } @Test @@ -183,7 +194,7 @@ public class IssueChangeEventServiceImplTest { .setMergeBranchUuid(project.uuid())); BranchDto branch1 = db.getDbClient().branchDao().selectByUuid(db.getSession(), pullRequest.uuid()).get(); ComponentDto file = db.components().insertComponent(newFileDto(pullRequest)); - IssueDto issue1 = db.issues().insert(rule, pullRequest, file, i-> i.setSeverity(MAJOR.name()).setType(RuleType.BUG)); + IssueDto issue1 = db.issues().insert(rule, pullRequest, file, i -> i.setSeverity(MAJOR.name()).setType(RuleType.BUG)); DefaultIssue defaultIssue1 = issue1.toDefaultIssue().setCurrentChangeWithoutAddChange(new FieldDiffs() .setDiff("resolution", null, null) @@ -198,29 +209,40 @@ public class IssueChangeEventServiceImplTest { underTest.distributeIssueChangeEvent(issues, projectsByUuid, branchesByProjectUuid); - verifyNoInteractions(eventsDistributor); } - private void assertIssueDistribution(ProjectDto project, BranchDto branch, IssueDto issue, @Nullable String severity, - @Nullable String type, @Nullable String transition, Boolean resolved, int times) { + private void assertNoIssueDistribution(ProjectDto project, BranchDto branch, IssueDto issue, @Nullable String severity, + @Nullable String type, @Nullable String transition, int page) { underTest.distributeIssueChangeEvent(issue.toDefaultIssue(), severity, type, transition, branch, project.getKey()); - ArgumentCaptor<IssueChangedEvent> eventCaptor = ArgumentCaptor.forClass(IssueChangedEvent.class); - verify(eventsDistributor, times(times)).pushEvent(eventCaptor.capture()); - - IssueChangedEvent issueChangedEvent = eventCaptor.getValue(); - assertThat(issueChangedEvent).isNotNull(); - assertThat(issueChangedEvent).extracting(IssueChangedEvent::getEvent, IssueChangedEvent::getProjectKey, - IssueChangedEvent::getUserSeverity, IssueChangedEvent::getUserType, IssueChangedEvent::getResolved) - .containsExactly("IssueChangedEvent", project.getKey(), severity, type, resolved); + Deque<PushEventDto> events = db.getDbClient().pushEventDao() + .selectChunkByProjectUuids(db.getSession(), Set.of(project.getUuid()), 1l, null, page); + assertThat(events).hasSizeLessThan(page); } - private void assertNoIssueDistribution(ProjectDto project, BranchDto branch, IssueDto issue, @Nullable String severity, - @Nullable String type, @Nullable String transition) { + private void assertPushEventIsPersisted(ProjectDto project, BranchDto branch, IssueDto issue, @Nullable String severity, + @Nullable String type, @Nullable String transition, Boolean resolved, int page) { underTest.distributeIssueChangeEvent(issue.toDefaultIssue(), severity, type, transition, branch, project.getKey()); - ArgumentCaptor<IssueChangedEvent> eventCaptor = ArgumentCaptor.forClass(IssueChangedEvent.class); - verifyNoMoreInteractions(eventsDistributor); + Deque<PushEventDto> events = db.getDbClient().pushEventDao() + .selectChunkByProjectUuids(db.getSession(), Set.of(project.getUuid()), 1l, null, page); + assertThat(events).isNotEmpty(); + assertThat(events).extracting(PushEventDto::getName, PushEventDto::getProjectUuid) + .contains(tuple("IssueChanged", project.getUuid())); + + String payload = new String(events.getLast().getPayload(), StandardCharsets.UTF_8); + if (severity != null) { + assertThat(payload).contains("\"userSeverity\":\"" + severity + "\""); + } + + if (type != null) { + assertThat(payload).contains("\"userType\":\"" + type + "\""); + } + + if (resolved != null) { + assertThat(payload).contains("\"resolved\":" + resolved); + } + } } diff --git a/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/issues/StandaloneIssueChangeEventsDistributorTest.java b/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/issues/StandaloneIssueChangeEventsDistributorTest.java deleted file mode 100644 index b3b9e0b84d9..00000000000 --- a/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/issues/StandaloneIssueChangeEventsDistributorTest.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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.pushapi.issues; - -import org.junit.Test; -import org.sonar.core.util.issue.IssueChangeListener; -import org.sonar.core.util.issue.IssueChangedEvent; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -public class StandaloneIssueChangeEventsDistributorTest { - IssueChangeListener issueChangeListener = mock(IssueChangeListener.class); - IssueChangedEvent event = mock(IssueChangedEvent.class); - - public final StandaloneIssueChangeEventsDistributor underTest = new StandaloneIssueChangeEventsDistributor(); - - @Test - public void subscribe_and_push_publishesToListener() { - underTest.subscribe(issueChangeListener); - underTest.pushEvent(event); - verify(issueChangeListener).listen(event); - } -} diff --git a/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/qualityprofile/QualityProfileChangeEventServiceImplTest.java b/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/qualityprofile/QualityProfileChangeEventServiceImplTest.java index 3484c38d878..1cab5c35a69 100644 --- a/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/qualityprofile/QualityProfileChangeEventServiceImplTest.java +++ b/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/qualityprofile/QualityProfileChangeEventServiceImplTest.java @@ -19,17 +19,17 @@ */ package org.sonar.server.pushapi.qualityprofile; +import java.nio.charset.StandardCharsets; import java.util.Collection; import java.util.Collections; +import java.util.Deque; +import java.util.Set; import org.junit.Rule; import org.junit.Test; -import org.mockito.ArgumentCaptor; import org.sonar.api.rule.RuleKey; -import org.sonar.core.util.ParamChange; -import org.sonar.core.util.rule.RuleChange; -import org.sonar.core.util.rule.RuleSetChangedEvent; import org.sonar.db.DbTester; import org.sonar.db.project.ProjectDto; +import org.sonar.db.pushevent.PushEventDto; import org.sonar.db.qualityprofile.ActiveRuleDto; import org.sonar.db.qualityprofile.ActiveRuleParamDto; import org.sonar.db.qualityprofile.QProfileDto; @@ -37,14 +37,11 @@ import org.sonar.db.qualityprofile.QualityProfileTesting; import org.sonar.db.rule.RuleDto; import org.sonar.db.rule.RuleParamDto; import org.sonar.server.qualityprofile.ActiveRuleChange; +import org.sonarqube.ws.Common; import static java.util.List.of; import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.tuple; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection; import static org.sonar.db.rule.RuleTesting.newCustomRule; import static org.sonar.db.rule.RuleTesting.newTemplateRule; import static org.sonar.server.qualityprofile.ActiveRuleChange.Type.ACTIVATED; @@ -54,9 +51,7 @@ public class QualityProfileChangeEventServiceImplTest { @Rule public DbTester db = DbTester.create(); - RuleActivatorEventsDistributor eventsDistributor = mock(RuleActivatorEventsDistributor.class); - - public final QualityProfileChangeEventServiceImpl underTest = new QualityProfileChangeEventServiceImpl(db.getDbClient(), eventsDistributor); + public final QualityProfileChangeEventServiceImpl underTest = new QualityProfileChangeEventServiceImpl(db.getDbClient()); @Test public void distributeRuleChangeEvent() { @@ -84,33 +79,27 @@ public class QualityProfileChangeEventServiceImplTest { underTest.distributeRuleChangeEvent(profiles, of(activeRuleChange), "xoo"); - ArgumentCaptor<RuleSetChangedEvent> eventCaptor = ArgumentCaptor.forClass(RuleSetChangedEvent.class); - verify(eventsDistributor).pushEvent(eventCaptor.capture()); - - RuleSetChangedEvent ruleSetChangedEvent = eventCaptor.getValue(); - assertThat(ruleSetChangedEvent).isNotNull(); - assertThat(ruleSetChangedEvent).extracting(RuleSetChangedEvent::getEvent, - RuleSetChangedEvent::getLanguage, RuleSetChangedEvent::getProjects) - .containsExactly("RuleSetChanged", "xoo", new String[]{project.getKey()}); - - assertThat(ruleSetChangedEvent.getActivatedRules()) - .extracting(RuleChange::getKey, RuleChange::getLanguage, - RuleChange::getSeverity, RuleChange::getTemplateKey) - .containsExactly(tuple("repo:ruleKey", "xoo", null, "xoo:template-key")); + Deque<PushEventDto> events = db.getDbClient().pushEventDao() + .selectChunkByProjectUuids(db.getSession(), Set.of(project.getUuid()), 1l, null, 1); - assertThat(ruleSetChangedEvent.getActivatedRules()[0].getParams()).hasSize(1); - ParamChange actualParamChange = ruleSetChangedEvent.getActivatedRules()[0].getParams()[0]; - assertThat(actualParamChange) - .extracting(ParamChange::getKey, ParamChange::getValue) - .containsExactly("paramChangeKey", "paramChangeValue"); + assertThat(events).isNotEmpty().hasSize(1); + assertThat(events.getFirst()) + .extracting(PushEventDto::getName, PushEventDto::getLanguage) + .contains("RuleSetChanged", "xoo"); - assertThat(ruleSetChangedEvent.getDeactivatedRules()).isEmpty(); + String ruleSetChangedEvent = new String(events.getFirst().getPayload(), StandardCharsets.UTF_8); + assertThat(ruleSetChangedEvent) + .contains("\"activatedRules\":[{\"key\":\"repo:ruleKey\"," + + "\"language\":\"xoo\"," + + "\"templateKey\":\"xoo:template-key\"," + + "\"params\":[{\"key\":\"paramChangeKey\",\"value\":\"paramChangeValue\"}]}]," + + "\"deactivatedRules\":[]"); } @Test public void publishRuleActivationToSonarLintClients() { - ProjectDto projectDao = new ProjectDto(); + ProjectDto projectDao = new ProjectDto().setUuid("project-uuid"); QProfileDto activatedQualityProfile = QualityProfileTesting.newQualityProfileDto(); activatedQualityProfile.setLanguage("xoo"); db.qualityProfiles().insert(activatedQualityProfile); @@ -134,30 +123,21 @@ public class QualityProfileChangeEventServiceImplTest { underTest.publishRuleActivationToSonarLintClients(projectDao, activatedQualityProfile, deactivatedQualityProfile); - ArgumentCaptor<RuleSetChangedEvent> eventCaptor = ArgumentCaptor.forClass(RuleSetChangedEvent.class); - verify(eventsDistributor).pushEvent(eventCaptor.capture()); - - RuleSetChangedEvent ruleSetChangedEvent = eventCaptor.getValue(); - assertThat(ruleSetChangedEvent).isNotNull(); - assertThat(ruleSetChangedEvent).extracting(RuleSetChangedEvent::getEvent, - RuleSetChangedEvent::getLanguage, RuleSetChangedEvent::getProjects) - .containsExactly("RuleSetChanged", "xoo", new String[]{null}); - - // activated rule - assertThat(ruleSetChangedEvent.getActivatedRules()) - .extracting(RuleChange::getKey, RuleChange::getLanguage, - RuleChange::getSeverity, RuleChange::getTemplateKey) - .containsExactly(tuple("repo:ruleKey", "xoo", rule1.getSeverityString(), null)); - - assertThat(ruleSetChangedEvent.getActivatedRules()[0].getParams()).hasSize(1); - ParamChange actualParamChange = ruleSetChangedEvent.getActivatedRules()[0].getParams()[0]; - assertThat(actualParamChange) - .extracting(ParamChange::getKey, ParamChange::getValue) - .containsExactly(activeRuleParam1.getKey(), activeRuleParam1.getValue()); - - // deactivated rule - assertThat(ruleSetChangedEvent.getDeactivatedRules()) - .containsExactly("repo2:ruleKey2"); + Deque<PushEventDto> events = db.getDbClient().pushEventDao() + .selectChunkByProjectUuids(db.getSession(), Set.of(projectDao.getUuid()), 1l, null, 1); + + assertThat(events).isNotEmpty().hasSize(1); + assertThat(events.getFirst()) + .extracting(PushEventDto::getName, PushEventDto::getLanguage) + .contains("RuleSetChanged", "xoo"); + + String ruleSetChangedEvent = new String(events.getFirst().getPayload(), StandardCharsets.UTF_8); + + assertThat(ruleSetChangedEvent) + .contains("\"activatedRules\":[{\"key\":\"repo:ruleKey\"," + + "\"language\":\"xoo\",\"severity\":\"" + Common.Severity.forNumber(rule1.getSeverity()).name() + "\"," + + "\"params\":[{\"key\":\"" + activeRuleParam1.getKey() + "\",\"value\":\"" + activeRuleParam1.getValue() + "\"}]}]," + + "\"deactivatedRules\":[\"repo2:ruleKey2\"]"); } } diff --git a/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/qualityprofile/RuleSetChangeBroadcastUtilsTest.java b/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/qualityprofile/RuleSetChangeBroadcastUtilsTest.java deleted file mode 100644 index e36a5df6fc5..00000000000 --- a/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/qualityprofile/RuleSetChangeBroadcastUtilsTest.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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.pushapi.qualityprofile; - -import java.util.Set; -import java.util.function.Predicate; -import javax.servlet.AsyncContext; -import org.junit.Test; -import org.sonar.api.rule.Severity; -import org.sonar.core.util.ParamChange; -import org.sonar.core.util.rule.RuleChange; -import org.sonar.core.util.rule.RuleSetChangedEvent; -import org.sonar.server.pushapi.sonarlint.SonarLintClient; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; - -public class RuleSetChangeBroadcastUtilsTest { - - private final static String JAVA_KEY = "java"; - private final static String PROJECT_KEY_1 = "projectKey1"; - private final static String PROJECT_KEY_2 = "projectKey2"; - private final static String USER_UUID = "userUUID"; - private final static String[] DEACTIVATED_RULES = {"repo2:rule-key2"}; - - private final static Set<String> EXAMPLE_KEYS = Set.of(PROJECT_KEY_1, PROJECT_KEY_2); - - private final AsyncContext asyncContext = mock(AsyncContext.class); - - @Test - public void getsFilterForEvent() { - RuleChange javaRule = new RuleChange(); - javaRule.setLanguage(JAVA_KEY); - javaRule.setParams(new ParamChange[]{new ParamChange("param-key", "param-value")}); - javaRule.setTemplateKey("repo:template-key"); - javaRule.setSeverity(Severity.CRITICAL); - javaRule.setKey("repo:rule-key"); - - RuleChange[] activatedRules = {javaRule}; - RuleSetChangedEvent ruleSetChangedEvent = new RuleSetChangedEvent(EXAMPLE_KEYS.toArray(String[]::new), activatedRules, - DEACTIVATED_RULES, JAVA_KEY); - Predicate<SonarLintClient> predicate = RuleSetChangeBroadcastUtils.getFilterForEvent(ruleSetChangedEvent); - assertThat(predicate.test(new SonarLintClient(asyncContext, Set.of(PROJECT_KEY_1), Set.of(JAVA_KEY), USER_UUID))).isTrue(); - assertThat(predicate.test(new SonarLintClient(asyncContext, Set.of(PROJECT_KEY_2), Set.of(JAVA_KEY), USER_UUID))).isTrue(); - assertThat(predicate.test(new SonarLintClient(asyncContext, Set.of(PROJECT_KEY_1), Set.of(), USER_UUID))).isFalse(); - assertThat(predicate.test(new SonarLintClient(asyncContext, Set.of(), Set.of(JAVA_KEY), USER_UUID))).isFalse(); - assertThat(predicate.test(new SonarLintClient(asyncContext, Set.of("another-project"), Set.of(), USER_UUID))).isFalse(); - assertThat(predicate.test(new SonarLintClient(asyncContext, Set.of(""), Set.of("another-language"), USER_UUID))).isFalse(); - assertThat(predicate.test(new SonarLintClient(asyncContext, Set.of("another-project"), Set.of("another-language"), USER_UUID))).isFalse(); - } - - @Test - public void getsMessageForEvent() { - RuleSetChangedEvent ruleSetChangedEvent = new RuleSetChangedEvent(new String[]{PROJECT_KEY_1}, new RuleChange[0], - DEACTIVATED_RULES, JAVA_KEY); - - String message = RuleSetChangeBroadcastUtils.getMessage(ruleSetChangedEvent); - - assertThat(message).isEqualTo("event: RuleSetChanged\n" + - "data: {\"activatedRules\":[]," + - "\"projects\":[\"" + PROJECT_KEY_1 + "\"]," + - "\"deactivatedRules\":[\"repo2:rule-key2\"]}"); - } -} diff --git a/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/sonarlint/SonarLintClientsRegistryTest.java b/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/sonarlint/SonarLintClientsRegistryTest.java index 517c8a39b65..2f5867216dc 100644 --- a/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/sonarlint/SonarLintClientsRegistryTest.java +++ b/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/sonarlint/SonarLintClientsRegistryTest.java @@ -28,15 +28,7 @@ import javax.servlet.ServletResponse; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; -import org.sonar.api.rule.Severity; -import org.sonar.core.util.ParamChange; -import org.sonar.core.util.issue.Issue; -import org.sonar.core.util.issue.IssueChangedEvent; -import org.sonar.core.util.rule.RuleChange; -import org.sonar.core.util.rule.RuleSetChangedEvent; import org.sonar.server.exceptions.ForbiddenException; -import org.sonar.server.pushapi.issues.StandaloneIssueChangeEventsDistributor; -import org.sonar.server.pushapi.qualityprofile.StandaloneRuleActivatorEventsDistributor; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -52,6 +44,7 @@ import static org.mockito.Mockito.when; import static org.sonar.test.EventAssert.assertThatEvent; public class SonarLintClientsRegistryTest { + private static final String EVENT_NAME = "RuleSetChanged"; private final AsyncContext defaultAsyncContext = mock(AsyncContext.class); @@ -62,14 +55,12 @@ public class SonarLintClientsRegistryTest { private final ServletOutputStream outputStream = mock(ServletOutputStream.class); private final SonarLintClientPermissionsValidator permissionsValidator = mock(SonarLintClientPermissionsValidator.class); - private final StandaloneRuleActivatorEventsDistributor ruleEventsDistributor = mock(StandaloneRuleActivatorEventsDistributor.class); - private final StandaloneIssueChangeEventsDistributor issueChangeEventsDistributor = mock(StandaloneIssueChangeEventsDistributor.class); private SonarLintClientsRegistry underTest; @Before public void before() { - underTest = new SonarLintClientsRegistry(issueChangeEventsDistributor, ruleEventsDistributor, permissionsValidator); + underTest = new SonarLintClientsRegistry(permissionsValidator); } @Test @@ -100,7 +91,7 @@ public class SonarLintClientsRegistryTest { } @Test - public void listen_givenOneClientInterestedInJavaEvents_sendOneJavaEvent() throws IOException { + public void listen_givenOneClientInterestedInJavaEvents_sendAllJavaEvents() throws IOException { Set<String> javaLanguageKey = Set.of("java"); when(defaultAsyncContext.getResponse()).thenReturn(response); when(response.getOutputStream()).thenReturn(outputStream); @@ -108,19 +99,36 @@ public class SonarLintClientsRegistryTest { underTest.registerClient(sonarLintClient); - RuleChange javaRule = createRuleChange(); + SonarLintPushEvent event1 = new SonarLintPushEvent(EVENT_NAME, "data".getBytes(StandardCharsets.UTF_8), "project1", "java"); + SonarLintPushEvent event2 = new SonarLintPushEvent(EVENT_NAME, "data".getBytes(StandardCharsets.UTF_8), "project2", "java"); + SonarLintPushEvent event3 = new SonarLintPushEvent(EVENT_NAME, "data".getBytes(StandardCharsets.UTF_8), "project3", "java"); - RuleChange[] activatedRules = {javaRule}; - String[] deactivatedRules = {"repo2:rule-key2"}; - RuleSetChangedEvent ruleSetChangedEvent = new RuleSetChangedEvent(exampleKeys.toArray(String[]::new), activatedRules, deactivatedRules, "java"); - underTest.listen(ruleSetChangedEvent); + underTest.broadcastMessage(event1); ArgumentCaptor<byte[]> captor = ArgumentCaptor.forClass(byte[].class); verify(outputStream).write(captor.capture()); String message = new String(captor.getValue()); assertThatEvent(message) - .hasType("RuleSetChanged") - .hasJsonData(getClass().getResource("rule-change-event-data.json")); + .hasType(EVENT_NAME); + + clearInvocations(outputStream); + + underTest.broadcastMessage(event2); + + verify(outputStream).write(captor.capture()); + message = new String(captor.getValue()); + assertThatEvent(message) + .hasType(EVENT_NAME); + + clearInvocations(outputStream); + + + underTest.broadcastMessage(event3); + + verify(outputStream).write(captor.capture()); + message = new String(captor.getValue()); + assertThatEvent(message) + .hasType(EVENT_NAME); } @Test @@ -132,10 +140,9 @@ public class SonarLintClientsRegistryTest { underTest.registerClient(sonarLintClient); - RuleChange[] activatedRules = {}; - String[] deactivatedRules = {"repo:rule-key"}; - RuleSetChangedEvent ruleSetChangedEvent = new RuleSetChangedEvent(exampleKeys.toArray(String[]::new), activatedRules, deactivatedRules, "java"); - underTest.listen(ruleSetChangedEvent); + SonarLintPushEvent event = new SonarLintPushEvent(EVENT_NAME, "data".getBytes(StandardCharsets.UTF_8), "project1", "java"); + + underTest.broadcastMessage(event); verifyNoInteractions(outputStream); } @@ -144,74 +151,56 @@ public class SonarLintClientsRegistryTest { public void listen_givenOneClientInterestedInProjA_DontCheckPermissionsForProjB() throws IOException { when(defaultAsyncContext.getResponse()).thenReturn(response); when(response.getOutputStream()).thenReturn(outputStream); - Set<String> clientProjectKeys = Set.of("projA"); - Set<String> eventProjectKeys = Set.of("projA", "projB"); - SonarLintClient sonarLintClient = new SonarLintClient(defaultAsyncContext, clientProjectKeys, Set.of("java"), USER_UUID); + SonarLintClient sonarLintClient = new SonarLintClient(defaultAsyncContext, Set.of("projA"), Set.of("java"), USER_UUID); underTest.registerClient(sonarLintClient); - RuleChange[] activatedRules = {}; - String[] deactivatedRules = {"repo:rule-key"}; - RuleSetChangedEvent ruleSetChangedEvent = new RuleSetChangedEvent(eventProjectKeys.toArray(String[]::new), activatedRules, deactivatedRules, "java"); - underTest.listen(ruleSetChangedEvent); + SonarLintPushEvent event1 = new SonarLintPushEvent(EVENT_NAME, "data".getBytes(StandardCharsets.UTF_8), "projA", "java"); + SonarLintPushEvent event2 = new SonarLintPushEvent(EVENT_NAME, "data".getBytes(StandardCharsets.UTF_8), "projB", "java"); ArgumentCaptor<Set<String>> argument = ArgumentCaptor.forClass(Set.class); - verify(permissionsValidator).validateUserCanReceivePushEventForProjects(anyString(), argument.capture()); - assertThat(argument.getValue()).isEqualTo(clientProjectKeys); - } - - @Test - public void listen_givenUserNotPermittedToReceiveRuleSetChangedEvent_closeConnection() { - RuleChange[] activatedRules = {}; - String[] deactivatedRules = {"repo:rule-key"}; - RuleSetChangedEvent ruleSetChangedEvent = new RuleSetChangedEvent(exampleKeys.toArray(String[]::new), activatedRules, deactivatedRules, "java"); - SonarLintClient sonarLintClient = createSampleSLClient(); - underTest.registerClient(sonarLintClient); - doThrow(new ForbiddenException("Access forbidden")).when(permissionsValidator).validateUserCanReceivePushEventForProjects(anyString(), anySet()); + underTest.broadcastMessage(event1); + underTest.broadcastMessage(event2); - underTest.listen(ruleSetChangedEvent); - - verify(sonarLintClient).close(); + verify(permissionsValidator, times(1)).validateUserCanReceivePushEventForProjects(anyString(), argument.capture()); + assertThat(argument.getValue()).hasSize(1).contains("projA"); } @Test - public void listen_givenUserNotPermittedToReceiveIssueChangeEvent_closeConnection() { - Issue[] issues = new Issue[] {new Issue("issue-1", "branch-1")}; - IssueChangedEvent issueChangedEvent = new IssueChangedEvent("project1", issues, true, "BLOCKER", "BUG"); - + public void listen_givenUserNotPermittedToReceiveEvent_closeConnection() { SonarLintClient sonarLintClient = createSampleSLClient(); underTest.registerClient(sonarLintClient); doThrow(new ForbiddenException("Access forbidden")).when(permissionsValidator).validateUserCanReceivePushEventForProjects(anyString(), anySet()); - underTest.listen(issueChangedEvent); + SonarLintPushEvent event = new SonarLintPushEvent(EVENT_NAME, "data".getBytes(StandardCharsets.UTF_8), "project1", "java"); + + underTest.broadcastMessage(event); verify(sonarLintClient).close(); } @Test public void listen_givenUnregisteredClient_closeConnection() throws IOException { - RuleChange[] activatedRules = {}; - String[] deactivatedRules = {"repo:rule-key"}; - RuleSetChangedEvent ruleSetChangedEvent = new RuleSetChangedEvent(exampleKeys.toArray(String[]::new), activatedRules, deactivatedRules, "java"); - SonarLintClient sonarLintClient = createSampleSLClient(); underTest.registerClient(sonarLintClient); doThrow(new IOException("Broken pipe")).when(sonarLintClient).writeAndFlush(anyString()); - underTest.listen(ruleSetChangedEvent); + SonarLintPushEvent event = new SonarLintPushEvent(EVENT_NAME, "data".getBytes(StandardCharsets.UTF_8), "project1", "java"); + + underTest.broadcastMessage(event); underTest.registerClient(sonarLintClient); doThrow(new IllegalStateException("Things went wrong")).when(sonarLintClient).writeAndFlush(anyString()); - underTest.listen(ruleSetChangedEvent); + underTest.broadcastMessage(event); verify(sonarLintClient, times(2)).close(); } @Test public void broadcast_push_event_to_clients() throws IOException { - SonarLintPushEvent event = new SonarLintPushEvent("event", "data".getBytes(StandardCharsets.UTF_8), "project2"); + SonarLintPushEvent event = new SonarLintPushEvent("event", "data".getBytes(StandardCharsets.UTF_8), "project2", null); SonarLintClient sonarLintClient = createSampleSLClient(); underTest.registerClient(sonarLintClient); @@ -224,7 +213,7 @@ public class SonarLintClientsRegistryTest { @Test public void broadcast_skips_push_if_event_project_does_not_match_with_client() throws IOException { - SonarLintPushEvent event = new SonarLintPushEvent("event", "data".getBytes(StandardCharsets.UTF_8), "project4"); + SonarLintPushEvent event = new SonarLintPushEvent("event", "data".getBytes(StandardCharsets.UTF_8), "project4", null); SonarLintClient sonarLintClient = createSampleSLClient(); underTest.registerClient(sonarLintClient); @@ -238,7 +227,7 @@ public class SonarLintClientsRegistryTest { @Test public void broadcast_givenUserNotPermittedToReceiveSonarLintPushEvent_closeConnection() { - SonarLintPushEvent event = new SonarLintPushEvent("event", "data".getBytes(StandardCharsets.UTF_8), "project1"); + SonarLintPushEvent event = new SonarLintPushEvent("event", "data".getBytes(StandardCharsets.UTF_8), "project1", null); SonarLintClient sonarLintClient = createSampleSLClient(); underTest.registerClient(sonarLintClient); @@ -251,7 +240,7 @@ public class SonarLintClientsRegistryTest { @Test public void broadcast_givenUnregisteredClient_closeConnection() throws IOException { - SonarLintPushEvent event = new SonarLintPushEvent("event", "data".getBytes(StandardCharsets.UTF_8), "project1"); + SonarLintPushEvent event = new SonarLintPushEvent("event", "data".getBytes(StandardCharsets.UTF_8), "project1", null); SonarLintClient sonarLintClient = createSampleSLClient(); underTest.registerClient(sonarLintClient); @@ -268,42 +257,11 @@ public class SonarLintClientsRegistryTest { } @Test - public void registerClient_whenCalledFirstTime_registerAlsoToListenToEvents() { - underTest.registerClient(createSampleSLClient()); - - verify(ruleEventsDistributor).subscribe(underTest); - verify(issueChangeEventsDistributor).subscribe(underTest); - } - - @Test - public void registerClient_whenCalledSecondTime_doNotRegisterToEvents() { - underTest.registerClient(createSampleSLClient()); - clearInvocations(ruleEventsDistributor); - clearInvocations(issueChangeEventsDistributor); - - underTest.registerClient(createSampleSLClient()); - verifyNoInteractions(ruleEventsDistributor); - verifyNoInteractions(issueChangeEventsDistributor); - } - - @Test - public void registerClient_whenExceptionAndCalledSecondTime_registerToRuleEvents() { - doThrow(new RuntimeException()).when(ruleEventsDistributor).subscribe(any()); - underTest.registerClient(createSampleSLClient()); - clearInvocations(ruleEventsDistributor); - - underTest.registerClient(createSampleSLClient()); - verify(ruleEventsDistributor).subscribe(underTest); - } - - @Test - public void registerClient_whenExceptionAndCalledSecondTime_registerToIssueChangeEvents() { - doThrow(new RuntimeException()).when(issueChangeEventsDistributor).subscribe(any()); - underTest.registerClient(createSampleSLClient()); - clearInvocations(issueChangeEventsDistributor); + public void registerClient_whenCalledFirstTime_addsAsyncListenerToClient() { + SonarLintClient client = mock(SonarLintClient.class); + underTest.registerClient(client); - underTest.registerClient(createSampleSLClient()); - verify(issueChangeEventsDistributor).subscribe(underTest); + verify(client).addListener(any()); } private SonarLintClient createSampleSLClient() { @@ -313,14 +271,4 @@ public class SonarLintClientsRegistryTest { when(mock.getUserUuid()).thenReturn("userUuid"); return mock; } - - private RuleChange createRuleChange() { - RuleChange javaRule = new RuleChange(); - javaRule.setLanguage("java"); - javaRule.setParams(new ParamChange[] {new ParamChange("param-key", "param-value")}); - javaRule.setTemplateKey("repo:template-key"); - javaRule.setSeverity(Severity.CRITICAL); - javaRule.setKey("repo:rule-key"); - return javaRule; - } } diff --git a/server/sonar-webserver-pushapi/src/test/resources/org/sonar/server/pushapi/sonarlint/rule-change-event-data.json b/server/sonar-webserver-pushapi/src/test/resources/org/sonar/server/pushapi/sonarlint/rule-change-event-data.json deleted file mode 100644 index 81d1dc7920b..00000000000 --- a/server/sonar-webserver-pushapi/src/test/resources/org/sonar/server/pushapi/sonarlint/rule-change-event-data.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "projects": [ - "project2", - "project1", - "project3" - ], - "activatedRules": [ - { - "key": "repo:rule-key", - "templateKey": "repo:template-key", - "severity": "CRITICAL", - "language": "java", - "params": [ - { - "value": "param-value", - "key": "param-key" - } - ] - } - ], - "deactivatedRules": [ - "repo2:rule-key2" - ] -} diff --git a/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java b/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java index d1f9dbfeeb0..cb000db07cb 100644 --- a/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java +++ b/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java @@ -185,12 +185,8 @@ import org.sonar.server.projectlink.ws.ProjectLinksModule; import org.sonar.server.projecttag.ws.ProjectTagsWsModule; import org.sonar.server.property.InternalPropertiesImpl; import org.sonar.server.pushapi.ServerPushModule; -import org.sonar.server.pushapi.issues.DistributedIssueChangeEventsDistributor; import org.sonar.server.pushapi.issues.IssueChangeEventServiceImpl; -import org.sonar.server.pushapi.issues.StandaloneIssueChangeEventsDistributor; -import org.sonar.server.pushapi.qualityprofile.DistributedRuleActivatorEventsDistributor; import org.sonar.server.pushapi.qualityprofile.QualityProfileChangeEventServiceImpl; -import org.sonar.server.pushapi.qualityprofile.StandaloneRuleActivatorEventsDistributor; import org.sonar.server.qualitygate.ProjectsInWarningModule; import org.sonar.server.qualitygate.QualityGateModule; import org.sonar.server.qualitygate.notification.QGChangeNotificationHandler; @@ -285,12 +281,6 @@ public class PlatformLevel4 extends PlatformLevel { addIfCluster(new NodeHealthModule()); - addIfCluster(DistributedRuleActivatorEventsDistributor.class); - addIfStandalone(StandaloneRuleActivatorEventsDistributor.class); - - addIfCluster(DistributedIssueChangeEventsDistributor.class); - addIfStandalone(StandaloneIssueChangeEventsDistributor.class); - add( RuleDescriptionFormatter.class, ClusterVerification.class, diff --git a/sonar-core/src/main/java/org/sonar/core/util/issue/IssueChangeListener.java b/sonar-core/src/main/java/org/sonar/core/util/issue/IssueChangeListener.java deleted file mode 100644 index 6c1bcfe4b19..00000000000 --- a/sonar-core/src/main/java/org/sonar/core/util/issue/IssueChangeListener.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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.core.util.issue; - -public interface IssueChangeListener { - void listen(IssueChangedEvent event); -} diff --git a/sonar-core/src/main/java/org/sonar/core/util/issue/IssueChangedEvent.java b/sonar-core/src/main/java/org/sonar/core/util/issue/IssueChangedEvent.java index 0d7c8afce6e..687e585bbfa 100644 --- a/sonar-core/src/main/java/org/sonar/core/util/issue/IssueChangedEvent.java +++ b/sonar-core/src/main/java/org/sonar/core/util/issue/IssueChangedEvent.java @@ -24,7 +24,7 @@ import javax.annotation.CheckForNull; import javax.annotation.Nullable; public class IssueChangedEvent implements Serializable { - private static final String EVENT = "IssueChangedEvent"; + private static final String EVENT = "IssueChanged"; private final String projectKey; private final Issue[] issues; diff --git a/sonar-core/src/main/java/org/sonar/core/util/rule/RuleActivationListener.java b/sonar-core/src/main/java/org/sonar/core/util/rule/RuleActivationListener.java deleted file mode 100644 index 56f3df113fe..00000000000 --- a/sonar-core/src/main/java/org/sonar/core/util/rule/RuleActivationListener.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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.core.util.rule; - -public interface RuleActivationListener { - - void listen(RuleSetChangedEvent event); -} diff --git a/sonar-core/src/main/java/org/sonar/core/util/rule/RuleSetChangedEvent.java b/sonar-core/src/main/java/org/sonar/core/util/rule/RuleSetChangedEvent.java index ad16db983a1..3fe89180543 100644 --- a/sonar-core/src/main/java/org/sonar/core/util/rule/RuleSetChangedEvent.java +++ b/sonar-core/src/main/java/org/sonar/core/util/rule/RuleSetChangedEvent.java @@ -22,36 +22,23 @@ package org.sonar.core.util.rule; import java.io.Serializable; public class RuleSetChangedEvent implements Serializable { - - private static final String EVENT = "RuleSetChanged"; - private final String[] projects; - private final String language; private final RuleChange[] activatedRules; private final String[] deactivatedRules; - public RuleSetChangedEvent(String[] projects, RuleChange[] activatedRules, String[] deactivatedRules, String language) { - this.projects = projects; + public RuleSetChangedEvent(String projectKey, RuleChange[] activatedRules, String[] deactivatedRules) { + this.projects = new String[]{projectKey}; this.activatedRules = activatedRules; this.deactivatedRules = deactivatedRules; if (activatedRules.length == 0 && deactivatedRules.length == 0) { throw new IllegalArgumentException("Can't create RuleSetChangedEvent without any rules that have changed"); } - this.language = language; - } - - public String getEvent() { - return EVENT; } public String[] getProjects() { return projects; } - public String getLanguage() { - return language; - } - public RuleChange[] getActivatedRules() { return activatedRules; } diff --git a/sonar-core/src/test/java/org/sonar/core/util/issue/IssueChangedEventTest.java b/sonar-core/src/test/java/org/sonar/core/util/issue/IssueChangedEventTest.java index 0693c406c65..b836b89d6d7 100644 --- a/sonar-core/src/test/java/org/sonar/core/util/issue/IssueChangedEventTest.java +++ b/sonar-core/src/test/java/org/sonar/core/util/issue/IssueChangedEventTest.java @@ -34,7 +34,7 @@ public class IssueChangedEventTest { Issue[] issues = new Issue[]{new Issue(ISSUE_KEY, BRANCH_NAME)}; IssueChangedEvent event = new IssueChangedEvent(PROJECT_KEY, issues, null, null, null); - assertThat(event.getEvent()).isEqualTo("IssueChangedEvent"); + assertThat(event.getEvent()).isEqualTo("IssueChanged"); assertThat(event.getProjectKey()).isEqualTo(PROJECT_KEY); assertThat(event.getResolved()).isNull(); assertThat(event.getUserSeverity()).isNull(); @@ -47,7 +47,7 @@ public class IssueChangedEventTest { Issue[] issues = new Issue[]{new Issue(ISSUE_KEY, BRANCH_NAME)}; IssueChangedEvent event = new IssueChangedEvent(PROJECT_KEY, issues, true, "BLOCKER", "BUG"); - assertThat(event.getEvent()).isEqualTo("IssueChangedEvent"); + assertThat(event.getEvent()).isEqualTo("IssueChanged"); assertThat(event.getProjectKey()).isEqualTo(PROJECT_KEY); assertThat(event.getResolved()).isTrue(); assertThat(event.getUserSeverity()).isEqualTo("BLOCKER"); diff --git a/sonar-core/src/test/java/org/sonar/core/util/rule/RuleSetChangedEventTest.java b/sonar-core/src/test/java/org/sonar/core/util/rule/RuleSetChangedEventTest.java index 5c1b52e9c14..362bb9fab82 100644 --- a/sonar-core/src/test/java/org/sonar/core/util/rule/RuleSetChangedEventTest.java +++ b/sonar-core/src/test/java/org/sonar/core/util/rule/RuleSetChangedEventTest.java @@ -27,36 +27,23 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; public class RuleSetChangedEventTest { @Test - public void getLanguage_givenNoDeactivatedRules_languageIsCorrectlyIdentified() { - String[] projects = {"sonarqube"}; - RuleChange[] activatedRules = {createRuleChange("java")}; - String[] deactivatedRules = {}; - RuleSetChangedEvent event = new RuleSetChangedEvent(projects, activatedRules, deactivatedRules, "java"); - - String language = event.getLanguage(); - - assertThat(language).isEqualTo("java"); - } - - @Test - public void getLanguage_givenNoActivatedRules_languageIsCorrectlyIdentified() { - String[] projects = {"sonarqube"}; - RuleChange[] activatedRules = {}; + public void getDeactivatedAndActivatedRules() { + String project = "sonarqube"; + RuleChange[] activatedRules = { new RuleChange()}; String[] deactivatedRules = {"ruleKey"}; - RuleSetChangedEvent event = new RuleSetChangedEvent(projects, activatedRules, deactivatedRules, "java"); - - String language = event.getLanguage(); + RuleSetChangedEvent event = new RuleSetChangedEvent(project, activatedRules, deactivatedRules); - assertThat(language).isEqualTo("java"); + assertThat(event.getActivatedRules()).isEqualTo(activatedRules); + assertThat(event.getDeactivatedRules()).isEqualTo(deactivatedRules); } @Test public void getLanguage_givenBothArraysEmpty_throwException() { - String[] projects = {"sonarqube"}; + String project = "sonarqube"; RuleChange[] activatedRules = {}; String[] deactivatedRules = {}; - assertThatThrownBy(() -> new RuleSetChangedEvent(projects, activatedRules, deactivatedRules, "java")) + assertThatThrownBy(() -> new RuleSetChangedEvent(project, activatedRules, deactivatedRules)) .isInstanceOf(IllegalArgumentException.class); } |