001    /*
002     * Sonar, open source software quality management tool.
003     * Copyright (C) 2008-2011 SonarSource
004     * mailto:contact AT sonarsource DOT com
005     *
006     * Sonar is free software; you can redistribute it and/or
007     * modify it under the terms of the GNU Lesser General Public
008     * License as published by the Free Software Foundation; either
009     * version 3 of the License, or (at your option) any later version.
010     *
011     * Sonar is distributed in the hope that it will be useful,
012     * but WITHOUT ANY WARRANTY; without even the implied warranty of
013     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014     * Lesser General Public License for more details.
015     *
016     * You should have received a copy of the GNU Lesser General Public
017     * License along with Sonar; if not, write to the Free Software
018     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
019     */
020    package org.sonar.batch.index;
021    
022    import com.google.common.collect.LinkedHashMultimap;
023    import com.google.common.collect.SetMultimap;
024    import org.apache.commons.lang.math.NumberUtils;
025    import org.slf4j.LoggerFactory;
026    import org.sonar.api.database.DatabaseSession;
027    import org.sonar.api.database.model.MeasureModel;
028    import org.sonar.api.database.model.Snapshot;
029    import org.sonar.api.measures.Measure;
030    import org.sonar.api.measures.Metric;
031    import org.sonar.api.measures.RuleMeasure;
032    import org.sonar.api.resources.Resource;
033    import org.sonar.api.resources.ResourceUtils;
034    import org.sonar.api.rules.Rule;
035    import org.sonar.api.rules.RuleFinder;
036    import org.sonar.api.utils.SonarException;
037    
038    import java.util.Collection;
039    import java.util.Map;
040    
041    public final class MeasurePersister {
042    
043      private boolean delayedMode = false;
044      private SetMultimap<Resource, Measure> unsavedMeasuresByResource = LinkedHashMultimap.create();
045      private DatabaseSession session;
046      private ResourcePersister resourcePersister;
047      private RuleFinder ruleFinder;
048      private MemoryOptimizer memoryOptimizer;
049    
050    
051      public MeasurePersister(DatabaseSession session, ResourcePersister resourcePersister, RuleFinder ruleFinder, MemoryOptimizer memoryOptimizer) {
052        this.session = session;
053        this.resourcePersister = resourcePersister;
054        this.ruleFinder = ruleFinder;
055        this.memoryOptimizer = memoryOptimizer;
056      }
057    
058      public void setDelayedMode(boolean delayedMode) {
059        this.delayedMode = delayedMode;
060      }
061    
062      public void saveMeasure(Resource resource, Measure measure) {
063        boolean saveLater = (measure.getPersistenceMode().useMemory() && delayedMode);
064        if (saveLater) {
065          unsavedMeasuresByResource.put(resource, measure);
066    
067        } else {
068          Snapshot snapshot = resourcePersister.getSnapshotOrFail(resource);
069          MeasureModel model = null;
070          if (measure.getId() != null) {
071            // update
072            model = session.reattach(MeasureModel.class, measure.getId());
073            model = mergeModel(measure, model);
074            model.save(session);
075    
076          } else if (shouldPersistMeasure(resource, measure)) {
077            // insert
078            model = createModel(measure);
079            model.setSnapshotId(snapshot.getId());
080            model.save(session);
081            measure.setId(model.getId()); // could be removed
082          }
083          if (model != null) {
084            memoryOptimizer.evictDataMeasure(measure, model);
085          }
086        }
087      }
088    
089      public Measure reloadMeasure(Measure measure) {
090        return memoryOptimizer.reloadMeasure(measure);
091      }
092    
093      static boolean shouldPersistMeasure(Resource resource, Measure measure) {
094        Metric metric = measure.getMetric();
095        return measure.getPersistenceMode().useDatabase() &&
096            !(ResourceUtils.isEntity(resource) && isBestValueMeasure(measure, metric));
097      }
098    
099      static boolean isBestValueMeasure(Measure measure, Metric metric) {
100        return measure.getId() == null &&
101            metric.isOptimizedBestValue() == Boolean.TRUE &&
102            metric.getBestValue() != null &&
103            (measure.getValue() == null || NumberUtils.compare(metric.getBestValue(), measure.getValue()) == 0) &&
104            measure.getAlertStatus() == null &&
105            measure.getDescription() == null &&
106            measure.getTendency() == null &&
107            measure.getUrl() == null &&
108            !measure.hasData() &&
109            (measure.getVariation1() == null || NumberUtils.compare(measure.getVariation1().doubleValue(), 0.0) == 0) &&
110            (measure.getVariation2() == null || NumberUtils.compare(measure.getVariation2().doubleValue(), 0.0) == 0) &&
111            (measure.getVariation3() == null || NumberUtils.compare(measure.getVariation3().doubleValue(), 0.0) == 0) &&
112            (measure.getVariation4() == null || NumberUtils.compare(measure.getVariation4().doubleValue(), 0.0) == 0) &&
113            (measure.getVariation5() == null || NumberUtils.compare(measure.getVariation5().doubleValue(), 0.0) == 0);
114      }
115    
116      public void dump() {
117        LoggerFactory.getLogger(getClass()).debug("{} measures to dump", unsavedMeasuresByResource.size());
118        Map<Resource, Collection<Measure>> map = unsavedMeasuresByResource.asMap();
119        for (Map.Entry<Resource, Collection<Measure>> entry : map.entrySet()) {
120          Resource resource = entry.getKey();
121          Snapshot snapshot = resourcePersister.getSnapshot(entry.getKey());
122          for (Measure measure : entry.getValue()) {
123            if (shouldPersistMeasure(resource, measure)) {
124              MeasureModel model = createModel(measure);
125              model.setSnapshotId(snapshot.getId());
126              model.save(session);
127            }
128          }
129        }
130    
131        session.commit();
132        unsavedMeasuresByResource.clear();
133      }
134    
135      MeasureModel createModel(Measure measure) {
136        return mergeModel(measure, new MeasureModel());
137      }
138    
139      MeasureModel mergeModel(Measure measure, MeasureModel merge) {
140        merge.setMetricId(measure.getMetric().getId());// we assume that the index has updated the metric
141        merge.setDescription(measure.getDescription());
142        merge.setData(measure.getData());
143        merge.setAlertStatus(measure.getAlertStatus());
144        merge.setAlertText(measure.getAlertText());
145        merge.setTendency(measure.getTendency());
146        merge.setVariationValue1(measure.getVariation1());
147        merge.setVariationValue2(measure.getVariation2());
148        merge.setVariationValue3(measure.getVariation3());
149        merge.setVariationValue4(measure.getVariation4());
150        merge.setVariationValue5(measure.getVariation5());
151        merge.setUrl(measure.getUrl());
152        merge.setCharacteristic(measure.getCharacteristic());
153        if (measure.getValue() != null) {
154          merge.setValue(measure.getValue().doubleValue());
155        } else {
156          merge.setValue(null);
157        }
158        if (measure instanceof RuleMeasure) {
159          RuleMeasure ruleMeasure = (RuleMeasure) measure;
160          merge.setRulePriority(ruleMeasure.getRulePriority());
161          if (ruleMeasure.getRule() != null) {
162            Rule ruleWithId = ruleFinder.findByKey(ruleMeasure.getRule().getRepositoryKey(), ruleMeasure.getRule().getKey());
163            if (ruleWithId != null) {
164              merge.setRuleId(ruleWithId.getId());
165            } else {
166              throw new SonarException("Can not save a measure with unknown rule " + ruleMeasure);
167            }
168          }
169        }
170        return merge;
171      }
172    }