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}