1   
2   
3   
4   
5   
6   
7   
8   
9   
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  package org.apache.maven.plugins.dependency;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.util.ArrayList;
24  import java.util.Arrays;
25  import java.util.HashSet;
26  import java.util.LinkedHashSet;
27  import java.util.List;
28  import java.util.Set;
29  
30  import org.apache.maven.artifact.Artifact;
31  import org.apache.maven.artifact.ArtifactUtils;
32  import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
33  import org.apache.maven.artifact.repository.ArtifactRepository;
34  import org.apache.maven.artifact.resolver.ArtifactResolutionException;
35  import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
36  import org.apache.maven.execution.MavenSession;
37  import org.apache.maven.model.Dependency;
38  import org.apache.maven.plugin.AbstractMojo;
39  import org.apache.maven.plugin.MojoExecution;
40  import org.apache.maven.plugin.MojoExecution.Source;
41  import org.apache.maven.plugin.MojoExecutionException;
42  import org.apache.maven.plugin.MojoFailureException;
43  import org.apache.maven.plugins.annotations.Component;
44  import org.apache.maven.plugins.annotations.Mojo;
45  import org.apache.maven.plugins.annotations.Parameter;
46  import org.apache.maven.project.MavenProject;
47  import org.apache.maven.shared.artifact.filter.resolve.AbstractFilter;
48  import org.apache.maven.shared.artifact.filter.resolve.AndFilter;
49  import org.apache.maven.shared.artifact.filter.resolve.Node;
50  import org.apache.maven.shared.artifact.filter.resolve.PatternExclusionsFilter;
51  import org.apache.maven.shared.artifact.filter.resolve.PatternInclusionsFilter;
52  import org.apache.maven.shared.artifact.filter.resolve.ScopeFilter;
53  import org.apache.maven.shared.artifact.filter.resolve.TransformableFilter;
54  import org.apache.maven.shared.artifact.filter.resolve.transform.ArtifactIncludeFilterTransformer;
55  import org.apache.maven.shared.transfer.artifact.DefaultArtifactCoordinate;
56  import org.apache.maven.shared.transfer.artifact.TransferUtils;
57  import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResolver;
58  import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResolverException;
59  import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResult;
60  import org.apache.maven.shared.transfer.dependencies.resolve.DependencyResolver;
61  import org.apache.maven.shared.transfer.dependencies.resolve.DependencyResolverException;
62  import org.apache.maven.shared.utils.logging.MessageBuilder;
63  import org.apache.maven.shared.utils.logging.MessageUtils;
64  import org.codehaus.plexus.util.FileUtils;
65  
66  
67  
68  
69  
70  
71  
72  
73  @Mojo(name = "purge-local-repository", threadSafe = true, requiresProject = false)
74  public class PurgeLocalRepositoryMojo extends AbstractMojo {
75  
76      private static final String VERSION_FUZZINESS = "version";
77  
78      private static final String ARTIFACT_ID_FUZZINESS = "artifactId";
79  
80      private static final String GROUP_ID_FUZZINESS = "groupId";
81  
82      
83  
84  
85      @Parameter(defaultValue = "${reactorProjects}", readonly = true, required = true)
86      private List<MavenProject> reactorProjects;
87  
88      
89  
90  
91      @Component
92      private MavenProject project;
93  
94      @Component
95      private MavenSession session;
96  
97      
98  
99  
100     @Parameter(defaultValue = "${mojo}", required = true, readonly = true)
101     private MojoExecution mojoExecution;
102 
103     
104 
105 
106     @Component
107     private ArtifactHandlerManager artifactHandlerManager;
108 
109     
110 
111 
112 
113 
114 
115 
116 
117     @Parameter
118     private List<String> manualIncludes;
119 
120     
121 
122 
123 
124 
125 
126 
127     @Parameter(property = "manualInclude")
128     private String manualInclude;
129 
130     
131 
132 
133 
134 
135     @Parameter
136     private List<String> includes;
137 
138     
139 
140 
141 
142 
143 
144 
145     @Parameter(property = "include")
146     private String include;
147 
148     
149 
150 
151     @Parameter
152     private List<String> excludes;
153 
154     
155 
156 
157 
158 
159     @Parameter(property = "exclude")
160     private String exclude;
161 
162     
163 
164 
165 
166     @Parameter(property = "reResolve", defaultValue = "true")
167     private boolean reResolve;
168 
169     
170 
171 
172     @Parameter(defaultValue = "${localRepository}", readonly = true, required = true)
173     private ArtifactRepository localRepository;
174 
175     
176 
177 
178     @Component
179     private DependencyResolver dependencyResolver;
180 
181     
182 
183 
184     @Component
185     private ArtifactResolver artifactResolver;
186 
187     
188 
189 
190 
191 
192 
193 
194 
195 
196     @Parameter(property = "resolutionFuzziness", defaultValue = "version")
197     private String resolutionFuzziness;
198 
199     
200 
201 
202     @Parameter(property = "actTransitively", defaultValue = "true")
203     private boolean actTransitively;
204 
205     
206 
207 
208     @Parameter(property = "verbose", defaultValue = "false")
209     private boolean verbose;
210 
211     
212 
213 
214 
215 
216     @Parameter(property = "snapshotsOnly", defaultValue = "false")
217     private boolean snapshotsOnly;
218 
219     
220 
221 
222 
223 
224     @Parameter(property = "skip", defaultValue = "false")
225     private boolean skip;
226 
227     
228 
229 
230     private class DirectDependencyFilter extends AbstractFilter {
231         private final Artifact projectArtifact;
232 
233         private final List<Dependency> directDependencies;
234 
235         
236 
237 
238 
239 
240         DirectDependencyFilter(Artifact projectArtifact, List<Dependency> directDependencies) {
241             this.projectArtifact = projectArtifact;
242             this.directDependencies = directDependencies;
243         }
244 
245         @Override
246         public boolean accept(Node node, List<Node> parents) {
247 
248             if (artifactsGAMatch(node, projectArtifact.getGroupId(), projectArtifact.getArtifactId())) {
249                 return true;
250             }
251             for (Dependency dep : directDependencies) {
252                 if (this.artifactsGAMatch(node, dep.getGroupId(), dep.getArtifactId())) {
253                     return true;
254                 }
255             }
256             return false;
257         }
258 
259         
260 
261 
262         private boolean artifactsGAMatch(Node node, String groupId, String artifactId) {
263             if (node.getDependency() == null) {
264                 return false;
265             }
266 
267             if (!node.getDependency().getGroupId().equals(groupId)) {
268                 getLog().debug("Different groupId: " + node.getDependency() + "  " + groupId);
269                 return false;
270             }
271             if (!node.getDependency().getArtifactId().equals(artifactId)) {
272                 getLog().debug("Different artifactId: " + node.getDependency() + "  " + artifactId);
273                 return false;
274             }
275             return true;
276         }
277     }
278 
279     
280 
281 
282     private static class SnapshotsFilter extends AbstractFilter {
283         @Override
284         public boolean accept(Node node, List<Node> parents) {
285             if (node.getDependency() == null) {
286                 return false;
287             } else {
288                 return ArtifactUtils.isSnapshot(node.getDependency().getVersion());
289             }
290         }
291     }
292 
293     @Override
294     public void execute() throws MojoExecutionException, MojoFailureException {
295         if (isSkip()) {
296             getLog().info("Skipping plugin execution");
297             return;
298         }
299 
300         if (!(manualInclude == null || manualInclude.isEmpty())) {
301             manualIncludes = this.parseIncludes(manualInclude);
302         }
303         
304         if (manualIncludes != null && !manualIncludes.isEmpty()) {
305             manualPurge(manualIncludes);
306             return;
307         }
308 
309         Set<Artifact> purgedArtifacts = new HashSet<>();
310         if (shouldPurgeAllProjectsInReactor()) {
311             for (MavenProject reactorProject : reactorProjects) {
312                 purgeLocalRepository(reactorProject, purgedArtifacts);
313             }
314         } else {
315             purgeLocalRepository(project, purgedArtifacts);
316         }
317     }
318 
319     
320 
321 
322 
323 
324 
325     private boolean shouldPurgeAllProjectsInReactor() {
326         Source source = mojoExecution.getSource();
327         return reactorProjects.size() > 1 && source == Source.CLI;
328     }
329 
330     
331 
332 
333 
334 
335 
336 
337     private void purgeLocalRepository(MavenProject theProject, Set<Artifact> purgedArtifacts)
338             throws MojoFailureException {
339         List<Dependency> dependencies = theProject.getDependencies();
340 
341         TransformableFilter dependencyFilter = createPurgeArtifactsFilter(theProject, dependencies, purgedArtifacts);
342 
343         Set<Artifact> resolvedArtifactsToPurge =
344                 getFilteredResolvedArtifacts(theProject, dependencies, dependencyFilter);
345 
346         if (resolvedArtifactsToPurge.isEmpty()) {
347             getLog().info("No artifacts included for purge for project: " + getProjectKey(theProject));
348             return;
349         }
350 
351         purgeArtifacts(theProject, resolvedArtifactsToPurge);
352         purgedArtifacts.addAll(resolvedArtifactsToPurge);
353 
354         if (reResolve) {
355             getLog().info("Re-resolving dependencies");
356             try {
357                 reResolveArtifacts(theProject, resolvedArtifactsToPurge);
358             } catch (ArtifactResolutionException e) {
359                 String failureMessage = "Failed to refresh project dependencies for: " + theProject.getId();
360                 throw new MojoFailureException(failureMessage, e);
361             }
362         }
363     }
364 
365     
366 
367 
368 
369 
370 
371     private void manualPurge(List<String> theIncludes) throws MojoExecutionException {
372         MessageBuilder messageBuilder = MessageUtils.buffer();
373 
374         getLog().info(messageBuilder
375                 .a("Deleting ")
376                 .strong(theIncludes.size())
377                 .a(" manual ")
378                 .a(theIncludes.size() != 1 ? "dependencies" : "dependency")
379                 .a(" from ")
380                 .strong(localRepository.getBasedir())
381                 .build());
382 
383         for (String gavPattern : theIncludes) {
384             if (gavPattern == null || gavPattern.isEmpty()) {
385                 getLog().debug("Skipping empty gav pattern");
386                 continue;
387             }
388 
389             String relativePath = gavToPath(gavPattern);
390             if (relativePath == null || relativePath.isEmpty()) {
391                 getLog().debug("Skipping empty relative path for gav pattern: " + gavPattern);
392                 continue;
393             }
394 
395             File purgeDir = new File(localRepository.getBasedir(), relativePath);
396             if (purgeDir.exists()) {
397                 getLog().debug("Deleting directory: " + purgeDir);
398                 try {
399                     FileUtils.deleteDirectory(purgeDir);
400                 } catch (IOException e) {
401                     throw new MojoExecutionException("Unable to purge directory: " + purgeDir);
402                 }
403             } else {
404                 getLog().debug("Directory: " + purgeDir + " doesn't exist");
405             }
406         }
407     }
408 
409     
410 
411 
412 
413 
414 
415     private String gavToPath(String gav) {
416         if (gav == null || gav.isEmpty()) {
417             return null;
418         }
419 
420         String[] pathComponents = gav.split(":");
421 
422         StringBuilder path = new StringBuilder(pathComponents[0].replace('.', '/'));
423 
424         for (int i = 1; i < pathComponents.length; ++i) {
425             path.append("/").append(pathComponents[i]);
426         }
427 
428         return path.toString();
429     }
430 
431     
432 
433 
434 
435 
436 
437 
438 
439 
440     private TransformableFilter createPurgeArtifactsFilter(
441             MavenProject theProject, List<Dependency> dependencies, Set<Artifact> purgedArtifacts) {
442         List<TransformableFilter> subFilters = new ArrayList<>();
443 
444         
445         subFilters.add(ScopeFilter.excluding(Artifact.SCOPE_SYSTEM));
446 
447         if (this.snapshotsOnly) {
448             subFilters.add(new SnapshotsFilter());
449         }
450 
451         
452         if (!(this.include == null || this.include.isEmpty())) {
453             this.includes = parseIncludes(this.include);
454         }
455         if (this.includes != null) {
456             subFilters.add(new PatternInclusionsFilter(includes));
457         }
458 
459         if (!(this.exclude == null || this.exclude.isEmpty())) {
460             this.excludes = parseIncludes(this.exclude);
461         }
462         if (this.excludes != null) {
463             subFilters.add(new PatternExclusionsFilter(excludes));
464         }
465 
466         if (!actTransitively) {
467             subFilters.add(new DirectDependencyFilter(theProject.getArtifact(), dependencies));
468         }
469 
470         List<String> exclusions = new ArrayList<>(reactorProjects.size());
471         
472         for (MavenProject reactorProject : reactorProjects) {
473             exclusions.add(toPatternExcludes(reactorProject.getArtifact()));
474         }
475         
476         for (Artifact purgedArtifact : purgedArtifacts) {
477             exclusions.add(toPatternExcludes(purgedArtifact));
478         }
479         subFilters.add(new PatternExclusionsFilter(exclusions));
480 
481         return new AndFilter(subFilters);
482     }
483 
484     
485 
486 
487 
488 
489 
490     private String toPatternExcludes(Artifact artifact) {
491         return artifact.getGroupId() + ":" + artifact.getArtifactId() + ":"
492                 + artifact.getArtifactHandler().getExtension() + ":" + artifact.getVersion();
493     }
494 
495     
496 
497 
498 
499 
500 
501     private List<String> parseIncludes(String theInclude) {
502         List<String> theIncludes = new ArrayList<>();
503 
504         if (theInclude != null) {
505             String[] elements = theInclude.split(",");
506             theIncludes.addAll(Arrays.asList(elements));
507         }
508 
509         return theIncludes;
510     }
511 
512     private Set<Artifact> getFilteredResolvedArtifacts(
513             MavenProject theProject, List<Dependency> dependencies, TransformableFilter filter) {
514         try {
515             Iterable<ArtifactResult> results = dependencyResolver.resolveDependencies(
516                     session.getProjectBuildingRequest(), theProject.getModel(), filter);
517 
518             Set<Artifact> resolvedArtifacts = new LinkedHashSet<>();
519 
520             for (ArtifactResult artResult : results) {
521                 resolvedArtifacts.add(artResult.getArtifact());
522             }
523 
524             return resolvedArtifacts;
525         } catch (DependencyResolverException e) {
526             getLog().info("Unable to resolve all dependencies for: " + getProjectKey(theProject)
527                     + ". Falling back to non-transitive mode for initial artifact resolution.");
528         }
529 
530         Set<Artifact> resolvedArtifacts = new LinkedHashSet<>();
531 
532         ArtifactFilter artifactFilter = filter.transform(new ArtifactIncludeFilterTransformer());
533 
534         for (Dependency dependency : dependencies) {
535             DefaultArtifactCoordinate coordinate = new DefaultArtifactCoordinate();
536             coordinate.setGroupId(dependency.getGroupId());
537             coordinate.setArtifactId(dependency.getArtifactId());
538             coordinate.setVersion(dependency.getVersion());
539             coordinate.setExtension(artifactHandlerManager
540                     .getArtifactHandler(dependency.getType())
541                     .getExtension());
542             try {
543                 Artifact artifact = artifactResolver
544                         .resolveArtifact(session.getProjectBuildingRequest(), coordinate)
545                         .getArtifact();
546                 if (artifactFilter.include(artifact)) {
547                     resolvedArtifacts.add(artifact);
548                 }
549             } catch (ArtifactResolverException e) {
550                 getLog().debug("Unable to resolve artifact: " + coordinate);
551             }
552         }
553         return resolvedArtifacts;
554     }
555 
556     private void purgeArtifacts(MavenProject theProject, Set<Artifact> artifacts) {
557         MessageBuilder messageBuilder = MessageUtils.buffer();
558 
559         getLog().info(messageBuilder
560                 .a("Deleting ")
561                 .strong(artifacts.size())
562                 .a(" ")
563                 .strong(actTransitively ? "transitive" : "direct")
564                 .a(artifacts.size() != 1 ? " dependencies" : " dependency")
565                 .a(" for project ")
566                 .strong(getProjectKey(theProject))
567                 .a(" from ")
568                 .strong(localRepository.getBasedir())
569                 .a(" with artifact ")
570                 .strong(resolutionFuzziness)
571                 .a(" resolution fuzziness")
572                 .build());
573 
574         for (Artifact artifact : artifacts) {
575             verbose("Purging artifact: " + artifact.getId());
576 
577             File deleteTarget = findDeleteTarget(artifact);
578 
579             verbose("Deleting: " + deleteTarget);
580 
581             if (deleteTarget.isDirectory()) {
582                 try {
583                     FileUtils.deleteDirectory(deleteTarget);
584                 } catch (IOException e) {
585                     getLog().warn("Unable to purge local repository location: " + deleteTarget, e);
586                 }
587             } else {
588                 if (!deleteTarget.delete()) {
589                     deleteTarget.deleteOnExit();
590                     getLog().warn("Unable to purge local repository location immediately: " + deleteTarget);
591                 }
592             }
593             artifact.setResolved(false);
594         }
595     }
596 
597     private void reResolveArtifacts(MavenProject theProject, Set<Artifact> artifacts)
598             throws ArtifactResolutionException {
599         
600         
601         for (Artifact artifact : artifacts) {
602             verbose("Resolving artifact: " + artifact.getId());
603 
604             try {
605                 
606                 artifactResolver.resolveArtifact(
607                         session.getProjectBuildingRequest(), TransferUtils.toArtifactCoordinate(artifact));
608                 
609             } catch (ArtifactResolverException e) {
610                 verbose(e.getMessage());
611             }
612         }
613 
614         List<Artifact> missingArtifacts = new ArrayList<>();
615 
616         for (Artifact artifact : artifacts) {
617             try {
618                 artifactResolver.resolveArtifact(session.getProjectBuildingRequest(), artifact);
619             } catch (ArtifactResolverException e) {
620                 verbose(e.getMessage());
621                 missingArtifacts.add(artifact);
622             }
623         }
624 
625         if (!missingArtifacts.isEmpty()) {
626             StringBuilder message = new StringBuilder("required artifacts missing:");
627             message.append(System.lineSeparator());
628             for (Artifact missingArtifact : missingArtifacts) {
629                 message.append("  ").append(missingArtifact.getId()).append(System.lineSeparator());
630             }
631             message.append(System.lineSeparator());
632             message.append("for the artifact:");
633 
634             throw new ArtifactResolutionException(
635                     message.toString(), theProject.getArtifact(), theProject.getRemoteArtifactRepositories());
636         }
637     }
638 
639     private File findDeleteTarget(Artifact artifact) {
640         
641         File deleteTarget = new File(localRepository.getBasedir(), localRepository.pathOf(artifact));
642 
643         if (GROUP_ID_FUZZINESS.equals(resolutionFuzziness)) {
644             
645             deleteTarget = deleteTarget.getParentFile().getParentFile().getParentFile();
646         } else if (ARTIFACT_ID_FUZZINESS.equals(resolutionFuzziness)) {
647             
648             deleteTarget = deleteTarget.getParentFile().getParentFile();
649         } else if (VERSION_FUZZINESS.equals(resolutionFuzziness)) {
650             
651             deleteTarget = deleteTarget.getParentFile();
652         }
653         
654         return deleteTarget;
655     }
656 
657     private void verbose(String message) {
658         if (verbose || getLog().isDebugEnabled()) {
659             getLog().info(message);
660         }
661     }
662 
663     private String getProjectKey(MavenProject project) {
664         return project.getArtifactId();
665     }
666 
667     
668 
669 
670     public boolean isSkip() {
671         return skip;
672     }
673 
674     
675 
676 
677     public void setSkip(boolean skip) {
678         this.skip = skip;
679     }
680 }