diff options
author | Pierre <pierre.guillot@sonarsource.com> | 2022-02-03 12:10:18 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2022-02-18 15:48:04 +0000 |
commit | 0c8ff1bac53c2dfa89e7be014b64b411f24434c0 (patch) | |
tree | bcad0a2d4daafe106d82f7765be71fec146eb879 | |
parent | 24cd8d71c3b6df3f33f1ff0232c83bbb0a9fc060 (diff) | |
download | sonarqube-0c8ff1bac53c2dfa89e7be014b64b411f24434c0.tar.gz sonarqube-0c8ff1bac53c2dfa89e7be014b64b411f24434c0.zip |
SONAR-15919 add ruleset changes
42 files changed, 1027 insertions, 83 deletions
diff --git a/server/sonar-main/src/test/java/org/sonar/application/cluster/AppNodesClusterHostsConsistencyTest.java b/server/sonar-main/src/test/java/org/sonar/application/cluster/AppNodesClusterHostsConsistencyTest.java index 40875de72c1..cf0c8a1cd9e 100644 --- a/server/sonar-main/src/test/java/org/sonar/application/cluster/AppNodesClusterHostsConsistencyTest.java +++ b/server/sonar-main/src/test/java/org/sonar/application/cluster/AppNodesClusterHostsConsistencyTest.java @@ -40,6 +40,8 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; import org.sonar.application.config.TestAppSettings; +import org.sonar.core.util.RuleActivationListener; +import org.sonar.core.util.RuleSetChangeEvent; import org.sonar.process.cluster.hz.DistributedAnswer; import org.sonar.process.cluster.hz.DistributedCall; import org.sonar.process.cluster.hz.DistributedCallback; @@ -195,6 +197,16 @@ public class AppNodesClusterHostsConsistencyTest { } @Override + public void subscribeRuleActivationTopic(RuleActivationListener listener) { + + } + + @Override + public void publishEvent(RuleSetChangeEvent event) { + + } + + @Override public void close() { } diff --git a/server/sonar-process/src/main/java/org/sonar/process/cluster/hz/HazelcastMember.java b/server/sonar-process/src/main/java/org/sonar/process/cluster/hz/HazelcastMember.java index 65f43bae707..320a867eb29 100644 --- a/server/sonar-process/src/main/java/org/sonar/process/cluster/hz/HazelcastMember.java +++ b/server/sonar-process/src/main/java/org/sonar/process/cluster/hz/HazelcastMember.java @@ -26,6 +26,8 @@ import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.concurrent.locks.Lock; +import org.sonar.core.util.RuleActivationListener; +import org.sonar.core.util.RuleSetChangeEvent; import org.sonar.process.ProcessId; public interface HazelcastMember extends AutoCloseable { @@ -106,6 +108,10 @@ public interface HazelcastMember extends AutoCloseable { */ <T> void callAsync(DistributedCall<T> callable, MemberSelector memberSelector, DistributedCallback<T> callback); + void subscribeRuleActivationTopic(RuleActivationListener listener); + + void publishEvent(RuleSetChangeEvent event); + @Override void close(); } diff --git a/server/sonar-process/src/main/java/org/sonar/process/cluster/hz/HazelcastMemberImpl.java b/server/sonar-process/src/main/java/org/sonar/process/cluster/hz/HazelcastMemberImpl.java index 685910361af..9b223cb0c61 100644 --- a/server/sonar-process/src/main/java/org/sonar/process/cluster/hz/HazelcastMemberImpl.java +++ b/server/sonar-process/src/main/java/org/sonar/process/cluster/hz/HazelcastMemberImpl.java @@ -27,6 +27,8 @@ 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; @@ -37,6 +39,8 @@ 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.RuleActivationListener; +import org.sonar.core.util.RuleSetChangeEvent; class HazelcastMemberImpl implements HazelcastMember { @@ -126,6 +130,18 @@ class HazelcastMemberImpl implements HazelcastMember { } @Override + public void subscribeRuleActivationTopic(RuleActivationListener listener) { + ITopic<RuleSetChangeEvent> topic = hzInstance.getTopic("ruleActivated"); + MessageListener<RuleSetChangeEvent> hzListener = message -> listener.listen(message.getMessageObject()); + topic.addMessageListener(hzListener); + } + + @Override + public void publishEvent(RuleSetChangeEvent event) { + hzInstance.getTopic("ruleActivated").publish(event); + } + + @Override public void close() { try { hzInstance.shutdown(); diff --git a/server/sonar-webserver-auth/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileUpdateImpl.java b/server/sonar-webserver-auth/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileUpdateImpl.java index d6e019d79c4..9f0f059f6b5 100644 --- a/server/sonar-webserver-auth/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileUpdateImpl.java +++ b/server/sonar-webserver-auth/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileUpdateImpl.java @@ -40,11 +40,14 @@ public class BuiltInQProfileUpdateImpl implements BuiltInQProfileUpdate { private final DbClient dbClient; private final RuleActivator ruleActivator; private final ActiveRuleIndexer activeRuleIndexer; + private final QualityProfileChangeEventService qualityProfileChangeEventService; - public BuiltInQProfileUpdateImpl(DbClient dbClient, RuleActivator ruleActivator, ActiveRuleIndexer activeRuleIndexer) { + public BuiltInQProfileUpdateImpl(DbClient dbClient, RuleActivator ruleActivator, ActiveRuleIndexer activeRuleIndexer, + QualityProfileChangeEventService qualityProfileChangeEventService) { this.dbClient = dbClient; this.ruleActivator = ruleActivator; this.activeRuleIndexer = activeRuleIndexer; + this.qualityProfileChangeEventService = qualityProfileChangeEventService; } public List<ActiveRuleChange> update(DbSession dbSession, BuiltInQProfile builtInDefinition, RulesProfileDto initialRuleProfile) { @@ -56,8 +59,8 @@ public class BuiltInQProfileUpdateImpl implements BuiltInQProfileUpdate { // all rules, including those which are removed from built-in profile Set<String> ruleUuids = Stream.concat( - deactivatedRuleUuids.stream(), - builtInDefinition.getActiveRules().stream().map(BuiltInQProfile.ActiveRule::getRuleUuid)) + deactivatedRuleUuids.stream(), + builtInDefinition.getActiveRules().stream().map(BuiltInQProfile.ActiveRule::getRuleUuid)) .collect(toSet()); Collection<RuleActivation> activations = new ArrayList<>(); @@ -69,13 +72,16 @@ public class BuiltInQProfileUpdateImpl implements BuiltInQProfileUpdate { RuleActivationContext context = ruleActivator.createContextForBuiltInProfile(dbSession, initialRuleProfile, ruleUuids); List<ActiveRuleChange> changes = new ArrayList<>(); - for (RuleActivation activation : activations) { - changes.addAll(ruleActivator.activate(dbSession, activation, context)); - } + + changes.addAll(ruleActivator.activate(dbSession, activations, context)); // these rules are no longer part of the built-in profile deactivatedRuleUuids.forEach(ruleUuid -> changes.addAll(ruleActivator.deactivate(dbSession, context, ruleUuid, false))); + if (!changes.isEmpty()) { + qualityProfileChangeEventService.distributeRuleChangeEvent(context.getProfiles(), changes, initialRuleProfile.getLanguage()); + } + activeRuleIndexer.commitAndIndex(dbSession, changes); return changes; } diff --git a/server/sonar-webserver-auth/src/main/java/org/sonar/server/qualityprofile/DistributedRuleActivatorEventsDistributor.java b/server/sonar-webserver-auth/src/main/java/org/sonar/server/qualityprofile/DistributedRuleActivatorEventsDistributor.java new file mode 100644 index 00000000000..a6a3b847913 --- /dev/null +++ b/server/sonar-webserver-auth/src/main/java/org/sonar/server/qualityprofile/DistributedRuleActivatorEventsDistributor.java @@ -0,0 +1,45 @@ +/* + * 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.qualityprofile; + +import org.sonar.api.server.ServerSide; +import org.sonar.core.util.RuleActivationListener; +import org.sonar.core.util.RuleSetChangeEvent; +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(RuleSetChangeEvent event) { + hazelcastMember.publishEvent(event); + } +} diff --git a/server/sonar-webserver-auth/src/main/java/org/sonar/server/qualityprofile/QualityProfileChangeEventService.java b/server/sonar-webserver-auth/src/main/java/org/sonar/server/qualityprofile/QualityProfileChangeEventService.java new file mode 100644 index 00000000000..008afff27f5 --- /dev/null +++ b/server/sonar-webserver-auth/src/main/java/org/sonar/server/qualityprofile/QualityProfileChangeEventService.java @@ -0,0 +1,33 @@ +/* + * 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.qualityprofile; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import org.sonar.db.project.ProjectDto; +import org.sonar.db.qualityprofile.QProfileDto; + +public interface QualityProfileChangeEventService { + + void publishRuleActivationToSonarLintClients(ProjectDto project, Optional<QProfileDto> activatedProfile, Optional<QProfileDto> deactivatedProfile); + + void distributeRuleChangeEvent(Collection<QProfileDto> profiles, List<ActiveRuleChange> activeRuleChanges, String language); +} diff --git a/server/sonar-webserver-auth/src/main/java/org/sonar/server/qualityprofile/QualityProfileChangeEventServiceImpl.java b/server/sonar-webserver-auth/src/main/java/org/sonar/server/qualityprofile/QualityProfileChangeEventServiceImpl.java new file mode 100644 index 00000000000..22c96e6ff4a --- /dev/null +++ b/server/sonar-webserver-auth/src/main/java/org/sonar/server/qualityprofile/QualityProfileChangeEventServiceImpl.java @@ -0,0 +1,206 @@ +/* + * 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.qualityprofile; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import org.jetbrains.annotations.NotNull; +import org.sonar.api.server.ServerSide; +import org.sonar.core.util.ParamChange; +import org.sonar.core.util.RuleChange; +import org.sonar.core.util.RuleSetChangeEvent; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.project.ProjectDto; +import org.sonar.db.qualityprofile.ActiveRuleParamDto; +import org.sonar.db.qualityprofile.ProjectQprofileAssociationDto; +import org.sonar.db.qualityprofile.QProfileDto; +import org.sonar.db.rule.RuleDto; +import org.sonar.server.rule.index.RuleIndex; +import org.sonar.server.rule.index.RuleQuery; + +@ServerSide +public class QualityProfileChangeEventServiceImpl implements QualityProfileChangeEventService { + + private final DbClient dbClient; + private final RuleIndex ruleIndex; + private final RuleActivatorEventsDistributor eventsDistributor; + + public QualityProfileChangeEventServiceImpl(DbClient dbClient, RuleIndex ruleIndex, RuleActivatorEventsDistributor eventsDistributor) { + this.dbClient = dbClient; + this.ruleIndex = ruleIndex; + this.eventsDistributor = eventsDistributor; + } + + @Override + public void publishRuleActivationToSonarLintClients(ProjectDto project, Optional<QProfileDto> activatedProfile, Optional<QProfileDto> deactivatedProfile) { + List<RuleChange> activatedRules = new ArrayList<>(); + List<RuleChange> deactivatedRules = new ArrayList<>(); + + try (DbSession dbSession = dbClient.openSession(false)) { + + if (activatedProfile.isPresent()) { + RuleQuery query = new RuleQuery().setQProfile(activatedProfile.get()).setActivation(true).setIncludeExternal(true); + // .setLanguages() ? + Iterator<String> searchIdResult = ruleIndex.searchAll(query); + List<String> uuids = new ArrayList<>(); + while (searchIdResult.hasNext()) { + uuids.add(searchIdResult.next()); + } + + List<RuleDto> ruleDtos = dbClient.ruleDao().selectByUuids(dbSession, uuids); + Map<String, List<ActiveRuleParamDto>> paramsByRuleUuid = dbClient.activeRuleDao().selectParamsByActiveRuleUuids(dbSession, uuids) + .stream().collect(Collectors.groupingBy(ActiveRuleParamDto::getActiveRuleUuid)); + + for (RuleDto ruleDto : ruleDtos) { + RuleChange ruleChange = toRuleChange(ruleDto, paramsByRuleUuid); + activatedRules.add(ruleChange); + } + } + + if (deactivatedProfile.isPresent()) { + RuleQuery query = new RuleQuery().setQProfile(deactivatedProfile.get()).setActivation(true).setIncludeExternal(true); + // .setLanguages() ? + Iterator<String> searchIdResult = ruleIndex.searchAll(query); + List<String> uuids = new ArrayList<>(); + while (searchIdResult.hasNext()) { + uuids.add(searchIdResult.next()); + } + + List<RuleDto> ruleDtos = dbClient.ruleDao().selectByUuids(dbSession, uuids); + Map<String, List<ActiveRuleParamDto>> paramsByRuleUuid = dbClient.activeRuleDao().selectParamsByActiveRuleUuids(dbSession, uuids) + .stream().collect(Collectors.groupingBy(ActiveRuleParamDto::getActiveRuleUuid)); + + for (RuleDto ruleDto : ruleDtos) { + RuleChange ruleChange = toRuleChange(ruleDto, paramsByRuleUuid); + deactivatedRules.add(ruleChange); + } + } + + } + + RuleSetChangeEvent event = new RuleSetChangeEvent(new String[]{project.getKey()}, activatedRules.toArray(new RuleChange[0]), deactivatedRules.toArray(new RuleChange[0])); + eventsDistributor.pushEvent(event); + } + + @NotNull + private RuleChange toRuleChange(RuleDto ruleDto, Map<String, List<ActiveRuleParamDto>> paramsByRuleUuid) { + RuleChange ruleChange = new RuleChange(); + ruleChange.setKey(ruleDto.getRuleKey()); + ruleChange.setLanguage(ruleDto.getLanguage()); + ruleChange.setSeverity(ruleDto.getSeverityString()); + + List<ParamChange> paramChanges = new ArrayList<>(); + List<ActiveRuleParamDto> activeRuleParamDtos = paramsByRuleUuid.getOrDefault(ruleDto.getUuid(), new ArrayList<>()); + for (ActiveRuleParamDto activeRuleParam : activeRuleParamDtos) { + paramChanges.add(new ParamChange(activeRuleParam.getKey(), activeRuleParam.getValue())); + } + ruleChange.setParams(paramChanges.toArray(new ParamChange[0])); + + String templateUuid = ruleDto.getTemplateUuid(); + if (templateUuid != null && !"".equals(templateUuid)) { + try (DbSession dbSession = dbClient.openSession(false)) { + RuleDto templateRule = dbClient.ruleDao().selectByUuid(templateUuid, dbSession) + .orElseThrow(() -> new IllegalStateException(String.format("unknow Template Rule '%s'", templateUuid))); + ruleChange.setTemplateKey(templateRule.getRuleKey()); + } + } + + return ruleChange; + } + + public void distributeRuleChangeEvent(Collection<QProfileDto> profiles, List<ActiveRuleChange> activeRuleChanges, String language) { + if (activeRuleChanges.isEmpty()) { + return; + } + + Set<RuleChange> activatedRules = new HashSet<>(); + Set<RuleChange> deactivatedRules = new HashSet<>(); + + for (ActiveRuleChange arc : activeRuleChanges) { + + RuleChange ruleChange = new RuleChange(); + ruleChange.setKey(arc.getActiveRule().getRuleKey().rule()); + ruleChange.setSeverity(arc.getSeverity()); + ruleChange.setLanguage(language); + + Optional<String> templateKey = templateKey(arc); + templateKey.ifPresent(ruleChange::setTemplateKey); + + // params + List<ParamChange> paramChanges = new ArrayList<>(); + for (Map.Entry<String, String> entry : arc.getParameters().entrySet()) { + paramChanges.add(new ParamChange(entry.getKey(), entry.getValue())); + } + ruleChange.setParams(paramChanges.toArray(new ParamChange[0])); + + switch (arc.getType()) { + case ACTIVATED: + case UPDATED: + activatedRules.add(ruleChange); + break; + case DEACTIVATED: + deactivatedRules.add(ruleChange); + break; + } + } + + Set<String> projectKeys = getProjectKeys(profiles); + + RuleSetChangeEvent event = new RuleSetChangeEvent(projectKeys.toArray(new String[0]), activatedRules.toArray(new RuleChange[0]), deactivatedRules.toArray(new RuleChange[0])); + eventsDistributor.pushEvent(event); + + } + + private Optional<String> templateKey(ActiveRuleChange arc) { + try (DbSession dbSession = dbClient.openSession(false)) { + String ruleUuid = arc.getRuleUuid(); + RuleDto rule = dbClient.ruleDao().selectByUuid(ruleUuid, dbSession).orElseThrow(() -> new IllegalStateException("unknow rule")); + String templateUuid = rule.getTemplateUuid(); + if (templateUuid != null && !"".equals(templateUuid)) { + RuleDto templateRule = dbClient.ruleDao().selectByUuid(templateUuid, dbSession) + .orElseThrow(() -> new IllegalStateException(String.format("unknow Template Rule '%s'", templateUuid))); + return Optional.of(templateRule.getRuleKey()); + } + } + return Optional.empty(); + } + + private Set<String> getProjectKeys(Collection<QProfileDto> profiles) { + Set<String> projectKeys = new HashSet<>(); + 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()); + } + } + return projectKeys; + } + } + +} diff --git a/server/sonar-webserver-auth/src/main/java/org/sonar/server/qualityprofile/RuleActivator.java b/server/sonar-webserver-auth/src/main/java/org/sonar/server/qualityprofile/RuleActivator.java index 63a98c72e49..713ae6ce9ab 100644 --- a/server/sonar-webserver-auth/src/main/java/org/sonar/server/qualityprofile/RuleActivator.java +++ b/server/sonar-webserver-auth/src/main/java/org/sonar/server/qualityprofile/RuleActivator.java @@ -52,6 +52,7 @@ import org.sonar.server.user.UserSession; import org.sonar.server.util.TypeValidations; import static com.google.common.base.Preconditions.checkArgument; +import static java.util.stream.Collectors.toList; import static org.sonar.server.exceptions.BadRequestException.checkRequest; /** @@ -72,9 +73,17 @@ public class RuleActivator { this.userSession = userSession; } + + public List<ActiveRuleChange> activate(DbSession dbSession, Collection<RuleActivation> activations, RuleActivationContext context) { + return activations.stream().map(a -> activate(dbSession, a, context)) + .flatMap(List::stream) + .collect(toList()); + } + public List<ActiveRuleChange> activate(DbSession dbSession, RuleActivation activation, RuleActivationContext context) { context.reset(activation.getRuleUuid()); - return doActivate(dbSession, activation, context); + List<ActiveRuleChange> activeRuleChanges = doActivate(dbSession, activation, context); + return activeRuleChanges; } private List<ActiveRuleChange> doActivate(DbSession dbSession, RuleActivation activation, RuleActivationContext context) { @@ -357,7 +366,8 @@ public class RuleActivator { public List<ActiveRuleChange> deactivate(DbSession dbSession, RuleActivationContext context, String ruleUuid, boolean force) { context.reset(ruleUuid); - return doDeactivate(dbSession, context, force); + List<ActiveRuleChange> activeRuleChanges = doDeactivate(dbSession, context, force); + return activeRuleChanges; } private List<ActiveRuleChange> doDeactivate(DbSession dbSession, RuleActivationContext context, boolean force) { diff --git a/server/sonar-webserver-auth/src/main/java/org/sonar/server/qualityprofile/RuleActivatorEventsDistributor.java b/server/sonar-webserver-auth/src/main/java/org/sonar/server/qualityprofile/RuleActivatorEventsDistributor.java new file mode 100644 index 00000000000..d41f1a5abd1 --- /dev/null +++ b/server/sonar-webserver-auth/src/main/java/org/sonar/server/qualityprofile/RuleActivatorEventsDistributor.java @@ -0,0 +1,30 @@ +/* + * 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.qualityprofile; + +import org.sonar.core.util.RuleActivationListener; +import org.sonar.core.util.RuleSetChangeEvent; + +public interface RuleActivatorEventsDistributor { + + void subscribe(RuleActivationListener listener); + + void pushEvent(RuleSetChangeEvent event); +}
\ No newline at end of file diff --git a/server/sonar-webserver-auth/src/main/java/org/sonar/server/qualityprofile/StandaloneRuleActivatorEventsDistributor.java b/server/sonar-webserver-auth/src/main/java/org/sonar/server/qualityprofile/StandaloneRuleActivatorEventsDistributor.java new file mode 100644 index 00000000000..43fc97da319 --- /dev/null +++ b/server/sonar-webserver-auth/src/main/java/org/sonar/server/qualityprofile/StandaloneRuleActivatorEventsDistributor.java @@ -0,0 +1,42 @@ +/* + * 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.qualityprofile; + +import java.util.ArrayList; +import java.util.List; +import org.sonar.api.server.ServerSide; +import org.sonar.core.util.RuleActivationListener; +import org.sonar.core.util.RuleSetChangeEvent; + +@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(RuleSetChangeEvent event) { + listeners.forEach(l -> l.listen(event)); + } +} diff --git a/server/sonar-webserver-auth/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileUpdateImplTest.java b/server/sonar-webserver-auth/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileUpdateImplTest.java index cfbf49f137f..024de98b39f 100644 --- a/server/sonar-webserver-auth/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileUpdateImplTest.java +++ b/server/sonar-webserver-auth/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileUpdateImplTest.java @@ -56,7 +56,12 @@ import static java.util.Collections.emptyMap; import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.groups.Tuple.tuple; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.sonar.api.rules.RulePriority.BLOCKER; import static org.sonar.api.rules.RulePriority.CRITICAL; import static org.sonar.api.rules.RulePriority.MAJOR; @@ -78,9 +83,11 @@ public class BuiltInQProfileUpdateImplTest { private System2 system2 = new TestSystem2().setNow(NOW); private ActiveRuleIndexer activeRuleIndexer = mock(ActiveRuleIndexer.class); private TypeValidations typeValidations = new TypeValidations(asList(new StringTypeValidation(), new IntegerTypeValidation())); + private QualityProfileChangeEventService qualityProfileChangeEventService = mock(QualityProfileChangeEventService.class); private RuleActivator ruleActivator = new RuleActivator(system2, db.getDbClient(), typeValidations, userSession); - private BuiltInQProfileUpdateImpl underTest = new BuiltInQProfileUpdateImpl(db.getDbClient(), ruleActivator, activeRuleIndexer); + private BuiltInQProfileUpdateImpl underTest = new BuiltInQProfileUpdateImpl(db.getDbClient(), ruleActivator, activeRuleIndexer, + qualityProfileChangeEventService); private RulesProfileDto persistedProfile; @@ -105,13 +112,14 @@ public class BuiltInQProfileUpdateImplTest { newQp.done(); BuiltInQProfile builtIn = builtInProfileRepository.create(context.profile("xoo", "Sonar way"), rule1, rule2); - underTest.update(db.getSession(), builtIn, persistedProfile); + List<ActiveRuleChange> changes = underTest.update(db.getSession(), builtIn, persistedProfile); List<ActiveRuleDto> activeRules = db.getDbClient().activeRuleDao().selectByRuleProfile(db.getSession(), persistedProfile); assertThat(activeRules).hasSize(2); assertThatRuleIsNewlyActivated(activeRules, rule1, CRITICAL); assertThatRuleIsNewlyActivated(activeRules, rule2, MAJOR); assertThatProfileIsMarkedAsUpdated(persistedProfile); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(persistedProfile.getLanguage())); } @Test @@ -125,12 +133,13 @@ public class BuiltInQProfileUpdateImplTest { activateRuleInDb(persistedProfile, rule, BLOCKER); - underTest.update(db.getSession(), builtIn, persistedProfile); + List<ActiveRuleChange> changes = underTest.update(db.getSession(), builtIn, persistedProfile); List<ActiveRuleDto> activeRules = db.getDbClient().activeRuleDao().selectByRuleProfile(db.getSession(), persistedProfile); assertThat(activeRules).hasSize(1); assertThatRuleIsUpdated(activeRules, rule, CRITICAL); assertThatProfileIsMarkedAsUpdated(persistedProfile); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(persistedProfile.getLanguage())); } @Test @@ -150,6 +159,7 @@ public class BuiltInQProfileUpdateImplTest { assertThat(activeRules).hasSize(1); assertThatRuleIsUntouched(activeRules, rule, CRITICAL); assertThatProfileIsNotMarkedAsUpdated(persistedProfile); + verifyNoInteractions(qualityProfileChangeEventService); } @Test @@ -166,12 +176,13 @@ public class BuiltInQProfileUpdateImplTest { // so rule1 must be deactivated activateRuleInDb(persistedProfile, rule1, CRITICAL); - underTest.update(db.getSession(), builtIn, persistedProfile); + List<ActiveRuleChange> changes = underTest.update(db.getSession(), builtIn, persistedProfile); List<ActiveRuleDto> activeRules = db.getDbClient().activeRuleDao().selectByRuleProfile(db.getSession(), persistedProfile); assertThat(activeRules).hasSize(1); assertThatRuleIsDeactivated(activeRules, rule1); assertThatProfileIsMarkedAsUpdated(persistedProfile); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(persistedProfile.getLanguage())); } @Test @@ -193,7 +204,7 @@ public class BuiltInQProfileUpdateImplTest { activateRuleInDb(persistedProfile, rule1, BLOCKER); activateRuleInDb(persistedProfile, rule3, BLOCKER); - underTest.update(db.getSession(), builtIn, persistedProfile); + List<ActiveRuleChange> changes = underTest.update(db.getSession(), builtIn, persistedProfile); List<ActiveRuleDto> activeRules = db.getDbClient().activeRuleDao().selectByRuleProfile(db.getSession(), persistedProfile); assertThat(activeRules).hasSize(2); @@ -201,6 +212,7 @@ public class BuiltInQProfileUpdateImplTest { assertThatRuleIsNewlyActivated(activeRules, rule2, MAJOR); assertThatRuleIsDeactivated(activeRules, rule3); assertThatProfileIsMarkedAsUpdated(persistedProfile); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(persistedProfile.getLanguage())); } // SONAR-10473 @@ -222,9 +234,10 @@ public class BuiltInQProfileUpdateImplTest { rule.setSeverity(Severity.MINOR); db.rules().update(rule); - underTest.update(db.getSession(), builtIn, persistedProfile); + List<ActiveRuleChange> changes = underTest.update(db.getSession(), builtIn, persistedProfile); activeRules = db.getDbClient().activeRuleDao().selectByRuleProfile(db.getSession(), persistedProfile); assertThatRuleIsNewlyActivated(activeRules, rule, MINOR); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(persistedProfile.getLanguage())); } @Test @@ -237,20 +250,22 @@ public class BuiltInQProfileUpdateImplTest { newQp.activateRule(rule.getRepositoryKey(), rule.getRuleKey()); newQp.done(); BuiltInQProfile builtIn = builtInProfileRepository.create(context.profile(newQp.language(), newQp.name()), rule); - underTest.update(db.getSession(), builtIn, persistedProfile); + List<ActiveRuleChange> changes = underTest.update(db.getSession(), builtIn, persistedProfile); List<ActiveRuleDto> activeRules = db.getDbClient().activeRuleDao().selectByRuleProfile(db.getSession(), persistedProfile); assertThat(activeRules).hasSize(1); assertThatRuleHasParams(db, activeRules.get(0), tuple("min", "10")); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(persistedProfile.getLanguage())); // emulate an upgrade of analyzer that changes the default value of parameter min ruleParam.setDefaultValue("20"); db.getDbClient().ruleDao().updateRuleParam(db.getSession(), rule, ruleParam); - underTest.update(db.getSession(), builtIn, persistedProfile); + changes = underTest.update(db.getSession(), builtIn, persistedProfile); activeRules = db.getDbClient().activeRuleDao().selectByRuleProfile(db.getSession(), persistedProfile); assertThat(activeRules).hasSize(1); assertThatRuleHasParams(db, activeRules.get(0), tuple("min", "20")); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(persistedProfile.getLanguage())); } @Test @@ -272,6 +287,7 @@ public class BuiltInQProfileUpdateImplTest { assertThatRuleIsActivated(profile, rule, changes, rule.getSeverityString(), null, emptyMap()); assertThatRuleIsActivated(childProfile, rule, changes, rule.getSeverityString(), INHERITED, emptyMap()); assertThatRuleIsActivated(grandchildProfile, rule, changes, rule.getSeverityString(), INHERITED, emptyMap()); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(persistedProfile.getLanguage())); } // SONAR-14559 @@ -299,6 +315,7 @@ public class BuiltInQProfileUpdateImplTest { List<ActiveRuleDto> childActiveRules = db.getDbClient().activeRuleDao().selectByRuleProfile(db.getSession(), RulesProfileDto.from(childProfile)); assertThatRuleIsUpdated(childActiveRules, rule, RulePriority.BLOCKER, INHERITED); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(persistedProfile.getLanguage())); } @Test @@ -327,6 +344,7 @@ public class BuiltInQProfileUpdateImplTest { assertThatRuleHasParams(db, parentActiveRuleDto, tuple("min", "10")); assertThatRuleHasParams(db, childActiveRuleDto, tuple("min", "10")); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(persistedProfile.getLanguage())); } @Test @@ -345,6 +363,7 @@ public class BuiltInQProfileUpdateImplTest { BuiltInQProfile builtIn = builtInProfileRepository.create(context.profile(profile.getLanguage(), profile.getName()), rule); List<ActiveRuleChange> changes = underTest.update(db.getSession(), builtIn, RulesProfileDto.from(profile)); assertThat(changes).hasSize(2).extracting(ActiveRuleChange::getType).containsOnly(ActiveRuleChange.Type.ACTIVATED); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(persistedProfile.getLanguage())); // second run, without any input changes RuleActivator ruleActivatorWithoutDescendants = new RuleActivator(system2, db.getDbClient(), typeValidations, userSession) { @@ -355,8 +374,10 @@ public class BuiltInQProfileUpdateImplTest { }; } }; - changes = new BuiltInQProfileUpdateImpl(db.getDbClient(), ruleActivatorWithoutDescendants, activeRuleIndexer).update(db.getSession(), builtIn, RulesProfileDto.from(profile)); + changes = new BuiltInQProfileUpdateImpl(db.getDbClient(), ruleActivatorWithoutDescendants, activeRuleIndexer, qualityProfileChangeEventService) + .update(db.getSession(), builtIn, RulesProfileDto.from(profile)); assertThat(changes).isEmpty(); + verifyNoMoreInteractions(qualityProfileChangeEventService); } @Test @@ -376,6 +397,7 @@ public class BuiltInQProfileUpdateImplTest { BuiltInQProfile builtIn = builtInProfileRepository.create(context.profile(profile.getLanguage(), profile.getName()), rule); List<ActiveRuleChange> changes = underTest.update(db.getSession(), builtIn, RulesProfileDto.from(profile)); assertThat(changes).hasSize(3).extracting(ActiveRuleChange::getType).containsOnly(ActiveRuleChange.Type.ACTIVATED); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(persistedProfile.getLanguage())); // second run to deactivate the rule context = new BuiltInQualityProfilesDefinition.Context(); @@ -384,6 +406,7 @@ public class BuiltInQProfileUpdateImplTest { builtIn = builtInProfileRepository.create(context.profile(profile.getLanguage(), profile.getName()), rule); changes = underTest.update(db.getSession(), builtIn, RulesProfileDto.from(profile)); assertThat(changes).hasSize(3).extracting(ActiveRuleChange::getType).containsOnly(ActiveRuleChange.Type.DEACTIVATED); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(persistedProfile.getLanguage())); assertThatRuleIsDeactivated(profile, rule); assertThatRuleIsDeactivated(childProfile, rule); diff --git a/server/sonar-webserver-auth/src/test/java/org/sonar/server/qualityprofile/RuleActivatorTest.java b/server/sonar-webserver-auth/src/test/java/org/sonar/server/qualityprofile/RuleActivatorTest.java index 45310b7d9b3..cdcd4fa41e2 100644 --- a/server/sonar-webserver-auth/src/test/java/org/sonar/server/qualityprofile/RuleActivatorTest.java +++ b/server/sonar-webserver-auth/src/test/java/org/sonar/server/qualityprofile/RuleActivatorTest.java @@ -50,6 +50,7 @@ import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertThrows; +import static org.mockito.Mockito.mock; import static org.sonar.server.qualityprofile.ActiveRuleInheritance.INHERITED; import static org.sonar.server.qualityprofile.ActiveRuleInheritance.OVERRIDES; @@ -69,6 +70,7 @@ public class RuleActivatorTest { private final System2 system2 = new TestSystem2().setNow(NOW); private final TypeValidations typeValidations = new TypeValidations(asList(new StringTypeValidation(), new IntegerTypeValidation())); + private final QualityProfileChangeEventService qualityProfileChangeEventService = mock(QualityProfileChangeEventService.class); private final RuleActivator underTest = new RuleActivator(system2, db.getDbClient(), typeValidations, userSession); @Test diff --git a/server/sonar-webserver-pushapi/build.gradle b/server/sonar-webserver-pushapi/build.gradle index a2c94714bd0..f3f66fea494 100644 --- a/server/sonar-webserver-pushapi/build.gradle +++ b/server/sonar-webserver-pushapi/build.gradle @@ -8,6 +8,7 @@ dependencies { compile 'javax.servlet:javax.servlet-api' compile project(':server:sonar-webserver-auth') compile project(':server:sonar-webserver-ws') + compile project(':server:sonar-webserver-webapi') testCompile 'junit:junit' testCompile 'org.assertj:assertj-core' diff --git a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/ServerPushClient.java b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/ServerPushClient.java index 4904b9e7cd1..cc0d3d7f0c7 100644 --- a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/ServerPushClient.java +++ b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/ServerPushClient.java @@ -20,6 +20,7 @@ package org.sonar.server.pushapi; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; @@ -34,6 +35,17 @@ public abstract class ServerPushClient { private static final Logger LOG = Loggers.get(ServerPushClient.class); private static final int DEFAULT_HEARTBEAT_PERIOD = 60; + + + + private static final byte[] CRLF = new byte[] {'\r', '\n'}; + private static final byte[] DATA_END = new byte[] {'\n', '\n'}; + private static final byte[] DATA_FIELD = "data: ".getBytes(StandardCharsets.UTF_8); + + + + + protected final AsyncContext asyncContext; private final ScheduledExecutorService executorService; @@ -50,6 +62,11 @@ public abstract class ServerPushClient { startedHeartbeat = executorService.schedule(heartbeatTask, DEFAULT_HEARTBEAT_PERIOD, TimeUnit.SECONDS); } + public void writeAndFlush(String payload) throws IOException { + output().write(payload.getBytes(StandardCharsets.UTF_8)); + flush(); + } + public void writeAndFlush(char character) { write(character); flush(); diff --git a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/sonarlint/SonarLintClient.java b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/sonarlint/SonarLintClient.java index b022cbcb25d..93512a974ec 100644 --- a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/sonarlint/SonarLintClient.java +++ b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/sonarlint/SonarLintClient.java @@ -39,6 +39,14 @@ public class SonarLintClient extends ServerPushClient { this.languages = languages; } + public Set<String> getLanguages() { + return languages; + } + + public Set<String> getClientProjectKeys() { + return projectKeys; + } + @Override public boolean equals(Object o) { if (this == o) { diff --git a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/sonarlint/SonarLintClientsRegistry.java b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/sonarlint/SonarLintClientsRegistry.java index e93bf5f9073..4b72f6de66d 100644 --- a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/sonarlint/SonarLintClientsRegistry.java +++ b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/sonarlint/SonarLintClientsRegistry.java @@ -19,25 +19,46 @@ */ package org.sonar.server.pushapi.sonarlint; +import java.io.IOException; +import java.util.Collections; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Predicate; import javax.servlet.AsyncEvent; import javax.servlet.AsyncListener; +import org.json.JSONArray; +import org.json.JSONObject; 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.ParamChange; +import org.sonar.core.util.RuleActivationListener; +import org.sonar.core.util.RuleChange; +import org.sonar.core.util.RuleSetChangeEvent; +import org.sonar.server.qualityprofile.RuleActivatorEventsDistributor; + +import static java.util.Arrays.asList; @ServerSide -public class SonarLintClientsRegistry { +public class SonarLintClientsRegistry implements RuleActivationListener { private static final Logger LOG = Loggers.get(SonarLintClientsRegistry.class); + private final RuleActivatorEventsDistributor ruleActivatorEventsDistributor; + + public SonarLintClientsRegistry(RuleActivatorEventsDistributor ruleActivatorEventsDistributor) { + this.ruleActivatorEventsDistributor = ruleActivatorEventsDistributor; + } + + private final List<SonarLintClient> clients = new CopyOnWriteArrayList<>(); public void registerClient(SonarLintClient sonarLintClient) { clients.add(sonarLintClient); sonarLintClient.scheduleHeartbeat(); sonarLintClient.addListener(new SonarLintClientEventsListener(sonarLintClient)); + ruleActivatorEventsDistributor.subscribe(this); + LOG.debug("Registering new SonarLint client"); } @@ -50,6 +71,71 @@ public class SonarLintClientsRegistry { return clients.size(); } + @Override + public void listen(RuleSetChangeEvent ruleChangeEvent) { + LOG.info("Generating a RuleSetChangeEvent"); + // TODO filter on languages here as well + broadcastMessage(ruleChangeEvent, f -> f.getClientProjectKeys().isEmpty() || !Collections.disjoint(f.getClientProjectKeys(), asList(ruleChangeEvent.getProjects()))); + } + + + public void broadcastMessage(RuleSetChangeEvent message, Predicate<SonarLintClient> filter) { + String jsonString = getJSONString(message); + clients.stream().filter(filter).forEach(c -> { + try { + c.writeAndFlush(jsonString); + } catch (IOException e) { + LOG.error("Unable to send message to a client: " + e.getMessage()); + } + }); + } + + + public String getJSONString(RuleSetChangeEvent ruleSetChangeEvent) { + JSONObject result = new JSONObject(); + result.put("event", ruleSetChangeEvent.getEvent()); + + JSONObject data = new JSONObject(); + data.put("projects", ruleSetChangeEvent.getProjects()); + + JSONArray activatedRulesJson = new JSONArray(); + for (RuleChange rule : ruleSetChangeEvent.getActivatedRules()) { + activatedRulesJson.put(toJson(rule)); + } + data.put("activatedRules", activatedRulesJson); + + JSONArray deactivatedRulesJson = new JSONArray(); + for (RuleChange rule : ruleSetChangeEvent.getDeactivatedRules()) { + deactivatedRulesJson.put(toJson(rule)); + } + data.put("deactivatedRules", deactivatedRulesJson); + + result.put("data", data); + return result.toString(); + } + + private 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 JSONObject toJson(ParamChange paramChange) { + JSONObject param = new JSONObject(); + param.put("key", paramChange.getKey()); + param.put("value", paramChange.getValue()); + return param; + } + class SonarLintClientEventsListener implements AsyncListener { private final SonarLintClient client; diff --git a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/sonarlint/SonarLintPushAction.java b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/sonarlint/SonarLintPushAction.java index 5bcce895562..2c27c48cda1 100644 --- a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/sonarlint/SonarLintPushAction.java +++ b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/sonarlint/SonarLintPushAction.java @@ -84,7 +84,7 @@ public class SonarLintPushAction extends ServerPushAction { if (!isServerSideEventsRequest(servletRequest)) { servletResponse.stream().setStatus(HttpServletResponse.SC_NOT_ACCEPTABLE); - return; + return; // TODO fixme this is not closing the connexion properly } setHeadersForResponse(servletResponse); @@ -92,7 +92,7 @@ public class SonarLintPushAction extends ServerPushAction { AsyncContext asyncContext = servletRequest.startAsync(); asyncContext.setTimeout(0); - var sonarLintClient = new SonarLintClient(asyncContext, params.getProjectKeys(), params.getLanguages()); + SonarLintClient sonarLintClient = new SonarLintClient(asyncContext, params.getProjectKeys(), params.getLanguages()); clientsRegistry.registerClient(sonarLintClient); } diff --git a/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/sonarlint/SonarLintClientsRegistryTest.java b/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/sonarlint/SonarLintClientsRegistryTest.java index 450245c13c0..49076199685 100644 --- a/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/sonarlint/SonarLintClientsRegistryTest.java +++ b/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/sonarlint/SonarLintClientsRegistryTest.java @@ -23,6 +23,7 @@ import java.util.Set; import javax.servlet.AsyncContext; import org.junit.Before; import org.junit.Test; +import org.sonar.server.qualityprofile.StandaloneRuleActivatorEventsDistributor; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -39,7 +40,7 @@ public class SonarLintClientsRegistryTest { @Before public void before() { - underTest = new SonarLintClientsRegistry(); + underTest = new SonarLintClientsRegistry(mock(StandaloneRuleActivatorEventsDistributor.class)); } @Test diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/QProfileResetImpl.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/QProfileResetImpl.java index e4e0cab8bf1..7deadcbbfee 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/QProfileResetImpl.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/QProfileResetImpl.java @@ -41,11 +41,13 @@ public class QProfileResetImpl implements QProfileReset { private final DbClient db; private final RuleActivator activator; private final ActiveRuleIndexer activeRuleIndexer; + private final QualityProfileChangeEventService qualityProfileChangeEventService; - public QProfileResetImpl(DbClient db, RuleActivator activator, ActiveRuleIndexer activeRuleIndexer) { + public QProfileResetImpl(DbClient db, RuleActivator activator, ActiveRuleIndexer activeRuleIndexer, QualityProfileChangeEventService qualityProfileChangeEventService) { this.db = db; this.activator = activator; this.activeRuleIndexer = activeRuleIndexer; + this.qualityProfileChangeEventService = qualityProfileChangeEventService; } @Override @@ -87,6 +89,7 @@ public class QProfileResetImpl implements QProfileReset { // ignore, probably a rule inherited from parent that can't be deactivated } } + qualityProfileChangeEventService.distributeRuleChangeEvent(List.of(profile), changes, profile.getLanguage()); activeRuleIndexer.commitAndIndex(dbSession, changes); return result; } diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/QProfileRulesImpl.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/QProfileRulesImpl.java index dca861c597e..db891a34ecc 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/QProfileRulesImpl.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/QProfileRulesImpl.java @@ -44,12 +44,15 @@ public class QProfileRulesImpl implements QProfileRules { private final RuleActivator ruleActivator; private final RuleIndex ruleIndex; private final ActiveRuleIndexer activeRuleIndexer; + private final QualityProfileChangeEventService qualityProfileChangeEventService; - public QProfileRulesImpl(DbClient db, RuleActivator ruleActivator, RuleIndex ruleIndex, ActiveRuleIndexer activeRuleIndexer) { + public QProfileRulesImpl(DbClient db, RuleActivator ruleActivator, RuleIndex ruleIndex, ActiveRuleIndexer activeRuleIndexer, + QualityProfileChangeEventService qualityProfileChangeEventService) { this.db = db; this.ruleActivator = ruleActivator; this.ruleIndex = ruleIndex; this.activeRuleIndexer = activeRuleIndexer; + this.qualityProfileChangeEventService = qualityProfileChangeEventService; } @Override @@ -63,6 +66,7 @@ public class QProfileRulesImpl implements QProfileRules { for (RuleActivation activation : activations) { changes.addAll(ruleActivator.activate(dbSession, activation, context)); } + qualityProfileChangeEventService.distributeRuleChangeEvent(List.of(profile), changes, profile.getLanguage()); activeRuleIndexer.commitAndIndex(dbSession, changes); return changes; } @@ -70,10 +74,12 @@ public class QProfileRulesImpl implements QProfileRules { @Override public BulkChangeResult bulkActivateAndCommit(DbSession dbSession, QProfileDto profile, RuleQuery ruleQuery, @Nullable String severity) { verifyNotBuiltIn(profile); - return doBulk(dbSession, profile, ruleQuery, (context, ruleDefinition) -> { + BulkChangeResult bulkChangeResult = doBulk(dbSession, profile, ruleQuery, (context, ruleDefinition) -> { RuleActivation activation = RuleActivation.create(ruleDefinition.getUuid(), severity, null); return ruleActivator.activate(dbSession, activation, context); }); + qualityProfileChangeEventService.distributeRuleChangeEvent(List.of(profile), bulkChangeResult.getChanges(), profile.getLanguage()); + return bulkChangeResult; } @Override @@ -85,6 +91,9 @@ public class QProfileRulesImpl implements QProfileRules { for (String ruleUuid : ruleUuids) { changes.addAll(ruleActivator.deactivate(dbSession, context, ruleUuid, false)); } + + qualityProfileChangeEventService.distributeRuleChangeEvent(List.of(profile), changes, profile.getLanguage()); + activeRuleIndexer.commitAndIndex(dbSession, changes); return changes; } @@ -92,7 +101,12 @@ public class QProfileRulesImpl implements QProfileRules { @Override public BulkChangeResult bulkDeactivateAndCommit(DbSession dbSession, QProfileDto profile, RuleQuery ruleQuery) { verifyNotBuiltIn(profile); - return doBulk(dbSession, profile, ruleQuery, (context, ruleDefinition) -> ruleActivator.deactivate(dbSession, context, ruleDefinition.getUuid(), false)); + BulkChangeResult bulkChangeResult = doBulk(dbSession, profile, ruleQuery, (context, ruleDefinition) -> + ruleActivator.deactivate(dbSession, context, ruleDefinition.getUuid(), false)); + + qualityProfileChangeEventService.distributeRuleChangeEvent(List.of(profile), bulkChangeResult.getChanges(), profile.getLanguage()); + + return bulkChangeResult; } @Override diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/QProfileTreeImpl.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/QProfileTreeImpl.java index b38f695d8f8..d315e15b2cd 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/QProfileTreeImpl.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/QProfileTreeImpl.java @@ -41,12 +41,14 @@ public class QProfileTreeImpl implements QProfileTree { private final RuleActivator ruleActivator; private final System2 system2; private final ActiveRuleIndexer activeRuleIndexer; + private final QualityProfileChangeEventService qualityProfileChangeEventService; - public QProfileTreeImpl(DbClient db, RuleActivator ruleActivator, System2 system2, ActiveRuleIndexer activeRuleIndexer) { + public QProfileTreeImpl(DbClient db, RuleActivator ruleActivator, System2 system2, ActiveRuleIndexer activeRuleIndexer, QualityProfileChangeEventService qualityProfileChangeEventService) { this.db = db; this.ruleActivator = ruleActivator; this.system2 = system2; this.activeRuleIndexer = activeRuleIndexer; + this.qualityProfileChangeEventService = qualityProfileChangeEventService; } @Override @@ -92,6 +94,7 @@ public class QProfileTreeImpl implements QProfileTree { // TODO return errors } } + qualityProfileChangeEventService.distributeRuleChangeEvent(List.of(profile), changes, profile.getLanguage()); return changes; } @@ -120,6 +123,8 @@ public class QProfileTreeImpl implements QProfileTree { changes.add(new ActiveRuleChange(ActiveRuleChange.Type.UPDATED, activeRule, context.getRule().get()).setInheritance(null)); } } + + qualityProfileChangeEventService.distributeRuleChangeEvent(List.of(profile), changes, profile.getLanguage()); return changes; } diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ws/AddProjectAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ws/AddProjectAction.java index 60646999ff5..202758dc446 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ws/AddProjectAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ws/AddProjectAction.java @@ -19,6 +19,7 @@ */ package org.sonar.server.qualityprofile.ws; +import java.util.Optional; import org.sonar.api.resources.Languages; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Response; @@ -30,8 +31,11 @@ import org.sonar.db.DbSession; import org.sonar.db.project.ProjectDto; import org.sonar.db.qualityprofile.QProfileDto; import org.sonar.server.component.ComponentFinder; +import org.sonar.server.qualityprofile.QualityProfileChangeEventService; import org.sonar.server.user.UserSession; +import static java.util.Optional.empty; +import static java.util.Optional.of; import static org.sonar.server.user.AbstractUserSession.insufficientPrivilegesException; import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001; import static org.sonarqube.ws.client.qualityprofile.QualityProfileWsParameters.ACTION_ADD_PROJECT; @@ -44,13 +48,16 @@ public class AddProjectAction implements QProfileWsAction { private final Languages languages; private final ComponentFinder componentFinder; private final QProfileWsSupport wsSupport; + private final QualityProfileChangeEventService qualityProfileChangeEventService; - public AddProjectAction(DbClient dbClient, UserSession userSession, Languages languages, ComponentFinder componentFinder, QProfileWsSupport wsSupport) { + public AddProjectAction(DbClient dbClient, UserSession userSession, Languages languages, ComponentFinder componentFinder, + QProfileWsSupport wsSupport, QualityProfileChangeEventService qualityProfileChangeEventService) { this.dbClient = dbClient; this.userSession = userSession; this.languages = languages; this.componentFinder = componentFinder; this.wsSupport = wsSupport; + this.qualityProfileChangeEventService = qualityProfileChangeEventService; } @Override @@ -80,24 +87,36 @@ public class AddProjectAction implements QProfileWsAction { userSession.checkLoggedIn(); try (DbSession dbSession = dbClient.openSession(false)) { + ProjectDto project = loadProject(dbSession, request); QProfileDto profile = wsSupport.getProfile(dbSession, QProfileReference.fromName(request)); checkPermissions(dbSession, profile, project); - QProfileDto currentProfile = dbClient.qualityProfileDao().selectAssociatedToProjectAndLanguage(dbSession, project, profile.getLanguage()); + + Optional<QProfileDto> deactivatedProfile = empty(); + if (currentProfile == null) { + QProfileDto defaultProfile = dbClient.qualityProfileDao().selectDefaultProfile(dbSession, profile.getLanguage()); + if (defaultProfile != null) { + deactivatedProfile = of(defaultProfile); + } + // project uses the default profile dbClient.qualityProfileDao().insertProjectProfileAssociation(dbSession, project, profile); dbSession.commit(); } else if (!profile.getKee().equals(currentProfile.getKee())) { + deactivatedProfile = of(currentProfile); dbClient.qualityProfileDao().updateProjectProfileAssociation(dbSession, project, profile.getKee(), currentProfile.getKee()); dbSession.commit(); } + Optional<QProfileDto> activatedProfile = of(profile); + qualityProfileChangeEventService.publishRuleActivationToSonarLintClients(project, activatedProfile, deactivatedProfile); } response.noContent(); } + private ProjectDto loadProject(DbSession dbSession, Request request) { String projectKey = request.mandatoryParam(PARAM_PROJECT); return componentFinder.getProjectByKey(dbSession, projectKey); diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ws/RemoveProjectAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ws/RemoveProjectAction.java index 4ddf8ced7a4..81c7987dc5b 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ws/RemoveProjectAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ws/RemoveProjectAction.java @@ -19,6 +19,7 @@ */ package org.sonar.server.qualityprofile.ws; +import java.util.Optional; import org.sonar.api.resources.Languages; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Response; @@ -30,8 +31,11 @@ import org.sonar.db.DbSession; import org.sonar.db.project.ProjectDto; import org.sonar.db.qualityprofile.QProfileDto; import org.sonar.server.component.ComponentFinder; +import org.sonar.server.qualityprofile.QualityProfileChangeEventService; import org.sonar.server.user.UserSession; +import static java.util.Optional.empty; +import static java.util.Optional.of; import static org.sonar.server.user.AbstractUserSession.insufficientPrivilegesException; import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001; import static org.sonarqube.ws.client.qualityprofile.QualityProfileWsParameters.ACTION_REMOVE_PROJECT; @@ -44,13 +48,16 @@ public class RemoveProjectAction implements QProfileWsAction { private final Languages languages; private final ComponentFinder componentFinder; private final QProfileWsSupport wsSupport; + private final QualityProfileChangeEventService qualityProfileChangeEventService; - public RemoveProjectAction(DbClient dbClient, UserSession userSession, Languages languages, ComponentFinder componentFinder, QProfileWsSupport wsSupport) { + public RemoveProjectAction(DbClient dbClient, UserSession userSession, Languages languages, ComponentFinder componentFinder, + QProfileWsSupport wsSupport, QualityProfileChangeEventService qualityProfileChangeEventService) { this.dbClient = dbClient; this.userSession = userSession; this.languages = languages; this.componentFinder = componentFinder; this.wsSupport = wsSupport; + this.qualityProfileChangeEventService = qualityProfileChangeEventService; } @Override @@ -86,6 +93,17 @@ public class RemoveProjectAction implements QProfileWsAction { dbClient.qualityProfileDao().deleteProjectProfileAssociation(dbSession, project, profile); dbSession.commit(); + Optional<QProfileDto> deactivatedProfile = of(profile); + Optional<QProfileDto> activatedProfile = empty(); + + // publish change for rules in the default quality profile + QProfileDto defaultProfile = dbClient.qualityProfileDao().selectDefaultProfile(dbSession, profile.getLanguage()); + if (defaultProfile != null) { + activatedProfile = of(defaultProfile); + } + + qualityProfileChangeEventService.publishRuleActivationToSonarLintClients(project, activatedProfile, deactivatedProfile); + response.noContent(); } } diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/QProfileComparisonTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/QProfileComparisonTest.java index 2f410c9e96b..3254844f7be 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/QProfileComparisonTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/QProfileComparisonTest.java @@ -49,6 +49,7 @@ import org.sonar.server.util.TypeValidations; import static java.util.Collections.singleton; import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; public class QProfileComparisonTest { @@ -74,8 +75,9 @@ public class QProfileComparisonTest { dbSession = db.openSession(false); RuleIndex ruleIndex = new RuleIndex(es.client(), System2.INSTANCE); ActiveRuleIndexer activeRuleIndexer = new ActiveRuleIndexer(db, es.client()); + QualityProfileChangeEventService qualityProfileChangeEventService = mock(QualityProfileChangeEventService.class); RuleActivator ruleActivator = new RuleActivator(System2.INSTANCE, db, new TypeValidations(singletonList(new IntegerTypeValidation())), userSession); - qProfileRules = new QProfileRulesImpl(db, ruleActivator, ruleIndex, activeRuleIndexer); + qProfileRules = new QProfileRulesImpl(db, ruleActivator, ruleIndex, activeRuleIndexer, qualityProfileChangeEventService); comparison = new QProfileComparison(db); xooRule1 = RuleTesting.newXooX1().setSeverity("MINOR").getDefinition(); diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/QProfileResetImplTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/QProfileResetImplTest.java index ef1df8f5be6..08f2b22255a 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/QProfileResetImplTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/QProfileResetImplTest.java @@ -41,7 +41,12 @@ import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.tuple; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.internal.verification.VerificationModeFactory.times; import static org.sonar.server.qualityprofile.ActiveRuleChange.Type.ACTIVATED; public class QProfileResetImplTest { @@ -55,11 +60,12 @@ public class QProfileResetImplTest { private System2 system2 = new AlwaysIncreasingSystem2(); private ActiveRuleIndexer activeRuleIndexer = mock(ActiveRuleIndexer.class); + private QualityProfileChangeEventService qualityProfileChangeEventService = mock(QualityProfileChangeEventService.class); private TypeValidations typeValidations = new TypeValidations(asList(new StringTypeValidation(), new IntegerTypeValidation())); private RuleActivator ruleActivator = new RuleActivator(system2, db.getDbClient(), typeValidations, userSession); - private QProfileTree qProfileTree = new QProfileTreeImpl(db.getDbClient(), ruleActivator, system2, activeRuleIndexer); - private QProfileRules qProfileRules = new QProfileRulesImpl(db.getDbClient(), ruleActivator, null, activeRuleIndexer); - private QProfileResetImpl underTest = new QProfileResetImpl(db.getDbClient(), ruleActivator, activeRuleIndexer); + private QProfileTree qProfileTree = new QProfileTreeImpl(db.getDbClient(), ruleActivator, system2, activeRuleIndexer, mock(QualityProfileChangeEventService.class)); + private QProfileRules qProfileRules = new QProfileRulesImpl(db.getDbClient(), ruleActivator, null, activeRuleIndexer, qualityProfileChangeEventService); + private QProfileResetImpl underTest = new QProfileResetImpl(db.getDbClient(), ruleActivator, activeRuleIndexer, mock(QualityProfileChangeEventService.class)); @Test public void reset() { @@ -77,6 +83,7 @@ public class QProfileResetImplTest { assertThat(result.getChanges()) .extracting(ActiveRuleChange::getKey, ActiveRuleChange::getType) .containsExactlyInAnyOrder(tuple(ActiveRuleKey.of(profile, newRule.getKey()), ACTIVATED)); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), any(), eq(profile.getLanguage())); } @Test @@ -94,6 +101,7 @@ public class QProfileResetImplTest { assertThat(db.getDbClient().activeRuleDao().selectByProfile(db.getSession(), childProfile)) .extracting(OrgActiveRuleDto::getRuleKey) .containsExactlyInAnyOrder(newRule.getKey(), existingRule.getKey()); + verify(qualityProfileChangeEventService, times(2)).distributeRuleChangeEvent(any(), any(), eq(childProfile.getLanguage())); } @Test @@ -106,6 +114,7 @@ public class QProfileResetImplTest { }) .isInstanceOf(IllegalArgumentException.class) .hasMessage(String.format("Operation forbidden for built-in Quality Profile '%s'", profile.getKee())); + verifyNoInteractions(qualityProfileChangeEventService); } @Test @@ -118,5 +127,7 @@ public class QProfileResetImplTest { }) .isInstanceOf(NullPointerException.class) .hasMessage("Quality profile must be persisted"); + + verifyNoInteractions(qualityProfileChangeEventService); } } diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/QProfileRuleImplTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/QProfileRuleImplTest.java index 21e8a820c8c..e1479fcb1e4 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/QProfileRuleImplTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/QProfileRuleImplTest.java @@ -61,6 +61,11 @@ import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; import static org.sonar.api.rule.Severity.BLOCKER; import static org.sonar.api.rule.Severity.CRITICAL; import static org.sonar.api.rule.Severity.MAJOR; @@ -81,9 +86,10 @@ public class QProfileRuleImplTest { private ActiveRuleIndexer activeRuleIndexer = new ActiveRuleIndexer(db.getDbClient(), es.client()); private RuleIndexer ruleIndexer = new RuleIndexer(es.client(), db.getDbClient()); private TypeValidations typeValidations = new TypeValidations(asList(new StringTypeValidation(), new IntegerTypeValidation())); + private QualityProfileChangeEventService qualityProfileChangeEventService = mock(QualityProfileChangeEventService.class); private RuleActivator ruleActivator = new RuleActivator(system2, db.getDbClient(), typeValidations, userSession); - private QProfileRules underTest = new QProfileRulesImpl(db.getDbClient(), ruleActivator, ruleIndex, activeRuleIndexer); + private QProfileRules underTest = new QProfileRulesImpl(db.getDbClient(), ruleActivator, ruleIndex, activeRuleIndexer, qualityProfileChangeEventService); @Test public void system_activates_rule_without_parameters() { @@ -94,6 +100,7 @@ public class QProfileRuleImplTest { assertThatRuleIsActivated(profile, rule, changes, BLOCKER, null, emptyMap()); assertThatProfileIsUpdatedBySystem(profile); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), any(), eq(profile.getLanguage())); } @Test @@ -106,6 +113,7 @@ public class QProfileRuleImplTest { assertThatRuleIsActivated(profile, rule, changes, BLOCKER, null, emptyMap()); assertThatProfileIsUpdatedByUser(profile); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), any(), eq(profile.getLanguage())); } @Test @@ -119,6 +127,7 @@ public class QProfileRuleImplTest { assertThatRuleIsActivated(profile, rule, changes, rule.getSeverityString(), null, of("min", "10")); assertThatProfileIsUpdatedBySystem(profile); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), any(), eq(profile.getLanguage())); } @Test @@ -132,6 +141,7 @@ public class QProfileRuleImplTest { assertThatRuleIsActivated(profile, rule, changes, rule.getSeverityString(), null, of("min", "15")); assertThatProfileIsUpdatedBySystem(profile); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), any(), eq(profile.getLanguage())); } @Test @@ -144,6 +154,7 @@ public class QProfileRuleImplTest { assertThatRuleIsActivated(profile, rule, changes, rule.getSeverityString(), null, emptyMap()); assertThatProfileIsUpdatedBySystem(profile); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), any(), eq(profile.getLanguage())); } /** @@ -160,6 +171,7 @@ public class QProfileRuleImplTest { assertThatRuleIsActivated(profile, rule, changes, rule.getSeverityString(), null, of("min", "10")); assertThatProfileIsUpdatedBySystem(profile); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), any(), eq(profile.getLanguage())); } /** @@ -179,6 +191,7 @@ public class QProfileRuleImplTest { assertThatRuleIsActivated(profile, rule, changes, rule.getSeverityString(), null, of(paramWithoutDefault.getName(), "-10", paramWithDefault.getName(), paramWithDefault.getDefaultValue())); assertThatProfileIsUpdatedBySystem(profile); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), any(), eq(profile.getLanguage())); } @Test @@ -192,6 +205,7 @@ public class QProfileRuleImplTest { assertThatRuleIsActivated(profile, rule, changes, rule.getSeverityString(), null, of(param.getName(), param.getDefaultValue())); assertThatProfileIsUpdatedBySystem(profile); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), any(), eq(profile.getLanguage())); } @Test @@ -202,14 +216,16 @@ public class QProfileRuleImplTest { // initial activation RuleActivation activation = RuleActivation.create(rule.getUuid(), MAJOR, null); - activate(profile, activation); + List<ActiveRuleChange> changes = activate(profile, activation); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(profile.getLanguage())); // update RuleActivation updateActivation = RuleActivation.create(rule.getUuid(), CRITICAL, of(param.getName(), "20")); - List<ActiveRuleChange> changes = activate(profile, updateActivation); + changes = activate(profile, updateActivation); assertThatRuleIsUpdated(profile, rule, CRITICAL, null, of(param.getName(), "20")); assertThatProfileIsUpdatedBySystem(profile); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(profile.getLanguage())); } @Test @@ -221,14 +237,16 @@ public class QProfileRuleImplTest { // initial activation -> param "max" has a default value RuleActivation activation = RuleActivation.create(rule.getUuid()); - activate(profile, activation); + List<ActiveRuleChange> changes = activate(profile, activation); // update param "min", which has no default value RuleActivation updateActivation = RuleActivation.create(rule.getUuid(), MAJOR, of(paramWithoutDefault.getName(), "3")); - List<ActiveRuleChange> changes = activate(profile, updateActivation); + changes = activate(profile, updateActivation); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(profile.getLanguage())); assertThatRuleIsUpdated(profile, rule, MAJOR, null, of(paramWithDefault.getName(), "10", paramWithoutDefault.getName(), "3")); assertThatProfileIsUpdatedBySystem(profile); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(profile.getLanguage())); } @Test @@ -239,14 +257,16 @@ public class QProfileRuleImplTest { // initial activation -> param "max" has a default value RuleActivation activation = RuleActivation.create(rule.getUuid(), null, of(paramWithDefault.getName(), "20")); - activate(profile, activation); + List<ActiveRuleChange> changes = activate(profile, activation); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(profile.getLanguage())); // reset to default_value RuleActivation updateActivation = RuleActivation.create(rule.getUuid(), null, of(paramWithDefault.getName(), "")); - List<ActiveRuleChange> changes = activate(profile, updateActivation); + changes = activate(profile, updateActivation); assertThatRuleIsUpdated(profile, rule, rule.getSeverityString(), null, of(paramWithDefault.getName(), "10")); assertThat(changes).hasSize(1); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(profile.getLanguage())); } @Test @@ -258,14 +278,16 @@ public class QProfileRuleImplTest { // initial activation -> param "max" has a default value RuleActivation activation = RuleActivation.create(rule.getUuid(), null, of(paramWithoutDefault.getName(), "20")); - activate(profile, activation); + List<ActiveRuleChange> changes = activate(profile, activation); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(profile.getLanguage())); // remove parameter RuleActivation updateActivation = RuleActivation.create(rule.getUuid(), null, of(paramWithoutDefault.getName(), "")); - List<ActiveRuleChange> changes = activate(profile, updateActivation); + changes = activate(profile, updateActivation); assertThatRuleIsUpdated(profile, rule, rule.getSeverityString(), null, of(paramWithDefault.getName(), paramWithDefault.getDefaultValue())); assertThat(changes).hasSize(1); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(profile.getLanguage())); } @Test @@ -279,6 +301,7 @@ public class QProfileRuleImplTest { List<ActiveRuleChange> changes = activate(profile, activation); db.getDbClient().activeRuleDao().deleteParametersByRuleProfileUuids(db.getSession(), asList(profile.getRulesProfileUuid())); assertThatRuleIsActivated(profile, rule, changes, rule.getSeverityString(), null, emptyMap()); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(profile.getLanguage())); // contrary to activerule, the param is supposed to be inserted but not updated RuleActivation updateActivation = RuleActivation.create(rule.getUuid(), null, of(param.getName(), "")); @@ -286,6 +309,7 @@ public class QProfileRuleImplTest { assertThatRuleIsUpdated(profile, rule, rule.getSeverityString(), null, of(param.getName(), param.getDefaultValue())); assertThat(changes).hasSize(1); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(profile.getLanguage())); } @Test @@ -295,13 +319,15 @@ public class QProfileRuleImplTest { // initial activation RuleActivation activation = RuleActivation.create(rule.getUuid()); - activate(profile, activation); + List<ActiveRuleChange> changes = activate(profile, activation); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(profile.getLanguage())); // update with exactly the same severity and params activation = RuleActivation.create(rule.getUuid()); - List<ActiveRuleChange> changes = activate(profile, activation); + changes = activate(profile, activation); assertThat(changes).isEmpty(); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(profile.getLanguage())); } @Test @@ -312,13 +338,16 @@ public class QProfileRuleImplTest { // initial activation -> param "max" has a default value RuleActivation activation = RuleActivation.create(rule.getUuid(), BLOCKER, of(param.getName(), "20")); - activate(profile, activation); + List<ActiveRuleChange> changes = activate(profile, activation); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(profile.getLanguage())); + // update without any severity or params => keep RuleActivation update = RuleActivation.create(rule.getUuid()); - List<ActiveRuleChange> changes = activate(profile, update); + changes = activate(profile, update); assertThat(changes).isEmpty(); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(profile.getLanguage())); } @Test @@ -328,6 +357,7 @@ public class QProfileRuleImplTest { RuleActivation activation = RuleActivation.create(rule.getUuid()); expectFailure("java rule " + rule.getKey() + " cannot be activated on js profile " + profile.getKee(), () -> activate(profile, activation)); + verifyNoInteractions(qualityProfileChangeEventService); } @Test @@ -337,6 +367,7 @@ public class QProfileRuleImplTest { RuleActivation activation = RuleActivation.create(rule.getUuid()); expectFailure("Rule was removed: " + rule.getKey(), () -> activate(profile, activation)); + verifyNoInteractions(qualityProfileChangeEventService); } @Test @@ -346,6 +377,7 @@ public class QProfileRuleImplTest { RuleActivation activation = RuleActivation.create(rule.getUuid()); expectFailure("Rule template can't be activated on a Quality profile: " + rule.getKey(), () -> activate(profile, activation)); + verifyNoInteractions(qualityProfileChangeEventService); } @Test @@ -356,6 +388,7 @@ public class QProfileRuleImplTest { RuleActivation activation = RuleActivation.create(rule.getUuid(), null, of(param.getName(), "foo")); expectFailure("Value 'foo' must be an integer.", () -> activate(profile, activation)); + verifyNoInteractions(qualityProfileChangeEventService); } @Test @@ -368,13 +401,15 @@ public class QProfileRuleImplTest { // initial activation RuleActivation activation = RuleActivation.create(customRule.getUuid(), MAJOR, emptyMap()); - activate(profile, activation); + List<ActiveRuleChange> changes = activate(profile, activation); assertThatRuleIsActivated(profile, customRule, null, MAJOR, null, of("format", "txt")); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(profile.getLanguage())); // update -> parameter is not changed RuleActivation updateActivation = RuleActivation.create(customRule.getUuid(), BLOCKER, of("format", "xml")); - activate(profile, updateActivation); + changes = activate(profile, updateActivation); assertThatRuleIsActivated(profile, customRule, null, BLOCKER, null, of("format", "txt")); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(profile.getLanguage())); } @Test @@ -383,13 +418,15 @@ public class QProfileRuleImplTest { RuleDefinitionDto rule = createRule(); QProfileDto profile = createProfile(rule); RuleActivation activation = RuleActivation.create(rule.getUuid()); - activate(profile, activation); + List<ActiveRuleChange> changes = activate(profile, activation); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(profile.getLanguage())); - List<ActiveRuleChange> changes = deactivate(profile, rule); + changes = deactivate(profile, rule); verifyNoActiveRules(); assertThatProfileIsUpdatedByUser(profile); assertThat(changes).hasSize(1); assertThat(changes.get(0).getType()).isEqualTo(ActiveRuleChange.Type.DEACTIVATED); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(profile.getLanguage())); } @Test @@ -397,12 +434,14 @@ public class QProfileRuleImplTest { RuleDefinitionDto rule = createRule(); QProfileDto profile = createProfile(rule); RuleActivation activation = RuleActivation.create(rule.getUuid()); - activate(profile, activation); + List<ActiveRuleChange> changes = activate(profile, activation); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(profile.getLanguage())); - List<ActiveRuleChange> changes = deactivate(profile, rule); + changes = deactivate(profile, rule); verifyNoActiveRules(); assertThatProfileIsUpdatedBySystem(profile); assertThatChangeIsDeactivation(changes, rule); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(profile.getLanguage())); } private void assertThatChangeIsDeactivation(List<ActiveRuleChange> changes, RuleDefinitionDto rule) { @@ -420,6 +459,7 @@ public class QProfileRuleImplTest { List<ActiveRuleChange> changes = deactivate(profile, rule); verifyNoActiveRules(); assertThat(changes).isEmpty(); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), any(), eq(profile.getLanguage())); } @Test @@ -427,14 +467,16 @@ public class QProfileRuleImplTest { RuleDefinitionDto rule = createRule(); QProfileDto profile = createProfile(rule); RuleActivation activation = RuleActivation.create(rule.getUuid()); - activate(profile, activation); + List<ActiveRuleChange> changes = activate(profile, activation); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(profile.getLanguage())); rule.setStatus(RuleStatus.REMOVED); db.getDbClient().ruleDao().update(db.getSession(), rule); - List<ActiveRuleChange> changes = deactivate(profile, rule); + changes = deactivate(profile, rule); verifyNoActiveRules(); assertThatChangeIsDeactivation(changes, rule); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(profile.getLanguage())); } @Test @@ -448,6 +490,7 @@ public class QProfileRuleImplTest { assertThatProfileHasNoActiveRules(parentProfile); assertThatRuleIsActivated(childProfile, rule, changes, rule.getSeverityString(), null, emptyMap()); assertThatRuleIsActivated(grandChildProfile, rule, changes, rule.getSeverityString(), INHERITED, emptyMap()); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), any(), eq(childProfile.getLanguage())); } @Test @@ -460,17 +503,20 @@ public class QProfileRuleImplTest { System.out.println("ACTIVATE ON " + childProfile.getName()); RuleActivation initialActivation = RuleActivation.create(rule.getUuid(), MAJOR, of(param.getName(), "foo")); - activate(childProfile, initialActivation); + List<ActiveRuleChange> changes = activate(childProfile, initialActivation); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(childProfile.getLanguage())); System.out.println("---------------"); System.out.println("ACTIVATE ON " + childProfile.getName()); RuleActivation updateActivation = RuleActivation.create(rule.getUuid(), CRITICAL, of(param.getName(), "bar")); - List<ActiveRuleChange> changes = activate(childProfile, updateActivation); + changes = activate(childProfile, updateActivation); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(childProfile.getLanguage())); assertThatProfileHasNoActiveRules(parentProfile); assertThatRuleIsUpdated(childProfile, rule, CRITICAL, null, of(param.getName(), "bar")); assertThatRuleIsUpdated(grandChildProfile, rule, CRITICAL, INHERITED, of(param.getName(), "bar")); assertThat(changes).hasSize(2); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(childProfile.getLanguage())); } @Test @@ -482,15 +528,17 @@ public class QProfileRuleImplTest { QProfileDto grandChildProfile = createChildProfile(childProfile); RuleActivation initialActivation = RuleActivation.create(rule.getUuid(), MAJOR, of(param.getName(), "foo")); - activate(childProfile, initialActivation); + List<ActiveRuleChange> changes = activate(childProfile, initialActivation); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(childProfile.getLanguage())); RuleActivation overrideActivation = RuleActivation.create(rule.getUuid(), CRITICAL, of(param.getName(), "bar")); - List<ActiveRuleChange> changes = activate(grandChildProfile, overrideActivation); + changes = activate(grandChildProfile, overrideActivation); assertThatProfileHasNoActiveRules(parentProfile); assertThatRuleIsUpdated(childProfile, rule, MAJOR, null, of(param.getName(), "foo")); assertThatRuleIsUpdated(grandChildProfile, rule, CRITICAL, ActiveRuleInheritance.OVERRIDES, of(param.getName(), "bar")); assertThat(changes).hasSize(1); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(childProfile.getLanguage())); } @Test @@ -502,19 +550,22 @@ public class QProfileRuleImplTest { QProfileDto grandChildProfile = createChildProfile(childProfile); RuleActivation initialActivation = RuleActivation.create(rule.getUuid(), MAJOR, of(param.getName(), "foo")); - activate(childProfile, initialActivation); + List<ActiveRuleChange> changes = activate(childProfile, initialActivation); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(childProfile.getLanguage())); RuleActivation overrideActivation = RuleActivation.create(rule.getUuid(), CRITICAL, of(param.getName(), "bar")); - activate(grandChildProfile, overrideActivation); + changes = activate(grandChildProfile, overrideActivation); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(grandChildProfile.getLanguage())); // update child --> do not touch grandChild RuleActivation updateActivation = RuleActivation.create(rule.getUuid(), BLOCKER, of(param.getName(), "baz")); - List<ActiveRuleChange> changes = activate(childProfile, updateActivation); + changes = activate(childProfile, updateActivation); assertThatProfileHasNoActiveRules(parentProfile); assertThatRuleIsUpdated(childProfile, rule, BLOCKER, null, of(param.getName(), "baz")); assertThatRuleIsUpdated(grandChildProfile, rule, CRITICAL, ActiveRuleInheritance.OVERRIDES, of(param.getName(), "bar")); assertThat(changes).hasSize(1); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(childProfile.getLanguage())); } @Test @@ -526,19 +577,22 @@ public class QProfileRuleImplTest { QProfileDto grandChildProfile = createChildProfile(childProfile); RuleActivation initialActivation = RuleActivation.create(rule.getUuid(), MAJOR, of(param.getName(), "foo")); - activate(parentProfile, initialActivation); + List<ActiveRuleChange> changes = activate(parentProfile, initialActivation); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(parentProfile.getLanguage())); RuleActivation overrideActivation = RuleActivation.create(rule.getUuid(), CRITICAL, of(param.getName(), "bar")); - activate(grandChildProfile, overrideActivation); + changes = activate(grandChildProfile, overrideActivation); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(grandChildProfile.getLanguage())); // reset parent --> touch child but not grandChild RuleActivation updateActivation = RuleActivation.createReset(rule.getUuid()); - List<ActiveRuleChange> changes = activate(parentProfile, updateActivation); + changes = activate(parentProfile, updateActivation); assertThatRuleIsUpdated(parentProfile, rule, rule.getSeverityString(), null, of(param.getName(), param.getDefaultValue())); assertThatRuleIsUpdated(childProfile, rule, rule.getSeverityString(), INHERITED, of(param.getName(), param.getDefaultValue())); assertThatRuleIsUpdated(grandChildProfile, rule, CRITICAL, ActiveRuleInheritance.OVERRIDES, of(param.getName(), "bar")); assertThat(changes).hasSize(2); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(parentProfile.getLanguage())); } @Test @@ -549,14 +603,16 @@ public class QProfileRuleImplTest { QProfileDto childProfile = createChildProfile(parentProfile); RuleActivation childActivation = RuleActivation.create(rule.getUuid(), MAJOR, of(param.getName(), "foo")); - activate(childProfile, childActivation); + List<ActiveRuleChange> changes = activate(childProfile, childActivation); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(childProfile.getLanguage())); RuleActivation parentActivation = RuleActivation.create(rule.getUuid(), CRITICAL, of(param.getName(), "bar")); - List<ActiveRuleChange> changes = activate(parentProfile, parentActivation); + changes = activate(parentProfile, parentActivation); assertThatRuleIsUpdated(parentProfile, rule, CRITICAL, null, of(param.getName(), "bar")); assertThatRuleIsUpdated(childProfile, rule, MAJOR, ActiveRuleInheritance.OVERRIDES, of(param.getName(), "foo")); assertThat(changes).hasSize(2); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(parentProfile.getLanguage())); } @Test @@ -567,13 +623,15 @@ public class QProfileRuleImplTest { QProfileDto childProfile = createChildProfile(parentProfile); RuleActivation parentActivation = RuleActivation.create(rule.getUuid(), MAJOR, of(param.getName(), "foo")); - activate(parentProfile, parentActivation); + List<ActiveRuleChange> changes = activate(parentProfile, parentActivation); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(parentProfile.getLanguage())); RuleActivation overrideActivation = RuleActivation.create(rule.getUuid(), MAJOR, of(param.getName(), "foo")); - List<ActiveRuleChange> changes = activate(childProfile, overrideActivation); + changes = activate(childProfile, overrideActivation); assertThatRuleIsUpdated(childProfile, rule, MAJOR, INHERITED, of(param.getName(), "foo")); assertThat(changes).isEmpty(); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(childProfile.getLanguage())); } @Test @@ -586,11 +644,13 @@ public class QProfileRuleImplTest { List<ActiveRuleChange> changes = activate(parentProfile, activation); assertThatRuleIsActivated(parentProfile, rule, changes, rule.getSeverityString(), null, emptyMap()); assertThatRuleIsActivated(childProfile, rule, changes, rule.getSeverityString(), INHERITED, emptyMap()); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(parentProfile.getLanguage())); changes = deactivate(parentProfile, rule); assertThatProfileHasNoActiveRules(parentProfile); assertThatProfileHasNoActiveRules(childProfile); assertThat(changes).hasSize(2); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(parentProfile.getLanguage())); } @Test @@ -603,14 +663,17 @@ public class QProfileRuleImplTest { List<ActiveRuleChange> changes = activate(parentProfile, activation); assertThatRuleIsActivated(parentProfile, rule, changes, rule.getSeverityString(), null, emptyMap()); assertThatRuleIsActivated(childProfile, rule, changes, rule.getSeverityString(), INHERITED, emptyMap()); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(parentProfile.getLanguage())); activation = RuleActivation.create(rule.getUuid(), CRITICAL, null); - activate(childProfile, activation); + changes = activate(childProfile, activation); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(childProfile.getLanguage())); changes = deactivate(parentProfile, rule); assertThatProfileHasNoActiveRules(parentProfile); assertThatProfileHasNoActiveRules(childProfile); assertThat(changes).hasSize(2); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(parentProfile.getLanguage())); } @Test @@ -627,6 +690,7 @@ public class QProfileRuleImplTest { assertThatThrownBy(() -> deactivate(childProfile, rule)) .isInstanceOf(BadRequestException.class) .hasMessageContaining("Cannot deactivate inherited rule"); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), any(), eq(parentProfile.getLanguage())); } @Test @@ -651,6 +715,7 @@ public class QProfileRuleImplTest { assertThatRuleIsUpdated(childProfile, rule, CRITICAL, INHERITED, emptyMap()); assertThatRuleIsUpdated(parentProfile, rule, CRITICAL, null, emptyMap()); assertThat(changes).hasSize(1); + } @Test diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/QProfileRulesImplTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/QProfileRulesImplTest.java index 7d08990ec71..1384f1d6705 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/QProfileRulesImplTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/QProfileRulesImplTest.java @@ -43,6 +43,10 @@ import static java.util.Collections.singleton; import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.tuple; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; public class QProfileRulesImplTest { @@ -55,9 +59,11 @@ public class QProfileRulesImplTest { private RuleIndex ruleIndex = new RuleIndex(es.client(), System2.INSTANCE); private ActiveRuleIndexer activeRuleIndexer = new ActiveRuleIndexer(db.getDbClient(), es.client()); - private RuleActivator ruleActivator = new RuleActivator(System2.INSTANCE, db.getDbClient(), new TypeValidations(singletonList(new IntegerTypeValidation())), userSession); + private RuleActivator ruleActivator = new RuleActivator(System2.INSTANCE, db.getDbClient(), new TypeValidations(singletonList(new IntegerTypeValidation())), + userSession); + private QualityProfileChangeEventService qualityProfileChangeEventService = mock(QualityProfileChangeEventService.class); - private QProfileRules qProfileRules = new QProfileRulesImpl(db.getDbClient(), ruleActivator, ruleIndex, activeRuleIndexer); + private QProfileRules qProfileRules = new QProfileRulesImpl(db.getDbClient(), ruleActivator, ruleIndex, activeRuleIndexer, qualityProfileChangeEventService); @Test public void activate_one_rule() { @@ -70,6 +76,7 @@ public class QProfileRulesImplTest { assertThat(db.getDbClient().activeRuleDao().selectByProfile(db.getSession(), qProfile)) .extracting(ActiveRuleDto::getRuleKey, ActiveRuleDto::getSeverityString) .containsExactlyInAnyOrder(tuple(rule.getKey(), Severity.CRITICAL)); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), any(), eq(qProfile.getLanguage())); } @Test @@ -85,5 +92,6 @@ public class QProfileRulesImplTest { assertThat(db.getDbClient().qProfileChangeDao().selectByQuery(db.getSession(), new QProfileChangeQuery(qProfile.getKee()))) .extracting(QProfileChangeDto::getUserUuid, QProfileChangeDto::getDataAsMap) .containsExactlyInAnyOrder(tuple(user.getUuid(), ImmutableMap.of("ruleUuid", rule.getUuid(), "severity", Severity.CRITICAL))); + verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), any(), eq(qProfile.getLanguage())); } } diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/QProfileTreeImplTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/QProfileTreeImplTest.java index 513c1def2e4..ad5d09abcef 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/QProfileTreeImplTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/QProfileTreeImplTest.java @@ -47,6 +47,12 @@ import static java.util.Collections.emptyMap; import static java.util.Collections.singleton; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.sonar.api.rule.Severity.BLOCKER; import static org.sonar.server.qualityprofile.ActiveRuleInheritance.INHERITED; @@ -61,9 +67,10 @@ public class QProfileTreeImplTest { public UserSessionRule userSession = UserSessionRule.standalone(); private ActiveRuleIndexer activeRuleIndexer = new ActiveRuleIndexer(db.getDbClient(), es.client()); private TypeValidations typeValidations = new TypeValidations(asList(new StringTypeValidation(), new IntegerTypeValidation())); + private QualityProfileChangeEventService qualityProfileChangeEventService = mock(QualityProfileChangeEventService.class); private RuleActivator ruleActivator = new RuleActivator(system2, db.getDbClient(), typeValidations, userSession); - private QProfileRules qProfileRules = new QProfileRulesImpl(db.getDbClient(), ruleActivator, null, activeRuleIndexer); - private QProfileTree underTest = new QProfileTreeImpl(db.getDbClient(), ruleActivator, System2.INSTANCE, activeRuleIndexer); + private QProfileRules qProfileRules = new QProfileRulesImpl(db.getDbClient(), ruleActivator, null, activeRuleIndexer, qualityProfileChangeEventService); + private QProfileTree underTest = new QProfileTreeImpl(db.getDbClient(), ruleActivator, System2.INSTANCE, activeRuleIndexer, mock(QualityProfileChangeEventService.class)); @Test public void set_itself_as_parent_fails() { @@ -133,11 +140,13 @@ public class QProfileTreeImplTest { assertThat(changes).hasSize(1); assertThatRuleIsActivated(profile2, rule1, changes, rule1.getSeverityString(), INHERITED, emptyMap()); assertThatRuleIsActivated(profile2, rule2, null, rule2.getSeverityString(), null, emptyMap()); + verify(qualityProfileChangeEventService, times(2)).distributeRuleChangeEvent(any(), any(), eq(profile2.getLanguage())); changes = underTest.removeParentAndCommit(db.getSession(), profile2); assertThat(changes).hasSize(1); assertThatRuleIsActivated(profile2, rule2, null, rule2.getSeverityString(), null, emptyMap()); assertThatRuleIsNotPresent(profile2, rule1); + verify(qualityProfileChangeEventService, times(2)).distributeRuleChangeEvent(any(), any(), eq(profile2.getLanguage())); } @Test @@ -156,6 +165,7 @@ public class QProfileTreeImplTest { assertThat(changes).hasSize(1); assertThatRuleIsActivated(profile2, rule1, changes, rule1.getSeverityString(), INHERITED, emptyMap()); assertThatRuleIsActivated(profile2, rule2, null, rule2.getSeverityString(), null, emptyMap()); + verify(qualityProfileChangeEventService, times(2)).distributeRuleChangeEvent(any(), any(), eq(profile2.getLanguage())); RuleActivation activation = RuleActivation.create(rule1.getUuid(), BLOCKER, null); changes = activate(profile2, activation); @@ -168,6 +178,7 @@ public class QProfileTreeImplTest { // Not testing changes here since severity is not set in changelog assertThatRuleIsActivated(profile2, rule1, null, BLOCKER, null, emptyMap()); assertThatRuleIsActivated(profile2, rule2, null, rule2.getSeverityString(), null, emptyMap()); + verify(qualityProfileChangeEventService, times(3)).distributeRuleChangeEvent(anyList(), any(), eq(profile2.getLanguage())); } @Test @@ -183,6 +194,7 @@ public class QProfileTreeImplTest { QProfileDto childProfile = createProfile(rule1); List<ActiveRuleChange> changes = underTest.setParentAndCommit(db.getSession(), childProfile, parentProfile); + verify(qualityProfileChangeEventService, times(2)).distributeRuleChangeEvent(any(), any(), eq(childProfile.getLanguage())); assertThatRuleIsNotPresent(childProfile, rule1); assertThatRuleIsActivated(childProfile, rule2, changes, rule2.getSeverityString(), INHERITED, emptyMap()); diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/RegisterQualityProfilesNotificationTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/RegisterQualityProfilesNotificationTest.java index cf3d32c7810..5a49e3e5b79 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/RegisterQualityProfilesNotificationTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/RegisterQualityProfilesNotificationTest.java @@ -90,12 +90,13 @@ public class RegisterQualityProfilesNotificationTest { private DbClient dbClient = db.getDbClient(); private TypeValidations typeValidations = mock(TypeValidations.class); private ActiveRuleIndexer activeRuleIndexer = mock(ActiveRuleIndexer.class); + private QualityProfileChangeEventService qualityProfileChangeEventService = mock(QualityProfileChangeEventService.class); private ServerRuleFinder ruleFinder = new DefaultRuleFinder(dbClient); private BuiltInQProfileInsert builtInQProfileInsert = new BuiltInQProfileInsertImpl(dbClient, ruleFinder, system2, UuidFactoryFast.getInstance(), typeValidations, activeRuleIndexer); private RuleActivator ruleActivator = new RuleActivator(system2, dbClient, typeValidations, userSessionRule); - private QProfileRules qProfileRules = new QProfileRulesImpl(dbClient, ruleActivator, mock(RuleIndex.class), activeRuleIndexer); - private BuiltInQProfileUpdate builtInQProfileUpdate = new BuiltInQProfileUpdateImpl(dbClient, ruleActivator, activeRuleIndexer); + private QProfileRules qProfileRules = new QProfileRulesImpl(dbClient, ruleActivator, mock(RuleIndex.class), activeRuleIndexer, qualityProfileChangeEventService); + private BuiltInQProfileUpdate builtInQProfileUpdate = new BuiltInQProfileUpdateImpl(dbClient, ruleActivator, activeRuleIndexer, qualityProfileChangeEventService); private BuiltInQualityProfilesUpdateListener builtInQualityProfilesNotification = mock(BuiltInQualityProfilesUpdateListener.class); private RegisterQualityProfiles underTest = new RegisterQualityProfiles(builtInQProfileRepositoryRule, dbClient, builtInQProfileInsert, builtInQProfileUpdate, builtInQualityProfilesNotification, system2); diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/ws/AddProjectActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/ws/AddProjectActionTest.java index 010ee2997d0..ffed401c241 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/ws/AddProjectActionTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/ws/AddProjectActionTest.java @@ -36,14 +36,19 @@ import org.sonar.server.exceptions.ForbiddenException; import org.sonar.server.exceptions.NotFoundException; import org.sonar.server.exceptions.UnauthorizedException; import org.sonar.server.language.LanguageTesting; +import org.sonar.server.qualityprofile.QualityProfileChangeEventService; import org.sonar.server.tester.UserSessionRule; import org.sonar.server.ws.TestRequest; import org.sonar.server.ws.TestResponse; import org.sonar.server.ws.WsActionTester; import static java.lang.String.format; +import static java.util.Optional.empty; +import static java.util.Optional.of; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.sonar.db.permission.GlobalPermission.ADMINISTER_QUALITY_PROFILES; public class AddProjectActionTest { @@ -59,7 +64,8 @@ public class AddProjectActionTest { private final DbClient dbClient = db.getDbClient(); private final Languages languages = LanguageTesting.newLanguages(LANGUAGE_1, LANGUAGE_2); private final QProfileWsSupport wsSupport = new QProfileWsSupport(dbClient, userSession); - private final AddProjectAction underTest = new AddProjectAction(dbClient, userSession, languages, TestComponentFinder.from(db), wsSupport); + private QualityProfileChangeEventService qualityProfileChangeEventService = mock(QualityProfileChangeEventService.class); + private final AddProjectAction underTest = new AddProjectAction(dbClient, userSession, languages, TestComponentFinder.from(db), wsSupport, qualityProfileChangeEventService); private final WsActionTester tester = new WsActionTester(underTest); @Test @@ -88,6 +94,7 @@ public class AddProjectActionTest { assertThat(response.getStatus()).isEqualTo(HttpURLConnection.HTTP_NO_CONTENT); assertProjectIsAssociatedToProfile(project, profile); + verify(qualityProfileChangeEventService).publishRuleActivationToSonarLintClients(project, of(profile), empty()); } @Test @@ -101,6 +108,7 @@ public class AddProjectActionTest { call(project, qualityProfile); assertProjectIsAssociatedToProfile(project, qualityProfile); + verify(qualityProfileChangeEventService).publishRuleActivationToSonarLintClients(project, of(qualityProfile), empty()); } @Test @@ -117,6 +125,7 @@ public class AddProjectActionTest { assertProjectIsNotAssociatedToProfile(project, profile1); assertProjectIsAssociatedToProfile(project, profile2); + verify(qualityProfileChangeEventService).publishRuleActivationToSonarLintClients(project, of(profile2), of(profile1)); } @Test @@ -132,6 +141,7 @@ public class AddProjectActionTest { assertProjectIsAssociatedToProfile(project, profile3Language1); assertProjectIsAssociatedToProfile(project, profile2Language2); + verify(qualityProfileChangeEventService).publishRuleActivationToSonarLintClients(project, of(profile3Language1), of(profile1Language1)); } @Test @@ -143,6 +153,7 @@ public class AddProjectActionTest { call(project, profile); assertProjectIsAssociatedToProfile(project, profile); + verify(qualityProfileChangeEventService).publishRuleActivationToSonarLintClients(project, of(profile), empty()); } @Test diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/ws/ChangeParentActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/ws/ChangeParentActionTest.java index 7caf79e84ea..f2c2046719d 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/ws/ChangeParentActionTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/ws/ChangeParentActionTest.java @@ -49,6 +49,7 @@ import org.sonar.server.exceptions.BadRequestException; import org.sonar.server.exceptions.ForbiddenException; import org.sonar.server.language.LanguageTesting; import org.sonar.server.qualityprofile.QProfileTreeImpl; +import org.sonar.server.qualityprofile.QualityProfileChangeEventService; import org.sonar.server.qualityprofile.RuleActivator; import org.sonar.server.qualityprofile.index.ActiveRuleIndexer; import org.sonar.server.rule.index.RuleIndex; @@ -64,6 +65,7 @@ import static java.util.Arrays.asList; import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; import static org.sonar.db.permission.GlobalPermission.ADMINISTER_QUALITY_PROFILES; import static org.sonarqube.ws.client.qualityprofile.QualityProfileWsParameters.PARAM_LANGUAGE; import static org.sonarqube.ws.client.qualityprofile.QualityProfileWsParameters.PARAM_PARENT_QUALITY_PROFILE; @@ -98,7 +100,7 @@ public class ChangeParentActionTest { activeRuleIndexer = new ActiveRuleIndexer(dbClient, esClient); TypeValidations typeValidations = new TypeValidations(Collections.emptyList()); RuleActivator ruleActivator = new RuleActivator(System2.INSTANCE, dbClient, typeValidations, userSession); - qProfileTree = new QProfileTreeImpl(dbClient, ruleActivator, System2.INSTANCE, activeRuleIndexer); + qProfileTree = new QProfileTreeImpl(dbClient, ruleActivator, System2.INSTANCE, activeRuleIndexer, mock(QualityProfileChangeEventService.class)); ChangeParentAction underTest = new ChangeParentAction( dbClient, qProfileTree, diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/ws/CreateActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/ws/CreateActionTest.java index d71fe8bc706..51800d9ed99 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/ws/CreateActionTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/ws/CreateActionTest.java @@ -46,6 +46,7 @@ import org.sonar.server.qualityprofile.QProfileExporters; import org.sonar.server.qualityprofile.QProfileFactoryImpl; import org.sonar.server.qualityprofile.QProfileRules; import org.sonar.server.qualityprofile.QProfileRulesImpl; +import org.sonar.server.qualityprofile.QualityProfileChangeEventService; import org.sonar.server.qualityprofile.RuleActivator; import org.sonar.server.qualityprofile.index.ActiveRuleIndexer; import org.sonar.server.rule.index.RuleIndex; @@ -62,6 +63,7 @@ import org.sonarqube.ws.Qualityprofiles.CreateWsResponse.QualityProfile; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; import static org.sonar.db.permission.GlobalPermission.ADMINISTER_QUALITY_PROFILES; import static org.sonar.db.permission.GlobalPermission.SCAN; import static org.sonar.server.language.LanguageTesting.newLanguages; @@ -87,8 +89,9 @@ public class CreateActionTest { private RuleIndexer ruleIndexer = new RuleIndexer(es.client(), dbClient); private ActiveRuleIndexer activeRuleIndexer = new ActiveRuleIndexer(dbClient, es.client()); private ProfileImporter[] profileImporters = createImporters(); + private QualityProfileChangeEventService qualityProfileChangeEventService = mock(QualityProfileChangeEventService.class); private RuleActivator ruleActivator = new RuleActivator(System2.INSTANCE, dbClient, null, userSession); - private QProfileRules qProfileRules = new QProfileRulesImpl(dbClient, ruleActivator, ruleIndex, activeRuleIndexer); + private QProfileRules qProfileRules = new QProfileRulesImpl(dbClient, ruleActivator, ruleIndex, activeRuleIndexer, qualityProfileChangeEventService); private QProfileExporters qProfileExporters = new QProfileExporters(dbClient, null, qProfileRules, profileImporters); private CreateAction underTest = new CreateAction(dbClient, new QProfileFactoryImpl(dbClient, UuidFactoryFast.getInstance(), System2.INSTANCE, activeRuleIndexer), diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/ws/InheritanceActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/ws/InheritanceActionTest.java index ff77547505b..8204ffa7563 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/ws/InheritanceActionTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/ws/InheritanceActionTest.java @@ -45,6 +45,7 @@ import org.sonar.server.qualityprofile.QProfileRules; import org.sonar.server.qualityprofile.QProfileRulesImpl; import org.sonar.server.qualityprofile.QProfileTree; import org.sonar.server.qualityprofile.QProfileTreeImpl; +import org.sonar.server.qualityprofile.QualityProfileChangeEventService; import org.sonar.server.qualityprofile.RuleActivation; import org.sonar.server.qualityprofile.RuleActivator; import org.sonar.server.qualityprofile.index.ActiveRuleIndexer; @@ -59,6 +60,7 @@ import static java.util.Arrays.asList; import static java.util.Collections.singleton; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; import static org.sonar.test.JsonAssert.assertJson; import static org.sonarqube.ws.MediaTypes.PROTOBUF; import static org.sonarqube.ws.client.qualityprofile.QualityProfileWsParameters.PARAM_LANGUAGE; @@ -80,9 +82,10 @@ public class InheritanceActionTest { private ActiveRuleIndexer activeRuleIndexer = new ActiveRuleIndexer(dbClient, esClient); private RuleIndex ruleIndex = new RuleIndex(esClient, System2.INSTANCE); + private QualityProfileChangeEventService qualityProfileChangeEventService = mock(QualityProfileChangeEventService.class); private RuleActivator ruleActivator = new RuleActivator(System2.INSTANCE, dbClient, new TypeValidations(new ArrayList<>()), userSession); - private QProfileRules qProfileRules = new QProfileRulesImpl(dbClient, ruleActivator, ruleIndex, activeRuleIndexer); - private QProfileTree qProfileTree = new QProfileTreeImpl(dbClient, ruleActivator, System2.INSTANCE, activeRuleIndexer); + private QProfileRules qProfileRules = new QProfileRulesImpl(dbClient, ruleActivator, ruleIndex, activeRuleIndexer, qualityProfileChangeEventService); + private QProfileTree qProfileTree = new QProfileTreeImpl(dbClient, ruleActivator, System2.INSTANCE, activeRuleIndexer, mock(QualityProfileChangeEventService.class)); private WsActionTester ws = new WsActionTester(new InheritanceAction( dbClient, diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/ws/QProfilesWsMediumTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/ws/QProfilesWsMediumTest.java index 779ec6cbc8a..fc1a2256dc7 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/ws/QProfilesWsMediumTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/ws/QProfilesWsMediumTest.java @@ -43,6 +43,7 @@ import org.sonar.server.es.SearchOptions; import org.sonar.server.exceptions.BadRequestException; import org.sonar.server.qualityprofile.QProfileRules; import org.sonar.server.qualityprofile.QProfileRulesImpl; +import org.sonar.server.qualityprofile.QualityProfileChangeEventService; import org.sonar.server.qualityprofile.RuleActivator; import org.sonar.server.qualityprofile.index.ActiveRuleIndexer; import org.sonar.server.rule.index.RuleIndex; @@ -56,6 +57,7 @@ import org.sonar.server.ws.WsActionTester; import static java.util.Collections.emptyList; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_LANGUAGES; import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_QPROFILE; import static org.sonarqube.ws.client.qualityprofile.QualityProfileWsParameters.PARAM_KEY; @@ -81,8 +83,9 @@ public class QProfilesWsMediumTest { private final RuleIndexer ruleIndexer = new RuleIndexer(es.client(), dbClient); private final ActiveRuleIndexer activeRuleIndexer = new ActiveRuleIndexer(dbClient, es.client()); private final TypeValidations typeValidations = new TypeValidations(emptyList()); + private final QualityProfileChangeEventService qualityProfileChangeEventService = mock(QualityProfileChangeEventService.class); private final RuleActivator ruleActivator = new RuleActivator(System2.INSTANCE, dbClient, typeValidations, userSessionRule); - private final QProfileRules qProfileRules = new QProfileRulesImpl(dbClient, ruleActivator, ruleIndex, activeRuleIndexer); + private final QProfileRules qProfileRules = new QProfileRulesImpl(dbClient, ruleActivator, ruleIndex, activeRuleIndexer, qualityProfileChangeEventService); private final QProfileWsSupport qProfileWsSupport = new QProfileWsSupport(dbClient, userSessionRule); private final RuleQueryFactory ruleQueryFactory = new RuleQueryFactory(dbClient); diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/ws/RemoveProjectActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/ws/RemoveProjectActionTest.java index 2ace97fb69e..48d36e29b85 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/ws/RemoveProjectActionTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/ws/RemoveProjectActionTest.java @@ -22,6 +22,7 @@ package org.sonar.server.qualityprofile.ws; import java.net.HttpURLConnection; import org.junit.Rule; import org.junit.Test; +import org.mockito.Mockito; import org.sonar.api.resources.Languages; import org.sonar.api.resources.Qualifiers; import org.sonar.api.server.ws.WebService; @@ -38,14 +39,18 @@ import org.sonar.server.exceptions.ForbiddenException; import org.sonar.server.exceptions.NotFoundException; import org.sonar.server.exceptions.UnauthorizedException; import org.sonar.server.language.LanguageTesting; +import org.sonar.server.qualityprofile.QualityProfileChangeEventService; import org.sonar.server.tester.UserSessionRule; import org.sonar.server.ws.TestRequest; import org.sonar.server.ws.TestResponse; import org.sonar.server.ws.WsActionTester; import static java.lang.String.format; +import static java.util.Optional.empty; +import static java.util.Optional.of; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.verify; import static org.sonar.db.permission.GlobalPermission.ADMINISTER_QUALITY_PROFILES; public class RemoveProjectActionTest { @@ -61,8 +66,9 @@ public class RemoveProjectActionTest { private final Languages languages = LanguageTesting.newLanguages(LANGUAGE_1, LANGUAGE_2); private final QProfileWsSupport wsSupport = new QProfileWsSupport(dbClient, userSession); + private final QualityProfileChangeEventService qualityProfileChangeEventService = Mockito.mock(QualityProfileChangeEventService.class); private final RemoveProjectAction underTest = new RemoveProjectAction(dbClient, userSession, languages, - new ComponentFinder(dbClient, new ResourceTypesRule().setRootQualifiers(Qualifiers.PROJECT)), wsSupport); + new ComponentFinder(dbClient, new ResourceTypesRule().setRootQualifiers(Qualifiers.PROJECT)), wsSupport, qualityProfileChangeEventService); private final WsActionTester ws = new WsActionTester(underTest); @Test @@ -97,6 +103,7 @@ public class RemoveProjectActionTest { assertProjectIsNotAssociatedToProfile(project, profileLang1); assertProjectIsAssociatedToProfile(project, profileLang2); + verify(qualityProfileChangeEventService).publishRuleActivationToSonarLintClients(project, empty(), of(profileLang1)); } @Test @@ -110,6 +117,7 @@ public class RemoveProjectActionTest { assertThat(response.getStatus()).isEqualTo(HttpURLConnection.HTTP_NO_CONTENT); assertProjectIsNotAssociatedToProfile(project, profile); + verify(qualityProfileChangeEventService).publishRuleActivationToSonarLintClients(project, empty(), of(profile)); } @Test @@ -122,6 +130,7 @@ public class RemoveProjectActionTest { call(project, profile); assertProjectIsNotAssociatedToProfile(project, profile); + verify(qualityProfileChangeEventService).publishRuleActivationToSonarLintClients(project, empty(), of(profile)); } @Test @@ -136,6 +145,7 @@ public class RemoveProjectActionTest { call(project, profile); assertProjectIsNotAssociatedToProfile(project, profile); + verify(qualityProfileChangeEventService).publishRuleActivationToSonarLintClients(project, empty(), of(profile)); } @Test diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/rule/ws/SearchActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/rule/ws/SearchActionTest.java index 61c46293d17..d0026ee115f 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/rule/ws/SearchActionTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/rule/ws/SearchActionTest.java @@ -48,6 +48,7 @@ import org.sonar.server.language.LanguageTesting; import org.sonar.server.qualityprofile.ActiveRuleChange; import org.sonar.server.qualityprofile.QProfileRules; import org.sonar.server.qualityprofile.QProfileRulesImpl; +import org.sonar.server.qualityprofile.QualityProfileChangeEventService; import org.sonar.server.qualityprofile.RuleActivation; import org.sonar.server.qualityprofile.RuleActivator; import org.sonar.server.qualityprofile.index.ActiveRuleIndexer; @@ -105,12 +106,14 @@ public class SearchActionTest { private final ActiveRuleCompleter activeRuleCompleter = new ActiveRuleCompleter(db.getDbClient(), languages); private final RuleQueryFactory ruleQueryFactory = new RuleQueryFactory(db.getDbClient()); private final MacroInterpreter macroInterpreter = mock(MacroInterpreter.class); + private final QualityProfileChangeEventService qualityProfileChangeEventService = mock(QualityProfileChangeEventService.class); private final RuleMapper ruleMapper = new RuleMapper(languages, macroInterpreter); private final SearchAction underTest = new SearchAction(ruleIndex, activeRuleCompleter, ruleQueryFactory, db.getDbClient(), ruleMapper, new RuleWsSupport(db.getDbClient(), userSession)); private final TypeValidations typeValidations = new TypeValidations(asList(new StringTypeValidation(), new IntegerTypeValidation())); private final RuleActivator ruleActivator = new RuleActivator(System2.INSTANCE, db.getDbClient(), typeValidations, userSession); - private final QProfileRules qProfileRules = new QProfileRulesImpl(db.getDbClient(), ruleActivator, ruleIndex, activeRuleIndexer); + private final QProfileRules qProfileRules = new QProfileRulesImpl(db.getDbClient(), ruleActivator, ruleIndex, activeRuleIndexer, + qualityProfileChangeEventService); private final WsActionTester ws = new WsActionTester(underTest); @Before diff --git a/server/sonar-webserver-ws/src/main/java/org/sonar/server/ws/ServletRequest.java b/server/sonar-webserver-ws/src/main/java/org/sonar/server/ws/ServletRequest.java index cfc18bb2969..432f32b605c 100644 --- a/server/sonar-webserver-ws/src/main/java/org/sonar/server/ws/ServletRequest.java +++ b/server/sonar-webserver-ws/src/main/java/org/sonar/server/ws/ServletRequest.java @@ -69,6 +69,10 @@ public class ServletRequest extends ValidatingRequest { MediaTypes.DEFAULT)); } + public HttpServletRequest getHttpRequest() { + return source; + } + @Override public BufferedReader getReader() { try { diff --git a/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java b/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java index f5c1bde9d9b..c4504d656c9 100644 --- a/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java +++ b/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java @@ -189,6 +189,7 @@ import org.sonar.server.qualitygate.ws.QualityGateWsModule; import org.sonar.server.qualityprofile.BuiltInQPChangeNotificationHandler; import org.sonar.server.qualityprofile.BuiltInQPChangeNotificationTemplate; import org.sonar.server.qualityprofile.BuiltInQProfileRepositoryImpl; +import org.sonar.server.qualityprofile.DistributedRuleActivatorEventsDistributor; import org.sonar.server.qualityprofile.QProfileBackuperImpl; import org.sonar.server.qualityprofile.QProfileComparison; import org.sonar.server.qualityprofile.QProfileCopier; @@ -198,7 +199,9 @@ import org.sonar.server.qualityprofile.QProfileParser; import org.sonar.server.qualityprofile.QProfileResetImpl; import org.sonar.server.qualityprofile.QProfileRulesImpl; import org.sonar.server.qualityprofile.QProfileTreeImpl; +import org.sonar.server.qualityprofile.QualityProfileChangeEventServiceImpl; import org.sonar.server.qualityprofile.RuleActivator; +import org.sonar.server.qualityprofile.StandaloneRuleActivatorEventsDistributor; import org.sonar.server.qualityprofile.index.ActiveRuleIndexer; import org.sonar.server.qualityprofile.ws.QProfilesWsModule; import org.sonar.server.root.ws.RootWsModule; @@ -274,6 +277,9 @@ public class PlatformLevel4 extends PlatformLevel { addIfCluster(NodeHealthModule.class); + addIfCluster(DistributedRuleActivatorEventsDistributor.class); + addIfStandalone(StandaloneRuleActivatorEventsDistributor.class); + add( ClusterVerification.class, LogServerId.class, @@ -304,6 +310,7 @@ public class PlatformLevel4 extends PlatformLevel { QProfileTreeImpl.class, QProfileRulesImpl.class, RuleActivator.class, + QualityProfileChangeEventServiceImpl.class, QProfileExporters.class, QProfileFactoryImpl.class, QProfileCopier.class, diff --git a/sonar-core/src/main/java/org/sonar/core/util/ParamChange.java b/sonar-core/src/main/java/org/sonar/core/util/ParamChange.java new file mode 100644 index 00000000000..cff86851837 --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/util/ParamChange.java @@ -0,0 +1,38 @@ +/* + * 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; + +public class ParamChange { + String key; + String value; + + public ParamChange(String key, String value) { + this.key = key; + this.value = value; + } + + public String getKey() { + return key; + } + + public String getValue() { + return value; + } +} diff --git a/sonar-core/src/main/java/org/sonar/core/util/RuleActivationListener.java b/sonar-core/src/main/java/org/sonar/core/util/RuleActivationListener.java new file mode 100644 index 00000000000..d234d200d71 --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/util/RuleActivationListener.java @@ -0,0 +1,25 @@ +/* + * 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; + +public interface RuleActivationListener { + + void listen(RuleSetChangeEvent event); +}
\ No newline at end of file diff --git a/sonar-core/src/main/java/org/sonar/core/util/RuleChange.java b/sonar-core/src/main/java/org/sonar/core/util/RuleChange.java new file mode 100644 index 00000000000..016a08ec488 --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/util/RuleChange.java @@ -0,0 +1,68 @@ +/* + * 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; + +public class RuleChange { + String key; + String language; + String templateKey; + String severity; + ParamChange[] params = new ParamChange[0]; + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public String getLanguage() { + return language; + } + + public void setLanguage(String language) { + this.language = language; + } + + public String getTemplateKey() { + return templateKey; + } + + public void setTemplateKey(String templateKey) { + this.templateKey = templateKey; + } + + public String getSeverity() { + return severity; + } + + public void setSeverity(String severity) { + this.severity = severity; + } + + public ParamChange[] getParams() { + return params; + } + + public void setParams(ParamChange[] params) { + this.params = params; + } +} diff --git a/sonar-core/src/main/java/org/sonar/core/util/RuleSetChangeEvent.java b/sonar-core/src/main/java/org/sonar/core/util/RuleSetChangeEvent.java new file mode 100644 index 00000000000..2a7d4873836 --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/util/RuleSetChangeEvent.java @@ -0,0 +1,65 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.core.util; + +import java.io.Serializable; + +public class RuleSetChangeEvent implements Serializable { + + private final String event = "RuleSetChange"; + + private String[] projects; + private RuleChange[] activatedRules; + private RuleChange[] deactivatedRules; + + public RuleSetChangeEvent(String[] projects, RuleChange[] activatedRules, RuleChange[] deactivatedRules) { + this.projects = projects; + this.activatedRules = activatedRules; + this.deactivatedRules = deactivatedRules; + } + + public void setProjects(String[] projects) { + this.projects = projects; + } + + public void setActivatedRules(RuleChange[] activatedRules) { + this.activatedRules = activatedRules; + } + + public void setDeactivatedRules(RuleChange[] deactivatedRules) { + this.deactivatedRules = deactivatedRules; + } + + public String getEvent() { + return event; + } + + public String[] getProjects() { + return projects; + } + + public RuleChange[] getActivatedRules() { + return activatedRules; + } + + public RuleChange[] getDeactivatedRules() { + return deactivatedRules; + } +} |