]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-5743 New Source Code + SCM WS
authorJean-Baptiste Lievremont <jean-baptiste.lievremont@sonarsource.com>
Fri, 21 Nov 2014 14:50:57 +0000 (15:50 +0100)
committerJean-Baptiste Lievremont <jean-baptiste.lievremont@sonarsource.com>
Mon, 24 Nov 2014 12:27:46 +0000 (13:27 +0100)
24 files changed:
server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java
server/sonar-server/src/main/java/org/sonar/server/source/SourceService.java
server/sonar-server/src/main/java/org/sonar/server/source/index/SourceLineDoc.java
server/sonar-server/src/main/java/org/sonar/server/source/index/SourceLineIndex.java
server/sonar-server/src/main/java/org/sonar/server/source/ws/IndexAction.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/source/ws/SourcesWs.java
server/sonar-server/src/main/resources/org/sonar/server/source/ws/example-index.json [new file with mode: 0644]
server/sonar-server/src/main/resources/org/sonar/server/source/ws/example-show.json
server/sonar-server/src/test/java/org/sonar/server/es/EsTester.java
server/sonar-server/src/test/java/org/sonar/server/source/SourceServiceTest.java
server/sonar-server/src/test/java/org/sonar/server/source/index/SourceLineIndexTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/source/ws/IndexActionTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/source/ws/RawActionTest.java
server/sonar-server/src/test/java/org/sonar/server/source/ws/ScmActionTest.java
server/sonar-server/src/test/java/org/sonar/server/source/ws/ShowActionTest.java
server/sonar-server/src/test/java/org/sonar/server/source/ws/SourcesWsTest.java
server/sonar-server/src/test/resources/org/sonar/server/source/index/SourceLineIndexTest/file1_line1.json [new file with mode: 0644]
server/sonar-server/src/test/resources/org/sonar/server/source/index/SourceLineIndexTest/file1_line2.json [new file with mode: 0644]
server/sonar-server/src/test/resources/org/sonar/server/source/index/SourceLineIndexTest/file1_line3.json [new file with mode: 0644]
server/sonar-server/src/test/resources/org/sonar/server/source/index/SourceLineIndexTest/file2_line1.json [new file with mode: 0644]
server/sonar-server/src/test/resources/org/sonar/server/source/index/SourceLineIndexTest/file2_line2.json [new file with mode: 0644]
server/sonar-server/src/test/resources/org/sonar/server/source/index/SourceLineIndexTest/file2_line3.json [new file with mode: 0644]
server/sonar-server/src/test/resources/org/sonar/server/source/ws/IndexActionTest/show_source.json [new file with mode: 0644]
server/sonar-server/src/test/resources/org/sonar/server/source/ws/IndexActionTest/show_source_with_params_from_and_to.json [new file with mode: 0644]

