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