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    }