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 }