1 package org.apache.maven.plugins.dependency;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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.MojoExecutionException;
43 import org.apache.maven.plugin.MojoFailureException;
44 import org.apache.maven.plugin.MojoExecution.Source;
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.dependencies.resolve.DependencyResolver;
50 import org.apache.maven.shared.dependencies.resolve.DependencyResolverException;
51 import org.apache.maven.shared.artifact.DefaultArtifactCoordinate;
52 import org.apache.maven.shared.artifact.filter.resolve.AbstractFilter;
53 import org.apache.maven.shared.artifact.filter.resolve.AndFilter;
54 import org.apache.maven.shared.artifact.filter.resolve.Node;
55 import org.apache.maven.shared.artifact.filter.resolve.PatternExclusionsFilter;
56 import org.apache.maven.shared.artifact.filter.resolve.PatternInclusionsFilter;
57 import org.apache.maven.shared.artifact.filter.resolve.ScopeFilter;
58 import org.apache.maven.shared.artifact.filter.resolve.TransformableFilter;
59 import org.apache.maven.shared.artifact.filter.resolve.transform.ArtifactIncludeFilterTransformer;
60 import org.apache.maven.shared.artifact.resolve.ArtifactResolver;
61 import org.apache.maven.shared.artifact.resolve.ArtifactResolverException;
62 import org.apache.maven.shared.artifact.resolve.ArtifactResult;
63 import org.codehaus.plexus.util.FileUtils;
64 import org.codehaus.plexus.util.StringUtils;
65
66
67
68
69
70
71
72
73
74 @Mojo( name = "purge-local-repository", threadSafe = true, requiresProject = false )
75 public class PurgeLocalRepositoryMojo
76 extends AbstractMojo
77 {
78
79 public static final String FILE_FUZZINESS = "file";
80
81 public static final String VERSION_FUZZINESS = "version";
82
83 public static final String ARTIFACT_ID_FUZZINESS = "artifactId";
84
85 public static final String GROUP_ID_FUZZINESS = "groupId";
86
87
88
89
90 @Parameter( defaultValue = "${reactorProjects}", readonly = true, required = true )
91 private List<MavenProject> reactorProjects;
92
93
94
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
104
105 @Parameter( defaultValue = "${mojo}", required = true, readonly = true )
106 private MojoExecution mojoExecution;
107
108
109
110
111 @Component
112 private ArtifactHandlerManager artifactHandlerManager;
113
114
115
116
117
118
119
120
121
122 @Parameter
123 private List<String> manualIncludes;
124
125
126
127
128
129
130
131
132 @Parameter( property = "manualInclude" )
133 private String manualInclude;
134
135
136
137
138
139
140 @Parameter
141 private List<String> includes;
142
143
144
145
146
147
148
149
150 @Parameter( property = "include" )
151 private String include;
152
153
154
155
156 @Parameter
157 private List<String> excludes;
158
159
160
161
162
163
164 @Parameter( property = "exclude" )
165 private String exclude;
166
167
168
169
170
171 @Parameter( property = "reResolve", defaultValue = "true" )
172 private boolean reResolve;
173
174
175
176
177 @Parameter( defaultValue = "${localRepository}", readonly = true, required = true )
178 private ArtifactRepository localRepository;
179
180
181
182
183 @Component
184 private DependencyResolver dependencyResolver;
185
186
187
188
189 @Component
190 private ArtifactResolver artifactResolver;
191
192
193
194
195
196
197
198
199
200
201 @Parameter( property = "resolutionFuzziness", defaultValue = "version" )
202 private String resolutionFuzziness;
203
204
205
206
207 @Parameter( property = "actTransitively", defaultValue = "true" )
208 private boolean actTransitively;
209
210
211
212
213 @Parameter( property = "verbose", defaultValue = "false" )
214 private boolean verbose;
215
216
217
218
219
220
221 @Parameter( property = "snapshotsOnly", defaultValue = "false" )
222 private boolean snapshotsOnly;
223
224
225
226
227
228
229 @Parameter( property = "skip", defaultValue = "false" )
230 private boolean skip;
231
232
233
234
235 private class DirectDependencyFilter
236 extends AbstractFilter
237 {
238
239 private Artifact projectArtifact;
240
241 private List<Dependency> directDependencies;
242
243
244
245
246
247
248 public DirectDependencyFilter( Artifact projectArtifact, List<Dependency> directDependencies )
249 {
250 this.projectArtifact = projectArtifact;
251 this.directDependencies = directDependencies;
252 }
253
254 @Override
255 public boolean accept( Node node, List<Node> parents )
256 {
257
258 if ( artifactsGAMatch( node, projectArtifact.getGroupId(), projectArtifact.getArtifactId() ) )
259 {
260 return true;
261 }
262 for ( Dependency dep : directDependencies )
263 {
264 if ( this.artifactsGAMatch( node, dep.getGroupId(), dep.getArtifactId() ) )
265 {
266 return true;
267 }
268 }
269 return false;
270 }
271
272
273
274
275 private boolean artifactsGAMatch( Node node, String groupId, String artifactId )
276 {
277 if ( node.getDependency() == null )
278 {
279 return false;
280 }
281
282 if ( !node.getDependency().getGroupId().equals( groupId ) )
283 {
284 getLog().debug( "Different groupId: " + node.getDependency() + " " + groupId );
285 return false;
286 }
287 if ( !node.getDependency().getArtifactId().equals( artifactId ) )
288 {
289 getLog().debug( "Different artifactId: " + node.getDependency() + " " + artifactId );
290 return false;
291 }
292 return true;
293 }
294 }
295
296
297
298
299 private class SnapshotsFilter
300 extends AbstractFilter
301 {
302 @Override
303 public boolean accept( Node node, List<Node> parents )
304 {
305 if ( node.getDependency() == null )
306 {
307 return false;
308 }
309 else
310 {
311 return ArtifactUtils.isSnapshot( node.getDependency().getVersion() );
312 }
313 }
314 }
315
316 @Override
317 public void execute()
318 throws MojoExecutionException, MojoFailureException
319 {
320 if ( isSkip() )
321 {
322 getLog().info( "Skipping plugin execution" );
323 return;
324 }
325
326 if ( !StringUtils.isEmpty( manualInclude ) )
327 {
328 manualIncludes = this.parseIncludes( manualInclude );
329 }
330
331 if ( manualIncludes != null && manualIncludes.size() > 0 )
332 {
333 manualPurge( manualIncludes );
334 return;
335 }
336
337 Set<Artifact> purgedArtifacts = new HashSet<Artifact>();
338 if ( shouldPurgeAllProjectsInReactor() )
339 {
340 for ( MavenProject reactorProject : reactorProjects )
341 {
342 purgeLocalRepository( reactorProject, purgedArtifacts );
343 }
344 }
345 else
346 {
347 purgeLocalRepository( project, purgedArtifacts );
348 }
349 }
350
351
352
353
354
355
356
357 private boolean shouldPurgeAllProjectsInReactor()
358 {
359 Source source = mojoExecution.getSource();
360 return reactorProjects.size() > 1 && source == Source.CLI;
361 }
362
363
364
365
366
367
368
369
370 private void purgeLocalRepository( MavenProject project, Set<Artifact> purgedArtifacts )
371 throws MojoFailureException
372 {
373 List<Dependency> dependencies = project.getDependencies();
374
375 TransformableFilter dependencyFilter = createPurgeArtifactsFilter( project, dependencies, purgedArtifacts );
376
377 Set<Artifact> resolvedArtifactsToPurge =
378 getFilteredResolvedArtifacts( project, dependencies, dependencyFilter );
379
380 if ( resolvedArtifactsToPurge.isEmpty() )
381 {
382 getLog().info( "No artifacts included for purge for project: " + project.getId() );
383 return;
384 }
385
386 verbose( "Purging dependencies for project: " + project.getId() );
387 purgeArtifacts( resolvedArtifactsToPurge );
388 purgedArtifacts.addAll( resolvedArtifactsToPurge );
389
390 if ( reResolve )
391 {
392 ArtifactFilter artifactFilter = dependencyFilter.transform( new ArtifactIncludeFilterTransformer() );
393 try
394 {
395 reResolveArtifacts( project, resolvedArtifactsToPurge, artifactFilter );
396 }
397 catch ( ArtifactResolutionException e )
398 {
399 String failureMessage = "Failed to refresh project dependencies for: " + project.getId();
400 MojoFailureException failure = new MojoFailureException( failureMessage );
401 failure.initCause( e );
402
403 throw failure;
404 }
405 catch ( ArtifactNotFoundException e )
406 {
407 String failureMessage = "Failed to refresh project dependencies for: " + project.getId();
408 MojoFailureException failure = new MojoFailureException( failureMessage );
409 failure.initCause( e );
410
411 throw failure;
412 }
413 }
414 }
415
416
417
418
419
420
421
422 private void manualPurge( List<String> includes )
423 throws MojoExecutionException
424 {
425 for ( String gavPattern : includes )
426 {
427 if ( StringUtils.isEmpty( gavPattern ) )
428 {
429 getLog().debug( "Skipping empty gav pattern: " + gavPattern );
430 continue;
431 }
432
433 String relativePath = gavToPath( gavPattern );
434 if ( StringUtils.isEmpty( relativePath ) )
435 {
436 continue;
437 }
438
439 File purgeDir = new File( localRepository.getBasedir(), relativePath );
440 if ( purgeDir.exists() )
441 {
442 getLog().debug( "Deleting directory: " + purgeDir );
443 try
444 {
445 FileUtils.deleteDirectory( purgeDir );
446 }
447 catch ( IOException e )
448 {
449 throw new MojoExecutionException( "Unable to purge directory: " + purgeDir );
450 }
451 }
452 }
453 }
454
455
456
457
458
459
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
482
483
484
485
486
487
488
489 private TransformableFilter createPurgeArtifactsFilter( MavenProject project, List<Dependency> dependencies,
490 Set<Artifact> purgedArtifacts )
491 {
492 List<TransformableFilter> subFilters = new ArrayList<TransformableFilter>();
493
494
495 subFilters.add( ScopeFilter.excluding( Artifact.SCOPE_SYSTEM ) );
496
497 if ( this.snapshotsOnly )
498 {
499 subFilters.add( new SnapshotsFilter() );
500 }
501
502
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( project.getArtifact(), dependencies ) );
524 }
525
526 List<String> exclusions = new ArrayList<String>( reactorProjects.size() );
527
528 for ( MavenProject reactorProject : reactorProjects )
529 {
530 exclusions.add( toPatternExcludes( reactorProject.getArtifact() ) );
531 }
532
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
544
545
546
547
548 private String toPatternExcludes( Artifact artifact )
549 {
550 return artifact.getGroupId() + ":" + artifact.getArtifactId() + ":"
551 + artifact.getArtifactHandler().getExtension() + ":" + artifact.getVersion();
552 }
553
554
555
556
557
558
559
560 private List<String> parseIncludes( String include )
561 {
562 List<String> includes = new ArrayList<String>();
563
564 if ( include != null )
565 {
566 String[] elements = include.split( "," );
567 includes.addAll( Arrays.asList( elements ) );
568 }
569
570 return includes;
571 }
572
573 private Set<Artifact> getFilteredResolvedArtifacts( MavenProject project, List<Dependency> dependencies,
574 TransformableFilter filter )
575 {
576 try
577 {
578 Iterable<ArtifactResult> results =
579 dependencyResolver.resolveDependencies( session.getProjectBuildingRequest(), project.getModel(),
580 filter );
581
582 Set<Artifact> resolvedArtifacts = new LinkedHashSet<Artifact>();
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: " + project.getGroupId() + ":"
594 + project.getArtifactId() + ":" + project.getVersion()
595 + ". Falling back to non-transitive mode for initial artifact resolution." );
596 }
597
598 Set<Artifact> resolvedArtifacts = new LinkedHashSet<Artifact>();
599
600 ArtifactFilter artifactFilter = filter.transform( new ArtifactIncludeFilterTransformer() );
601
602 for ( Dependency dependency : dependencies )
603 {
604 DefaultArtifactCoordinate coordinate = new DefaultArtifactCoordinate();
605 coordinate.setGroupId( dependency.getGroupId() );
606 coordinate.setArtifactId( dependency.getArtifactId() );
607 coordinate.setVersion( dependency.getVersion() );
608 coordinate.setExtension( artifactHandlerManager.getArtifactHandler( dependency.getType() ).getExtension() );
609 try
610 {
611 Artifact artifact =
612 artifactResolver.resolveArtifact( session.getProjectBuildingRequest(), coordinate ).getArtifact();
613 if ( artifactFilter.include( artifact ) )
614 {
615 resolvedArtifacts.add( artifact );
616 }
617 }
618 catch ( ArtifactResolverException e )
619 {
620 getLog().debug( "Unable to resolve artifact: " + coordinate );
621 }
622 }
623 return resolvedArtifacts;
624 }
625
626 private void purgeArtifacts( Set<Artifact> artifacts )
627 throws MojoFailureException
628 {
629 for ( Artifact artifact : artifacts )
630 {
631 verbose( "Purging artifact: " + artifact.getId() );
632
633 File deleteTarget = findDeleteTarget( artifact );
634
635 verbose( "Deleting: " + deleteTarget );
636
637 if ( deleteTarget.isDirectory() )
638 {
639 try
640 {
641 FileUtils.deleteDirectory( deleteTarget );
642 }
643 catch ( IOException e )
644 {
645 getLog().warn( "Unable to purge local repository location: " + deleteTarget, e );
646 }
647 }
648 else
649 {
650 if ( !deleteTarget.delete() )
651 {
652 deleteTarget.deleteOnExit();
653 getLog().warn( "Unable to purge local repository location immediately: " + deleteTarget );
654 }
655 }
656 artifact.setResolved( false );
657 }
658 }
659
660 private void reResolveArtifacts( MavenProject project, Set<Artifact> artifacts, ArtifactFilter filter )
661 throws ArtifactResolutionException, ArtifactNotFoundException
662 {
663
664
665
666 for ( Artifact artifact : artifacts )
667 {
668 try
669 {
670
671 artifactResolver.resolveArtifact( session.getProjectBuildingRequest(),
672 org.apache.maven.shared.artifact.TransferUtils.toArtifactCoordinate( artifact ) );
673
674 }
675 catch ( ArtifactResolverException e )
676 {
677 verbose( e.getMessage() );
678 }
679 }
680
681 List<Artifact> missingArtifacts = new ArrayList<Artifact>();
682
683 for ( Artifact artifact : artifacts )
684 {
685 verbose( "Resolving artifact: " + artifact.getId() );
686
687 try
688 {
689 artifactResolver.resolveArtifact( session.getProjectBuildingRequest(), artifact );
690 }
691 catch ( ArtifactResolverException e )
692 {
693 verbose( e.getMessage() );
694 missingArtifacts.add( artifact );
695 }
696 }
697
698 if ( missingArtifacts.size() > 0 )
699 {
700 StringBuffer message = new StringBuffer( "required artifacts missing:\n" );
701 for ( Artifact missingArtifact : missingArtifacts )
702 {
703 message.append( " " ).append( missingArtifact.getId() ).append( '\n' );
704 }
705 message.append( "\nfor the artifact:" );
706
707 throw new ArtifactResolutionException( message.toString(), project.getArtifact(),
708 project.getRemoteArtifactRepositories() );
709 }
710 }
711
712 private File findDeleteTarget( Artifact artifact )
713 {
714
715 File deleteTarget = new File( localRepository.getBasedir(), localRepository.pathOf( artifact ) );
716
717 if ( GROUP_ID_FUZZINESS.equals( resolutionFuzziness ) )
718 {
719
720 deleteTarget = deleteTarget.getParentFile().getParentFile().getParentFile();
721 }
722 else if ( ARTIFACT_ID_FUZZINESS.equals( resolutionFuzziness ) )
723 {
724
725 deleteTarget = deleteTarget.getParentFile().getParentFile();
726 }
727 else if ( VERSION_FUZZINESS.equals( resolutionFuzziness ) )
728 {
729
730 deleteTarget = deleteTarget.getParentFile();
731 }
732
733 return deleteTarget;
734 }
735
736 private void verbose( String message )
737 {
738 if ( verbose || getLog().isDebugEnabled() )
739 {
740 getLog().info( message );
741 }
742 }
743
744 public boolean isSkip()
745 {
746 return skip;
747 }
748
749 public void setSkip( boolean skip )
750 {
751 this.skip = skip;
752 }
753 }