1 package org.apache.maven.plugins.pmd.exec;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.io.Closeable;
23 import java.io.File;
24 import java.io.FileInputStream;
25 import java.io.FileOutputStream;
26 import java.io.IOException;
27 import java.io.ObjectInputStream;
28 import java.io.ObjectOutputStream;
29 import java.io.OutputStreamWriter;
30 import java.io.Writer;
31 import java.util.ArrayList;
32 import java.util.Arrays;
33 import java.util.Iterator;
34 import java.util.List;
35 import java.util.Objects;
36
37 import org.apache.commons.io.IOUtils;
38 import org.apache.commons.lang3.StringUtils;
39 import org.apache.maven.plugin.MojoExecutionException;
40 import org.apache.maven.plugins.pmd.ExcludeViolationsFromFile;
41 import org.apache.maven.plugins.pmd.PmdCollectingRenderer;
42 import org.apache.maven.reporting.MavenReportException;
43 import org.codehaus.plexus.util.FileUtils;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
46
47 import net.sourceforge.pmd.PMD;
48 import net.sourceforge.pmd.PMDConfiguration;
49 import net.sourceforge.pmd.Report;
50 import net.sourceforge.pmd.RuleContext;
51 import net.sourceforge.pmd.RulePriority;
52 import net.sourceforge.pmd.RuleSetFactory;
53 import net.sourceforge.pmd.RuleSetNotFoundException;
54 import net.sourceforge.pmd.RuleViolation;
55 import net.sourceforge.pmd.RulesetsFactoryUtils;
56 import net.sourceforge.pmd.benchmark.TextTimingReportRenderer;
57 import net.sourceforge.pmd.benchmark.TimeTracker;
58 import net.sourceforge.pmd.benchmark.TimingReport;
59 import net.sourceforge.pmd.benchmark.TimingReportRenderer;
60 import net.sourceforge.pmd.lang.Language;
61 import net.sourceforge.pmd.lang.LanguageRegistry;
62 import net.sourceforge.pmd.lang.LanguageVersion;
63 import net.sourceforge.pmd.renderers.CSVRenderer;
64 import net.sourceforge.pmd.renderers.HTMLRenderer;
65 import net.sourceforge.pmd.renderers.Renderer;
66 import net.sourceforge.pmd.renderers.TextRenderer;
67 import net.sourceforge.pmd.renderers.XMLRenderer;
68 import net.sourceforge.pmd.util.datasource.DataSource;
69 import net.sourceforge.pmd.util.datasource.FileDataSource;
70
71
72
73
74 public class PmdExecutor extends Executor
75 {
76 private static final Logger LOG = LoggerFactory.getLogger( PmdExecutor.class );
77
78 public static PmdResult execute( PmdRequest request ) throws MavenReportException
79 {
80 if ( request.getJavaExecutable() != null )
81 {
82 return fork( request );
83 }
84
85
86 ClassLoader origLoader = Thread.currentThread().getContextClassLoader();
87 try
88 {
89 Thread.currentThread().setContextClassLoader( PmdExecutor.class.getClassLoader() );
90 PmdExecutor executor = new PmdExecutor( request );
91 return executor.run();
92 }
93 finally
94 {
95 Thread.currentThread().setContextClassLoader( origLoader );
96 }
97 }
98
99 private static PmdResult fork( PmdRequest request )
100 throws MavenReportException
101 {
102 File basePmdDir = new File ( request.getTargetDirectory(), "pmd" );
103 basePmdDir.mkdirs();
104 File pmdRequestFile = new File( basePmdDir, "pmdrequest.bin" );
105 try ( ObjectOutputStream out = new ObjectOutputStream( new FileOutputStream( pmdRequestFile ) ) )
106 {
107 out.writeObject( request );
108 }
109 catch ( IOException e )
110 {
111 throw new MavenReportException( e.getMessage(), e );
112 }
113
114 String classpath = buildClasspath();
115 ProcessBuilder pb = new ProcessBuilder();
116
117 pb.environment().put( "CLASSPATH", classpath );
118 pb.command().add( request.getJavaExecutable() );
119 pb.command().add( PmdExecutor.class.getName() );
120 pb.command().add( pmdRequestFile.getAbsolutePath() );
121
122 LOG.debug( "Executing: CLASSPATH={}, command={}", classpath, pb.command() );
123 try
124 {
125 final Process p = pb.start();
126
127
128 ProcessStreamHandler.start( p.getInputStream(), System.out );
129 ProcessStreamHandler.start( p.getErrorStream(), System.err );
130 int exit = p.waitFor();
131 LOG.debug( "PmdExecutor exit code: {}", exit );
132 if ( exit != 0 )
133 {
134 throw new MavenReportException( "PmdExecutor exited with exit code " + exit );
135 }
136 return new PmdResult( new File( request.getTargetDirectory(), "pmd.xml" ), request.getOutputEncoding() );
137 }
138 catch ( IOException e )
139 {
140 throw new MavenReportException( e.getMessage(), e );
141 }
142 catch ( InterruptedException e )
143 {
144 Thread.currentThread().interrupt();
145 throw new MavenReportException( e.getMessage(), e );
146 }
147 }
148
149
150
151
152
153
154
155
156
157
158
159
160 public static void main( String[] args )
161 {
162 File requestFile = new File( args[0] );
163 try ( ObjectInputStream in = new ObjectInputStream( new FileInputStream( requestFile ) ) )
164 {
165 PmdRequest request = (PmdRequest) in.readObject();
166 PmdExecutor pmdExecutor = new PmdExecutor( request );
167 pmdExecutor.setupLogLevel( request.getLogLevel() );
168 pmdExecutor.run();
169 System.exit( 0 );
170 }
171 catch ( IOException | ClassNotFoundException | MavenReportException e )
172 {
173 LOG.error( e.getMessage(), e );
174 }
175 System.exit( 1 );
176 }
177
178 private final PmdRequest request;
179
180 public PmdExecutor( PmdRequest request )
181 {
182 this.request = Objects.requireNonNull( request );
183 }
184
185 private PmdResult run() throws MavenReportException
186 {
187 setupPmdLogging( request.isShowPmdLog(), request.isColorizedLog(), request.getLogLevel() );
188
189 PMDConfiguration configuration = new PMDConfiguration();
190 LanguageVersion languageVersion = null;
191 Language language = LanguageRegistry
192 .findLanguageByTerseName( request.getLanguage() != null ? request.getLanguage() : "java" );
193 if ( language == null )
194 {
195 throw new MavenReportException( "Unsupported language: " + request.getLanguage() );
196 }
197 if ( request.getLanguageVersion() != null )
198 {
199 languageVersion = language.getVersion( request.getLanguageVersion() );
200 if ( languageVersion == null )
201 {
202 throw new MavenReportException( "Unsupported targetJdk value '" + request.getLanguageVersion() + "'." );
203 }
204 }
205 else
206 {
207 languageVersion = language.getDefaultVersion();
208 }
209 LOG.debug( "Using language " + languageVersion );
210 configuration.setDefaultLanguageVersion( languageVersion );
211
212 try
213 {
214 configuration.prependClasspath( request.getAuxClasspath() );
215 }
216 catch ( IOException e )
217 {
218 throw new MavenReportException( e.getMessage(), e );
219 }
220 if ( request.getSuppressMarker() != null )
221 {
222 configuration.setSuppressMarker( request.getSuppressMarker() );
223 }
224 if ( request.getAnalysisCacheLocation() != null )
225 {
226 configuration.setAnalysisCacheLocation( request.getAnalysisCacheLocation() );
227 LOG.debug( "Using analysis cache location: " + request.getAnalysisCacheLocation() );
228 }
229 else
230 {
231 configuration.setIgnoreIncrementalAnalysis( true );
232 }
233
234 configuration.setRuleSets( request.getRulesets() );
235 if ( request.getBenchmarkOutputLocation() != null )
236 {
237 configuration.setBenchmark( true );
238 }
239 List<File> files = request.getFiles();
240 List<DataSource> dataSources = new ArrayList<>( files.size() );
241 for ( File f : files )
242 {
243 dataSources.add( new FileDataSource( f ) );
244 }
245
246 PmdCollectingRenderer renderer = new PmdCollectingRenderer();
247
248 if ( StringUtils.isBlank( request.getRulesets() ) )
249 {
250 LOG.debug( "Skipping PMD execution as no rulesets are defined." );
251 }
252 else
253 {
254 if ( request.getBenchmarkOutputLocation() != null )
255 {
256 TimeTracker.startGlobalTracking();
257 }
258
259 try
260 {
261 processFilesWithPMD( configuration, dataSources, renderer );
262 }
263 finally
264 {
265 if ( request.getAuxClasspath() != null )
266 {
267 ClassLoader classLoader = configuration.getClassLoader();
268 if ( classLoader instanceof Closeable )
269 {
270 IOUtils.closeQuietly( (Closeable) classLoader );
271 }
272 }
273 if ( request.getBenchmarkOutputLocation() != null )
274 {
275 TimingReport timingReport = TimeTracker.stopGlobalTracking();
276 writeBenchmarkReport( timingReport, request.getBenchmarkOutputLocation(),
277 request.getOutputEncoding() );
278 }
279 }
280 }
281
282 if ( renderer.hasErrors() )
283 {
284 if ( !request.isSkipPmdError() )
285 {
286 LOG.error( "PMD processing errors:" );
287 LOG.error( renderer.getErrorsAsString( request.isDebugEnabled() ) );
288 throw new MavenReportException( "Found " + renderer.getErrors().size() + " PMD processing errors" );
289 }
290 LOG.warn( "There are {} PMD processing errors:", renderer.getErrors().size() );
291 LOG.warn( renderer.getErrorsAsString( request.isDebugEnabled() ) );
292 }
293
294 removeExcludedViolations( renderer.getViolations() );
295
296 Report report = renderer.asReport();
297
298
299
300 writeXmlReport( report );
301
302
303
304
305
306 String format = request.getFormat();
307 if ( !"html".equals( format ) && !"xml".equals( format ) )
308 {
309 writeFormattedReport( report );
310 }
311
312 return new PmdResult( new File( request.getTargetDirectory(), "pmd.xml" ), request.getOutputEncoding() );
313 }
314
315 private void writeBenchmarkReport( TimingReport timingReport, String benchmarkOutputLocation, String encoding )
316 {
317 try ( Writer writer = new OutputStreamWriter( new FileOutputStream( benchmarkOutputLocation ), encoding ) )
318 {
319 final TimingReportRenderer renderer = new TextTimingReportRenderer();
320 renderer.render( timingReport, writer );
321 }
322 catch ( IOException e )
323 {
324 LOG.error( "Unable to generate benchmark file: {}", benchmarkOutputLocation, e );
325 }
326 }
327
328 private void processFilesWithPMD( PMDConfiguration pmdConfiguration, List<DataSource> dataSources,
329 PmdCollectingRenderer renderer ) throws MavenReportException
330 {
331 RuleSetFactory ruleSetFactory = RulesetsFactoryUtils.createFactory(
332 RulePriority.valueOf( request.getMinimumPriority() ), true, true );
333 try
334 {
335
336 ruleSetFactory.createRuleSets( pmdConfiguration.getRuleSets() );
337 }
338 catch ( RuleSetNotFoundException e1 )
339 {
340 throw new MavenReportException( "The ruleset could not be loaded", e1 );
341 }
342
343 try
344 {
345 LOG.debug( "Executing PMD..." );
346 RuleContext ruleContext = new RuleContext();
347 PMD.processFiles( pmdConfiguration, ruleSetFactory, dataSources, ruleContext,
348 Arrays.<Renderer>asList( renderer ) );
349
350 LOG.debug( "PMD finished. Found {} violations.", renderer.getViolations().size() );
351 }
352 catch ( Exception e )
353 {
354 String message = "Failure executing PMD: " + e.getLocalizedMessage();
355 if ( !request.isSkipPmdError() )
356 {
357 throw new MavenReportException( message, e );
358 }
359 LOG.warn( message, e );
360 }
361 }
362
363
364
365
366
367
368
369
370 private void writeXmlReport( Report report ) throws MavenReportException
371 {
372 File targetFile = writeReport( report, new XMLRenderer( request.getOutputEncoding() ), "xml" );
373 if ( request.isIncludeXmlInSite() )
374 {
375 File siteDir = new File( request.getReportOutputDirectory() );
376 siteDir.mkdirs();
377 try
378 {
379 FileUtils.copyFile( targetFile, new File( siteDir, "pmd.xml" ) );
380 }
381 catch ( IOException e )
382 {
383 throw new MavenReportException( e.getMessage(), e );
384 }
385 }
386 }
387
388 private File writeReport( Report report, Renderer r, String extension ) throws MavenReportException
389 {
390 if ( r == null )
391 {
392 return null;
393 }
394
395 File targetDir = new File( request.getTargetDirectory() );
396 targetDir.mkdirs();
397 File targetFile = new File( targetDir, "pmd." + extension );
398 try ( Writer writer = new OutputStreamWriter( new FileOutputStream( targetFile ),
399 request.getOutputEncoding() ) )
400 {
401 r.setWriter( writer );
402 r.start();
403 r.renderFileReport( report );
404 r.end();
405 r.flush();
406 }
407 catch ( IOException ioe )
408 {
409 throw new MavenReportException( ioe.getMessage(), ioe );
410 }
411
412 return targetFile;
413 }
414
415
416
417
418
419
420
421 private void writeFormattedReport( Report report )
422 throws MavenReportException
423 {
424 Renderer renderer = createRenderer( request.getFormat(), request.getOutputEncoding() );
425 writeReport( report, renderer, request.getFormat() );
426 }
427
428
429
430
431
432
433
434
435 public static Renderer createRenderer( String format, String outputEncoding ) throws MavenReportException
436 {
437 Renderer result = null;
438 if ( "xml".equals( format ) )
439 {
440 result = new XMLRenderer( outputEncoding );
441 }
442 else if ( "txt".equals( format ) )
443 {
444 result = new TextRenderer();
445 }
446 else if ( "csv".equals( format ) )
447 {
448 result = new CSVRenderer();
449 }
450 else if ( "html".equals( format ) )
451 {
452 result = new HTMLRenderer();
453 }
454 else if ( !"".equals( format ) && !"none".equals( format ) )
455 {
456 try
457 {
458 result = (Renderer) Class.forName( format ).getConstructor().newInstance();
459 }
460 catch ( Exception e )
461 {
462 throw new MavenReportException(
463 "Can't find PMD custom format " + format + ": " + e.getClass().getName(), e );
464 }
465 }
466
467 return result;
468 }
469
470 private void removeExcludedViolations( List<RuleViolation> violations )
471 throws MavenReportException
472 {
473 ExcludeViolationsFromFile excludeFromFile = new ExcludeViolationsFromFile();
474
475 try
476 {
477 excludeFromFile.loadExcludeFromFailuresData( request.getExcludeFromFailureFile() );
478 }
479 catch ( MojoExecutionException e )
480 {
481 throw new MavenReportException( "Unable to load exclusions", e );
482 }
483
484 LOG.debug( "Removing excluded violations. Using {} configured exclusions.",
485 excludeFromFile.countExclusions() );
486 int violationsBefore = violations.size();
487
488 Iterator<RuleViolation> iterator = violations.iterator();
489 while ( iterator.hasNext() )
490 {
491 RuleViolation rv = iterator.next();
492 if ( excludeFromFile.isExcludedFromFailure( rv ) )
493 {
494 iterator.remove();
495 }
496 }
497
498 int numberOfExcludedViolations = violationsBefore - violations.size();
499 LOG.debug( "Excluded {} violations.", numberOfExcludedViolations );
500 }
501 }