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