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 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    }