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 }