diff options
author | Simon Brandhof <simon.brandhof@gmail.com> | 2013-03-14 15:49:22 +0100 |
---|---|---|
committer | Simon Brandhof <simon.brandhof@gmail.com> | 2013-03-14 15:49:22 +0100 |
commit | 8e7ffed1b130c47fe5ac394b4f5c78dd901bff1b (patch) | |
tree | aee2f7cf51e711f85493df84ddf2d65bed6cd7f3 | |
parent | 6f752d47dbf6438e73e86acdf8d481102b04ce73 (diff) | |
download | sonarqube-8e7ffed1b130c47fe5ac394b4f5c78dd901bff1b.tar.gz sonarqube-8e7ffed1b130c47fe5ac394b4f5c78dd901bff1b.zip |
SONAR-4069 fix task extension point
15 files changed, 348 insertions, 285 deletions
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java index 07f2c9fe38d..188bf6d0ee2 100644 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java @@ -117,6 +117,13 @@ import java.util.List; @Properties({ @Property( + key = CoreProperties.TASK, + name = "Task to be executed", + defaultValue = CoreProperties.SCAN_TASK, + module = false, + project = false, + global = false), + @Property( key = CoreProperties.SERVER_BASE_URL, defaultValue = CoreProperties.SERVER_BASE_URL_DEFAULT_VALUE, name = "Server base URL", diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/TaskContainer.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/TaskContainer.java index 7afac65e0ed..bc50ede3ea2 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/TaskContainer.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/TaskContainer.java @@ -19,6 +19,8 @@ */ package org.sonar.batch.bootstrap; +import org.sonar.api.CoreProperties; +import org.sonar.api.config.Settings; import org.sonar.api.platform.ComponentContainer; import org.sonar.api.resources.ResourceTypes; import org.sonar.api.task.Task; @@ -27,8 +29,6 @@ import org.sonar.api.task.TaskExtension; import org.sonar.api.utils.SonarException; import org.sonar.batch.tasks.Tasks; -import javax.annotation.Nullable; - public class TaskContainer extends ComponentContainer { public TaskContainer(ComponentContainer parent) { @@ -41,23 +41,24 @@ public class TaskContainer extends ComponentContainer { installComponentsUsingTaskExtensions(); } - public void executeTask(@Nullable String key) { + public void execute() { startComponents(); - TaskDefinition taskDef = get(Tasks.class).getTaskDefinition(key); - if (taskDef != null) { - Task task = get(taskDef.getTask()); - if (task != null) { - task.execute(); - } else { - throw new IllegalStateException("Command " + key + " is badly defined."); - } + // default value is declared in CorePlugin + String taskKey = get(Settings.class).getString(CoreProperties.TASK); + + TaskDefinition def = get(Tasks.class).definition(taskKey); + if (def == null) { + throw new SonarException("Task " + taskKey + " does not exist."); + } + Task task = get(def.taskClass()); + if (task != null) { + task.execute(); } else { - throw new SonarException("Command " + key + " does not exist."); + throw new IllegalStateException("Task " + taskKey + " is badly defined."); } } - private void installTaskExtensions() { get(ExtensionInstaller.class).install(this, new ExtensionInstaller.ComponentFilter() { public boolean accept(Object extension) { diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrapper/Batch.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrapper/Batch.java index 0ea009fd64e..c4d03ea8845 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrapper/Batch.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrapper/Batch.java @@ -27,7 +27,7 @@ import org.sonar.batch.bootstrap.BootstrapContainer; import org.sonar.batch.bootstrap.BootstrapProperties; import org.sonar.batch.bootstrap.TaskContainer; import org.sonar.batch.scan.ScanTask; -import org.sonar.batch.tasks.ListTasksTask; +import org.sonar.batch.tasks.ListTask; import org.sonar.core.PicoUtils; import java.util.Collections; @@ -35,7 +35,7 @@ import java.util.List; import java.util.Map; /** - * Entry point for batch bootstrappers. + * Entry point for sonar-runner. * * @since 2.14 */ @@ -43,8 +43,7 @@ public final class Batch { private LoggingConfiguration logging; private List<Object> components; - private Map<String, String> globalProperties = Maps.newHashMap(); - private String taskCommand; + private Map<String, String> bootstrapProperties = Maps.newHashMap(); private ProjectReactor projectReactor; private Batch(Builder builder) { @@ -53,16 +52,15 @@ public final class Batch { if (builder.environment != null) { components.add(builder.environment); } - if (builder.globalProperties != null) { - globalProperties.putAll(builder.globalProperties); + if (builder.bootstrapProperties != null) { + bootstrapProperties.putAll(builder.bootstrapProperties); } else { // For backward compatibility, previously all properties were set in root project - globalProperties.putAll(Maps.fromProperties(builder.projectReactor.getRoot().getProperties())); + bootstrapProperties.putAll(Maps.fromProperties(builder.projectReactor.getRoot().getProperties())); } - this.taskCommand = builder.taskCommand; projectReactor = builder.projectReactor; if (builder.isEnableLoggingConfiguration()) { - logging = LoggingConfiguration.create().setProperties(globalProperties); + logging = LoggingConfiguration.create().setProperties(bootstrapProperties); } } @@ -83,29 +81,31 @@ public final class Batch { } private void startBatch() { - BootstrapContainer bootstrapModule = null; + BootstrapContainer bootstrapContainer = null; try { List all = Lists.newArrayList(components); - all.add(new BootstrapProperties(globalProperties)); - all.add(projectReactor); + all.add(new BootstrapProperties(bootstrapProperties)); + if (projectReactor != null) { + all.add(projectReactor); + } - bootstrapModule = BootstrapContainer.create(all); - bootstrapModule.startComponents(); + bootstrapContainer = BootstrapContainer.create(all); + bootstrapContainer.startComponents(); - TaskContainer taskContainer = new TaskContainer(bootstrapModule); + TaskContainer taskContainer = new TaskContainer(bootstrapContainer); taskContainer.add( ScanTask.DEFINITION, ScanTask.class, - ListTasksTask.DEFINITION, ListTasksTask.class + ListTask.DEFINITION, ListTask.class ); - taskContainer.executeTask(taskCommand); + taskContainer.execute(); } catch (RuntimeException e) { PicoUtils.propagateStartupException(e); } finally { try { - if (bootstrapModule != null) { - bootstrapModule.stopComponents(); + if (bootstrapContainer != null) { + bootstrapContainer.stopComponents(); } } catch (Exception e) { // never throw exceptions in a finally block @@ -119,8 +119,7 @@ public final class Batch { } public static final class Builder { - private Map<String, String> globalProperties; - private String taskCommand; + private Map<String, String> bootstrapProperties; private ProjectReactor projectReactor; private EnvironmentInformation environment; private List<Object> components = Lists.newArrayList(); @@ -145,12 +144,7 @@ public final class Batch { } public Builder setGlobalProperties(Map<String, String> globalProperties) { - this.globalProperties = globalProperties; - return this; - } - - public Builder setTaskCommand(String taskCommand) { - this.taskCommand = taskCommand; + this.bootstrapProperties = globalProperties; return this; } diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/ScanTask.java b/sonar-batch/src/main/java/org/sonar/batch/scan/ScanTask.java index 8ce1266e456..49e25856afa 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan/ScanTask.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/ScanTask.java @@ -19,19 +19,19 @@ */ package org.sonar.batch.scan; +import org.sonar.api.CoreProperties; import org.sonar.api.platform.ComponentContainer; import org.sonar.api.task.Task; import org.sonar.api.task.TaskDefinition; import org.sonar.batch.bootstrap.TaskContainer; public class ScanTask implements Task { - public static final String COMMAND = "inspect"; - public static final TaskDefinition DEFINITION = TaskDefinition.create() - .setDescription("Scan project and upload report to server") - .setName("Project Scan") - .setCommand(COMMAND) - .setTask(ScanTask.class); + public static final TaskDefinition DEFINITION = TaskDefinition.builder() + .description("Scan project") + .key(CoreProperties.SCAN_TASK) + .taskClass(ScanTask.class) + .build(); private final ComponentContainer taskContainer; diff --git a/sonar-batch/src/main/java/org/sonar/batch/tasks/ListTasksTask.java b/sonar-batch/src/main/java/org/sonar/batch/tasks/ListTask.java index dd08729b2cf..93002714b91 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/tasks/ListTasksTask.java +++ b/sonar-batch/src/main/java/org/sonar/batch/tasks/ListTask.java @@ -22,28 +22,28 @@ package org.sonar.batch.tasks; import org.sonar.api.task.Task; import org.sonar.api.task.TaskDefinition; -public class ListTasksTask implements Task { +public class ListTask implements Task { - public static final String COMMAND = "list-tasks"; + public static final String KEY = "list"; - public static final TaskDefinition DEFINITION = TaskDefinition.create() - .setDescription("List available tasks") - .setName("List Tasks") - .setCommand(COMMAND) - .setTask(ListTasksTask.class); + public static final TaskDefinition DEFINITION = TaskDefinition.builder() + .key(KEY) + .description("List available tasks") + .taskClass(ListTask.class) + .build(); - private final Tasks taskManager; + private final Tasks tasks; - public ListTasksTask(Tasks taskManager) { - this.taskManager = taskManager; + public ListTask(Tasks tasks) { + this.tasks = tasks; } public void execute() { logBlankLine(); log("Available tasks:"); logBlankLine(); - for (TaskDefinition taskDef : taskManager.getTaskDefinitions()) { - log(" - " + taskDef.getCommand() + ": " + taskDef.getDescription()); + for (TaskDefinition def : tasks.definitions()) { + log(" - " + def.key() + ": " + def.description()); } logBlankLine(); } diff --git a/sonar-batch/src/main/java/org/sonar/batch/tasks/Tasks.java b/sonar-batch/src/main/java/org/sonar/batch/tasks/Tasks.java index 3c7de6d9117..6a2fa892720 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/tasks/Tasks.java +++ b/sonar-batch/src/main/java/org/sonar/batch/tasks/Tasks.java @@ -19,103 +19,56 @@ */ package org.sonar.batch.tasks; +import com.google.common.collect.ImmutableSortedMap; import com.google.common.collect.Maps; -import org.apache.commons.lang.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.sonar.api.CoreProperties; -import org.sonar.api.config.Settings; import org.sonar.api.task.Task; import org.sonar.api.task.TaskComponent; import org.sonar.api.task.TaskDefinition; import org.sonar.api.utils.SonarException; -import org.sonar.batch.scan.ScanTask; - -import javax.annotation.Nullable; +import java.util.Collection; import java.util.Map; -import java.util.regex.Pattern; +import java.util.SortedMap; public class Tasks implements TaskComponent { - private static final Logger LOG = LoggerFactory.getLogger(Tasks.class); - private static final String COMMAND_PATTERN = "[a-zA-Z0-9\\-\\_]+"; - - private final TaskDefinition[] taskDefinitions; - private final Settings settings; - - private final Map<String, TaskDefinition> taskDefByCommand = Maps.newHashMap(); - private final Map<Class<? extends Task>, TaskDefinition> taskDefByTask = Maps.newHashMap(); + private final SortedMap<String, TaskDefinition> byKey; - public Tasks(Settings settings, TaskDefinition[] taskDefinitions) { - this.settings = settings; - this.taskDefinitions = taskDefinitions; + public Tasks(TaskDefinition[] definitions) { + SortedMap<String, TaskDefinition> map = Maps.newTreeMap(); + for (TaskDefinition definition : definitions) { + if (map.containsKey(definition.key())) { + throw new SonarException("Task '" + definition.key() + "' is declared twice"); + } + map.put(definition.key(), definition); + } + this.byKey = ImmutableSortedMap.copyOf(map); } - public TaskDefinition getTaskDefinition(@Nullable String command) { - String finalCommand = StringUtils.defaultIfBlank(command, settings.getString(CoreProperties.TASK)); - finalCommand = StringUtils.defaultIfBlank(finalCommand, ScanTask.COMMAND); - - if (taskDefByCommand.containsKey(finalCommand)) { - return taskDefByCommand.get(finalCommand); - } - throw new SonarException("No task found for command: " + finalCommand); + public TaskDefinition definition(String taskKey) { + return byKey.get(taskKey); } - public TaskDefinition[] getTaskDefinitions() { - return taskDefinitions; + public Collection<TaskDefinition> definitions() { + return byKey.values(); } /** * Perform validation of task definitions */ public void start() { - for (TaskDefinition def : taskDefinitions) { - validateTask(def); - validateName(def); - validateCommand(def); - validateDescription(def); - } + checkDuplicatedClasses(); } - private void validateName(TaskDefinition def) { - if (StringUtils.isBlank(def.getName())) { - throw new SonarException("Task definition for task '" + def.getTask().getName() + "' doesn't define task name"); + private void checkDuplicatedClasses() { + Map<Class<? extends Task>, TaskDefinition> byClass = Maps.newHashMap(); + for (TaskDefinition def : definitions()) { + TaskDefinition other = byClass.get(def.taskClass()); + if (other == null) { + byClass.put(def.taskClass(), def); + } else { + throw new SonarException("Task '" + def.taskClass().getName() + "' is defined twice: first by '" + other.key() + "' and then by '" + def.key() + "'"); + } } - } - - private void validateCommand(TaskDefinition def) { - String command = def.getCommand(); - if (StringUtils.isBlank(command)) { - throw new SonarException("Task definition '" + def.getName() + "' doesn't define task command"); - } - if (!Pattern.matches(COMMAND_PATTERN, command)) { - throw new SonarException("Command '" + command + "' for task definition '" + def.getName() + "' is not valid and should match " + COMMAND_PATTERN); - } - if (taskDefByCommand.containsKey(command)) { - throw new SonarException("Task '" + def.getName() + "' uses the same command than task '" + taskDefByCommand.get(command).getName() + "'"); - } - taskDefByCommand.put(command, def); - } - - private void validateDescription(TaskDefinition def) { - if (StringUtils.isBlank(def.getDescription())) { - LOG.warn("Task definition {} doesn't define a description. Using name as description.", def.getName()); - def.setDescription(def.getName()); - } - } - - private void validateTask(TaskDefinition def) { - Class<? extends Task> taskClass = def.getTask(); - if (taskClass == null) { - throw new SonarException("Task definition '" + def.getName() + "' doesn't define the associated task class"); - } - if (taskDefByTask.containsKey(taskClass)) { - throw new SonarException("Task '" + def.getTask().getName() + "' is defined twice: first by '" + taskDefByTask.get(taskClass).getName() + "' and then by '" + def.getName() - + "'"); - } - taskDefByTask.put(taskClass, def); - } - } diff --git a/sonar-batch/src/test/java/org/sonar/batch/scan/ScanTaskTest.java b/sonar-batch/src/test/java/org/sonar/batch/scan/ScanTaskTest.java new file mode 100644 index 00000000000..fcf4c52244a --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/scan/ScanTaskTest.java @@ -0,0 +1,33 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2012 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.batch.scan; + +import org.junit.Test; +import org.sonar.api.CoreProperties; + +import static org.fest.assertions.Assertions.assertThat; + +public class ScanTaskTest { + @Test + public void test_definition() { + assertThat(ScanTask.DEFINITION).isNotNull(); + assertThat(ScanTask.DEFINITION.key()).isEqualTo(CoreProperties.SCAN_TASK); + } +} diff --git a/sonar-batch/src/test/java/org/sonar/batch/tasks/ListTasksTaskTest.java b/sonar-batch/src/test/java/org/sonar/batch/tasks/ListTaskTest.java index d66c0c688ed..0848ffc6894 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/tasks/ListTasksTaskTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/tasks/ListTaskTest.java @@ -20,25 +20,42 @@ package org.sonar.batch.tasks; import org.junit.Test; +import org.sonar.api.task.Task; import org.sonar.api.task.TaskDefinition; +import java.util.Arrays; + import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -public class ListTasksTaskTest { +public class ListTaskTest { @Test public void should_list_available_tasks() { - TaskDefinition def = TaskDefinition.create().setCommand("purge").setName("Purge").setDescription("Purge database"); Tasks tasks = mock(Tasks.class); - when(tasks.getTaskDefinitions()).thenReturn(new TaskDefinition[]{def}); - ListTasksTask task = spy(new ListTasksTask(tasks)); + when(tasks.definitions()).thenReturn(Arrays.asList( + TaskDefinition.builder().key("foo").description("Foo").taskClass(FooTask.class).build(), + TaskDefinition.builder().key("purge").description("Purge database").taskClass(FakePurgeTask.class).build() + )); + + ListTask task = spy(new ListTask(tasks)); task.execute(); verify(task, times(1)).log("Available tasks:"); + verify(task, times(1)).log(" - foo: Foo"); verify(task, times(1)).log(" - purge: Purge database"); } + + private static class FakePurgeTask implements Task { + public void execute() { + } + } + + private static class FooTask implements Task { + public void execute() { + } + } } diff --git a/sonar-batch/src/test/java/org/sonar/batch/tasks/TasksTest.java b/sonar-batch/src/test/java/org/sonar/batch/tasks/TasksTest.java index 102576ec196..a2e394d9b1b 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/tasks/TasksTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/tasks/TasksTest.java @@ -19,12 +19,9 @@ */ package org.sonar.batch.tasks; -import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import org.sonar.api.CoreProperties; -import org.sonar.api.config.Settings; import org.sonar.api.task.Task; import org.sonar.api.task.TaskDefinition; import org.sonar.api.utils.SonarException; @@ -37,115 +34,43 @@ public class TasksTest { @Rule public ExpectedException thrown = ExpectedException.none(); - private Settings settings; - - @Before - public void prepare() { - settings = new Settings(); - } - - @Test - public void shouldReturnTaskDefinitions() { - Tasks tasks = new Tasks(settings, new TaskDefinition[] {ScanTask.DEFINITION, ListTasksTask.DEFINITION}); - assertThat(tasks.getTaskDefinitions().length).isEqualTo(2); - } - - @Test - public void shouldReturnInspectionTask() { - Tasks tasks = new Tasks(settings, new TaskDefinition[] {ScanTask.DEFINITION, ListTasksTask.DEFINITION}); - tasks.start(); - assertThat(tasks.getTaskDefinition(ScanTask.COMMAND)).isEqualTo(ScanTask.DEFINITION); - } - - @Test - public void shouldReturnInspectionTaskByDefault() { - Tasks tasks = new Tasks(settings, new TaskDefinition[] {ScanTask.DEFINITION, ListTasksTask.DEFINITION}); - tasks.start(); - assertThat(tasks.getTaskDefinition(null)).isEqualTo(ScanTask.DEFINITION); - } - - @Test - public void shouldReturnUsePropertyWhenNoCommand() { - Tasks tasks = new Tasks(settings, new TaskDefinition[] {ScanTask.DEFINITION, ListTasksTask.DEFINITION}); - tasks.start(); - assertThat(tasks.getTaskDefinition(ListTasksTask.COMMAND)).isEqualTo(ListTasksTask.DEFINITION); - assertThat(tasks.getTaskDefinition(null)).isEqualTo(ScanTask.DEFINITION); - - settings.setProperty(CoreProperties.TASK, ListTasksTask.COMMAND); - assertThat(tasks.getTaskDefinition(null)).isEqualTo(ListTasksTask.DEFINITION); - assertThat(tasks.getTaskDefinition(ScanTask.COMMAND)).isEqualTo(ScanTask.DEFINITION); - } - - @Test - public void shouldThrowWhenCommandNotFound() { - Tasks tasks = new Tasks(settings, new TaskDefinition[] {ScanTask.DEFINITION, ListTasksTask.DEFINITION}); - - thrown.expect(SonarException.class); - thrown.expectMessage("No task found for command: not-exists"); - - tasks.getTaskDefinition("not-exists"); - } - - @Test - public void shouldThrowWhenCommandMissing() { - Tasks tasks = new Tasks(settings, new TaskDefinition[] {TaskDefinition.create().setName("foo").setTask(FakeTask1.class)}); - - thrown.expect(SonarException.class); - thrown.expectMessage("Task definition 'foo' doesn't define task command"); - - tasks.start(); - } - @Test - public void shouldThrowWhenCommandInvalid() { - Tasks tasks = new Tasks(settings, new TaskDefinition[] {TaskDefinition.create().setName("foo").setTask(FakeTask1.class).setCommand("Azc-12_bbC")}); - tasks.start(); - - tasks = new Tasks(settings, new TaskDefinition[] {TaskDefinition.create().setName("foo").setTask(FakeTask1.class).setCommand("with space")}); - - thrown.expect(SonarException.class); - thrown.expectMessage("Command 'with space' for task definition 'foo' is not valid and should match [a-zA-Z0-9\\-\\_]+"); - - tasks.start(); + public void should_get_definitions() { + Tasks tasks = new Tasks(new TaskDefinition[]{ScanTask.DEFINITION, ListTask.DEFINITION}); + assertThat(tasks.definitions()).hasSize(2); } @Test - public void shouldThrowWhenDuplicateCommand() { - Tasks tasks = new Tasks(settings, new TaskDefinition[] { - TaskDefinition.create().setName("foo1").setTask(FakeTask1.class).setCommand("cmd"), - TaskDefinition.create().setName("foo2").setTask(FakeTask2.class).setCommand("cmd")}); - - thrown.expect(SonarException.class); - thrown.expectMessage("Task 'foo2' uses the same command than task 'foo1'"); - + public void should_get_definition_by_key() { + Tasks tasks = new Tasks(new TaskDefinition[]{ScanTask.DEFINITION, ListTask.DEFINITION}); tasks.start(); + assertThat(tasks.definition(ListTask.DEFINITION.key())).isEqualTo(ListTask.DEFINITION); } @Test - public void shouldThrowWhenNameMissing() { - Tasks tasks = new Tasks(settings, new TaskDefinition[] {TaskDefinition.create().setCommand("foo").setTask(FakeTask1.class)}); + public void should_return_null_if_task_not_found() { + Tasks tasks = new Tasks(new TaskDefinition[]{ScanTask.DEFINITION, ListTask.DEFINITION}); - thrown.expect(SonarException.class); - thrown.expectMessage("Task definition for task 'org.sonar.batch.tasks.TasksTest$FakeTask1' doesn't define task name"); - - tasks.start(); + assertThat(tasks.definition("not-exists")).isNull(); } @Test - public void shouldThrowWhenTaskMissing() { - Tasks tasks = new Tasks(settings, new TaskDefinition[] {TaskDefinition.create().setCommand("foo").setName("bar")}); - + public void should_fail_on_duplicated_keys() { thrown.expect(SonarException.class); - thrown.expectMessage("Task definition 'bar' doesn't define the associated task class"); + thrown.expectMessage("Task 'foo' is declared twice"); - tasks.start(); + new Tasks(new TaskDefinition[]{ + TaskDefinition.builder().key("foo").taskClass(FakeTask1.class).description("foo1").build(), + TaskDefinition.builder().key("foo").taskClass(FakeTask2.class).description("foo2").build() + }); } @Test - public void shouldThrowWhenDuplicateTask() { - Tasks tasks = new Tasks(settings, new TaskDefinition[] { - TaskDefinition.create().setName("foo1").setTask(FakeTask1.class).setCommand("cmd1"), - TaskDefinition.create().setName("foo2").setTask(FakeTask1.class).setCommand("cmd2")}); + public void should_fail_on_duplicated_class() { + Tasks tasks = new Tasks(new TaskDefinition[]{ + TaskDefinition.builder().key("foo1").taskClass(FakeTask1.class).description("foo1").build(), + TaskDefinition.builder().key("foo2").taskClass(FakeTask1.class).description("foo1").build() + }); thrown.expect(SonarException.class); thrown.expectMessage("Task 'org.sonar.batch.tasks.TasksTest$FakeTask1' is defined twice: first by 'foo1' and then by 'foo2'"); @@ -153,23 +78,12 @@ public class TasksTest { tasks.start(); } - @Test - public void shouldUseNameWhenDescriptionIsMissing() { - Tasks tasks = new Tasks(settings, new TaskDefinition[] {TaskDefinition.create().setName("foo").setCommand("cmd").setTask(FakeTask1.class)}); - tasks.start(); - - assertThat(tasks.getTaskDefinition("cmd").getDescription()).isEqualTo("foo"); - } - private static class FakeTask1 implements Task { - public void execute() { } - } private static class FakeTask2 implements Task { - public void execute() { } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java b/sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java index fdacc52dfbd..6d51b51a2a8 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java @@ -344,8 +344,13 @@ public interface CoreProperties { String TASK = "sonar.task"; /** + * @since 3.6 + */ + String SCAN_TASK = "scan"; + + /** * @deprecated replaced in v3.4 by properties specific to languages, for example sonar.java.coveragePlugin - * See http://jira.codehaus.org/browse/SONARJAVA-39 for more details. + * See http://jira.codehaus.org/browse/SONARJAVA-39 for more details. */ @Deprecated String CORE_COVERAGE_PLUGIN_PROPERTY = "sonar.core.codeCoveragePlugin"; diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/task/Task.java b/sonar-plugin-api/src/main/java/org/sonar/api/task/Task.java index 42bfdddcaac..e705c2725b6 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/task/Task.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/task/Task.java @@ -22,7 +22,7 @@ package org.sonar.api.task; /** * Implement this interface to provide the behavior of a task. - * @since 3.5 + * @since 3.6 */ public interface Task extends TaskExtension { diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/task/TaskComponent.java b/sonar-plugin-api/src/main/java/org/sonar/api/task/TaskComponent.java index 346b8bcf351..7cf26d1b132 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/task/TaskComponent.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/task/TaskComponent.java @@ -20,10 +20,9 @@ package org.sonar.api.task; /** - * Dependency Injection : all the classes implementing this interface are available in the task IoC container. - * Just add a parameter to the constructor of your component. + * All the classes implementing this interface can be injected in public constructors of {@link TaskExtension}. * - * @since 3.5 + * @since 3.6 */ public interface TaskComponent { } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/task/TaskDefinition.java b/sonar-plugin-api/src/main/java/org/sonar/api/task/TaskDefinition.java index 2d7d595bd78..1fba731dd49 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/task/TaskDefinition.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/task/TaskDefinition.java @@ -19,64 +19,104 @@ */ package org.sonar.api.task; +import com.google.common.base.Preconditions; +import com.google.common.base.Strings; + +import java.util.regex.Pattern; + /** - * Implement this interface to provide a new task. - * @since 3.5 + * Register and describe a {@link TaskExtension}. + * + * @since 3.6 */ -public class TaskDefinition implements TaskComponent { +public class TaskDefinition implements TaskExtension, Comparable<TaskDefinition> { + static final String KEY_PATTERN = "[a-zA-Z0-9\\-\\_]+"; - private String name; - private String description; - private String command; - private Class<? extends Task> task; - - private TaskDefinition() { + private final String key; + private final String description; + private final Class<? extends Task> taskClass; + private TaskDefinition(Builder builder) { + this.key = builder.key; + this.description = builder.description; + this.taskClass = builder.taskClass; } - public static TaskDefinition create() { - return new TaskDefinition(); + public String description() { + return description; } - public String getName() { - return name; + public String key() { + return key; } - public TaskDefinition setName(String name) { - this.name = name; - return this; + public Class<? extends Task> taskClass() { + return taskClass; } - public String getDescription() { - return description; + @Override + public String toString() { + return "Task " + key + "[class=" + taskClass.getName() + ", desc=" + description + "]"; } - public TaskDefinition setDescription(String description) { - this.description = description; - return this; + public static Builder builder() { + return new Builder(); } - public String getCommand() { - return command; - } + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } - public TaskDefinition setCommand(String command) { - this.command = command; - return this; + TaskDefinition that = (TaskDefinition) o; + if (!key.equals(that.key)) { + return false; + } + return true; } - public Class<? extends Task> getTask() { - return task; + @Override + public int hashCode() { + return key.hashCode(); } - public TaskDefinition setTask(Class<? extends Task> task) { - this.task = task; - return this; + public int compareTo(TaskDefinition o) { + return key.compareTo(o.key); } - @Override - public String toString() { - return "Definition of task " + task + " with command " + command; - } + public static class Builder { + private String key; + private String description; + private Class<? extends Task> taskClass; + + private Builder() { + } + public Builder key(String key) { + this.key = key; + return this; + } + + public Builder description(String s) { + this.description = s; + return this; + } + + public Builder taskClass(Class<? extends Task> taskClass) { + this.taskClass = taskClass; + return this; + } + + public TaskDefinition build() { + Preconditions.checkArgument(!Strings.isNullOrEmpty(key), "Task key must be set"); + Preconditions.checkArgument(Pattern.matches(KEY_PATTERN, key), "Task key '" + key + "' must match " + KEY_PATTERN); + Preconditions.checkArgument(!Strings.isNullOrEmpty(description), "Description must be set for task '" + key + "'"); + Preconditions.checkArgument(taskClass != null, "Class must be set for task '" + key + "'"); + return new TaskDefinition(this); + } + } } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/task/TaskExtension.java b/sonar-plugin-api/src/main/java/org/sonar/api/task/TaskExtension.java index 92c3c7230de..8406650ef79 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/task/TaskExtension.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/task/TaskExtension.java @@ -22,9 +22,9 @@ package org.sonar.api.task; import org.sonar.api.Extension; /** - * Task extension point. + * Task extension point * - * @since 3.5 + * @since 3.6 */ public interface TaskExtension extends Extension, TaskComponent { } diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/task/TaskDefinitionTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/task/TaskDefinitionTest.java new file mode 100644 index 00000000000..dcd581946aa --- /dev/null +++ b/sonar-plugin-api/src/test/java/org/sonar/api/task/TaskDefinitionTest.java @@ -0,0 +1,100 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2012 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.api.task; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import static org.fest.assertions.Assertions.assertThat; + +public class TaskDefinitionTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void should_build() { + TaskDefinition def = TaskDefinition.builder().key("foo").taskClass(FooTask.class).description("Foo").build(); + assertThat(def.key()).isEqualTo("foo"); + assertThat(def.description()).isEqualTo("Foo"); + assertThat(def.taskClass()).isEqualTo(FooTask.class); + assertThat(def.toString()).isEqualTo("Task foo[class=org.sonar.api.task.TaskDefinitionTest$FooTask, desc=Foo]"); + } + + @Test + public void test_equals_and_hashcode() { + TaskDefinition def1 = TaskDefinition.builder().key("one").taskClass(FooTask.class).description("Foo").build(); + TaskDefinition def1bis = TaskDefinition.builder().key("one").taskClass(FooTask.class).description("Foo").build(); + TaskDefinition def2 = TaskDefinition.builder().key("two").taskClass(FooTask.class).description("Foo").build(); + + assertThat(def1).isEqualTo(def1); + assertThat(def1).isEqualTo(def1bis); + assertThat(def2).isNotEqualTo(def1); + assertThat(def2).isNotEqualTo("one"); + assertThat(def2).isNotEqualTo(null); + + assertThat(def1.hashCode()).isEqualTo(def1.hashCode()); + assertThat(def1.hashCode()).isEqualTo(def1bis.hashCode()); + } + + @Test + public void test_compare() { + TaskDefinition foo = TaskDefinition.builder().key("foo").taskClass(FooTask.class).description("Foo").build(); + TaskDefinition bar = TaskDefinition.builder().key("bar").taskClass(FooTask.class).description("Bar").build(); + + assertThat(foo.compareTo(bar)).isGreaterThan(0); + assertThat(foo.compareTo(foo)).isEqualTo(0); + assertThat(bar.compareTo(foo)).isLessThan(0); + } + + @Test + public void description_should_be_required() { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("Description must be set for task 'foo'"); + TaskDefinition.builder().key("foo").taskClass(FooTask.class).build(); + } + + @Test + public void key_should_be_required() { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("Task key must be set"); + TaskDefinition.builder().description("Foo").taskClass(FooTask.class).build(); + } + + @Test + public void key_should_not_contain_spaces() { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("Task key 'fo o' must match " + TaskDefinition.KEY_PATTERN); + TaskDefinition.builder().key("fo o").description("foo").taskClass(FooTask.class).build(); + } + + @Test + public void class_should_be_required() { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("Class must be set for task 'foo'"); + TaskDefinition.builder().key("foo").description("Foo").build(); + } + + private static class FooTask implements Task { + public void execute() { + } + } +} |