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