index b7a594aa4f8540bde242556593a38841d9ad0a4f..ec5e1f47fbf0feb9fa7ffc072297cb6bb9c66548 100644 (file)
@@ -545,6 +545,7 @@ class ServerComponents {
     pico.addSingleton(SourceService.class);
     pico.addSingleton(SourcesWs.class);
     pico.addSingleton(ShowAction.class);
+    pico.addSingleton(IndexAction.class);
     pico.addSingleton(ScmWriter.class);
     pico.addSingleton(RawAction.class);
     pico.addSingleton(ScmAction.class);
index 65e8843f73dc990cac8bb729340a275257cd4b98..d86f24b683bd4dda919dc0c3898ee50ccd9d4493 100644 (file)
@@ -21,6 +21,7 @@
 package org.sonar.server.source;
 
 import com.google.common.base.Splitter;
+import org.elasticsearch.common.collect.Lists;
 import org.sonar.api.ServerComponent;
 import org.sonar.api.measures.CoreMetrics;
 import org.sonar.api.web.UserRole;
@@ -30,6 +31,8 @@ import org.sonar.core.persistence.DbSession;
 import org.sonar.core.persistence.MyBatis;
 import org.sonar.core.source.db.SnapshotSourceDao;
 import org.sonar.server.db.DbClient;
+import org.sonar.server.source.index.SourceLineDoc;
+import org.sonar.server.source.index.SourceLineIndex;
 import org.sonar.server.user.UserSession;
 
 import javax.annotation.CheckForNull;
@@ -44,17 +47,20 @@ public class SourceService implements ServerComponent {
   private final DbClient dbClient;
   private final HtmlSourceDecorator sourceDecorator;
   private final SnapshotSourceDao snapshotSourceDao;
+  private final SourceLineIndex sourceLineIndex;
 
   /**
    * Old service to colorize code
    */
   private final DeprecatedSourceDecorator deprecatedSourceDecorator;
 
-  public SourceService(DbClient dbClient, HtmlSourceDecorator sourceDecorator, SnapshotSourceDao snapshotSourceDao, DeprecatedSourceDecorator deprecatedSourceDecorator) {
+  public SourceService(DbClient dbClient, HtmlSourceDecorator sourceDecorator, SnapshotSourceDao snapshotSourceDao, DeprecatedSourceDecorator deprecatedSourceDecorator,
+      SourceLineIndex sourceLineIndex) {
     this.dbClient = dbClient;
     this.sourceDecorator = sourceDecorator;
     this.snapshotSourceDao = snapshotSourceDao;
     this.deprecatedSourceDecorator = deprecatedSourceDecorator;
+    this.sourceLineIndex = sourceLineIndex;
   }
 
   @CheckForNull
@@ -87,6 +93,17 @@ public class SourceService implements ServerComponent {
     return null;
   }
 
+  /**
+   * Raw lines of source file.
+   */
+  public List<String> getLinesAsTxt(String fileUuid) {
+    List<String> lines = Lists.newArrayList();
+    for (SourceLineDoc lineDoc: sourceLineIndex.getLines(fileUuid, 1, Integer.MAX_VALUE)) {
+      lines.add(lineDoc.source());
+    }
+    return lines;
+  }
+
   @CheckForNull
   public String getScmAuthorData(String fileKey) {
     checkPermission(fileKey);
index 1f90dc638ad9f55159afc38e656fdeabc02b2876..ae7849c96aa859d6c4b801209c69a655fdc8f12b 100644 (file)
@@ -22,6 +22,9 @@ package org.sonar.server.source.index;
 import org.sonar.server.search.BaseDoc;
 import org.sonar.server.search.BaseNormalizer;
 
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
 import java.util.Date;
 import java.util.Map;
 
@@ -55,27 +58,30 @@ public class SourceLineDoc extends BaseDoc {
     setField(SourceLineIndexDefinition.FIELD_LINE, line);
   }
 
+  @CheckForNull
   public String scmRevision() {
-    return getField(SourceLineIndexDefinition.FIELD_SCM_REVISION);
+    return getNullableField(SourceLineIndexDefinition.FIELD_SCM_REVISION);
   }
 
   public void setScmRevision(String scmRevision) {
     setField(SourceLineIndexDefinition.FIELD_SCM_REVISION, scmRevision);
   }
 
+  @CheckForNull
   public String scmAuthor() {
-    return getField(SourceLineIndexDefinition.FIELD_SCM_AUTHOR);
+    return getNullableField(SourceLineIndexDefinition.FIELD_SCM_AUTHOR);
   }
 
   public void setScmAuthor(String scmAuthor) {
     setField(SourceLineIndexDefinition.FIELD_SCM_AUTHOR, scmAuthor);
   }
 
+  @CheckForNull
   public Date scmDate() {
-    return getField(SourceLineIndexDefinition.FIELD_SCM_DATE);
+    return getNullableField(SourceLineIndexDefinition.FIELD_SCM_DATE);
   }
 
-  public void setScmDate(Date scmDate) {
+  public void setScmDate(@Nullable Date scmDate) {
     setField(SourceLineIndexDefinition.FIELD_SCM_DATE, scmDate);
   }
 
index 36b39b968e2f6494cce53aa4f9cd8d4879684454..bbaffed232a7312b9a88ebcac19b86e30ce220cb 100644 (file)
  */
 package org.sonar.server.source.index;
 
+import com.google.common.base.Preconditions;
+import org.elasticsearch.common.collect.Lists;
 import org.elasticsearch.index.query.QueryBuilders;
+import org.elasticsearch.search.SearchHit;
+import org.elasticsearch.search.sort.SortOrder;
 import org.sonar.api.ServerComponent;
 import org.sonar.server.es.EsClient;
 
+import java.util.List;
+
 public class SourceLineIndex implements ServerComponent {
 
   private final EsClient esClient;
@@ -31,6 +37,9 @@ public class SourceLineIndex implements ServerComponent {
     this.esClient = esClient;
   }
 
+  /**
+   * Unindex all lines in file with UUID <code>fileUuid</code> above line <code>lastLine</code>
+   */
   public void deleteLinesFromFileAbove(String fileUuid, int lastLine) {
     esClient.prepareDeleteByQuery(SourceLineIndexDefinition.INDEX_SOURCE_LINES)
       .setTypes(SourceLineIndexDefinition.TYPE_SOURCE_LINE)
@@ -39,4 +48,33 @@ public class SourceLineIndex implements ServerComponent {
         .must(QueryBuilders.rangeQuery(SourceLineIndexDefinition.FIELD_LINE).gt(lastLine))
       ).get();
   }
+
+  /**
+   * Get lines of code for file with UUID <code>fileUuid</code> with line numbers
+   * between <code>from</code> and <code>to</code> (both inclusive). Line numbers
+   * start at 1.
+   * @param fileUuid the UUID of the file for which to get source code
+   * @param from starting line; must be strictly positive
+   * @param to ending line; must be greater than or equal to <code>to</code>
+   */
+  public List<SourceLineDoc> getLines(String fileUuid, int from, int to) {
+    Preconditions.checkArgument(from > 0, "Minimum value for 'from' is 1");
+    Preconditions.checkArgument(to >= from, "'to' must be larger than or equal to 'from'");
+    List<SourceLineDoc> lines = Lists.newArrayList();
+
+    for (SearchHit hit: esClient.prepareSearch(SourceLineIndexDefinition.INDEX_SOURCE_LINES)
+      .setTypes(SourceLineIndexDefinition.TYPE_SOURCE_LINE)
+      .setSize(1 + to - from)
+      .setQuery(QueryBuilders.boolQuery()
+        .must(QueryBuilders.termQuery(SourceLineIndexDefinition.FIELD_FILE_UUID, fileUuid))
+        .must(QueryBuilders.rangeQuery(SourceLineIndexDefinition.FIELD_LINE)
+          .gte(from)
+          .lte(to)))
+      .addSort(SourceLineIndexDefinition.FIELD_LINE, SortOrder.ASC)
+      .get().getHits().getHits()) {
+      lines.add(new SourceLineDoc(hit.sourceAsMap()));
+    }
+
+    return lines;
+  }
 }
diff --git a/server/sonar-server/src/main/java/org/sonar/server/source/ws/IndexAction.java b/server/sonar-server/src/main/java/org/sonar/server/source/ws/IndexAction.java
new file mode 100644 (file)
index 0000000..33afb3a
--- /dev/null
@@ -0,0 +1,110 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 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.server.source.ws;
+
+import com.google.common.io.Resources;
+import org.apache.commons.lang.ObjectUtils;
+import org.sonar.api.server.ws.Request;
+import org.sonar.api.server.ws.RequestHandler;
+import org.sonar.api.server.ws.Response;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.api.utils.text.JsonWriter;
+import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.source.index.SourceLineDoc;
+import org.sonar.server.source.index.SourceLineIndex;
+
+import java.util.Date;
+import java.util.List;
+
+public class IndexAction implements RequestHandler {
+
+  private final SourceLineIndex sourceLineIndex;
+
+  public IndexAction(SourceLineIndex sourceLineIndex) {
+    this.sourceLineIndex = sourceLineIndex;
+  }
+
+  void define(WebService.NewController controller) {
+    WebService.NewAction action = controller.createAction("index")
+      .setDescription("Show source code with line oriented info. Require Browse permission on file's project<br/>" +
+        "Each element of the result array is an object which contains:" +
+        "<ol>" +
+        "<li>Line number</li>" +
+        "<li>Content of the line</li>" +
+        "<li>Author of the line (from SCM information)</li>" +
+        "<li>Revision of the line (from SCM information)</li>" +
+        "<li>Last commit date of the line (from SCM information)</li>" +
+        "</ol>")
+      .setSince("5.0")
+      .setInternal(true)
+      .setResponseExample(Resources.getResource(getClass(), "example-index.json"))
+      .setHandler(this);
+
+    action
+      .createParam("uuid")
+      .setRequired(true)
+      .setDescription("File uuid")
+      .setExampleValue("f333aab4-7e3a-4d70-87e1-f4c491f05e5c");
+
+    action
+      .createParam("from")
+      .setDescription("First line to return. Starts at 1")
+      .setExampleValue("10")
+      .setDefaultValue("1");
+
+    action
+      .createParam("to")
+      .setDescription("Last line to return (inclusive)")
+      .setExampleValue("20");
+  }
+
+  @Override
+  public void handle(Request request, Response response) {
+    String fileUuid = request.mandatoryParam("uuid");
+    int from = Math.max(request.mandatoryParamAsInt("from"), 1);
+    int to = (Integer) ObjectUtils.defaultIfNull(request.paramAsInt("to"), Integer.MAX_VALUE);
+
+    List<SourceLineDoc> sourceLines = sourceLineIndex.getLines(fileUuid, from, to);
+    if (sourceLines.isEmpty()) {
+      throw new NotFoundException("File '" + fileUuid + "' has no sources");
+    }
+
+    JsonWriter json = response.newJsonWriter().beginObject();
+    writeSource(sourceLines, from, json);
+
+    json.endObject().close();
+  }
+
+  private void writeSource(List<SourceLineDoc> lines, int from, JsonWriter json) {
+    json.name("sources").beginArray();
+    for (SourceLineDoc line: lines) {
+      json.beginObject()
+        .prop("line", line.line())
+        .prop("code", line.source())
+        .prop("scmAuthor", line.scmAuthor())
+        .prop("scmRevision", line.scmRevision());
+      Date scmDate = line.scmDate();
+      json.prop("scmDate", scmDate == null ? null : DateUtils.formatDateTime(scmDate))
+        .endObject();
+    }
+    json.endArray();
+  }
+}
index e161a6349e22d98be1460beb9e9069c268471954..a2d7624c6a14e636401c71ebe839fa4529748de1 100644 (file)
@@ -25,11 +25,13 @@ import org.sonar.api.server.ws.WebService;
 public class SourcesWs implements WebService {
 
   private final ShowAction showAction;
+  private final IndexAction show2Action;
   private final RawAction rawAction;
   private final ScmAction scmAction;
 
-  public SourcesWs(ShowAction showAction, RawAction rawAction, ScmAction scmAction) {
+  public SourcesWs(ShowAction showAction, RawAction rawAction, ScmAction scmAction, IndexAction show2Action) {
     this.showAction = showAction;
+    this.show2Action = show2Action;
     this.rawAction = rawAction;
     this.scmAction = scmAction;
   }
@@ -40,6 +42,7 @@ public class SourcesWs implements WebService {
       .setSince("4.2")
       .setDescription("Display sources information");
     showAction.define(controller);
+    show2Action.define(controller);
     rawAction.define(controller);
     scmAction.define(controller);
     controller.done();
diff --git a/server/sonar-server/src/main/resources/org/sonar/server/source/ws/example-index.json b/server/sonar-server/src/main/resources/org/sonar/server/source/ws/example-index.json
new file mode 100644 (file)
index 0000000..3cdfdd6
--- /dev/null
@@ -0,0 +1,10 @@
+{
+  "sources": [
+    {"line": 20, "code": "<span class=\"k\">package </span>org.sonar.check;", "scmAuthor": "simon.brandhof@sonarsource.com", "scmDate": "2011-01-02T12:34:56+0100", "scmRevision":"a1e2b3e5d6f5"],
+    {"line": 21, "code":"", "scmAuthor": "simon.brandhof@sonarsource.com", "scmDate": "2011-01-02T12:34:56+0100", "scmRevision":"a1e2b3e5d6f5"],
+    {"line": 22, "code":"<span class=\"k\">public </span><span class=\"k\">enum </span><span class=\"sym-922 sym\">Priority</span> {", "scmAuthor": "simon.brandhof@sonarsource.com", "scmDate": "2011-01-02T12:34:56+0100", "scmRevision":"a1e2b3e5d6f5"],
+    {"line": 23, "code":"  <span class=\"cppd\">/**</span>", "scmAuthor": "simon.brandhof@sonarsource.com", "scmDate": "2011-01-02T12:34:56+0100", "scmRevision":"a1e2b3e5d6f5"],
+    {"line": 24, "code":"<span class=\"cppd\">   * WARNING : DO NOT CHANGE THE ENUMERATION ORDER</span>", "scmAuthor": "simon.brandhof@sonarsource.com", "scmDate": "2011-01-02T12:34:56+0100", "scmRevision":"a1e2b3e5d6f5"],
+    {"line": 25, "code":"<span class=\"cppd\">   * the enum ordinal is used for db persistence</span>", "scmAuthor": "simon.brandhof@sonarsource.com", "scmDate": "2011-01-02T12:34:56+0100", "scmRevision":"a1e2b3e5d6f5"]
+  ]
+}
index 74ef52f451019492f43b3001bf74bdcd1fbe2c12..cf0fe2e8ff421016c6eceddc7e6f3e8ca86d60eb 100644 (file)
@@ -1,10 +1,10 @@
 {
   "sources": [
-    [20, "<span class=\"k\">package </span>org.sonar.check;"],
-    [21, ""],
-    [22, "<span class=\"k\">public </span><span class=\"k\">enum </span><span class=\"sym-922 sym\">Priority</span> {"],
-    [23, "  <span class=\"cppd\">/**</span>"],
-    [24, "<span class=\"cppd\">   * WARNING : DO NOT CHANGE THE ENUMERATION ORDER</span>"],
-    [25, "<span class=\"cppd\">   * the enum ordinal is used for db persistence</span>"]
+    {"line": 20, "code": "<span class=\"k\">package </span>org.sonar.check;", "scmAuthor": "simon.brandhof@sonarsource.com", "scmDate": "2011-01-02T12:34:56+0100", ""],
+    {"line": 21, "code":""],
+    {"line": 22, "code":"<span class=\"k\">public </span><span class=\"k\">enum </span><span class=\"sym-922 sym\">Priority</span> {"],
+    {"line": 23, "code":"  <span class=\"cppd\">/**</span>"],
+    {"line": 24, "code":"<span class=\"cppd\">   * WARNING : DO NOT CHANGE THE ENUMERATION ORDER</span>"],
+    {"line": 25, "code":"<span class=\"cppd\">   * the enum ordinal is used for db persistence</span>"]
   ]
 }
index 9aa29f0e1bdd47aefea0aa992ad8d440c93b5fa5..e5d8ca32ca1158084dbf3a3b512731af4d767a96 100644 (file)
 package org.sonar.server.es;
 
 import com.google.common.collect.Lists;
+import org.apache.commons.io.IOUtils;
 import org.apache.commons.lang.math.RandomUtils;
 import org.elasticsearch.action.admin.indices.delete.DeleteIndexResponse;
+import org.elasticsearch.action.bulk.BulkRequestBuilder;
+import org.elasticsearch.action.index.IndexRequest;
 import org.elasticsearch.cluster.metadata.IndexMetaData;
 import org.elasticsearch.cluster.node.DiscoveryNode;
 import org.elasticsearch.common.settings.ImmutableSettings;
@@ -32,7 +35,9 @@ import org.junit.rules.ExternalResource;
 import org.sonar.api.config.Settings;
 import org.sonar.api.platform.ComponentContainer;
 import org.sonar.core.profiling.Profiling;
+import org.sonar.test.TestUtils;
 
+import java.io.FileInputStream;
 import java.util.Collections;
 import java.util.List;
 
@@ -113,6 +118,15 @@ public class EsTester extends ExternalResource {
       .get();
   }
 
+  public void putDocuments(String index, String type, Class<?> testClass, String... jsonPaths) throws Exception {
+    BulkRequestBuilder bulk = client.prepareBulk().setRefresh(true);
+    for (String path: jsonPaths) {
+      bulk.add(new IndexRequest(index, type).source(IOUtils.toString(
+        new FileInputStream(TestUtils.getResource(testClass, path)))));
+    }
+    bulk.get();
+  }
+
   public Node node() {
     return node;
   }
index b017e96d336721118652cb78eaade2adafdc5f81..01950d94669ed783994b884eb10edf2f691b232f 100644 (file)
@@ -33,13 +33,20 @@ import org.sonar.core.source.db.SnapshotSourceDao;
 import org.sonar.server.db.DbClient;
 import org.sonar.server.exceptions.ForbiddenException;
 import org.sonar.server.measure.persistence.MeasureDao;
+import org.sonar.server.source.index.SourceLineIndex;
 import org.sonar.server.user.MockUserSession;
 
 import java.util.List;
 
 import static org.fest.assertions.Assertions.assertThat;
 import static org.fest.assertions.Fail.fail;
-import static org.mockito.Mockito.*;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
 
 @RunWith(MockitoJUnitRunner.class)
 public class SourceServiceTest {
@@ -59,17 +66,21 @@ public class SourceServiceTest {
   @Mock
   MeasureDao measureDao;
 
+  @Mock
+  SourceLineIndex sourceLineIndex;
+
   static final String PROJECT_KEY = "org.sonar.sample";
   static final String COMPONENT_KEY = "org.sonar.sample:Sample";
 
   SourceService service;
 
+
   @Before
   public void setUp() throws Exception {
     DbClient dbClient = mock(DbClient.class);
     when(dbClient.openSession(false)).thenReturn(session);
     when(dbClient.measureDao()).thenReturn(measureDao);
-    service = new SourceService(dbClient, sourceDecorator, snapshotSourceDao, deprecatedSourceDecorator);
+    service = new SourceService(dbClient, sourceDecorator, snapshotSourceDao, deprecatedSourceDecorator, sourceLineIndex);
   }
 
   @Test
diff --git a/server/sonar-server/src/test/java/org/sonar/server/source/index/SourceLineIndexTest.java b/server/sonar-server/src/test/java/org/sonar/server/source/index/SourceLineIndexTest.java
new file mode 100644 (file)
index 0000000..ea3feff
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 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.server.source.index;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.config.Settings;
+import org.sonar.server.es.EsTester;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+public class SourceLineIndexTest {
+
+  @Rule
+  public EsTester es = new EsTester().addDefinitions(new SourceLineIndexDefinition(new Settings()));
+
+  private SourceLineIndex index;
+
+  @Before
+  public void setUp() {
+    index = new SourceLineIndex(es.client());
+  }
+
+  @Test
+  public void should_retrieve_line_range() throws Exception {
+    es.putDocuments(SourceLineIndexDefinition.INDEX_SOURCE_LINES, SourceLineIndexDefinition.TYPE_SOURCE_LINE,
+      this.getClass(),
+      "file1_line1.json",
+      "file1_line2.json",
+      "file1_line3.json",
+      "file2_line1.json",
+      "file2_line2.json",
+      "file2_line3.json");
+    assertThat(index.getLines("file1", 1, 3)).hasSize(3);
+    assertThat(index.getLines("file1", 1, Integer.MAX_VALUE)).hasSize(3);
+    assertThat(index.getLines("file1", 2, 2)).hasSize(1);
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void should_reject_from_less_than_1() {
+    index.getLines("polop", 0, 0);
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void should_reject_to_less_than_from() {
+    index.getLines("polop", 2, 1);
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/source/ws/IndexActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/source/ws/IndexActionTest.java
new file mode 100644 (file)
index 0000000..07e9622
--- /dev/null
@@ -0,0 +1,152 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 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.server.source.ws;
+
+import com.google.common.collect.ImmutableMap;
+import org.elasticsearch.common.collect.Lists;
+import org.elasticsearch.common.collect.Maps;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.search.BaseNormalizer;
+import org.sonar.server.source.index.SourceLineDoc;
+import org.sonar.server.source.index.SourceLineIndex;
+import org.sonar.server.source.index.SourceLineIndexDefinition;
+import org.sonar.server.ws.WsTester;
+
+import java.util.Date;
+import java.util.Map;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static org.fest.assertions.Assertions.assertThat;
+import static org.fest.assertions.Fail.fail;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class IndexActionTest {
+
+  @Mock
+  SourceLineIndex sourceLineIndex;
+
+  WsTester tester;
+
+  @Before
+  public void setUp() throws Exception {
+    tester = new WsTester(
+      new SourcesWs(
+        mock(ShowAction.class),
+        mock(RawAction.class),
+        mock(ScmAction.class),
+        new IndexAction(sourceLineIndex)
+      )
+    );
+  }
+
+  @Test
+  public void show_source() throws Exception {
+    String componentUuid = "efgh";
+    Date updatedAt = new Date();
+    Date scmDate = DateUtils.parseDateTime("2014-01-01T12:34:56+0100");
+    SourceLineDoc line1 = new SourceLineDoc(ImmutableMap.<String, Object>builder()
+      .put(SourceLineIndexDefinition.FIELD_PROJECT_UUID, "abcd")
+      .put(SourceLineIndexDefinition.FIELD_FILE_UUID, "efgh")
+      .put(SourceLineIndexDefinition.FIELD_LINE, 1)
+      .put(SourceLineIndexDefinition.FIELD_SCM_REVISION, "cafebabe")
+      .put(SourceLineIndexDefinition.FIELD_SCM_DATE, scmDate)
+      .put(SourceLineIndexDefinition.FIELD_SCM_AUTHOR, "polop")
+      .put(SourceLineIndexDefinition.FIELD_SOURCE, "class Polop {")
+      .put(BaseNormalizer.UPDATED_AT_FIELD, updatedAt)
+      .build());
+    SourceLineDoc line2 = new SourceLineDoc(ImmutableMap.<String, Object>builder()
+      .put(SourceLineIndexDefinition.FIELD_PROJECT_UUID, "abcd")
+      .put(SourceLineIndexDefinition.FIELD_FILE_UUID, "efgh")
+      .put(SourceLineIndexDefinition.FIELD_LINE, 2)
+      .put(SourceLineIndexDefinition.FIELD_SCM_REVISION, "cafebabe")
+      .put(SourceLineIndexDefinition.FIELD_SCM_DATE, scmDate)
+      .put(SourceLineIndexDefinition.FIELD_SCM_AUTHOR, "polop")
+      .put(SourceLineIndexDefinition.FIELD_SOURCE, "  // Empty")
+      .put(BaseNormalizer.UPDATED_AT_FIELD, updatedAt)
+      .build());
+    SourceLineDoc line3 = new SourceLineDoc(ImmutableMap.<String, Object>builder()
+      .put(SourceLineIndexDefinition.FIELD_PROJECT_UUID, "abcd")
+      .put(SourceLineIndexDefinition.FIELD_FILE_UUID, "efgh")
+      .put(SourceLineIndexDefinition.FIELD_LINE, 3)
+      .put(SourceLineIndexDefinition.FIELD_SCM_REVISION, "cafebabe")
+      .put(SourceLineIndexDefinition.FIELD_SCM_DATE, scmDate)
+      .put(SourceLineIndexDefinition.FIELD_SCM_AUTHOR, "polop")
+      .put(SourceLineIndexDefinition.FIELD_SOURCE, "}")
+      .put(BaseNormalizer.UPDATED_AT_FIELD, updatedAt)
+      .build());
+    when(sourceLineIndex.getLines(eq(componentUuid), anyInt(), anyInt())).thenReturn(newArrayList(
+      line1,
+      line2,
+      line3
+    ));
+
+    WsTester.TestRequest request = tester.newGetRequest("api/sources", "index").setParam("uuid", componentUuid);
+    // Using non-strict match b/c of dates
+    request.execute().assertJson(getClass(), "show_source.json", false);
+  }
+
+  @Test
+  public void fail_to_show_source_if_no_source_found() throws Exception {
+    String componentKey = "src/Foo.java";
+    when(sourceLineIndex.getLines(anyString(), anyInt(), anyInt())).thenReturn(Lists.<SourceLineDoc>newArrayList());
+
+    try {
+      WsTester.TestRequest request = tester.newGetRequest("api/sources", "index").setParam("uuid", componentKey);
+      request.execute();
+      fail();
+    } catch (Exception e) {
+      assertThat(e).isInstanceOf(NotFoundException.class);
+    }
+  }
+
+  @Test
+  public void show_source_with_from_and_to_params() throws Exception {
+    String fileKey = "src/Foo.java";
+    Map<String, Object> fieldMap = Maps.newHashMap();
+    fieldMap.put(SourceLineIndexDefinition.FIELD_PROJECT_UUID, "abcd");
+    fieldMap.put(SourceLineIndexDefinition.FIELD_FILE_UUID, "efgh");
+    fieldMap.put(SourceLineIndexDefinition.FIELD_LINE, 3);
+    fieldMap.put(SourceLineIndexDefinition.FIELD_SCM_REVISION, "cafebabe");
+    fieldMap.put(SourceLineIndexDefinition.FIELD_SCM_DATE, null);
+    fieldMap.put(SourceLineIndexDefinition.FIELD_SCM_AUTHOR, "polop");
+    fieldMap.put(SourceLineIndexDefinition.FIELD_SOURCE, "}");
+    fieldMap.put(BaseNormalizer.UPDATED_AT_FIELD, new Date());
+    when(sourceLineIndex.getLines(fileKey, 3, 3)).thenReturn(newArrayList(
+      new SourceLineDoc(fieldMap)
+    ));
+    WsTester.TestRequest request = tester
+      .newGetRequest("api/sources", "index")
+      .setParam("uuid", fileKey)
+      .setParam("from", "3")
+      .setParam("to", "3");
+    request.execute().assertJson(getClass(), "show_source_with_params_from_and_to.json");
+  }
+}
index 158a4fb5791aafc1225f31d9a99177d791f2796c..312efb4a2c2937b5bbc992fbcac467ae4786d4b6 100644 (file)
@@ -34,8 +34,6 @@ import org.sonar.server.exceptions.NotFoundException;
 import org.sonar.server.source.SourceService;
 import org.sonar.server.ws.WsTester;
 
-import java.util.Collections;
-
 import static com.google.common.collect.Lists.newArrayList;
 import static org.fest.assertions.Assertions.assertThat;
 import static org.fest.assertions.Fail.fail;
@@ -66,7 +64,7 @@ public class RawActionTest {
   public void setUp() throws Exception {
     when(dbClient.componentDao()).thenReturn(componentDao);
     when(dbClient.openSession(false)).thenReturn(session);
-    tester = new WsTester(new SourcesWs(mock(ShowAction.class), new RawAction(dbClient, sourceService), mock(ScmAction.class)));
+    tester = new WsTester(new SourcesWs(mock(ShowAction.class), new RawAction(dbClient, sourceService), mock(ScmAction.class), mock(IndexAction.class)));
   }
 
   @Test
index a572c19e7eff0e7322d4b5a117fa567b07fe5b89..bd5ee2e012e1ffd5720f3c44fdd54c30e8bb36bc 100644 (file)
@@ -26,13 +26,15 @@ import org.sonar.server.ws.WsTester;
 
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 public class ScmActionTest {
 
   SourceService sourceService = mock(SourceService.class);
   ScmWriter scmWriter = mock(ScmWriter.class);
-  WsTester tester = new WsTester(new SourcesWs(mock(ShowAction.class), mock(RawAction.class), new ScmAction(sourceService, scmWriter)));
+  WsTester tester = new WsTester(new SourcesWs(mock(ShowAction.class), mock(RawAction.class), new ScmAction(sourceService, scmWriter), mock(IndexAction.class)));
 
   @Test
   public void get_scm() throws Exception {
index ace2c100374438dbc0470cddf8e0712944e93a42..53888aa4e3318eab23a4707e6fdda5bc795c1cc7 100644 (file)
@@ -31,7 +31,9 @@ import static org.fest.assertions.Fail.fail;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 public class ShowActionTest {
 
@@ -41,7 +43,7 @@ public class ShowActionTest {
 
   @Before
   public void setUp() throws Exception {
-    tester = new WsTester(new SourcesWs(new ShowAction(sourceService), mock(RawAction.class), new ScmAction(sourceService, mock(ScmWriter.class))));
+    tester = new WsTester(new SourcesWs(new ShowAction(sourceService), mock(RawAction.class), new ScmAction(sourceService, mock(ScmWriter.class)), mock(IndexAction.class)));
   }
 
   @Test
index 512c771618cc7d7e2b748cd75ce5ae7cda880fcf..dc96bc36b93ebec6e2d3f919c1b68ec96ad62bf2 100644 (file)
@@ -24,6 +24,7 @@ import org.junit.Test;
 import org.sonar.api.server.ws.WebService;
 import org.sonar.server.db.DbClient;
 import org.sonar.server.source.SourceService;
+import org.sonar.server.source.index.SourceLineIndex;
 import org.sonar.server.ws.WsTester;
 
 import static org.fest.assertions.Assertions.assertThat;
@@ -34,7 +35,8 @@ public class SourcesWsTest {
   ShowAction showAction = new ShowAction(mock(SourceService.class));
   RawAction rawAction = new RawAction(mock(DbClient.class), mock(SourceService.class));
   ScmAction scmAction = new ScmAction(mock(SourceService.class), new ScmWriter());
-  WsTester tester = new WsTester(new SourcesWs(showAction, rawAction, scmAction));
+  IndexAction indexAction = new IndexAction(mock(SourceLineIndex.class));
+  WsTester tester = new WsTester(new SourcesWs(showAction, rawAction, scmAction, indexAction));
 
   @Test
   public void define_ws() throws Exception {
@@ -42,7 +44,7 @@ public class SourcesWsTest {
     assertThat(controller).isNotNull();
     assertThat(controller.since()).isEqualTo("4.2");
     assertThat(controller.description()).isNotEmpty();
-    assertThat(controller.actions()).hasSize(3);
+    assertThat(controller.actions()).hasSize(4);
 
     WebService.Action show = controller.action("show");
     assertThat(show).isNotNull();
@@ -67,5 +69,13 @@ public class SourcesWsTest {
     assertThat(scm.isInternal()).isFalse();
     assertThat(scm.responseExampleAsString()).isNotEmpty();
     assertThat(scm.params()).hasSize(4);
+
+    WebService.Action index = controller.action("index");
+    assertThat(index).isNotNull();
+    assertThat(index.handler()).isSameAs(indexAction);
+    assertThat(index.since()).isEqualTo("5.0");
+    assertThat(index.isInternal()).isTrue();
+    assertThat(index.responseExampleAsString()).isNotEmpty();
+    assertThat(index.params()).hasSize(3);
   }
 }
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/source/index/SourceLineIndexTest/file1_line1.json b/server/sonar-server/src/test/resources/org/sonar/server/source/index/SourceLineIndexTest/file1_line1.json
new file mode 100644 (file)
index 0000000..cfc6358
--- /dev/null
@@ -0,0 +1,10 @@
+{
+  "projectUuid": "abcd",
+  "fileUuid": "file1",
+  "line": 1,
+  "scmAuthor": "polop",
+  "scmDate": "2014-01-01T12:34:56.7+01:00",
+  "scmRevision": "cafebabe",
+  "source": "// Empty",
+  "updatedAt": "2014-01-01T23:45:01.8+01:00"
+}
\ No newline at end of file
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/source/index/SourceLineIndexTest/file1_line2.json b/server/sonar-server/src/test/resources/org/sonar/server/source/index/SourceLineIndexTest/file1_line2.json
new file mode 100644 (file)
index 0000000..e4bafc7
--- /dev/null
@@ -0,0 +1,10 @@
+{
+  "projectUuid": "abcd",
+  "fileUuid": "file1",
+  "line": 2,
+  "scmAuthor": "polop",
+  "scmDate": "2014-01-01T12:34:56.7+01:00",
+  "scmRevision": "cafebabe",
+  "source": "// Empty",
+  "updatedAt": "2014-01-01T23:45:01.8+01:00"
+}
\ No newline at end of file
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/source/index/SourceLineIndexTest/file1_line3.json b/server/sonar-server/src/test/resources/org/sonar/server/source/index/SourceLineIndexTest/file1_line3.json
new file mode 100644 (file)
index 0000000..6dce5c1
--- /dev/null
@@ -0,0 +1,10 @@
+{
+  "projectUuid": "abcd",
+  "fileUuid": "file1",
+  "line": 3,
+  "scmAuthor": "polop",
+  "scmDate": "2014-01-01T12:34:56.7+01:00",
+  "scmRevision": "cafebabe",
+  "source": "// Empty",
+  "updatedAt": "2014-01-01T23:45:01.8+01:00"
+}
\ No newline at end of file
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/source/index/SourceLineIndexTest/file2_line1.json b/server/sonar-server/src/test/resources/org/sonar/server/source/index/SourceLineIndexTest/file2_line1.json
new file mode 100644 (file)
index 0000000..d919263
--- /dev/null
@@ -0,0 +1,10 @@
+{
+  "projectUuid": "abcd",
+  "fileUuid": "file2",
+  "line": 1,
+  "scmAuthor": "polop",
+  "scmDate": "2014-01-01T12:34:56.7+01:00",
+  "scmRevision": "cafebabe",
+  "source": "// Empty",
+  "updatedAt": "2014-01-01T23:45:01.8+01:00"
+}
\ No newline at end of file
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/source/index/SourceLineIndexTest/file2_line2.json b/server/sonar-server/src/test/resources/org/sonar/server/source/index/SourceLineIndexTest/file2_line2.json
new file mode 100644 (file)
index 0000000..fc8aa03
--- /dev/null
@@ -0,0 +1,10 @@
+{
+  "projectUuid": "abcd",
+  "fileUuid": "file2",
+  "line": 2,
+  "scmAuthor": "polop",
+  "scmDate": "2014-01-01T12:34:56.7+01:00",
+  "scmRevision": "cafebabe",
+  "source": "// Empty",
+  "updatedAt": "2014-01-01T23:45:01.8+01:00"
+}
\ No newline at end of file
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/source/index/SourceLineIndexTest/file2_line3.json b/server/sonar-server/src/test/resources/org/sonar/server/source/index/SourceLineIndexTest/file2_line3.json
new file mode 100644 (file)
index 0000000..a1383e7
--- /dev/null
@@ -0,0 +1,10 @@
+{
+  "projectUuid": "abcd",
+  "fileUuid": "file2",
+  "line": 3,
+  "scmAuthor": "polop",
+  "scmDate": "2014-01-01T12:34:56.7+01:00",
+  "scmRevision": "cafebabe",
+  "source": "// Empty",
+  "updatedAt": "2014-01-01T23:45:01.8+01:00"
+}
\ No newline at end of file
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/source/ws/IndexActionTest/show_source.json b/server/sonar-server/src/test/resources/org/sonar/server/source/ws/IndexActionTest/show_source.json
new file mode 100644 (file)
index 0000000..27d3618
--- /dev/null
@@ -0,0 +1,30 @@
+{
+  "sources": [
+    {
+      "code": "class Polop {",
+      "line": 1,
+      "scmAuthor": "polop",
+      "scmRevision": "cafebabe"
+    },
+    {
+      "code": "  // Empty",
+      "line": 2,
+      "scmAuthor": "polop",
+      "scmRevision": "cafebabe"
+    },
+    {
+      "code": "}",
+      "line": 3,
+      "scmAuthor": "polop",
+      "scmRevision": "cafebabe"
+    }
+  ]
+}
+
+
+
+
+
+
+
+
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/source/ws/IndexActionTest/show_source_with_params_from_and_to.json b/server/sonar-server/src/test/resources/org/sonar/server/source/ws/IndexActionTest/show_source_with_params_from_and_to.json
new file mode 100644 (file)
index 0000000..ba2a4c6
--- /dev/null
@@ -0,0 +1,10 @@
+{
+  "sources": [
+    {
+      "code": "}",
+      "line": 3,
+      "scmAuthor": "polop",
+      "scmRevision": "cafebabe"
+    }
+  ]
+}