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.plugins.findbugs;
021    
022    import com.google.common.collect.Lists;
023    import edu.umd.cs.findbugs.*;
024    import edu.umd.cs.findbugs.config.UserPreferences;
025    import org.apache.commons.io.FileUtils;
026    import org.apache.commons.io.IOUtils;
027    import org.apache.commons.io.output.NullOutputStream;
028    import org.apache.commons.lang.StringUtils;
029    import org.slf4j.Logger;
030    import org.slf4j.LoggerFactory;
031    import org.sonar.api.BatchExtension;
032    import org.sonar.api.utils.Logs;
033    import org.sonar.api.utils.SonarException;
034    import org.sonar.api.utils.TimeProfiler;
035    
036    import java.io.File;
037    import java.io.IOException;
038    import java.io.OutputStream;
039    import java.io.PrintStream;
040    import java.lang.reflect.Field;
041    import java.net.URL;
042    import java.util.Enumeration;
043    import java.util.List;
044    import java.util.concurrent.Callable;
045    import java.util.concurrent.ExecutorService;
046    import java.util.concurrent.Executors;
047    import java.util.concurrent.TimeUnit;
048    
049    /**
050     * @since 2.4
051     */
052    public class FindbugsExecutor implements BatchExtension {
053    
054      private static final Logger LOG = LoggerFactory.getLogger(FindbugsExecutor.class);
055    
056      private FindbugsConfiguration configuration;
057    
058      public FindbugsExecutor(FindbugsConfiguration configuration) {
059        this.configuration = configuration;
060      }
061    
062      public File execute() {
063        TimeProfiler profiler = new TimeProfiler().start("Execute Findbugs " + FindbugsVersion.getVersion());
064        ClassLoader initialClassLoader = Thread.currentThread().getContextClassLoader();
065        Thread.currentThread().setContextClassLoader(FindBugs2.class.getClassLoader());
066    
067        OutputStream xmlOutput = null;
068        ExecutorService executorService = Executors.newSingleThreadExecutor();
069        try {
070          DetectorFactoryCollection detectorFactory = loadFindbugsPlugins();
071    
072          final FindBugs2 engine = new FindBugs2();
073    
074          Project project = configuration.getFindbugsProject();
075          engine.setProject(project);
076    
077          XMLBugReporter xmlBugReporter = new XMLBugReporter(project);
078          xmlBugReporter.setPriorityThreshold(Detector.LOW_PRIORITY);
079          xmlBugReporter.setAddMessages(true);
080    
081          File xmlReport = configuration.getTargetXMLReport();
082          if (xmlReport != null) {
083            LOG.info("Findbugs output report: " + xmlReport.getAbsolutePath());
084            xmlOutput = FileUtils.openOutputStream(xmlReport);
085          } else {
086            xmlOutput = new NullOutputStream();
087          }
088          xmlBugReporter.setOutputStream(new PrintStream(xmlOutput));
089    
090          engine.setBugReporter(xmlBugReporter);
091    
092          UserPreferences userPreferences = UserPreferences.createDefaultUserPreferences();
093          userPreferences.setEffort(configuration.getEffort());
094          engine.setUserPreferences(userPreferences);
095    
096          engine.addFilter(configuration.saveIncludeConfigXml().getAbsolutePath(), true);
097          engine.addFilter(configuration.saveExcludeConfigXml().getAbsolutePath(), false);
098    
099          for (File filterFile : configuration.getExcludesFilters()) {
100            if (filterFile.isFile()) {
101              engine.addFilter(filterFile.getAbsolutePath(), false);
102            } else {
103              LOG.warn("FindBugs filter-file not found: {}", filterFile);
104            }
105          }
106    
107          engine.setDetectorFactoryCollection(detectorFactory);
108          engine.setAnalysisFeatureSettings(FindBugs.DEFAULT_EFFORT);
109    
110          engine.finishSettings();
111    
112          executorService.submit(new FindbugsTask(engine)).get(configuration.getTimeout(), TimeUnit.MILLISECONDS);
113    
114          profiler.stop();
115    
116          resetDetectorFactoryCollection();
117    
118          return xmlReport;
119        } catch (Exception e) {
120          throw new SonarException("Can not execute Findbugs", e);
121        } finally {
122          executorService.shutdown();
123          IOUtils.closeQuietly(xmlOutput);
124          Thread.currentThread().setContextClassLoader(initialClassLoader);
125        }
126      }
127    
128      private static class FindbugsTask implements Callable<Object> {
129    
130        private FindBugs2 engine;
131    
132        public FindbugsTask(FindBugs2 engine) {
133          this.engine = engine;
134        }
135    
136        public Object call() throws Exception {
137          try {
138            engine.execute();
139          } finally {
140            engine.dispose();
141          }
142          return null;
143        }
144      }
145    
146      private DetectorFactoryCollection loadFindbugsPlugins() {
147        List<URL> plugins = Lists.newArrayList();
148        try {
149          Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources("findbugs.xml");
150          while (urls.hasMoreElements()) {
151            URL url = urls.nextElement();
152            String path = StringUtils.removeStart(StringUtils.substringBefore(url.toString(), "!"), "jar:");
153            Logs.INFO.info("Found findbugs plugin: " + path);
154            plugins.add(new URL(path));
155          }
156        } catch (IOException e) {
157          throw new SonarException(e);
158        }
159    
160        resetDetectorFactoryCollection();
161        DetectorFactoryCollection detectorFactory = DetectorFactoryCollection.rawInstance();
162        detectorFactory.setPluginList(plugins.toArray(new URL[plugins.size()]));
163        for (Plugin plugin : detectorFactory.plugins()) {
164          Logs.INFO.info("Loaded plugin " + plugin.getPluginId());
165        }
166    
167        return detectorFactory;
168      }
169    
170      /**
171       * Unfortunately without reflection it's impossible to reset {@link DetectorFactoryCollection}.
172       */
173      private static void resetDetectorFactoryCollection() {
174        try {
175          Field field = DetectorFactoryCollection.class.getDeclaredField("theInstance");
176          field.setAccessible(true);
177          field.set(null, null);
178        } catch (Exception e) {
179          throw new SonarException(e);
180        }
181      }
182    
183    }