001    /*
002     * Sonar, open source software quality management tool.
003     * Copyright (C) 2009 SonarSource SA
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.indexer;
021    
022    import com.google.common.collect.Lists;
023    import com.google.common.collect.Maps;
024    import com.google.common.collect.Sets;
025    import org.slf4j.Logger;
026    import org.slf4j.LoggerFactory;
027    import org.sonar.api.batch.Event;
028    import org.sonar.api.batch.SonarIndex;
029    import org.sonar.api.database.DatabaseSession;
030    import org.sonar.api.database.daos.MeasuresDao;
031    import org.sonar.api.database.model.MeasureModel;
032    import org.sonar.api.database.model.ResourceModel;
033    import org.sonar.api.database.model.Snapshot;
034    import org.sonar.api.database.model.SnapshotSource;
035    import org.sonar.api.design.Dependency;
036    import org.sonar.api.design.DependencyDto;
037    import org.sonar.api.measures.Measure;
038    import org.sonar.api.measures.MeasuresFilter;
039    import org.sonar.api.measures.MeasuresFilters;
040    import org.sonar.api.measures.Metric;
041    import org.sonar.api.resources.Project;
042    import org.sonar.api.resources.ProjectLink;
043    import org.sonar.api.resources.Resource;
044    import org.sonar.api.resources.ResourceUtils;
045    import org.sonar.api.rules.Violation;
046    import org.sonar.batch.ProjectTree;
047    import org.sonar.batch.ResourceFilters;
048    import org.sonar.batch.ViolationFilters;
049    import org.sonar.batch.ViolationsDao;
050    
051    import java.util.*;
052    
053    public class DefaultSonarIndex extends SonarIndex {
054    
055      private static final Logger LOG = LoggerFactory.getLogger(DefaultSonarIndex.class);
056    
057      private DatabaseSession session;
058      private ResourcePersisters resourcePersisters;
059      private Bucket<Project> rootProjectBucket;
060      private Bucket<Project> selectedProjectBucket;
061    
062      private ViolationFilters violationFilters;
063      private ResourceFilters resourceFilters;
064    
065      // data
066      private Map<Resource, Bucket> buckets = Maps.newHashMap();
067      private Set<Dependency> dependencies = Sets.newHashSet();
068      private Map<Resource, Map<Resource, Dependency>> outgoingDependenciesByResource = new HashMap<Resource, Map<Resource, Dependency>>();
069      private Map<Resource, Map<Resource, Dependency>> incomingDependenciesByResource = new HashMap<Resource, Map<Resource, Dependency>>();
070    
071      // dao
072      private ViolationsDao violationsDao;
073      private MeasuresDao measuresDao;
074      private ProjectTree projectTree;
075    
076      public DefaultSonarIndex(DatabaseSession session, ProjectTree projectTree) {
077        this.session = session;
078        this.projectTree = projectTree;
079        this.resourcePersisters = new ResourcePersisters(session);
080      }
081    
082      public void start() {
083        Project rootProject = projectTree.getRootProject();
084    
085        this.rootProjectBucket = new Bucket<Project>(rootProject);
086        persist(rootProjectBucket);
087        this.buckets.put(rootProject, rootProjectBucket);
088        this.selectedProjectBucket = rootProjectBucket;
089    
090        for (Project project : rootProject.getModules()) {
091          addProject(project);
092        }
093        session.commit();
094      }
095    
096      private void addProject(Project project) {
097        addResource(project);
098        for (Project module : project.getModules()) {
099          addProject(module);
100        }
101      }
102    
103    
104      public void selectProject(Project project, ResourceFilters resourceFilters, ViolationFilters violationFilters, MeasuresDao measuresDao, ViolationsDao violationsDao) {
105        this.selectedProjectBucket = buckets.get(project);
106        this.resourceFilters = resourceFilters;
107        this.violationFilters = violationFilters;
108        this.violationsDao = violationsDao;
109        this.measuresDao = measuresDao;
110      }
111    
112      /**
113       * Keep only project stuff
114       */
115      public void clear() {
116        Iterator<Map.Entry<Resource, Bucket>> it = buckets.entrySet().iterator();
117        while (it.hasNext()) {
118          Map.Entry<Resource, Bucket> entry = it.next();
119          Resource resource = entry.getKey();
120          if (!ResourceUtils.isSet(resource)) {
121            entry.getValue().clear();
122            it.remove();
123          }
124        }
125    
126        Set<Dependency> projectDependencies = getDependenciesBetweenProjects();
127        dependencies.clear();
128        incomingDependenciesByResource.clear();
129        outgoingDependenciesByResource.clear();
130        for (Dependency projectDependency : projectDependencies) {
131          registerDependency(projectDependency);
132        }
133      }
134    
135      /* ------------ RESOURCES */
136      public Project getRootProject() {
137        return rootProjectBucket.getResource();
138      }
139    
140      public Project getProject() {
141        return selectedProjectBucket.getResource();
142      }
143    
144      public Bucket getBucket(Resource resource) {
145        return buckets.get(resource);
146      }
147    
148      public Resource getResource(Resource resource) {
149        Bucket bucket = buckets.get(resource);
150        if (bucket != null) {
151          return bucket.getResource();
152        }
153        return null;
154      }
155    
156      public List<Resource> getChildren(Resource resource) {
157        List<Resource> children = Lists.newArrayList();
158        Bucket<?> bucket = buckets.get(resource);
159        if (bucket != null) {
160          for (Bucket childBucket : bucket.getChildren()) {
161            children.add(childBucket.getResource());
162          }
163        }
164        return children;
165      }
166    
167      public Resource addResource(Resource resource) {
168        return getOrCreateBucket(resource).getResource();
169      }
170    
171      private Bucket<Resource> getOrCreateBucket(Resource resource) {
172        Bucket bucket = buckets.get(resource);
173        if (bucket != null) {
174          return bucket;
175        }
176    
177        prepareResource(resource);
178        bucket = new Bucket<Resource>(resource);
179        buckets.put(resource, bucket);
180    
181        Bucket parentBucket = null;
182        Resource parent = resource.getParent();
183        if (parent != null) {
184          parentBucket = getOrCreateBucket(parent);
185        } else if (!ResourceUtils.isLibrary(resource)) {
186          parentBucket = selectedProjectBucket;
187        }
188        bucket.setParent(parentBucket);
189        bucket.setProject(selectedProjectBucket);
190    
191        persist(bucket);
192        return bucket;
193      }
194    
195      private void prepareResource(Resource resource) {
196        resource.setExcluded(resourceFilters != null && resourceFilters.isExcluded(resource));
197      }
198    
199      private void persist(Bucket bucket) {
200        resourcePersisters.get(bucket).persist(bucket);
201      }
202    
203      /* ------------ MEASURES */
204      public Measure getMeasure(Resource resource, Metric metric) {
205        return getOrCreateBucket(resource).getMeasures(MeasuresFilters.metric(metric));
206      }
207    
208      public <M> M getMeasures(Resource resource, MeasuresFilter<M> filter) {
209        return getOrCreateBucket(resource).getMeasures(filter);
210      }
211    
212    
213      /* ------------ SOURCE CODE */
214    
215      public void setSource(Resource resource, String source) {
216        Bucket bucket = getOrCreateBucket(resource);
217    
218        if (!bucket.isExcluded()) {
219          if (bucket.isSourceSaved()) {
220            LOG.warn("Trying to save twice the source of " + resource);
221    
222          } else {
223            session.save(new SnapshotSource(bucket.getSnapshotId(), source));
224            bucket.setSourceSaved(true);
225          }
226        }
227      }
228    
229    
230      /* ------------ RULE VIOLATIONS */
231    
232      public void addViolation(Violation violation) {
233        Bucket bucket;
234        Resource resource = violation.getResource();
235        if (resource == null) {
236          bucket = selectedProjectBucket;
237        } else {
238          bucket = getOrCreateBucket(resource);
239        }
240        if (!bucket.isExcluded()) {
241          persistViolation(violation, bucket.getSnapshot());
242        }
243      }
244    
245      private void persistViolation(Violation violation, Snapshot snapshot) {
246        boolean isIgnored = violationFilters != null && violationFilters.isIgnored(violation);
247        if (!isIgnored) {
248          violationsDao.saveViolation(snapshot, violation);
249        }
250      }
251    
252    
253      /* ------------ MEASURES */
254      public Measure saveMeasure(Resource resource, Measure measure) {
255        if (measure.getId() == null) {
256          return addMeasure(resource, measure);
257        }
258        return updateMeasure(measure);
259      }
260    
261      public Measure addMeasure(Resource resource, Measure measure) {
262        Bucket bucket = getOrCreateBucket(resource);
263        if (!bucket.isExcluded()) {
264          if (shouldPersistMeasure(resource, measure)) {
265            persistMeasure(bucket, measure);
266          }
267    
268          if (measure.getPersistenceMode().useMemory()) {
269            bucket.addMeasure(measure);
270          }
271        }
272        return measure;
273      }
274    
275      public Measure updateMeasure(Measure measure) {
276        if (measure.getId() == null) {
277          throw new IllegalStateException("Measure can not be updated because it has never been saved");
278        }
279    
280        MeasureModel model = session.reattach(MeasureModel.class, measure.getId());
281        model = MeasureModel.build(measure, model);
282        model.setMetric(measuresDao.getMetric(measure.getMetric()));
283        model.save(session);
284        return measure;
285      }
286    
287      private void persistMeasure(Bucket bucket, Measure measure) {
288        Metric metric = measuresDao.getMetric(measure.getMetric());
289        MeasureModel measureModel = MeasureModel.build(measure);
290        measureModel.setMetric(metric); // hibernate synchronized metric
291        measureModel.setSnapshotId(bucket.getSnapshotId());
292        measureModel.save(session);
293    
294        // the id is saved for future updates
295        measure.setId(measureModel.getId());
296      }
297    
298      private boolean shouldPersistMeasure(Resource resource, Measure measure) {
299        Metric metric = measure.getMetric();
300        return measure.getPersistenceMode().useDatabase() && !(
301            ResourceUtils.isEntity(resource) &&
302                metric.isOptimizedBestValue() == Boolean.TRUE &&
303                metric.getBestValue() != null &&
304                metric.getBestValue().equals(measure.getValue()) &&
305                !measure.hasOptionalData());
306      }
307    
308    
309      /* --------------- DEPENDENCIES */
310      public Dependency saveDependency(Dependency dependency) {
311        Dependency persistedDep = getEdge(dependency.getFrom(), dependency.getTo());
312        if (persistedDep != null) {
313          return persistedDep;
314        }
315        Bucket from = getOrCreateBucket(dependency.getFrom());
316        Bucket to = getOrCreateBucket(dependency.getTo());
317    
318        DependencyDto dto = new DependencyDto();
319        dto.setFromResourceId(from.getResourceId());
320        dto.setFromScope(from.getResource().getScope());
321        dto.setFromSnapshotId(from.getSnapshotId());
322        dto.setToResourceId(to.getResourceId());
323        dto.setToSnapshotId(to.getSnapshotId());
324        dto.setToScope(to.getResource().getScope());
325        dto.setProjectSnapshotId(selectedProjectBucket.getSnapshotId());
326        dto.setUsage(dependency.getUsage());
327        dto.setWeight(dependency.getWeight());
328    
329        Dependency parentDependency = dependency.getParent();
330        if (parentDependency != null) {
331          saveDependency(parentDependency);
332          dto.setParentDependencyId(parentDependency.getId());
333        }
334        session.save(dto);
335        dependency.setId(dto.getId());
336        registerDependency(dependency);
337    
338        return dependency;
339      }
340    
341      protected void registerDependency(Dependency dependency) {
342        dependencies.add(dependency);
343        registerOutgoingDependency(dependency);
344        registerIncomingDependency(dependency);
345      }
346    
347      private void registerOutgoingDependency(Dependency dependency) {
348        Map<Resource, Dependency> outgoingDeps = outgoingDependenciesByResource.get(dependency.getFrom());
349        if (outgoingDeps == null) {
350          outgoingDeps = new HashMap<Resource, Dependency>();
351          outgoingDependenciesByResource.put(dependency.getFrom(), outgoingDeps);
352        }
353        outgoingDeps.put(dependency.getTo(), dependency);
354      }
355    
356      private void registerIncomingDependency(Dependency dependency) {
357        Map<Resource, Dependency> incomingDeps = incomingDependenciesByResource.get(dependency.getTo());
358        if (incomingDeps == null) {
359          incomingDeps = new HashMap<Resource, Dependency>();
360          incomingDependenciesByResource.put(dependency.getTo(), incomingDeps);
361        }
362        incomingDeps.put(dependency.getFrom(), dependency);
363      }
364    
365      public Set<Dependency> getDependencies() {
366        return dependencies;
367      }
368    
369    
370      /* ------------ LINKS */
371    
372      public void saveLink(ProjectLink link) {
373        ResourceModel projectDao = session.reattach(ResourceModel.class, selectedProjectBucket.getResourceId());
374        ProjectLink dbLink = projectDao.getProjectLink(link.getKey());
375        if (dbLink == null) {
376          link.setResource(projectDao);
377          projectDao.getProjectLinks().add(link);
378          session.save(link);
379    
380        } else {
381          dbLink.copyFieldsFrom(link);
382          session.save(dbLink);
383        }
384      }
385    
386      public void deleteLink(String key) {
387        ResourceModel projectDao = session.reattach(ResourceModel.class, selectedProjectBucket.getResourceId());
388        ProjectLink dbLink = projectDao.getProjectLink(key);
389        if (dbLink != null) {
390          session.remove(dbLink);
391          projectDao.getProjectLinks().remove(dbLink);
392        }
393      }
394    
395    
396      /* ----------- EVENTS */
397      public List<Event> getEvents(Resource resource) {
398        Bucket bucket = getOrCreateBucket(resource);
399        return session.getResults(Event.class, "resourceId", bucket.getResourceId());
400      }
401    
402      public void deleteEvent(Event event) {
403        session.remove(event);
404      }
405    
406      public Event createEvent(Resource resource, String name, String description, String category, Date date) {
407        Bucket bucket = getOrCreateBucket(resource);
408        Event event;
409        if (date == null) {
410          event = new Event(name, description, category, bucket.getSnapshot());
411        } else {
412          event = new Event(name, description, category, date, bucket.getResourceId());
413        }
414        return session.save(event);
415      }
416    
417      public Dependency getEdge(Resource from, Resource to) {
418        Map<Resource, Dependency> map = outgoingDependenciesByResource.get(from);
419        if (map != null) {
420          return map.get(to);
421        }
422        return null;
423      }
424    
425      public boolean hasEdge(Resource from, Resource to) {
426        return getEdge(from, to) != null;
427      }
428    
429      public Set<Resource> getVertices() {
430        return buckets.keySet();
431      }
432    
433      public Collection<Dependency> getOutgoingEdges(Resource from) {
434        Map<Resource, Dependency> deps = outgoingDependenciesByResource.get(from);
435        if (deps != null) {
436          return deps.values();
437        }
438        return Collections.emptyList();
439      }
440    
441      public Collection<Dependency> getIncomingEdges(Resource to) {
442        Map<Resource, Dependency> deps = incomingDependenciesByResource.get(to);
443        if (deps != null) {
444          return deps.values();
445        }
446        return Collections.emptyList();
447      }
448    
449      public Set<Dependency> getDependenciesBetweenProjects() {
450        Set<Dependency> result = new HashSet<Dependency>();
451        for (Project project : projectTree.getProjects()) {
452          Collection<Dependency> deps = getOutgoingDependencies(project);
453          for (Dependency dep : deps) {
454            if (ResourceUtils.isSet(dep.getTo())) {
455              result.add(dep);
456            }
457          }
458        }
459        return result;
460      }
461    }