From e16ae19153ea4c041b5208dbbde94e56c01ae1be Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Lievremont Date: Tue, 16 Dec 2014 15:17:53 +0100 Subject: [PATCH] SONAR-5901 Add Java implementation of bulk tag removal --- .../sonar/server/issue/RemoveTagsAction.java | 79 ++++++++ .../server/platform/ServerComponents.java | 190 ++++++++++++++++-- .../server/issue/RemoveTagsActionTest.java | 91 +++++++++ 3 files changed, 338 insertions(+), 22 deletions(-) create mode 100644 server/sonar-server/src/main/java/org/sonar/server/issue/RemoveTagsAction.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/issue/RemoveTagsActionTest.java diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/RemoveTagsAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/RemoveTagsAction.java new file mode 100644 index 00000000000..b8d0f635624 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/RemoveTagsAction.java @@ -0,0 +1,79 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.issue; + +import com.google.common.base.Splitter; +import com.google.common.base.Strings; +import com.google.common.collect.Sets; +import org.apache.commons.collections.CollectionUtils; +import org.sonar.api.ServerComponent; +import org.sonar.api.issue.Issue; +import org.sonar.api.issue.condition.IsUnResolved; +import org.sonar.api.issue.internal.DefaultIssue; +import org.sonar.api.server.rule.RuleTagFormat; +import org.sonar.core.issue.IssueUpdater; +import org.sonar.server.user.UserSession; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + + +public class RemoveTagsAction extends Action implements ServerComponent { + + public static final String KEY = "remove_tags"; + + private static final Splitter TAGS_SPLITTER = Splitter.on(',').trimResults().omitEmptyStrings(); + + private final IssueUpdater issueUpdater; + + public RemoveTagsAction(IssueUpdater issueUpdater) { + super(KEY); + this.issueUpdater = issueUpdater; + super.setConditions(new IsUnResolved()); + } + + @Override + public boolean verify(Map properties, List issues, UserSession userSession){ + parseTags(properties); + return true; + } + + @Override + @SuppressWarnings("unchecked") + public boolean execute(Map properties, Context context) { + Collection tags = CollectionUtils.subtract(context.issue().tags(), parseTags(properties)); + return issueUpdater.setTags((DefaultIssue) context.issue(), tags, context.issueChangeContext()); + } + + private Set parseTags(Map properties) { + Set result = Sets.newHashSet(); + String tagsString = (String) properties.get("tags"); + if (!Strings.isNullOrEmpty(tagsString)) { + for(String tag: TAGS_SPLITTER.split(tagsString)) { + RuleTagFormat.validate(tag); + result.add(tag); + } + } + return result; + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java index 589a54a6232..306c3a007d1 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java @@ -19,8 +19,6 @@ */ package org.sonar.server.platform; -import org.sonar.server.issue.AddTagsAction; - import com.google.common.collect.Lists; import org.sonar.api.config.EmailSettings; import org.sonar.api.issue.action.Actions; @@ -56,7 +54,13 @@ import org.sonar.core.measure.db.MeasureFilterDao; import org.sonar.core.metric.DefaultMetricFinder; import org.sonar.core.notification.DefaultNotificationManager; import org.sonar.core.permission.PermissionFacade; -import org.sonar.core.persistence.*; +import org.sonar.core.persistence.DaoUtils; +import org.sonar.core.persistence.DatabaseVersion; +import org.sonar.core.persistence.DefaultDatabase; +import org.sonar.core.persistence.MyBatis; +import org.sonar.core.persistence.PreviewDatabaseFactory; +import org.sonar.core.persistence.SemaphoreUpdater; +import org.sonar.core.persistence.SemaphoresImpl; import org.sonar.core.preview.PreviewCache; import org.sonar.core.profiling.Profiling; import org.sonar.core.purge.PurgeProfiler; @@ -82,7 +86,11 @@ import org.sonar.server.activity.index.ActivityNormalizer; import org.sonar.server.activity.ws.ActivitiesWebService; import org.sonar.server.activity.ws.ActivityMapping; import org.sonar.server.authentication.ws.AuthenticationWs; -import org.sonar.server.batch.*; +import org.sonar.server.batch.BatchIndex; +import org.sonar.server.batch.BatchWs; +import org.sonar.server.batch.GlobalReferentialsAction; +import org.sonar.server.batch.ProjectReferentialsAction; +import org.sonar.server.batch.UploadReportAction; import org.sonar.server.charts.ChartFactory; import org.sonar.server.component.ComponentCleanerService; import org.sonar.server.component.ComponentService; @@ -90,10 +98,25 @@ import org.sonar.server.component.DefaultComponentFinder; import org.sonar.server.component.DefaultRubyComponentService; import org.sonar.server.component.db.ComponentDao; import org.sonar.server.component.db.SnapshotDao; -import org.sonar.server.component.ws.*; -import org.sonar.server.computation.*; +import org.sonar.server.component.ws.ComponentAppAction; +import org.sonar.server.component.ws.ComponentsWs; +import org.sonar.server.component.ws.EventsWs; +import org.sonar.server.component.ws.ProjectsWs; +import org.sonar.server.component.ws.ResourcesWs; +import org.sonar.server.computation.AnalysisReportQueue; +import org.sonar.server.computation.AnalysisReportService; +import org.sonar.server.computation.AnalysisReportTaskCleaner; +import org.sonar.server.computation.AnalysisReportTaskLauncher; +import org.sonar.server.computation.ComputationService; import org.sonar.server.computation.db.AnalysisReportDao; -import org.sonar.server.computation.step.*; +import org.sonar.server.computation.step.ComponentIndexationInDatabaseStep; +import org.sonar.server.computation.step.ComputationStepRegistry; +import org.sonar.server.computation.step.DataCleanerStep; +import org.sonar.server.computation.step.DigestReportStep; +import org.sonar.server.computation.step.IndexProjectIssuesStep; +import org.sonar.server.computation.step.InvalidatePreviewCacheStep; +import org.sonar.server.computation.step.SwitchSnapshotStep; +import org.sonar.server.computation.step.SynchronizeProjectPermissionsStep; import org.sonar.server.computation.ws.ActiveAnalysisReportsAction; import org.sonar.server.computation.ws.AnalysisReportHistorySearchAction; import org.sonar.server.computation.ws.AnalysisReportWebService; @@ -109,7 +132,14 @@ import org.sonar.server.db.DbClient; import org.sonar.server.db.EmbeddedDatabaseFactory; import org.sonar.server.db.migrations.DatabaseMigrations; import org.sonar.server.db.migrations.DatabaseMigrator; -import org.sonar.server.debt.*; +import org.sonar.server.debt.DebtCharacteristicsXMLImporter; +import org.sonar.server.debt.DebtModelBackup; +import org.sonar.server.debt.DebtModelLookup; +import org.sonar.server.debt.DebtModelOperations; +import org.sonar.server.debt.DebtModelPluginRepository; +import org.sonar.server.debt.DebtModelService; +import org.sonar.server.debt.DebtModelXMLExporter; +import org.sonar.server.debt.DebtRulesXMLImporter; import org.sonar.server.design.FileDesignWidget; import org.sonar.server.duplication.ws.DuplicationsJsonWriter; import org.sonar.server.duplication.ws.DuplicationsParser; @@ -117,14 +147,33 @@ import org.sonar.server.duplication.ws.DuplicationsWs; import org.sonar.server.es.EsClient; import org.sonar.server.es.IndexCreator; import org.sonar.server.es.IndexRegistry; -import org.sonar.server.issue.*; +import org.sonar.server.issue.ActionService; +import org.sonar.server.issue.AddTagsAction; +import org.sonar.server.issue.AssignAction; +import org.sonar.server.issue.CommentAction; +import org.sonar.server.issue.InternalRubyIssueService; +import org.sonar.server.issue.IssueBulkChangeService; +import org.sonar.server.issue.IssueChangelogFormatter; +import org.sonar.server.issue.IssueChangelogService; +import org.sonar.server.issue.IssueCommentService; +import org.sonar.server.issue.IssueQueryService; +import org.sonar.server.issue.IssueService; +import org.sonar.server.issue.PlanAction; +import org.sonar.server.issue.RemoveTagsAction; +import org.sonar.server.issue.ServerIssueStorage; +import org.sonar.server.issue.SetSeverityAction; +import org.sonar.server.issue.TransitionAction; import org.sonar.server.issue.actionplan.ActionPlanService; import org.sonar.server.issue.actionplan.ActionPlanWs; import org.sonar.server.issue.db.IssueDao; import org.sonar.server.issue.filter.IssueFilterService; import org.sonar.server.issue.filter.IssueFilterWriter; import org.sonar.server.issue.filter.IssueFilterWs; -import org.sonar.server.issue.index.*; +import org.sonar.server.issue.index.IssueAuthorizationIndexer; +import org.sonar.server.issue.index.IssueIndex; +import org.sonar.server.issue.index.IssueIndexDefinition; +import org.sonar.server.issue.index.IssueIndexer; +import org.sonar.server.issue.index.IssueNormalizer; import org.sonar.server.issue.ws.IssueActionsWriter; import org.sonar.server.issue.ws.IssueShowAction; import org.sonar.server.issue.ws.IssuesWs; @@ -147,34 +196,118 @@ import org.sonar.server.platform.ws.L10nWs; import org.sonar.server.platform.ws.RestartHandler; import org.sonar.server.platform.ws.ServerWs; import org.sonar.server.platform.ws.SystemWs; -import org.sonar.server.plugins.*; +import org.sonar.server.plugins.InstalledPluginReferentialFactory; +import org.sonar.server.plugins.PluginDownloader; +import org.sonar.server.plugins.ServerExtensionInstaller; +import org.sonar.server.plugins.ServerPluginJarInstaller; +import org.sonar.server.plugins.ServerPluginJarsInstaller; +import org.sonar.server.plugins.ServerPluginRepository; +import org.sonar.server.plugins.UpdateCenterClient; +import org.sonar.server.plugins.UpdateCenterMatrixFactory; import org.sonar.server.properties.ProjectSettingsFactory; import org.sonar.server.qualitygate.QgateProjectFinder; import org.sonar.server.qualitygate.QualityGates; import org.sonar.server.qualitygate.RegisterQualityGates; -import org.sonar.server.qualitygate.ws.*; -import org.sonar.server.qualityprofile.*; +import org.sonar.server.qualitygate.ws.QGatesAppAction; +import org.sonar.server.qualitygate.ws.QGatesCopyAction; +import org.sonar.server.qualitygate.ws.QGatesCreateAction; +import org.sonar.server.qualitygate.ws.QGatesCreateConditionAction; +import org.sonar.server.qualitygate.ws.QGatesDeleteConditionAction; +import org.sonar.server.qualitygate.ws.QGatesDeselectAction; +import org.sonar.server.qualitygate.ws.QGatesDestroyAction; +import org.sonar.server.qualitygate.ws.QGatesListAction; +import org.sonar.server.qualitygate.ws.QGatesRenameAction; +import org.sonar.server.qualitygate.ws.QGatesSearchAction; +import org.sonar.server.qualitygate.ws.QGatesSelectAction; +import org.sonar.server.qualitygate.ws.QGatesSetAsDefaultAction; +import org.sonar.server.qualitygate.ws.QGatesShowAction; +import org.sonar.server.qualitygate.ws.QGatesUnsetDefaultAction; +import org.sonar.server.qualitygate.ws.QGatesUpdateConditionAction; +import org.sonar.server.qualitygate.ws.QGatesWs; +import org.sonar.server.qualityprofile.BuiltInProfiles; +import org.sonar.server.qualityprofile.QProfileBackuper; +import org.sonar.server.qualityprofile.QProfileCopier; +import org.sonar.server.qualityprofile.QProfileExporters; +import org.sonar.server.qualityprofile.QProfileFactory; +import org.sonar.server.qualityprofile.QProfileLoader; +import org.sonar.server.qualityprofile.QProfileLookup; +import org.sonar.server.qualityprofile.QProfileProjectLookup; +import org.sonar.server.qualityprofile.QProfileProjectOperations; +import org.sonar.server.qualityprofile.QProfileReset; +import org.sonar.server.qualityprofile.QProfileService; +import org.sonar.server.qualityprofile.QProfiles; +import org.sonar.server.qualityprofile.RegisterQualityProfiles; +import org.sonar.server.qualityprofile.RuleActivator; +import org.sonar.server.qualityprofile.RuleActivatorContextFactory; import org.sonar.server.qualityprofile.db.ActiveRuleDao; import org.sonar.server.qualityprofile.index.ActiveRuleIndex; import org.sonar.server.qualityprofile.index.ActiveRuleNormalizer; -import org.sonar.server.qualityprofile.ws.*; -import org.sonar.server.rule.*; +import org.sonar.server.qualityprofile.ws.BulkRuleActivationActions; +import org.sonar.server.qualityprofile.ws.ProfilesWs; +import org.sonar.server.qualityprofile.ws.QProfileRestoreBuiltInAction; +import org.sonar.server.qualityprofile.ws.QProfilesWs; +import org.sonar.server.qualityprofile.ws.RuleActivationActions; +import org.sonar.server.rule.DefaultRuleFinder; +import org.sonar.server.rule.DeprecatedRulesDefinitionLoader; +import org.sonar.server.rule.RegisterRules; +import org.sonar.server.rule.RubyRuleService; +import org.sonar.server.rule.RuleCreator; +import org.sonar.server.rule.RuleDefinitionsLoader; +import org.sonar.server.rule.RuleDeleter; +import org.sonar.server.rule.RuleOperations; +import org.sonar.server.rule.RuleRepositories; +import org.sonar.server.rule.RuleService; +import org.sonar.server.rule.RuleUpdater; import org.sonar.server.rule.db.RuleDao; import org.sonar.server.rule.index.RuleIndex; import org.sonar.server.rule.index.RuleNormalizer; -import org.sonar.server.rule.ws.*; -import org.sonar.server.search.*; +import org.sonar.server.rule.ws.ActiveRuleCompleter; +import org.sonar.server.rule.ws.AppAction; +import org.sonar.server.rule.ws.DeleteAction; +import org.sonar.server.rule.ws.RuleMapping; +import org.sonar.server.rule.ws.RulesWebService; +import org.sonar.server.rule.ws.SearchAction; +import org.sonar.server.rule.ws.TagsAction; +import org.sonar.server.rule.ws.UpdateAction; +import org.sonar.server.search.IndexClient; +import org.sonar.server.search.IndexQueue; +import org.sonar.server.search.IndexSynchronizer; +import org.sonar.server.search.SearchClient; +import org.sonar.server.search.SearchHealth; import org.sonar.server.source.HtmlSourceDecorator; import org.sonar.server.source.IndexSourceLinesStep; import org.sonar.server.source.SourceService; import org.sonar.server.source.index.SourceLineIndex; import org.sonar.server.source.index.SourceLineIndexDefinition; import org.sonar.server.source.index.SourceLineIndexer; -import org.sonar.server.source.ws.*; +import org.sonar.server.source.ws.HashAction; +import org.sonar.server.source.ws.IndexAction; +import org.sonar.server.source.ws.LinesAction; +import org.sonar.server.source.ws.RawAction; +import org.sonar.server.source.ws.ScmAction; +import org.sonar.server.source.ws.ScmWriter; import org.sonar.server.source.ws.ShowAction; -import org.sonar.server.startup.*; +import org.sonar.server.source.ws.SourcesWs; +import org.sonar.server.startup.CleanPreviewAnalysisCache; +import org.sonar.server.startup.CopyRequirementsFromCharacteristicsToRules; +import org.sonar.server.startup.GeneratePluginIndex; +import org.sonar.server.startup.JdbcDriverDeployer; +import org.sonar.server.startup.LogServerId; +import org.sonar.server.startup.RegisterDashboards; +import org.sonar.server.startup.RegisterDebtModel; +import org.sonar.server.startup.RegisterMetrics; +import org.sonar.server.startup.RegisterNewMeasureFilters; +import org.sonar.server.startup.RegisterPermissionTemplates; +import org.sonar.server.startup.RegisterServletFilters; +import org.sonar.server.startup.RenameDeprecatedPropertyKeys; +import org.sonar.server.startup.ServerMetadataPersister; import org.sonar.server.test.CoverageService; -import org.sonar.server.test.ws.*; +import org.sonar.server.test.ws.CoverageShowAction; +import org.sonar.server.test.ws.CoverageWs; +import org.sonar.server.test.ws.TestsCoveredFilesAction; +import org.sonar.server.test.ws.TestsShowAction; +import org.sonar.server.test.ws.TestsTestCasesAction; +import org.sonar.server.test.ws.TestsWs; import org.sonar.server.text.MacroInterpreter; import org.sonar.server.text.RubyTextService; import org.sonar.server.ui.JRubyI18n; @@ -182,14 +315,26 @@ import org.sonar.server.ui.JRubyProfiling; import org.sonar.server.ui.PageDecorations; import org.sonar.server.ui.Views; import org.sonar.server.updatecenter.ws.UpdateCenterWs; -import org.sonar.server.user.*; +import org.sonar.server.user.DefaultUserService; +import org.sonar.server.user.DoPrivileged; +import org.sonar.server.user.GroupMembershipFinder; +import org.sonar.server.user.GroupMembershipService; +import org.sonar.server.user.NewUserNotifier; +import org.sonar.server.user.SecurityRealmFactory; +import org.sonar.server.user.UserService; import org.sonar.server.user.db.GroupDao; import org.sonar.server.user.index.UserIndexDefinition; import org.sonar.server.user.index.UserIndexer; import org.sonar.server.user.ws.FavoritesWs; import org.sonar.server.user.ws.UserPropertiesWs; import org.sonar.server.user.ws.UsersWs; -import org.sonar.server.util.*; +import org.sonar.server.util.BooleanTypeValidation; +import org.sonar.server.util.FloatTypeValidation; +import org.sonar.server.util.IntegerTypeValidation; +import org.sonar.server.util.StringListTypeValidation; +import org.sonar.server.util.StringTypeValidation; +import org.sonar.server.util.TextTypeValidation; +import org.sonar.server.util.TypeValidations; import org.sonar.server.ws.ListingWs; import org.sonar.server.ws.WebServiceEngine; @@ -540,6 +685,7 @@ class ServerComponents { pico.addSingleton(CommentAction.class); pico.addSingleton(TransitionAction.class); pico.addSingleton(AddTagsAction.class); + pico.addSingleton(RemoveTagsAction.class); // technical debt pico.addSingleton(DebtModelService.class); diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/RemoveTagsActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/RemoveTagsActionTest.java new file mode 100644 index 00000000000..24194997643 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/RemoveTagsActionTest.java @@ -0,0 +1,91 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.issue; + +import com.google.common.collect.ImmutableSet; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.mockito.Matchers; +import org.sonar.api.issue.internal.DefaultIssue; +import org.sonar.api.issue.internal.IssueChangeContext; +import org.sonar.core.issue.IssueUpdater; + +import java.util.Collection; +import java.util.Map; + +import static com.google.common.collect.Maps.newHashMap; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class RemoveTagsActionTest { + + private RemoveTagsAction action; + + private IssueUpdater issueUpdater = mock(IssueUpdater.class); + + @Rule + public ExpectedException throwable = ExpectedException.none(); + + @Before + public void before() { + action = new RemoveTagsAction(issueUpdater); + } + + @Test + @SuppressWarnings("unchecked") + public void should_execute() { + Map properties = newHashMap(); + properties.put("tags", "tag2,tag3"); + + DefaultIssue issue = mock(DefaultIssue.class); + when(issue.tags()).thenReturn(ImmutableSet.of("tag1", "tag3")); + + Action.Context context = mock(Action.Context.class); + when(context.issue()).thenReturn(issue); + + action.execute(properties, context); + verify(issueUpdater).setTags(eq(issue), + (Collection) Matchers.argThat(org.hamcrest.Matchers.containsInAnyOrder("tag1")), + any(IssueChangeContext.class)); + } + + @Test + public void should_fail_if_tag_is_not_valid() throws Exception { + throwable.expect(IllegalArgumentException.class); + throwable.expectMessage("Tag 'th ag' is invalid. Rule tags accept only the characters: a-z, 0-9, '+', '-', '#', '.'"); + + Map properties = newHashMap(); + properties.put("tags", "th ag"); + + DefaultIssue issue = mock(DefaultIssue.class); + when(issue.tags()).thenReturn(ImmutableSet.of("tag1", "tag3")); + + Action.Context context = mock(Action.Context.class); + when(context.issue()).thenReturn(issue); + + action.execute(properties, context); + } +} -- 2.39.5