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