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 String doExecute() throws MojoFailureException {
81          try {
82              return 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      * @return What was checked, for the final log line
111      * @throws ValidationException If any of them fail
112      */
113     @SuppressWarnings("PMD.CognitiveComplexity")
114     private String run() throws ValidationException {
115         final List<Violation> results = new ArrayList<>(0);
116         final MavenEnvironment env = this.env();
117         final ValidatorsProvider prov = this.validators(env);
118         final Collection<ResourceValidator> resources = prov.externalResource();
119         final Collection<File> files = env.files("*.*");
120         if (!files.isEmpty()) {
121             final Collection<Future<Collection<Violation>>> futures =
122                 this.submit(env, files, resources);
123             for (final Future<Collection<Violation>> future : futures) {
124                 try {
125                     if ("forever".equalsIgnoreCase(this.timeout)) {
126                         results.addAll(future.get());
127                     } else {
128                         final long value = this.timeoutValue();
129                         final TimeUnit units = this.timeoutUnits();
130                         Logger.debug(
131                             this,
132                             "Waiting up to %d %s for validator result",
133                             value,
134                             units
135                         );
136                         results.addAll(future.get(value, units));
137                     }
138                 } catch (final InterruptedException ex) {
139                     Thread.currentThread().interrupt();
140                     throw new IllegalStateException(ex);
141                 } catch (final ExecutionException | TimeoutException ex) {
142                     throw new IllegalStateException(ex);
143                 }
144             }
145             Collections.sort(results);
146             for (final Violation result : results) {
147                 Logger.info(
148                     this,
149                     "%s: %s[%s]: %s (%s)",
150                     result.validator(),
151                     result.file().replace(
152                         String.format(
153                             "%s/", this.session().getExecutionRootDirectory()
154                         ),
155                         ""
156                     ),
157                     result.lines(),
158                     result.message(),
159                     result.name()
160                 );
161             }
162         }
163         if (!results.isEmpty()) {
164             throw new ValidationException(
165                 String.format("There are %d violations", results.size())
166             );
167         }
168         for (final Validator validator : prov.external()) {
169             Logger.info(this, "Starting %s validator", validator.name());
170             validator.validate(env);
171             Logger.info(this, "Finishing %s validator", validator.name());
172         }
173         for (final MavenValidator validator : prov.internal()) {
174             validator.validate(env);
175         }
176         return new Summary(files, resources).toString();
177     }
178 
179     /**
180      * Provider of validators, the one that was set or the default one.
181      * @param env Maven environment for the default provider
182      * @return The provider
183      */
184     private ValidatorsProvider validators(final MavenEnvironment env) {
185         final ValidatorsProvider prov;
186         if (this.provider == null) {
187             prov = new DefaultValidatorsProvider(env);
188         } else {
189             prov = this.provider;
190         }
191         return prov;
192     }
193 
194     /**
195      * Submit validators to executor.
196      * @param env Maven environment
197      * @param files List of files to validate
198      * @param validators Validators to use
199      * @return List of futures
200      */
201     private Collection<Future<Collection<Violation>>> submit(
202         final MavenEnvironment env, final Collection<File> files,
203         final Collection<ResourceValidator> validators
204     ) {
205         final Collection<Future<Collection<Violation>>> futures =
206             new ArrayList<>(validators.size());
207         for (final ResourceValidator validator : validators) {
208             futures.add(
209                 this.executors.submit(
210                     new CheckMojo.ValidatorCallable(validator, env, files)
211                 )
212             );
213         }
214         return futures;
215     }
216 
217     /**
218      * Timeout value for timeout.
219      * @return Timeout value
220      */
221     private long timeoutValue() {
222         final String clear = this.clearTimeout();
223         final long res;
224         if (clear.isEmpty()) {
225             res = 10L;
226         } else if (clear.endsWith("s") || clear.endsWith("m") || clear.endsWith("h")) {
227             res = Long.parseLong(clear.substring(0, clear.length() - 1));
228         } else {
229             res = Long.parseLong(clear);
230         }
231         return res;
232     }
233 
234     /**
235      * Time unit for timeout.
236      * @return Time unit
237      */
238     private TimeUnit timeoutUnits() {
239         final String clear = this.clearTimeout();
240         final TimeUnit unit;
241         if (clear.endsWith("s")) {
242             unit = TimeUnit.SECONDS;
243         } else if (clear.endsWith("m")) {
244             unit = TimeUnit.MINUTES;
245         } else if (clear.endsWith("h")) {
246             unit = TimeUnit.HOURS;
247         } else {
248             unit = TimeUnit.MINUTES;
249         }
250         return unit;
251     }
252 
253     /**
254      * Clear timeout string.
255      * @return Cleaned timeout
256      */
257     private String clearTimeout() {
258         final String clear;
259         if (this.timeout == null) {
260             clear = "";
261         } else {
262             clear = this.timeout.trim()
263             .replaceAll(" ", "")
264             .toLowerCase(Locale.ENGLISH);
265         }
266         return clear;
267     }
268 
269     /**
270      * Filter files based on excludes.
271      * @param env Maven environment
272      * @param files Files to exclude
273      * @param validator Validator to use
274      * @return Filtered files
275      */
276     private static Collection<File> filter(
277         final MavenEnvironment env,
278         final Collection<File> files, final ResourceValidator validator
279     ) {
280         final Collection<File> filtered = new ArrayList<>(files.size());
281         for (final File file : files) {
282             if (
283                 !env.exclude(
284                     validator.name().toLowerCase(Locale.ENGLISH),
285                     file.toString()
286                 )
287             ) {
288                 filtered.add(file);
289             }
290         }
291         return filtered;
292     }
293 
294     /**
295      * Callable for validators.
296      * @since 0.1
297      */
298     private static class ValidatorCallable
299         implements Callable<Collection<Violation>> {
300 
301         /**
302          * Validator to use.
303          */
304         private final ResourceValidator validator;
305 
306         /**
307          * Maven environment.
308          */
309         private final MavenEnvironment env;
310 
311         /**
312          * List of files to validate.
313          */
314         private final Collection<File> files;
315 
316         /**
317          * Constructor.
318          * @param validator Validator to use
319          * @param env Maven environment
320          * @param files List of files to validate
321          */
322         ValidatorCallable(
323             final ResourceValidator validator,
324             final MavenEnvironment env, final Collection<File> files
325         ) {
326             this.validator = validator;
327             this.env = env;
328             this.files = files;
329         }
330 
331         @Override
332         public Collection<Violation> call() {
333             return this.validator.validate(
334                 CheckMojo.filter(this.env, this.files, this.validator)
335             );
336         }
337     }
338 }