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.Parameter;
26  import org.apache.maven.project.MavenProject;
27  import org.codehaus.plexus.util.StringUtils;
28  import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
29  
30  import java.io.File;
31  import java.io.IOException;
32  import java.util.ArrayList;
33  import java.util.List;
34  
35  /**
36   * Base class for mojos that check if there were any PMD violations.
37   * @param <D> type of the check, e.g. {@link Violation} or {@link Duplication}.
38   *
39   * @author <a href="mailto:brett@apache.org">Brett Porter</a>
40   * @version $Id: AbstractPmdViolationCheckMojo.html 929258 2014-11-14 21:15:04Z mfriedenhagen $
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      @Parameter( defaultValue = "${project}", readonly = true, required = true )
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 )
249                         .append( failureCount > 1 ? "s" : "" );
250             }
251 
252             if ( warningCount > 0 )
253             {
254                 if ( failureCount > 0 )
255                 {
256                     message.append( " and " );
257                 }
258                 else
259                 {
260                     message.append( "You have " );
261                 }
262                 message.append( warningCount ).append( " warning" ).append( warningCount > 1 ? "s" : "" );
263             }
264 
265             message.append( ". For more details see:" ).append( outputFile.getAbsolutePath() );
266         }
267         return message.toString();
268     }
269 
270     /**
271      * Formats the failure details and prints them as an INFO message
272      *
273      * @param item
274      */
275     protected abstract void printError( D item, String severity );
276 
277     /**
278      * Gets the attributes and text for the violation tag and puts them in a
279      * HashMap
280      *
281      * @param analisysFile
282      * @throws XmlPullParserException
283      * @throws IOException
284      */
285     protected abstract List<D> getErrorDetails( File analisysFile )
286         throws XmlPullParserException, IOException;
287 
288     public boolean isFailOnViolation()
289     {
290         return failOnViolation;
291     }
292 }