Skip to content

Commit

Permalink
SONAR-6615 ws custom_measures/search search for custom measures
Browse files Browse the repository at this point in the history
  • Loading branch information
teryk committed Jun 26, 2015
1 parent 163630e commit 87fac1a
Show file tree
Hide file tree
Showing 19 changed files with 706 additions and 52 deletions.
Expand Up @@ -24,12 +24,14 @@
import java.util.List; import java.util.List;
import javax.annotation.CheckForNull; import javax.annotation.CheckForNull;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import org.apache.ibatis.session.RowBounds;
import org.sonar.api.server.ServerSide; import org.sonar.api.server.ServerSide;
import org.sonar.core.measure.custom.db.CustomMeasureDto; import org.sonar.core.measure.custom.db.CustomMeasureDto;
import org.sonar.core.measure.custom.db.CustomMeasureMapper; import org.sonar.core.measure.custom.db.CustomMeasureMapper;
import org.sonar.core.persistence.DaoComponent; import org.sonar.core.persistence.DaoComponent;
import org.sonar.core.persistence.DaoUtils; import org.sonar.core.persistence.DaoUtils;
import org.sonar.core.persistence.DbSession; import org.sonar.core.persistence.DbSession;
import org.sonar.server.es.SearchOptions;
import org.sonar.server.exceptions.NotFoundException; import org.sonar.server.exceptions.NotFoundException;


@ServerSide @ServerSide
Expand Down Expand Up @@ -77,11 +79,19 @@ public int countByComponentIdAndMetricId(DbSession session, String componentUuid
return mapper(session).countByComponentIdAndMetricId(componentUuid, metricId); return mapper(session).countByComponentIdAndMetricId(componentUuid, metricId);
} }


public List<CustomMeasureDto> selectByComponentUuid(DbSession session, String componentUuid, SearchOptions searchOptions) {
return mapper(session).selectByComponentUuid(componentUuid, new RowBounds(searchOptions.getOffset(), searchOptions.getLimit()));
}

public List<CustomMeasureDto> selectByComponentUuid(DbSession session, String componentUuid) { public List<CustomMeasureDto> selectByComponentUuid(DbSession session, String componentUuid) {
return mapper(session).selectByComponentUuid(componentUuid); return mapper(session).selectByComponentUuid(componentUuid);
} }


private CustomMeasureMapper mapper(DbSession session) { private CustomMeasureMapper mapper(DbSession session) {
return session.getMapper(CustomMeasureMapper.class); return session.getMapper(CustomMeasureMapper.class);
} }

