View Javadoc
1   package org.apache.maven.plugins.pmd;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.File;
23  import java.io.IOException;
24  import java.util.ArrayList;
25  import java.util.List;
26  
27  import org.apache.maven.plugin.AbstractMojo;
28  import org.apache.maven.plugin.MojoExecutionException;
29  import org.apache.maven.plugin.MojoFailureException;
30  import org.apache.maven.plugins.annotations.Parameter;
31  import org.apache.maven.project.MavenProject;
32  import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
33  
34  /**
35   * Base class for mojos that check if there were any PMD violations.
36   *
37   * @param <D> type of the check, e.g. {@link org.apache.maven.plugins.pmd.model.Violation}
38   * or {@link org.apache.maven.plugins.pmd.model.Duplication}.
39   * @author <a href="mailto:brett@apache.org">Brett Porter</a>
40   * @version $Id$
41   */
42  public abstract class AbstractPmdViolationCheckMojo<D>
43      extends AbstractMojo
44  {
45      /**
46       * The location of the XML report to check, as generated by the PMD report.
47       */
48      @Parameter( property = "project.build.directory", required = true )
49      private File targetDirectory;
50  
51      /**
52       * Whether to fail the build if the validation check fails.
53       * The properties {@code failurePriority} and {@code maxAllowedViolations} control
54       * under which conditions exactly the build should be failed.
55       */
56      @Parameter( property = "pmd.failOnViolation", defaultValue = "true", required = true )
57      protected boolean failOnViolation;
58  
59      /**
60       * Whether to build an aggregated report at the root, or build individual reports.
61       *
62       * @since 2.2
63       * @deprecated since 3.15.0 Use the goal <code>pmd:aggregate-check</code> or
64       * <code>pmd:aggregate-cpd-check</code> instead.
65       */
66      @Parameter( property = "aggregate", defaultValue = "false" )
67      @Deprecated
68      protected boolean aggregate;
69  
70      /**
71       * Print details of check failures to build output.
72       */
73      @Parameter( property = "pmd.verbose", defaultValue = "false" )
74      private boolean verbose;
75  
76      /**
77       * Print details of errors that cause build failure
78       *
79       * @since 3.0
80       */
81      @Parameter( property = "pmd.printFailingErrors", defaultValue = "false" )
82      private boolean printFailingErrors;
83  
84      /**
85       * File that lists classes and rules to be excluded from failures.
86       * For PMD, this is a properties file. For CPD, this
87       * is a text file that contains comma-separated lists of classes
88       * that are allowed to duplicate.
89       *
90       * @since 3.0
91       */
92      @Parameter( property = "pmd.excludeFromFailureFile", defaultValue = "" )
93      private String excludeFromFailureFile;
94  
95      /**
96       * The maximum number of failures allowed before execution fails.
97       * Used in conjunction with {@code failOnViolation=true} and utilizes {@code failurePriority}.
98       * This value has no meaning if {@code failOnViolation=false}.
99       * If the number of failures is greater than this number, the build will be failed.
100      * If the number of failures is less than or equal to this value,
101      * then the build will not be failed.
102      *
103      * @since 3.10.0
104      */
105     @Parameter( property = "pmd.maxAllowedViolations", defaultValue = "0" )
106     private int maxAllowedViolations;
107 
108     /** Helper to exclude violations from the result. */
109     private final ExcludeFromFile<D> excludeFromFile;
110 
111     /**
112      * Initialize this abstact check mojo by giving the correct ExcludeFromFile helper.
113      * @param excludeFromFile the needed helper, for the specific violation type
114      */
115     protected AbstractPmdViolationCheckMojo( ExcludeFromFile<D> excludeFromFile )
116     {
117         this.excludeFromFile = excludeFromFile;
118     }
119 
120     /**
121      * The project to analyze.
122      */
123     @Parameter( defaultValue = "${project}", readonly = true, required = true )
124     protected MavenProject project;
125 
126     protected void executeCheck( final String filename, final String tagName, final String key,
127                                  final int failurePriority )
128         throws MojoFailureException, MojoExecutionException
129     {
130         if ( aggregate && !project.isExecutionRoot() )
131         {
132             return;
133         }
134 
135         if ( !isAggregator() && "pom".equalsIgnoreCase( project.getPackaging() ) )
136         {
137             return;
138         }
139 
140         excludeFromFile.loadExcludeFromFailuresData( excludeFromFailureFile );
141         final File outputFile = new File( targetDirectory, filename );
142 
143         if ( outputFile.exists() )
144         {
145             getLog().info( "PMD version: " + AbstractPmdReport.getPmdVersion() );
146 
147             try
148             {
149                 final ViolationDetails<D> violations = getViolations( outputFile, failurePriority );
150 
151                 final List<D> failures = violations.getFailureDetails();
152                 final List<D> warnings = violations.getWarningDetails();
153 
154                 if ( verbose )
155                 {
156                     printErrors( failures, warnings );
157                 }
158 
159                 final int failureCount = failures.size();
160                 final int warningCount = warnings.size();
161 
162                 final String message = getMessage( failureCount, warningCount, key, outputFile );
163 
164                 getLog().debug( "PMD failureCount: " + failureCount + ", warningCount: " + warningCount );
165 
166                 if ( failureCount > getMaxAllowedViolations() && isFailOnViolation() )
167                 {
168                     throw new MojoFailureException( message );
169                 }
170 
171                 this.getLog().info( message );
172 
173                 if ( failureCount > 0 && isFailOnViolation() && failureCount <= getMaxAllowedViolations() )
174                 {
175                     this.getLog().info( "The build is not failed, since " + getMaxAllowedViolations()
176                         + " violations are allowed (maxAllowedViolations)." );
177                 }
178             }
179             catch ( final IOException | XmlPullParserException e )
180             {
181                 throw new MojoExecutionException(
182                                                   "Unable to read PMD results xml: " + outputFile.getAbsolutePath(),
183                                                   e );
184             }
185         }
186         else
187         {
188             throw new MojoFailureException( "Unable to perform check, " + "unable to find " + outputFile );
189         }
190     }
191 
192     /**
193      * Method for collecting the violations found by the PMD tool
194      *
195      * @param analysisFile
196      * @param failurePriority
197      * @return an int that specifies the number of violations found
198      * @throws XmlPullParserException
199      * @throws IOException
200      */
201     private ViolationDetails<D> getViolations( final File analysisFile, final int failurePriority )
202         throws XmlPullParserException, IOException
203     {
204         final List<D> failures = new ArrayList<>();
205         final List<D> warnings = new ArrayList<>();
206 
207         final List<D> violations = getErrorDetails( analysisFile );
208 
209         for ( final D violation : violations )
210         {
211             final int priority = getPriority( violation );
212             if ( priority <= failurePriority && !excludeFromFile.isExcludedFromFailure( violation ) )
213             {
214                 failures.add( violation );
215                 if ( printFailingErrors )
216                 {
217                     printError( violation, "Failure" );
218                 }
219             }
220             else
221             {
222                 warnings.add( violation );
223             }
224         }
225 
226         final ViolationDetails<D> details = newViolationDetailsInstance();
227         details.setFailureDetails( failures );
228         details.setWarningDetails( warnings );
229         return details;
230     }
231 
232     protected abstract int getPriority( D errorDetail );
233 
234     protected abstract ViolationDetails<D> newViolationDetailsInstance();
235 
236     /**
237      * Prints the warnings and failures
238      *
239      * @param failures list of failures
240      * @param warnings list of warnings
241      */
242     protected void printErrors( final List<D> failures, final List<D> warnings )
243     {
244         for ( final D warning : warnings )
245         {
246             printError( warning, "Warning" );
247         }
248 
249         for ( final D failure : failures )
250         {
251             printError( failure, "Failure" );
252         }
253     }
254 
255     /**
256      * Gets the output message
257      *
258      * @param failureCount
259      * @param warningCount
260      * @param key
261      * @param outputFile
262      * @return
263      */
264     private String getMessage( final int failureCount, final int warningCount, final String key, final File outputFile )
265     {
266         final StringBuilder message = new StringBuilder( 256 );
267         if ( failureCount > 0 || warningCount > 0 )
268         {
269             if ( failureCount > 0 )
270             {
271                 message.append( "You have " ).append( failureCount ).append( " " ).append( key ).
272                   append( failureCount > 1 ? "s" : "" );
273             }
274 
275             if ( warningCount > 0 )
276             {
277                 if ( failureCount > 0 )
278                 {
279                     message.append( " and " );
280                 }
281                 else
282                 {
283                     message.append( "You have " );
284                 }
285                 message.append( warningCount ).append( " warning" ).append( warningCount > 1 ? "s" : "" );
286             }
287 
288             message.append( ". For more details see: " ).append( outputFile.getAbsolutePath() );
289         }
290         return message.toString();
291     }
292 
293     /**
294      * Formats the failure details and prints them as an INFO message
295      *
296      * @param item either a {@link org.apache.maven.plugins.pmd.model.Violation} from PMD
297      * or a {@link org.apache.maven.plugins.pmd.model.Duplication} from CPD
298      * @param severity the found issue is prefixed with the given severity, usually "Warning" or "Failure".
299      */
300     protected abstract void printError( D item, String severity );
301 
302     /**
303      * Gets the attributes and text for the violation tag and puts them in a HashMap
304      *
305      * @param analysisFile the xml output from PMD or CPD
306      * @return all PMD {@link org.apache.maven.plugins.pmd.model.Violation}s
307      * or CPD {@link org.apache.maven.plugins.pmd.model.Duplication}s.
308      * @throws XmlPullParserException if the analysis file contains invalid XML
309      * @throws IOException if the analysis file could be read
310      */
311     protected abstract List<D> getErrorDetails( File analysisFile )
312         throws XmlPullParserException, IOException;
313 
314     public boolean isFailOnViolation()
315     {
316         return failOnViolation;
317     }
318 
319     public Integer getMaxAllowedViolations()
320     {
321         return maxAllowedViolations;
322     }
323 
324     protected boolean isAggregator()
325     {
326         // returning here aggregate for backwards compatibility
327         return aggregate;
328     }
329 }