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