*/
private List<String> projectPropertyKeys = Lists.newArrayList();
+ /**
+ * Optimization: fast way to get all context conditions
+ */
+ private ListMultimap<String, Condition> contextConditionsByCommand = ArrayListMultimap.create();
+
+ /**
+ * Optimization: fast way to get all review conditions
+ */
+ private ListMultimap<String, Condition> reviewConditionsByCommand = ArrayListMultimap.create();
+
+
public Workflow addCommand(String key) {
Preconditions.checkArgument(!Strings.isNullOrEmpty(key), "Empty command key");
commands.add(key);
return projectPropertyKeys;
}
+ /**
+ * Shortcut for: getReviewConditions(commandKey) + getContextConditions(commandKey)
+ */
public List<Condition> getConditions(String commandKey) {
return conditionsByCommand.get(commandKey);
}
+ public List<Condition> getReviewConditions(String commandKey) {
+ return reviewConditionsByCommand.get(commandKey);
+ }
+
+ public List<Condition> getContextConditions(String commandKey) {
+ return contextConditionsByCommand.get(commandKey);
+ }
+
public Workflow addCondition(String commandKey, Condition condition) {
Preconditions.checkArgument(hasCommand(commandKey), "Unknown command: " + commandKey);
Preconditions.checkNotNull(condition);
if (condition instanceof ProjectPropertyCondition) {
projectPropertyKeys.add(((ProjectPropertyCondition) condition).getPropertyKey());
}
+ if (condition.isOnContext()) {
+ contextConditionsByCommand.put(commandKey, condition);
+ } else {
+ reviewConditionsByCommand.put(commandKey, condition);
+ }
return this;
}
completeProjectSettings(context);
- for (Review review : reviews) {
- for (Map.Entry<String, Screen> entry : workflow.getScreensByCommand().entrySet()) {
- if (!verifyConditions || verifyConditions(review, context, entry.getKey())) {
- result.put(review.getViolationId(), entry.getValue());
+ for (Map.Entry<String, Screen> entry : workflow.getScreensByCommand().entrySet()) {
+ String commandKey = entry.getKey();
+ if (!verifyConditions || verifyConditions(null, context, workflow.getContextConditions(commandKey))) {
+ for (Review review : reviews) {
+ if (!verifyConditions || verifyConditions(review, context, workflow.getReviewConditions(commandKey))) {
+ result.put(review.getViolationId(), entry.getValue());
+ }
}
}
}
List<Screen> result = Lists.newArrayList();
completeProjectSettings(context);
for (Map.Entry<String, Screen> entry : workflow.getScreensByCommand().entrySet()) {
- if (!verifyConditions || verifyConditions(review, context, entry.getKey())) {
+ String commandKey = entry.getKey();
+ if (!verifyConditions || verifyConditions(review, context, workflow.getConditions(commandKey))) {
result.add(entry.getValue());
}
public void execute(String commandKey, MutableReview review, DefaultWorkflowContext context, Map<String, String> parameters) {
Preconditions.checkArgument(!Strings.isNullOrEmpty(commandKey), "Missing command");
Preconditions.checkArgument(workflow.hasCommand(commandKey), "Unknown command: " + commandKey);
- Preconditions.checkState(verifyConditions(review, context, commandKey), "Conditions are not respected");
completeProjectSettings(context);
+
+ Preconditions.checkState(verifyConditions(review, context, workflow.getConditions(commandKey)), "Conditions are not respected");
+
ImmutableMap<String, String> immutableParameters = ImmutableMap.copyOf(parameters);
// TODO execute functions are change state before functions that consume state (like "create-jira-issue")
- Review initialReview = ((DefaultReview)review).cloneImmutable();
+ Review initialReview = ((DefaultReview) review).cloneImmutable();
for (Function function : workflow.getFunctions(commandKey)) {
function.doExecute(review, initialReview, context, immutableParameters);
}
// TODO notify listeners
}
- private boolean verifyConditions(Review review, WorkflowContext context, String command) {
- return verifyConditions(review, context, workflow.getConditions(command));
- }
-
private boolean verifyConditions(Review review, WorkflowContext context, List<Condition> conditions) {
for (Condition condition : conditions) {
if (!condition.doVerify(review, context)) {
import org.sonar.core.review.workflow.review.Review;
import org.sonar.core.review.workflow.review.WorkflowContext;
+import javax.annotation.Nullable;
+
public abstract class Condition {
- private final boolean oncePerGroup;
+ private final boolean onContext;
- protected Condition(boolean oncePerGroup) {
- this.oncePerGroup = oncePerGroup;
+ protected Condition(boolean onContext) {
+ this.onContext = onContext;
}
- public final boolean isOncePerGroup() {
- return oncePerGroup;
+ public final boolean isOnContext() {
+ return onContext;
}
- public abstract boolean doVerify(Review review, WorkflowContext context);
+ /**
+ * @param review the review on "review conditions" like StatusCondition, null on "context conditions" like AdminRoleCondition or ProjectPropertyCondition
+ * @param context
+ * @return is the condition verified ?
+ */
+ public abstract boolean doVerify(@Nullable Review review, WorkflowContext context);
}
private Condition condition;
public NotCondition(Condition c) {
- super(c.isOncePerGroup());
+ super(c.isOnContext());
this.condition = c;
}
/**
* Note : implementation is still mutable.
*/
- public Review cloneImmutable() {
- DefaultReview clone = new DefaultReview();
+ public ImmutableReview cloneImmutable() {
+ ImmutableReview clone = new ImmutableReview();
clone.setAssigneeId(assigneeId);
clone.setLine(line);
clone.setManual(manual);
clone.setStatus(status);
clone.setSwitchedOff(switchedOff);
clone.setViolationId(violationId);
+ clone.setProperties(ImmutableMap.copyOf(getProperties()));
return clone;
}
}
--- /dev/null
+/*
+ * 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.core.review.workflow.review;
+
+import java.util.Map;
+
+public class ImmutableReview implements Review {
+ private Long violationId;
+ private Long reviewId;
+ private Long ruleId;
+ private Long assigneeId;
+ private Long line;
+ private boolean switchedOff = false;
+ private boolean manual = false;
+ private String message;
+ private String status;
+ private String resolution;
+ private String severity;
+ private Map<String, String> properties;
+
+ public Long getViolationId() {
+ return violationId;
+ }
+
+ void setViolationId(Long violationId) {
+ this.violationId = violationId;
+ }
+
+ public Long getReviewId() {
+ return reviewId;
+ }
+
+ void setReviewId(Long reviewId) {
+ this.reviewId = reviewId;
+ }
+
+ public Long getRuleId() {
+ return ruleId;
+ }
+
+ void setRuleId(Long ruleId) {
+ this.ruleId = ruleId;
+ }
+
+ public Long getAssigneeId() {
+ return assigneeId;
+ }
+
+ void setAssigneeId(Long assigneeId) {
+ this.assigneeId = assigneeId;
+ }
+
+ public Long getLine() {
+ return line;
+ }
+
+ void setLine(Long line) {
+ this.line = line;
+ }
+
+ public boolean isSwitchedOff() {
+ return switchedOff;
+ }
+
+ void setSwitchedOff(boolean switchedOff) {
+ this.switchedOff = switchedOff;
+ }
+
+ public boolean isManual() {
+ return manual;
+ }
+
+ void setManual(boolean manual) {
+ this.manual = manual;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ void setMessage(String message) {
+ this.message = message;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ void setStatus(String status) {
+ this.status = status;
+ }
+
+ public String getResolution() {
+ return resolution;
+ }
+
+ void setResolution(String resolution) {
+ this.resolution = resolution;
+ }
+
+ public String getSeverity() {
+ return severity;
+ }
+
+ void setSeverity(String severity) {
+ this.severity = severity;
+ }
+
+ public Map<String, String> getProperties() {
+ return properties;
+ }
+
+ void setProperties(Map<String, String> properties) {
+ this.properties = properties;
+ }
+}
package org.sonar.core.review.workflow;
import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Maps;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.ExpectedException;
import org.sonar.api.config.Settings;
import org.sonar.core.review.workflow.condition.Condition;
import org.sonar.core.review.workflow.condition.HasProjectPropertyCondition;
-import org.sonar.core.review.workflow.review.DefaultReview;
-import org.sonar.core.review.workflow.review.DefaultWorkflowContext;
-import org.sonar.core.review.workflow.review.Review;
-import org.sonar.core.review.workflow.review.WorkflowContext;
+import org.sonar.core.review.workflow.function.Function;
+import org.sonar.core.review.workflow.review.*;
import org.sonar.core.review.workflow.screen.CommentScreen;
import org.sonar.core.review.workflow.screen.Screen;
import java.util.List;
+import java.util.Map;
import static org.fest.assertions.Assertions.assertThat;
import static org.junit.matchers.JUnitMatchers.hasItem;
import static org.mockito.Mockito.*;
public class WorkflowEngineTest {
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
@Test
public void listAvailableScreensForReview_empty() {
WorkflowEngine engine = new WorkflowEngine(new Workflow(), mock(ReviewStore.class), new Settings());
verify(store).completeProjectSettings(eq(300L), any(Settings.class), (List<String>) argThat(hasItem("foo")));
}
+
+ @Test
+ public void execute_conditions_pass() {
+ Workflow workflow = new Workflow();
+ workflow.addCommand("resolve");
+ workflow.addCondition("resolve", new HasProjectPropertyCondition("foo"));
+ Function function = mock(Function.class);
+ workflow.addFunction("resolve", function);
+
+ ReviewStore store = mock(ReviewStore.class);
+ Settings settings = new Settings();
+ settings.setProperty("foo", "bar");
+ WorkflowEngine engine = new WorkflowEngine(workflow, store, settings);
+
+ MutableReview review = new DefaultReview().setViolationId(1000L);
+ Map<String, String> parameters = Maps.newHashMap();
+ DefaultWorkflowContext context = new DefaultWorkflowContext().setProjectId(300L);
+
+ engine.execute("resolve", review, context, parameters);
+
+ verify(store).completeProjectSettings(eq(300L), any(Settings.class), (List<String>) argThat(hasItem("foo")));
+ verify(function).doExecute(eq(review), any(ImmutableReview.class), eq(context), eq(parameters));
+ }
+
+ @Test
+ public void execute_fail_if_conditions_dont_pass() {
+ thrown.expect(IllegalStateException.class);
+ thrown.expectMessage("Conditions are not respected");
+
+ Workflow workflow = new Workflow();
+ workflow.addCommand("resolve");
+ workflow.addCondition("resolve", new HasProjectPropertyCondition("foo"));
+ Function function = mock(Function.class);
+ workflow.addFunction("resolve", function);
+
+ ReviewStore store = mock(ReviewStore.class);
+ Settings settings = new Settings();// missing property 'foo'
+ WorkflowEngine engine = new WorkflowEngine(workflow, store, settings);
+
+ MutableReview review = new DefaultReview().setViolationId(1000L);
+ Map<String, String> parameters = Maps.newHashMap();
+ DefaultWorkflowContext context = new DefaultWorkflowContext().setProjectId(300L);
+
+ engine.execute("resolve", review, context, parameters);
+ }
}
}
@Test
- public void addAndGetCondition() {
+ public void addCondition() {
Workflow workflow = new Workflow();
Condition condition = new StatusCondition("OPEN");
workflow.addCommand("resolve");
}
@Test
- public void addAndGetFunctions() {
+ public void keepFastLinksToReviewAndContextConditions() {
+ Workflow workflow = new Workflow();
+ workflow.addCommand("create-jira-issue");
+ Condition contextCondition = new HasProjectPropertyCondition("jira.url");
+ workflow.addCondition("create-jira-issue", contextCondition);
+ Condition reviewCondition = new StatusCondition("OPEN");
+ workflow.addCondition("create-jira-issue", reviewCondition);
+
+ assertThat(workflow.getContextConditions("create-jira-issue")).containsExactly(contextCondition);
+ assertThat(workflow.getReviewConditions("create-jira-issue")).containsExactly(reviewCondition);
+ }
+
+ @Test
+ public void addFunction() {
Workflow workflow = new Workflow();
workflow.addCommand("resolve");
return false;
}
};
- assertThat(condition.isOncePerGroup()).isTrue();
+ assertThat(condition.isOnContext()).isTrue();
}
@Test
return false;
}
};
- assertThat(condition.isOncePerGroup()).isFalse();
+ assertThat(condition.isOnContext()).isFalse();
}
}