From f1b5ffc899b37f43b7c8f1503a747df978b3e2e4 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Lievremont Date: Fri, 5 Dec 2014 16:26:39 +0100 Subject: [PATCH] SONAR-5889 Implement api/sources/index on ES index --- .../server/platform/ServerComponents.java | 1 + .../sonar/server/source/ws/IndexAction.java | 95 ++++++++++++++ .../org/sonar/server/source/ws/SourcesWs.java | 5 +- .../sonar/server/source/ws/example-index.json | 17 +++ .../server/source/ws/HashActionTest.java | 3 +- .../server/source/ws/IndexActionTest.java | 123 ++++++++++++++++++ .../server/source/ws/LinesActionTest.java | 3 +- .../sonar/server/source/ws/RawActionTest.java | 2 +- .../sonar/server/source/ws/ScmActionTest.java | 2 +- .../server/source/ws/ShowActionTest.java | 3 +- .../sonar/server/source/ws/SourcesWsTest.java | 28 ++-- .../ws/IndexActionTest/index-result.json | 6 + 12 files changed, 273 insertions(+), 15 deletions(-) create mode 100644 server/sonar-server/src/main/java/org/sonar/server/source/ws/IndexAction.java create mode 100644 server/sonar-server/src/main/resources/org/sonar/server/source/ws/example-index.json create mode 100644 server/sonar-server/src/test/java/org/sonar/server/source/ws/IndexActionTest.java create mode 100644 server/sonar-server/src/test/resources/org/sonar/server/source/ws/IndexActionTest/index-result.json diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java index 0abd8100276..b1f54a0cced 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java @@ -545,6 +545,7 @@ class ServerComponents { pico.addSingleton(HashAction.class); pico.addSingleton(ScmWriter.class); pico.addSingleton(RawAction.class); + pico.addSingleton(IndexAction.class); pico.addSingleton(ScmAction.class); pico.addSingleton(SourceLineIndexDefinition.class); pico.addSingleton(SourceLineIndex.class); 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 index 00000000000..73373395eb9 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/source/ws/IndexAction.java @@ -0,0 +1,95 @@ +/* + * 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.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.text.JsonWriter; +import org.sonar.api.web.UserRole; +import org.sonar.core.component.ComponentDto; +import org.sonar.core.persistence.DbSession; +import org.sonar.server.db.DbClient; +import org.sonar.server.source.SourceService; +import org.sonar.server.user.UserSession; + +import java.util.List; + +public class IndexAction implements RequestHandler { + + private final DbClient dbClient; + private final SourceService sourceService; + + public IndexAction(DbClient dbClient, SourceService sourceService) { + this.dbClient = dbClient; + this.sourceService = sourceService; + } + + void define(WebService.NewController controller) { + WebService.NewAction action = controller.createAction("index") + .setDescription("Get source code as line number / text pairs. Require See Source Code permission on file") + .setSince("5.0") + .setResponseExample(Resources.getResource(getClass(), "example-index.json")) + .setInternal(true) + .setHandler(this); + + action + .createParam("resource") + .setRequired(true) + .setDescription("File key") + .setExampleValue("my_project:/src/foo/Bar.php"); + + action + .createParam("from") + .setRequired(true) + .setDefaultValue(1) + .setDescription("First line"); + + action + .createParam("to") + .setRequired(false) + .setDescription("Last line (excluded). If not specified, all lines are returned until end of file"); + } + + @Override + public void handle(Request request, Response response) { + String fileKey = request.mandatoryParam("resource"); + UserSession.get().checkComponentPermission(UserRole.CODEVIEWER, fileKey); + Integer from = request.mandatoryParamAsInt("from"); + Integer to = request.paramAsInt("to"); + DbSession session = dbClient.openSession(false); + try { + ComponentDto componentDto = dbClient.componentDao().getByKey(session, fileKey); + List lines = sourceService.getLinesAsTxt(componentDto.uuid(), from, to == null ? null : to - 1); + JsonWriter json = response.newJsonWriter().beginArray().beginObject(); + Integer lineCounter = from; + for (String line: lines) { + json.prop(lineCounter.toString(), line); + lineCounter ++; + } + json.endObject().endArray().close();; + } finally { + session.close(); + } + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/source/ws/SourcesWs.java b/server/sonar-server/src/main/java/org/sonar/server/source/ws/SourcesWs.java index 75bc8580d19..259aef1ec66 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/source/ws/SourcesWs.java +++ b/server/sonar-server/src/main/java/org/sonar/server/source/ws/SourcesWs.java @@ -29,13 +29,15 @@ public class SourcesWs implements WebService { private final RawAction rawAction; private final ScmAction scmAction; private final HashAction hashAction; + private final IndexAction indexAction; - public SourcesWs(ShowAction showAction, RawAction rawAction, ScmAction scmAction, LinesAction show2Action, HashAction hashAction) { + public SourcesWs(ShowAction showAction, RawAction rawAction, ScmAction scmAction, LinesAction show2Action, HashAction hashAction, IndexAction indexAction) { this.showAction = showAction; this.show2Action = show2Action; this.rawAction = rawAction; this.scmAction = scmAction; this.hashAction = hashAction; + this.indexAction = indexAction; } @Override @@ -48,6 +50,7 @@ public class SourcesWs implements WebService { rawAction.define(controller); scmAction.define(controller); hashAction.define(controller); + indexAction.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 index 00000000000..89745d72612 --- /dev/null +++ b/server/sonar-server/src/main/resources/org/sonar/server/source/ws/example-index.json @@ -0,0 +1,17 @@ +[ + { + "48": " void define(WebService.NewController controller) {", + "49": " WebService.NewAction action = controller.createAction(\"raw\")", + "50": " .setDescription(\"Get source code as plain text. Require Code viewer permission on file\")", + "51": " .setSince(\"5.0\")", + "52": " .setResponseExample(Resources.getResource(getClass(), \"example-raw.txt\"))", + "53": " .setHandler(this);", + "54": "", + "55": " action", + "56": " .createParam(\"key\")", + "57": " .setRequired(true)", + "58": " .setDescription(\"File key\")", + "59": " .setExampleValue(\"my_project:/src/foo/Bar.php\");", + "60": " }" + } +] \ No newline at end of file diff --git a/server/sonar-server/src/test/java/org/sonar/server/source/ws/HashActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/source/ws/HashActionTest.java index 1fb3ed23cfd..5ed6696facb 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/source/ws/HashActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/source/ws/HashActionTest.java @@ -66,7 +66,8 @@ public class HashActionTest { mock(RawAction.class), mock(ScmAction.class), mock(LinesAction.class), - new HashAction(dbClient, fileSourceDao) + new HashAction(dbClient, fileSourceDao), + mock(IndexAction.class) ) ); } 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 index 00000000000..079666645dd --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/source/ws/IndexActionTest.java @@ -0,0 +1,123 @@ +/* + * 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 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.web.UserRole; +import org.sonar.core.component.ComponentDto; +import org.sonar.core.persistence.DbSession; +import org.sonar.server.component.ComponentTesting; +import org.sonar.server.component.db.ComponentDao; +import org.sonar.server.db.DbClient; +import org.sonar.server.exceptions.ForbiddenException; +import org.sonar.server.exceptions.NotFoundException; +import org.sonar.server.source.SourceService; +import org.sonar.server.user.MockUserSession; +import org.sonar.server.ws.WsTester; + +import static com.google.common.collect.Lists.newArrayList; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class IndexActionTest { + + @Mock + DbClient dbClient; + + @Mock + DbSession session; + + @Mock + ComponentDao componentDao; + + @Mock + SourceService sourceService; + + WsTester tester; + + ComponentDto project = ComponentTesting.newProjectDto(); + ComponentDto file = ComponentTesting.newFileDto(project); + + @Before + public void setUp() throws Exception { + when(dbClient.componentDao()).thenReturn(componentDao); + when(dbClient.openSession(false)).thenReturn(session); + tester = new WsTester(new SourcesWs(mock(ShowAction.class), mock(RawAction.class), mock(ScmAction.class), mock(LinesAction.class), + mock(HashAction.class), new IndexAction(dbClient, sourceService))); + } + + @Test + public void get_json() throws Exception { + String fileKey = "src/Foo.java"; + MockUserSession.set().addComponentPermission(UserRole.CODEVIEWER, "polop", fileKey); + when(componentDao.getByKey(session, fileKey)).thenReturn(file); + + when(sourceService.getLinesAsTxt(file.uuid(), 1, null)).thenReturn(newArrayList( + "public class HelloWorld {", + "}" + )); + + WsTester.TestRequest request = tester.newGetRequest("api/sources", "index").setParam("resource", fileKey); + request.execute().assertJson(this.getClass(), "index-result.json"); + } + + @Test + public void limit_range() throws Exception { + String fileKey = "src/Foo.java"; + MockUserSession.set().addComponentPermission(UserRole.CODEVIEWER, "polop", fileKey); + when(componentDao.getByKey(session, fileKey)).thenReturn(file); + + when(sourceService.getLinesAsTxt(file.uuid(), 1, 2)).thenReturn(newArrayList( + "public class HelloWorld {", + "}" + )); + + WsTester.TestRequest request = tester.newGetRequest("api/sources", "index") + .setParam("resource", fileKey).setParam("from", "1").setParam("to", "3"); + request.execute().assertJson(this.getClass(), "index-result.json"); + } + + @Test(expected = ForbiddenException.class) + public void requires_code_viewer_permission() throws Exception { + MockUserSession.set(); + tester.newGetRequest("api/sources", "index").setParam("resource", "any").execute(); + } + + @Test + public void close_db_session() throws Exception { + String fileKey = "src/Foo.java"; + MockUserSession.set().addComponentPermission(UserRole.CODEVIEWER, "polop", fileKey); + when(componentDao.getByKey(session, fileKey)).thenThrow(new NotFoundException()); + + WsTester.TestRequest request = tester.newGetRequest("api/sources", "index").setParam("resource", fileKey); + try { + request.execute(); + } catch(NotFoundException nfe) { + verify(session).close(); + } + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/source/ws/LinesActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/source/ws/LinesActionTest.java index 2039584edec..ddbb959010b 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/source/ws/LinesActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/source/ws/LinesActionTest.java @@ -78,7 +78,8 @@ public class LinesActionTest { mock(RawAction.class), mock(ScmAction.class), new LinesAction(sourceLineIndex, htmlSourceDecorator, componentService), - mock(HashAction.class) + mock(HashAction.class), + mock(IndexAction.class) ) ); when(htmlSourceDecorator.getDecoratedSourceAsHtml(anyString(), anyString(), anyString())).thenAnswer(new Answer() { diff --git a/server/sonar-server/src/test/java/org/sonar/server/source/ws/RawActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/source/ws/RawActionTest.java index a306752ce50..5b11c4fb8a1 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/source/ws/RawActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/source/ws/RawActionTest.java @@ -66,7 +66,7 @@ public class RawActionTest { 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), mock(LinesAction.class), - mock(HashAction.class))); + mock(HashAction.class), mock(IndexAction.class))); } @Test diff --git a/server/sonar-server/src/test/java/org/sonar/server/source/ws/ScmActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/source/ws/ScmActionTest.java index ba91095a920..97ccb21efa6 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/source/ws/ScmActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/source/ws/ScmActionTest.java @@ -35,7 +35,7 @@ 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), mock(LinesAction.class), - mock(HashAction.class))); + mock(HashAction.class), mock(IndexAction.class))); @Test public void get_scm() throws Exception { diff --git a/server/sonar-server/src/test/java/org/sonar/server/source/ws/ShowActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/source/ws/ShowActionTest.java index 5c9df69e4ec..c3471efa0bb 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/source/ws/ShowActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/source/ws/ShowActionTest.java @@ -67,7 +67,8 @@ public class ShowActionTest { when(dbClient.openSession(false)).thenReturn(session); tester = new WsTester(new SourcesWs(new ShowAction(sourceService, dbClient), mock(RawAction.class), new ScmAction(sourceService, mock(ScmWriter.class)), mock(LinesAction.class), - mock(HashAction.class))); + mock(HashAction.class), + mock(IndexAction.class))); } @Test diff --git a/server/sonar-server/src/test/java/org/sonar/server/source/ws/SourcesWsTest.java b/server/sonar-server/src/test/java/org/sonar/server/source/ws/SourcesWsTest.java index 20aed4eee31..db22e8bb0a8 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/source/ws/SourcesWsTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/source/ws/SourcesWsTest.java @@ -40,7 +40,8 @@ public class SourcesWsTest { ScmAction scmAction = new ScmAction(mock(SourceService.class), new ScmWriter()); LinesAction linesAction = new LinesAction(mock(SourceLineIndex.class), mock(HtmlSourceDecorator.class), mock(ComponentService.class)); HashAction hashAction = new HashAction(mock(DbClient.class), mock(FileSourceDao.class)); - WsTester tester = new WsTester(new SourcesWs(showAction, rawAction, scmAction, linesAction, hashAction)); + IndexAction indexAction = new IndexAction(mock(DbClient.class), mock(SourceService.class)); + WsTester tester = new WsTester(new SourcesWs(showAction, rawAction, scmAction, linesAction, hashAction, indexAction)); @Test public void define_ws() throws Exception { @@ -48,7 +49,7 @@ public class SourcesWsTest { assertThat(controller).isNotNull(); assertThat(controller.since()).isEqualTo("4.2"); assertThat(controller.description()).isNotEmpty(); - assertThat(controller.actions()).hasSize(5); + assertThat(controller.actions()).hasSize(6); WebService.Action show = controller.action("show"); assertThat(show).isNotNull(); @@ -74,13 +75,13 @@ public class SourcesWsTest { assertThat(scm.responseExampleAsString()).isNotEmpty(); assertThat(scm.params()).hasSize(4); - WebService.Action index = controller.action("lines"); - assertThat(index).isNotNull(); - assertThat(index.handler()).isSameAs(linesAction); - assertThat(index.since()).isEqualTo("5.0"); - assertThat(index.isInternal()).isTrue(); - assertThat(index.responseExampleAsString()).isNotEmpty(); - assertThat(index.params()).hasSize(3); + WebService.Action lines = controller.action("lines"); + assertThat(lines).isNotNull(); + assertThat(lines.handler()).isSameAs(linesAction); + assertThat(lines.since()).isEqualTo("5.0"); + assertThat(lines.isInternal()).isTrue(); + assertThat(lines.responseExampleAsString()).isNotEmpty(); + assertThat(lines.params()).hasSize(3); WebService.Action hash = controller.action("hash"); assertThat(hash).isNotNull(); @@ -89,5 +90,14 @@ public class SourcesWsTest { assertThat(hash.isInternal()).isTrue(); assertThat(hash.responseExampleAsString()).isNotEmpty(); assertThat(hash.params()).hasSize(1); + + 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/ws/IndexActionTest/index-result.json b/server/sonar-server/src/test/resources/org/sonar/server/source/ws/IndexActionTest/index-result.json new file mode 100644 index 00000000000..fb991865f86 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/source/ws/IndexActionTest/index-result.json @@ -0,0 +1,6 @@ +[ + { + "1": "public class HelloWorld {", + "2": "}" + } +] \ No newline at end of file -- 2.39.5