@@ -116,6 +116,13 @@ import org.sonar.plugins.core.widgets.reviews.UnplannedReviewsWidget; | |||
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, |
@@ -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) { |
@@ -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; | |||
} | |||
@@ -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; | |||
@@ -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(); | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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() { | |||
} | |||
} | |||
} |
@@ -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() { | |||
} | |||
@@ -343,9 +343,14 @@ 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"; |
@@ -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 { | |||
@@ -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 { | |||
} |
@@ -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); | |||
} | |||
} | |||
} |
@@ -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 { | |||
} |
@@ -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() { | |||
} | |||
} | |||
} |