@@ -28,7 +28,7 @@ | |||
</dependency> | |||
<dependency> | |||
<groupId>${project.groupId}</groupId> | |||
<artifactId>sonar-db</artifactId> | |||
<artifactId>sonar-dbClient</artifactId> | |||
<version>${project.version}</version> | |||
</dependency> | |||
<dependency> |
@@ -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(); | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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(); | |||
} | |||
} |
@@ -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; | |||
} | |||
} |