View Javadoc
1   package org.apache.maven.plugins.dependency;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   * http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.File;
23  import java.io.IOException;
24  import java.util.ArrayList;
25  import java.util.Arrays;
26  import java.util.HashSet;
27  import java.util.LinkedHashSet;
28  import java.util.List;
29  import java.util.Set;
30  
31  import org.apache.maven.artifact.Artifact;
32  import org.apache.maven.artifact.ArtifactUtils;
33  import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
34  import org.apache.maven.artifact.repository.ArtifactRepository;
35  import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
36  import org.apache.maven.artifact.resolver.ArtifactResolutionException;
37  import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
38  import org.apache.maven.execution.MavenSession;
39  import org.apache.maven.model.Dependency;
40  import org.apache.maven.plugin.AbstractMojo;
41  import org.apache.maven.plugin.MojoExecution;
42  import org.apache.maven.plugin.MojoExecution.Source;
43  import org.apache.maven.plugin.MojoExecutionException;
44  import org.apache.maven.plugin.MojoFailureException;
45  import org.apache.maven.plugins.annotations.Component;
46  import org.apache.maven.plugins.annotations.Mojo;
47  import org.apache.maven.plugins.annotations.Parameter;
48  import org.apache.maven.project.MavenProject;
49  import org.apache.maven.shared.artifact.filter.resolve.AbstractFilter;
50  import org.apache.maven.shared.artifact.filter.resolve.AndFilter;
51  import org.apache.maven.shared.artifact.filter.resolve.Node;
52  import org.apache.maven.shared.artifact.filter.resolve.PatternExclusionsFilter;
53  import org.apache.maven.shared.artifact.filter.resolve.PatternInclusionsFilter;
54  import org.apache.maven.shared.artifact.filter.resolve.ScopeFilter;
55  import org.apache.maven.shared.artifact.filter.resolve.TransformableFilter;
56  import org.apache.maven.shared.artifact.filter.resolve.transform.ArtifactIncludeFilterTransformer;
57  import org.apache.maven.shared.transfer.artifact.DefaultArtifactCoordinate;
58  import org.apache.maven.shared.transfer.artifact.TransferUtils;
59  import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResolver;
60  import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResolverException;
61  import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResult;
62  import org.apache.maven.shared.transfer.dependencies.resolve.DependencyResolver;
63  import org.apache.maven.shared.transfer.dependencies.resolve.DependencyResolverException;
64  import org.apache.maven.shared.utils.logging.MessageBuilder;
65  import org.apache.maven.shared.utils.logging.MessageUtils;
66  import org.codehaus.plexus.util.FileUtils;
67  import org.codehaus.plexus.util.StringUtils;
68  
69  /**
70   * When run on a project, remove the project dependencies from the local repository, and optionally re-resolve them.
71   * Outside of a project, remove the manually given dependencies.
72   *
73   * @author jdcasey
74   * @since 2.0
75   */
76  @Mojo( name = "purge-local-repository", threadSafe = true, requiresProject = false )
77  public class PurgeLocalRepositoryMojo
78      extends AbstractMojo
79  {
80  
81      private static final String VERSION_FUZZINESS = "version";
82  
83      private static final String ARTIFACT_ID_FUZZINESS = "artifactId";
84  
85      private static final String GROUP_ID_FUZZINESS = "groupId";
86  
87      /**
88       * The Maven projects in the reactor.
89       */
90      @Parameter( defaultValue = "${reactorProjects}", readonly = true, required = true )
91      private List<MavenProject> reactorProjects;
92  
93      /**
94       * The current Maven project.
95       */
96      @Parameter( defaultValue = "${project}", readonly = true, required = true )
97      private MavenProject project;
98  
99      @Parameter( defaultValue = "${session}", readonly = true, required = true )
100     private MavenSession session;
101 
102     /**
103      * This mojo execution, used to determine if it was launched from the lifecycle or the command-line.
104      */
105     @Parameter( defaultValue = "${mojo}", required = true, readonly = true )
106     private MojoExecution mojoExecution;
107 
108     /**
109      * Artifact handler manager.
110      */
111     @Component
112     private ArtifactHandlerManager artifactHandlerManager;
113 
114     /**
115      * The list of dependencies in the form of groupId:artifactId which should BE deleted/purged from the local
116      * repository. Note that using this parameter will deactivate the normal process for purging the current project
117      * dependency tree. If this parameter is used, only the included artifacts will be purged. The manualIncludes
118      * parameter should not be used in combination with the includes/excludes parameters.
119      *
120      * @since 2.6
121      */
122     @Parameter
123     private List<String> manualIncludes;
124 
125     /**
126      * Comma-separated list of groupId:artifactId entries, which should be used to manually include artifacts for
127      * deletion. This is a command-line alternative to the <code>manualIncludes</code> parameter, since List parameters
128      * are not currently compatible with CLI specification.
129      *
130      * @since 2.6
131      */
132     @Parameter( property = "manualInclude" )
133     private String manualInclude;
134 
135     /**
136      * The list of dependencies in the form of groupId:artifactId which should BE deleted/refreshed.
137      *
138      * @since 2.6
139      */
140     @Parameter
141     private List<String> includes;
142 
143     /**
144      * Comma-separated list of groupId:artifactId entries, which should be used to include artifacts for
145      * deletion/refresh. This is a command-line alternative to the <code>includes</code> parameter, since List
146      * parameters are not currently compatible with CLI specification.
147      *
148      * @since 2.6
149      */
150     @Parameter( property = "include" )
151     private String include;
152 
153     /**
154      * The list of dependencies in the form of groupId:artifactId which should NOT be deleted/refreshed.
155      */
156     @Parameter
157     private List<String> excludes;
158 
159     /**
160      * Comma-separated list of groupId:artifactId entries, which should be used to exclude artifacts from
161      * deletion/refresh. This is a command-line alternative to the <code>excludes</code> parameter, since List
162      * parameters are not currently compatible with CLI specification.
163      */
164     @Parameter( property = "exclude" )
165     private String exclude;
166 
167     /**
168      * Whether to re-resolve the artifacts once they have been deleted from the local repository. If you are running
169      * this mojo from the command-line, you may want to disable this. By default, artifacts will be re-resolved.
170      */
171     @Parameter( property = "reResolve", defaultValue = "true" )
172     private boolean reResolve;
173 
174     /**
175      * The local repository, from which to delete artifacts.
176      */
177     @Parameter( defaultValue = "${localRepository}", readonly = true, required = true )
178     private ArtifactRepository localRepository;
179 
180     /**
181      * The dependency resolver
182      */
183     @Component
184     private DependencyResolver dependencyResolver;
185 
186     /**
187      * The artifact resolver used to re-resolve dependencies, if that option is enabled.
188      */
189     @Component
190     private ArtifactResolver artifactResolver;
191 
192     /**
193      * Determines how liberally the plugin will delete an artifact from the local repository. Values are: <br/>
194      * <ul>
195      * <li><b>file</b> - Eliminate only the artifact's file.</li>
196      * <li><b>version</b> <i>(default)</i> - Eliminate all files associated with the version of the artifact.</li>
197      * <li><b>artifactId</b> - Eliminate all files associated with the artifact's artifactId.</li>
198      * <li><b>groupId</b> - Eliminate all files associated with the artifact's groupId.</li>
199      * </ul>
200      */
201     @Parameter( property = "resolutionFuzziness", defaultValue = "version" )
202     private String resolutionFuzziness;
203 
204     /**
205      * Whether this mojo should act on all transitive dependencies. Default value is true.
206      */
207     @Parameter( property = "actTransitively", defaultValue = "true" )
208     private boolean actTransitively;
209 
210     /**
211      * Whether this plugin should output verbose messages. Default is false.
212      */
213     @Parameter( property = "verbose", defaultValue = "false" )
214     private boolean verbose;
215 
216     /**
217      * Whether to purge only snapshot artifacts.
218      *
219      * @since 2.4
220      */
221     @Parameter( property = "snapshotsOnly", defaultValue = "false" )
222     private boolean snapshotsOnly;
223 
224     /**
225      * Skip plugin execution completely.
226      *
227      * @since 2.7
228      */
229     @Parameter( property = "skip", defaultValue = "false" )
230     private boolean skip;
231 
232     /**
233      * Includes only direct project dependencies.
234      */
235     private class DirectDependencyFilter
236         extends AbstractFilter
237     {
238         private Artifact projectArtifact;
239 
240         private List<Dependency> directDependencies;
241 
242         /**
243          * Default constructor
244          *
245          * @param directDependencies Set of dependencies objects which represent the direct dependencies of the project
246          */
247         DirectDependencyFilter( Artifact projectArtifact, List<Dependency> directDependencies )
248         {
249             this.projectArtifact = projectArtifact;
250             this.directDependencies = directDependencies;
251         }
252 
253         @Override
254         public boolean accept( Node node, List<Node> parents )
255         {
256 
257             if ( artifactsGAMatch( node, projectArtifact.getGroupId(), projectArtifact.getArtifactId() ) )
258             {
259                 return true;
260             }
261             for ( Dependency dep : directDependencies )
262             {
263                 if ( this.artifactsGAMatch( node, dep.getGroupId(), dep.getArtifactId() ) )
264                 {
265                     return true;
266                 }
267             }
268             return false;
269         }
270 
271         /*
272          * Compare the groupId:artifactId of two artifacts.
273          */
274         private boolean artifactsGAMatch( Node node, String groupId, String artifactId )
275         {
276             if ( node.getDependency() == null )
277             {
278                 return false;
279             }
280 
281             if ( !node.getDependency().getGroupId().equals( groupId ) )
282             {
283                 getLog().debug( "Different groupId: " + node.getDependency() + "  " + groupId );
284                 return false;
285             }
286             if ( !node.getDependency().getArtifactId().equals( artifactId ) )
287             {
288                 getLog().debug( "Different artifactId: " + node.getDependency() + "  " + artifactId );
289                 return false;
290             }
291             return true;
292         }
293     }
294 
295     /**
296      * Includes only snapshot artifacts
297      */
298     private static class SnapshotsFilter
299         extends AbstractFilter
300     {
301         @Override
302         public boolean accept( Node node, List<Node> parents )
303         {
304             if ( node.getDependency() == null )
305             {
306                 return false;
307             }
308             else
309             {
310                 return ArtifactUtils.isSnapshot( node.getDependency().getVersion() );
311             }
312         }
313     }
314 
315     @Override
316     public void execute()
317         throws MojoExecutionException, MojoFailureException
318     {
319         if ( isSkip() )
320         {
321             getLog().info( "Skipping plugin execution" );
322             return;
323         }
324 
325         if ( !StringUtils.isEmpty( manualInclude ) )
326         {
327             manualIncludes = this.parseIncludes( manualInclude );
328         }
329         // If it's a manual purge, the only step is to delete from the local repo
330         if ( manualIncludes != null && manualIncludes.size() > 0 )
331         {
332             manualPurge( manualIncludes );
333             return;
334         }
335 
336         Set<Artifact> purgedArtifacts = new HashSet<>();
337         if ( shouldPurgeAllProjectsInReactor() )
338         {
339             for ( MavenProject reactorProject : reactorProjects )
340             {
341                 purgeLocalRepository( reactorProject, purgedArtifacts );
342             }
343         }
344         else
345         {
346             purgeLocalRepository( project, purgedArtifacts );
347         }
348     }
349 
350     /**
351      * Determines if all projects in the reactor should be purged from their dependencies. When this goal is started on
352      * the command-line, it is always the case. When it is bound to a phase in the lifecycle, it is never the case.
353      *
354      * @return <code>true</code> if all projects in the reactor should be purged, <code>false</code> otherwise.
355      */
356     private boolean shouldPurgeAllProjectsInReactor()
357     {
358         Source source = mojoExecution.getSource();
359         return reactorProjects.size() > 1 && source == Source.CLI;
360     }
361 
362     /**
363      * Purges the local repository for the dependencies in the given Maven project.
364      *
365      * @param theProject Maven project.
366      * @param purgedArtifacts The artifacts that were already purged.
367      * @throws MojoFailureException in case of errors during the purge.
368      */
369     private void purgeLocalRepository( MavenProject theProject, Set<Artifact> purgedArtifacts )
370         throws MojoFailureException
371     {
372         List<Dependency> dependencies = theProject.getDependencies();
373 
374         TransformableFilter dependencyFilter = createPurgeArtifactsFilter( theProject, dependencies, purgedArtifacts );
375 
376         Set<Artifact> resolvedArtifactsToPurge =
377             getFilteredResolvedArtifacts( theProject, dependencies, dependencyFilter );
378 
379         if ( resolvedArtifactsToPurge.isEmpty() )
380         {
381             getLog().info( "No artifacts included for purge for project: " + getProjectKey( theProject ) );
382             return;
383         }
384 
385         purgeArtifacts( theProject, resolvedArtifactsToPurge );
386         purgedArtifacts.addAll( resolvedArtifactsToPurge );
387 
388         if ( reResolve )
389         {
390             getLog().info( "Re-resolving dependencies" );
391             ArtifactFilter artifactFilter = dependencyFilter.transform( new ArtifactIncludeFilterTransformer() );
392             try
393             {
394                 reResolveArtifacts( theProject, resolvedArtifactsToPurge, artifactFilter );
395             }
396             catch ( ArtifactResolutionException | ArtifactNotFoundException e )
397             {
398                 String failureMessage = "Failed to refresh project dependencies for: " + theProject.getId();
399                 throw new MojoFailureException( failureMessage, e );
400             }
401         }
402     }
403 
404     /**
405      * Purge/Delete artifacts from the local repository according to the given patterns.
406      *
407      * @param theIncludes The includes.
408      * @throws MojoExecutionException in case of an error.
409      */
410     private void manualPurge( List<String> theIncludes )
411         throws MojoExecutionException
412     {
413         MessageBuilder messageBuilder = MessageUtils.buffer();
414 
415         getLog().info( messageBuilder.a( "Deleting " ).strong( theIncludes.size() )
416             .a( " manual " )
417             .a( theIncludes.size() != 1 ? "dependencies" : "dependency" )
418             .a( " from " )
419             .strong( localRepository.getBasedir() )
420             .toString() );
421 
422         for ( String gavPattern : theIncludes )
423         {
424             if ( StringUtils.isEmpty( gavPattern ) )
425             {
426                 getLog().debug( "Skipping empty gav pattern" );
427                 continue;
428             }
429 
430             String relativePath = gavToPath( gavPattern );
431             if ( StringUtils.isEmpty( relativePath ) )
432             {
433                 getLog().debug( "Skipping empty relative path for gav pattern: " + gavPattern );
434                 continue;
435             }
436 
437             File purgeDir = new File( localRepository.getBasedir(), relativePath );
438             if ( purgeDir.exists() )
439             {
440                 getLog().debug( "Deleting directory: " + purgeDir );
441                 try
442                 {
443                     FileUtils.deleteDirectory( purgeDir );
444                 }
445                 catch ( IOException e )
446                 {
447                     throw new MojoExecutionException( "Unable to purge directory: " + purgeDir );
448                 }
449             }
450             else
451             {
452                 getLog().debug( "Directory: " + purgeDir + " doesn't exist" );
453             }
454         }
455     }
456 
457     /**
458      * Convert a groupId:artifactId:version to a file system path
459      *
460      * @param gav, the groupId:artifactId:version string
461      * @return the corresponding path
462      */
463     private String gavToPath( String gav )
464     {
465         if ( StringUtils.isEmpty( gav ) )
466         {
467             return null;
468         }
469 
470         String[] pathComponents = gav.split( ":" );
471 
472         StringBuilder path = new StringBuilder( pathComponents[0].replace( '.', '/' ) );
473 
474         for ( int i = 1; i < pathComponents.length; ++i )
475         {
476             path.append( "/" ).append( pathComponents[i] );
477         }
478 
479         return path.toString();
480     }
481 
482     /**
483      * Create the includes exclude filter to use when resolving and purging dependencies Also excludes any "system"
484      * scope dependencies
485      *
486      * @param theProject The Maven project.
487      * @param dependencies The dependencies to use as a reference if we're excluding transitive dependencies
488      * @param purgedArtifacts The artifacts already purged.
489      * @return the created filter
490      */
491     private TransformableFilter createPurgeArtifactsFilter( MavenProject theProject, List<Dependency> dependencies,
492                                                             Set<Artifact> purgedArtifacts )
493     {
494         List<TransformableFilter> subFilters = new ArrayList<>();
495 
496         // System dependencies should never be purged
497         subFilters.add( ScopeFilter.excluding( Artifact.SCOPE_SYSTEM ) );
498 
499         if ( this.snapshotsOnly )
500         {
501             subFilters.add( new SnapshotsFilter() );
502         }
503 
504         // The CLI includes/excludes overrides configuration in the pom
505         if ( !StringUtils.isEmpty( this.include ) )
506         {
507             this.includes = parseIncludes( this.include );
508         }
509         if ( this.includes != null )
510         {
511             subFilters.add( new PatternInclusionsFilter( includes ) );
512         }
513 
514         if ( !StringUtils.isEmpty( this.exclude ) )
515         {
516             this.excludes = parseIncludes( this.exclude );
517         }
518         if ( this.excludes != null )
519         {
520             subFilters.add( new PatternExclusionsFilter( excludes ) );
521         }
522 
523         if ( !actTransitively )
524         {
525             subFilters.add( new DirectDependencyFilter( theProject.getArtifact(), dependencies ) );
526         }
527 
528         List<String> exclusions = new ArrayList<>( reactorProjects.size() );
529         // It doesn't make sense to include projects from the reactor here since they're likely not able to be resolved
530         for ( MavenProject reactorProject : reactorProjects )
531         {
532             exclusions.add( toPatternExcludes( reactorProject.getArtifact() ) );
533         }
534         // There is no need to consider a second time artifacts that were already purged (re-resolved or not)
535         for ( Artifact purgedArtifact : purgedArtifacts )
536         {
537             exclusions.add( toPatternExcludes( purgedArtifact ) );
538         }
539         subFilters.add( new PatternExclusionsFilter( exclusions ) );
540 
541         return new AndFilter( subFilters );
542     }
543 
544     /**
545      * Returns a string that represents a pattern for an exclude filter for the given artifact.
546      *
547      * @param artifact Artifact.
548      * @return String representation of a pattern for an exclude filter for the given artifact.
549      */
550     private String toPatternExcludes( Artifact artifact )
551     {
552         return artifact.getGroupId() + ":" + artifact.getArtifactId() + ":"
553             + artifact.getArtifactHandler().getExtension() + ":" + artifact.getVersion();
554     }
555 
556     /**
557      * Convert comma separated list of includes to List object
558      *
559      * @param theInclude The list of includes
560      * @return the includes list
561      */
562     private List<String> parseIncludes( String theInclude )
563     {
564         List<String> theIncludes = new ArrayList<>();
565 
566         if ( theInclude != null )
567         {
568             String[] elements = theInclude.split( "," );
569             theIncludes.addAll( Arrays.asList( elements ) );
570         }
571 
572         return theIncludes;
573     }
574 
575     private Set<Artifact> getFilteredResolvedArtifacts( MavenProject theProject, List<Dependency> dependencies,
576                                                         TransformableFilter filter )
577     {
578         try
579         {
580             Iterable<ArtifactResult> results =
581                 dependencyResolver.resolveDependencies( session.getProjectBuildingRequest(), theProject.getModel(),
582                                                         filter );
583 
584             Set<Artifact> resolvedArtifacts = new LinkedHashSet<>();
585 
586             for ( ArtifactResult artResult : results )
587             {
588                 resolvedArtifacts.add( artResult.getArtifact() );
589             }
590 
591             return resolvedArtifacts;
592         }
593         catch ( DependencyResolverException e )
594         {
595             getLog().info( "Unable to resolve all dependencies for: " + getProjectKey( theProject )
596                 + ". Falling back to non-transitive mode for initial artifact resolution." );
597         }
598 
599         Set<Artifact> resolvedArtifacts = new LinkedHashSet<>();
600 
601         ArtifactFilter artifactFilter = filter.transform( new ArtifactIncludeFilterTransformer() );
602 
603         for ( Dependency dependency : dependencies )
604         {
605             DefaultArtifactCoordinate coordinate = new DefaultArtifactCoordinate();
606             coordinate.setGroupId( dependency.getGroupId() );
607             coordinate.setArtifactId( dependency.getArtifactId() );
608             coordinate.setVersion( dependency.getVersion() );
609             coordinate.setExtension( artifactHandlerManager.getArtifactHandler( dependency.getType() ).getExtension() );
610             try
611             {
612                 Artifact artifact =
613                     artifactResolver.resolveArtifact( session.getProjectBuildingRequest(), coordinate ).getArtifact();
614                 if ( artifactFilter.include( artifact ) )
615                 {
616                     resolvedArtifacts.add( artifact );
617                 }
618             }
619             catch ( ArtifactResolverException e )
620             {
621                 getLog().debug( "Unable to resolve artifact: " + coordinate );
622             }
623         }
624         return resolvedArtifacts;
625     }
626 
627     private void purgeArtifacts( MavenProject theProject, Set<Artifact> artifacts )
628         throws MojoFailureException
629     {
630         MessageBuilder messageBuilder = MessageUtils.buffer();
631 
632         getLog().info( messageBuilder.a( "Deleting " ).strong( artifacts.size() )
633             .a( " " ).strong( actTransitively ? "transitive" : "direct" )
634             .a( artifacts.size() != 1 ? " dependencies" : " dependency" )
635             .a( " for project " ).strong( getProjectKey( theProject ) )
636             .a( " from " ).strong( localRepository.getBasedir() )
637             .a( " with artifact " ).strong( resolutionFuzziness ).a( " resolution fuzziness" )
638             .toString() );
639 
640         for ( Artifact artifact : artifacts )
641         {
642             verbose( "Purging artifact: " + artifact.getId() );
643 
644             File deleteTarget = findDeleteTarget( artifact );
645 
646             verbose( "Deleting: " + deleteTarget );
647 
648             if ( deleteTarget.isDirectory() )
649             {
650                 try
651                 {
652                     FileUtils.deleteDirectory( deleteTarget );
653                 }
654                 catch ( IOException e )
655                 {
656                     getLog().warn( "Unable to purge local repository location: " + deleteTarget, e );
657                 }
658             }
659             else
660             {
661                 if ( !deleteTarget.delete() )
662                 {
663                     deleteTarget.deleteOnExit();
664                     getLog().warn( "Unable to purge local repository location immediately: " + deleteTarget );
665                 }
666             }
667             artifact.setResolved( false );
668         }
669     }
670 
671     private void reResolveArtifacts( MavenProject theProject, Set<Artifact> artifacts, ArtifactFilter filter )
672         throws ArtifactResolutionException, ArtifactNotFoundException
673     {
674         // Always need to re-resolve the poms in case they were purged along with the artifact
675         // because Maven 2 will not automatically re-resolve them when resolving the artifact
676         for ( Artifact artifact : artifacts )
677         {
678             verbose( "Resolving artifact: " + artifact.getId() );
679 
680             try
681             {
682                 //CHECKSTYLE_OFF: LineLength
683                 artifactResolver.resolveArtifact( session.getProjectBuildingRequest(),
684                                                   TransferUtils.toArtifactCoordinate( artifact ) );
685                 //CHECKSTYLE_ON: LineLength
686             }
687             catch ( ArtifactResolverException e )
688             {
689                 verbose( e.getMessage() );
690             }
691         }
692 
693         List<Artifact> missingArtifacts = new ArrayList<>();
694 
695         for ( Artifact artifact : artifacts )
696         {
697             try
698             {
699                 artifactResolver.resolveArtifact( session.getProjectBuildingRequest(), artifact );
700             }
701             catch ( ArtifactResolverException e )
702             {
703                 verbose( e.getMessage() );
704                 missingArtifacts.add( artifact );
705             }
706         }
707 
708         if ( missingArtifacts.size() > 0 )
709         {
710             StringBuilder message = new StringBuilder( "required artifacts missing:" );
711             message.append( System.lineSeparator() );
712             for ( Artifact missingArtifact : missingArtifacts )
713             {
714                 message.append( "  " ).append( missingArtifact.getId() ).append( System.lineSeparator() );
715             }
716             message.append( System.lineSeparator() );
717             message.append( "for the artifact:" );
718 
719             throw new ArtifactResolutionException( message.toString(), theProject.getArtifact(),
720                                                    theProject.getRemoteArtifactRepositories() );
721         }
722     }
723 
724     private File findDeleteTarget( Artifact artifact )
725     {
726         // Use localRepository.pathOf() in case artifact.getFile() is not set
727         File deleteTarget = new File( localRepository.getBasedir(), localRepository.pathOf( artifact ) );
728 
729         if ( GROUP_ID_FUZZINESS.equals( resolutionFuzziness ) )
730         {
731             // get the groupId dir.
732             deleteTarget = deleteTarget.getParentFile().getParentFile().getParentFile();
733         }
734         else if ( ARTIFACT_ID_FUZZINESS.equals( resolutionFuzziness ) )
735         {
736             // get the artifactId dir.
737             deleteTarget = deleteTarget.getParentFile().getParentFile();
738         }
739         else if ( VERSION_FUZZINESS.equals( resolutionFuzziness ) )
740         {
741             // get the version dir.
742             deleteTarget = deleteTarget.getParentFile();
743         }
744         // else it's file fuzziness.
745         return deleteTarget;
746     }
747 
748     private void verbose( String message )
749     {
750         if ( verbose || getLog().isDebugEnabled() )
751         {
752             getLog().info( message );
753         }
754     }
755 
756     private String getProjectKey( MavenProject project )
757     {
758         return project.getArtifactId();
759     }
760 
761     /**
762      * @return {@link #skip}
763      */
764     public boolean isSkip()
765     {
766         return skip;
767     }
768 
769     /**
770      * @param skip {@link #skip}
771      */
772     public void setSkip( boolean skip )
773     {
774         this.skip = skip;
775     }
776 }