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 analyzerName, final String failureName, 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             try {
138                 final ViolationDetails<D> violations = getViolations(outputFile, failurePriority);
139 
140                 final List<D> failures = violations.getFailureDetails();
141                 final List<D> warnings = violations.getWarningDetails();
142 
143                 if (verbose) {
144                     printErrors(failures, warnings);
145                 }
146 
147                 final int failureCount = failures.size();
148                 final int warningCount = warnings.size();
149 
150                 final String message = getMessage(failureCount, warningCount, analyzerName, failureName, outputFile);
151 
152                 if (failureCount > getMaxAllowedViolations() && isFailOnViolation()) {
153                     throw new MojoFailureException(message);
154                 }
155 
156                 if (!message.isEmpty()) {
157                     this.getLog().warn(message);
158                 }
159 
160                 if (failureCount > 0 && isFailOnViolation() && failureCount <= getMaxAllowedViolations()) {
161                     this.getLog()
162                             .info("The build has not failed because " + getMaxAllowedViolations()
163                                     + " violations are allowed (maxAllowedViolations).");
164                 }
165             } catch (final IOException | XmlPullParserException e) {
166                 throw new MojoExecutionException(
167                         "Unable to read " + analyzerName + " results XML: " + outputFile.getAbsolutePath(), e);
168             }
169         } else {
170             throw new MojoFailureException("Unable to perform check, " + "unable to find " + outputFile);
171         }
172     }
173 
174     /**
175      * Method for collecting the violations found by the PMD tool
176      *
177      * @param analysisFile
178      * @param failurePriority
179      * @return an int that specifies the number of violations found
180      * @throws XmlPullParserException
181      * @throws IOException
182      */
183     private ViolationDetails<D> getViolations(final File analysisFile, final int failurePriority)
184             throws XmlPullParserException, IOException {
185         final List<D> failures = new ArrayList<>();
186         final List<D> warnings = new ArrayList<>();
187 
188         final List<D> violations = getErrorDetails(analysisFile);
189 
190         for (final D violation : violations) {
191             final int priority = getPriority(violation);
192             if (priority <= failurePriority && !excludeFromFile.isExcludedFromFailure(violation)) {
193                 failures.add(violation);
194                 if (printFailingErrors) {
195                     printError(violation, "Failure");
196                 }
197             } else {
198                 warnings.add(violation);
199             }
200         }
201 
202         final ViolationDetails<D> details = newViolationDetailsInstance();
203         details.setFailureDetails(failures);
204         details.setWarningDetails(warnings);
205         return details;
206     }
207 
208     protected abstract int getPriority(D errorDetail);
209 
210     protected abstract ViolationDetails<D> newViolationDetailsInstance();
211 
212     /**
213      * Prints the warnings and failures
214      *
215      * @param failures list of failures
216      * @param warnings list of warnings
217      */
218     protected void printErrors(final List<D> failures, final List<D> warnings) {
219         for (final D warning : warnings) {
220             printError(warning, "Warning");
221         }
222 
223         for (final D failure : failures) {
224             printError(failure, "Failure");
225         }
226     }
227 
228     /**
229      * Gets the output message
230      *
231      * @param failureCount
232      * @param warningCount
233      * @param analyzerName
234      * @param failureName
235      * @param outputFile
236      * @return
237      */
238     private String getMessage(
239             final int failureCount,
240             final int warningCount,
241             final String analyzerName,
242             final String failureName,
243             final File outputFile) {
244         final StringBuilder message = new StringBuilder(256);
245         if (failureCount > 0 || warningCount > 0) {
246             if (failureCount > 0) {
247                 message.append(analyzerName)
248                         .append(" ")
249                         .append(AbstractPmdReport.getPmdVersion())
250                         .append(" has found ")
251                         .append(failureCount)
252                         .append(" ")
253                         .append(failureName)
254                         .append(failureCount > 1 ? "s" : "");
255             }
256 
257             if (warningCount > 0) {
258                 if (failureCount > 0) {
259                     message.append(" and issued ");
260                 } else {
261                     message.append(analyzerName)
262                             .append(" ")
263                             .append(AbstractPmdReport.getPmdVersion())
264                             .append(" has issued ");
265                 }
266                 message.append(warningCount).append(" warning").append(warningCount > 1 ? "s" : "");
267             }
268 
269             message.append(". For more details see: ").append(outputFile.getAbsolutePath());
270         }
271         return message.toString();
272     }
273 
274     /**
275      * Formats the failure details and prints them as an INFO message
276      *
277      * @param item either a {@link org.apache.maven.plugins.pmd.model.Violation} from PMD
278      * or a {@link org.apache.maven.plugins.pmd.model.Duplication} from CPD
279      * @param severity the found issue is prefixed with the given severity, usually "Warning" or "Failure".
280      */
281     protected abstract void printError(D item, String severity);
282 
283     /**
284      * Gets the attributes and text for the violation tag and puts them in a HashMap
285      *
286      * @param analysisFile the xml output from PMD or CPD
287      * @return all PMD {@link org.apache.maven.plugins.pmd.model.Violation}s
288      * or CPD {@link org.apache.maven.plugins.pmd.model.Duplication}s.
289      * @throws XmlPullParserException if the analysis file contains invalid XML
290      * @throws IOException if the analysis file could be read
291      */
292     protected abstract List<D> getErrorDetails(File analysisFile) throws XmlPullParserException, IOException;
293 
294     public boolean isFailOnViolation() {
295         return failOnViolation;
296     }
297 
298     public Integer getMaxAllowedViolations() {
299         return maxAllowedViolations;
300     }
301 
302     protected boolean isAggregator() {
303         // returning here aggregate for backwards compatibility
304         return aggregate;
305     }
306 }