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