diff options
author | Julien HENRY <julien.henry@sonarsource.com> | 2014-04-23 14:41:32 +0200 |
---|---|---|
committer | Julien HENRY <julien.henry@sonarsource.com> | 2014-04-24 10:20:15 +0200 |
commit | 46395126b184c343df5471a147cdb48133f59f6f (patch) | |
tree | fbf002385dd8a2a9f45caa722c8d44e6663340b2 /sonar-batch | |
parent | 092b18233548f6b71e02341a895146f6f0fc685c (diff) | |
download | sonarqube-46395126b184c343df5471a147cdb48133f59f6f.tar.gz sonarqube-46395126b184c343df5471a147cdb48133f59f6f.zip |
SONAR-3437, SONAR-5189 Store measures in a persistit cache
Diffstat (limited to 'sonar-batch')
19 files changed, 348 insertions, 659 deletions
diff --git a/sonar-batch/src/main/java/org/sonar/batch/DefaultTimeMachine.java b/sonar-batch/src/main/java/org/sonar/batch/DefaultTimeMachine.java index 466be91dc79..565417efad0 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/DefaultTimeMachine.java +++ b/sonar-batch/src/main/java/org/sonar/batch/DefaultTimeMachine.java @@ -38,7 +38,12 @@ import org.sonar.batch.index.DefaultIndex; import javax.annotation.Nullable; import javax.persistence.Query; -import java.util.*; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Set; public class DefaultTimeMachine implements TimeMachine { @@ -155,7 +160,6 @@ public class DefaultTimeMachine implements TimeMachine { static Measure toMeasure(MeasureModel model, Metric metric, @Nullable Characteristic characteristic) { // NOTE: measures on rule are not supported Measure measure = new Measure(metric); - measure.setId(model.getId()); measure.setDescription(model.getDescription()); measure.setValue(model.getValue()); measure.setData(model.getData(metric)); diff --git a/sonar-batch/src/main/java/org/sonar/batch/debt/DebtDecorator.java b/sonar-batch/src/main/java/org/sonar/batch/debt/DebtDecorator.java index 71e9cc36a1c..17475936308 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/debt/DebtDecorator.java +++ b/sonar-batch/src/main/java/org/sonar/batch/debt/DebtDecorator.java @@ -48,6 +48,7 @@ import org.sonar.api.technicaldebt.batch.TechnicalDebtModel; import javax.annotation.CheckForNull; import javax.annotation.Nullable; + import java.util.Arrays; import java.util.List; import java.util.Map; @@ -110,7 +111,7 @@ public final class DebtDecorator implements Decorator { for (Measure measure : context.getChildrenMeasures(MeasuresFilters.rules(CoreMetrics.TECHNICAL_DEBT))) { Long debt = measure.getValue().longValue(); RuleMeasure ruleMeasure = (RuleMeasure) measure; - total += computeDebt(debt, ruleMeasure.getRule().ruleKey(), ruleDebts, characteristicDebts); + total += computeDebt(debt, ruleMeasure.ruleKey(), ruleDebts, characteristicDebts); } context.saveMeasure(CoreMetrics.TECHNICAL_DEBT, total.doubleValue()); diff --git a/sonar-batch/src/main/java/org/sonar/batch/index/Bucket.java b/sonar-batch/src/main/java/org/sonar/batch/index/Bucket.java index af6a87ae8a6..ea4ed3b11a0 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/index/Bucket.java +++ b/sonar-batch/src/main/java/org/sonar/batch/index/Bucket.java @@ -19,24 +19,15 @@ */ package org.sonar.batch.index; -import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.ListMultimap; import com.google.common.collect.Lists; -import org.sonar.api.measures.CoreMetrics; -import org.sonar.api.measures.Measure; -import org.sonar.api.measures.MeasuresFilter; -import org.sonar.api.measures.MeasuresFilters; import org.sonar.api.resources.Resource; -import org.sonar.api.utils.SonarException; -import java.util.Collection; import java.util.Collections; import java.util.List; public final class Bucket { private Resource resource; - private ListMultimap<String, Measure> measuresByMetric = ArrayListMultimap.create(); private Bucket parent; private List<Bucket> children; @@ -79,30 +70,7 @@ public final class Bucket { return parent; } - public void addMeasure(Measure measure) { - List<Measure> metricMeasures = measuresByMetric.get(measure.getMetric().getKey()); - - boolean add = true; - if (metricMeasures != null) { - int index = metricMeasures.indexOf(measure); - if (index > -1) { - if (metricMeasures.get(index) == measure) { - add = false; - } else if (measure.getMetric().equals(CoreMetrics.TESTS)) { - // Hack for SONAR-5212 - measuresByMetric.remove(measure.getMetric().getKey(), metricMeasures.get(index)); - } else { - throw new SonarException("Can not add twice the same measure on " + resource + ": " + measure); - } - } - } - if (add) { - measuresByMetric.put(measure.getMetric().getKey(), measure); - } - } - public void clear() { - measuresByMetric = null; children = null; if (parent != null) { parent.removeChild(this); @@ -110,16 +78,6 @@ public final class Bucket { } } - public <M> M getMeasures(final MeasuresFilter<M> filter) { - Collection<Measure> unfiltered; - if (filter instanceof MeasuresFilters.MetricFilter) { - unfiltered = measuresByMetric.get(((MeasuresFilters.MetricFilter) filter).filterOnMetricKey()); - } else { - unfiltered = measuresByMetric.values(); - } - return filter.filter(unfiltered); - } - @Override public boolean equals(Object o) { if (this == o) { diff --git a/sonar-batch/src/main/java/org/sonar/batch/index/Cache.java b/sonar-batch/src/main/java/org/sonar/batch/index/Cache.java index 46f6bbf0a84..13e238fce5b 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/index/Cache.java +++ b/sonar-batch/src/main/java/org/sonar/batch/index/Cache.java @@ -26,6 +26,7 @@ import com.persistit.exception.PersistitException; import org.apache.commons.lang.builder.ToStringBuilder; import javax.annotation.CheckForNull; + import java.io.Serializable; import java.util.Iterator; import java.util.Set; @@ -219,7 +220,6 @@ public class Cache<V extends Serializable> { } } - /** * Clears the default as well as all group caches. */ @@ -294,6 +294,20 @@ public class Cache<V extends Serializable> { } /** + * Lazy-loading values for given keys + */ + public Iterable<V> values(Object firstKey, Object secondKey) { + try { + exchange.clear(); + exchange.append(firstKey).append(secondKey).append(Key.BEFORE); + Exchange iteratorExchange = new Exchange(exchange); + return new ValueIterable<V>(iteratorExchange, false); + } catch (Exception e) { + throw new IllegalStateException("Fail to get values from cache " + name, e); + } + } + + /** * Lazy-loading values for a given key */ public Iterable<V> values(Object key) { @@ -308,6 +322,20 @@ public class Cache<V extends Serializable> { } /** + * Lazy-loading values for a given key + */ + public Iterable<V> allValues(Object key) { + try { + exchange.clear(); + exchange.append(key).append(Key.BEFORE); + Exchange iteratorExchange = new Exchange(exchange); + return new ValueIterable<V>(iteratorExchange, true); + } catch (Exception e) { + throw new IllegalStateException("Fail to get values from cache " + name, e); + } + } + + /** * Lazy-loading values */ public Iterable<V> values() { @@ -352,7 +380,6 @@ public class Cache<V extends Serializable> { } } - // // LAZY ITERATORS AND ITERABLES // diff --git a/sonar-batch/src/main/java/org/sonar/batch/index/DefaultIndex.java b/sonar-batch/src/main/java/org/sonar/batch/index/DefaultIndex.java index 9624513e288..cea7aa71729 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/index/DefaultIndex.java +++ b/sonar-batch/src/main/java/org/sonar/batch/index/DefaultIndex.java @@ -52,6 +52,7 @@ import org.sonar.api.violations.ViolationQuery; import org.sonar.batch.ProjectTree; import org.sonar.batch.issue.DeprecatedViolations; import org.sonar.batch.issue.ModuleIssues; +import org.sonar.batch.scan.measure.MeasureCache; import org.sonar.core.component.ComponentKeys; import org.sonar.core.component.ScanGraph; @@ -85,17 +86,19 @@ public class DefaultIndex extends SonarIndex { private ProjectTree projectTree; private final DeprecatedViolations deprecatedViolations; private ModuleIssues moduleIssues; + private final MeasureCache measureCache; private ResourceKeyMigration migration; public DefaultIndex(PersistenceManager persistence, ProjectTree projectTree, MetricFinder metricFinder, - ScanGraph graph, DeprecatedViolations deprecatedViolations, ResourceKeyMigration migration) { + ScanGraph graph, DeprecatedViolations deprecatedViolations, ResourceKeyMigration migration, MeasureCache measureCache) { this.persistence = persistence; this.projectTree = projectTree; this.metricFinder = metricFinder; this.graph = graph; this.deprecatedViolations = deprecatedViolations; this.migration = migration; + this.measureCache = measureCache; } public void start() { @@ -174,24 +177,20 @@ public class DefaultIndex extends SonarIndex { @Override public Measure getMeasure(Resource resource, Metric metric) { - Bucket bucket = buckets.get(resource); - if (bucket != null) { - Measure measure = bucket.getMeasures(MeasuresFilters.metric(metric)); - if (measure != null) { - return persistence.reloadMeasure(measure); - } - } - return null; + return getMeasures(resource, MeasuresFilters.metric(metric)); } @Override public <M> M getMeasures(Resource resource, MeasuresFilter<M> filter) { - Bucket bucket = buckets.get(resource); - if (bucket != null) { - // TODO the data measures which are not kept in memory are not reloaded yet. Use getMeasure(). - return bucket.getMeasures(filter); + // Reload resource so that effective key is populated + Resource indexedResource = getResource(resource); + Iterable<Measure> unfiltered; + if (filter instanceof MeasuresFilters.MetricFilter) { + unfiltered = measureCache.byMetric(indexedResource, ((MeasuresFilters.MetricFilter) filter).filterOnMetricKey()); + } else { + unfiltered = measureCache.byResource(indexedResource); } - return null; + return filter.filter(unfiltered); } /** @@ -206,11 +205,7 @@ public class DefaultIndex extends SonarIndex { throw new SonarException("Unknown metric: " + measure.getMetricKey()); } measure.setMetric(metric); - bucket.addMeasure(measure); - - if (measure.getPersistenceMode().useDatabase()) { - persistence.saveMeasure(bucket.getResource(), measure); - } + measureCache.put(resource, measure); } return measure; } diff --git a/sonar-batch/src/main/java/org/sonar/batch/index/DefaultPersistenceManager.java b/sonar-batch/src/main/java/org/sonar/batch/index/DefaultPersistenceManager.java index 14da97381cf..39b9d8d472a 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/index/DefaultPersistenceManager.java +++ b/sonar-batch/src/main/java/org/sonar/batch/index/DefaultPersistenceManager.java @@ -22,7 +22,6 @@ package org.sonar.batch.index; import org.sonar.api.batch.Event; import org.sonar.api.database.model.Snapshot; import org.sonar.api.design.Dependency; -import org.sonar.api.measures.Measure; import org.sonar.api.resources.Project; import org.sonar.api.resources.ProjectLink; import org.sonar.api.resources.Resource; @@ -34,17 +33,14 @@ public final class DefaultPersistenceManager implements PersistenceManager { private ResourcePersister resourcePersister; private SourcePersister sourcePersister; - private MeasurePersister measurePersister; private DependencyPersister dependencyPersister; private LinkPersister linkPersister; private EventPersister eventPersister; public DefaultPersistenceManager(ResourcePersister resourcePersister, SourcePersister sourcePersister, - MeasurePersister measurePersister, DependencyPersister dependencyPersister, - LinkPersister linkPersister, EventPersister eventPersister) { + DependencyPersister dependencyPersister, LinkPersister linkPersister, EventPersister eventPersister) { this.resourcePersister = resourcePersister; this.sourcePersister = sourcePersister; - this.measurePersister = measurePersister; this.dependencyPersister = dependencyPersister; this.linkPersister = linkPersister; this.eventPersister = eventPersister; @@ -55,14 +51,6 @@ public final class DefaultPersistenceManager implements PersistenceManager { sourcePersister.clear(); } - public void setDelayedMode(boolean b) { - measurePersister.setDelayedMode(b); - } - - public void dump() { - measurePersister.dump(); - } - public void saveProject(Project project, Project parent) { resourcePersister.saveProject(project, parent); } @@ -82,16 +70,6 @@ public final class DefaultPersistenceManager implements PersistenceManager { return sourcePersister.getSource(resource); } - public void saveMeasure(Resource resource, Measure measure) { - if (ResourceUtils.isPersistable(resource)) { - measurePersister.saveMeasure(resource, measure); - } - } - - public Measure reloadMeasure(Measure measure) { - return measurePersister.reloadMeasure(measure); - } - public void saveDependency(Project project, Dependency dependency, Dependency parentDependency) { if (ResourceUtils.isPersistable(dependency.getFrom()) && ResourceUtils.isPersistable(dependency.getTo())) { dependencyPersister.saveDependency(project, dependency, parentDependency); diff --git a/sonar-batch/src/main/java/org/sonar/batch/index/MeasurePersister.java b/sonar-batch/src/main/java/org/sonar/batch/index/MeasurePersister.java index 8e61a931bd0..80cac3ab4cb 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/index/MeasurePersister.java +++ b/sonar-batch/src/main/java/org/sonar/batch/index/MeasurePersister.java @@ -20,94 +20,69 @@ package org.sonar.batch.index; import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.LinkedHashMultimap; -import com.google.common.collect.Lists; -import com.google.common.collect.SetMultimap; import org.apache.ibatis.session.SqlSession; -import org.slf4j.LoggerFactory; import org.sonar.api.database.model.MeasureMapper; import org.sonar.api.database.model.MeasureModel; import org.sonar.api.database.model.Snapshot; -import org.sonar.api.measures.CoreMetrics; import org.sonar.api.measures.Measure; import org.sonar.api.measures.RuleMeasure; import org.sonar.api.resources.Resource; import org.sonar.api.resources.ResourceUtils; +import org.sonar.api.rule.RuleKey; import org.sonar.api.rules.Rule; import org.sonar.api.rules.RuleFinder; import org.sonar.api.technicaldebt.batch.Characteristic; import org.sonar.api.utils.SonarException; +import org.sonar.batch.index.Cache.Entry; +import org.sonar.batch.scan.measure.MeasureCache; import org.sonar.core.persistence.MyBatis; -import java.util.Collection; -import java.util.List; -import java.util.Map; - -public final class MeasurePersister { +public final class MeasurePersister implements ScanPersister { private final MyBatis mybatis; - private final ResourcePersister resourcePersister; private final RuleFinder ruleFinder; - private final MemoryOptimizer memoryOptimizer; - private final SetMultimap<Resource, Measure> unsavedMeasuresByResource = LinkedHashMultimap.create(); - private boolean delayedMode = false; + private final MeasureCache measureCache; + private final SnapshotCache snapshotCache; + private final ResourceCache resourceCache; - public MeasurePersister(MyBatis mybatis, ResourcePersister resourcePersister, RuleFinder ruleFinder, MemoryOptimizer memoryOptimizer) { + public MeasurePersister(MyBatis mybatis, RuleFinder ruleFinder, + MeasureCache measureCache, SnapshotCache snapshotCache, ResourceCache resourceCache) { this.mybatis = mybatis; - this.resourcePersister = resourcePersister; this.ruleFinder = ruleFinder; - this.memoryOptimizer = memoryOptimizer; - } - - public void setDelayedMode(boolean delayedMode) { - this.delayedMode = delayedMode; + this.measureCache = measureCache; + this.snapshotCache = snapshotCache; + this.resourceCache = resourceCache; } - public Measure reloadMeasure(Measure measure) { - return memoryOptimizer.reloadMeasure(measure); - } - - public void dump() { - LoggerFactory.getLogger(getClass()).debug("{} measures to dump", unsavedMeasuresByResource.size()); + @Override + public void persist() { + SqlSession session = mybatis.openSession(); + try { + MeasureMapper mapper = session.getMapper(MeasureMapper.class); - insert(getMeasuresToSave()); - } + for (Entry<Measure> entry : measureCache.entries()) { + String effectiveKey = entry.key()[0].toString(); + Measure measure = entry.value(); + Resource resource = resourceCache.get(effectiveKey); - public void saveMeasure(Resource resource, Measure measure) { - if (shouldSaveLater(measure)) { - if (measure.getMetric().equals(CoreMetrics.TESTS) && unsavedMeasuresByResource.get(resource).contains(measure)) { - // Hack for SONAR-5212 - unsavedMeasuresByResource.remove(resource, measure); + if (shouldPersistMeasure(resource, measure)) { + Snapshot snapshot = snapshotCache.get(effectiveKey); + MeasureModel measureModel = model(measure).setSnapshotId(snapshot.getId()); + try { + mapper.insert(measureModel); + if (measureModel.getMeasureData() != null) { + mapper.insertData(measureModel.getMeasureData()); + } + } catch (Exception e) { + // SONAR-4066 + throw new SonarException(String.format("Unable to save measure for metric [%s] on component [%s]", measure.getMetricKey(), resource.getKey()), e); + } + } } - unsavedMeasuresByResource.put(resource, measure); - return; - } - MeasureModel model; - try { - model = insertOrUpdate(resource, measure); - } catch (Exception e) { - // SONAR-4066 - throw new SonarException(String.format("Unable to save measure for metric [%s] on component [%s]", measure.getMetricKey(), resource.getKey()), e); - } - if (model != null) { - memoryOptimizer.evictDataMeasure(measure, model); - } - } - private MeasureModel insertOrUpdate(Resource resource, Measure measure) { - Snapshot snapshot = resourcePersister.getSnapshotOrFail(resource); - if (measure.getId() != null) { - return update(measure, snapshot); - } - if (shouldPersistMeasure(resource, measure)) { - MeasureModel insert = insert(measure, snapshot); - measure.setId(insert.getId()); - return insert; + session.commit(); + } finally { + MyBatis.closeQuietly(session); } - return null; - } - - private boolean shouldSaveLater(Measure measure) { - return delayedMode && measure.getPersistenceMode().useMemory(); } @VisibleForTesting @@ -125,24 +100,6 @@ public final class MeasurePersister { || isNotEmpty; } - private List<MeasureModelAndDetails> getMeasuresToSave() { - List<MeasureModelAndDetails> measures = 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)) { - measures.add(new MeasureModelAndDetails(model(measure).setSnapshotId(snapshot.getId()), resource.getKey(), measure.getMetricKey())); - } - } - } - - unsavedMeasuresByResource.clear(); - return measures; - } - private MeasureModel model(Measure measure) { MeasureModel model = new MeasureModel(); // we assume that the index has updated the metric @@ -172,9 +129,9 @@ public final class MeasurePersister { if (measure instanceof RuleMeasure) { RuleMeasure ruleMeasure = (RuleMeasure) measure; model.setRulePriority(ruleMeasure.getSeverity()); - Rule rule = ruleMeasure.getRule(); - if (rule != null) { - Rule ruleWithId = ruleFinder.findByKey(rule.getRepositoryKey(), rule.getKey()); + RuleKey ruleKey = ruleMeasure.ruleKey(); + if (ruleKey != null) { + Rule ruleWithId = ruleFinder.findByKey(ruleKey); if (ruleWithId == null) { throw new SonarException("Can not save a measure with unknown rule " + ruleMeasure); } @@ -184,95 +141,4 @@ public final class MeasurePersister { return model; } - private void insert(Iterable<MeasureModelAndDetails> values) { - SqlSession session = mybatis.openSession(); - try { - MeasureMapper mapper = session.getMapper(MeasureMapper.class); - - for (MeasureModelAndDetails value : values) { - try { - mapper.insert(value.getMeasureModel()); - if (value.getMeasureModel().getMeasureData() != null) { - mapper.insertData(value.getMeasureModel().getMeasureData()); - } - } catch (Exception e) { - // SONAR-4066 - throw new SonarException(String.format("Unable to save measure for metric [%s] on component [%s]", value.getMetricKey(), value.getResourceKey()), e); - } - } - - session.commit(); - } finally { - MyBatis.closeQuietly(session); - } - } - - private MeasureModel insert(Measure measure, Snapshot snapshot) { - MeasureModel value = model(measure); - value.setSnapshotId(snapshot.getId()); - - SqlSession session = mybatis.openSession(); - try { - MeasureMapper mapper = session.getMapper(MeasureMapper.class); - - mapper.insert(value); - if (value.getMeasureData() != null) { - mapper.insertData(value.getMeasureData()); - } - - session.commit(); - } finally { - MyBatis.closeQuietly(session); - } - - return value; - } - - private MeasureModel update(Measure measure, Snapshot snapshot) { - MeasureModel value = model(measure); - value.setId(measure.getId()); - value.setSnapshotId(snapshot.getId()); - - SqlSession session = mybatis.openSession(); - try { - MeasureMapper mapper = session.getMapper(MeasureMapper.class); - - mapper.update(value); - mapper.deleteData(value); - if (value.getMeasureData() != null) { - mapper.insertData(value.getMeasureData()); - } - - session.commit(); - } finally { - MyBatis.closeQuietly(session); - } - - return value; - } - - // SONAR-4066 - private static class MeasureModelAndDetails { - private final MeasureModel measureModel; - private final String resourceKey; - private final String metricKey; - - public MeasureModelAndDetails(MeasureModel measureModel, String resourceKey, String metricKey) { - this.measureModel = measureModel; - this.resourceKey = resourceKey; - this.metricKey = metricKey; - } - - public MeasureModel getMeasureModel() { - return measureModel; - } - - public String getResourceKey() { - return resourceKey; - } - - public String getMetricKey() { - return metricKey; - } - } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/index/MemoryOptimizer.java b/sonar-batch/src/main/java/org/sonar/batch/index/MemoryOptimizer.java deleted file mode 100644 index 9daa941b823..00000000000 --- a/sonar-batch/src/main/java/org/sonar/batch/index/MemoryOptimizer.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * 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.batch.index; - -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.sonar.api.batch.events.DecoratorExecutionHandler; -import org.sonar.api.batch.events.DecoratorsPhaseHandler; -import org.sonar.api.batch.events.SensorExecutionHandler; -import org.sonar.api.database.DatabaseSession; -import org.sonar.api.database.model.MeasureData; -import org.sonar.api.database.model.MeasureModel; -import org.sonar.api.measures.Measure; -import org.sonar.api.measures.PersistenceMode; - -import java.util.List; -import java.util.Map; - -/** - * @since 2.7 - */ -public class MemoryOptimizer implements SensorExecutionHandler, DecoratorExecutionHandler, DecoratorsPhaseHandler { - - private static final Logger LOG = LoggerFactory.getLogger(MemoryOptimizer.class); - - private List<Measure> loadedMeasures = Lists.newArrayList(); - private Map<Long, Integer> dataIdByMeasureId = Maps.newHashMap(); - private DatabaseSession session; - - public MemoryOptimizer(DatabaseSession session) { - this.session = session; - } - - /** - * Remove data of a database measure from memory. - */ - public void evictDataMeasure(Measure measure, MeasureModel model) { - if (PersistenceMode.DATABASE.equals(measure.getPersistenceMode())) { - MeasureData data = model.getMeasureData(); - if (data != null && data.getId() != null) { - measure.unsetData(); - dataIdByMeasureId.put(measure.getId(), data.getId()); - } - } - } - - public Measure reloadMeasure(Measure measure) { - if (measure.getId() != null && dataIdByMeasureId.containsKey(measure.getId()) && !measure.hasData()) { - Integer dataId = dataIdByMeasureId.get(measure.getId()); - MeasureData data = session.getSingleResult(MeasureData.class, "id", dataId); - if (data == null) { - LOG.error("The MEASURE_DATA row with id {} is lost", dataId); - - } else { - if (LOG.isDebugEnabled()) { - LOG.debug("Reload the data measure: {}, id={}", measure.getMetricKey(), measure.getId()); - } - measure.setData(data.getText()); - loadedMeasures.add(measure); - } - } - return measure; - } - - public void flushMemory() { - if (LOG.isDebugEnabled() && !loadedMeasures.isEmpty()) { - LOG.debug("Flush {} data measures from memory: ", loadedMeasures.size()); - } - for (Measure measure : loadedMeasures) { - measure.unsetData(); - } - loadedMeasures.clear(); - } - - boolean isTracked(Long measureId) { - return dataIdByMeasureId.get(measureId) != null; - } - - public void onSensorExecution(SensorExecutionEvent event) { - if (event.isEnd()) { - flushMemory(); - session.commit(); - } - } - - public void onDecoratorExecution(DecoratorExecutionEvent event) { - if (event.isEnd()) { - flushMemory(); - } - } - - public void onDecoratorsPhase(DecoratorsPhaseEvent event) { - if (event.isEnd()) { - session.commit(); - } - } - -} diff --git a/sonar-batch/src/main/java/org/sonar/batch/index/PersistenceManager.java b/sonar-batch/src/main/java/org/sonar/batch/index/PersistenceManager.java index d42e1a9f2e1..d90f63d8b66 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/index/PersistenceManager.java +++ b/sonar-batch/src/main/java/org/sonar/batch/index/PersistenceManager.java @@ -22,7 +22,6 @@ package org.sonar.batch.index; import org.sonar.api.batch.Event; import org.sonar.api.database.model.Snapshot; import org.sonar.api.design.Dependency; -import org.sonar.api.measures.Measure; import org.sonar.api.resources.Project; import org.sonar.api.resources.ProjectLink; import org.sonar.api.resources.Resource; @@ -32,10 +31,6 @@ import java.util.List; public interface PersistenceManager { void clear(); - void setDelayedMode(boolean b); - - void dump(); - void saveProject(Project project, Project parent); Snapshot saveResource(Project project, Resource resource, Resource parent); @@ -44,10 +39,6 @@ public interface PersistenceManager { String getSource(Resource resource); - void saveMeasure(Resource resource, Measure measure); - - Measure reloadMeasure(Measure measure); - void saveDependency(Project project, Dependency dependency, Dependency parentDependency); void saveLink(Project project, ProjectLink link); diff --git a/sonar-batch/src/main/java/org/sonar/batch/phases/PhaseExecutor.java b/sonar-batch/src/main/java/org/sonar/batch/phases/PhaseExecutor.java index 0ba16ea66d7..90bafa7dc3e 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/phases/PhaseExecutor.java +++ b/sonar-batch/src/main/java/org/sonar/batch/phases/PhaseExecutor.java @@ -116,8 +116,6 @@ public final class PhaseExecutor { executeInitializersPhase(); - persistenceManager.setDelayedMode(true); - if (phases.isEnabled(Phases.Phase.SENSOR)) { // Index and lock the filesystem fs.index(); @@ -135,12 +133,6 @@ public final class PhaseExecutor { decoratorsExecutor.execute(); } - String saveMeasures = "Save measures"; - eventBus.fireEvent(new BatchStepEvent(saveMeasures, true)); - persistenceManager.dump(); - eventBus.fireEvent(new BatchStepEvent(saveMeasures, false)); - persistenceManager.setDelayedMode(false); - if (module.isRoot()) { jsonReport.execute(); @@ -162,6 +154,7 @@ public final class PhaseExecutor { LOGGER.debug("Execute {}", persister.getClass().getName()); persister.persist(); } + eventBus.fireEvent(new BatchStepEvent(persistersStep, false)); } diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java b/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java index e4eacf6aa8d..335884d7f87 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java @@ -21,7 +21,6 @@ package org.sonar.batch.scan; import com.google.common.annotations.VisibleForTesting; import org.sonar.api.BatchComponent; -import org.sonar.api.BatchExtension; import org.sonar.api.CoreProperties; import org.sonar.api.batch.InstantiationStrategy; import org.sonar.api.batch.bootstrap.ProjectBootstrapper; @@ -36,18 +35,40 @@ import org.sonar.batch.DefaultFileLinesContextFactory; import org.sonar.batch.DefaultResourceCreationLock; import org.sonar.batch.ProjectConfigurator; import org.sonar.batch.ProjectTree; -import org.sonar.batch.bootstrap.*; +import org.sonar.batch.bootstrap.BootstrapSettings; +import org.sonar.batch.bootstrap.ExtensionInstaller; +import org.sonar.batch.bootstrap.ExtensionMatcher; +import org.sonar.batch.bootstrap.ExtensionUtils; +import org.sonar.batch.bootstrap.MetricProvider; import org.sonar.batch.components.PeriodsDefinition; import org.sonar.batch.debt.DebtModelProvider; import org.sonar.batch.debt.IssueChangelogDebtCalculator; -import org.sonar.batch.index.*; -import org.sonar.batch.issue.*; +import org.sonar.batch.index.Caches; +import org.sonar.batch.index.ComponentDataCache; +import org.sonar.batch.index.ComponentDataPersister; +import org.sonar.batch.index.DefaultIndex; +import org.sonar.batch.index.DefaultPersistenceManager; +import org.sonar.batch.index.DefaultResourcePersister; +import org.sonar.batch.index.DependencyPersister; +import org.sonar.batch.index.EventPersister; +import org.sonar.batch.index.LinkPersister; +import org.sonar.batch.index.MeasurePersister; +import org.sonar.batch.index.ResourceCache; +import org.sonar.batch.index.ResourceKeyMigration; +import org.sonar.batch.index.SnapshotCache; +import org.sonar.batch.index.SourcePersister; +import org.sonar.batch.issue.DefaultProjectIssues; +import org.sonar.batch.issue.DeprecatedViolations; +import org.sonar.batch.issue.IssueCache; +import org.sonar.batch.issue.IssuePersister; +import org.sonar.batch.issue.ScanIssueStorage; import org.sonar.batch.phases.GraphPersister; import org.sonar.batch.profiling.PhasesSumUpTimeProfiler; import org.sonar.batch.rule.RulesProvider; import org.sonar.batch.scan.filesystem.InputFileCache; import org.sonar.batch.scan.maven.FakeMavenPluginExecutor; import org.sonar.batch.scan.maven.MavenPluginExecutor; +import org.sonar.batch.scan.measure.MeasureCache; import org.sonar.batch.source.HighlightableBuilder; import org.sonar.batch.source.SymbolizableBuilder; import org.sonar.core.component.ScanGraph; @@ -112,7 +133,6 @@ public class ProjectScanContainer extends ComponentContainer { EventPersister.class, LinkPersister.class, MeasurePersister.class, - MemoryOptimizer.class, DefaultResourcePersister.class, SourcePersister.class, DefaultNotificationManager.class, @@ -169,6 +189,9 @@ public class ProjectScanContainer extends ComponentContainer { // Differential periods PeriodsDefinition.class, + // Measures + MeasureCache.class, + ProjectSettingsReady.class); } diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/measure/MeasureCache.java b/sonar-batch/src/main/java/org/sonar/batch/scan/measure/MeasureCache.java new file mode 100644 index 00000000000..1f668614d8f --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/measure/MeasureCache.java @@ -0,0 +1,61 @@ +/* + * 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.batch.scan.measure; + +import com.google.common.base.Preconditions; +import org.sonar.api.BatchComponent; +import org.sonar.api.measures.Measure; +import org.sonar.api.resources.Resource; +import org.sonar.batch.index.Cache; +import org.sonar.batch.index.Cache.Entry; +import org.sonar.batch.index.Caches; + +/** + * Cache of all measures. This cache is shared amongst all project modules. + */ +public class MeasureCache implements BatchComponent { + + private final Cache<Measure> cache; + + public MeasureCache(Caches caches) { + cache = caches.createCache("measures"); + } + + public Iterable<Entry<Measure>> entries() { + return cache.entries(); + } + + public Iterable<Measure> byResource(Resource r) { + return cache.allValues(r.getEffectiveKey()); + } + + public MeasureCache put(Resource resource, Measure measure) { + Preconditions.checkNotNull(resource.getEffectiveKey()); + Preconditions.checkNotNull(measure.getMetricKey()); + cache.put(resource.getEffectiveKey(), measure.getMetricKey(), measure.hashCode(), measure); + return this; + } + + public Iterable<Measure> byMetric(Resource resource, String metricKey) { + Preconditions.checkNotNull(resource.getEffectiveKey()); + Preconditions.checkNotNull(metricKey); + return cache.values(resource.getEffectiveKey(), metricKey); + } +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/measure/package-info.java b/sonar-batch/src/main/java/org/sonar/batch/scan/measure/package-info.java new file mode 100644 index 00000000000..64f9c876250 --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/measure/package-info.java @@ -0,0 +1,27 @@ +/* + * 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. + */ + +/** + * This package is a part of bootstrap process, so we should take care about backward compatibility. + */ +@ParametersAreNonnullByDefault +package org.sonar.batch.scan.measure; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/sonar-batch/src/test/java/org/sonar/batch/debt/DebtDecoratorTest.java b/sonar-batch/src/test/java/org/sonar/batch/debt/DebtDecoratorTest.java index fc82ece746c..6edbbab8fbf 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/debt/DebtDecoratorTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/debt/DebtDecoratorTest.java @@ -59,7 +59,7 @@ import java.util.Collections; import static com.google.common.collect.Lists.newArrayList; import static org.fest.assertions.Assertions.assertThat; import static org.mockito.Matchers.any; -import static org.mockito.Mockito.argThat; +import static org.mockito.Matchers.argThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; @@ -204,7 +204,7 @@ public class DebtDecoratorTest { new RuleMeasure(CoreMetrics.TECHNICAL_DEBT, org.sonar.api.rules.Rule.create(ruleKey1.repository(), ruleKey1.rule()), null, null) .setValue(5d * ONE_DAY_IN_MINUTES) - )); + )); decorator.decorate(resource, context); verify(context).saveMeasure(CoreMetrics.TECHNICAL_DEBT, 7d * ONE_DAY_IN_MINUTES); @@ -225,7 +225,7 @@ public class DebtDecoratorTest { new RuleMeasure(CoreMetrics.TECHNICAL_DEBT, org.sonar.api.rules.Rule.create(ruleKey2.repository(), ruleKey2.rule()) , null, null).setValue(10d * ONE_DAY_IN_MINUTES) - )); + )); decorator.decorate(resource, context); verify(context).saveMeasure(CoreMetrics.TECHNICAL_DEBT, 15d * ONE_DAY_IN_MINUTES); @@ -319,8 +319,7 @@ public class DebtDecoratorTest { description.appendText(new StringBuilder() .append("value=").append(value).append(",") .append("characteristic=").append(characteristic.key()).append(",") - .append("metric=").append(metric.getKey()).toString()) - ; + .append("metric=").append(metric.getKey()).toString()); } } @@ -342,8 +341,7 @@ public class DebtDecoratorTest { } RuleMeasure m = (RuleMeasure) o; return ObjectUtils.equals(metric, m.getMetric()) && - ObjectUtils.equals(ruleKey.repository(), m.getRule().getRepositoryKey()) && - ObjectUtils.equals(ruleKey.rule(), m.getRule().getKey()) && + ObjectUtils.equals(ruleKey, m.ruleKey()) && ObjectUtils.equals(value, m.getValue()); } diff --git a/sonar-batch/src/test/java/org/sonar/batch/index/BucketTest.java b/sonar-batch/src/test/java/org/sonar/batch/index/BucketTest.java index 2fc521a64d5..cb20f15cde6 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/index/BucketTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/index/BucketTest.java @@ -20,12 +20,9 @@ package org.sonar.batch.index; import org.junit.Test; -import org.sonar.api.measures.Measure; -import org.sonar.api.measures.MeasuresFilters; import org.sonar.api.measures.Metric; import org.sonar.api.resources.Directory; import org.sonar.api.resources.File; -import org.sonar.api.utils.SonarException; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.core.IsNot.not; @@ -52,42 +49,6 @@ public class BucketTest { } @Test - public void shouldAddNewMeasure() { - Bucket fileBucket = new Bucket(javaFile); - Measure measure = new Measure(ncloc).setValue(1200.0); - fileBucket.addMeasure(measure); - - assertThat(fileBucket.getMeasures(MeasuresFilters.all()).size(), is(1)); - assertThat(fileBucket.getMeasures(MeasuresFilters.metric(ncloc)), is(measure)); - } - - @Test - public void shouldUpdateMeasure() { - Bucket fileBucket = new Bucket(javaFile); - Measure measure = new Measure(ncloc).setValue(1200.0); - fileBucket.addMeasure(measure); - - assertThat(fileBucket.getMeasures(MeasuresFilters.all()).size(), is(1)); - assertThat(fileBucket.getMeasures(MeasuresFilters.metric(ncloc)).getValue(), is(1200.0)); - - measure.setValue(500.0); - fileBucket.addMeasure(measure); - - assertThat(fileBucket.getMeasures(MeasuresFilters.all()).size(), is(1)); - assertThat(fileBucket.getMeasures(MeasuresFilters.metric(ncloc)).getValue(), is(500.0)); - } - - @Test(expected = SonarException.class) - public void shouldFailIfAddingSameMeasures() { - Bucket fileBucket = new Bucket(javaFile); - Measure measure = new Measure(ncloc).setValue(1200.0); - fileBucket.addMeasure(measure); - - measure = new Measure(ncloc).setValue(500.0); - fileBucket.addMeasure(measure); - } - - @Test public void shouldBeEquals() { assertEquals(new Bucket(directory), new Bucket(directory)); assertEquals(new Bucket(directory).hashCode(), new Bucket(directory).hashCode()); diff --git a/sonar-batch/src/test/java/org/sonar/batch/index/DefaultIndexTest.java b/sonar-batch/src/test/java/org/sonar/batch/index/DefaultIndexTest.java index 4abc71694cf..9a162fbf425 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/index/DefaultIndexTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/index/DefaultIndexTest.java @@ -28,7 +28,13 @@ import org.sonar.api.measures.Measure; import org.sonar.api.measures.MeasuresFilters; import org.sonar.api.measures.MetricFinder; import org.sonar.api.profiles.RulesProfile; -import org.sonar.api.resources.*; +import org.sonar.api.resources.Directory; +import org.sonar.api.resources.File; +import org.sonar.api.resources.Java; +import org.sonar.api.resources.Library; +import org.sonar.api.resources.Project; +import org.sonar.api.resources.Qualifiers; +import org.sonar.api.resources.Resource; import org.sonar.api.rules.Rule; import org.sonar.api.rules.RuleFinder; import org.sonar.api.rules.Violation; @@ -36,6 +42,7 @@ import org.sonar.api.violations.ViolationQuery; import org.sonar.batch.ProjectTree; import org.sonar.batch.issue.DeprecatedViolations; import org.sonar.batch.issue.ModuleIssues; +import org.sonar.batch.scan.measure.MeasureCache; import org.sonar.core.component.ScanGraph; import java.io.IOException; @@ -68,7 +75,8 @@ public class DefaultIndexTest { ruleFinder = mock(RuleFinder.class); ProjectTree projectTree = mock(ProjectTree.class); - index = new DefaultIndex(mock(PersistenceManager.class), projectTree, metricFinder, mock(ScanGraph.class), deprecatedViolations, mock(ResourceKeyMigration.class)); + index = new DefaultIndex(mock(PersistenceManager.class), projectTree, metricFinder, mock(ScanGraph.class), deprecatedViolations, mock(ResourceKeyMigration.class), + mock(MeasureCache.class)); java.io.File baseDir = temp.newFolder(); project = new Project("project"); diff --git a/sonar-batch/src/test/java/org/sonar/batch/index/MeasurePersisterTest.java b/sonar-batch/src/test/java/org/sonar/batch/index/MeasurePersisterTest.java index fc3a69628d2..9785c944b7a 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/index/MeasurePersisterTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/index/MeasurePersisterTest.java @@ -23,8 +23,6 @@ import org.apache.commons.lang.StringUtils; import org.junit.Before; import org.junit.Test; import org.junit.rules.ExpectedException; -import org.mockito.ArgumentCaptor; -import org.sonar.api.database.model.MeasureModel; import org.sonar.api.database.model.Snapshot; import org.sonar.api.measures.CoreMetrics; import org.sonar.api.measures.Measure; @@ -38,13 +36,13 @@ import org.sonar.api.rules.Rule; import org.sonar.api.rules.RuleFinder; import org.sonar.api.rules.RulePriority; import org.sonar.api.utils.SonarException; +import org.sonar.batch.scan.measure.MeasureCache; import org.sonar.core.persistence.AbstractDaoTestCase; +import java.util.Arrays; + import static org.fest.assertions.Assertions.assertThat; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; public class MeasurePersisterTest extends AbstractDaoTestCase { @@ -63,22 +61,28 @@ public class MeasurePersisterTest extends AbstractDaoTestCase { MeasurePersister measurePersister; RuleFinder ruleFinder = mock(RuleFinder.class); - ResourcePersister resourcePersister = mock(ResourcePersister.class); - MemoryOptimizer memoryOptimizer = mock(MemoryOptimizer.class); Project project = new Project("foo"); Directory aDirectory = new Directory("org/foo"); File aFile = new File("org/foo/Bar.java"); Snapshot projectSnapshot = snapshot(PROJECT_SNAPSHOT_ID); Snapshot packageSnapshot = snapshot(PACKAGE_SNAPSHOT_ID); + private SnapshotCache snapshotCache; + + private MeasureCache measureCache; + @Before public void mockResourcePersister() { - when(resourcePersister.getSnapshotOrFail(project)).thenReturn(projectSnapshot); - when(resourcePersister.getSnapshotOrFail(aDirectory)).thenReturn(packageSnapshot); - when(resourcePersister.getSnapshot(project)).thenReturn(projectSnapshot); - when(resourcePersister.getSnapshot(aDirectory)).thenReturn(packageSnapshot); + snapshotCache = mock(SnapshotCache.class); + measureCache = mock(MeasureCache.class); + ResourceCache resourceCache = mock(ResourceCache.class); + when(snapshotCache.get("foo")).thenReturn(projectSnapshot); + when(snapshotCache.get("foo:org/foo")).thenReturn(packageSnapshot); + when(resourceCache.get("foo")).thenReturn(project); + when(resourceCache.get("foo:org/foo/Bar.java")).thenReturn(aFile); + when(resourceCache.get("foo:org/foo")).thenReturn(aDirectory); - measurePersister = new MeasurePersister(getMyBatis(), resourcePersister, ruleFinder, memoryOptimizer); + measurePersister = new MeasurePersister(getMyBatis(), ruleFinder, measureCache, snapshotCache, resourceCache); } @Test @@ -86,11 +90,10 @@ public class MeasurePersisterTest extends AbstractDaoTestCase { setupData("empty"); Measure measure = new Measure(ncloc()).setValue(1234.0); - measurePersister.saveMeasure(project, measure); + when(measureCache.entries()).thenReturn(Arrays.asList(new Cache.Entry<Measure>(new String[] {"foo", "ncloc"}, measure))); + measurePersister.persist(); checkTables("shouldInsertMeasure", "project_measures"); - verify(memoryOptimizer).evictDataMeasure(eq(measure), any(MeasureModel.class)); - assertThat(measure.getId()).isNotNull(); } @Test @@ -98,20 +101,12 @@ public class MeasurePersisterTest extends AbstractDaoTestCase { setupData("empty"); Measure measure = new Measure(ncloc()).setValue(1234.0).setAlertText(TOO_LONG); + when(measureCache.entries()).thenReturn(Arrays.asList(new Cache.Entry<Measure>(new String[] {"foo", "ncloc"}, measure))); thrown.expect(SonarException.class); thrown.expectMessage("Unable to save measure for metric [ncloc] on component [foo]"); - measurePersister.saveMeasure(project, measure); - } - - @Test - public void should_reload_measure() { - Measure measure = new Measure(ncloc()); - - measurePersister.reloadMeasure(measure); - - verify(memoryOptimizer).reloadMeasure(measure); + measurePersister.persist(); } @Test @@ -122,10 +117,11 @@ public class MeasurePersisterTest extends AbstractDaoTestCase { when(ruleFinder.findByKey("pmd", "key")).thenReturn(rule); Measure measure = new RuleMeasure(ncloc(), rule, RulePriority.MAJOR, 1).setValue(1234.0); - measurePersister.saveMeasure(project, measure); + when(measureCache.entries()).thenReturn(Arrays.asList(new Cache.Entry<Measure>(new String[] {"foo", "ncloc"}, measure))); + + measurePersister.persist(); checkTables("shouldInsertRuleMeasure", "project_measures"); - assertThat(measure.getId()).isNotNull(); } @Test @@ -133,21 +129,21 @@ public class MeasurePersisterTest extends AbstractDaoTestCase { setupData("empty"); Measure withLargeData = new Measure(ncloc()).setData(LONG); - measurePersister.saveMeasure(project, withLargeData); + when(measureCache.entries()).thenReturn(Arrays.asList(new Cache.Entry<Measure>(new String[] {"foo", "ncloc"}, withLargeData))); - checkTables("shouldInsertMeasureWithLargeData", "project_measures", "measure_data"); + measurePersister.persist(); - ArgumentCaptor<MeasureModel> validMeasureModel = ArgumentCaptor.forClass(MeasureModel.class); - verify(memoryOptimizer).evictDataMeasure(eq(withLargeData), validMeasureModel.capture()); - assertThat(validMeasureModel.getValue().getMeasureData().getId()).isNotNull(); - assertThat(withLargeData.getId()).isNotNull(); + checkTables("shouldInsertMeasureWithLargeData", "project_measures", "measure_data"); } @Test public void should_not_save_best_values() { setupData("empty"); - measurePersister.saveMeasure(aFile, new Measure(coverage()).setValue(100.0)); + Measure measure = new Measure(coverage()).setValue(100.0); + when(measureCache.entries()).thenReturn(Arrays.asList(new Cache.Entry<Measure>(new String[] {"foo:org/foo/Bar.java", "coverage"}, measure))); + + measurePersister.persist(); assertEmptyTables("project_measures", "measure_data"); } @@ -156,7 +152,10 @@ public class MeasurePersisterTest extends AbstractDaoTestCase { public void should_not_save_memory_only_measures() { setupData("empty"); - measurePersister.saveMeasure(aFile, new Measure("ncloc").setPersistenceMode(PersistenceMode.MEMORY)); + Measure measure = new Measure("ncloc").setPersistenceMode(PersistenceMode.MEMORY); + when(measureCache.entries()).thenReturn(Arrays.asList(new Cache.Entry<Measure>(new String[] {"foo:org/foo/Bar.java", "ncloc"}, measure))); + + measurePersister.persist(); assertEmptyTables("project_measures", "measure_data"); } @@ -165,88 +164,15 @@ public class MeasurePersisterTest extends AbstractDaoTestCase { public void should_always_save_non_file_measures() { setupData("empty"); - measurePersister.saveMeasure(project, new Measure(ncloc()).setValue(200.0)); - measurePersister.saveMeasure(aDirectory, new Measure(ncloc()).setValue(300.0)); - - checkTables("shouldAlwaysPersistNonFileMeasures", "project_measures"); - } - - @Test - public void should_update_measure() { - setupData("data"); - - measurePersister.saveMeasure(project, new Measure(coverage()).setValue(12.5).setId(1L)); - measurePersister.saveMeasure(project, new Measure(coverage()).setData(SHORT).setId(2L)); - measurePersister.saveMeasure(aDirectory, new Measure(coverage()).setData(LONG).setId(3L)); - - checkTables("shouldUpdateMeasure", "project_measures", "measure_data"); - } - - @Test - public void should_add_delayed_measure_several_times() { - setupData("empty"); - - Measure measure = new Measure(ncloc()); - - measurePersister.setDelayedMode(true); - measurePersister.saveMeasure(project, measure.setValue(200.0)); - measurePersister.saveMeasure(project, measure.setValue(300.0)); - measurePersister.dump(); - - checkTables("shouldAddDelayedMeasureSeveralTimes", "project_measures"); - } - - @Test - public void should_delay_saving() { - setupData("empty"); - - measurePersister.setDelayedMode(true); - measurePersister.saveMeasure(project, new Measure(ncloc()).setValue(1234.0).setData(SHORT)); - measurePersister.saveMeasure(aDirectory, new Measure(ncloc()).setValue(50.0).setData(LONG)); - - assertEmptyTables("project_measures"); - - measurePersister.dump(); - checkTables("shouldDelaySaving", "project_measures", "measure_data"); - } - - @Test - public void should_display_contextual_info_when_error_during_delay_saving() { - setupData("empty"); - - measurePersister.setDelayedMode(true); - - measurePersister.saveMeasure(project, new Measure(ncloc()).setValue(1234.0).setData(SHORT).setAlertText(TOO_LONG)); - - thrown.expect(SonarException.class); - thrown.expectMessage("Unable to save measure for metric [ncloc] on component [foo]"); - - measurePersister.dump(); - } - - @Test - public void should_not_delay_saving_with_database_only_measure() { - setupData("empty"); - - measurePersister.setDelayedMode(true); - measurePersister.saveMeasure(project, new Measure(ncloc()).setValue(1234.0).setPersistenceMode(PersistenceMode.DATABASE)); - measurePersister.saveMeasure(aDirectory, new Measure(ncloc()).setValue(50.0)); + Measure measure1 = new Measure(ncloc()).setValue(200.0); + Measure measure2 = new Measure(ncloc()).setValue(300.0); + when(measureCache.entries()).thenReturn(Arrays.asList( + new Cache.Entry<Measure>(new String[] {"foo", "ncloc"}, measure1), + new Cache.Entry<Measure>(new String[] {"foo:org/foo", "ncloc"}, measure2))); - checkTables("shouldInsertMeasure", "project_measures"); - } + measurePersister.persist(); - @Test - public void should_not_save_best_value_measures_in_delayed_mode() { - setupData("empty"); - - measurePersister.setDelayedMode(true); - measurePersister.saveMeasure(aFile, new Measure(coverage()).setValue(100.0)); - - assertEmptyTables("project_measures", "measure_data"); - - measurePersister.dump(); - - assertEmptyTables("project_measures", "measure_data"); + checkTables("shouldAlwaysPersistNonFileMeasures", "project_measures"); } @Test diff --git a/sonar-batch/src/test/java/org/sonar/batch/index/MemoryOptimizerTest.java b/sonar-batch/src/test/java/org/sonar/batch/index/MemoryOptimizerTest.java deleted file mode 100644 index 468654da72c..00000000000 --- a/sonar-batch/src/test/java/org/sonar/batch/index/MemoryOptimizerTest.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * 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.batch.index; - -import org.junit.Test; -import org.sonar.api.database.model.MeasureData; -import org.sonar.api.database.model.MeasureModel; -import org.sonar.api.measures.CoreMetrics; -import org.sonar.api.measures.Measure; -import org.sonar.api.measures.PersistenceMode; -import org.sonar.jpa.test.AbstractDbUnitTestCase; - -import static org.hamcrest.Matchers.greaterThan; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.core.IsNull.nullValue; -import static org.junit.Assert.assertThat; - -public class MemoryOptimizerTest extends AbstractDbUnitTestCase { - - @Test - public void shouldEvictDatabaseOnlyMeasure() { - MemoryOptimizer optimizer = new MemoryOptimizer(getSession()); - Measure measure = new Measure(CoreMetrics.CONDITIONS_BY_LINE) - .setData("10=23") - .setPersistenceMode(PersistenceMode.DATABASE) - .setId(12345L); - MeasureModel model = newPersistedModel(); - - optimizer.evictDataMeasure(measure, model); - - assertThat(optimizer.isTracked(12345L),is(true)); - assertThat(measure.getData(), nullValue());// data has been removed from memory - } - - @Test - public void shouldNotEvictStandardMeasure() { - MemoryOptimizer optimizer = new MemoryOptimizer(getSession()); - Measure measure = new Measure(CoreMetrics.PROFILE) - .setData("Sonar way") - .setId(12345L); - MeasureModel model = newPersistedModel(); - - optimizer.evictDataMeasure(measure, model); - - assertThat(optimizer.isTracked(12345L),is(false)); - assertThat(measure.getData(), is("Sonar way")); - } - - @Test - public void shouldReloadEvictedMeasure() { - setupData("shouldReloadEvictedMeasure"); - MemoryOptimizer optimizer = new MemoryOptimizer(getSession()); - Measure measure = new Measure(CoreMetrics.CONDITIONS_BY_LINE) - .setData("initial") - .setPersistenceMode(PersistenceMode.DATABASE) - .setId(12345L); - - optimizer.evictDataMeasure(measure, newPersistedModel()); - assertThat(measure.getData(), nullValue()); - - optimizer.reloadMeasure(measure); - - assertThat(measure.getData().length(), greaterThan(5)); - - optimizer.flushMemory(); - assertThat(measure.getData(), nullValue()); - } - - private MeasureModel newPersistedModel() { - MeasureModel model = new MeasureModel(); - model.setId(12345L); - MeasureData measureData = new MeasureData(); - measureData.setId(500); - model.setMeasureData(measureData); - return model; - } -} diff --git a/sonar-batch/src/test/java/org/sonar/batch/scan/measure/MeasureCacheTest.java b/sonar-batch/src/test/java/org/sonar/batch/scan/measure/MeasureCacheTest.java new file mode 100644 index 00000000000..a1791ff676d --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/scan/measure/MeasureCacheTest.java @@ -0,0 +1,83 @@ +/* + * 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.batch.scan.measure; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.measures.Measure; +import org.sonar.api.measures.RuleMeasure; +import org.sonar.api.resources.Project; +import org.sonar.api.rules.RulePriority; +import org.sonar.batch.index.Caches; +import org.sonar.batch.index.CachesTest; + +import static org.fest.assertions.Assertions.assertThat; + +public class MeasureCacheTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + Caches caches; + + @Before + public void start() throws Exception { + caches = CachesTest.createCacheOnTemp(temp); + caches.start(); + } + + @After + public void stop() { + caches.stop(); + } + + @Test + public void should_add_measure() throws Exception { + MeasureCache cache = new MeasureCache(caches); + Project p = new Project("struts"); + + assertThat(cache.entries()).hasSize(0); + + assertThat(cache.byResource(p)).hasSize(0); + assertThat(cache.byMetric(p, "ncloc")).hasSize(0); + + Measure m = new Measure(CoreMetrics.NCLOC, 1.0); + cache.put(p, m); + + assertThat(cache.entries()).hasSize(1); + + assertThat(cache.byMetric(p, "ncloc")).hasSize(1); + assertThat(cache.byMetric(p, "ncloc").iterator().next()).isEqualTo(m); + assertThat(cache.byResource(p)).hasSize(1); + assertThat(cache.byResource(p).iterator().next()).isEqualTo(m); + + Measure mRule = RuleMeasure.createForPriority(CoreMetrics.CRITICAL_VIOLATIONS, RulePriority.BLOCKER, 1.0); + cache.put(p, mRule); + + assertThat(cache.entries()).hasSize(2); + + assertThat(cache.byResource(p)).hasSize(2); + assertThat(cache.byMetric(p, "ncloc")).hasSize(1); + } +} |