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    }