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