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