]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-3755 keep state changes
authorSimon Brandhof <simon.brandhof@gmail.com>
Tue, 30 Apr 2013 09:08:36 +0000 (11:08 +0200)
committerSimon Brandhof <simon.brandhof@gmail.com>
Tue, 30 Apr 2013 09:08:49 +0000 (11:08 +0200)
36 files changed:
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/DefaultIssueHandlerContext.java [deleted file]
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueHandlers.java
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueTracking.java
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueTrackingDecorator.java
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueHandlersTest.java
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueTrackingDecoratorTest.java
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueTrackingTest.java
sonar-batch/src/main/java/org/sonar/batch/issue/DeprecatedViolations.java
sonar-batch/src/main/java/org/sonar/batch/issue/ScanIssues.java
sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java
sonar-batch/src/test/java/org/sonar/batch/issue/ScanIssuesTest.java
sonar-core/src/main/java/org/sonar/core/issue/DefaultIssue.java
sonar-core/src/main/java/org/sonar/core/issue/DefaultIssueBuilder.java
sonar-core/src/main/java/org/sonar/core/issue/IssueChange.java [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/issue/IssueChangeContext.java [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/issue/IssueUpdater.java [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/issue/db/IssueDto.java
sonar-core/src/main/java/org/sonar/core/issue/workflow/Function.java
sonar-core/src/main/java/org/sonar/core/issue/workflow/FunctionExecutor.java [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/issue/workflow/IssueWorkflow.java
sonar-core/src/main/java/org/sonar/core/issue/workflow/SetClosedAt.java [deleted file]
sonar-core/src/main/java/org/sonar/core/issue/workflow/SetClosedDate.java [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/issue/workflow/SetResolution.java
sonar-core/src/main/java/org/sonar/core/issue/workflow/Transition.java
sonar-core/src/main/java/org/sonar/core/issue/workflow/UnsetClosedAt.java [deleted file]
sonar-core/src/test/java/org/sonar/core/issue/DefaultIssueBuilderTest.java
sonar-core/src/test/java/org/sonar/core/issue/db/IssueDtoTest.java
sonar-core/src/test/java/org/sonar/core/issue/workflow/IssueWorkflowTest.java
sonar-core/src/test/java/org/sonar/core/issue/workflow/SetResolutionTest.java [deleted file]
sonar-core/src/test/java/org/sonar/core/issue/workflow/TransitionTest.java
sonar-core/src/test/java/org/sonar/core/issue/workflow/UnsetClosedAtTest.java [deleted file]
sonar-plugin-api/src/main/java/org/sonar/api/issue/IssueHandler.java
sonar-server/src/main/java/org/sonar/server/issue/JRubyInternalIssues.java
sonar-server/src/main/java/org/sonar/server/issue/ServerIssueActions.java
sonar-server/src/main/java/org/sonar/server/platform/Platform.java
sonar-server/src/main/webapp/WEB-INF/app/controllers/api/issues_controller.rb

diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/DefaultIssueHandlerContext.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/DefaultIssueHandlerContext.java
deleted file mode 100644 (file)
index c9810f7..0000000
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.plugins.core.issue;
-
-import org.sonar.api.issue.Issue;
-import org.sonar.api.issue.IssueHandler;
-import org.sonar.core.issue.DefaultIssue;
-
-import javax.annotation.Nullable;
-
-class DefaultIssueHandlerContext implements IssueHandler.IssueContext {
-
-  private final DefaultIssue issue;
-
-  DefaultIssueHandlerContext(DefaultIssue issue) {
-    this.issue = issue;
-  }
-
-  @Override
-  public Issue issue() {
-    return issue;
-  }
-
-  @Override
-  public boolean isNew() {
-    return issue.isNew();
-  }
-
-  @Override
-  public boolean isAlive() {
-    return issue.isAlive();
-  }
-
-  @Override
-  public IssueHandler.IssueContext setLine(@Nullable Integer line) {
-    issue.setLine(line);
-    return this;
-  }
-
-  @Override
-  public IssueHandler.IssueContext setDescription(String description) {
-    issue.setDescription(description);
-    return this;
-  }
-
-  @Override
-  public IssueHandler.IssueContext setSeverity(String severity) {
-    issue.setSeverity(severity);
-    return this;
-  }
-
-  @Override
-  public IssueHandler.IssueContext setAuthorLogin(@Nullable String login) {
-    issue.setAuthorLogin(login);
-    return this;
-  }
-
-  @Override
-  public IssueHandler.IssueContext setAttribute(String key, @Nullable String value) {
-    issue.setAttribute(key, value);
-    return this;
-  }
-
-  @Override
-  public IssueHandler.IssueContext assignTo(@Nullable String login) {
-    issue.setAssignee(login);
-    return this;
-  }
-}
index 3e496edcb69103eaee699be679cbf26a3a8bee57..96be94d7966b4922e2025007c80c41d36f190bdb 100644 (file)
 package org.sonar.plugins.core.issue;
 
 import org.sonar.api.BatchExtension;
+import org.sonar.api.issue.Issue;
 import org.sonar.api.issue.IssueHandler;
 import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.IssueUpdater;
+
+import javax.annotation.Nullable;
 
 public class IssueHandlers implements BatchExtension {
   private final IssueHandler[] handlers;
+  private final DefaultContext context;
 
-  public IssueHandlers(IssueHandler[] handlers) {
+  public IssueHandlers(IssueUpdater updater, IssueHandler[] handlers) {
     this.handlers = handlers;
+    this.context = new DefaultContext(updater);
   }
 
-  public IssueHandlers() {
-    this(new IssueHandler[0]);
+  public IssueHandlers(IssueUpdater updater) {
+    this(updater, new IssueHandler[0]);
   }
 
   public void execute(DefaultIssue issue) {
-    DefaultIssueHandlerContext context = new DefaultIssueHandlerContext(issue);
+    context.reset(issue);
     for (IssueHandler handler : handlers) {
       handler.onIssue(context);
     }
   }
 
+  static class DefaultContext implements IssueHandler.Context {
+    private final IssueUpdater updater;
+    private DefaultIssue issue;
+
+
+    private DefaultContext(IssueUpdater updater) {
+      this.updater = updater;
+    }
+
+    private void reset(DefaultIssue i) {
+      this.issue = i;
+    }
+
+    @Override
+    public Issue issue() {
+      return issue;
+    }
+
+    @Override
+    public boolean isNew() {
+      return issue.isNew();
+    }
+
+    @Override
+    public boolean isAlive() {
+      return issue.isAlive();
+    }
+
+    @Override
+    public IssueHandler.Context setLine(@Nullable Integer line) {
+      updater.setLine(issue, line);
+      return this;
+    }
+
+    @Override
+    public IssueHandler.Context setDescription(@Nullable String description) {
+      updater.setDescription(issue, description);
+      return this;
+    }
+
+    @Override
+    public IssueHandler.Context setSeverity(String severity) {
+      updater.setSeverity(issue, severity);
+      return this;
+    }
+
+    @Override
+    public IssueHandler.Context setAuthorLogin(@Nullable String login) {
+      updater.setAuthorLogin(issue, login);
+      return this;
+    }
+
+    @Override
+    public IssueHandler.Context setAttribute(String key, @Nullable String value) {
+      throw new UnsupportedOperationException("TODO");
+    }
+
+    @Override
+    public IssueHandler.Context assign(@Nullable String assignee) {
+      updater.assign(issue, assignee);
+      return this;
+    }
+
+    @Override
+    public IssueHandler.Context comment(String message) {
+      return null;
+    }
+  }
+
 }
index 2c1f78e570e626938c0b3d121afcdf93c9123b8a..65485967058a27720d8771723bc1138d90f191aa 100644 (file)
@@ -349,6 +349,8 @@ public class IssueTracking implements BatchExtension {
       if (pastIssue.isManualSeverity()) {
         newIssue.setManualSeverity(true);
         newIssue.setSeverity(pastIssue.getSeverity());
+      } else if (!Objects.equal(pastIssue.getSeverity(), newIssue.severity())) {
+        newIssue.setDiff("severity", pastIssue.getSeverity(), newIssue.severity());
       }
       newIssue.setResolution(pastIssue.getResolution());
       newIssue.setStatus(pastIssue.getStatus());
@@ -358,15 +360,13 @@ public class IssueTracking implements BatchExtension {
       newIssue.setAlive(true);
       newIssue.setAuthorLogin(pastIssue.getAuthorLogin());
       if (pastIssue.getAttributes() != null) {
+        //TODO do not loose new attributes
         newIssue.setAttributes(KeyValueFormat.parse(pastIssue.getAttributes()));
       }
 
       lastIssuesByRule.remove(getRuleId(newIssue), pastIssue);
       issueMap.put(newIssue, pastIssue);
       unmappedLastIssues.remove(pastIssue);
-    } else {
-      newIssue.setNew(true);
-      newIssue.setCreatedAt(project.getAnalysisDate());
     }
   }
 
index 7ef5af13ac7ca11075f8e0a66f56a54ad6bb0bac..f6c8b833d69ec54ea2264b40c00c1d5b8a9b743f 100644 (file)
@@ -31,6 +31,7 @@ import org.sonar.api.resources.ResourceUtils;
 import org.sonar.api.resources.Scopes;
 import org.sonar.batch.issue.ScanIssues;
 import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.IssueChangeContext;
 import org.sonar.core.issue.db.IssueDto;
 import org.sonar.core.issue.workflow.IssueWorkflow;
 
@@ -83,8 +84,9 @@ public class IssueTrackingDecorator implements Decorator {
         addDead(issues);
       }
 
+      IssueChangeContext changeContext = IssueChangeContext.createScan(context.getProject().getAnalysisDate());
       for (DefaultIssue issue : issues) {
-        workflow.doAutomaticTransition(issue);
+        workflow.doAutomaticTransition(issue, changeContext);
         handlers.execute(issue);
         scanIssues.addOrUpdate(issue);
       }
index dbc5e7bec7e7296caf774e22bca25eb809ae7f31..684f6431770d031055ec32bb63fd4dbf365d2845 100644 (file)
@@ -23,33 +23,38 @@ import org.junit.Test;
 import org.mockito.ArgumentMatcher;
 import org.sonar.api.issue.IssueHandler;
 import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.IssueChangeContext;
+import org.sonar.core.issue.IssueUpdater;
 
 import static org.mockito.Matchers.argThat;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
 
 public class IssueHandlersTest {
   @Test
   public void should_execute_handlers() throws Exception {
     IssueHandler h1 = mock(IssueHandler.class);
     IssueHandler h2 = mock(IssueHandler.class);
+    IssueUpdater updater = mock(IssueUpdater.class);
 
-    IssueHandlers handlers = new IssueHandlers(new IssueHandler[]{h1, h2});
+    IssueHandlers handlers = new IssueHandlers(updater, new IssueHandler[]{h1, h2});
     final DefaultIssue issue = new DefaultIssue();
     handlers.execute(issue);
 
-    verify(h1).onIssue(argThat(new ArgumentMatcher<IssueHandler.IssueContext>() {
+    verify(h1).onIssue(argThat(new ArgumentMatcher<IssueHandler.Context>() {
       @Override
       public boolean matches(Object o) {
-        return ((IssueHandler.IssueContext) o).issue() == issue;
+        return ((IssueHandler.Context) o).issue() == issue;
       }
     }));
   }
 
   @Test
   public void test_no_handlers() {
-    IssueHandlers handlers = new IssueHandlers();
+    IssueUpdater updater = mock(IssueUpdater.class);
+    IssueHandlers handlers = new IssueHandlers(updater);
     handlers.execute(new DefaultIssue());
-    // do not fail
+    verifyZeroInteractions(updater);
   }
 }
index 72d74f2118631830110f1a56ff2bb5d0686658d0..3eae474dd8476d9c1d70c920dd544452d102009d 100644 (file)
@@ -23,12 +23,14 @@ import com.google.common.collect.Sets;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentMatcher;
+import org.mockito.Mockito;
 import org.sonar.api.batch.DecoratorContext;
 import org.sonar.api.resources.File;
 import org.sonar.api.resources.Project;
 import org.sonar.api.resources.Resource;
 import org.sonar.batch.issue.ScanIssues;
 import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.IssueChangeContext;
 import org.sonar.core.issue.db.IssueDto;
 import org.sonar.core.issue.workflow.IssueWorkflow;
 import org.sonar.core.persistence.AbstractDaoTestCase;
@@ -88,7 +90,7 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase {
     List<IssueDto> dbIssues = Collections.emptyList();
     when(initialOpenIssues.selectAndRemove(123)).thenReturn(dbIssues);
 
-    decorator.decorate(file, mock(DecoratorContext.class));
+    decorator.decorate(file, mock(DecoratorContext.class, Mockito.RETURNS_MOCKS));
 
     // Apply filters, track, apply transitions, notify extensions then update cache
     verify(filters).accept(issue);
@@ -99,8 +101,8 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase {
         return issues.size() == 1 && issues.get(0) == issue;
       }
     }));
-    verify(workflow).doAutomaticTransition(issue);
-    verify(handlers).execute(issue);
+    verify(workflow).doAutomaticTransition(eq(issue), any(IssueChangeContext.class));
+    verify(handlers).execute(eq(issue));
     verify(scanIssues).addOrUpdate(issue);
   }
 
@@ -117,9 +119,9 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase {
     List<IssueDto> unmatchedIssues = Arrays.asList(unmatchedIssue);
     when(tracking.track(eq(file), anyCollection(), anyCollection())).thenReturn(Sets.newHashSet(unmatchedIssues));
 
-    decorator.decorate(file, mock(DecoratorContext.class));
+    decorator.decorate(file, mock(DecoratorContext.class, Mockito.RETURNS_MOCKS));
 
-    verify(workflow, times(2)).doAutomaticTransition(any(DefaultIssue.class));
+    verify(workflow, times(2)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
     verify(handlers, times(2)).execute(any(DefaultIssue.class));
     verify(scanIssues, times(2)).addOrUpdate(any(DefaultIssue.class));
 
@@ -141,10 +143,10 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase {
     IssueDto deadIssue = new IssueDto().setKey("ABCDE").setResolution("OPEN").setStatus("OPEN").setRuleKey_unit_test_only("squid", "AvoidCycle");
     when(initialOpenIssues.getAllIssues()).thenReturn(Arrays.asList(deadIssue));
 
-    decorator.decorate(project, mock(DecoratorContext.class));
+    decorator.decorate(project, mock(DecoratorContext.class, Mockito.RETURNS_MOCKS));
 
     // the dead issue must be closed -> apply automatic transition, notify handlers and add to cache
-    verify(workflow, times(2)).doAutomaticTransition(any(DefaultIssue.class));
+    verify(workflow, times(2)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
     verify(handlers, times(2)).execute(any(DefaultIssue.class));
     verify(scanIssues, times(2)).addOrUpdate(any(DefaultIssue.class));
 
index ad4a625b2f3886fb20dc15a949031be40a8a4a2f..82eeebdbd5a039d07a83ad891b7076cc1bf85d6d 100644 (file)
@@ -206,17 +206,6 @@ public class IssueTrackingTest {
     assertThat(newIssue.isNew()).isFalse();
   }
 
-  @Test
-  public void should_set_date_of_new_issues() {
-    DefaultIssue newIssue = newDefaultIssue("message", 1, RuleKey.of("squid", "AvoidCycle"), "checksum");
-    assertThat(newIssue.createdAt()).isNull();
-
-    Map<DefaultIssue, IssueDto> mapping = tracking.mapIssues(newArrayList(newIssue), Lists.<IssueDto>newArrayList());
-    assertThat(mapping.size()).isEqualTo(0);
-    assertThat(newIssue.createdAt()).isEqualTo(analysisDate);
-    assertThat(newIssue.isNew()).isTrue();
-  }
-
   @Test
   public void should_set_severity_if_severity_has_been_changed_by_user() {
     DefaultIssue newIssue = newDefaultIssue("message", 1, RuleKey.of("squid", "AvoidCycle"), "checksum").setSeverity("MAJOR");
index 28d69f309c47e9e1da238f56ac0fb180a6e9a678..c7e421bb3352214cc2cded9fc598818c84b19885 100644 (file)
 package org.sonar.batch.issue;
 
 import org.sonar.api.BatchComponent;
-import org.sonar.api.issue.Issue;
 import org.sonar.api.resources.Resource;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.api.rules.Violation;
 import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.DefaultIssueBuilder;
 
 import java.util.Collection;
-import java.util.UUID;
 
 public class DeprecatedViolations implements BatchComponent {
 
@@ -47,21 +46,12 @@ public class DeprecatedViolations implements BatchComponent {
   }
 
   DefaultIssue toIssue(Violation violation) {
-    DefaultIssue issue = new DefaultIssue()
-      .setComponentKey(violation.getResource().getEffectiveKey())
-      .setKey(UUID.randomUUID().toString())
-      .setRuleKey(RuleKey.of(violation.getRule().getRepositoryKey(), violation.getRule().getKey()))
-      .setCost(violation.getCost())
-      .setLine(violation.getLineId())
-      .setDescription(violation.getMessage())
-      .setResolution(Issue.RESOLUTION_OPEN)
-      .setStatus(Issue.STATUS_OPEN)
-      .setManualSeverity(false)
-      .setManual(false)
-      .setSeverity(violation.getSeverity() != null ? violation.getSeverity().name() : null);
-
-    // FIXME
-    //issue.setPerson(violation.getPersonId());
-    return issue;
+    return (DefaultIssue) new DefaultIssueBuilder(violation.getResource().getEffectiveKey())
+      .ruleKey(RuleKey.of(violation.getRule().getRepositoryKey(), violation.getRule().getKey()))
+      .cost(violation.getCost())
+      .line(violation.getLineId())
+      .description(violation.getMessage())
+      .severity(violation.getSeverity() != null ? violation.getSeverity().name() : null)
+      .build();
   }
 }
index b1dcdaf2993bb162c86713d4b7d45f511cd42abd..4719c96353dadb653e1a9b6cf8fe1c41207a6b36 100644 (file)
@@ -28,7 +28,6 @@ import org.sonar.api.rules.ActiveRule;
 import org.sonar.core.issue.DefaultIssue;
 
 import java.util.Collection;
-import java.util.UUID;
 
 /**
  * Central component to manage issues
@@ -65,10 +64,8 @@ public class ScanIssues {
       // rule does not exist or is not enabled -> ignore the issue
       return false;
     }
-    String key = UUID.randomUUID().toString();
-    Preconditions.checkState(!Strings.isNullOrEmpty(key), "Fail to generate issue key");
-    issue.setKey(key);
     issue.setCreatedAt(project.getAnalysisDate());
+    issue.setUpdatedAt(project.getAnalysisDate());
     if (issue.severity() == null) {
       issue.setSeverity(activeRule.getSeverity().name());
     }
index e73daf25d5beffe07a414b2583a12d452b00af77..6fd278c0e05e53b04ab8a8a93cf1bc272f9f196f 100644 (file)
@@ -30,24 +30,8 @@ import org.sonar.batch.DefaultFileLinesContextFactory;
 import org.sonar.batch.DefaultResourceCreationLock;
 import org.sonar.batch.ProjectConfigurator;
 import org.sonar.batch.ProjectTree;
-import org.sonar.batch.bootstrap.BatchSettings;
-import org.sonar.batch.bootstrap.ExtensionInstaller;
-import org.sonar.batch.bootstrap.ExtensionMatcher;
-import org.sonar.batch.bootstrap.ExtensionUtils;
-import org.sonar.batch.bootstrap.MetricProvider;
-import org.sonar.batch.index.Caches;
-import org.sonar.batch.index.ComponentDataCache;
-import org.sonar.batch.index.ComponentDataPersister;
-import org.sonar.batch.index.DefaultIndex;
-import org.sonar.batch.index.DefaultPersistenceManager;
-import org.sonar.batch.index.DefaultResourcePersister;
-import org.sonar.batch.index.DependencyPersister;
-import org.sonar.batch.index.EventPersister;
-import org.sonar.batch.index.LinkPersister;
-import org.sonar.batch.index.MeasurePersister;
-import org.sonar.batch.index.MemoryOptimizer;
-import org.sonar.batch.index.SnapshotCache;
-import org.sonar.batch.index.SourcePersister;
+import org.sonar.batch.bootstrap.*;
+import org.sonar.batch.index.*;
 import org.sonar.batch.issue.DeprecatedViolations;
 import org.sonar.batch.issue.IssueCache;
 import org.sonar.batch.issue.IssuePersister;
@@ -58,6 +42,8 @@ import org.sonar.batch.scan.maven.MavenPluginExecutor;
 import org.sonar.batch.scan.source.HighlightableBuilder;
 import org.sonar.batch.scan.source.SymbolPerspectiveBuilder;
 import org.sonar.core.component.ScanGraph;
+import org.sonar.core.issue.IssueUpdater;
+import org.sonar.core.issue.workflow.FunctionExecutor;
 import org.sonar.core.issue.workflow.IssueWorkflow;
 import org.sonar.core.notification.DefaultNotificationManager;
 import org.sonar.core.test.TestPlanBuilder;
@@ -83,44 +69,46 @@ public class ProjectScanContainer extends ComponentContainer {
 
   private void addBatchComponents() {
     add(
-        DefaultResourceCreationLock.class,
-        DefaultPersistenceManager.class,
-        DependencyPersister.class,
-        EventPersister.class,
-        LinkPersister.class,
-        MeasurePersister.class,
-        MemoryOptimizer.class,
-        DefaultResourcePersister.class,
-        SourcePersister.class,
-        DefaultNotificationManager.class,
-        MetricProvider.class,
-        ProjectConfigurator.class,
-        DefaultIndex.class,
-        DefaultFileLinesContextFactory.class,
-        ProjectLock.class,
-        LastSnapshots.class,
-        Caches.class,
-        SnapshotCache.class,
-        ComponentDataCache.class,
-        ComponentDataPersister.class,
+      DefaultResourceCreationLock.class,
+      DefaultPersistenceManager.class,
+      DependencyPersister.class,
+      EventPersister.class,
+      LinkPersister.class,
+      MeasurePersister.class,
+      MemoryOptimizer.class,
+      DefaultResourcePersister.class,
+      SourcePersister.class,
+      DefaultNotificationManager.class,
+      MetricProvider.class,
+      ProjectConfigurator.class,
+      DefaultIndex.class,
+      DefaultFileLinesContextFactory.class,
+      ProjectLock.class,
+      LastSnapshots.class,
+      Caches.class,
+      SnapshotCache.class,
+      ComponentDataCache.class,
+      ComponentDataPersister.class,
 
-        // issues
-        IssueWorkflow.class,
-        DeprecatedViolations.class,
-        IssueCache.class,
-        IssuePersister.class,
+      // issues
+      IssueUpdater.class,
+      FunctionExecutor.class,
+      IssueWorkflow.class,
+      DeprecatedViolations.class,
+      IssueCache.class,
+      IssuePersister.class,
 
-        // tests
-        TestPlanPerspectiveLoader.class,
-        TestablePerspectiveLoader.class,
-        TestPlanBuilder.class,
-        TestableBuilder.class,
-        ScanGraph.create(),
-        GraphPersister.class,
+      // tests
+      TestPlanPerspectiveLoader.class,
+      TestablePerspectiveLoader.class,
+      TestPlanBuilder.class,
+      TestableBuilder.class,
+      ScanGraph.create(),
+      GraphPersister.class,
 
-        // lang
-        HighlightableBuilder.class,
-        SymbolPerspectiveBuilder.class);
+      // lang
+      HighlightableBuilder.class,
+      SymbolPerspectiveBuilder.class);
   }
 
   private void fixMavenExecutor() {
index 963f0618ffc68c4585d2a7b30bbaccccdaf71676..695d2292f977e342f2511eb48158ecad56f71b3b 100644 (file)
@@ -84,13 +84,15 @@ public class ScanIssuesTest {
     Date analysisDate = new Date();
     when(project.getAnalysisDate()).thenReturn(analysisDate);
 
-    DefaultIssue issue = new DefaultIssue().setRuleKey(RuleKey.of("squid", "AvoidCycle")).setSeverity(Severity.CRITICAL);
+    DefaultIssue issue = new DefaultIssue()
+      .setKey("ABCDE")
+      .setRuleKey(RuleKey.of("squid", "AvoidCycle"))
+      .setSeverity(Severity.CRITICAL);
     boolean added = scanIssues.initAndAddIssue(issue);
 
     assertThat(added).isTrue();
     ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
     verify(cache).put(argument.capture());
-    assertThat(argument.getValue().key()).isNotNull();
     assertThat(argument.getValue().severity()).isEqualTo(Severity.CRITICAL);
     assertThat(argument.getValue().createdAt()).isEqualTo(analysisDate);
   }
@@ -111,7 +113,6 @@ public class ScanIssuesTest {
 
     ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
     verify(cache).put(argument.capture());
-    assertThat(argument.getValue().key()).isNotNull();
     assertThat(argument.getValue().severity()).isEqualTo(Severity.INFO);
     assertThat(argument.getValue().createdAt()).isEqualTo(analysisDate);
   }
index bb6fe07a4c74cd3278d5deeef8c305e0ab8d2fad..23d3dfd82b357aa815121691d9a0efbbc1677c39 100644 (file)
  */
 package org.sonar.core.issue;
 
+import com.google.common.base.Objects;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Maps;
 import org.apache.commons.lang.StringUtils;
 import org.apache.commons.lang.builder.ToStringBuilder;
@@ -36,9 +36,8 @@ import java.io.Serializable;
 import java.util.Collections;
 import java.util.Date;
 import java.util.Map;
-import java.util.Set;
 
-public class DefaultIssue implements Issue, Serializable {
+public class DefaultIssue implements Issue {
 
   private String key;
   private String componentKey;
@@ -60,8 +59,8 @@ public class DefaultIssue implements Issue, Serializable {
   private boolean isNew = true;
   private boolean isAlive = true;
   private Map<String, String> attributes = null;
-
-  private String authorLogin;
+  private String authorLogin = null;
+  private IssueChange change = null;
 
   public String key() {
     return key;
@@ -100,7 +99,7 @@ public class DefaultIssue implements Issue, Serializable {
     return this;
   }
 
-  public boolean isManualSeverity() {
+  public boolean manualSeverity() {
     return manualSeverity;
   }
 
@@ -198,6 +197,7 @@ public class DefaultIssue implements Issue, Serializable {
     return closedAt;
   }
 
+  // TODO rename setClosedDate
   public DefaultIssue setClosedAt(@Nullable Date d) {
     this.closedAt = d;
     return this;
@@ -273,6 +273,20 @@ public class DefaultIssue implements Issue, Serializable {
     return this;
   }
 
+  public DefaultIssue setDiff(String field, @Nullable Serializable oldValue, @Nullable Serializable newValue) {
+    if (!Objects.equal(oldValue, newValue)) {
+      if (change == null) {
+        change = new IssueChange();
+      }
+      change.setDiff(field, oldValue, newValue);
+    }
+    return this;
+  }
+
+  public IssueChange change() {
+    return change;
+  }
+
   @Override
   public boolean equals(Object o) {
     if (this == o) {
@@ -297,6 +311,4 @@ public class DefaultIssue implements Issue, Serializable {
   public String toString() {
     return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
   }
-
-
 }
index eb80c617351cb52d18b21016c28535d031ac3c4c..3ed6d326c568751a2a1901212b71f37acfb428cb 100644 (file)
  */
 package org.sonar.core.issue;
 
+import com.google.common.base.Objects;
 import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
 import com.google.common.collect.Maps;
 import org.sonar.api.issue.Issuable;
 import org.sonar.api.issue.Issue;
 import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rule.Severity;
 
+import java.util.Date;
 import java.util.Map;
+import java.util.UUID;
 
 public class DefaultIssueBuilder implements Issuable.IssueBuilder {
 
@@ -88,18 +93,25 @@ public class DefaultIssueBuilder implements Issuable.IssueBuilder {
   }
 
   @Override
-  public Issue build() {
+  public DefaultIssue build() {
     Preconditions.checkNotNull(componentKey, "Component key must be set");
     Preconditions.checkNotNull(ruleKey, "Rule key must be set");
 
     DefaultIssue issue = new DefaultIssue();
+    String key = UUID.randomUUID().toString();
+    Preconditions.checkState(!Strings.isNullOrEmpty(key), "Fail to generate issue key");
+    issue.setKey(key);
+    Date now = new Date();
+    issue.setCreatedAt(now);
+    issue.setUpdatedAt(now);
     issue.setComponentKey(componentKey);
     issue.setRuleKey(ruleKey);
     issue.setDescription(description);
-    issue.setSeverity(severity);
+    issue.setSeverity(Objects.firstNonNull(severity, Severity.MAJOR));
     issue.setCost(cost);
     issue.setLine(line);
     issue.setManual(manual);
+    issue.setManualSeverity(manual);
     issue.setAttributes(attributes);
     issue.setNew(true);
     issue.setAlive(true);
diff --git a/sonar-core/src/main/java/org/sonar/core/issue/IssueChange.java b/sonar-core/src/main/java/org/sonar/core/issue/IssueChange.java
new file mode 100644 (file)
index 0000000..d64c6b9
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.core.issue;
+
+import com.google.common.collect.Maps;
+
+import javax.annotation.Nullable;
+import java.io.Serializable;
+import java.util.Map;
+
+public class IssueChange implements Serializable {
+
+  public static class Diff<T extends Serializable> implements Serializable {
+    private T before, after;
+
+    public Diff(@Nullable T before, @Nullable T after) {
+      this.before = before;
+      this.after = after;
+    }
+
+    public T before() {
+      return before;
+    }
+
+    public T after() {
+      return after;
+    }
+
+    void setAfter(T t) {
+      this.after = t;
+    }
+  }
+
+  private Map<String, Diff> diffs = Maps.newLinkedHashMap();
+
+  public Map<String, Diff> diffs() {
+    return diffs;
+  }
+
+  @SuppressWarnings("unchecked")
+  public void setDiff(String field, @Nullable Serializable oldValue, @Nullable Serializable newValue) {
+    Diff diff = diffs.get(field);
+    if (diff == null) {
+      diff = new Diff(oldValue, newValue);
+      diffs.put(field, diff);
+    } else {
+      diff.setAfter(newValue);
+    }
+  }
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/issue/IssueChangeContext.java b/sonar-core/src/main/java/org/sonar/core/issue/IssueChangeContext.java
new file mode 100644 (file)
index 0000000..a8422d6
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.core.issue;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import java.io.Serializable;
+import java.util.Date;
+
+public class IssueChangeContext implements Serializable {
+
+  private String login;
+  private Date date;
+  private boolean automatic;
+
+  private IssueChangeContext(@Nullable String login, Date date, boolean automatic) {
+    this.login = login;
+    this.date = date;
+    this.automatic = automatic;
+  }
+
+  @CheckForNull
+  public String login() {
+    return login;
+  }
+
+  public Date date() {
+    return date;
+  }
+
+  public boolean automatic() {
+    return automatic;
+  }
+
+  public static IssueChangeContext createScan(Date date) {
+    return new IssueChangeContext(null, date, true);
+  }
+
+  public static IssueChangeContext createUser(Date date, String login) {
+    return new IssueChangeContext(login, date, false);
+  }
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/issue/IssueUpdater.java b/sonar-core/src/main/java/org/sonar/core/issue/IssueUpdater.java
new file mode 100644 (file)
index 0000000..013e4b2
--- /dev/null
@@ -0,0 +1,110 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.core.issue;
+
+import com.google.common.base.Objects;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.BatchComponent;
+import org.sonar.api.ServerComponent;
+
+import javax.annotation.Nullable;
+import java.util.Date;
+
+public class IssueUpdater implements BatchComponent, ServerComponent {
+
+  public boolean setSeverity(DefaultIssue issue, String severity) {
+    if (!Objects.equal(severity, issue.severity())) {
+      issue.setDiff("severity", issue.severity(), severity);
+      issue.setSeverity(severity);
+      return true;
+    }
+    return false;
+  }
+
+  public DefaultIssue setManualSeverity(DefaultIssue issue, String severity) {
+    if (!issue.manualSeverity() || !Objects.equal(severity, issue.severity())) {
+      issue.setDiff("severity", issue.severity(), severity);
+      issue.setSeverity(severity);
+      issue.setManualSeverity(true);
+    }
+    return issue;
+  }
+
+  public boolean assign(DefaultIssue issue, @Nullable String assignee) {
+    String sanitizedAssignee = StringUtils.defaultIfBlank(assignee, null);
+    if (!Objects.equal(sanitizedAssignee, issue.assignee())) {
+      issue.setDiff("assignee", issue.assignee(), sanitizedAssignee);
+      issue.setAssignee(sanitizedAssignee);
+      return true;
+    }
+    return false;
+  }
+
+  public DefaultIssue setLine(DefaultIssue issue, @Nullable Integer line) {
+    if (!Objects.equal(line, issue.line())) {
+      issue.setLine(line);
+    }
+    return issue;
+  }
+
+  public DefaultIssue setResolution(DefaultIssue issue, String resolution) {
+    if (!Objects.equal(resolution, issue.resolution())) {
+      issue.setDiff("resolution", issue.resolution(), resolution);
+      issue.setResolution(resolution);
+    }
+    return issue;
+  }
+
+  public DefaultIssue setStatus(DefaultIssue issue, String status) {
+    if (!Objects.equal(status, issue.status())) {
+      issue.setDiff("status", issue.status(), status);
+      issue.setStatus(status);
+    }
+    return issue;
+  }
+
+  public DefaultIssue setAuthorLogin(DefaultIssue issue, @Nullable String authorLogin) {
+    if (!Objects.equal(authorLogin, issue.authorLogin())) {
+      issue.setAuthorLogin(authorLogin);
+    }
+    return issue;
+  }
+
+  public DefaultIssue setDescription(DefaultIssue issue, @Nullable String description) {
+    if (!Objects.equal(description, issue.description())) {
+      if (issue.manual()) {
+        issue.setDiff("description", issue.description(), description);
+      }
+      issue.setDescription(description);
+    }
+    return issue;
+  }
+
+  public DefaultIssue setClosedDate(DefaultIssue issue, @Nullable Date date) {
+    if (!Objects.equal(date, issue.closedAt())) {
+      issue.setDiff("closedDate", issue.closedAt(), date);
+      issue.setClosedAt(date);
+    }
+    return issue;
+  }
+
+  // TODO setAttribute
+  // TODO comment
+}
index 4c5385c390bee07f75b8ac06037d9491597a4833..40ea1e5741115caf98432e0a28ce9988be6e44f0 100644 (file)
@@ -308,7 +308,7 @@ public final class IssueDto {
       .setSeverity(issue.severity())
       .setChecksum(issue.getChecksum())
       .setManualIssue(issue.manual())
-      .setManualSeverity(issue.isManualSeverity())
+      .setManualSeverity(issue.manualSeverity())
       .setUserLogin(issue.userLogin())
       .setAssignee(issue.assignee())
       .setCreatedAt(issue.createdAt())
index a45a595acbdf78afced8533e945ca89b6a8ed712..82b79dc1ec96c146f88ee4d188559d68000782ee 100644 (file)
  */
 package org.sonar.core.issue.workflow;
 
-import org.sonar.core.issue.DefaultIssue;
+import org.sonar.api.issue.Issue;
 
 interface Function {
-  void execute(DefaultIssue issue);
+  interface Context {
+    Issue issue();
+    Context setResolution(String s);
+    Context setClosedDate(boolean b);
+  }
+
+  void execute(Context context);
 }
diff --git a/sonar-core/src/main/java/org/sonar/core/issue/workflow/FunctionExecutor.java b/sonar-core/src/main/java/org/sonar/core/issue/workflow/FunctionExecutor.java
new file mode 100644 (file)
index 0000000..a54badf
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.core.issue.workflow;
+
+import org.sonar.api.BatchComponent;
+import org.sonar.api.ServerComponent;
+import org.sonar.api.issue.Issue;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.IssueChangeContext;
+import org.sonar.core.issue.IssueUpdater;
+
+public class FunctionExecutor implements BatchComponent, ServerComponent {
+
+  private final IssueUpdater updater;
+
+  public FunctionExecutor(IssueUpdater updater) {
+    this.updater = updater;
+  }
+
+  public void execute(Function[] functions, DefaultIssue issue, IssueChangeContext changeContext) {
+    if (functions.length > 0) {
+      FunctionContext functionContext = new FunctionContext(updater, issue, changeContext);
+      for (Function function : functions) {
+        function.execute(functionContext);
+      }
+    }
+  }
+
+  static class FunctionContext implements Function.Context {
+
+    private final IssueUpdater updater;
+    private final DefaultIssue issue;
+    private final IssueChangeContext changeContext;
+
+    FunctionContext(IssueUpdater updater, DefaultIssue issue, IssueChangeContext changeContext) {
+      this.updater = updater;
+      this.issue = issue;
+      this.changeContext = changeContext;
+    }
+
+    @Override
+    public Issue issue() {
+      return issue;
+    }
+
+    @Override
+    public Function.Context setResolution(String s) {
+      updater.setResolution(issue, s);
+      return this;
+    }
+
+    @Override
+    public Function.Context setClosedDate(boolean b) {
+      updater.setClosedDate(issue, b ? changeContext.date() : null);
+      return null;
+    }
+  }
+}
index 3d630bc4f4c225ab326e2d27c289036bb4df46e5..f536d2c534cfd513a2b5a006a7ef57e50279da73 100644 (file)
@@ -25,30 +25,23 @@ import org.sonar.api.ServerComponent;
 import org.sonar.api.issue.DefaultTransitions;
 import org.sonar.api.issue.Issue;
 import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.IssueChangeContext;
 
 import java.util.List;
 
 public class IssueWorkflow implements BatchComponent, ServerComponent, Startable {
 
   private StateMachine machine;
+  private final FunctionExecutor functionExecutor;
+
+  public IssueWorkflow(FunctionExecutor functionExecutor) {
+    this.functionExecutor = functionExecutor;
+  }
 
   @Override
   public void start() {
     machine = StateMachine.builder()
       .states(Issue.STATUS_OPEN, Issue.STATUS_REOPENED, Issue.STATUS_RESOLVED, Issue.STATUS_CLOSED)
-      .transition(Transition.builder(DefaultTransitions.CLOSE)
-        .from(Issue.STATUS_OPEN).to(Issue.STATUS_CLOSED)
-        .functions(new SetResolution(Issue.RESOLUTION_FIXED), SetClosedAt.CLOSED_AT)
-        .build())
-      .transition(Transition.builder(DefaultTransitions.CLOSE)
-        .from(Issue.STATUS_RESOLVED).to(Issue.STATUS_CLOSED)
-        .functions(SetClosedAt.CLOSED_AT)
-        .build())
-      .transition(Transition.builder(DefaultTransitions.CLOSE)
-        .from(Issue.STATUS_REOPENED).to(Issue.STATUS_CLOSED)
-        .functions(SetClosedAt.CLOSED_AT)
-        .functions(new SetResolution(Issue.RESOLUTION_FIXED))
-        .build())
       .transition(Transition.builder(DefaultTransitions.RESOLVE)
         .from(Issue.STATUS_OPEN).to(Issue.STATUS_RESOLVED)
         .functions(new SetResolution(Issue.RESOLUTION_FIXED))
@@ -63,7 +56,7 @@ public class IssueWorkflow implements BatchComponent, ServerComponent, Startable
         .build())
       .transition(Transition.builder(DefaultTransitions.REOPEN)
         .from(Issue.STATUS_CLOSED).to(Issue.STATUS_REOPENED)
-        .functions(new SetResolution(Issue.RESOLUTION_OPEN), new UnsetClosedAt())
+        .functions(new SetResolution(Issue.RESOLUTION_OPEN), new SetClosedDate(false))
         .build())
       .transition(Transition.builder(DefaultTransitions.FALSE_POSITIVE)
         .from(Issue.STATUS_OPEN).to(Issue.STATUS_RESOLVED)
@@ -82,13 +75,13 @@ public class IssueWorkflow implements BatchComponent, ServerComponent, Startable
       .transition(Transition.builder("automaticclose")
         .from(Issue.STATUS_OPEN).to(Issue.STATUS_CLOSED)
         .conditions(new IsAlive(false), new IsManual(false))
-        .functions(new SetResolution(Issue.RESOLUTION_FIXED), SetClosedAt.CLOSED_AT)
+        .functions(new SetResolution(Issue.RESOLUTION_FIXED), new SetClosedDate(true))
         .automatic()
         .build())
       .transition(Transition.builder("automaticclose")
         .from(Issue.STATUS_REOPENED).to(Issue.STATUS_CLOSED)
         .conditions(new IsAlive(false))
-        .functions(new SetResolution(Issue.RESOLUTION_FIXED), SetClosedAt.CLOSED_AT)
+        .functions(new SetResolution(Issue.RESOLUTION_FIXED), new SetClosedDate(true))
         .automatic()
         .build())
         // Close the issues marked as resolved and that do not exist anymore.
@@ -96,7 +89,7 @@ public class IssueWorkflow implements BatchComponent, ServerComponent, Startable
       .transition(Transition.builder("automaticclose")
         .from(Issue.STATUS_RESOLVED).to(Issue.STATUS_CLOSED)
         .conditions(new IsAlive(false))
-        .functions(SetClosedAt.CLOSED_AT)
+        .functions(new SetClosedDate(true))
         .automatic()
         .build())
       .transition(Transition.builder("automaticreopen")
@@ -112,24 +105,26 @@ public class IssueWorkflow implements BatchComponent, ServerComponent, Startable
   public void stop() {
   }
 
-  public boolean doManualTransition(DefaultIssue issue, String transitionKey) {
+  public boolean doTransition(DefaultIssue issue, String transitionKey, IssueChangeContext issueChangeContext) {
     Transition transition = machine.state(issue.status()).transition(transitionKey);
     if (transition != null && !transition.automatic()) {
-      transition.execute(issue);
+      functionExecutor.execute(transition.functions(), issue, issueChangeContext);
+      issue.setStatus(transition.to());
       return true;
     }
     return false;
   }
 
-  public List<Transition> outManualTransitions(Issue issue) {
+  public List<Transition> outTransitions(Issue issue) {
     return machine.state(issue.status()).outManualTransitions(issue);
   }
 
 
-  public void doAutomaticTransition(DefaultIssue issue) {
+  public void doAutomaticTransition(DefaultIssue issue, IssueChangeContext issueChangeContext) {
     Transition transition = machine.state(issue.status()).outAutomaticTransition(issue);
     if (transition != null) {
-      transition.execute(issue);
+      functionExecutor.execute(transition.functions(), issue, issueChangeContext);
+      issue.setStatus(transition.to());
     }
   }
 
diff --git a/sonar-core/src/main/java/org/sonar/core/issue/workflow/SetClosedAt.java b/sonar-core/src/main/java/org/sonar/core/issue/workflow/SetClosedAt.java
deleted file mode 100644 (file)
index ca2f47c..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.core.issue.workflow;
-
-import org.sonar.core.issue.DefaultIssue;
-
-import java.util.Date;
-
-class SetClosedAt implements Function {
-  static final SetClosedAt CLOSED_AT = new SetClosedAt();
-
-  private SetClosedAt() {
-  }
-
-  @Override
-  public void execute(DefaultIssue issue) {
-    issue.setClosedAt(new Date());
-  }
-}
diff --git a/sonar-core/src/main/java/org/sonar/core/issue/workflow/SetClosedDate.java b/sonar-core/src/main/java/org/sonar/core/issue/workflow/SetClosedDate.java
new file mode 100644 (file)
index 0000000..203cc47
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.core.issue.workflow;
+
+public class SetClosedDate implements Function {
+  private final boolean set;
+
+  public SetClosedDate(boolean set) {
+    this.set = set;
+  }
+
+  @Override
+  public void execute(Context context) {
+    context.setClosedDate(set);
+  }
+}
index c18bbc5493f87cd4f3614f41f202d7219f69f47a..e5147d4bf9986a680f58018dc16adb19ecbff32f 100644 (file)
@@ -21,19 +21,17 @@ package org.sonar.core.issue.workflow;
 
 import com.google.common.base.Preconditions;
 import com.google.common.base.Strings;
-import org.sonar.core.issue.DefaultIssue;
 
-class SetResolution implements Function {
+public class SetResolution implements Function {
   private final String resolution;
 
-  SetResolution(String resolution) {
+  public SetResolution(String resolution) {
     Preconditions.checkArgument(!Strings.isNullOrEmpty(resolution), "Resolution must be set");
     this.resolution = resolution;
   }
 
   @Override
-  public void execute(DefaultIssue issue) {
-    issue.setResolution(resolution);
+  public void execute(Context context) {
+    context.setResolution(resolution);
   }
-
 }
index 632b944f71189928c828284211ed32d0b73c1cc2..ce4b4aecdb2220ebdcbc1de522a86e1ecd717271 100644 (file)
@@ -24,10 +24,8 @@ import com.google.common.base.Strings;
 import com.google.common.collect.Lists;
 import org.apache.commons.lang.StringUtils;
 import org.sonar.api.issue.Issue;
-import org.sonar.core.issue.DefaultIssue;
 
 import java.util.Arrays;
-import java.util.Date;
 import java.util.List;
 
 public class Transition {
@@ -79,13 +77,6 @@ public class Transition {
     return true;
   }
 
-  public void execute(DefaultIssue issue) {
-    for (Function function : functions) {
-      function.execute(issue);
-    }
-    issue.setStatus(to);
-    issue.setUpdatedAt(new Date());
-  }
 
   public static TransitionBuilder builder(String key) {
     return new TransitionBuilder(key);
diff --git a/sonar-core/src/main/java/org/sonar/core/issue/workflow/UnsetClosedAt.java b/sonar-core/src/main/java/org/sonar/core/issue/workflow/UnsetClosedAt.java
deleted file mode 100644 (file)
index 87540a1..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.core.issue.workflow;
-
-import org.sonar.core.issue.DefaultIssue;
-
-class UnsetClosedAt implements Function {
-
-  @Override
-  public void execute(DefaultIssue issue) {
-    issue.setClosedAt(null);
-  }
-
-}
index 4a0834f8fad56360abe541d378c518dc5d2dc12d..e625470bedc8bfb982bdf2bcc24e1a00f3f54d36 100644 (file)
@@ -42,7 +42,7 @@ public class DefaultIssueBuilderTest {
       .build();
 
     assertThat(issue).isNotNull();
-    assertThat(issue.key()).isNull();
+    assertThat(issue.key()).isNotNull();
     assertThat(issue.cost()).isEqualTo(10000.0);
     assertThat(issue.componentKey()).isEqualTo(componentKey);
     assertThat(issue.description()).isEqualTo("the desc");
@@ -50,7 +50,7 @@ public class DefaultIssueBuilderTest {
     assertThat(issue.ruleKey().repository()).isEqualTo("squid");
     assertThat(issue.ruleKey().rule()).isEqualTo("NullDereference");
     assertThat(issue.severity()).isEqualTo(Severity.CRITICAL);
-    assertThat(issue.updatedAt()).isNull();
+    assertThat(issue.updatedAt()).isNotNull();
     assertThat(issue.closedAt()).isNull();
     assertThat(issue.assignee()).isNull();
     assertThat(issue.isNew()).isTrue();
index 8600e27ba998d7853f249346557c65682cbec1dd..89aeb4fbd5a7756c9e7abac9daa57a647ecc4a9a 100644 (file)
@@ -25,7 +25,6 @@ import org.junit.Test;
 import org.junit.rules.ExpectedException;
 import org.sonar.api.issue.Issue;
 import org.sonar.core.issue.DefaultIssue;
-import org.sonar.core.issue.db.IssueDto;
 
 import java.util.Date;
 
@@ -86,7 +85,7 @@ public class IssueDtoTest {
     assertThat(issue.line()).isEqualTo(6);
     assertThat(issue.severity()).isEqualTo("BLOCKER");
     assertThat(issue.description()).isEqualTo("message");
-    assertThat(issue.isManualSeverity()).isTrue();
+    assertThat(issue.manualSeverity()).isTrue();
     assertThat(issue.manual()).isTrue();
     assertThat(issue.userLogin()).isEqualTo("arthur");
     assertThat(issue.assignee()).isEqualTo("perceval");
index 77abe01720908dbed3bb766ac9871a82c427dbbe..19d07e6c2134421498b5f74cd65a250fd9f7bbb7 100644 (file)
@@ -24,15 +24,19 @@ import com.google.common.collect.Collections2;
 import org.junit.Test;
 import org.sonar.api.issue.Issue;
 import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.IssueChangeContext;
+import org.sonar.core.issue.IssueUpdater;
 
 import javax.annotation.Nullable;
 import java.util.Collection;
+import java.util.Date;
 import java.util.List;
 
 import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
 
 public class IssueWorkflowTest {
-  IssueWorkflow workflow = new IssueWorkflow();
+  IssueWorkflow workflow = new IssueWorkflow(new FunctionExecutor(new IssueUpdater()));
 
   @Test
   public void should_init_state_machine() throws Exception {
@@ -51,9 +55,9 @@ public class IssueWorkflowTest {
     workflow.start();
 
     DefaultIssue issue = new DefaultIssue().setStatus(Issue.STATUS_OPEN);
-    List<Transition> transitions = workflow.outManualTransitions(issue);
-    assertThat(transitions).hasSize(3);
-    assertThat(keys(transitions)).containsOnly("close", "falsepositive", "resolve");
+    List<Transition> transitions = workflow.outTransitions(issue);
+    assertThat(transitions).hasSize(2);
+    assertThat(keys(transitions)).containsOnly("falsepositive", "resolve");
   }
 
   @Test
@@ -65,7 +69,7 @@ public class IssueWorkflowTest {
       .setStatus(Issue.STATUS_RESOLVED)
       .setNew(false)
       .setAlive(false);
-    workflow.doAutomaticTransition(issue);
+    workflow.doAutomaticTransition(issue, IssueChangeContext.createScan(new Date()));
     assertThat(issue.resolution()).isEqualTo(Issue.RESOLUTION_FIXED);
     assertThat(issue.status()).isEqualTo(Issue.STATUS_CLOSED);
     assertThat(issue.closedAt()).isNotNull();
diff --git a/sonar-core/src/test/java/org/sonar/core/issue/workflow/SetResolutionTest.java b/sonar-core/src/test/java/org/sonar/core/issue/workflow/SetResolutionTest.java
deleted file mode 100644 (file)
index 2ab0651..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.core.issue.workflow;
-
-import org.junit.Test;
-import org.sonar.api.issue.Issue;
-import org.sonar.core.issue.DefaultIssue;
-
-import static org.fest.assertions.Assertions.assertThat;
-import static org.fest.assertions.Fail.fail;
-
-public class SetResolutionTest {
-  @Test
-  public void should_set_resolution() throws Exception {
-    DefaultIssue issue = new DefaultIssue();
-    SetResolution function = new SetResolution(Issue.RESOLUTION_FIXED);
-    function.execute(issue);
-    assertThat(issue.resolution()).isEqualTo(Issue.RESOLUTION_FIXED);
-  }
-
-  @Test
-  public void resolution_should_not_be_empty() throws Exception {
-    try {
-      new SetResolution("");
-      fail();
-    } catch (IllegalArgumentException e) {
-      assertThat(e).hasMessage("Resolution must be set");
-    }
-  }
-}
index 4f068afe6cc35b4d68c4c69018d30a662f4f167d..caf383720f48127766c00a11015f664b953a7f28 100644 (file)
@@ -25,7 +25,6 @@ import org.sonar.core.issue.DefaultIssue;
 import static org.fest.assertions.Assertions.assertThat;
 import static org.fest.assertions.Fail.fail;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 public class TransitionTest {
@@ -101,22 +100,6 @@ public class TransitionTest {
     }
   }
 
-  @Test
-  public void should_execute_functions() throws Exception {
-    DefaultIssue issue = new DefaultIssue();
-    Transition transition = Transition.builder("close")
-      .from("OPEN").to("CLOSED")
-      .conditions(condition1, condition2)
-      .functions(function1, function2)
-      .build();
-    transition.execute(issue);
-
-    assertThat(issue.status()).isEqualTo("CLOSED");
-    assertThat(issue.updatedAt()).isNotNull();
-    verify(function1).execute(issue);
-    verify(function2).execute(issue);
-  }
-
   @Test
   public void should_verify_conditions() throws Exception {
     DefaultIssue issue = new DefaultIssue();
diff --git a/sonar-core/src/test/java/org/sonar/core/issue/workflow/UnsetClosedAtTest.java b/sonar-core/src/test/java/org/sonar/core/issue/workflow/UnsetClosedAtTest.java
deleted file mode 100644 (file)
index e74f78b..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.core.issue.workflow;
-
-import org.junit.Test;
-import org.sonar.core.issue.DefaultIssue;
-
-import java.util.Date;
-
-import static org.fest.assertions.Assertions.assertThat;
-
-public class UnsetClosedAtTest {
-  @Test
-  public void should_remove_date() throws Exception {
-    UnsetClosedAt function = new UnsetClosedAt();
-    DefaultIssue issue = new DefaultIssue().setCreatedAt(new Date()).setCreatedAt(new Date());
-    function.execute(issue);
-    assertThat(issue.closedAt()).isNull();
-    assertThat(issue.createdAt()).isNotNull();
-  }
-}
index 6ba7c0edb7cc55887d139b7f4bc62ef17c1280b5..8b7a6aadcc4ec6a11872bfc9080c34296e59c1c9 100644 (file)
@@ -28,31 +28,29 @@ import javax.annotation.Nullable;
  */
 public interface IssueHandler extends BatchExtension {
 
-  interface IssueContext {
+  interface Context {
     Issue issue();
 
     boolean isNew();
 
     boolean isAlive();
 
-    IssueContext setLine(@Nullable Integer line);
+    Context setLine(@Nullable Integer line);
 
-    IssueContext setDescription(String description);
+    Context setDescription(@Nullable String description);
 
-    // set manual severity ?
-    IssueContext setSeverity(String severity);
+    Context setSeverity(String severity);
 
-    // TODO rename to setScmLogin ?
-    IssueContext setAuthorLogin(@Nullable String login);
+    Context setAuthorLogin(@Nullable String login);
 
-    IssueContext setAttribute(String key, @Nullable String value);
+    Context setAttribute(String key, @Nullable String value);
 
-    IssueContext assignTo(@Nullable String login);
+    Context assign(@Nullable String login);
 
-    //TODO IssueContext comment(String comment);
+    Context comment(String message);
 
   }
 
-  void onIssue(IssueContext context);
+  void onIssue(Context context);
 
 }
index fab07960dcd7c93430884968cf276f5284e34e32..a529f1206fd234a79a654b1f6df146a2b1bbef67 100644 (file)
@@ -21,10 +21,14 @@ package org.sonar.server.issue;
 
 import org.sonar.api.ServerComponent;
 import org.sonar.api.issue.Issue;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.DefaultIssueBuilder;
 import org.sonar.core.issue.workflow.Transition;
 import org.sonar.server.platform.UserSession;
 
 import java.util.List;
+import java.util.Map;
 
 /**
  * All the issue features that are not published to public API
@@ -38,10 +42,34 @@ public class JRubyInternalIssues implements ServerComponent {
   }
 
   public List<Transition> listTransitions(String issueKey) {
-    return actions.listTransitions(issueKey, UserSession.get().userId());
+    return actions.listTransitions(issueKey, UserSession.get());
   }
 
   public Issue doTransition(String issueKey, String transitionKey) {
     return actions.doTransition(issueKey, transitionKey, UserSession.get());
   }
+
+  public Issue assign(String issueKey, String transitionKey) {
+    return actions.assign(issueKey, transitionKey, UserSession.get());
+  }
+
+  public Issue setSeverity(String issueKey, String severity) {
+    return actions.setSeverity(issueKey, severity, UserSession.get());
+  }
+
+  public Issue create(Map<String, String> parameters) {
+    String componentKey = parameters.get("component");
+    // TODO verify authorization
+    // TODO check existence of component
+    DefaultIssueBuilder builder = new DefaultIssueBuilder(componentKey);
+    String line = parameters.get("line");
+    builder.line(line != null ? Integer.parseInt(line) : null);
+    builder.description(parameters.get("description"));
+    builder.severity(parameters.get("severity"));
+    // TODO verify existence of rule
+    builder.ruleKey(RuleKey.parse(parameters.get("rule")));
+    builder.manual(true);
+    Issue issue = builder.build();
+    return actions.create((DefaultIssue) issue, UserSession.get());
+  }
 }
index cb902736e472fbae3476ad8f53804d7a556edaa3..e56f966fc320be2128fda9f323ed7903747f4832 100644 (file)
@@ -21,19 +21,25 @@ package org.sonar.server.issue;
 
 import org.sonar.api.ServerComponent;
 import org.sonar.api.issue.Issue;
-import org.sonar.api.issue.IssueFinder;
+import org.sonar.api.rules.Rule;
+import org.sonar.api.rules.RuleFinder;
 import org.sonar.api.web.UserRole;
 import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.IssueChangeContext;
+import org.sonar.core.issue.IssueUpdater;
 import org.sonar.core.issue.db.IssueDao;
 import org.sonar.core.issue.db.IssueDto;
 import org.sonar.core.issue.workflow.IssueWorkflow;
 import org.sonar.core.issue.workflow.Transition;
+import org.sonar.core.resource.ResourceDao;
+import org.sonar.core.resource.ResourceDto;
+import org.sonar.core.resource.ResourceQuery;
 import org.sonar.core.user.AuthorizationDao;
 import org.sonar.server.platform.UserSession;
 
 import javax.annotation.Nullable;
 import java.util.Arrays;
-import java.util.Collections;
+import java.util.Date;
 import java.util.List;
 
 /**
@@ -42,27 +48,73 @@ import java.util.List;
 public class ServerIssueActions implements ServerComponent {
 
   private final IssueWorkflow workflow;
-  private final IssueFinder finder;
   private final IssueDao issueDao;
   private final AuthorizationDao authorizationDao;
+  private final ResourceDao resourceDao;
+  private final RuleFinder ruleFinder;
+  private final IssueUpdater issueUpdater;
 
-  public ServerIssueActions(IssueWorkflow workflow, IssueFinder finder, IssueDao issueDao, AuthorizationDao authorizationDao) {
+  public ServerIssueActions(IssueWorkflow workflow, IssueDao issueDao,
+                            AuthorizationDao authorizationDao, ResourceDao resourceDao, RuleFinder ruleFinder, IssueUpdater issueUpdater) {
     this.workflow = workflow;
-    this.finder = finder;
     this.issueDao = issueDao;
     this.authorizationDao = authorizationDao;
+    this.resourceDao = resourceDao;
+    this.ruleFinder = ruleFinder;
+    this.issueUpdater = issueUpdater;
   }
 
-  public List<Transition> listTransitions(String issueKey, @Nullable Integer userId) {
-    Issue issue = finder.findByKey(issueKey /*, userId */);
-    // TODO check authorization
-    if (issue == null) {
-      return Collections.emptyList();
-    }
-    return workflow.outManualTransitions(issue);
+  public List<Transition> listTransitions(String issueKey, UserSession userSession) {
+    IssueDto dto = loadDto(issueKey, userSession);
+    DefaultIssue issue = dto.toDefaultIssue();
+    return workflow.outTransitions(issue);
   }
 
   public Issue doTransition(String issueKey, String transition, UserSession userSession) {
+    IssueDto dto = loadDto(issueKey, userSession);
+    DefaultIssue issue = dto.toDefaultIssue();
+    IssueChangeContext context = IssueChangeContext.createUser(new Date(), userSession.login());
+    if (workflow.doTransition(issue, transition, context)) {
+      issueDao.update(Arrays.asList(IssueDto.toDto(issue, dto.getResourceId(), dto.getRuleId())));
+    }
+    return issue;
+  }
+
+  public Issue assign(String issueKey, @Nullable String assigneeLogin, UserSession userSession) {
+    IssueDto dto = loadDto(issueKey, userSession);
+    DefaultIssue issue = dto.toDefaultIssue();
+
+    // TODO check that assignee exists
+    if (issueUpdater.assign(issue, assigneeLogin)) {
+      issueDao.update(Arrays.asList(IssueDto.toDto(issue, dto.getResourceId(), dto.getRuleId())));
+    }
+    return issue;
+  }
+
+  public Issue setSeverity(String issueKey, String severity, UserSession userSession) {
+    IssueDto dto = loadDto(issueKey, userSession);
+    DefaultIssue issue = dto.toDefaultIssue();
+
+    if (issueUpdater.setSeverity(issue, severity)) {
+      issueDao.update(Arrays.asList(IssueDto.toDto(issue, dto.getResourceId(), dto.getRuleId())));
+    }
+    return issue;
+  }
+
+  public Issue create(DefaultIssue issue, UserSession userSession) {
+    issue.setManual(true);
+    issue.setUserLogin(userSession.login());
+
+    // TODO check that rule and component exist
+    Rule rule = ruleFinder.findByKey(issue.ruleKey());
+    ResourceDto resourceDto = resourceDao.getResource(ResourceQuery.create().setKey(issue.componentKey()));
+    IssueDto dto = IssueDto.toDto(issue, resourceDto.getId().intValue(), rule.getId());
+    issueDao.insert(dto);
+
+    return issue;
+  }
+
+  private IssueDto loadDto(String issueKey, UserSession userSession) {
     if (!userSession.isLoggedIn()) {
       // must be logged
       throw new IllegalStateException("User is not logged in");
@@ -75,10 +127,6 @@ public class ServerIssueActions implements ServerComponent {
     if (!authorizationDao.isAuthorizedComponentId(dto.getResourceId(), userSession.userId(), requiredRole)) {
       throw new IllegalStateException("User does not have the role " + requiredRole + " required to change the issue: " + issueKey);
     }
-    DefaultIssue issue = dto.toDefaultIssue();
-    if (workflow.doManualTransition(issue, transition)) {
-      issueDao.update(Arrays.asList(IssueDto.toDto(issue, dto.getResourceId(), dto.getRuleId())));
-    }
-    return issue;
+    return dto;
   }
 }
index c02d1a7e1cdd25dc77000090a9e2dd54655bdfc4..896ddb28287d2115813190c9766eb4ba1129712e 100644 (file)
@@ -40,6 +40,8 @@ import org.sonar.core.config.Logback;
 import org.sonar.core.i18n.GwtI18n;
 import org.sonar.core.i18n.I18nManager;
 import org.sonar.core.i18n.RuleI18nManager;
+import org.sonar.core.issue.IssueUpdater;
+import org.sonar.core.issue.workflow.FunctionExecutor;
 import org.sonar.core.issue.workflow.IssueWorkflow;
 import org.sonar.core.measure.MeasureFilterEngine;
 import org.sonar.core.measure.MeasureFilterExecutor;
@@ -240,6 +242,8 @@ public final class Platform {
     servicesContainer.addSingleton(Periods.class);
 
     // issues
+    servicesContainer.addSingleton(IssueUpdater.class);
+    servicesContainer.addSingleton(FunctionExecutor.class);
     servicesContainer.addSingleton(IssueWorkflow.class);
     servicesContainer.addSingleton(ServerIssueActions.class);
     servicesContainer.addSingleton(ServerIssueFinder.class);
index 4886e1e8df747688d0849ab8f0eaecc1c8f74060..f473065fc85df361793009de7d28248d0928254e 100644 (file)
@@ -54,20 +54,54 @@ class Api::IssuesController < Api::ApiController
     issue = Internal.issues.doTransition(params[:issue], params[:transition])
     if issue
       render :json => jsonp({
-        :issue => issue_to_json(issue)
-      })
+                              :issue => issue_to_json(issue)
+                            })
     else
       render :status => 400
     end
   end
 
-  # POST /api/issues/create?severity=xxx>&<resolution=xxx>&component=<component key>
+  # POST /api/issues/add_comment?issue=<key>&text=<text>
+  # Note that the text can also be set in the post body
+  def add_comment
+    verify_post_request
+    require_parameters :issue, :text
+
+    text = Api::Utils.read_post_request_param(:text)
+    Internal.issues.addComment(params[:issue], text)
+    # TODO add more response data ?
+    render :json => jsonp({})
+  end
+
+  # POST /api/issues/assign?issue=<key>&assignee=<optional assignee>
+  # A nil assignee will remove the assignee.
+  def assign
+    verify_post_request
+    require_parameters :issue
+
+    Internal.issues.assign(params[:issue], params[:assignee])
+    # TODO return the assignee
+    render :json => jsonp({})
+  end
+
+  # POST /api/issues/create
+  #
+  # Mandatory parameters
+  # 'component' is the component key
+  # 'rule' includes the repository key and the rule key, for example 'squid:AvoidCycle'
+  #
+  # Optional parameters
+  # 'severity' is in BLOCKER, CRITICAL, ... INFO. Default value is MAJOR.
+  # 'line' starts at 1
+  # 'description' is the plain-text description
+  #
   def create
     verify_post_request
     access_denied unless logged_in?
+    require_parameters :component, :rule
 
-    # TODO
-    render :json => jsonp({})
+    issue = Internal.issues.create(params)
+    render :json => jsonp({:issue => issue_to_json(issue)})
   end
 
   private