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.checkstyle.exec;
20  
21  import java.io.ByteArrayInputStream;
22  import java.io.File;
23  import java.io.FileInputStream;
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.net.MalformedURLException;
27  import java.util.ArrayList;
28  import java.util.Collection;
29  import java.util.HashMap;
30  import java.util.LinkedHashSet;
31  import java.util.List;
32  import java.util.Map;
33  import java.util.Properties;
34  import java.util.Set;
35  
36  import com.puppycrawl.tools.checkstyle.Checker;
37  import com.puppycrawl.tools.checkstyle.ConfigurationLoader;
38  import com.puppycrawl.tools.checkstyle.ConfigurationLoader.IgnoredModulesOptions;
39  import com.puppycrawl.tools.checkstyle.DefaultConfiguration;
40  import com.puppycrawl.tools.checkstyle.PackageNamesLoader;
41  import com.puppycrawl.tools.checkstyle.PropertiesExpander;
42  import com.puppycrawl.tools.checkstyle.api.AuditListener;
43  import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
44  import com.puppycrawl.tools.checkstyle.api.Configuration;
45  import com.puppycrawl.tools.checkstyle.api.FilterSet;
46  import com.puppycrawl.tools.checkstyle.filters.SuppressionsLoader;
47  import org.apache.commons.lang3.StringUtils;
48  import org.apache.maven.artifact.Artifact;
49  import org.apache.maven.artifact.DependencyResolutionRequiredException;
50  import org.apache.maven.model.Resource;
51  import org.apache.maven.project.MavenProject;
52  import org.codehaus.plexus.component.annotations.Component;
53  import org.codehaus.plexus.component.annotations.Requirement;
54  import org.codehaus.plexus.logging.AbstractLogEnabled;
55  import org.codehaus.plexus.resource.ResourceManager;
56  import org.codehaus.plexus.resource.loader.FileResourceCreationException;
57  import org.codehaus.plexus.resource.loader.FileResourceLoader;
58  import org.codehaus.plexus.resource.loader.ResourceNotFoundException;
59  import org.codehaus.plexus.util.FileUtils;
60  
61  /**
62   * @author Olivier Lamy
63   * @since 2.5
64   *
65   */
66  @Component(role = CheckstyleExecutor.class, hint = "default", instantiationStrategy = "per-lookup")
67  public class DefaultCheckstyleExecutor extends AbstractLogEnabled implements CheckstyleExecutor {
68      @Requirement(hint = "default")
69      private ResourceManager locator;
70  
71      @Requirement(hint = "license")
72      private ResourceManager licenseLocator;
73  
74      public CheckstyleResults executeCheckstyle(CheckstyleExecutorRequest request)
75              throws CheckstyleExecutorException, CheckstyleException {
76          if (getLogger().isDebugEnabled()) {
77              getLogger().debug("executeCheckstyle start headerLocation : " + request.getHeaderLocation());
78          }
79  
80          MavenProject project = request.getProject();
81  
82          configureResourceLocator(locator, request, null);
83  
84          configureResourceLocator(licenseLocator, request, request.getLicenseArtifacts());
85  
86          // Config is less critical than License, locator can still be used.
87          // configureResourceLocator( configurationLocator, request, request.getConfigurationArtifacts() );
88  
89          List<File> files;
90          try {
91              files = getFilesToProcess(request);
92          } catch (IOException e) {
93              throw new CheckstyleExecutorException("Error getting files to process", e);
94          }
95  
96          final String suppressionsFilePath = getSuppressionsFilePath(request);
97          FilterSet filterSet = getSuppressionsFilterSet(suppressionsFilePath);
98  
99          Checker checker = new Checker();
100 
101         // setup classloader, needed to avoid "Unable to get class information for ..." errors
102         List<String> classPathStrings = new ArrayList<>();
103         List<String> outputDirectories = new ArrayList<>();
104 
105         // stand-alone
106         Collection<File> sourceDirectories = null;
107         Collection<File> testSourceDirectories = request.getTestSourceDirectories();
108 
109         // aggregator
110         Map<MavenProject, Collection<File>> sourceDirectoriesByProject = new HashMap<>();
111         Map<MavenProject, Collection<File>> testSourceDirectoriesByProject = new HashMap<>();
112 
113         if (request.isAggregate()) {
114             for (MavenProject childProject : request.getReactorProjects()) {
115                 sourceDirectories =
116                         new ArrayList<>(childProject.getCompileSourceRoots().size());
117                 List<String> compileSourceRoots = childProject.getCompileSourceRoots();
118                 for (String compileSourceRoot : compileSourceRoots) {
119                     sourceDirectories.add(new File(compileSourceRoot));
120                 }
121                 sourceDirectoriesByProject.put(childProject, sourceDirectories);
122 
123                 testSourceDirectories =
124                         new ArrayList<>(childProject.getTestCompileSourceRoots().size());
125                 List<String> testCompileSourceRoots = childProject.getTestCompileSourceRoots();
126                 for (String testCompileSourceRoot : testCompileSourceRoots) {
127                     testSourceDirectories.add(new File(testCompileSourceRoot));
128                 }
129                 testSourceDirectoriesByProject.put(childProject, testSourceDirectories);
130 
131                 prepareCheckstylePaths(
132                         request,
133                         childProject,
134                         classPathStrings,
135                         outputDirectories,
136                         sourceDirectories,
137                         testSourceDirectories);
138             }
139         } else {
140             sourceDirectories = request.getSourceDirectories();
141             prepareCheckstylePaths(
142                     request, project, classPathStrings, outputDirectories, sourceDirectories, testSourceDirectories);
143         }
144 
145         checker.setModuleClassLoader(Thread.currentThread().getContextClassLoader());
146 
147         if (filterSet != null) {
148             checker.addFilter(filterSet);
149         }
150         Configuration configuration = getConfiguration(request);
151         checker.configure(configuration);
152 
153         AuditListener listener = request.getListener();
154 
155         if (listener != null) {
156             checker.addListener(listener);
157         }
158 
159         if (request.isConsoleOutput()) {
160             checker.addListener(request.getConsoleListener());
161         }
162 
163         CheckstyleCheckerListener checkerListener = new CheckstyleCheckerListener(configuration);
164         if (request.isAggregate()) {
165             for (MavenProject childProject : request.getReactorProjects()) {
166                 sourceDirectories = sourceDirectoriesByProject.get(childProject);
167                 testSourceDirectories = testSourceDirectoriesByProject.get(childProject);
168                 addSourceDirectory(
169                         checkerListener,
170                         sourceDirectories,
171                         testSourceDirectories,
172                         childProject.getResources(),
173                         request);
174             }
175         } else {
176             addSourceDirectory(
177                     checkerListener, sourceDirectories, testSourceDirectories, request.getResources(), request);
178         }
179 
180         checker.addListener(checkerListener);
181 
182         int nbErrors = checker.process(files);
183 
184         checker.destroy();
185 
186         if (request.getStringOutputStream() != null) {
187             String message = request.getStringOutputStream().toString().trim();
188 
189             if (message.length() > 0) {
190                 getLogger().info(message);
191             }
192         }
193 
194         if (nbErrors > 0) {
195             StringBuilder message = new StringBuilder("There ");
196             if (nbErrors == 1) {
197                 message.append("is");
198             } else {
199                 message.append("are");
200             }
201             message.append(" ");
202             message.append(nbErrors);
203             message.append(" error");
204             if (nbErrors != 1) {
205                 message.append("s");
206             }
207             message.append(" reported by Checkstyle");
208             String version = getCheckstyleVersion();
209             if (version != null) {
210                 message.append(" ");
211                 message.append(version);
212             }
213             message.append(" with ");
214             message.append(request.getConfigLocation());
215             message.append(" ruleset.");
216 
217             if (request.isFailsOnError()) {
218                 // TODO: should be a failure, not an error. Report is not meant to
219                 // throw an exception here (so site would
220                 // work regardless of config), but should record this information
221                 throw new CheckstyleExecutorException(message.toString());
222             } else {
223                 getLogger().info(message.toString());
224             }
225         }
226 
227         return checkerListener.getResults();
228     }
229 
230     protected void addSourceDirectory(
231             CheckstyleCheckerListener sinkListener,
232             Collection<File> sourceDirectories,
233             Collection<File> testSourceDirectories,
234             List<Resource> resources,
235             CheckstyleExecutorRequest request) {
236         if (sourceDirectories != null) {
237             for (File sourceDirectory : sourceDirectories) {
238                 if (sourceDirectory.exists()) {
239                     sinkListener.addSourceDirectory(sourceDirectory);
240                 }
241             }
242         }
243 
244         if (request.isIncludeTestSourceDirectory() && (testSourceDirectories != null)) {
245             for (File testSourceDirectory : testSourceDirectories) {
246                 if (testSourceDirectory.isDirectory()) {
247                     sinkListener.addSourceDirectory(testSourceDirectory);
248                 }
249             }
250         }
251 
252         if (resources != null) {
253             for (Resource resource : resources) {
254                 if (resource.getDirectory() != null) {
255                     File resourcesDirectory = new File(resource.getDirectory());
256                     if (resourcesDirectory.exists() && resourcesDirectory.isDirectory()) {
257                         sinkListener.addSourceDirectory(resourcesDirectory);
258                         getLogger()
259                                 .debug("Added '" + resourcesDirectory.getAbsolutePath() + "' as a source directory.");
260                     }
261                 }
262             }
263         }
264     }
265 
266     public Configuration getConfiguration(CheckstyleExecutorRequest request) throws CheckstyleExecutorException {
267         try {
268             // Checkstyle will always use the context classloader in order
269             // to load resources (dtds),
270             // so we have to fix it
271             ClassLoader checkstyleClassLoader = PackageNamesLoader.class.getClassLoader();
272             Thread.currentThread().setContextClassLoader(checkstyleClassLoader);
273             String configFile = getConfigFile(request);
274             Properties overridingProperties = getOverridingProperties(request);
275             IgnoredModulesOptions omitIgnoredModules;
276             if (request.isOmitIgnoredModules()) {
277                 omitIgnoredModules = IgnoredModulesOptions.OMIT;
278             } else {
279                 omitIgnoredModules = IgnoredModulesOptions.EXECUTE;
280             }
281             Configuration config = ConfigurationLoader.loadConfiguration(
282                     configFile, new PropertiesExpander(overridingProperties), omitIgnoredModules);
283             String effectiveEncoding = StringUtils.isNotEmpty(request.getEncoding())
284                     ? request.getEncoding()
285                     : System.getProperty("file.encoding", "UTF-8");
286 
287             if (StringUtils.isEmpty(request.getEncoding())) {
288                 getLogger()
289                         .warn("File encoding has not been set, using platform encoding " + effectiveEncoding
290                                 + ", i.e. build is platform dependent!");
291             }
292 
293             if ("Checker".equals(config.getName())
294                     || "com.puppycrawl.tools.checkstyle.Checker".equals(config.getName())) {
295                 if (config instanceof DefaultConfiguration) {
296                     // MCHECKSTYLE-173 Only add the "charset" attribute if it has not been set
297                     addAttributeIfNotExists((DefaultConfiguration) config, "charset", effectiveEncoding);
298                     addAttributeIfNotExists((DefaultConfiguration) config, "cacheFile", request.getCacheFile());
299                 } else {
300                     getLogger().warn("Failed to configure file encoding on module " + config);
301                 }
302             }
303             return config;
304         } catch (CheckstyleException e) {
305             throw new CheckstyleExecutorException("Failed during checkstyle configuration", e);
306         }
307     }
308 
309     private void addAttributeIfNotExists(DefaultConfiguration config, String name, String value) {
310         try {
311             // MCHECKSTYLE-132 DefaultConfiguration addAttribute has changed in checkstyle 5.3
312             if (config.getAttribute(name) == null) {
313                 config.addAttribute(name, value);
314             }
315         } catch (CheckstyleException ex) {
316             // MCHECKSTYLE-159 Checkstyle 5.4+ throws an exception when trying to access an attribute that doesn't exist
317             config.addAttribute(name, value);
318         }
319     }
320 
321     private void prepareCheckstylePaths(
322             CheckstyleExecutorRequest request,
323             MavenProject project,
324             List<String> classPathStrings,
325             List<String> outputDirectories,
326             Collection<File> sourceDirectories,
327             Collection<File> testSourceDirectories)
328             throws CheckstyleExecutorException {
329         try {
330             outputDirectories.add(project.getBuild().getOutputDirectory());
331 
332             if (request.isIncludeTestSourceDirectory()
333                     && (testSourceDirectories != null)
334                     && anyDirectoryExists(testSourceDirectories)) {
335                 classPathStrings.addAll(project.getTestClasspathElements());
336                 outputDirectories.add(project.getBuild().getTestOutputDirectory());
337             } else {
338                 classPathStrings.addAll(project.getCompileClasspathElements());
339             }
340         } catch (DependencyResolutionRequiredException e) {
341             throw new CheckstyleExecutorException(e.getMessage(), e);
342         }
343     }
344 
345     private boolean anyDirectoryExists(Collection<File> files) {
346         for (File file : files) {
347             if (file.isDirectory()) {
348                 return true;
349             }
350         }
351         return false;
352     }
353 
354     /**
355      * Get the effective Checkstyle version at runtime.
356      * @return the MANIFEST implementation version of Checkstyle API package (can be <code>null</code>)
357      *
358      *@todo Copied from CheckstyleReportGenerator - move to a utility class
359      */
360     private String getCheckstyleVersion() {
361         Package checkstyleApiPackage = Configuration.class.getPackage();
362 
363         return (checkstyleApiPackage == null) ? null : checkstyleApiPackage.getImplementationVersion();
364     }
365 
366     private Properties getOverridingProperties(CheckstyleExecutorRequest request) throws CheckstyleExecutorException {
367         Properties p = new Properties();
368         try {
369             if (request.getPropertiesLocation() != null) {
370                 if (getLogger().isDebugEnabled()) {
371                     getLogger().debug("request.getPropertiesLocation() " + request.getPropertiesLocation());
372                 }
373 
374                 File propertiesFile =
375                         locator.getResourceAsFile(request.getPropertiesLocation(), "checkstyle-checker.properties");
376 
377                 if (propertiesFile != null) {
378                     try (InputStream in = new FileInputStream(propertiesFile)) {
379                         p.load(in);
380                     }
381                 }
382             }
383 
384             if (StringUtils.isNotEmpty(request.getPropertyExpansion())) {
385                 String propertyExpansion = request.getPropertyExpansion();
386                 // Convert \ to \\, so that p.load will convert it back properly
387                 propertyExpansion = StringUtils.replace(propertyExpansion, "\\", "\\\\");
388                 p.load(new ByteArrayInputStream(propertyExpansion.getBytes()));
389             }
390 
391             // Workaround for MCHECKSTYLE-48
392             // Make sure that "config/maven-header.txt" is the default value
393             // for headerLocation, if configLocation="config/maven_checks.xml"
394             String headerLocation = request.getHeaderLocation();
395             if ("config/maven_checks.xml".equals(request.getConfigLocation())) {
396 
397                 if ("LICENSE.txt".equals(request.getHeaderLocation())) {
398                     headerLocation = "config/maven-header.txt";
399                 }
400             }
401             if (getLogger().isDebugEnabled()) {
402                 getLogger().debug("headerLocation " + headerLocation);
403             }
404 
405             if (headerLocation != null && !headerLocation.isEmpty()) {
406                 try {
407                     File headerFile = licenseLocator.getResourceAsFile(headerLocation, "checkstyle-header.txt");
408 
409                     if (headerFile != null) {
410                         p.setProperty("checkstyle.header.file", headerFile.getAbsolutePath());
411                     }
412                 } catch (FileResourceCreationException | ResourceNotFoundException e) {
413                     getLogger().debug("Unable to process header location: " + headerLocation);
414                     getLogger().debug("Checkstyle will throw exception if ${checkstyle.header.file} is used");
415                 }
416             }
417 
418             if (request.getCacheFile() != null) {
419                 p.setProperty("checkstyle.cache.file", request.getCacheFile());
420             }
421         } catch (IOException | ResourceNotFoundException | FileResourceCreationException e) {
422             throw new CheckstyleExecutorException("Failed to get overriding properties", e);
423         }
424         if (request.getSuppressionsFileExpression() != null) {
425             String suppressionsFilePath = getSuppressionsFilePath(request);
426 
427             if (suppressionsFilePath != null) {
428                 p.setProperty(request.getSuppressionsFileExpression(), suppressionsFilePath);
429             }
430         }
431 
432         return p;
433     }
434 
435     private List<File> getFilesToProcess(CheckstyleExecutorRequest request) throws IOException {
436         StringBuilder excludesStr = new StringBuilder();
437 
438         if (StringUtils.isNotEmpty(request.getExcludes())) {
439             excludesStr.append(request.getExcludes());
440         }
441 
442         String[] defaultExcludes = FileUtils.getDefaultExcludes();
443         for (String defaultExclude : defaultExcludes) {
444             if (excludesStr.length() > 0) {
445                 excludesStr.append(",");
446             }
447 
448             excludesStr.append(defaultExclude);
449         }
450 
451         Set<File> files = new LinkedHashSet<>();
452         if (request.isAggregate()) {
453             for (MavenProject project : request.getReactorProjects()) {
454                 Set<File> sourceDirectories = new LinkedHashSet<>();
455 
456                 // CompileSourceRoots are absolute paths
457                 List<String> compileSourceRoots = project.getCompileSourceRoots();
458                 for (String compileSourceRoot : compileSourceRoots) {
459                     sourceDirectories.add(new File(compileSourceRoot));
460                 }
461 
462                 Set<File> testSourceDirectories = new LinkedHashSet<>();
463                 // CompileSourceRoots are absolute paths
464                 List<String> testCompileSourceRoots = project.getTestCompileSourceRoots();
465                 for (String testCompileSourceRoot : testCompileSourceRoots) {
466                     testSourceDirectories.add(new File(testCompileSourceRoot));
467                 }
468 
469                 addFilesToProcess(
470                         request,
471                         sourceDirectories,
472                         project.getResources(),
473                         project.getTestResources(),
474                         files,
475                         testSourceDirectories);
476             }
477         } else {
478             Collection<File> sourceDirectories = request.getSourceDirectories();
479             addFilesToProcess(
480                     request,
481                     sourceDirectories,
482                     request.getResources(),
483                     request.getTestResources(),
484                     files,
485                     request.getTestSourceDirectories());
486         }
487 
488         getLogger().debug("Added " + files.size() + " files to process.");
489 
490         return new ArrayList<>(files);
491     }
492 
493     private void addFilesToProcess(
494             CheckstyleExecutorRequest request,
495             Collection<File> sourceDirectories,
496             List<Resource> resources,
497             List<Resource> testResources,
498             Collection<File> files,
499             Collection<File> testSourceDirectories)
500             throws IOException {
501         if (sourceDirectories != null) {
502             for (File sourceDirectory : sourceDirectories) {
503                 if (sourceDirectory.isDirectory()) {
504                     final List<File> sourceFiles =
505                             FileUtils.getFiles(sourceDirectory, request.getIncludes(), request.getExcludes());
506                     files.addAll(sourceFiles);
507                     getLogger()
508                             .debug("Added " + sourceFiles.size() + " source files found in '"
509                                     + sourceDirectory.getAbsolutePath() + "'.");
510                 }
511             }
512         }
513 
514         if (request.isIncludeTestSourceDirectory() && testSourceDirectories != null) {
515             for (File testSourceDirectory : testSourceDirectories) {
516                 if (testSourceDirectory.isDirectory()) {
517                     final List<File> testSourceFiles =
518                             FileUtils.getFiles(testSourceDirectory, request.getIncludes(), request.getExcludes());
519 
520                     files.addAll(testSourceFiles);
521                     getLogger()
522                             .debug("Added " + testSourceFiles.size() + " test source files found in '"
523                                     + testSourceDirectory.getAbsolutePath() + "'.");
524                 }
525             }
526         }
527 
528         if (resources != null && request.isIncludeResources()) {
529             addResourceFilesToProcess(request, resources, files);
530         } else {
531             getLogger().debug("No resources found in this project.");
532         }
533 
534         if (testResources != null && request.isIncludeTestResources()) {
535             addResourceFilesToProcess(request, testResources, files);
536         } else {
537             getLogger().debug("No test resources found in this project.");
538         }
539     }
540 
541     private void addResourceFilesToProcess(
542             CheckstyleExecutorRequest request, List<Resource> resources, Collection<File> files) throws IOException {
543         for (Resource resource : resources) {
544             if (resource.getDirectory() != null) {
545                 File resourcesDirectory = new File(resource.getDirectory());
546                 if (resourcesDirectory.isDirectory()) {
547                     String includes = request.getResourceIncludes();
548                     String excludes = request.getResourceExcludes();
549 
550                     // MCHECKSTYLE-214: Only with project-root respect in/excludes, otherwise you'll get every file
551                     if (resourcesDirectory.equals(request.getProject().getBasedir())) {
552                         String resourceIncludes =
553                                 StringUtils.join(resource.getIncludes().iterator(), ",");
554                         if (includes == null || includes.isEmpty()) {
555                             includes = resourceIncludes;
556                         } else {
557                             includes += "," + resourceIncludes;
558                         }
559 
560                         String resourceExcludes =
561                                 StringUtils.join(resource.getExcludes().iterator(), ",");
562                         if (excludes == null || excludes.isEmpty()) {
563                             excludes = resourceExcludes;
564                         } else {
565                             excludes += "," + resourceExcludes;
566                         }
567                     }
568 
569                     List<File> resourceFiles = FileUtils.getFiles(resourcesDirectory, includes, excludes);
570                     files.addAll(resourceFiles);
571                     getLogger()
572                             .debug("Added " + resourceFiles.size() + " resource files found in '"
573                                     + resourcesDirectory.getAbsolutePath() + "'.");
574                 } else {
575                     getLogger()
576                             .debug("The resources directory '" + resourcesDirectory.getAbsolutePath()
577                                     + "' does not exist or is not a directory.");
578                 }
579             }
580         }
581     }
582 
583     private FilterSet getSuppressionsFilterSet(final String suppressionsFilePath) throws CheckstyleExecutorException {
584         if (suppressionsFilePath == null) {
585             return null;
586         }
587 
588         try {
589             return SuppressionsLoader.loadSuppressions(suppressionsFilePath);
590         } catch (CheckstyleException ce) {
591             throw new CheckstyleExecutorException("Failed to load suppressions file from: " + suppressionsFilePath, ce);
592         }
593     }
594 
595     private String getSuppressionsFilePath(final CheckstyleExecutorRequest request) throws CheckstyleExecutorException {
596         final String suppressionsLocation = request.getSuppressionsLocation();
597         if (suppressionsLocation == null || suppressionsLocation.isEmpty()) {
598             return null;
599         }
600 
601         try {
602             File suppressionsFile = locator.getResourceAsFile(suppressionsLocation, "checkstyle-suppressions.xml");
603             return suppressionsFile == null ? null : suppressionsFile.getAbsolutePath();
604         } catch (ResourceNotFoundException e) {
605             throw new CheckstyleExecutorException(
606                     "Unable to find suppressions file at location: " + suppressionsLocation, e);
607         } catch (FileResourceCreationException e) {
608             throw new CheckstyleExecutorException(
609                     "Unable to process suppressions file location: " + suppressionsLocation, e);
610         }
611     }
612 
613     private String getConfigFile(CheckstyleExecutorRequest request) throws CheckstyleExecutorException {
614         try {
615             if (getLogger().isDebugEnabled()) {
616                 getLogger().debug("request.getConfigLocation() " + request.getConfigLocation());
617             }
618 
619             File configFile = locator.getResourceAsFile(request.getConfigLocation(), "checkstyle-checker.xml");
620             if (configFile == null) {
621                 throw new CheckstyleExecutorException(
622                         "Unable to process config location: " + request.getConfigLocation());
623             }
624             return configFile.getAbsolutePath();
625         } catch (ResourceNotFoundException e) {
626             throw new CheckstyleExecutorException(
627                     "Unable to find configuration file at location: " + request.getConfigLocation(), e);
628         } catch (FileResourceCreationException e) {
629             throw new CheckstyleExecutorException(
630                     "Unable to process configuration file at location: " + request.getConfigLocation(), e);
631         }
632     }
633 
634     /**
635      * Configures search paths in the resource locator.
636      * This method should only be called once per execution.
637      *
638      * @param request executor request data.
639      */
640     private void configureResourceLocator(
641             final ResourceManager resourceManager,
642             final CheckstyleExecutorRequest request,
643             final List<Artifact> additionalArtifacts) {
644         final MavenProject project = request.getProject();
645         resourceManager.setOutputDirectory(new File(project.getBuild().getDirectory()));
646 
647         // Recurse up the parent hierarchy and add project directories to the search roots
648         MavenProject parent = project;
649         while (parent != null && parent.getFile() != null) {
650             // MCHECKSTYLE-131 ( olamy ) I don't like this hack.
651             // (dkulp) Me either.   It really pollutes the location stuff
652             // by allowing searches of stuff outside the current module.
653             File dir = parent.getFile().getParentFile();
654             resourceManager.addSearchPath(FileResourceLoader.ID, dir.getAbsolutePath());
655             parent = parent.getParent();
656         }
657         resourceManager.addSearchPath("url", "");
658 
659         // MCHECKSTYLE-225: load licenses from additional artifacts, not from classpath
660         if (additionalArtifacts != null) {
661             for (Artifact licenseArtifact : additionalArtifacts) {
662                 try {
663                     resourceManager.addSearchPath(
664                             "jar", "jar:" + licenseArtifact.getFile().toURI().toURL());
665                 } catch (MalformedURLException e) {
666                     // noop
667                 }
668             }
669         }
670     }
671 }