View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.plugins.pmd;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.util.ArrayList;
24  import java.util.List;
25  
26  import org.apache.maven.plugin.AbstractMojo;
27  import org.apache.maven.plugin.MojoExecutionException;
28  import org.apache.maven.plugin.MojoFailureException;
29  import org.apache.maven.plugins.annotations.Parameter;
30  import org.apache.maven.project.MavenProject;
31  import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
32  
33  /**
34   * Base class for mojos that check if there were any PMD violations.
35   *
36   * @param <D> type of the check, e.g. {@link org.apache.maven.plugins.pmd.model.Violation}
37   * or {@link org.apache.maven.plugins.pmd.model.Duplication}.
38   * @author <a href="mailto:brett@apache.org">Brett Porter</a>
39   * @version $Id$
40   */
41  public abstract class AbstractPmdViolationCheckMojo<D> extends AbstractMojo {
42      /**
43       * The location of the XML report to check, as generated by the PMD report.
44       */
45      @Parameter(property = "project.build.directory", required = true)
46      private File targetDirectory;
47  
48      /**
49       * Whether to fail the build if the validation check fails.
50       * The properties {@code failurePriority} and {@code maxAllowedViolations} control
51       * under which conditions exactly the build should be failed.
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       * @deprecated since 3.15.0 Use the goal <code>pmd:aggregate-check</code> or
61       * <code>pmd:aggregate-cpd-check</code> instead.
62       */
63      @Parameter(property = "aggregate", defaultValue = "false")
64      @Deprecated
65      protected boolean aggregate;
66  
67      /**
68       * Print details of check failures to build output.
69       */
70      @Parameter(property = "pmd.verbose", defaultValue = "false")
71      private boolean verbose;
72  
73      /**
74       * Print details of errors that cause build failure
75       *
76       * @since 3.0
77       */
78      @Parameter(property = "pmd.printFailingErrors", defaultValue = "false")
79      private boolean printFailingErrors;
80  
81      /**
82       * File that lists classes and rules to be excluded from failures.
83       * For PMD, this is a properties file. For CPD, this
84       * is a text file that contains comma-separated lists of classes
85       * that are allowed to duplicate.
86       *
87       * @since 3.0
88       */
89      @Parameter(property = "pmd.excludeFromFailureFile", defaultValue = "")
90      private String excludeFromFailureFile;
91  
92      /**
93       * The maximum number of failures allowed before execution fails.
94       * Used in conjunction with {@code failOnViolation=true} and utilizes {@code failurePriority}.
95       * This value has no meaning if {@code failOnViolation=false}.
96       * If the number of failures is greater than this number, the build will be failed.
97       * If the number of failures is less than or equal to this value,
98       * then the build will not be failed.
99       *
100      * @since 3.10.0
101      */
102     @Parameter(property = "pmd.maxAllowedViolations", defaultValue = "0")
103     private int maxAllowedViolations;
104 
105     /** Helper to exclude violations from the result. */
106     private final ExcludeFromFile<D> excludeFromFile;
107 
108     /**
109      * Initialize this abstact check mojo by giving the correct ExcludeFromFile helper.
110      * @param excludeFromFile the needed helper, for the specific violation type
111      */
112     protected AbstractPmdViolationCheckMojo(ExcludeFromFile<D> excludeFromFile) {
113         this.excludeFromFile = excludeFromFile;
114     }
115 
116     /**
117      * The project to analyze.
118      */
119     @Parameter(defaultValue = "${project}", readonly = true, required = true)
120     protected MavenProject project;
121 
122     protected void executeCheck(
123             final String filename, final String tagName, final String key, final int failurePriority)
124             throws MojoFailureException, MojoExecutionException {
125         if (aggregate && !project.isExecutionRoot()) {
126             return;
127         }
128 
129         if (!isAggregator() && "pom".equalsIgnoreCase(project.getPackaging())) {
130             return;
131         }
132 
133         excludeFromFile.loadExcludeFromFailuresData(excludeFromFailureFile);
134         final File outputFile = new File(targetDirectory, filename);
135 
136         if (outputFile.exists()) {
137             getLog().info("PMD version: " + AbstractPmdReport.getPmdVersion());
138 
139             try {
140                 final ViolationDetails<D> violations = getViolations(outputFile, failurePriority);
141 
142                 final List<D> failures = violations.getFailureDetails();
143                 final List<D> warnings = violations.getWarningDetails();
144 
145                 if (verbose) {
146                     printErrors(failures, warnings);
147                 }
148 
149                 final int failureCount = failures.size();
150                 final int warningCount = warnings.size();
151 
152                 final String message = getMessage(failureCount, warningCount, key, outputFile);
153 
154                 getLog().debug("PMD failureCount: " + failureCount + ", warningCount: " + warningCount);
155 
156                 if (failureCount > getMaxAllowedViolations() && isFailOnViolation()) {
157                     throw new MojoFailureException(message);
158                 }
159 
160                 this.getLog().info(message);
161 
162                 if (failureCount > 0 && isFailOnViolation() && failureCount <= getMaxAllowedViolations()) {
163                     this.getLog()
164                             .info("The build is not failed, since " + getMaxAllowedViolations()
165                                     + " violations are allowed (maxAllowedViolations).");
166                 }
167             } catch (final IOException | XmlPullParserException e) {
168                 throw new MojoExecutionException("Unable to read PMD results xml: " + outputFile.getAbsolutePath(), e);
169             }
170         } else {
171             throw new MojoFailureException("Unable to perform check, " + "unable to find " + outputFile);
172         }
173     }
174 
175     /**
176      * Method for collecting the violations found by the PMD tool
177      *
178      * @param analysisFile
179      * @param failurePriority
180      * @return an int that specifies the number of violations found
181      * @throws XmlPullParserException
182      * @throws IOException
183      */
184     private ViolationDetails<D> getViolations(final File analysisFile, final int failurePriority)
185             throws XmlPullParserException, IOException {
186         final List<D> failures = new ArrayList<>();
187         final List<D> warnings = new ArrayList<>();
188 
189         final List<D> violations = getErrorDetails(analysisFile);
190 
191         for (final D violation : violations) {
192             final int priority = getPriority(violation);
193             if (priority <= failurePriority && !excludeFromFile.isExcludedFromFailure(violation)) {
194                 failures.add(violation);
195                 if (printFailingErrors) {
196                     printError(violation, "Failure");
197                 }
198             } else {
199                 warnings.add(violation);
200             }
201         }
202 
203         final ViolationDetails<D> details = newViolationDetailsInstance();
204         details.setFailureDetails(failures);
205         details.setWarningDetails(warnings);
206         return details;
207     }
208 
209     protected abstract int getPriority(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         for (final D warning : warnings) {
221             printError(warning, "Warning");
222         }
223 
224         for (final D failure : failures) {
225             printError(failure, "Failure");
226         }
227     }
228 
229     /**
230      * Gets the output message
231      *
232      * @param failureCount
233      * @param warningCount
234      * @param key
235      * @param outputFile
236      * @return
237      */
238     private String getMessage(final int failureCount, final int warningCount, final String key, final File outputFile) {
239         final StringBuilder message = new StringBuilder(256);
240         if (failureCount > 0 || warningCount > 0) {
241             if (failureCount > 0) {
242                 message.append("You have ")
243                         .append(failureCount)
244                         .append(" ")
245                         .append(key)
246                         .append(failureCount > 1 ? "s" : "");
247             }
248 
249             if (warningCount > 0) {
250                 if (failureCount > 0) {
251                     message.append(" and ");
252                 } else {
253                     message.append("You have ");
254                 }
255                 message.append(warningCount).append(" warning").append(warningCount > 1 ? "s" : "");
256             }
257 
258             message.append(". For more details see: ").append(outputFile.getAbsolutePath());
259         }
260         return message.toString();
261     }
262 
263     /**
264      * Formats the failure details and prints them as an INFO message
265      *
266      * @param item either a {@link org.apache.maven.plugins.pmd.model.Violation} from PMD
267      * or a {@link org.apache.maven.plugins.pmd.model.Duplication} from CPD
268      * @param severity the found issue is prefixed with the given severity, usually "Warning" or "Failure".
269      */
270     protected abstract void printError(D item, String severity);
271 
272     /**
273      * Gets the attributes and text for the violation tag and puts them in a HashMap
274      *
275      * @param analysisFile the xml output from PMD or CPD
276      * @return all PMD {@link org.apache.maven.plugins.pmd.model.Violation}s
277      * or CPD {@link org.apache.maven.plugins.pmd.model.Duplication}s.
278      * @throws XmlPullParserException if the analysis file contains invalid XML
279      * @throws IOException if the analysis file could be read
280      */
281     protected abstract List<D> getErrorDetails(File analysisFile) throws XmlPullParserException, IOException;
282 
283     public boolean isFailOnViolation() {
284         return failOnViolation;
285     }
286 
287     public Integer getMaxAllowedViolations() {
288         return maxAllowedViolations;
289     }
290 
291     protected boolean isAggregator() {
292         // returning here aggregate for backwards compatibility
293         return aggregate;
294     }
295 }