*/
package org.sonar.batch.index;
-import org.sonar.api.database.model.MeasureDto;
+import javax.annotation.Nullable;
import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.collect.Iterables;
import com.google.common.collect.LinkedHashMultimap;
+import com.google.common.collect.Lists;
import com.google.common.collect.SetMultimap;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.ibatis.session.SqlSession;
import org.slf4j.LoggerFactory;
+import org.sonar.api.database.model.MeasureDataDto;
+import org.sonar.api.database.model.MeasureDto;
import org.sonar.api.database.model.MeasureModel;
import org.sonar.api.database.model.MeasureModelMapper;
import org.sonar.api.database.model.Snapshot;
import org.sonar.core.persistence.MyBatis;
import java.util.Collection;
+import java.util.List;
import java.util.Map;
+import static com.google.common.collect.Iterables.filter;
+
+import static com.google.common.base.Predicates.not;
+
public final class MeasurePersister {
private final MyBatis mybatis;
private final ResourcePersister resourcePersister;
this.delayedMode = delayedMode;
}
+ public Measure reloadMeasure(Measure measure) {
+ return memoryOptimizer.reloadMeasure(measure);
+ }
+
+ public void dump() {
+ LoggerFactory.getLogger(getClass()).debug("{} measures to dump", unsavedMeasuresByResource.size());
+
+ List<MeasureDto> measuresToSave = getMeasuresToSave();
+ insert(filter(measuresToSave, HAS_LARGE_DATA));
+ batchInsert(filter(measuresToSave, not(HAS_LARGE_DATA)));
+ }
+
public void saveMeasure(Resource resource, Measure measure) {
if (shouldSaveLater(measure)) {
unsavedMeasuresByResource.put(resource, measure);
return;
}
- MeasureModel model = null;
- if (measure.getId() != null) {
- model = update(measure);
- } else if (shouldPersistMeasure(resource, measure)) {
- model = insert(measure, resourcePersister.getSnapshotOrFail(resource));
- }
+ MeasureModel model = insertOrUpdate(resource, measure);
if (model != null) {
memoryOptimizer.evictDataMeasure(measure, model);
}
}
- public Measure reloadMeasure(Measure measure) {
- return memoryOptimizer.reloadMeasure(measure);
+ private MeasureModel insertOrUpdate(Resource resource, Measure measure) {
+ if (measure.getId() != null) {
+ return update(measure);
+ }
+ if (shouldPersistMeasure(resource, measure)) {
+ return insert(measure, resourcePersister.getSnapshotOrFail(resource));
+ }
+ return null;
}
private boolean shouldSaveLater(Measure measure) {
&& (measure.getVariation5() == null || NumberUtils.compare(measure.getVariation5().doubleValue(), 0.0) == 0);
}
- public void dump() {
- LoggerFactory.getLogger(getClass()).debug("{} measures to dump", unsavedMeasuresByResource.size());
-
- SqlSession session = mybatis.openSession();
- try {
- MeasureModelMapper mapper = session.getMapper(MeasureModelMapper.class);
+ private List<MeasureDto> getMeasuresToSave() {
+ List<MeasureDto> batch = Lists.newArrayList();
- Map<Resource, Collection<Measure>> map = unsavedMeasuresByResource.asMap();
- for (Map.Entry<Resource, Collection<Measure>> entry : map.entrySet()) {
- Resource resource = entry.getKey();
- Snapshot snapshot = resourcePersister.getSnapshot(entry.getKey());
- for (Measure measure : entry.getValue()) {
- if (shouldPersistMeasure(resource, measure)) {
- mapper.insert(new MeasureDto(model(measure).setSnapshotId(snapshot.getId())));
- }
+ Map<Resource, Collection<Measure>> map = unsavedMeasuresByResource.asMap();
+ for (Map.Entry<Resource, Collection<Measure>> entry : map.entrySet()) {
+ Resource resource = entry.getKey();
+ Snapshot snapshot = resourcePersister.getSnapshot(entry.getKey());
+ for (Measure measure : entry.getValue()) {
+ if (shouldPersistMeasure(resource, measure)) {
+ batch.add(new MeasureDto(model(measure).setSnapshotId(snapshot.getId())));
}
}
- session.commit();
- } finally {
- MyBatis.closeQuietly(session);
}
unsavedMeasuresByResource.clear();
+ return batch;
}
private MeasureModel model(Measure measure) {
return model;
}
+ private void batchInsert(Iterable<MeasureDto> values) {
+ SqlSession session = mybatis.openBatchSession();
+ try {
+ MeasureModelMapper mapper = session.getMapper(MeasureModelMapper.class);
+ for (MeasureDto value : values) {
+ mapper.insert(value);
+ }
+ session.commit();
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ private void insert(Iterable<MeasureDto> values) {
+ SqlSession session = mybatis.openSession();
+ try {
+ MeasureModelMapper mapper = session.getMapper(MeasureModelMapper.class);
+ for (MeasureDto value : values) {
+ mapper.insert(value);
+ mapper.insertData(new MeasureDataDto(value.getId(), value.getSnapshotId(), value.getMeasureData().getData()));
+ }
+ session.commit();
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
private MeasureModel insert(Measure measure, Snapshot snapshot) {
MeasureModel model = model(measure).setSnapshotId(snapshot.getId());
SqlSession session = mybatis.openSession();
try {
MeasureModelMapper mapper = session.getMapper(MeasureModelMapper.class);
- mapper.insert(new MeasureDto(model));
+ MeasureDto value = new MeasureDto(model);
+ mapper.insert(value);
+ if (value.getMeasureData() != null) {
+ mapper.insertData(new MeasureDataDto(value.getId(), value.getSnapshotId(), value.getMeasureData().getData()));
+ }
session.commit();
} finally {
MyBatis.closeQuietly(session);
return model;
}
+
+ private static final Predicate<MeasureDto> HAS_LARGE_DATA = new Predicate<MeasureDto>() {
+ public boolean apply(@Nullable MeasureDto measure) {
+ return (null != measure) && (measure.getMeasureData() != null);
+ }
+ };
}
*/
package org.sonar.batch.index;
+import org.apache.commons.lang.StringUtils;
import org.junit.Before;
import org.junit.Test;
import org.sonar.api.database.model.MeasureModel;
checkTables("shouldInsertRuleMeasure", "project_measures");
}
+ @Test
+ public void shouldInsertMeasureWithTextData() {
+ setupData("empty");
+
+ measurePersister.saveMeasure(project, new Measure(ncloc()).setData("SHORT"));
+ measurePersister.saveMeasure(project, new Measure(ncloc()).setData(StringUtils.repeat("0123456789", 10)));
+
+ checkTables("shouldInsertMeasureWithLargeData", "project_measures", "measure_data");
+ }
+
@Test
public void shouldUpdateMeasure() {
setupData("data");
--- /dev/null
+<dataset>
+
+ <project_measures id="1" VALUE="[null]" METRIC_ID="1" SNAPSHOT_ID="3001" alert_text="[null]" RULES_CATEGORY_ID="[null]"
+ RULE_ID="[null]" text_value="SHORT" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]" rule_priority="[null]" characteristic_id="[null]" url="[null]"
+ person_id="[null]"
+ variation_value_1="[null]" variation_value_2="[null]" variation_value_3="[null]" variation_value_4="[null]" variation_value_5="[null]"/>
+
+ <project_measures id="2" VALUE="[null]" METRIC_ID="1" SNAPSHOT_ID="3001" alert_text="[null]" RULES_CATEGORY_ID="[null]"
+ RULE_ID="[null]" text_value="[null]"
+ tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]" rule_priority="[null]" characteristic_id="[null]" url="[null]"
+ person_id="[null]"
+ variation_value_1="[null]" variation_value_2="[null]" variation_value_3="[null]" variation_value_4="[null]" variation_value_5="[null]"/>
+
+ <measure_data id="1" measure_id="2" snapshot_id="3001" data="MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OQ=="/>
+
+</dataset>
*/
package org.sonar.core.persistence;
-import org.sonar.api.database.model.MeasureDto;
-
-import org.sonar.api.database.model.MeasureModel;
-
-import org.sonar.api.database.model.MeasureModelMapper;
-
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.mapping.Environment;
-import org.apache.ibatis.session.*;
+import org.apache.ibatis.session.Configuration;
+import org.apache.ibatis.session.ExecutorType;
+import org.apache.ibatis.session.SqlSession;
+import org.apache.ibatis.session.SqlSessionFactory;
+import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
import org.slf4j.LoggerFactory;
import org.sonar.api.BatchComponent;
import org.sonar.api.ServerComponent;
-import org.sonar.core.dashboard.*;
+import org.sonar.api.database.model.MeasureDataDto;
+import org.sonar.api.database.model.MeasureDto;
+import org.sonar.api.database.model.MeasureModelMapper;
+import org.sonar.core.dashboard.ActiveDashboardDto;
+import org.sonar.core.dashboard.ActiveDashboardMapper;
+import org.sonar.core.dashboard.DashboardDto;
+import org.sonar.core.dashboard.DashboardMapper;
+import org.sonar.core.dashboard.WidgetDto;
+import org.sonar.core.dashboard.WidgetMapper;
+import org.sonar.core.dashboard.WidgetPropertyDto;
+import org.sonar.core.dashboard.WidgetPropertyMapper;
import org.sonar.core.dependency.DependencyDto;
import org.sonar.core.dependency.DependencyMapper;
import org.sonar.core.dependency.ResourceSnapshotDto;
import org.sonar.core.dependency.ResourceSnapshotMapper;
import org.sonar.core.duplication.DuplicationMapper;
import org.sonar.core.duplication.DuplicationUnitDto;
-import org.sonar.core.filter.*;
+import org.sonar.core.filter.CriterionDto;
+import org.sonar.core.filter.CriterionMapper;
+import org.sonar.core.filter.FilterColumnDto;
+import org.sonar.core.filter.FilterColumnMapper;
+import org.sonar.core.filter.FilterDto;
+import org.sonar.core.filter.FilterMapper;
import org.sonar.core.properties.PropertiesMapper;
import org.sonar.core.properties.PropertyDto;
import org.sonar.core.purge.PurgeMapper;
import org.sonar.core.purge.PurgeVendorMapper;
import org.sonar.core.purge.PurgeableSnapshotDto;
-import org.sonar.core.resource.*;
+import org.sonar.core.resource.ResourceDto;
+import org.sonar.core.resource.ResourceIndexDto;
+import org.sonar.core.resource.ResourceIndexerMapper;
+import org.sonar.core.resource.ResourceKeyUpdaterMapper;
+import org.sonar.core.resource.ResourceMapper;
+import org.sonar.core.resource.SnapshotDto;
import org.sonar.core.review.ReviewCommentDto;
import org.sonar.core.review.ReviewCommentMapper;
import org.sonar.core.review.ReviewDto;
import org.sonar.core.rule.RuleMapper;
import org.sonar.core.template.LoadedTemplateDto;
import org.sonar.core.template.LoadedTemplateMapper;
-import org.sonar.core.user.*;
+import org.sonar.core.user.AuthorDto;
+import org.sonar.core.user.AuthorMapper;
+import org.sonar.core.user.GroupDto;
+import org.sonar.core.user.GroupRoleDto;
+import org.sonar.core.user.RoleMapper;
+import org.sonar.core.user.UserDto;
+import org.sonar.core.user.UserMapper;
+import org.sonar.core.user.UserRoleDto;
import java.io.InputStream;
loadAlias(conf, "Widget", WidgetDto.class);
loadAlias(conf, "WidgetProperty", WidgetPropertyDto.class);
loadAlias(conf, "MeasureDto", MeasureDto.class);
+ loadAlias(conf, "MeasureDataDto", MeasureDataDto.class);
loadMapper(conf, ActiveDashboardMapper.class);
loadMapper(conf, AuthorMapper.class);
private InputStream getPathToMapper(Class mapperClass) {
InputStream input = getClass().getResourceAsStream(
- "/" + StringUtils.replace(mapperClass.getName(), ".", "/") + "-" + database.getDialect().getId() + ".xml");
+ "/" + StringUtils.replace(mapperClass.getName(), ".", "/") + "-" + database.getDialect().getId() + ".xml");
if (input == null) {
input = getClass().getResourceAsStream("/" + StringUtils.replace(mapperClass.getName(), ".", "/") + ".xml");
}
--- /dev/null
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.api.database.model;
+
+public class MeasureDataDto {
+ private final Long measureId;
+ private final Integer snapshotId;
+ private final byte[] data;
+
+ public MeasureDataDto(Long measureId, Integer snapshotId, byte[] data) {
+ this.measureId = measureId;
+ this.snapshotId = snapshotId;
+ this.data = data;
+ }
+
+ public Long getMeasureId() {
+ return measureId;
+ }
+
+ public Integer getSnapshotId() {
+ return snapshotId;
+ }
+
+ public byte[] getData() {
+ return data;
+ }
+}
import java.util.Date;
public class MeasureDto {
- private final Long id;
- private final Double value;
- private final String textValue;
- private final Integer tendency;
- private final Integer metricId;
- private final Integer snapshotId;
- private final Integer projectId;
- private final String description;
- private final Date measureDate;
- private final Integer ruleId;
+ private final Long id;
+ private final Double value;
+ private final String textValue;
+ private final Integer tendency;
+ private final Integer metricId;
+ private final Integer snapshotId;
+ private final Integer projectId;
+ private final String description;
+ private final Date measureDate;
+ private final Integer ruleId;
private final Integer rulePriority;
private final String alertStatus;
- private final String alertText;
- private final Double variationValue1;
- private final Double variationValue2;
- private final Double variationValue3;
- private final Double variationValue4;
- private final Double variationValue5;
- private final String url;
- private final Integer characteristicId;
- private final Integer personId;
-
- // private List<MeasureData> measureData = new ArrayList<MeasureData>();
+ private final String alertText;
+ private final Double variationValue1;
+ private final Double variationValue2;
+ private final Double variationValue3;
+ private final Double variationValue4;
+ private final Double variationValue5;
+ private final String url;
+ private final Integer characteristicId;
+ private final Integer personId;
+ private final MeasureData measureData;
public MeasureDto(MeasureModel model) {
id = model.getId();
url = model.getUrl();
characteristicId = (null == model.getCharacteristic()) ? null : model.getCharacteristic().getId();
personId = model.getPersonId();
+ measureData = model.getMeasureData();
}
public Long getId() {
public Integer getPersonId() {
return personId;
}
+
+ public MeasureData getMeasureData() {
+ return measureData;
+ }
}
public interface MeasureModelMapper {
void insert(MeasureDto measure);
+ void insertData(MeasureDataDto data);
+
void update(MeasureDto measure);
}
)
</insert>
-
+ <insert id="insertData" parameterType="MeasureDataDto" useGeneratedKeys="true" keyProperty="id">
+ INSERT INTO measure_data (measure_id, snapshot_id, data)
+ VALUES (#{measureId}, #{snapshotId}, #{data})
+ </insert>
+
<update id="update" parameterType="MeasureDto">
UPDATE project_measures
SET