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