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 }