@@ -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; | |||
} |
@@ -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} | |||
) |
@@ -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); |
@@ -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()); | |||
} | |||
} | |||
} |
@@ -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) | |||
; | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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"); |
@@ -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; | |||
@@ -198,26 +194,6 @@ public class AppNodesClusterHostsConsistencyTest { | |||
callback.onComplete((Map<Member, T>) hostsPerMember); | |||
} | |||
@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() { | |||
@@ -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(); | |||
} |
@@ -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 { | |||
@@ -131,30 +125,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 { |
@@ -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) |
@@ -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); | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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); | |||
} |
@@ -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)); | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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); | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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)); | |||
} | |||
} |
@@ -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); | |||
} | |||
@@ -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) { |
@@ -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; | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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}"); | |||
} | |||
} |
@@ -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); | |||
} | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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\"]"); | |||
} | |||
} |
@@ -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\"]}"); | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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" | |||
] | |||
} |
@@ -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, |
@@ -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); | |||
} |
@@ -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; |
@@ -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); | |||
} |
@@ -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; | |||
} |
@@ -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"); |
@@ -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); | |||
} | |||