Browse Source

SONAR-4069 fix task extension point

tags/3.6
Simon Brandhof 11 years ago
parent
commit
8e7ffed1b1

+ 7
- 0
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java View File

@@ -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,

+ 14
- 13
sonar-batch/src/main/java/org/sonar/batch/bootstrap/TaskContainer.java View File

@@ -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) {

+ 21
- 27
sonar-batch/src/main/java/org/sonar/batch/bootstrapper/Batch.java View File

@@ -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;
}


+ 6
- 6
sonar-batch/src/main/java/org/sonar/batch/scan/ScanTask.java View File

@@ -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;


sonar-batch/src/main/java/org/sonar/batch/tasks/ListTasksTask.java → sonar-batch/src/main/java/org/sonar/batch/tasks/ListTask.java View File

@@ -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();
}

+ 27
- 74
sonar-batch/src/main/java/org/sonar/batch/tasks/Tasks.java View File

@@ -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);
}

}

+ 33
- 0
sonar-batch/src/test/java/org/sonar/batch/scan/ScanTaskTest.java View File

@@ -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);
}
}

sonar-batch/src/test/java/org/sonar/batch/tasks/ListTasksTaskTest.java → sonar-batch/src/test/java/org/sonar/batch/tasks/ListTaskTest.java View File

@@ -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() {
}
}
}

+ 20
- 106
sonar-batch/src/test/java/org/sonar/batch/tasks/TasksTest.java View File

@@ -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() {
}


+ 6
- 1
sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java View File

@@ -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";

+ 1
- 1
sonar-plugin-api/src/main/java/org/sonar/api/task/Task.java View File

@@ -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 {


+ 2
- 3
sonar-plugin-api/src/main/java/org/sonar/api/task/TaskComponent.java View File

@@ -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 {
}

+ 76
- 36
sonar-plugin-api/src/main/java/org/sonar/api/task/TaskDefinition.java View File

@@ -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);
}
}
}

+ 2
- 2
sonar-plugin-api/src/main/java/org/sonar/api/task/TaskExtension.java View File

@@ -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 {
}

+ 100
- 0
sonar-plugin-api/src/test/java/org/sonar/api/task/TaskDefinitionTest.java View File

@@ -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() {
}
}
}

Loading…
Cancel
Save