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       */
64      @Parameter( property = "aggregate", defaultValue = "false" )
65      protected boolean aggregate;
66  
67      /**
68       * Print details of check failures to build output.
69       */
70      @Parameter( property = "pmd.verbose", defaultValue = "false" )
71      private boolean verbose;
72  
73      /**
74       * Print details of errors that cause build failure
75       *
76       * @since 3.0
77       */
78      @Parameter( property = "pmd.printFailingErrors", defaultValue = "false" )
79      private boolean printFailingErrors;
80  
81      /**
82       * File that lists classes and rules to be excluded from failures.
83       * For PMD, this is a properties file. For CPD, this
84       * is a text file that contains comma-separated lists of classes
85       * that are allowed to duplicate.
86       *
87       * @since 3.0
88       */
89      @Parameter( property = "pmd.excludeFromFailureFile", defaultValue = "" )
90      private String excludeFromFailureFile;
91  
92      /**
93       * The maximum number of failures allowed before execution fails.
94       * Used in conjunction with {@code failOnViolation=true} and utilizes {@code failurePriority}.
95       * This value has no meaning if {@code failOnViolation=false}.
96       * If the number of failures is greater than this number, the build will be failed.
97       * If the number of failures is less than or equal to this value,
98       * then the build will not be failed.
99       *
100      * @since 3.10.0
101      */
102     @Parameter( property = "pmd.maxAllowedViolations", defaultValue = "0" )
103     private int maxAllowedViolations;
104 
105     /** Helper to exclude violations from the result. */
106     private final ExcludeFromFile<D> excludeFromFile;
107 
108     /**
109      * Initialize this abstact check mojo by giving the correct ExcludeFromFile helper.
110      * @param excludeFromFile the needed helper, for the specific violation type
111      */
112     protected AbstractPmdViolationCheckMojo( ExcludeFromFile<D> excludeFromFile )
113     {
114         this.excludeFromFile = excludeFromFile;
115     }
116 
117     /**
118      * The project to analyze.
119      */
120     @Parameter( defaultValue = "${project}", readonly = true, required = true )
121     protected MavenProject project;
122 
123     protected void executeCheck( final String filename, final String tagName, final String key,
124                                  final int failurePriority )
125         throws MojoFailureException, MojoExecutionException
126     {
127         getLog().info( "PMD version: " + AbstractPmdReport.getPmdVersion() );
128 
129         if ( aggregate && !project.isExecutionRoot() )
130         {
131             return;
132         }
133 
134         if ( "pom".equals( project.getPackaging() ) && !aggregate )
135         {
136             return;
137         }
138 
139         excludeFromFile.loadExcludeFromFailuresData( excludeFromFailureFile );
140         final File outputFile = new File( targetDirectory, filename );
141 
142         if ( outputFile.exists() )
143         {
144             try
145             {
146                 final ViolationDetails<D> violations = getViolations( outputFile, failurePriority );
147 
148                 final List<D> failures = violations.getFailureDetails();
149                 final List<D> warnings = violations.getWarningDetails();
150 
151                 if ( verbose )
152                 {
153                     printErrors( failures, warnings );
154                 }
155 
156                 final int failureCount = failures.size();
157                 final int warningCount = warnings.size();
158 
159                 final String message = getMessage( failureCount, warningCount, key, outputFile );
160 
161                 getLog().debug( "PMD failureCount: " + failureCount + ", warningCount: " + warningCount );
162 
163                 if ( failureCount > getMaxAllowedViolations() && isFailOnViolation() )
164                 {
165                     throw new MojoFailureException( message );
166                 }
167 
168                 this.getLog().info( message );
169 
170                 if ( failureCount > 0 && isFailOnViolation() && failureCount <= getMaxAllowedViolations() )
171                 {
172                     this.getLog().info( "The build is not failed, since " + getMaxAllowedViolations()
173                         + " violations are allowed (maxAllowedViolations)." );
174                 }
175             }
176             catch ( final IOException | XmlPullParserException e )
177             {
178                 throw new MojoExecutionException(
179                                                   "Unable to read PMD results xml: " + outputFile.getAbsolutePath(),
180                                                   e );
181             }
182         }
183         else
184         {
185             throw new MojoFailureException( "Unable to perform check, " + "unable to find " + outputFile );
186         }
187     }
188 
189     /**
190      * Method for collecting the violations found by the PMD tool
191      *
192      * @param analysisFile
193      * @param failurePriority
194      * @return an int that specifies the number of violations found
195      * @throws XmlPullParserException
196      * @throws IOException
197      */
198     private ViolationDetails<D> getViolations( final File analysisFile, final int failurePriority )
199         throws XmlPullParserException, IOException
200     {
201         final List<D> failures = new ArrayList<>();
202         final List<D> warnings = new ArrayList<>();
203 
204         final List<D> violations = getErrorDetails( analysisFile );
205 
206         for ( final D violation : violations )
207         {
208             final int priority = getPriority( violation );
209             if ( priority <= failurePriority && !excludeFromFile.isExcludedFromFailure( violation ) )
210             {
211                 failures.add( violation );
212                 if ( printFailingErrors )
213                 {
214                     printError( violation, "Failure" );
215                 }
216             }
217             else
218             {
219                 warnings.add( violation );
220             }
221         }
222 
223         final ViolationDetails<D> details = newViolationDetailsInstance();
224         details.setFailureDetails( failures );
225         details.setWarningDetails( warnings );
226         return details;
227     }
228 
229     protected abstract int getPriority( D errorDetail );
230 
231     protected abstract ViolationDetails<D> newViolationDetailsInstance();
232 
233     /**
234      * Prints the warnings and failures
235      *
236      * @param failures list of failures
237      * @param warnings list of warnings
238      */
239     protected void printErrors( final List<D> failures, final List<D> warnings )
240     {
241         for ( final D warning : warnings )
242         {
243             printError( warning, "Warning" );
244         }
245 
246         for ( final D failure : failures )
247         {
248             printError( failure, "Failure" );
249         }
250     }
251 
252     /**
253      * Gets the output message
254      *
255      * @param failureCount
256      * @param warningCount
257      * @param key
258      * @param outputFile
259      * @return
260      */
261     private String getMessage( final int failureCount, final int warningCount, final String key, final File outputFile )
262     {
263         final StringBuilder message = new StringBuilder( 256 );
264         if ( failureCount > 0 || warningCount > 0 )
265         {
266             if ( failureCount > 0 )
267             {
268                 message.append( "You have " ).append( failureCount ).append( " " ).append( key ).
269                   append( failureCount > 1 ? "s" : "" );
270             }
271 
272             if ( warningCount > 0 )
273             {
274                 if ( failureCount > 0 )
275                 {
276                     message.append( " and " );
277                 }
278                 else
279                 {
280                     message.append( "You have " );
281                 }
282                 message.append( warningCount ).append( " warning" ).append( warningCount > 1 ? "s" : "" );
283             }
284 
285             message.append( ". For more details see: " ).append( outputFile.getAbsolutePath() );
286         }
287         return message.toString();
288     }
289 
290     /**
291      * Formats the failure details and prints them as an INFO message
292      *
293      * @param item either a {@link org.apache.maven.plugins.pmd.model.Violation} from PMD
294      * or a {@link org.apache.maven.plugins.pmd.model.Duplication} from CPD
295      * @param severity the found issue is prefixed with the given severity, usually "Warning" or "Failure".
296      */
297     protected abstract void printError( D item, String severity );
298 
299     /**
300      * Gets the attributes and text for the violation tag and puts them in a HashMap
301      *
302      * @param analysisFile the xml output from PMD or CPD
303      * @return all PMD {@link org.apache.maven.plugins.pmd.model.Violation}s
304      * or CPD {@link org.apache.maven.plugins.pmd.model.Duplication}s.
305      * @throws XmlPullParserException if the analysis file contains invalid XML
306      * @throws IOException if the analysis file could be read
307      */
308     protected abstract List<D> getErrorDetails( File analysisFile )
309         throws XmlPullParserException, IOException;
310 
311     public boolean isFailOnViolation()
312     {
313         return failOnViolation;
314     }
315 
316     public Integer getMaxAllowedViolations()
317     {
318         return maxAllowedViolations;
319     }
320 }