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