1   
2   
3   
4   
5   
6   
7   
8   
9   
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  package org.apache.maven.plugins.pmd.exec;
20  
21  import java.io.Closeable;
22  import java.io.File;
23  import java.io.FileInputStream;
24  import java.io.FileOutputStream;
25  import java.io.IOException;
26  import java.io.ObjectInputStream;
27  import java.io.ObjectOutputStream;
28  import java.io.OutputStreamWriter;
29  import java.io.UnsupportedEncodingException;
30  import java.io.Writer;
31  import java.nio.charset.Charset;
32  import java.nio.charset.IllegalCharsetNameException;
33  import java.nio.charset.UnsupportedCharsetException;
34  import java.nio.file.Files;
35  import java.util.ArrayList;
36  import java.util.List;
37  import java.util.Objects;
38  
39  import net.sourceforge.pmd.PMDConfiguration;
40  import net.sourceforge.pmd.PmdAnalysis;
41  import net.sourceforge.pmd.benchmark.TextTimingReportRenderer;
42  import net.sourceforge.pmd.benchmark.TimeTracker;
43  import net.sourceforge.pmd.benchmark.TimingReport;
44  import net.sourceforge.pmd.benchmark.TimingReportRenderer;
45  import net.sourceforge.pmd.lang.Language;
46  import net.sourceforge.pmd.lang.LanguageVersion;
47  import net.sourceforge.pmd.lang.rule.RulePriority;
48  import net.sourceforge.pmd.lang.rule.RuleSetLoadException;
49  import net.sourceforge.pmd.lang.rule.RuleSetLoader;
50  import net.sourceforge.pmd.renderers.CSVRenderer;
51  import net.sourceforge.pmd.renderers.HTMLRenderer;
52  import net.sourceforge.pmd.renderers.Renderer;
53  import net.sourceforge.pmd.renderers.TextRenderer;
54  import net.sourceforge.pmd.renderers.XMLRenderer;
55  import net.sourceforge.pmd.reporting.Report;
56  import org.apache.maven.plugin.MojoExecutionException;
57  import org.apache.maven.plugins.pmd.ExcludeViolationsFromFile;
58  import org.apache.maven.reporting.MavenReportException;
59  import org.codehaus.plexus.util.FileUtils;
60  import org.slf4j.Logger;
61  import org.slf4j.LoggerFactory;
62  
63  
64  
65  
66  public class PmdExecutor extends Executor {
67      private static final Logger LOG = LoggerFactory.getLogger(PmdExecutor.class);
68  
69      public PmdResult fork(String javaExecutable) throws MavenReportException {
70          File basePmdDir = new File(request.getTargetDirectory(), "pmd");
71          basePmdDir.mkdirs();
72          File pmdRequestFile = new File(basePmdDir, "pmdrequest.bin");
73          try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(pmdRequestFile))) {
74              out.writeObject(request);
75          } catch (IOException e) {
76              throw new MavenReportException(e.getMessage(), e);
77          }
78  
79          String classpath = buildClasspath();
80          ProcessBuilder pb = new ProcessBuilder();
81          
82          pb.environment().put("CLASSPATH", classpath);
83          pb.command().add(javaExecutable);
84          pb.command().add(PmdExecutor.class.getName());
85          pb.command().add(pmdRequestFile.getAbsolutePath());
86  
87          LOG.debug("Executing: CLASSPATH={}, command={}", classpath, pb.command());
88          try {
89              final Process p = pb.start();
90              
91              
92              ProcessStreamHandler.start(p.getInputStream(), System.out);
93              ProcessStreamHandler.start(p.getErrorStream(), System.err);
94              int exit = p.waitFor();
95              LOG.debug("PmdExecutor exit code: {}", exit);
96              if (exit != 0) {
97                  throw new MavenReportException("PmdExecutor exited with exit code " + exit);
98              }
99              return new PmdResult(new File(request.getTargetDirectory(), "pmd.xml"), request.getOutputEncoding());
100         } catch (IOException e) {
101             throw new MavenReportException(e.getMessage(), e);
102         } catch (InterruptedException e) {
103             Thread.currentThread().interrupt();
104             throw new MavenReportException(e.getMessage(), e);
105         }
106     }
107 
108     
109 
110 
111 
112 
113 
114 
115 
116 
117 
118 
119     public static void main(String[] args) {
120         File requestFile = new File(args[0]);
121         try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(requestFile))) {
122             PmdRequest request = (PmdRequest) in.readObject();
123             PmdExecutor pmdExecutor = new PmdExecutor(request);
124             pmdExecutor.run();
125             System.exit(0);
126         } catch (IOException | ClassNotFoundException | MavenReportException e) {
127             LOG.error(e.getMessage(), e);
128         }
129         System.exit(1);
130     }
131 
132     private final PmdRequest request;
133 
134     public PmdExecutor(PmdRequest request) {
135         this.request = Objects.requireNonNull(request);
136     }
137 
138     public PmdResult run() throws MavenReportException {
139         PMDConfiguration configuration = new PMDConfiguration();
140         LanguageVersion languageVersion = null;
141         Language language = configuration
142                 .getLanguageRegistry()
143                 .getLanguageById(request.getLanguage() != null ? request.getLanguage() : "java");
144         if (language == null) {
145             throw new MavenReportException("Unsupported language: " + request.getLanguage());
146         }
147         if (request.getLanguageVersion() != null) {
148             languageVersion = language.getVersion(request.getLanguageVersion());
149             if (languageVersion == null) {
150                 throw new MavenReportException("Unsupported targetJdk value '" + request.getLanguageVersion() + "'.");
151             }
152         } else {
153             languageVersion = language.getDefaultVersion();
154         }
155         LOG.debug("Using language " + languageVersion);
156         configuration.setDefaultLanguageVersion(languageVersion);
157 
158         if (request.getSourceEncoding() != null) {
159             configuration.setSourceEncoding(Charset.forName(request.getSourceEncoding()));
160         }
161 
162         configuration.prependAuxClasspath(request.getAuxClasspath());
163 
164         if (request.getSuppressMarker() != null) {
165             configuration.setSuppressMarker(request.getSuppressMarker());
166         }
167         if (request.getAnalysisCacheLocation() != null) {
168             configuration.setAnalysisCacheLocation(request.getAnalysisCacheLocation());
169             LOG.debug("Using analysis cache location: " + request.getAnalysisCacheLocation());
170         } else {
171             configuration.setIgnoreIncrementalAnalysis(true);
172         }
173 
174         configuration.setRuleSets(request.getRulesets());
175         configuration.setMinimumPriority(RulePriority.valueOf(request.getMinimumPriority()));
176         if (request.getBenchmarkOutputLocation() != null) {
177             TimeTracker.startGlobalTracking();
178         }
179         List<File> files = request.getFiles();
180 
181         Report report = null;
182 
183         if (request.getRulesets().isEmpty()) {
184             LOG.debug("Skipping PMD execution as no rulesets are defined.");
185         } else {
186             if (request.getBenchmarkOutputLocation() != null) {
187                 TimeTracker.startGlobalTracking();
188             }
189 
190             try {
191                 report = processFilesWithPMD(configuration, files);
192             } finally {
193                 if (request.getAuxClasspath() != null) {
194                     ClassLoader classLoader = configuration.getClassLoader();
195                     if (classLoader instanceof Closeable) {
196                         Closeable closeable = (Closeable) classLoader;
197                         try {
198                             closeable.close();
199                         } catch (IOException ex) {
200                             
201                         }
202                     }
203                 }
204                 if (request.getBenchmarkOutputLocation() != null) {
205                     TimingReport timingReport = TimeTracker.stopGlobalTracking();
206                     writeBenchmarkReport(
207                             timingReport, request.getBenchmarkOutputLocation(), request.getOutputEncoding());
208                 }
209             }
210         }
211 
212         if (report != null && !report.getProcessingErrors().isEmpty()) {
213             List<Report.ProcessingError> errors = report.getProcessingErrors();
214             if (!request.isSkipPmdError()) {
215                 LOG.error("PMD processing errors:");
216                 LOG.error(getErrorsAsString(errors, request.isDebugEnabled()));
217                 String msg = errors.size() > 1
218                         ? "Found " + errors.size() + " PMD processing errors"
219                         : "Found 1 PMD processing error";
220                 throw new MavenReportException(msg);
221             }
222             String message = errors.size() > 1
223                     ? "There are " + errors.size() + " PMD processing errors:"
224                     : "There is 1 PMD processing error:";
225             LOG.warn(message);
226             LOG.warn(getErrorsAsString(errors, request.isDebugEnabled()));
227         }
228 
229         report = removeExcludedViolations(report);
230         
231         
232         
233         try {
234             writeXmlReport(report);
235         } catch (IOException e) {
236             throw new MavenReportException("Failed to write XML report", e);
237         }
238 
239         
240         
241         
242         
243         String format = request.getFormat();
244         if (!"html".equals(format) && !"xml".equals(format)) {
245             try {
246                 writeFormattedReport(report);
247             } catch (IOException e) {
248                 throw new MavenReportException("Failed to write formatted " + format + " report", e);
249             }
250         }
251 
252         return new PmdResult(new File(request.getTargetDirectory(), "pmd.xml"), request.getOutputEncoding());
253     }
254 
255     
256 
257 
258 
259 
260     private String getErrorsAsString(List<Report.ProcessingError> errors, boolean withDetails) {
261         List<String> errorsAsString = new ArrayList<>(errors.size());
262         for (Report.ProcessingError error : errors) {
263             errorsAsString.add(error.getFileId().getAbsolutePath() + ": " + error.getMsg());
264             if (withDetails) {
265                 errorsAsString.add(error.getDetail());
266             }
267         }
268         return String.join(System.lineSeparator(), errorsAsString);
269     }
270 
271     private void writeBenchmarkReport(TimingReport timingReport, String benchmarkOutputLocation, String encoding) {
272         try (Writer writer = new OutputStreamWriter(new FileOutputStream(benchmarkOutputLocation), encoding)) {
273             final TimingReportRenderer renderer = new TextTimingReportRenderer();
274             renderer.render(timingReport, writer);
275         } catch (IOException e) {
276             LOG.error("Unable to generate benchmark file: {}", benchmarkOutputLocation, e);
277         }
278     }
279 
280     private Report processFilesWithPMD(PMDConfiguration pmdConfiguration, List<File> files)
281             throws MavenReportException {
282         Report report = null;
283         RuleSetLoader rulesetLoader =
284                 RuleSetLoader.fromPmdConfig(pmdConfiguration).warnDeprecated(true);
285         try {
286             
287             rulesetLoader.loadFromResources(pmdConfiguration.getRuleSetPaths());
288         } catch (RuleSetLoadException e1) {
289             throw new MavenReportException("The ruleset could not be loaded", e1);
290         }
291 
292         try (PmdAnalysis pmdAnalysis = PmdAnalysis.create(pmdConfiguration)) {
293             for (File file : files) {
294                 pmdAnalysis.files().addFile(file.toPath());
295             }
296             LOG.debug("Executing PMD...");
297             report = pmdAnalysis.performAnalysisAndCollectReport();
298             LOG.debug(
299                     "PMD finished. Found {} violations.", report.getViolations().size());
300         } catch (Exception e) {
301             String message = "Failure executing PMD: " + e.getLocalizedMessage();
302             if (!request.isSkipPmdError()) {
303                 throw new MavenReportException(message, e);
304             }
305             LOG.warn(message, e);
306         }
307         return report;
308     }
309 
310     
311 
312 
313 
314     private void writeXmlReport(Report report) throws IOException {
315         File targetFile = writeReport(report, new XMLRenderer(request.getOutputEncoding()));
316         if (request.isIncludeXmlInReports()) {
317             File outputDirectory = new File(request.getReportOutputDirectory());
318             if (!outputDirectory.exists() && !outputDirectory.mkdirs()) {
319                 throw new IOException("Couldn't create report output directory: " + outputDirectory);
320             }
321             FileUtils.copyFile(targetFile, new File(outputDirectory, "pmd.xml"));
322         }
323     }
324 
325     private File writeReport(Report report, Renderer renderer) throws IOException {
326         if (renderer == null) {
327             return null;
328         }
329 
330         File targetDir = new File(request.getTargetDirectory());
331         if (!targetDir.exists() && !targetDir.mkdirs()) {
332             throw new IOException("Couldn't create report target directory: " + targetDir);
333         }
334 
335         String extension = renderer.defaultFileExtension();
336         File targetFile = new File(targetDir, "pmd." + extension);
337         try (Writer writer =
338                 Files.newBufferedWriter(targetFile.toPath(), Charset.forName(request.getOutputEncoding()))) {
339             renderer.setWriter(writer);
340             renderer.start();
341             if (report != null) {
342                 renderer.renderFileReport(report);
343             }
344             renderer.end();
345             renderer.flush();
346         } catch (UnsupportedCharsetException | IllegalCharsetNameException ex) {
347             throw new UnsupportedEncodingException(ex.getMessage());
348         }
349 
350         return targetFile;
351     }
352 
353     
354 
355 
356     private void writeFormattedReport(Report report) throws IOException, MavenReportException {
357         Renderer renderer = createRenderer(request.getFormat(), request.getOutputEncoding());
358         writeReport(report, renderer);
359     }
360 
361     
362 
363 
364 
365 
366 
367 
368     public static Renderer createRenderer(String format, String outputEncoding) throws MavenReportException {
369         LOG.debug("Renderer requested: {}", format);
370         Renderer result = null;
371         if ("xml".equals(format)) {
372             result = new XMLRenderer(outputEncoding);
373         } else if ("txt".equals(format)) {
374             result = new TextRenderer();
375         } else if ("csv".equals(format)) {
376             result = new CSVRenderer();
377         } else if ("html".equals(format)) {
378             result = new HTMLRenderer();
379         } else if (!"".equals(format) && !"none".equals(format)) {
380             try {
381                 result = (Renderer) Class.forName(format).getConstructor().newInstance();
382             } catch (Exception e) {
383                 throw new MavenReportException(
384                         "Can't find PMD custom format " + format + ": "
385                                 + e.getClass().getName(),
386                         e);
387             }
388         }
389 
390         return result;
391     }
392 
393     private Report removeExcludedViolations(Report report) throws MavenReportException {
394         if (report == null) {
395             return null;
396         }
397 
398         ExcludeViolationsFromFile excludeFromFile = new ExcludeViolationsFromFile();
399 
400         try {
401             excludeFromFile.loadExcludeFromFailuresData(request.getExcludeFromFailureFile());
402         } catch (MojoExecutionException e) {
403             throw new MavenReportException("Unable to load exclusions", e);
404         }
405 
406         LOG.debug("Removing excluded violations. Using {} configured exclusions.", excludeFromFile.countExclusions());
407         int violationsBefore = report.getViolations().size();
408 
409         Report filtered =
410                 report.filterViolations(ruleViolation -> !excludeFromFile.isExcludedFromFailure(ruleViolation));
411 
412         int numberOfExcludedViolations =
413                 violationsBefore - filtered.getViolations().size();
414         LOG.debug("Excluded {} violations.", numberOfExcludedViolations);
415         return filtered;
416     }
417 }