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