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 @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
303 Bucket bucket = buckets.get(resource);
304 if (bucket == null) {
305 return Collections.emptyList();
306 }
307
308 List<Violation> filteredViolations = Lists.newArrayList();
309 ViolationQuery.SwitchMode mode = violationQuery.getSwitchMode();
310 for (Violation violation : bucket.getViolations()) {
311 if (isFiltered(violation, mode)) {
312 filteredViolations.add(violation);
313 }
314 }
315 return filteredViolations;
316 }
317
318 private static boolean isFiltered(Violation violation, ViolationQuery.SwitchMode mode) {
319 return (mode == ViolationQuery.SwitchMode.BOTH
320 || (mode == ViolationQuery.SwitchMode.OFF && violation.isSwitchedOff())
321 || (mode == ViolationQuery.SwitchMode.ON && !violation.isSwitchedOff()));
322 }
323
324 @Override
325 public void addViolation(Violation violation, boolean force) {
326 Resource resource = violation.getResource();
327 if (resource == null) {
328 violation.setResource(currentProject);
329 } else if (!Scopes.isHigherThanOrEquals(resource, Scopes.FILE)) {
330 throw new IllegalArgumentException("Violations are only supported on files, directories and project");
331 }
332
333 if (violation.getRule() == null) {
334 LOG.warn("Rule is null, ignoring violation {}", violation);
335 return;
336 }
337
338 Bucket bucket = checkIndexed(resource);
339 if (bucket == null || bucket.isExcluded()) {
340 return;
341 }
342
343 boolean isIgnored = !force && violationFilters != null && violationFilters.isIgnored(violation);
344 if (!isIgnored) {
345 addViolation(violation, bucket);
346 }
347 }
348
349 private void addViolation(Violation violation, Bucket bucket) {
350 // TODO this code is not the responsibility of this index. It should be moved somewhere else.
351 if (!violation.isManual()) {
352 ActiveRule activeRule = profile.getActiveRule(violation.getRule());
353 if (activeRule != null) {
354 violation.setSeverity(activeRule.getSeverity());
355 } else if (currentProject.getReuseExistingRulesConfig()) {
356 violation.setSeverity(violation.getRule().getSeverity());
357 } else {
358 LoggerFactory.getLogger(getClass()).debug("Rule is not activated, ignoring violation {}", violation);
359 return;
360 }
361 }
362
363 doAddViolation(violation, bucket);
364 }
365
366 private void doAddViolation(Violation violation, Bucket bucket) {
367 bucket.addViolation(violation);
368 }
369
370 //
371 //
372 //
373 // LINKS
374 //
375 //
376 //
377
378 @Override
379 public void addLink(ProjectLink link) {
380 persistence.saveLink(currentProject, link);
381 }
382
383 @Override
384 public void deleteLink(String key) {
385 persistence.deleteLink(currentProject, key);
386 }
387
388 //
389 //
390 //
391 // EVENTS
392 //
393 //
394 //
395
396 @Override
397 public List<Event> getEvents(Resource resource) {
398 // currently events are not cached in memory
399 return persistence.getEvents(resource);
400 }
401
402 @Override
403 public void deleteEvent(Event event) {
404 persistence.deleteEvent(event);
405 }
406
407 @Override
408 public Event addEvent(Resource resource, String name, String description, String category, Date date) {
409 Event event = new Event(name, description, category);
410 event.setDate(date);
411 event.setCreatedAt(new Date());
412
413 persistence.saveEvent(resource, event);
414 return null;
415 }
416
417 @Override
418 public void setSource(Resource reference, String source) {
419 Bucket bucket = checkIndexed(reference);
420 if (bucket != null && !bucket.isExcluded()) {
421 persistence.setSource(reference, source);
422 }
423 }
424
425 @Override
426 public String getSource(Resource resource) {
427 return persistence.getSource(resource);
428 }
429
430 /**
431 * Does nothing if the resource is already registered.
432 */
433 @Override
434 public Resource addResource(Resource resource) {
435 Bucket bucket = doIndex(resource);
436 return bucket != null ? bucket.getResource() : null;
437 }
438
439 @Override
440 public <R extends Resource> R getResource(R reference) {
441 Bucket bucket = buckets.get(reference);
442 if (bucket != null) {
443 return (R) bucket.getResource();
444 }
445 return null;
446 }
447
448 static String createUID(Project project, Resource resource) {
449 String uid = resource.getKey();
450 if (!StringUtils.equals(Scopes.PROJECT, resource.getScope())) {
451 // not a project nor a library
452 uid = new StringBuilder(ResourceModel.KEY_SIZE)
453 .append(project.getKey())
454 .append(':')
455 .append(resource.getKey())
456 .toString();
457 }
458 return uid;
459 }
460
461 private boolean checkExclusion(Resource resource, Bucket parent) {
462 boolean excluded = (parent != null && parent.isExcluded()) || (resourceFilters != null && resourceFilters.isExcluded(resource));
463 resource.setExcluded(excluded);
464 return excluded;
465 }
466
467 @Override
468 public List<Resource> getChildren(Resource resource) {
469 return getChildren(resource, false);
470 }
471
472 public List<Resource> getChildren(Resource resource, boolean acceptExcluded) {
473 List<Resource> children = Lists.newLinkedList();
474 Bucket bucket = getBucket(resource, acceptExcluded);
475 if (bucket != null) {
476 for (Bucket childBucket : bucket.getChildren()) {
477 if (acceptExcluded || !childBucket.isExcluded()) {
478 children.add(childBucket.getResource());
479 }
480 }
481 }
482 return children;
483 }
484
485 @Override
486 public Resource getParent(Resource resource) {
487 Bucket bucket = getBucket(resource, false);
488 if (bucket != null && bucket.getParent() != null) {
489 return bucket.getParent().getResource();
490 }
491 return null;
492 }
493
494 @Override
495 public boolean index(Resource resource) {
496 Bucket bucket = doIndex(resource);
497 return bucket != null && !bucket.isExcluded();
498 }
499
500 private Bucket doIndex(Resource resource) {
501 if (resource.getParent() != null) {
502 doIndex(resource.getParent());
503 }
504 return doIndex(resource, resource.getParent());
505 }
506
507 @Override
508 public boolean index(Resource resource, Resource parentReference) {
509 Bucket bucket = doIndex(resource, parentReference);
510 return bucket != null && !bucket.isExcluded();
511 }
512
513 private Bucket doIndex(Resource resource, Resource parentReference) {
514 Bucket bucket = buckets.get(resource);
515 if (bucket != null) {
516 return bucket;
517 }
518
519 checkLock(resource);
520
521 Resource parent = null;
522 if (!ResourceUtils.isLibrary(resource)) {
523 // a library has no parent
524 parent = (Resource) ObjectUtils.defaultIfNull(parentReference, currentProject);
525 }
526
527 Bucket parentBucket = getBucket(parent, true);
528 if (parentBucket == null && parent != null) {
529 LOG.warn("Resource ignored, parent is not indexed: " + resource);
530 return null;
531 }
532
533 resource.setEffectiveKey(createUID(currentProject, resource));
534 bucket = new Bucket(resource).setParent(parentBucket);
535 buckets.put(resource, bucket);
536
537 boolean excluded = checkExclusion(resource, parentBucket);
538 if (!excluded) {
539 persistence.saveResource(currentProject, resource, (parentBucket != null ? parentBucket.getResource() : null));
540 }
541 return bucket;
542 }
543
544 private void checkLock(Resource resource) {
545 if (lock.isLocked() && !ResourceUtils.isLibrary(resource)) {
546 if (lock.isFailWhenLocked()) {
547 throw new SonarException("Index is locked, resource can not be indexed: " + resource);
548 }
549 }
550 }
551
552 private Bucket checkIndexed(Resource resource) {
553 Bucket bucket = getBucket(resource, true);
554 if (bucket == null) {
555 if (lock.isLocked()) {
556 if (lock.isFailWhenLocked()) {
557 throw new ResourceNotIndexedException(resource);
558 }
559 LOG.warn("Resource will be ignored in next Sonar versions, index is locked: " + resource);
560 }
561 if (Scopes.isDirectory(resource) || Scopes.isFile(resource)) {
562 bucket = doIndex(resource);
563 } else if (!lock.isLocked()) {
564 LOG.warn("Resource will be ignored in next Sonar versions, it must be indexed before adding data: " + resource);
565 }
566 }
567 return bucket;
568 }
569
570 @Override
571 public boolean isExcluded(Resource reference) {
572 Bucket bucket = getBucket(reference, true);
573 return bucket != null && bucket.isExcluded();
574 }
575
576 @Override
577 public boolean isIndexed(Resource reference, boolean acceptExcluded) {
578 return getBucket(reference, acceptExcluded) != null;
579 }
580
581 private Bucket getBucket(Resource resource, boolean acceptExcluded) {
582 Bucket bucket = null;
583 if (resource != null) {
584 bucket = buckets.get(resource);
585 if (!acceptExcluded && bucket != null && bucket.isExcluded()) {
586 bucket = null;
587 }
588 }
589 return bucket;
590 }
591 }