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 */
020package org.sonar.plugins.findbugs;
021
022import com.google.common.collect.Lists;
023import edu.umd.cs.findbugs.DetectorFactoryCollection;
024import edu.umd.cs.findbugs.FindBugs;
025import edu.umd.cs.findbugs.FindBugs2;
026import edu.umd.cs.findbugs.Plugin;
027import edu.umd.cs.findbugs.PluginException;
028import edu.umd.cs.findbugs.Priorities;
029import edu.umd.cs.findbugs.Project;
030import edu.umd.cs.findbugs.XMLBugReporter;
031import edu.umd.cs.findbugs.config.UserPreferences;
032import edu.umd.cs.findbugs.plugins.DuplicatePluginIdException;
033import org.apache.commons.io.FileUtils;
034import org.apache.commons.io.IOUtils;
035import org.apache.commons.io.output.NullOutputStream;
036import org.apache.commons.lang.StringUtils;
037import org.slf4j.Logger;
038import org.slf4j.LoggerFactory;
039import org.sonar.api.BatchExtension;
040import org.sonar.api.utils.SonarException;
041import org.sonar.api.utils.TimeProfiler;
042
043import java.io.File;
044import java.io.IOException;
045import java.io.OutputStream;
046import java.io.PrintStream;
047import java.net.URL;
048import java.util.Collection;
049import java.util.Enumeration;
050import java.util.List;
051import java.util.concurrent.Callable;
052import java.util.concurrent.ExecutorService;
053import java.util.concurrent.Executors;
054import java.util.concurrent.TimeUnit;
055
056/**
057 * @since 2.4
058 */
059public class FindbugsExecutor implements BatchExtension {
060
061  private static final String FINDBUGS_CORE_PLUGIN_ID = "edu.umd.cs.findbugs.plugins.core";
062
063  private static final Logger LOG = LoggerFactory.getLogger(FindbugsExecutor.class);
064
065  private FindbugsConfiguration configuration;
066
067  public FindbugsExecutor(FindbugsConfiguration configuration) {
068    this.configuration = configuration;
069  }
070
071  public File execute() {
072    TimeProfiler profiler = new TimeProfiler().start("Execute Findbugs " + FindbugsVersion.getVersion());
073    // We keep a handle on the current security manager because FB plays with it and we need to restore it before shutting down the executor
074    // service
075    SecurityManager currentSecurityManager = System.getSecurityManager();
076    ClassLoader initialClassLoader = Thread.currentThread().getContextClassLoader();
077    Thread.currentThread().setContextClassLoader(FindBugs2.class.getClassLoader());
078
079    OutputStream xmlOutput = null;
080    Collection<Plugin> customPlugins = null;
081    ExecutorService executorService = Executors.newSingleThreadExecutor();
082    try {
083      final FindBugs2 engine = new FindBugs2();
084
085      customPlugins = loadFindbugsPlugins();
086
087      disableUpdateChecksOnEveryPlugin();
088
089      Project project = configuration.getFindbugsProject();
090      engine.setProject(project);
091
092      XMLBugReporter xmlBugReporter = new XMLBugReporter(project);
093      xmlBugReporter.setPriorityThreshold(Priorities.LOW_PRIORITY);
094      xmlBugReporter.setAddMessages(true);
095
096      File xmlReport = configuration.getTargetXMLReport();
097      if (xmlReport != null) {
098        LOG.info("Findbugs output report: " + xmlReport.getAbsolutePath());
099        xmlOutput = FileUtils.openOutputStream(xmlReport);
100      } else {
101        xmlOutput = new NullOutputStream();
102      }
103      xmlBugReporter.setOutputStream(new PrintStream(xmlOutput));
104
105      engine.setBugReporter(xmlBugReporter);
106
107      UserPreferences userPreferences = UserPreferences.createDefaultUserPreferences();
108      userPreferences.setEffort(configuration.getEffort());
109      engine.setUserPreferences(userPreferences);
110
111      engine.addFilter(configuration.saveIncludeConfigXml().getAbsolutePath(), true);
112      engine.addFilter(configuration.saveExcludeConfigXml().getAbsolutePath(), false);
113
114      for (File filterFile : configuration.getExcludesFilters()) {
115        if (filterFile.isFile()) {
116          engine.addFilter(filterFile.getAbsolutePath(), false);
117        } else {
118          LOG.warn("FindBugs filter-file not found: {}", filterFile);
119        }
120      }
121
122      engine.setDetectorFactoryCollection(DetectorFactoryCollection.instance());
123      engine.setAnalysisFeatureSettings(FindBugs.DEFAULT_EFFORT);
124
125      engine.finishSettings();
126
127      executorService.submit(new FindbugsTask(engine)).get(configuration.getTimeout(), TimeUnit.MILLISECONDS);
128
129      profiler.stop();
130
131      return xmlReport;
132    } catch (Exception e) {
133      throw new SonarException("Can not execute Findbugs", e);
134    } finally {
135      // we set back the original security manager BEFORE shutting down the executor service, otherwise there's a problem with Java 5
136      System.setSecurityManager(currentSecurityManager);
137      resetCustomPluginList(customPlugins);
138      executorService.shutdown();
139      IOUtils.closeQuietly(xmlOutput);
140      Thread.currentThread().setContextClassLoader(initialClassLoader);
141    }
142  }
143
144  private static class FindbugsTask implements Callable<Object> {
145
146    private FindBugs2 engine;
147
148    public FindbugsTask(FindBugs2 engine) {
149      this.engine = engine;
150    }
151
152    public Object call() throws Exception {
153      try {
154        engine.execute();
155      } finally {
156        engine.dispose();
157      }
158      return null;
159    }
160  }
161
162  private Collection<Plugin> loadFindbugsPlugins() {
163    ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
164
165    List<String> pluginJarPathList = Lists.newArrayList();
166    try {
167      Enumeration<URL> urls = contextClassLoader.getResources("findbugs.xml");
168      while (urls.hasMoreElements()) {
169        URL url = urls.nextElement();
170        pluginJarPathList.add(StringUtils.removeStart(StringUtils.substringBefore(url.toString(), "!"), "jar:file:"));
171      }
172    } catch (IOException e) {
173      throw new SonarException(e);
174    }
175
176    List<Plugin> customPluginList = Lists.newArrayList();
177    for (String path : pluginJarPathList) {
178      try {
179        Plugin plugin = Plugin.addCustomPlugin(new File(path).toURI(), contextClassLoader);
180        if (plugin != null) {
181          customPluginList.add(plugin);
182          LOG.info("Found findbugs plugin: " + path);
183        }
184      } catch (PluginException e) {
185        LOG.warn("Failed to load plugin for custom detector: " + path);
186      } catch (DuplicatePluginIdException e) {
187        // FB Core plugin is always loaded, so we'll get an exception for it always
188        if (!FINDBUGS_CORE_PLUGIN_ID.equals(e.getPluginId())) {
189          // log only if it's not the FV Core plugin
190          LOG.debug("Plugin already loaded: exception ignored: " + e.getMessage());
191        }
192      }
193    }
194
195    return customPluginList;
196  }
197
198  /**
199   * Disable the update check for every plugin. See http://findbugs.sourceforge.net/updateChecking.html
200   */
201  private void disableUpdateChecksOnEveryPlugin() {
202    for (Plugin plugin : Plugin.getAllPlugins()) {
203      plugin.setMyGlobalOption("noUpdateChecks", "true");
204    }
205  }
206
207  private static void resetCustomPluginList(Collection<Plugin> customPlugins) {
208    if (customPlugins != null) {
209      for (Plugin plugin : customPlugins) {
210        Plugin.removeCustomPlugin(plugin);
211      }
212    }
213  }
214
215}