001/*
002 * Sonar, open source software quality management tool.
003 * Copyright (C) 2008-2012 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 */
020package org.sonar.batch.index;
021
022import com.google.common.collect.LinkedHashMultimap;
023import com.google.common.collect.SetMultimap;
024import org.apache.commons.lang.math.NumberUtils;
025import org.slf4j.LoggerFactory;
026import org.sonar.api.database.DatabaseSession;
027import org.sonar.api.database.model.MeasureModel;
028import org.sonar.api.database.model.Snapshot;
029import org.sonar.api.measures.Measure;
030import org.sonar.api.measures.Metric;
031import org.sonar.api.measures.RuleMeasure;
032import org.sonar.api.resources.Resource;
033import org.sonar.api.resources.ResourceUtils;
034import org.sonar.api.rules.Rule;
035import org.sonar.api.rules.RuleFinder;
036import org.sonar.api.utils.SonarException;
037
038import java.util.Collection;
039import java.util.Map;
040
041public 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    merge.setPersonId(measure.getPersonId());
154    if (measure.getValue() != null) {
155      merge.setValue(measure.getValue().doubleValue());
156    } else {
157      merge.setValue(null);
158    }
159    if (measure instanceof RuleMeasure) {
160      RuleMeasure ruleMeasure = (RuleMeasure) measure;
161      merge.setRulePriority(ruleMeasure.getSeverity());
162      if (ruleMeasure.getRule() != null) {
163        Rule ruleWithId = ruleFinder.findByKey(ruleMeasure.getRule().getRepositoryKey(), ruleMeasure.getRule().getKey());
164        if (ruleWithId != null) {
165          merge.setRuleId(ruleWithId.getId());
166        } else {
167          throw new SonarException("Can not save a measure with unknown rule " + ruleMeasure);
168        }
169      }
170    }
171    return merge;
172  }
173}