Browse Source

SONAR-6821 WS qualityprofiles/search use protocol buffers to build response

tags/5.2-RC1
Teryk Bellahsene 8 years ago
parent
commit
aaf2651b94

+ 1
- 1
microbenchmark-template/pom.xml View File

@@ -28,7 +28,7 @@
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>sonar-db</artifactId>
<artifactId>sonar-dbClient</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>

+ 9
- 9
server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileLoader.java View File

@@ -46,12 +46,12 @@ import org.sonar.server.user.UserSession;
@ServerSide
public class QProfileLoader {

private final DbClient db;
private final DbClient dbClient;
private final IndexClient index;
private final UserSession userSession;

public QProfileLoader(DbClient db, IndexClient index, UserSession userSession) {
this.db = db;
public QProfileLoader(DbClient dbClient, IndexClient index, UserSession userSession) {
this.dbClient = dbClient;
this.index = index;
this.userSession = userSession;
}
@@ -61,9 +61,9 @@ public class QProfileLoader {
* profiles are not indexed and declared as a business object
*/
public List<QualityProfileDto> findAll() {
DbSession dbSession = db.openSession(false);
DbSession dbSession = dbClient.openSession(false);
try {
return db.qualityProfileDao().selectAll(dbSession);
return dbClient.qualityProfileDao().selectAll(dbSession);
} finally {
dbSession.close();
}
@@ -71,9 +71,9 @@ public class QProfileLoader {

@CheckForNull
public QualityProfileDto getByKey(String key) {
DbSession dbSession = db.openSession(false);
DbSession dbSession = dbClient.openSession(false);
try {
return db.qualityProfileDao().selectByKey(dbSession, key);
return dbClient.qualityProfileDao().selectByKey(dbSession, key);
} finally {
dbSession.close();
}
@@ -81,9 +81,9 @@ public class QProfileLoader {

@CheckForNull
public QualityProfileDto getByLangAndName(String lang, String name) {
DbSession dbSession = db.openSession(false);
DbSession dbSession = dbClient.openSession(false);
try {
return db.qualityProfileDao().selectByNameAndLanguage(name, lang, dbSession);
return dbClient.qualityProfileDao().selectByNameAndLanguage(name, lang, dbSession);
} finally {
dbSession.close();
}

+ 78
- 40
server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/SearchAction.java View File

@@ -21,6 +21,13 @@ package org.sonar.server.qualityprofile.ws;

import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.apache.commons.lang.builder.CompareToBuilder;
import org.sonar.api.resources.Languages;
import org.sonar.api.server.ws.Request;
@@ -28,21 +35,15 @@ import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
import org.sonar.api.server.ws.WebService.NewAction;
import org.sonar.api.server.ws.WebService.Param;
import org.sonar.api.utils.text.JsonWriter;
import org.sonar.db.qualityprofile.QualityProfileDao;
import org.sonar.core.util.NonNullInputFunction;
import org.sonar.db.qualityprofile.QualityProfileDao;
import org.sonar.server.qualityprofile.QProfile;
import org.sonar.server.qualityprofile.QProfileLoader;
import org.sonar.server.qualityprofile.QProfileLookup;
import org.sonarqube.ws.QualityProfiles.WsSearchResponse;
import org.sonarqube.ws.QualityProfiles.WsSearchResponse.QualityProfile;

import javax.annotation.CheckForNull;
import javax.annotation.Nullable;

import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.sonar.server.ws.WsUtils.writeProtobuf;

public class SearchAction implements QProfileWsAction {

@@ -56,12 +57,16 @@ public class SearchAction implements QProfileWsAction {
private static final String FIELD_PARENT_NAME = "parentName";
private static final String FIELD_ACTIVE_RULE_COUNT = "activeRuleCount";
private static final String FIELD_PROJECT_COUNT = "projectCount";
private static final String FIELD_RULES_UPDATED_AT = "rulesUpdatedAt";

private static final Set<String> ALL_FIELDS = ImmutableSet.of(
FIELD_KEY, FIELD_NAME, FIELD_LANGUAGE, FIELD_LANGUAGE_NAME, FIELD_IS_INHERITED, FIELD_PARENT_KEY, FIELD_PARENT_NAME, FIELD_IS_DEFAULT, FIELD_ACTIVE_RULE_COUNT,
FIELD_PROJECT_COUNT);
FIELD_PROJECT_COUNT, FIELD_RULES_UPDATED_AT);

private static final String PARAM_LANGUAGE = FIELD_LANGUAGE;
private static final String PARAM_COMPONENT_KEY = "componentKey";
private static final String PARAM_DEFAULT = "default";
private static final String PARAM_PROFILE_NAME = "profileName";

private final Languages languages;
private final QProfileLookup profileLookup;
@@ -77,16 +82,28 @@ public class SearchAction implements QProfileWsAction {

@Override
public void define(WebService.NewController controller) {
NewAction search = controller.createAction("search")
NewAction action = controller.createAction("search")
.setSince("5.2")
.setDescription("List quality profiles.")
.setHandler(this)
.addFieldsParam(ALL_FIELDS)
.setResponseExample(getClass().getResource("example-search.json"));

search.createParam(PARAM_LANGUAGE)
action.createParam(PARAM_LANGUAGE)
.setDescription("The key of a language supported by the platform. If specified, only profiles for the given language are returned.")
.setPossibleValues(LanguageParamUtils.getLanguageKeys(languages));

action.createParam(PARAM_COMPONENT_KEY)
.setDescription("Project or module key")
.setExampleValue("org.codehaus.sonar:sonar");

action.createParam(PARAM_DEFAULT)
.setDescription("Return default quality profiles")
.setBooleanPossibleValues();

action.createParam(PARAM_PROFILE_NAME)
.setDescription("Profile name")
.setExampleValue("SonarQube Way");
}

@Override
@@ -95,7 +112,7 @@ public class SearchAction implements QProfileWsAction {

String language = request.param(PARAM_LANGUAGE);

List<QProfile> profiles = null;
List<QProfile> profiles;
if (language == null) {
profiles = profileLookup.allProfiles();
} else {
@@ -111,13 +128,12 @@ public class SearchAction implements QProfileWsAction {
.toComparison();
}
});
WsSearchResponse protobufResponse = buildResponse(profiles, fields);

JsonWriter json = response.newJsonWriter().beginObject();
writeProfiles(json, profiles, fields);
json.endObject().close();
writeProtobuf(protobufResponse, request, response);
}

private void writeProfiles(JsonWriter json, List<QProfile> profiles, List<String> fields) {
private WsSearchResponse buildResponse(List<QProfile> profiles, List<String> fields) {
Map<String, QProfile> profilesByKey = Maps.uniqueIndex(profiles, new NonNullInputFunction<QProfile, String>() {
@Override
protected String doApply(QProfile input) {
@@ -127,8 +143,9 @@ public class SearchAction implements QProfileWsAction {
Map<String, Long> activeRuleCountByKey = profileLoader.countAllActiveRules();
Map<String, Long> projectCountByKey = qualityProfileDao.countProjectsByProfileKey();

json.name("profiles")
.beginArray();
WsSearchResponse.Builder response = WsSearchResponse.newBuilder();
QualityProfile.Builder profileBuilder = QualityProfile.newBuilder();

for (QProfile profile : profiles) {
if (languages.get(profile.language()) == null) {
// Hide profiles on an unsupported language
@@ -138,47 +155,68 @@ public class SearchAction implements QProfileWsAction {
String key = profile.key();
Long activeRuleCount = activeRuleCountByKey.containsKey(key) ? activeRuleCountByKey.get(key) : 0L;
Long projectCount = projectCountByKey.containsKey(key) ? projectCountByKey.get(key) : 0L;
json.beginObject()
.prop(FIELD_KEY, nullUnlessNeeded(FIELD_KEY, key, fields))
.prop(FIELD_NAME, nullUnlessNeeded(FIELD_NAME, profile.name(), fields))
.prop(FIELD_ACTIVE_RULE_COUNT, nullUnlessNeeded(FIELD_ACTIVE_RULE_COUNT, activeRuleCount, fields));
profileBuilder.clear();

if (!profile.isDefault()) {
json.prop(FIELD_PROJECT_COUNT, nullUnlessNeeded(FIELD_PROJECT_COUNT, projectCount, fields));
if (shouldSetValue(FIELD_KEY, profile.key(), fields)) {
profileBuilder.setKey(profile.key());
}
if (shouldSetValue(FIELD_NAME, profile.name(), fields)) {
profileBuilder.setName(profile.name());
}
if (shouldSetValue(FIELD_ACTIVE_RULE_COUNT, activeRuleCount, fields)) {
profileBuilder.setActiveRuleCount(activeRuleCount);
}
if (!profile.isDefault() && shouldSetValue(FIELD_PROJECT_COUNT, projectCount, fields)) {
profileBuilder.setProjectCount(projectCount);
}
writeLanguageFields(json, profile, fields);
writeParentFields(json, profile, fields, profilesByKey);

writeLanguageFields(profileBuilder, profile, fields);
writeParentFields(profileBuilder, profile, fields, profilesByKey);
// Special case for booleans
if (fieldIsNeeded(FIELD_IS_INHERITED, fields)) {
json.prop(FIELD_IS_INHERITED, profile.isInherited());
profileBuilder.setIsInherited(profile.isInherited());
}
if (fieldIsNeeded(FIELD_IS_DEFAULT, fields)) {
json.prop(FIELD_IS_DEFAULT, profile.isDefault());
profileBuilder.setIsDefault(profile.isDefault());
}
json.endObject();
response.addProfiles(profileBuilder);
}
json.endArray();

return response.build();
}

private void writeLanguageFields(JsonWriter json, QProfile profile, List<String> fields) {
private void writeLanguageFields(QualityProfile.Builder profileBuilder, QProfile profile, List<String> fields) {
String languageKey = profile.language();
json.prop(FIELD_LANGUAGE, nullUnlessNeeded(FIELD_LANGUAGE, languageKey, fields))
.prop(FIELD_LANGUAGE_NAME, nullUnlessNeeded(FIELD_LANGUAGE_NAME, languages.get(languageKey).getName(), fields));
if (shouldSetValue(FIELD_LANGUAGE, languageKey, fields)) {
profileBuilder.setLanguage(languageKey);
}
String languageName = languages.get(languageKey).getName();
if (shouldSetValue(FIELD_LANGUAGE_NAME, languageName, fields)) {
profileBuilder.setLanguageName(languageName);
}
}

private void writeParentFields(JsonWriter json, QProfile profile, List<String> fields, Map<String, QProfile> profilesByKey) {
private static void writeParentFields(QualityProfile.Builder profileBuilder, QProfile profile, List<String> fields, Map<String, QProfile> profilesByKey) {
String parentKey = profile.parent();
QProfile parent = parentKey == null ? null : profilesByKey.get(parentKey);
json.prop(FIELD_PARENT_KEY, nullUnlessNeeded(FIELD_PARENT_KEY, parentKey, fields))
.prop(FIELD_PARENT_NAME, nullUnlessNeeded(FIELD_PARENT_NAME, parent == null ? parentKey : parent.name(), fields));
if (shouldSetValue(FIELD_PARENT_KEY, parentKey, fields)) {
profileBuilder.setParentKey(parentKey);
}
if (parent != null && shouldSetValue(FIELD_PARENT_NAME, parent.name(), fields)) {
profileBuilder.setParentName(parent.name());
}
}

@CheckForNull
private <T> T nullUnlessNeeded(String field, T value, @Nullable List<String> fields) {
private static <T> T valueIfFieldNeeded(String field, T value, @Nullable List<String> fields) {
return fieldIsNeeded(field, fields) ? value : null;
}

private boolean fieldIsNeeded(String field, @Nullable List<String> fields) {
private static <T> boolean shouldSetValue(String field, T value, List<String> fields) {
return valueIfFieldNeeded(field, value, fields) != null;
}

private static boolean fieldIsNeeded(String field, @Nullable List<String> fields) {
return fields == null || fields.contains(field);
}
}

+ 45
- 49
server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/SearchActionTest.java View File

@@ -20,70 +20,61 @@
package org.sonar.server.qualityprofile.ws;

import com.google.common.collect.ImmutableMap;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.api.resources.Language;
import org.sonar.api.resources.Languages;
import org.sonar.api.server.ws.WebService.Param;
import org.sonar.api.utils.System2;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.DbTester;
import org.sonar.db.component.ComponentDao;
import org.sonar.db.component.ComponentTesting;
import org.sonar.db.qualityprofile.QualityProfileDao;
import org.sonar.db.qualityprofile.QualityProfileDto;
import org.sonar.db.component.ComponentTesting;
import org.sonar.server.db.DbClient;
import org.sonar.server.language.LanguageTesting;
import org.sonar.server.qualityprofile.QProfileLoader;
import org.sonar.server.qualityprofile.QProfileLookup;
import org.sonar.server.ws.WsTester;
import org.sonar.server.ws.WsActionTester;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.sonar.test.JsonAssert.assertJson;

public class SearchActionTest {

@Rule
public DbTester dbTester = DbTester.create(System2.INSTANCE);
public DbTester db = DbTester.create(System2.INSTANCE);
@Rule
public ExpectedException expectedException = ExpectedException.none();

// TODO remove mock
private QProfileLoader profileLoader = mock(QProfileLoader.class);
private DbClient dbClient;

private QualityProfileDao qualityProfileDao;

private Language xoo1;
private Language xoo2;

private WsTester tester;

private DbSession session;

private QProfileLoader profileLoader;
private DbSession dbSession;
private WsActionTester ws;

@Before
public void setUp() {
dbTester.truncateTables();
qualityProfileDao = new QualityProfileDao(dbTester.myBatis(), mock(System2.class));
dbClient = new DbClient(dbTester.database(), dbTester.myBatis(), qualityProfileDao);
session = dbClient.openSession(false);

// TODO Replace with actual implementation after removal of DaoV2...
profileLoader = mock(QProfileLoader.class);
db.truncateTables();
dbClient = db.getDbClient();
qualityProfileDao = dbClient.qualityProfileDao();
dbSession = db.getSession();

xoo1 = LanguageTesting.newLanguage("xoo1");
xoo2 = LanguageTesting.newLanguage("xoo2");

tester = new WsTester(new QProfilesWs(
mock(RuleActivationActions.class),
mock(BulkRuleActivationActions.class),
mock(ProjectAssociationActions.class),
new SearchAction(new Languages(xoo1, xoo2), new QProfileLookup(dbClient), profileLoader, qualityProfileDao)));
}

@After
public void teadDown() {
session.close();
ws = new WsActionTester(new SearchAction(
new Languages(xoo1, xoo2),
new QProfileLookup(dbClient),
profileLoader,
qualityProfileDao));
}

@Test
@@ -91,48 +82,53 @@ public class SearchActionTest {
when(profileLoader.countAllActiveRules()).thenReturn(ImmutableMap.of(
"sonar-way-xoo1-12345", 11L,
"my-sonar-way-xoo2-34567", 33L
));
));

qualityProfileDao.insert(session,
qualityProfileDao.insert(dbSession,
QualityProfileDto.createFor("sonar-way-xoo1-12345").setLanguage(xoo1.getKey()).setName("Sonar way").setDefault(true),
QualityProfileDto.createFor("sonar-way-xoo2-23456").setLanguage(xoo2.getKey()).setName("Sonar way"),
QualityProfileDto.createFor("my-sonar-way-xoo2-34567").setLanguage(xoo2.getKey()).setName("My Sonar way").setParentKee("sonar-way-xoo2-23456"),
QualityProfileDto.createFor("sonar-way-other-666").setLanguage("other").setName("Sonar way").setDefault(true)
);
new ComponentDao().insert(session,
);
new ComponentDao().insert(dbSession,
ComponentTesting.newProjectDto("project-uuid1"),
ComponentTesting.newProjectDto("project-uuid2"));
qualityProfileDao.insertProjectProfileAssociation("project-uuid1", "sonar-way-xoo2-23456", session);
qualityProfileDao.insertProjectProfileAssociation("project-uuid2", "sonar-way-xoo2-23456", session);
session.commit();
qualityProfileDao.insertProjectProfileAssociation("project-uuid1", "sonar-way-xoo2-23456", dbSession);
qualityProfileDao.insertProjectProfileAssociation("project-uuid2", "sonar-way-xoo2-23456", dbSession);
commit();

tester.newGetRequest("api/qualityprofiles", "search").execute().assertJson(this.getClass(), "search.json");
String result = ws.newRequest().execute().getInput();

assertJson(result).isSimilarTo(getClass().getResource("SearchActionTest/search.json"));
}

@Test
public void search_with_fields() throws Exception {
qualityProfileDao.insert(session,
qualityProfileDao.insert(dbSession,
QualityProfileDto.createFor("sonar-way-xoo1-12345").setLanguage(xoo1.getKey()).setName("Sonar way").setDefault(true),
QualityProfileDto.createFor("sonar-way-xoo2-23456").setLanguage(xoo2.getKey()).setName("Sonar way"),
QualityProfileDto.createFor("my-sonar-way-xoo2-34567").setLanguage(xoo2.getKey()).setName("My Sonar way").setParentKee("sonar-way-xoo2-23456")
);
session.commit();
);
commit();

tester.newGetRequest("api/qualityprofiles", "search").setParam(Param.FIELDS, "key,language").execute().assertJson(this.getClass(), "search_fields.json");
}
String result = ws.newRequest().setParam(Param.FIELDS, "key,language").execute().getInput();

@Test(expected = IllegalArgumentException.class)
public void fail_on_unknown_fields() throws Exception {
tester.newGetRequest("api/qualityprofiles", "search").setParam(Param.FIELDS, "polop").execute();
assertJson(result).isSimilarTo(getClass().getResource("SearchActionTest/search_fields.json"));
}

@Test
public void search_for_language() throws Exception {
qualityProfileDao.insert(session,
qualityProfileDao.insert(dbSession,
QualityProfileDto.createFor("sonar-way-xoo1-12345").setLanguage(xoo1.getKey()).setName("Sonar way")
);
session.commit();
commit();

String result = ws.newRequest().setParam("language", xoo1.getKey()).execute().getInput();

assertJson(result).isSimilarTo(getClass().getResource("SearchActionTest/search_xoo1.json"));
}

tester.newGetRequest("api/qualityprofiles", "search").setParam("language", xoo1.getKey()).execute().assertJson(this.getClass(), "search_xoo1.json");
private void commit() {
dbSession.commit();
}
}

+ 2483
- 0
sonar-ws/src/main/gen-java/org/sonarqube/ws/QualityProfiles.java
File diff suppressed because it is too large
View File


+ 46
- 0
sonar-ws/src/main/protobuf/ws-qualityprofiles.proto View File

@@ -0,0 +1,46 @@
// SonarQube, open source software quality management tool.
// Copyright (C) 2008-2015 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.

syntax = "proto2";

package sonarqube.ws.rules;

option java_package = "org.sonarqube.ws";

option java_outer_classname = "QualityProfiles";

option optimize_for = SPEED;

// WS api/qualityprofiles/search
message WsSearchResponse {
repeated QualityProfile profiles = 1;

message QualityProfile {
optional string key = 1;
optional string name = 2;
optional string language = 3;
optional string languageName = 4;
optional bool isInherited = 5;
optional string parentKey = 6;
optional string parentName = 7;
optional bool isDefault = 8;
optional int64 activeRuleCount = 9;
optional int64 projectCount = 10;
optional string rulesUpdatedAt = 11;
}
}

Loading…
Cancel
Save