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.Lists; 023import com.google.common.collect.Maps; 024import com.google.common.collect.Sets; 025import org.apache.commons.lang.ObjectUtils; 026import org.apache.commons.lang.StringUtils; 027import org.slf4j.Logger; 028import org.slf4j.LoggerFactory; 029import org.sonar.api.batch.Event; 030import org.sonar.api.batch.SonarIndex; 031import org.sonar.api.database.model.ResourceModel; 032import org.sonar.api.design.Dependency; 033import org.sonar.api.measures.*; 034import org.sonar.api.profiles.RulesProfile; 035import org.sonar.api.resources.*; 036import org.sonar.api.rules.ActiveRule; 037import org.sonar.api.rules.Violation; 038import org.sonar.api.utils.SonarException; 039import org.sonar.api.violations.ViolationQuery; 040import org.sonar.batch.DefaultResourceCreationLock; 041import org.sonar.batch.ProjectTree; 042import org.sonar.batch.ResourceFilters; 043import org.sonar.batch.ViolationFilters; 044 045import java.util.*; 046 047public class DefaultIndex extends SonarIndex { 048 049 private static final Logger LOG = LoggerFactory.getLogger(DefaultIndex.class); 050 051 private RulesProfile profile; 052 private PersistenceManager persistence; 053 private DefaultResourceCreationLock lock; 054 private MetricFinder metricFinder; 055 056 // filters 057 private ViolationFilters violationFilters; 058 private ResourceFilters resourceFilters; 059 060 // caches 061 private Project currentProject; 062 private Map<Resource, Bucket> buckets = Maps.newHashMap(); 063 private Set<Dependency> dependencies = Sets.newHashSet(); 064 private Map<Resource, Map<Resource, Dependency>> outgoingDependenciesByResource = Maps.newHashMap(); 065 private Map<Resource, Map<Resource, Dependency>> incomingDependenciesByResource = Maps.newHashMap(); 066 private ProjectTree projectTree; 067 068 public DefaultIndex(PersistenceManager persistence, DefaultResourceCreationLock lock, ProjectTree projectTree, MetricFinder metricFinder) { 069 this.persistence = persistence; 070 this.lock = lock; 071 this.projectTree = projectTree; 072 this.metricFinder = metricFinder; 073 } 074 075 public void start() { 076 Project rootProject = projectTree.getRootProject(); 077 doStart(rootProject); 078 } 079 080 void doStart(Project rootProject) { 081 Bucket bucket = new Bucket(rootProject); 082 buckets.put(rootProject, bucket); 083 persistence.saveProject(rootProject, null); 084 currentProject = rootProject; 085 086 for (Project project : rootProject.getModules()) { 087 addProject(project); 088 } 089 } 090 091 private void addProject(Project project) { 092 addResource(project); 093 for (Project module : project.getModules()) { 094 addProject(module); 095 } 096 } 097 098 @Override 099 public Project getProject() { 100 return currentProject; 101 } 102 103 public void setCurrentProject(Project project, ResourceFilters resourceFilters, ViolationFilters violationFilters, RulesProfile profile) { 104 this.currentProject = project; 105 106 // the following components depend on the current project, so they need to be reloaded. 107 this.resourceFilters = resourceFilters; 108 this.violationFilters = violationFilters; 109 this.profile = profile; 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 projectDependency.setId(null); 132 registerDependency(projectDependency); 133 } 134 135 lock.unlock(); 136 } 137 138 @Override 139 public Measure getMeasure(Resource resource, Metric metric) { 140 Bucket bucket = buckets.get(resource); 141 if (bucket != null) { 142 Measure measure = bucket.getMeasures(MeasuresFilters.metric(metric)); 143 if (measure != null) { 144 return persistence.reloadMeasure(measure); 145 } 146 } 147 return null; 148 } 149 150 @Override 151 public <M> M getMeasures(Resource resource, MeasuresFilter<M> filter) { 152 Bucket bucket = buckets.get(resource); 153 if (bucket != null) { 154 // TODO the data measures which are not kept in memory are not reloaded yet. Use getMeasure(). 155 return bucket.getMeasures(filter); 156 } 157 return null; 158 } 159 160 /** 161 * the measure is updated if it's already registered. 162 */ 163 @Override 164 public Measure addMeasure(Resource resource, Measure measure) { 165 Bucket bucket = checkIndexed(resource); 166 if (bucket != null && !bucket.isExcluded()) { 167 Metric metric = metricFinder.findByKey(measure.getMetricKey()); 168 if (metric == null) { 169 throw new SonarException("Unknown metric: " + measure.getMetricKey()); 170 } 171 measure.setMetric(metric); 172 bucket.addMeasure(measure); 173 174 if (measure.getPersistenceMode().useDatabase()) { 175 persistence.saveMeasure(resource, measure); 176 } 177 } 178 return measure; 179 } 180 181 // 182 // 183 // 184 // DEPENDENCIES 185 // 186 // 187 // 188 189 @Override 190 public Dependency addDependency(Dependency dependency) { 191 Dependency existingDep = getEdge(dependency.getFrom(), dependency.getTo()); 192 if (existingDep != null) { 193 return existingDep; 194 } 195 196 Dependency parentDependency = dependency.getParent(); 197 if (parentDependency != null) { 198 addDependency(parentDependency); 199 } 200 201 if (registerDependency(dependency)) { 202 persistence.saveDependency(currentProject, dependency, parentDependency); 203 } 204 return dependency; 205 } 206 207 boolean registerDependency(Dependency dependency) { 208 Bucket fromBucket = doIndex(dependency.getFrom()); 209 Bucket toBucket = doIndex(dependency.getTo()); 210 211 if (fromBucket != null && !fromBucket.isExcluded() && toBucket != null && !toBucket.isExcluded()) { 212 dependencies.add(dependency); 213 registerOutgoingDependency(dependency); 214 registerIncomingDependency(dependency); 215 return true; 216 } 217 return false; 218 } 219 220 private void registerOutgoingDependency(Dependency dependency) { 221 Map<Resource, Dependency> outgoingDeps = outgoingDependenciesByResource.get(dependency.getFrom()); 222 if (outgoingDeps == null) { 223 outgoingDeps = new HashMap<Resource, Dependency>(); 224 outgoingDependenciesByResource.put(dependency.getFrom(), outgoingDeps); 225 } 226 outgoingDeps.put(dependency.getTo(), dependency); 227 } 228 229 private void registerIncomingDependency(Dependency dependency) { 230 Map<Resource, Dependency> incomingDeps = incomingDependenciesByResource.get(dependency.getTo()); 231 if (incomingDeps == null) { 232 incomingDeps = new HashMap<Resource, Dependency>(); 233 incomingDependenciesByResource.put(dependency.getTo(), incomingDeps); 234 } 235 incomingDeps.put(dependency.getFrom(), dependency); 236 } 237 238 @Override 239 public Set<Dependency> getDependencies() { 240 return dependencies; 241 } 242 243 public Dependency getEdge(Resource from, Resource to) { 244 Map<Resource, Dependency> map = outgoingDependenciesByResource.get(from); 245 if (map != null) { 246 return map.get(to); 247 } 248 return null; 249 } 250 251 public boolean hasEdge(Resource from, Resource to) { 252 return getEdge(from, to) != null; 253 } 254 255 public Set<Resource> getVertices() { 256 return buckets.keySet(); 257 } 258 259 public Collection<Dependency> getOutgoingEdges(Resource from) { 260 Map<Resource, Dependency> deps = outgoingDependenciesByResource.get(from); 261 if (deps != null) { 262 return deps.values(); 263 } 264 return Collections.emptyList(); 265 } 266 267 public Collection<Dependency> getIncomingEdges(Resource to) { 268 Map<Resource, Dependency> deps = incomingDependenciesByResource.get(to); 269 if (deps != null) { 270 return deps.values(); 271 } 272 return Collections.emptyList(); 273 } 274 275 Set<Dependency> getDependenciesBetweenProjects() { 276 Set<Dependency> result = Sets.newLinkedHashSet(); 277 for (Dependency dependency : dependencies) { 278 if (ResourceUtils.isSet(dependency.getFrom()) || ResourceUtils.isSet(dependency.getTo())) { 279 result.add(dependency); 280 } 281 } 282 return result; 283 } 284 285 // 286 // 287 // 288 // VIOLATIONS 289 // 290 // 291 // 292 293 /** 294 * {@inheritDoc} 295 */ 296 @Override 297 public List<Violation> getViolations(ViolationQuery violationQuery) { 298 Resource resource = violationQuery.getResource(); 299 if (resource == null) { 300 throw new IllegalArgumentException("A resource must be set on the ViolationQuery in order to search for violations."); 301 } 302 Bucket bucket = buckets.get(resource); 303 if (bucket == null) { 304 return Collections.emptyList(); 305 } 306 List<Violation> filteredViolations = Lists.newArrayList(); 307 ViolationQuery.SwitchMode mode = violationQuery.getSwitchMode(); 308 for (Violation violation : bucket.getViolations()) { 309 if (mode == ViolationQuery.SwitchMode.BOTH || 310 (mode == ViolationQuery.SwitchMode.OFF && violation.isSwitchedOff()) || 311 (mode == ViolationQuery.SwitchMode.ON && !violation.isSwitchedOff())) { 312 filteredViolations.add(violation); 313 } 314 } 315 return filteredViolations; 316 } 317 318 @Override 319 public void addViolation(Violation violation, boolean force) { 320 Resource resource = violation.getResource(); 321 if (resource == null) { 322 violation.setResource(currentProject); 323 } else if (!Scopes.isHigherThanOrEquals(resource, Scopes.FILE)) { 324 throw new IllegalArgumentException("Violations are only supported on files, directories and project"); 325 } 326 327 if (violation.getRule() == null) { 328 LOG.warn("Rule is null, ignoring violation {}", violation); 329 return; 330 } 331 332 Bucket bucket = checkIndexed(resource); 333 if (bucket != null && !bucket.isExcluded()) { 334 boolean isIgnored = !force && violationFilters != null && violationFilters.isIgnored(violation); 335 if (!isIgnored) { 336 337 // TODO this code is not the responsibility of this index. It should be moved somewhere else. 338 339 if (violation.isManual()) { 340 doAddViolation(violation, bucket); 341 } else { 342 ActiveRule activeRule = profile.getActiveRule(violation.getRule()); 343 if (activeRule == null) { 344 if (currentProject.getReuseExistingRulesConfig()) { 345 violation.setSeverity(violation.getRule().getSeverity()); 346 doAddViolation(violation, bucket); 347 348 } else { 349 LoggerFactory.getLogger(getClass()).debug("Rule is not activated, ignoring violation {}", violation); 350 } 351 352 } else { 353 violation.setSeverity(activeRule.getSeverity()); 354 doAddViolation(violation, bucket); 355 } 356 } 357 } 358 } 359 } 360 361 private void doAddViolation(Violation violation, Bucket bucket) { 362 bucket.addViolation(violation); 363 } 364 365 // 366 // 367 // 368 // LINKS 369 // 370 // 371 // 372 373 @Override 374 public void addLink(ProjectLink link) { 375 persistence.saveLink(currentProject, link); 376 } 377 378 @Override 379 public void deleteLink(String key) { 380 persistence.deleteLink(currentProject, key); 381 } 382 383 // 384 // 385 // 386 // EVENTS 387 // 388 // 389 // 390 391 @Override 392 public List<Event> getEvents(Resource resource) { 393 // currently events are not cached in memory 394 return persistence.getEvents(resource); 395 } 396 397 @Override 398 public void deleteEvent(Event event) { 399 persistence.deleteEvent(event); 400 } 401 402 @Override 403 public Event addEvent(Resource resource, String name, String description, String category, Date date) { 404 Event event = new Event(name, description, category); 405 event.setDate(date); 406 event.setCreatedAt(new Date()); 407 408 persistence.saveEvent(resource, event); 409 return null; 410 } 411 412 @Override 413 public void setSource(Resource reference, String source) { 414 Bucket bucket = checkIndexed(reference); 415 if (bucket != null && !bucket.isExcluded()) { 416 persistence.setSource(reference, source); 417 } 418 } 419 420 @Override 421 public String getSource(Resource resource) { 422 return persistence.getSource(resource); 423 } 424 425 /** 426 * Does nothing if the resource is already registered. 427 */ 428 @Override 429 public Resource addResource(Resource resource) { 430 Bucket bucket = doIndex(resource); 431 return bucket != null ? bucket.getResource() : null; 432 } 433 434 @Override 435 public <R extends Resource> R getResource(R reference) { 436 Bucket bucket = buckets.get(reference); 437 if (bucket != null) { 438 return (R) bucket.getResource(); 439 } 440 return null; 441 } 442 443 static String createUID(Project project, Resource resource) { 444 String uid = resource.getKey(); 445 if (!StringUtils.equals(Scopes.PROJECT, resource.getScope())) { 446 // not a project nor a library 447 uid = new StringBuilder(ResourceModel.KEY_SIZE) 448 .append(project.getKey()) 449 .append(':') 450 .append(resource.getKey()) 451 .toString(); 452 } 453 return uid; 454 } 455 456 private boolean checkExclusion(Resource resource, Bucket parent) { 457 boolean excluded = (parent != null && parent.isExcluded()) || (resourceFilters != null && resourceFilters.isExcluded(resource)); 458 resource.setExcluded(excluded); 459 return excluded; 460 } 461 462 @Override 463 public List<Resource> getChildren(Resource resource) { 464 return getChildren(resource, false); 465 } 466 467 public List<Resource> getChildren(Resource resource, boolean acceptExcluded) { 468 List<Resource> children = Lists.newLinkedList(); 469 Bucket bucket = getBucket(resource, acceptExcluded); 470 if (bucket != null) { 471 for (Bucket childBucket : bucket.getChildren()) { 472 if (acceptExcluded || !childBucket.isExcluded()) { 473 children.add(childBucket.getResource()); 474 } 475 } 476 } 477 return children; 478 } 479 480 @Override 481 public Resource getParent(Resource resource) { 482 Bucket bucket = getBucket(resource, false); 483 if (bucket != null && bucket.getParent() != null) { 484 return bucket.getParent().getResource(); 485 } 486 return null; 487 } 488 489 @Override 490 public boolean index(Resource resource) { 491 Bucket bucket = doIndex(resource); 492 return bucket != null && !bucket.isExcluded(); 493 } 494 495 private Bucket doIndex(Resource resource) { 496 if (resource.getParent() != null) { 497 doIndex(resource.getParent()); 498 } 499 return doIndex(resource, resource.getParent()); 500 } 501 502 @Override 503 public boolean index(Resource resource, Resource parentReference) { 504 Bucket bucket = doIndex(resource, parentReference); 505 return bucket != null && !bucket.isExcluded(); 506 } 507 508 private Bucket doIndex(Resource resource, Resource parentReference) { 509 Bucket bucket = buckets.get(resource); 510 if (bucket != null) { 511 return bucket; 512 } 513 514 checkLock(resource); 515 516 Resource parent = null; 517 if (!ResourceUtils.isLibrary(resource)) { 518 // a library has no parent 519 parent = (Resource) ObjectUtils.defaultIfNull(parentReference, currentProject); 520 } 521 522 Bucket parentBucket = getBucket(parent, true); 523 if (parentBucket == null && parent != null) { 524 LOG.warn("Resource ignored, parent is not indexed: " + resource); 525 return null; 526 } 527 528 resource.setEffectiveKey(createUID(currentProject, resource)); 529 bucket = new Bucket(resource).setParent(parentBucket); 530 buckets.put(resource, bucket); 531 532 boolean excluded = checkExclusion(resource, parentBucket); 533 if (!excluded) { 534 persistence.saveResource(currentProject, resource, (parentBucket != null ? parentBucket.getResource() : null)); 535 } 536 return bucket; 537 } 538 539 private void checkLock(Resource resource) { 540 if (lock.isLocked() && !ResourceUtils.isLibrary(resource)) { 541 if (lock.isFailWhenLocked()) { 542 throw new SonarException("Index is locked, resource can not be indexed: " + resource); 543 } 544 } 545 } 546 547 private Bucket checkIndexed(Resource resource) { 548 Bucket bucket = getBucket(resource, true); 549 if (bucket == null) { 550 if (lock.isLocked()) { 551 if (lock.isFailWhenLocked()) { 552 throw new ResourceNotIndexedException(resource); 553 } 554 LOG.warn("Resource will be ignored in next Sonar versions, index is locked: " + resource); 555 } 556 if (Scopes.isDirectory(resource) || Scopes.isFile(resource)) { 557 bucket = doIndex(resource); 558 } else if (!lock.isLocked()) { 559 LOG.warn("Resource will be ignored in next Sonar versions, it must be indexed before adding data: " + resource); 560 } 561 } 562 return bucket; 563 } 564 565 @Override 566 public boolean isExcluded(Resource reference) { 567 Bucket bucket = getBucket(reference, true); 568 return bucket != null && bucket.isExcluded(); 569 } 570 571 @Override 572 public boolean isIndexed(Resource reference, boolean acceptExcluded) { 573 return getBucket(reference, acceptExcluded) != null; 574 } 575 576 private Bucket getBucket(Resource resource, boolean acceptExcluded) { 577 Bucket bucket = null; 578 if (resource != null) { 579 bucket = buckets.get(resource); 580 if (!acceptExcluded && bucket != null && bucket.isExcluded()) { 581 bucket = null; 582 } 583 } 584 return bucket; 585 } 586}