View Javadoc
1   package org.apache.maven.plugin.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 org.apache.maven.plugin.AbstractMojo;
23  import org.apache.maven.plugin.MojoExecutionException;
24  import org.apache.maven.plugin.MojoFailureException;
25  import org.apache.maven.plugins.annotations.Component;
26  import org.apache.maven.plugins.annotations.Parameter;
27  import org.apache.maven.project.MavenProject;
28  import org.codehaus.plexus.util.StringUtils;
29  import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
30  
31  import java.io.File;
32  import java.io.IOException;
33  import java.util.ArrayList;
34  import java.util.List;
35  
36  /**
37   * Base class for mojos that check if there were any PMD violations.
38   *
39   * @author <a href="mailto:brett@apache.org">Brett Porter</a>
40   * @version $Id: AbstractPmdViolationCheckMojo.html 901148 2014-03-11 20:12:51Z dennisl $
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       * The project language, for determining whether to run the report.
59       */
60      @Parameter( property = "project.artifact.artifactHandler.language", required = true, readonly = true )
61      private String language;
62  
63      /**
64       * Whether to build an aggregated report at the root, or build individual reports.
65       *
66       * @since 2.2
67       */
68      @Parameter( property = "aggregate", defaultValue = "false" )
69      protected boolean aggregate;
70  
71      /**
72       * Print details of check failures to build output.
73       */
74      @Parameter( property = "pmd.verbose", defaultValue = "false" )
75      private boolean verbose;
76  
77      /**
78       * Print details of errors that cause build failure
79       *
80       * @since 3.0
81       */
82      @Parameter( property = "pmd.printFailingErrors", defaultValue = "false" )
83      private boolean printFailingErrors;
84  
85      /**
86       * File that lists classes and rules to be excluded from failures
87       * For PMD, this is a properties file
88       * For CPD, this is a text file that contains comma-separated lists of classes that are allowed to duplicate
89       *
90       * @since 3.0
91       */
92      @Parameter( property = "pmd.excludeFromFailureFile", defaultValue = "" )
93      private String excludeFromFailureFile;
94  
95      /**
96       * The project to analyze.
97       */
98      @Component
99      protected MavenProject project;
100 
101     protected void executeCheck( final String filename, final String tagName, final String key,
102                                  final int failurePriority )
103         throws MojoFailureException, MojoExecutionException
104     {
105         if ( aggregate && !project.isExecutionRoot() )
106         {
107             return;
108         }
109 
110         if ( "java".equals( language ) || aggregate )
111         {
112             if ( !StringUtils.isEmpty( excludeFromFailureFile ) )
113             {
114                 loadExcludeFromFailuresData( excludeFromFailureFile );
115             }
116             final File outputFile = new File( targetDirectory, filename );
117 
118             if ( outputFile.exists() )
119             {
120                 try
121                 {
122                     final ViolationDetails<D> violations = getViolations( outputFile, failurePriority );
123 
124                     final List<D> failures = violations.getFailureDetails();
125                     final List<D> warnings = violations.getWarningDetails();
126 
127                     if ( verbose )
128                     {
129                         printErrors( failures, warnings );
130                     }
131 
132                     final int failureCount = failures.size();
133                     final int warningCount = warnings.size();
134 
135                     final String message = getMessage( failureCount, warningCount, key, outputFile );
136 
137                     getLog().debug( "PMD failureCount: " + failureCount + ", warningCount: " + warningCount );
138 
139                     if ( failureCount > 0 && isFailOnViolation() )
140                     {
141                         throw new MojoFailureException( message );
142                     }
143 
144                     this.getLog().info( message );
145                 }
146                 catch ( final IOException e )
147                 {
148                     throw new MojoExecutionException( "Unable to read PMD results xml: " + outputFile.getAbsolutePath(),
149                                                       e );
150                 }
151                 catch ( final XmlPullParserException e )
152                 {
153                     throw new MojoExecutionException( "Unable to read PMD results xml: " + outputFile.getAbsolutePath(),
154                                                       e );
155                 }
156             }
157             else
158             {
159                 throw new MojoFailureException( "Unable to perform check, " + "unable to find " + outputFile );
160             }
161         }
162     }
163 
164     protected abstract void loadExcludeFromFailuresData( String excludeFromFailureFile )
165         throws MojoExecutionException;
166 
167     /**
168      * Method for collecting the violations found by the PMD tool
169      *
170      * @param analysisFile
171      * @param failurePriority
172      * @return an int that specifies the number of violations found
173      * @throws XmlPullParserException
174      * @throws IOException
175      */
176     private ViolationDetails<D> getViolations( final File analysisFile, final int failurePriority )
177         throws XmlPullParserException, IOException
178     {
179         final List<D> failures = new ArrayList<D>();
180         final List<D> warnings = new ArrayList<D>();
181 
182         final List<D> violations = getErrorDetails( analysisFile );
183 
184         for ( final D violation : violations )
185         {
186             final int priority = getPriority( violation );
187             if ( priority <= failurePriority && !isExcludedFromFailure( violation ) )
188             {
189                 failures.add( violation );
190                 if ( printFailingErrors )
191                 {
192                     printError( violation, "Failure" );
193                 }
194             }
195             else
196             {
197                 warnings.add( violation );
198             }
199         }
200 
201         final ViolationDetails<D> details = newViolationDetailsInstance();
202         details.setFailureDetails( failures );
203         details.setWarningDetails( warnings );
204         return details;
205     }
206 
207     protected abstract int getPriority( D errorDetail );
208 
209     protected abstract boolean isExcludedFromFailure( D errorDetail );
210 
211     protected abstract ViolationDetails<D> newViolationDetailsInstance();
212 
213     /**
214      * Prints the warnings and failures
215      *
216      * @param failures list of failures
217      * @param warnings list of warnings
218      */
219     protected void printErrors( final List<D> failures, final List<D> warnings )
220     {
221         for ( final D warning : warnings )
222         {
223             printError( warning, "Warning" );
224         }
225 
226         for ( final D failure : failures )
227         {
228             printError( failure, "Failure" );
229         }
230     }
231 
232     /**
233      * Gets the output message
234      *
235      * @param failureCount
236      * @param warningCount
237      * @param key
238      * @param outputFile
239      * @return
240      */
241     private String getMessage( final int failureCount, final int warningCount, final String key, final File outputFile )
242     {
243         final StringBuilder message = new StringBuilder( 256 );
244         if ( failureCount > 0 || warningCount > 0 )
245         {
246             if ( failureCount > 0 )
247             {
248                 message.append( "You have " ).append( failureCount ).append( " " ).append( key ).append( failureCount > 1 ? "s" : "" );
249             }
250 
251             if ( warningCount > 0 )
252             {
253                 if ( failureCount > 0 )
254                 {
255                     message.append( " and " );
256                 }
257                 else
258                 {
259                     message.append( "You have " );
260                 }
261                 message.append( warningCount ).append( " warning" ).append( warningCount > 1 ? "s" : "" );
262             }
263 
264             message.append( ". For more details see:" ).append( outputFile.getAbsolutePath() );
265         }
266         return message.toString();
267     }
268 
269     /**
270      * Formats the failure details and prints them as an INFO message
271      *
272      * @param item
273      */
274     protected abstract void printError( D item, String severity );
275 
276     /**
277      * Gets the attributes and text for the violation tag and puts them in a
278      * HashMap
279      *
280      * @param analisysFile
281      * @throws XmlPullParserException
282      * @throws IOException
283      */
284     protected abstract List<D> getErrorDetails( File analisysFile )
285         throws XmlPullParserException, IOException;
286 
287     public boolean isFailOnViolation()
288     {
289         return failOnViolation;
290     }
291 }