public int countByComponentUuid(DbSession dbSession, String uuid) {
return mapper(dbSession).countByComponentUuid(uuid);
}
} }
Expand Up @@ -132,7 +132,7 @@ public void handle(Request request, Response response) throws Exception {
dbSession.commit(); dbSession.commit();


JsonWriter json = response.newJsonWriter(); JsonWriter json = response.newJsonWriter();
customMeasureJsonWriter.write(json, measure, metric, component, user); customMeasureJsonWriter.write(json, measure, metric, component, user, CustomMeasureJsonWriter.OPTIONAL_FIELDS);
json.close(); json.close();
} finally { } finally {
MyBatis.closeQuietly(dbSession); MyBatis.closeQuietly(dbSession);
Expand Down
Expand Up @@ -20,55 +20,63 @@


package org.sonar.server.measure.custom.ws; package org.sonar.server.measure.custom.ws;


import com.google.common.collect.ImmutableSet;
import java.util.Collection;
import java.util.Date; import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.sonar.api.measures.Metric; import org.sonar.api.measures.Metric;
import org.sonar.api.server.ServerSide;
import org.sonar.api.user.User; import org.sonar.api.user.User;
import org.sonar.api.utils.text.JsonWriter; import org.sonar.api.utils.text.JsonWriter;
import org.sonar.core.component.ComponentDto; import org.sonar.core.component.ComponentDto;
import org.sonar.core.measure.custom.db.CustomMeasureDto; import org.sonar.core.measure.custom.db.CustomMeasureDto;
import org.sonar.core.metric.db.MetricDto; import org.sonar.core.metric.db.MetricDto;
import org.sonar.server.metric.ws.MetricJsonWriter;
import org.sonar.server.user.ws.UserJsonWriter; import org.sonar.server.user.ws.UserJsonWriter;


import static org.sonar.server.measure.custom.ws.CreateAction.PARAM_DESCRIPTION; import static org.sonar.server.ws.JsonWriterUtils.isFieldNeeded;
import static org.sonar.server.measure.custom.ws.CreateAction.PARAM_PROJECT_ID; import static org.sonar.server.ws.JsonWriterUtils.writeIfNeeded;
import static org.sonar.server.measure.custom.ws.CreateAction.PARAM_PROJECT_KEY;
import static org.sonar.server.measure.custom.ws.CreateAction.PARAM_VALUE;


@ServerSide
public class CustomMeasureJsonWriter { public class CustomMeasureJsonWriter {
private static final String FIELD_ID = "id"; private static final String FIELD_ID = "id";
private static final String FIELD_PROJECT_ID = PARAM_PROJECT_ID; private static final String FIELD_PROJECT_ID = "projectId";
private static final String FIELD_PROJECT_KEY = PARAM_PROJECT_KEY; private static final String FIELD_PROJECT_KEY = "projectKey";
private static final String FIELD_VALUE = PARAM_VALUE; private static final String FIELD_VALUE = "value";
private static final String FIELD_DESCRIPTION = PARAM_DESCRIPTION; private static final String FIELD_DESCRIPTION = "description";
private static final String FIELD_METRIC = "metric"; private static final String FIELD_METRIC = "metric";
private static final String FIELD_METRIC_KEY = "key";
private static final String FIELD_METRIC_ID = "id";
private static final String FIELD_METRIC_TYPE = "type";
private static final String FIELD_CREATED_AT = "createdAt"; private static final String FIELD_CREATED_AT = "createdAt";
private static final String FIELD_UPDATED_AT = "updatedAt"; private static final String FIELD_UPDATED_AT = "updatedAt";
private static final String FIELD_USER = "user"; private static final String FIELD_USER = "user";


public static final Set<String> OPTIONAL_FIELDS = ImmutableSet.of(FIELD_PROJECT_ID, FIELD_PROJECT_KEY, FIELD_VALUE, FIELD_DESCRIPTION, FIELD_METRIC, FIELD_CREATED_AT,
FIELD_UPDATED_AT, FIELD_USER);

private final UserJsonWriter userJsonWriter; private final UserJsonWriter userJsonWriter;


public CustomMeasureJsonWriter(UserJsonWriter userJsonWriter) { public CustomMeasureJsonWriter(UserJsonWriter userJsonWriter) {
this.userJsonWriter = userJsonWriter; this.userJsonWriter = userJsonWriter;
} }


public void write(JsonWriter json, CustomMeasureDto measure, MetricDto metric, ComponentDto component, User user) { public void write(JsonWriter json, CustomMeasureDto measure, MetricDto metric, ComponentDto component, User user, Collection<String> fieldsToReturn) {
json.beginObject(); json.beginObject();
json.prop(FIELD_ID, String.valueOf(measure.getId())); json.prop(FIELD_ID, String.valueOf(measure.getId()));
json.name(FIELD_METRIC); writeIfNeeded(json, measureValue(measure, metric), FIELD_VALUE, fieldsToReturn);
writeMetric(json, metric); writeIfNeeded(json, measure.getDescription(), FIELD_DESCRIPTION, fieldsToReturn);
json.prop(FIELD_PROJECT_ID, component.uuid()); if (isFieldNeeded(FIELD_METRIC, fieldsToReturn)) {
json.prop(FIELD_PROJECT_KEY, component.key()); json.name(FIELD_METRIC);
json.prop(FIELD_DESCRIPTION, measure.getDescription()); MetricJsonWriter.write(json, metric, MetricJsonWriter.MANDATORY_FIELDS);
json.prop(FIELD_VALUE, measureValue(measure, metric)); }
json.propDateTime(FIELD_CREATED_AT, new Date(measure.getCreatedAt())); writeIfNeeded(json, component.uuid(), FIELD_PROJECT_ID, fieldsToReturn);
json.propDateTime(FIELD_UPDATED_AT, new Date(measure.getUpdatedAt())); writeIfNeeded(json, component.key(), FIELD_PROJECT_KEY, fieldsToReturn);
json.name(FIELD_USER); writeIfNeeded(json, new Date(measure.getCreatedAt()), FIELD_CREATED_AT, fieldsToReturn);
userJsonWriter.write(json, user); writeIfNeeded(json, new Date(measure.getUpdatedAt()), FIELD_UPDATED_AT, fieldsToReturn);

if (isFieldNeeded(FIELD_USER, fieldsToReturn)) {
json.name(FIELD_USER);
userJsonWriter.write(json, user);
}

json.endObject(); json.endObject();
} }


Expand Down Expand Up @@ -99,12 +107,13 @@ private String measureValue(CustomMeasureDto measure, MetricDto metric) {
} }
} }


