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         if ( aggregate && !project.isExecutionRoot() )
128         {
129             return;
130         }
131 
132         if ( "pom".equals( project.getPackaging() ) && !aggregate )
133         {
134             return;
135         }
136 
137         excludeFromFile.loadExcludeFromFailuresData( excludeFromFailureFile );
138         final File outputFile = new File( targetDirectory, filename );
139 
140         if ( outputFile.exists() )
141         {
142             try
143             {
144                 final ViolationDetails<D> violations = getViolations( outputFile, failurePriority );
145 
146                 final List<D> failures = violations.getFailureDetails();
147                 final List<D> warnings = violations.getWarningDetails();
148 
149                 if ( verbose )
150                 {
151                     printErrors( failures, warnings );
152                 }
153 
154                 final int failureCount = failures.size();
155                 final int warningCount = warnings.size();
156 
157                 final String message = getMessage( failureCount, warningCount, key, outputFile );
158 
159                 getLog().debug( "PMD failureCount: " + failureCount + ", warningCount: " + warningCount );
160 
161                 if ( failureCount > getMaxAllowedViolations() && isFailOnViolation() )
162                 {
163                     throw new MojoFailureException( message );
164                 }
165 
166                 this.getLog().info( message );
167 
168                 if ( failureCount > 0 && isFailOnViolation() && failureCount <= getMaxAllowedViolations() )
169                 {
170                     this.getLog().info( "The build is not failed, since " + getMaxAllowedViolations()
171                         + " violations are allowed (maxAllowedViolations)." );
172                 }
173             }
174             catch ( final IOException e )
175             {
176                 throw new MojoExecutionException(
177                                                   "Unable to read PMD results xml: " + outputFile.getAbsolutePath(),
178                                                   e );
179             }
180             catch ( final XmlPullParserException e )
181             {
182                 throw new MojoExecutionException(
183                                                   "Unable to read PMD results xml: " + outputFile.getAbsolutePath(),
184                                                   e );
185             }
186         }
187         else
188         {
189             throw new MojoFailureException( "Unable to perform check, " + "unable to find " + outputFile );
190         }
191     }
192 
193     /**
194      * Method for collecting the violations found by the PMD tool
195      *
196      * @param analysisFile
197      * @param failurePriority
198      * @return an int that specifies the number of violations found
199      * @throws XmlPullParserException
200      * @throws IOException
201      */
202     private ViolationDetails<D> getViolations( final File analysisFile, final int failurePriority )
203         throws XmlPullParserException, IOException
204     {
205         final List<D> failures = new ArrayList<>();
206         final List<D> warnings = new ArrayList<>();
207 
208         final List<D> violations = getErrorDetails( analysisFile );
209 
210         for ( final D violation : violations )
211         {
212             final int priority = getPriority( violation );
213             if ( priority <= failurePriority && !excludeFromFile.isExcludedFromFailure( violation ) )
214             {
215                 failures.add( violation );
216                 if ( printFailingErrors )
217                 {
218                     printError( violation, "Failure" );
219                 }
220             }
221             else
222             {
223                 warnings.add( violation );
224             }
225         }
226 
227         final ViolationDetails<D> details = newViolationDetailsInstance();
228         details.setFailureDetails( failures );
229         details.setWarningDetails( warnings );
230         return details;
231     }
232 
233     protected abstract int getPriority( D errorDetail );
234 
235     protected abstract ViolationDetails<D> newViolationDetailsInstance();
236 
237     /**
238      * Prints the warnings and failures
239      *
240      * @param failures list of failures
241      * @param warnings list of warnings
242      */
243     protected void printErrors( final List<D> failures, final List<D> warnings )
244     {
245         for ( final D warning : warnings )
246         {
247             printError( warning, "Warning" );
248         }
249 
250         for ( final D failure : failures )
251         {
252             printError( failure, "Failure" );
253         }
254     }
255 
256     /**
257      * Gets the output message
258      *
259      * @param failureCount
260      * @param warningCount
261      * @param key
262      * @param outputFile
263      * @return
264      */
265     private String getMessage( final int failureCount, final int warningCount, final String key, final File outputFile )
266     {
267         final StringBuilder message = new StringBuilder( 256 );
268         if ( failureCount > 0 || warningCount > 0 )
269         {
270             if ( failureCount > 0 )
271             {
272                 message.append( "You have " ).append( failureCount ).append( " " ).append( key ).
273                   append( failureCount > 1 ? "s" : "" );
274             }
275 
276             if ( warningCount > 0 )
277             {
278                 if ( failureCount > 0 )
279                 {
280                     message.append( " and " );
281                 }
282                 else
283                 {
284                     message.append( "You have " );
285                 }
286                 message.append( warningCount ).append( " warning" ).append( warningCount > 1 ? "s" : "" );
287             }
288 
289             message.append( ". For more details see: " ).append( outputFile.getAbsolutePath() );
290         }
291         return message.toString();
292     }
293 
294     /**
295      * Formats the failure details and prints them as an INFO message
296      *
297      * @param item either a {@link org.apache.maven.plugins.pmd.model.Violation} from PMD
298      * or a {@link org.apache.maven.plugins.pmd.model.Duplication} from CPD
299      * @param severity the found issue is prefixed with the given severity, usually "Warning" or "Failure".
300      */
301     protected abstract void printError( D item, String severity );
302 
303     /**
304      * Gets the attributes and text for the violation tag and puts them in a HashMap
305      *
306      * @param analysisFile the xml output from PMD or CPD
307      * @return all PMD {@link org.apache.maven.plugins.pmd.model.Violation}s
308      * or CPD {@link org.apache.maven.plugins.pmd.model.Duplication}s.
309      * @throws XmlPullParserException if the analysis file contains invalid XML
310      * @throws IOException if the analysis file could be read
311      */
312     protected abstract List<D> getErrorDetails( File analysisFile )
313         throws XmlPullParserException, IOException;
314 
315     public boolean isFailOnViolation()
316     {
317         return failOnViolation;
318     }
319 
320     public Integer getMaxAllowedViolations()
321     {
322         return maxAllowedViolations;
323     }
324 }