View Javadoc
1   /*
2    * SPDX-FileCopyrightText: Copyright (c) 2011-2026 Yegor Bugayenko
3    * SPDX-License-Identifier: MIT
4    */
5   package com.qulice.maven;
6   
7   import com.jcabi.log.Logger;
8   import com.qulice.spi.ResourceValidator;
9   import com.qulice.spi.ValidationException;
10  import com.qulice.spi.Validator;
11  import com.qulice.spi.Violation;
12  import java.io.File;
13  import java.util.ArrayList;
14  import java.util.Collection;
15  import java.util.Collections;
16  import java.util.List;
17  import java.util.Locale;
18  import java.util.concurrent.Callable;
19  import java.util.concurrent.ExecutionException;
20  import java.util.concurrent.ExecutorService;
21  import java.util.concurrent.Executors;
22  import java.util.concurrent.Future;
23  import java.util.concurrent.TimeUnit;
24  import java.util.concurrent.TimeoutException;
25  import org.apache.maven.plugin.MojoFailureException;
26  import org.apache.maven.plugins.annotations.LifecyclePhase;
27  import org.apache.maven.plugins.annotations.Mojo;
28  import org.apache.maven.plugins.annotations.Parameter;
29  import org.apache.maven.plugins.annotations.ResolutionScope;
30  
31  /**
32   * Check the project and find all possible violations.
33   * @since 0.3
34   */
35  @Mojo(
36      name = "check",
37      defaultPhase = LifecyclePhase.VERIFY,
38      requiresDependencyResolution = ResolutionScope.TEST,
39      threadSafe = true
40  )
41  public final class CheckMojo extends AbstractQuliceMojo {
42  
43      /**
44       * Executors for validators.
45       */
46      private final ExecutorService executors;
47  
48      /**
49       * Provider of validators, if it was set from the outside.
50       */
51      private ValidatorsProvider provider;
52  
53      /**
54       * Check timeout.
55       * Can be a number of minutes.
56       * Can be a string with time units, like '10m' or '1h'.
57       * Time units are 's' for seconds, 'm' for minutes, 'h' for hours.
58       * Can also be a string 'forever' to disable timeout.
59       * Defaults to 10 minutes.
60       */
61      @Parameter(property = "qulice.check-timeout", defaultValue = "10")
62      private String timeout;
63  
64      /**
65       * Default constructor.
66       */
67      public CheckMojo() {
68          this(Executors.newFixedThreadPool(5));
69      }
70  
71      /**
72       * Primary constructor.
73       * @param svc Executors to run resource validators in
74       */
75      private CheckMojo(final ExecutorService svc) {
76          this.executors = svc;
77      }
78  
79      @Override
80      public void doExecute() throws MojoFailureException {
81          try {
82              this.run();
83          } catch (final ValidationException ex) {
84              Logger.info(
85                  this,
86                  "Read our quality policy: https://www.qulice.com/quality.html"
87              );
88              throw new MojoFailureException("Failure", ex);
89          }
90      }
91  
92      /**
93       * Set provider of validators.
94       * @param prov The provider
95       */
96      public void setValidatorsProvider(final ValidatorsProvider prov) {
97          this.provider = prov;
98      }
99  
100     /**
101      * Set timeout for checks.
102      * @param time Timeout value
103      */
104     public void setTimeout(final String time) {
105         this.timeout = time;
106     }
107 
108     /**
109      * Run them all.
110      * @throws ValidationException If any of them fail
111      */
112     @SuppressWarnings("PMD.CognitiveComplexity")
113     private void run() throws ValidationException {
114         final List<Violation> results = new ArrayList<>(0);
115         final MavenEnvironment env = this.env();
116         final ValidatorsProvider prov = this.validators(env);
117         final Collection<File> files = env.files("*.*");
118         if (!files.isEmpty()) {
119             final Collection<Future<Collection<Violation>>> futures =
120                 this.submit(env, files, prov.externalResource());
121             for (final Future<Collection<Violation>> future : futures) {
122                 try {
123                     if ("forever".equalsIgnoreCase(this.timeout)) {
124                         results.addAll(future.get());
125                     } else {
126                         final long value = this.timeoutValue();
127                         final TimeUnit units = this.timeoutUnits();
128                         Logger.debug(
129                             this,
130                             "Waiting up to %d %s for validator result",
131                             value,
132                             units
133                         );
134                         results.addAll(future.get(value, units));
135                     }
136                 } catch (final InterruptedException ex) {
137                     Thread.currentThread().interrupt();
138                     throw new IllegalStateException(ex);
139                 } catch (final ExecutionException | TimeoutException ex) {
140                     throw new IllegalStateException(ex);
141                 }
142             }
143             Collections.sort(results);
144             for (final Violation result : results) {
145                 Logger.info(
146                     this,
147                     "%s: %s[%s]: %s (%s)",
148                     result.validator(),
149                     result.file().replace(
150                         String.format(
151                             "%s/", this.session().getExecutionRootDirectory()
152                         ),
153                         ""
154                     ),
155                     result.lines(),
156                     result.message(),
157                     result.name()
158                 );
159             }
160         }
161         if (!results.isEmpty()) {
162             throw new ValidationException(
163                 String.format("There are %d violations", results.size())
164             );
165         }
166         for (final Validator validator : prov.external()) {
167             Logger.info(this, "Starting %s validator", validator.name());
168             validator.validate(env);
169             Logger.info(this, "Finishing %s validator", validator.name());
170         }
171         for (final MavenValidator validator : prov.internal()) {
172             validator.validate(env);
173         }
174     }
175 
176     /**
177      * Provider of validators, the one that was set or the default one.
178      * @param env Maven environment for the default provider
179      * @return The provider
180      */
181     private ValidatorsProvider validators(final MavenEnvironment env) {
182         final ValidatorsProvider prov;
183         if (this.provider == null) {
184             prov = new DefaultValidatorsProvider(env);
185         } else {
186             prov = this.provider;
187         }
188         return prov;
189     }
190 
191     /**
192      * Submit validators to executor.
193      * @param env Maven environment
194      * @param files List of files to validate
195      * @param validators Validators to use
196      * @return List of futures
197      */
198     private Collection<Future<Collection<Violation>>> submit(
199         final MavenEnvironment env, final Collection<File> files,
200         final Collection<ResourceValidator> validators
201     ) {
202         final Collection<Future<Collection<Violation>>> futures =
203             new ArrayList<>(validators.size());
204         for (final ResourceValidator validator : validators) {
205             futures.add(
206                 this.executors.submit(
207                     new CheckMojo.ValidatorCallable(validator, env, files)
208                 )
209             );
210         }
211         return futures;
212     }
213 
214     /**
215      * Timeout value for timeout.
216      * @return Timeout value
217      */
218     private long timeoutValue() {
219         final String clear = this.clearTimeout();
220         final long res;
221         if (clear.isEmpty()) {
222             res = 10L;
223         } else if (clear.endsWith("s") || clear.endsWith("m") || clear.endsWith("h")) {
224             res = Long.parseLong(clear.substring(0, clear.length() - 1));
225         } else {
226             res = Long.parseLong(clear);
227         }
228         return res;
229     }
230 
231     /**
232      * Time unit for timeout.
233      * @return Time unit
234      */
235     private TimeUnit timeoutUnits() {
236         final String clear = this.clearTimeout();
237         final TimeUnit unit;
238         if (clear.endsWith("s")) {
239             unit = TimeUnit.SECONDS;
240         } else if (clear.endsWith("m")) {
241             unit = TimeUnit.MINUTES;
242         } else if (clear.endsWith("h")) {
243             unit = TimeUnit.HOURS;
244         } else {
245             unit = TimeUnit.MINUTES;
246         }
247         return unit;
248     }
249 
250     /**
251      * Clear timeout string.
252      * @return Cleaned timeout
253      */
254     private String clearTimeout() {
255         final String clear;
256         if (this.timeout == null) {
257             clear = "";
258         } else {
259             clear = this.timeout.trim()
260             .replaceAll(" ", "")
261             .toLowerCase(Locale.ENGLISH);
262         }
263         return clear;
264     }
265 
266     /**
267      * Filter files based on excludes.
268      * @param env Maven environment
269      * @param files Files to exclude
270      * @param validator Validator to use
271      * @return Filtered files
272      */
273     private static Collection<File> filter(
274         final MavenEnvironment env,
275         final Collection<File> files, final ResourceValidator validator
276     ) {
277         final Collection<File> filtered = new ArrayList<>(files.size());
278         for (final File file : files) {
279             if (
280                 !env.exclude(
281                     validator.name().toLowerCase(Locale.ENGLISH),
282                     file.toString()
283                 )
284             ) {
285                 filtered.add(file);
286             }
287         }
288         return filtered;
289     }
290 
291     /**
292      * Callable for validators.
293      * @since 0.1
294      */
295     private static class ValidatorCallable
296         implements Callable<Collection<Violation>> {
297 
298         /**
299          * Validator to use.
300          */
301         private final ResourceValidator validator;
302 
303         /**
304          * Maven environment.
305          */
306         private final MavenEnvironment env;
307 
308         /**
309          * List of files to validate.
310          */
311         private final Collection<File> files;
312 
313         /**
314          * Constructor.
315          * @param validator Validator to use
316          * @param env Maven environment
317          * @param files List of files to validate
318          */
319         ValidatorCallable(
320             final ResourceValidator validator,
321             final MavenEnvironment env, final Collection<File> files
322         ) {
323             this.validator = validator;
324             this.env = env;
325             this.files = files;
326         }
327 
328         @Override
329         public Collection<Violation> call() {
330             return this.validator.validate(
331                 CheckMojo.filter(this.env, this.files, this.validator)
332             );
333         }
334     }
335 }