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 }