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     */
020    package org.sonar.batch.index;
021    
022    import com.google.common.annotations.VisibleForTesting;
023    import com.google.common.collect.LinkedHashMultimap;
024    import com.google.common.collect.Lists;
025    import com.google.common.collect.SetMultimap;
026    import org.apache.ibatis.session.SqlSession;
027    import org.slf4j.LoggerFactory;
028    import org.sonar.api.database.model.MeasureMapper;
029    import org.sonar.api.database.model.MeasureModel;
030    import org.sonar.api.database.model.Snapshot;
031    import org.sonar.api.measures.Measure;
032    import org.sonar.api.measures.RuleMeasure;
033    import org.sonar.api.resources.Resource;
034    import org.sonar.api.resources.ResourceUtils;
035    import org.sonar.api.rules.Rule;
036    import org.sonar.api.rules.RuleFinder;
037    import org.sonar.api.utils.SonarException;
038    import org.sonar.core.persistence.MyBatis;
039    
040    import java.util.Collection;
041    import java.util.List;
042    import java.util.Map;
043    
044    public final class MeasurePersister {
045      private final MyBatis mybatis;
046      private final ResourcePersister resourcePersister;
047      private final RuleFinder ruleFinder;
048      private final MemoryOptimizer memoryOptimizer;
049      private final SetMultimap<Resource, Measure> unsavedMeasuresByResource = LinkedHashMultimap.create();
050      private boolean delayedMode = false;
051    
052      public MeasurePersister(MyBatis mybatis, ResourcePersister resourcePersister, RuleFinder ruleFinder, MemoryOptimizer memoryOptimizer) {
053        this.mybatis = mybatis;
054        this.resourcePersister = resourcePersister;
055        this.ruleFinder = ruleFinder;
056        this.memoryOptimizer = memoryOptimizer;
057      }
058    
059      public void setDelayedMode(boolean delayedMode) {
060        this.delayedMode = delayedMode;
061      }
062    
063      public Measure reloadMeasure(Measure measure) {
064        return memoryOptimizer.reloadMeasure(measure);
065      }
066    
067      public void dump() {
068        LoggerFactory.getLogger(getClass()).debug("{} measures to dump", unsavedMeasuresByResource.size());
069    
070        insert(getMeasuresToSave());
071      }
072    
073      public void saveMeasure(Resource resource, Measure measure) {
074        if (shouldSaveLater(measure)) {
075          unsavedMeasuresByResource.put(resource, measure);
076          return;
077        }
078    
079        MeasureModel model = insertOrUpdate(resource, measure);
080        if (model != null) {
081          memoryOptimizer.evictDataMeasure(measure, model);
082        }
083      }
084    
085      private MeasureModel insertOrUpdate(Resource resource, Measure measure) {
086        Snapshot snapshot = resourcePersister.getSnapshotOrFail(resource);
087        if (measure.getId() != null) {
088          return update(measure, snapshot);
089        }
090        if (shouldPersistMeasure(resource, measure)) {
091          MeasureModel insert = insert(measure, snapshot);
092          measure.setId(insert.getId());
093          return insert;
094        }
095        return null;
096      }
097    
098      private boolean shouldSaveLater(Measure measure) {
099        return delayedMode && measure.getPersistenceMode().useMemory();
100      }
101    
102      @VisibleForTesting
103      static boolean shouldPersistMeasure(Resource resource, Measure measure) {
104        return measure.getPersistenceMode().useDatabase() &&
105          !(ResourceUtils.isEntity(resource) && measure.isBestValue());
106      }
107    
108      private List<MeasureModel> getMeasuresToSave() {
109        List<MeasureModel> measures = Lists.newArrayList();
110    
111        Map<Resource, Collection<Measure>> map = unsavedMeasuresByResource.asMap();
112        for (Map.Entry<Resource, Collection<Measure>> entry : map.entrySet()) {
113          Resource resource = entry.getKey();
114          Snapshot snapshot = resourcePersister.getSnapshot(entry.getKey());
115          for (Measure measure : entry.getValue()) {
116            if (shouldPersistMeasure(resource, measure)) {
117              measures.add(model(measure).setSnapshotId(snapshot.getId()));
118            }
119          }
120        }
121    
122        unsavedMeasuresByResource.clear();
123        return measures;
124      }
125    
126      private MeasureModel model(Measure measure) {
127        MeasureModel model = new MeasureModel();
128        model.setMetricId(measure.getMetric().getId()); // we assume that the index has updated the metric
129        model.setDescription(measure.getDescription());
130        model.setData(measure.getData());
131        model.setAlertStatus(measure.getAlertStatus());
132        model.setAlertText(measure.getAlertText());
133        model.setTendency(measure.getTendency());
134        model.setVariationValue1(measure.getVariation1());
135        model.setVariationValue2(measure.getVariation2());
136        model.setVariationValue3(measure.getVariation3());
137        model.setVariationValue4(measure.getVariation4());
138        model.setVariationValue5(measure.getVariation5());
139        model.setUrl(measure.getUrl());
140        model.setCharacteristic(measure.getCharacteristic());
141        model.setPersonId(measure.getPersonId());
142        if (measure.getValue() != null) {
143          model.setValue(measure.getValue().doubleValue());
144        } else {
145          model.setValue(null);
146        }
147        if (measure instanceof RuleMeasure) {
148          RuleMeasure ruleMeasure = (RuleMeasure) measure;
149          model.setRulePriority(ruleMeasure.getSeverity());
150          if (ruleMeasure.getRule() != null) {
151            Rule ruleWithId = ruleFinder.findByKey(ruleMeasure.getRule().getRepositoryKey(), ruleMeasure.getRule().getKey());
152            if (ruleWithId == null) {
153              throw new SonarException("Can not save a measure with unknown rule " + ruleMeasure);
154            }
155            model.setRuleId(ruleWithId.getId());
156          }
157        }
158        return model;
159      }
160    
161      private void insert(Iterable<MeasureModel> values) {
162        SqlSession session = mybatis.openSession();
163        try {
164          MeasureMapper mapper = session.getMapper(MeasureMapper.class);
165    
166          for (MeasureModel value : values) {
167            mapper.insert(value);
168            if (value.getMeasureData() != null) {
169              mapper.insertData(value.getMeasureData());
170            }
171          }
172    
173          session.commit();
174        } finally {
175          MyBatis.closeQuietly(session);
176        }
177      }
178    
179      private MeasureModel insert(Measure measure, Snapshot snapshot) {
180        MeasureModel value = model(measure);
181        value.setSnapshotId(snapshot.getId());
182    
183        SqlSession session = mybatis.openSession();
184        try {
185          MeasureMapper mapper = session.getMapper(MeasureMapper.class);
186    
187          mapper.insert(value);
188          if (value.getMeasureData() != null) {
189            mapper.insertData(value.getMeasureData());
190          }
191    
192          session.commit();
193        } finally {
194          MyBatis.closeQuietly(session);
195        }
196    
197        return value;
198      }
199    
200      private MeasureModel update(Measure measure, Snapshot snapshot) {
201        MeasureModel value = model(measure);
202        value.setId(measure.getId());
203        value.setSnapshotId(snapshot.getId());
204    
205        SqlSession session = mybatis.openSession();
206        try {
207          MeasureMapper mapper = session.getMapper(MeasureMapper.class);
208    
209          mapper.update(value);
210          mapper.deleteData(value);
211          if (value.getMeasureData() != null) {
212            mapper.insertData(value.getMeasureData());
213          }
214    
215          session.commit();
216        } finally {
217          MyBatis.closeQuietly(session);
218        }
219    
220        return value;
221      }
222    }