private static void writeMetric(JsonWriter json, MetricDto metric) { public void write(JsonWriter json, List<CustomMeasureDto> customMeasures, ComponentDto project, Map<Integer, MetricDto> metricsById, Map<String, User> usersByLogin,
json.beginObject(); Collection<String> fieldsToReturn) {
json.prop(FIELD_METRIC_ID, String.valueOf(metric.getId())); json.name("customMeasures");
json.prop(FIELD_METRIC_KEY, metric.getKey()); json.beginArray();
json.prop(FIELD_METRIC_TYPE, metric.getValueType()); for (CustomMeasureDto customMeasure : customMeasures) {
json.endObject(); write(json, customMeasure, metricsById.get(customMeasure.getMetricId()), project, usersByLogin.get(customMeasure.getUserLogin()), fieldsToReturn);
}
json.endArray();
} }

} }
Expand Up @@ -30,6 +30,7 @@ protected void configureModule() {
CreateAction.class, CreateAction.class,
UpdateAction.class, UpdateAction.class,
DeleteAction.class, DeleteAction.class,
SearchAction.class,
MetricsAction.class, MetricsAction.class,
CustomMeasureJsonWriter.class, CustomMeasureJsonWriter.class,
CustomMeasureValidator.class); CustomMeasureValidator.class);
Expand Down
@@ -0,0 +1,180 @@
/*
* 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.measure.custom.ws;

import com.google.common.base.Function;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import java.util.List;
import java.util.Map;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
import org.sonar.api.user.User;
import org.sonar.api.utils.text.JsonWriter;
import org.sonar.core.component.ComponentDto;
import org.sonar.core.measure.custom.db.CustomMeasureDto;
import org.sonar.core.metric.db.MetricDto;
import org.sonar.core.persistence.DbSession;
import org.sonar.core.persistence.MyBatis;
import org.sonar.server.db.DbClient;
import org.sonar.server.es.SearchOptions;
import org.sonar.server.user.index.UserIndex;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.Sets.newHashSet;

public class SearchAction implements CustomMeasuresWsAction {

public static final String ACTION = "search";
public static final String PARAM_PROJECT_ID = "projectId";
public static final String PARAM_PROJECT_KEY = "projectKey";

private final DbClient dbClient;
private final UserIndex userIndex;
private final CustomMeasureJsonWriter customMeasureJsonWriter;

public SearchAction(DbClient dbClient, UserIndex userIndex, CustomMeasureJsonWriter customMeasureJsonWriter) {
this.dbClient = dbClient;
this.userIndex = userIndex;
this.customMeasureJsonWriter = customMeasureJsonWriter;
}

@Override
public void define(WebService.NewController context) {
WebService.NewAction action = context.createAction(ACTION)
.setDescription("List custom measures. The project id or project key must be provided.")
.setSince("5.2")
.addFieldsParam(CustomMeasureJsonWriter.OPTIONAL_FIELDS)
.addPagingParams(100)
.setResponseExample(getClass().getResource("example-search.json"))
.setHandler(this);

action.createParam(PARAM_PROJECT_ID)
.setDescription("Project id")
.setExampleValue("ce4c03d6-430f-40a9-b777-ad877c00aa4d");

action.createParam(PARAM_PROJECT_KEY)
.setDescription("Project key")
.setExampleValue("org.apache.hbas:hbase");
}

@Override
public void handle(Request request, Response response) throws Exception {
String projectUuid = request.param(PARAM_PROJECT_ID);
String projectKey = request.param(PARAM_PROJECT_KEY);
List<String> fieldsToReturn = request.paramAsStrings(WebService.Param.FIELDS);
SearchOptions searchOptions = new SearchOptions()
.setPage(request.mandatoryParamAsInt(WebService.Param.PAGE),
request.mandatoryParamAsInt(WebService.Param.PAGE_SIZE));

DbSession dbSession = dbClient.openSession(false);
try {
ComponentDto project = searchProject(dbSession, projectUuid, projectKey);
List<CustomMeasureDto> customMeasures = searchCustomMeasures(dbSession, project, searchOptions);
int nbCustomMeasures = countTotalOfCustomMeasures(dbSession, project);
Map<String, User> usersByLogin = usersByLogin(customMeasures);
Map<Integer, MetricDto> metricsById = metricsById(dbSession, customMeasures);

writeResponse(response, customMeasures, nbCustomMeasures, project, metricsById, usersByLogin, searchOptions, fieldsToReturn);
} finally {
MyBatis.closeQuietly(dbSession);
}
}

private int countTotalOfCustomMeasures(DbSession dbSession, ComponentDto project) {
return dbClient.customMeasureDao().countByComponentUuid(dbSession, project.uuid());
}

private List<CustomMeasureDto> searchCustomMeasures(DbSession dbSession, ComponentDto project, SearchOptions searchOptions) {
return dbClient.customMeasureDao().selectByComponentUuid(dbSession, project.uuid(), searchOptions);
}

private void writeResponse(Response response, List<CustomMeasureDto> customMeasures, int nbCustomMeasures, ComponentDto project, Map<Integer, MetricDto> metricsById,
Map<String, User> usersByLogin, SearchOptions searchOptions, List<String> fieldsToReturn) {
JsonWriter json = response.newJsonWriter();
json.beginObject();
customMeasureJsonWriter.write(json, customMeasures, project, metricsById, usersByLogin, fieldsToReturn);
searchOptions.writeJson(json, nbCustomMeasures);
json.endObject();
json.close();
}

private Map<Integer, MetricDto> metricsById(DbSession dbSession, List<CustomMeasureDto> customMeasures) {
List<MetricDto> metrics = dbClient.metricDao().selectByIds(dbSession, newHashSet(Lists.transform(customMeasures, CustomMeasureToMetricIdFunction.INSTANCE)));
return Maps.uniqueIndex(metrics, MetricToIdFunction.INSTANCE);
}

private Map<String, User> usersByLogin(List<CustomMeasureDto> customMeasures) {
return FluentIterable.from(customMeasures)
.transform(CustomMeasureToUserLoginFunction.INSTANCE)
.toMap(new UserLoginToUserFunction());
}

private ComponentDto searchProject(DbSession dbSession, @Nullable String projectUuid, @Nullable String projectKey) {
checkArgument(projectUuid != null ^ projectKey != null, "The project id or project key must be provided, not both.");
ComponentDto project;
if (projectUuid != null) {
project = dbClient.componentDao().selectByUuid(dbSession, projectUuid);
} else {
project = dbClient.componentDao().selectByKey(dbSession, projectKey);
}

return project;
}

private enum CustomMeasureToUserLoginFunction implements Function<CustomMeasureDto, String> {
INSTANCE;

@Override
public String apply(@Nonnull CustomMeasureDto customMeasure) {
return customMeasure.getUserLogin();
}
}

private final class UserLoginToUserFunction implements Function<String, User> {
@Override
public User apply(@Nonnull String userLogin) {
return userIndex.getByLogin(userLogin);
}
}

private enum CustomMeasureToMetricIdFunction implements Function<CustomMeasureDto, Integer> {
INSTANCE;

@Override
public Integer apply(@Nonnull CustomMeasureDto customMeasure) {
return customMeasure.getMetricId();
}
}

private enum MetricToIdFunction implements Function<MetricDto, Integer> {
INSTANCE;

@Override
public Integer apply(@Nonnull MetricDto metric) {
return metric.getId();
}
}
}
Expand Up @@ -100,7 +100,6 @@ public void handle(Request request, Response response) throws Exception {
checkPermissions(component); checkPermissions(component);
User user = userIndex.getByLogin(userSession.getLogin()); User user = userIndex.getByLogin(userSession.getLogin());



setValue(customMeasure, value, metric); setValue(customMeasure, value, metric);
setDescription(customMeasure, description); setDescription(customMeasure, description);
customMeasure.setUserLogin(user.login()); customMeasure.setUserLogin(user.login());
Expand All @@ -109,7 +108,7 @@ public void handle(Request request, Response response) throws Exception {
dbSession.commit(); dbSession.commit();


JsonWriter json = response.newJsonWriter(); JsonWriter json = response.newJsonWriter();
customMeasureJsonWriter.write(json, customMeasure, metric, component, user); customMeasureJsonWriter.write(json, customMeasure, metric, component, user, CustomMeasureJsonWriter.OPTIONAL_FIELDS);
json.close(); json.close();
} finally { } finally {
MyBatis.closeQuietly(dbSession); MyBatis.closeQuietly(dbSession);
Expand Down
Expand Up @@ -25,9 +25,11 @@
import com.google.common.collect.Collections2; import com.google.common.collect.Collections2;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import javax.annotation.CheckForNull; import javax.annotation.CheckForNull;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
Expand Down Expand Up @@ -111,6 +113,16 @@ public List<MetricDto> selectAvailableCustomMetricsByComponentKey(DbSession sess
return mapper(session).selectAvailableCustomMetricsByComponentUuid(projectKey); return mapper(session).selectAvailableCustomMetricsByComponentUuid(projectKey);
} }


public List<MetricDto> selectByIds(final DbSession session, Set<Integer> idsSet) {
List<Integer> ids = new ArrayList<>(idsSet);
return DaoUtils.executeLargeInputs(ids, new Function<List<Integer>, List<MetricDto>>() {
@Override
public List<MetricDto> apply(@Nonnull List<Integer> ids) {
return mapper(session).selectByIds(ids);
}
});
}

private static class NotEmptyPredicate implements Predicate<String> { private static class NotEmptyPredicate implements Predicate<String> {


@Override @Override
Expand Down

0 comments on commit 87fac1a

Please sign in to comment.