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 }