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 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.StringUtils;
33  import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
34  
35  /**
36   * Base class for mojos that check if there were any PMD violations.
37   *
38   * @param <D> type of the check, e.g. {@link Violation} or {@link Duplication}.
39   * @author <a href="mailto:brett@apache.org">Brett Porter</a>
40   * @version $Id: AbstractPmdViolationCheckMojo.html 938498 2015-01-31 17:43:24Z michaelo $
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 For PMD, this is a properties file For CPD, this
87       * is a text file that contains comma-separated lists of classes that are allowed to duplicate
88       *
89       * @since 3.0
90       */
91      @Parameter( property = "pmd.excludeFromFailureFile", defaultValue = "" )
92      private String excludeFromFailureFile;
93  
94      /**
95       * The project to analyze.
96       */
97      @Parameter( defaultValue = "${project}", readonly = true, required = true )
98      protected MavenProject project;
99  
100     protected void executeCheck( final String filename, final String tagName, final String key,
101                                  final int failurePriority )
102         throws MojoFailureException, MojoExecutionException
103     {
104         if ( aggregate && !project.isExecutionRoot() )
105         {
106             return;
107         }
108 
109        if ( "java".equals( language )
110                 || "jsp".equals( language )
111                 || aggregate )
112         {
113             if ( !StringUtils.isEmpty( excludeFromFailureFile ) )
114             {
115                 loadExcludeFromFailuresData( excludeFromFailureFile );
116             }
117             final File outputFile = new File( targetDirectory, filename );
118 
119             if ( outputFile.exists() )
120             {
121                 try
122                 {
123                     final ViolationDetails<D> violations = getViolations( outputFile, failurePriority );
124 
125                     final List<D> failures = violations.getFailureDetails();
126                     final List<D> warnings = violations.getWarningDetails();
127 
128                     if ( verbose )
129                     {
130                         printErrors( failures, warnings );
131                     }
132 
133                     final int failureCount = failures.size();
134                     final int warningCount = warnings.size();
135 
136                     final String message = getMessage( failureCount, warningCount, key, outputFile );
137 
138                     getLog().debug( "PMD failureCount: " + failureCount + ", warningCount: " + warningCount );
139 
140                     if ( failureCount > 0 && isFailOnViolation() )
141                     {
142                         throw new MojoFailureException( message );
143                     }
144 
145                     this.getLog().info( message );
146                 }
147                 catch ( final IOException e )
148                 {
149                     throw new MojoExecutionException(
150                                                       "Unable to read PMD results xml: " + outputFile.getAbsolutePath(),
151                                                       e );
152                 }
153                 catch ( final XmlPullParserException e )
154                 {
155                     throw new MojoExecutionException(
156                                                       "Unable to read PMD results xml: " + outputFile.getAbsolutePath(),
157                                                       e );
158                 }
159             }
160             else
161             {
162                 throw new MojoFailureException( "Unable to perform check, " + "unable to find " + outputFile );
163             }
164         }
165     }
166 
167     protected abstract void loadExcludeFromFailuresData( String excludeFromFailureFile )
168         throws MojoExecutionException;
169 
170     /**
171      * Method for collecting the violations found by the PMD tool
172      *
173      * @param analysisFile
174      * @param failurePriority
175      * @return an int that specifies the number of violations found
176      * @throws XmlPullParserException
177      * @throws IOException
178      */
179     private ViolationDetails<D> getViolations( final File analysisFile, final int failurePriority )
180         throws XmlPullParserException, IOException
181     {
182         final List<D> failures = new ArrayList<D>();
183         final List<D> warnings = new ArrayList<D>();
184 
185         final List<D> violations = getErrorDetails( analysisFile );
186 
187         for ( final D violation : violations )
188         {
189             final int priority = getPriority( violation );
190             if ( priority <= failurePriority && !isExcludedFromFailure( violation ) )
191             {
192                 failures.add( violation );
193                 if ( printFailingErrors )
194                 {
195                     printError( violation, "Failure" );
196                 }
197             }
198             else
199             {
200                 warnings.add( violation );
201             }
202         }
203 
204         final ViolationDetails<D> details = newViolationDetailsInstance();
205         details.setFailureDetails( failures );
206         details.setWarningDetails( warnings );
207         return details;
208     }
209 
210     protected abstract int getPriority( D errorDetail );
211 
212     protected abstract boolean isExcludedFromFailure( 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
277      */
278     protected abstract void printError( D item, String severity );
279 
280     /**
281      * Gets the attributes and text for the violation tag and puts them in a HashMap
282      *
283      * @param analisysFile
284      * @throws XmlPullParserException
285      * @throws IOException
286      */
287     protected abstract List<D> getErrorDetails( File analisysFile )
288         throws XmlPullParserException, IOException;
289 
290     public boolean isFailOnViolation()
291     {
292         return failOnViolation;
293     }
294 }