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