View Javadoc

1   package org.apache.maven.plugins.shade.mojo;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.File;
23  import java.io.FileInputStream;
24  import java.io.FileOutputStream;
25  import java.io.IOException;
26  import java.io.Writer;
27  import java.util.ArrayList;
28  import java.util.Arrays;
29  import java.util.Collections;
30  import java.util.HashMap;
31  import java.util.HashSet;
32  import java.util.Iterator;
33  import java.util.LinkedHashSet;
34  import java.util.List;
35  import java.util.Map;
36  import java.util.Set;
37  
38  import org.apache.maven.artifact.Artifact;
39  import org.apache.maven.artifact.factory.ArtifactFactory;
40  import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
41  import org.apache.maven.artifact.repository.ArtifactRepository;
42  import org.apache.maven.artifact.resolver.ArtifactCollector;
43  import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
44  import org.apache.maven.artifact.resolver.ArtifactResolutionException;
45  import org.apache.maven.artifact.resolver.ArtifactResolver;
46  import org.apache.maven.model.Dependency;
47  import org.apache.maven.model.Exclusion;
48  import org.apache.maven.model.Model;
49  import org.apache.maven.plugin.AbstractMojo;
50  import org.apache.maven.plugin.MojoExecutionException;
51  import org.apache.maven.plugins.shade.Shader;
52  import org.apache.maven.plugins.shade.filter.SimpleFilter;
53  import org.apache.maven.plugins.shade.pom.PomWriter;
54  import org.apache.maven.plugins.shade.relocation.SimpleRelocator;
55  import org.apache.maven.plugins.shade.resource.ResourceTransformer;
56  import org.apache.maven.project.MavenProject;
57  import org.apache.maven.project.MavenProjectBuilder;
58  import org.apache.maven.project.MavenProjectHelper;
59  import org.apache.maven.project.ProjectBuildingException;
60  import org.apache.maven.shared.dependency.tree.DependencyNode;
61  import org.apache.maven.shared.dependency.tree.DependencyTreeBuilder;
62  import org.apache.maven.shared.dependency.tree.DependencyTreeBuilderException;
63  import org.codehaus.plexus.util.FileUtils;
64  import org.codehaus.plexus.util.IOUtil;
65  import org.codehaus.plexus.util.WriterFactory;
66  
67  /**
68   * Mojo that performs shading delegating to the Shader component.
69   *
70   * @author Jason van Zyl
71   * @author Mauro Talevi
72   * @author David Blevins
73   * @author Hiram Chirino
74   * @goal shade
75   * @phase package
76   * @requiresDependencyResolution runtime
77   * @threadSafe
78   */
79  public class ShadeMojo
80      extends AbstractMojo
81  {
82      /**
83       * @parameter default-value="${project}"
84       * @readonly
85       * @required
86       */
87      private MavenProject project;
88  
89      /**
90       * @component
91       * @required
92       * @readonly
93       */
94      private MavenProjectHelper projectHelper;
95  
96      /**
97       * @component
98       * @required
99       * @readonly
100      */
101     private Shader shader;
102 
103     /**
104      * The dependency tree builder to use.
105      *
106      * @component
107      * @required
108      * @readonly
109      */
110     private DependencyTreeBuilder dependencyTreeBuilder;
111 
112     /**
113      * ProjectBuilder, needed to create projects from the artifacts.
114      *
115      * @component
116      * @required
117      * @readonly
118      */
119     private MavenProjectBuilder mavenProjectBuilder;
120 
121     /**
122      * The artifact metadata source to use.
123      *
124      * @component
125      * @required
126      * @readonly
127      */
128     private ArtifactMetadataSource artifactMetadataSource;
129 
130     /**
131      * The artifact collector to use.
132      *
133      * @component
134      * @required
135      * @readonly
136      */
137     private ArtifactCollector artifactCollector;
138 
139     /**
140      * Remote repositories which will be searched for source attachments.
141      *
142      * @parameter default-value="${project.remoteArtifactRepositories}"
143      * @required
144      * @readonly
145      */
146     protected List remoteArtifactRepositories;
147 
148     /**
149      * Local maven repository.
150      *
151      * @parameter default-value="${localRepository}"
152      * @required
153      * @readonly
154      */
155     protected ArtifactRepository localRepository;
156 
157     /**
158      * Artifact factory, needed to download source jars for inclusion in classpath.
159      *
160      * @component
161      * @required
162      * @readonly
163      */
164     protected ArtifactFactory artifactFactory;
165 
166     /**
167      * Artifact resolver, needed to download source jars for inclusion in classpath.
168      *
169      * @component
170      * @required
171      * @readonly
172      */
173     protected ArtifactResolver artifactResolver;
174 
175     /**
176      * Artifacts to include/exclude from the final artifact. Artifacts are denoted by composite identifiers of the
177      * general form <code>groupId:artifactId:type:classifier</code>. Since version 1.3, the wildcard characters '*' and
178      * '?' can be used within the sub parts of those composite identifiers to do pattern matching. For convenience, the
179      * syntax <code>groupId</code> is equivalent to <code>groupId:*:*:*</code>, <code>groupId:artifactId</code> is
180      * equivalent to <code>groupId:artifactId:*:*</code> and <code>groupId:artifactId:classifier</code> is equivalent to
181      * <code>groupId:artifactId:*:classifier</code>. For example:
182      * <pre>
183      * &lt;artifactSet&gt;
184      *   &lt;includes&gt;
185      *     &lt;include&gt;org.apache.maven:*&lt;/include&gt;
186      *   &lt;/includes&gt;
187      *   &lt;excludes&gt;
188      *     &lt;exclude&gt;*:maven-core&lt;/exclude&gt;
189      *   &lt;/excludes&gt;
190      * &lt;/artifactSet&gt;
191      * </pre>
192      * 
193      * @parameter
194      */
195     private ArtifactSet artifactSet;
196 
197     /**
198      * Packages to be relocated. For example:
199      * <pre>
200      * &lt;relocations&gt;
201      *   &lt;relocation&gt;
202      *     &lt;pattern&gt;org.apache&lt;/pattern&gt;
203      *     &lt;shadedPattern&gt;hidden.org.apache&lt;/shadedPattern&gt;
204      *     &lt;excludes&gt;
205      *       &lt;exclude&gt;org.apache.ExcludedClass&lt;/exclude&gt;
206      *     &lt;/excludes&gt;
207      *   &lt;/relocation&gt;
208      * &lt;/relocations&gt;
209      * </pre>
210      *
211      * @parameter
212      */
213     private PackageRelocation[] relocations;
214 
215     /**
216      * Resource transformers to be used. Please see the "Examples" section for more information on available
217      * transformers and their configuration.
218      * 
219      * @parameter
220      */
221     private ResourceTransformer[] transformers;
222 
223     /**
224      * Archive Filters to be used. Allows you to specify an artifact in the form of a composite identifier as used by
225      * {@link #artifactSet} and a set of include/exclude file patterns for filtering which contents of the archive are
226      * added to the shaded jar. From a logical perspective, includes are processed before excludes, thus it's possible
227      * to use an include to collect a set of files from the archive then use excludes to further reduce the set. By
228      * default, all files are included and no files are excluded. If multiple filters apply to an artifact, the
229      * intersection of the matched files will be included in the final JAR. For example:
230      * <pre>
231      * &lt;filters&gt;
232      *   &lt;filter&gt;
233      *     &lt;artifact&gt;junit:junit&lt;/artifact&gt;
234      *     &lt;includes&gt;
235      *       &lt;include&gt;org/junit/**&lt;/include&gt;
236      *     &lt;/includes&gt;
237      *     &lt;excludes&gt;
238      *       &lt;exclude&gt;org/junit/experimental/**&lt;/exclude&gt;
239      *     &lt;/excludes&gt;
240      *   &lt;/filter&gt;
241      * &lt;/filters&gt;
242      * </pre>
243      * 
244      * @parameter
245      */
246     private ArchiveFilter[] filters;
247 
248     /**
249      * The destination directory for the shaded artifact.
250      *
251      * @parameter default-value="${project.build.directory}"
252      */
253     private File outputDirectory;
254 
255     /**
256      * The name of the shaded artifactId.
257      * 
258      * If you like to change the name of the native artifact, you may use the &lt;build>&lt;finalName> setting.
259      * If this is set to something different than &lt;build>&lt;finalName>, no file replacement
260      * will be performed, even if shadedArtifactAttached is being used.
261      *
262      * @parameter expression="${finalName}"
263      */
264     private String finalName;
265 
266     /**
267      * The name of the shaded artifactId. So you may want to use a different artifactId and keep
268      * the standard version. If the original artifactId was "foo" then the final artifact would
269      * be something like foo-1.0.jar. So if you change the artifactId you might have something
270      * like foo-special-1.0.jar.
271      *
272      * @parameter expression="${shadedArtifactId}" default-value="${project.artifactId}"
273      */
274     private String shadedArtifactId;
275 
276     /**
277      * If specified, this will include only artifacts which have groupIds which
278      * start with this.
279      *
280      * @parameter expression="${shadedGroupFilter}"
281      */
282     private String shadedGroupFilter;
283 
284     /**
285      * Defines whether the shaded artifact should be attached as classifier to
286      * the original artifact.  If false, the shaded jar will be the main artifact
287      * of the project
288      *
289      * @parameter expression="${shadedArtifactAttached}" default-value="false"
290      */
291     private boolean shadedArtifactAttached;
292 
293     /**
294      * Flag whether to generate a simplified POM for the shaded artifact. If set to <code>true</code>, dependencies that
295      * have been included into the uber JAR will be removed from the <code>&lt;dependencies&gt;</code> section of the
296      * generated POM. The reduced POM will be named <code>dependency-reduced-pom.xml</code> and is stored into the same
297      * directory as the shaded artifact.
298      *
299      * @parameter expression="${createDependencyReducedPom}" default-value="true"
300      */
301     private boolean createDependencyReducedPom;
302 
303     /**
304      * When true, dependencies are kept in the pom but with scope 'provided'; when false,
305      * the dependency is removed.
306      *
307      * @parameter expression="${keepDependenciesWithProvidedScope}" default-value="false"
308      */
309     private boolean keepDependenciesWithProvidedScope;
310 
311     /**
312      * When true, transitive deps of removed dependencies are promoted to direct dependencies.
313      * This should allow the drop in replacement of the removed deps with the new shaded
314      * jar and everything should still work.
315      *
316      * @parameter expression="${promoteTransitiveDependencies}" default-value="false"
317      */
318     private boolean promoteTransitiveDependencies;
319 
320     /**
321      * The name of the classifier used in case the shaded artifact is attached.
322      *
323      * @parameter expression="${shadedClassifierName}" default-value="shaded"
324      */
325     private String shadedClassifierName;
326 
327     /**
328      * When true, it will attempt to create a sources jar as well
329      *
330      * @parameter expression="${createSourcesJar}" default-value="false"
331      */
332     private boolean createSourcesJar;
333 
334     /**
335      * The path to the output file for the shaded artifact. When this parameter is set, the created archive will neither
336      * replace the project's main artifact nor will it be attached. Hence, this parameter causes the parameters
337      * {@link #finalName}, {@link #shadedArtifactAttached}, {@link #shadedClassifierName} and
338      * {@link #createDependencyReducedPom} to be ignored when used.
339      * 
340      * @parameter
341      * @since 1.3
342      */
343     private File outputFile;
344 
345     /** @throws MojoExecutionException  */
346     public void execute()
347         throws MojoExecutionException
348     {
349         Set artifacts = new LinkedHashSet();
350         Set artifactIds = new LinkedHashSet();
351         Set sourceArtifacts = new LinkedHashSet();
352 
353         ArtifactSelector artifactSelector =
354             new ArtifactSelector( project.getArtifact(), artifactSet, shadedGroupFilter );
355 
356         if ( artifactSelector.isSelected( project.getArtifact() ) && !"pom".equals( project.getArtifact().getType() ) )
357         {
358             if ( project.getArtifact().getFile() == null )
359             {
360                 getLog().error( "The project main artifact does not exist. This could have the following" );
361                 getLog().error( "reasons:" );
362                 getLog().error( "- You have invoked the goal directly from the command line. This is not" );
363                 getLog().error( "  supported. Please add the goal to the default lifecycle via an" );
364                 getLog().error( "  <execution> element in your POM and use \"mvn package\" to have it run." );
365                 getLog().error( "- You have bound the goal to a lifecycle phase before \"package\". Please" );
366                 getLog().error( "  remove this binding from your POM such that the goal will be run in" );
367                 getLog().error( "  the proper phase." );
368                 throw new MojoExecutionException( "Failed to create shaded artifact, "
369                     + "project main artifact does not exist." );
370             }
371 
372             artifacts.add( project.getArtifact().getFile() );
373 
374             if ( createSourcesJar )
375             {
376                 File file = shadedSourcesArtifactFile();
377                 if ( file.isFile() )
378                 {
379                     sourceArtifacts.add( file );
380                 }
381             }
382         }
383 
384         for ( Iterator it = project.getArtifacts().iterator(); it.hasNext(); )
385         {
386             Artifact artifact = (Artifact) it.next();
387 
388             if ( !artifactSelector.isSelected( artifact ) )
389             {
390                 getLog().info( "Excluding " + artifact.getId() + " from the shaded jar." );
391 
392                 continue;
393             }
394 
395             if ( "pom".equals( artifact.getType() ) )
396             {
397                 getLog().info( "Skipping pom dependency " + artifact.getId() + " in the shaded jar." );
398                 continue;
399             }
400 
401             getLog().info( "Including " + artifact.getId() + " in the shaded jar." );
402 
403             artifacts.add( artifact.getFile() );
404 
405             artifactIds.add( getId( artifact ) );
406 
407             if ( createSourcesJar )
408             {
409                 File file = resolveArtifactSources( artifact );
410                 if ( file != null )
411                 {
412                     sourceArtifacts.add( file );
413                 }
414             }
415         }
416 
417 
418         File outputJar = ( outputFile != null ) ? outputFile : shadedArtifactFileWithClassifier();
419         File sourcesJar = shadedSourceArtifactFileWithClassifier();
420 
421         // Now add our extra resources
422         try
423         {
424             List filters = getFilters();
425 
426             List relocators = getRelocators();
427 
428             List resourceTransformers = getResourceTransformers();
429 
430             shader.shade( artifacts, outputJar, filters, relocators, resourceTransformers );
431 
432             if ( createSourcesJar )
433             {
434                 shader.shade( sourceArtifacts, sourcesJar, filters, relocators, resourceTransformers );
435             }
436 
437             if ( outputFile == null )
438             {
439                 boolean renamed = false;
440 
441                 // rename the output file if a specific finalName is set
442                 // but don't rename if the finalName is the <build><finalName>
443                 // because this will be handled implicitely later
444                 if ( finalName != null && finalName.length() > 0
445                     && !finalName.equals( project.getBuild().getFinalName() ) )
446                 {
447                     String finalFileName = finalName + "." + project.getArtifact().getArtifactHandler().getExtension();
448                     File finalFile = new File( outputDirectory, finalFileName );
449                     replaceFile( finalFile, outputJar );
450                     outputJar = finalFile;
451 
452                     renamed = true;
453                 }
454 
455                 if ( shadedArtifactAttached )
456                 {
457                     getLog().info( "Attaching shaded artifact." );
458                     projectHelper.attachArtifact( project, project.getArtifact().getType(), shadedClassifierName,
459                                                   outputJar );
460                     if ( createSourcesJar )
461                     {
462                         projectHelper.attachArtifact( project, "jar", shadedClassifierName + "-sources", sourcesJar );
463                     }
464                 }
465                 else if ( !renamed )
466                 {
467                     getLog().info( "Replacing original artifact with shaded artifact." );
468                     File originalArtifact = project.getArtifact().getFile();
469                     replaceFile( originalArtifact, outputJar );
470 
471                     if ( createSourcesJar )
472                     {
473                         File shadedSources = shadedSourcesArtifactFile();
474 
475                         replaceFile( shadedSources, sourcesJar );
476 
477                         projectHelper.attachArtifact( project, "jar", "sources", shadedSources );
478                     }
479 
480                     if ( createDependencyReducedPom )
481                     {
482                         createDependencyReducedPom( artifactIds );
483                     }
484                 }
485             }
486         }
487         catch ( Exception e )
488         {
489             throw new MojoExecutionException( "Error creating shaded jar: " + e.getMessage(), e );
490         }
491     }
492 
493     private void replaceFile( File oldFile, File newFile ) throws MojoExecutionException
494     {
495         getLog().info( "Replacing " + oldFile + " with " + newFile );
496 
497         File origFile = new File( outputDirectory, "original-" + oldFile.getName() );
498         if ( oldFile.exists() && !oldFile.renameTo( origFile ) )
499         {
500             //try a gc to see if an unclosed stream needs garbage collecting
501             System.gc();
502             System.gc();
503 
504             if ( !oldFile.renameTo( origFile ) )
505             {
506                 // Still didn't work.   We'll do a copy
507                 try
508                 {
509                     FileOutputStream fout = new FileOutputStream( origFile );
510                     FileInputStream fin = new FileInputStream( oldFile );
511                     try
512                     {
513                         IOUtil.copy( fin, fout );
514                     }
515                     finally
516                     {
517                         IOUtil.close( fin );
518                         IOUtil.close( fout );
519                     }
520                 }
521                 catch ( IOException ex )
522                 {
523                     //kind of ignorable here.   We're just trying to save the original
524                     getLog().warn( ex );
525                 }
526             }
527         }
528         if ( !newFile.renameTo( oldFile ) )
529         {
530             //try a gc to see if an unclosed stream needs garbage collecting
531             System.gc();
532             System.gc();
533 
534             if ( !newFile.renameTo( oldFile ) )
535             {
536                 // Still didn't work.   We'll do a copy
537                 try
538                 {
539                     FileOutputStream fout = new FileOutputStream( oldFile );
540                     FileInputStream fin = new FileInputStream( newFile );
541                     try
542                     {
543                         IOUtil.copy( fin, fout );
544                     }
545                     finally
546                     {
547                         IOUtil.close( fin );
548                         IOUtil.close( fout );
549                     }
550                 }
551                 catch ( IOException ex )
552                 {
553                     throw new MojoExecutionException( "Could not replace original artifact with shaded artifact!", ex );
554                 }
555             }
556         }
557     }
558 
559     private File resolveArtifactSources( Artifact artifact )
560     {
561 
562         Artifact resolvedArtifact =
563             artifactFactory.createArtifactWithClassifier( artifact.getGroupId(),
564                                                           artifact.getArtifactId(),
565                                                           artifact.getVersion(),
566                                                           "java-source",
567                                                           "sources" );
568 
569         try
570         {
571             artifactResolver.resolve( resolvedArtifact, remoteArtifactRepositories, localRepository );
572         }
573         catch ( ArtifactNotFoundException e )
574         {
575             // ignore, the jar has not been found
576         }
577         catch ( ArtifactResolutionException e )
578         {
579             getLog().warn( "Could not get sources for " + artifact );
580         }
581 
582         if ( resolvedArtifact.isResolved() )
583         {
584             return resolvedArtifact.getFile();
585         }
586         return null;
587     }
588 
589     private List getRelocators()
590     {
591         List relocators = new ArrayList();
592 
593         if ( relocations == null )
594         {
595             return relocators;
596         }
597 
598         for ( int i = 0; i < relocations.length; i++ )
599         {
600             PackageRelocation r = relocations[i];
601 
602             relocators.add( new SimpleRelocator( r.getPattern(), r.getShadedPattern(), r.getExcludes() ) );
603         }
604 
605         return relocators;
606     }
607 
608     private List getResourceTransformers()
609     {
610         if ( transformers == null )
611         {
612             return Collections.EMPTY_LIST;
613         }
614 
615         return Arrays.asList( transformers );
616     }
617 
618     private List getFilters()
619     {
620         List filters = new ArrayList();
621 
622         if ( this.filters == null || this.filters.length <= 0 )
623         {
624             return filters;
625         }
626 
627         Map artifacts = new HashMap();
628 
629         artifacts.put( project.getArtifact(), new ArtifactId( project.getArtifact() ) );
630 
631         for ( Iterator it = project.getArtifacts().iterator(); it.hasNext(); )
632         {
633             Artifact artifact = (Artifact) it.next();
634 
635             artifacts.put( artifact, new ArtifactId( artifact ) );
636         }
637 
638         for ( int i = 0; i < this.filters.length; i++ )
639         {
640             ArchiveFilter filter = this.filters[i];
641 
642             ArtifactId pattern = new ArtifactId( filter.getArtifact() );
643 
644             Set jars = new HashSet();
645 
646             for ( Iterator it = artifacts.entrySet().iterator(); it.hasNext(); )
647             {
648                 Map.Entry entry = (Map.Entry) it.next();
649 
650                 if ( ( (ArtifactId) entry.getValue() ).matches( pattern ) )
651                 {
652                     jars.add( ( (Artifact) entry.getKey() ).getFile() );
653                 }
654             }
655 
656             if ( jars.isEmpty() )
657             {
658                 getLog().info( "No artifact matching filter " + filter.getArtifact() );
659 
660                 continue;
661             }
662 
663             filters.add( new SimpleFilter( jars, filter.getIncludes(), filter.getExcludes() ) );
664 
665         }
666 
667         return filters;
668     }
669 
670     private File shadedArtifactFileWithClassifier()
671     {
672         Artifact artifact = project.getArtifact();
673         final String shadedName =
674             shadedArtifactId + "-" + artifact.getVersion() + "-" + shadedClassifierName + "."
675                 + artifact.getArtifactHandler().getExtension();
676         return new File( outputDirectory, shadedName );
677     }
678 
679     private File shadedSourceArtifactFileWithClassifier()
680     {
681         Artifact artifact = project.getArtifact();
682         final String shadedName =
683             shadedArtifactId + "-" + artifact.getVersion() + "-" + shadedClassifierName + "-sources."
684                 + artifact.getArtifactHandler().getExtension();
685         return new File( outputDirectory, shadedName );
686     }
687 
688     private File shadedSourcesArtifactFile()
689     {
690         Artifact artifact = project.getArtifact();
691 
692         String shadedName;
693 
694         if ( project.getBuild().getFinalName() != null )
695         {
696             shadedName = project.getBuild().getFinalName() + "-sources." + artifact.getArtifactHandler().getExtension();
697         }
698         else
699         {
700             shadedName = shadedArtifactId + "-" + artifact.getVersion() + "-sources."
701                 + artifact.getArtifactHandler().getExtension();
702         }
703 
704         return new File( outputDirectory, shadedName );
705     }
706 
707     // We need to find the direct dependencies that have been included in the uber JAR so that we can modify the
708     // POM accordingly.
709     private void createDependencyReducedPom( Set artifactsToRemove )
710         throws IOException, DependencyTreeBuilderException, ProjectBuildingException
711     {
712         Model model = project.getOriginalModel();
713         List dependencies = new ArrayList();
714 
715         boolean modified = false;
716 
717         List transitiveDeps = new ArrayList();
718 
719         for ( Iterator it = project.getArtifacts().iterator(); it.hasNext(); )
720         {
721             Artifact artifact = (Artifact) it.next();
722 
723             //promote
724             Dependency dep = new Dependency();
725             dep.setArtifactId( artifact.getArtifactId() );
726             if ( artifact.hasClassifier() )
727             {
728                 dep.setClassifier( artifact.getClassifier() );
729             }
730             dep.setGroupId( artifact.getGroupId() );
731             dep.setOptional( artifact.isOptional() );
732             dep.setScope( artifact.getScope() );
733             dep.setType( artifact.getType() );
734             dep.setVersion( artifact.getVersion() );
735 
736             //we'll figure out the exclusions in a bit.
737 
738             transitiveDeps.add( dep );
739         }
740         List origDeps = project.getDependencies();
741 
742         if ( promoteTransitiveDependencies )
743         {
744             origDeps = transitiveDeps;
745         }
746 
747         for ( Iterator i = origDeps.iterator(); i.hasNext(); )
748         {
749             Dependency d = (Dependency) i.next();
750 
751             dependencies.add( d );
752 
753             String id = getId( d );
754 
755             if ( artifactsToRemove.contains( id ) )
756             {
757                 modified = true;
758 
759                 if ( keepDependenciesWithProvidedScope )
760                 {
761                     d.setScope( "provided" );
762                 }
763                 else
764                 {
765                     dependencies.remove( d );
766                 }
767             }
768         }
769 
770         // Check to see if we have a reduction and if so rewrite the POM.
771         if ( modified )
772         {
773             while ( modified )
774             {
775 
776                 model.setDependencies( dependencies );
777 
778                 /*
779                  * NOTE: Be sure to create the POM in the original base directory to be able to resolve the relativePath
780                  * to local parent POMs when invoking the project builder below.
781                  */
782                 File f = new File( project.getBasedir(), "dependency-reduced-pom.xml" );
783                 if ( f.exists() )
784                 {
785                     f.delete();
786                 }
787 
788                 Writer w = WriterFactory.newXmlWriter( f );
789 
790                 try
791                 {
792                     PomWriter.write( w, model, true );
793                 }
794                 finally
795                 {
796                     w.close();
797                 }
798 
799                 MavenProject p2 = mavenProjectBuilder.build( f, localRepository, null );
800                 modified = updateExcludesInDeps( p2, dependencies, transitiveDeps );
801 
802             }
803 
804             /*
805              * NOTE: Although the dependency reduced POM in the project directory is temporary build output, we have to
806              * use that for the file of the project instead of something in target to avoid messing up the base
807              * directory of the project. We'll delete this file on exit to make sure it gets cleaned up but keep a copy
808              * for inspection in the target directory as well.
809              */
810             File f = new File( project.getBasedir(), "dependency-reduced-pom.xml" );
811             File f2 = new File( outputDirectory, "dependency-reduced-pom.xml" );
812             FileUtils.copyFile( f, f2 );
813             FileUtils.forceDeleteOnExit( f );
814             project.setFile( f );
815         }
816     }
817 
818     private String getId( Artifact artifact )
819     {
820         return getId( artifact.getGroupId(), artifact.getArtifactId(), artifact.getType(), artifact.getClassifier() );
821     }
822 
823     private String getId( Dependency dependency )
824     {
825         return getId( dependency.getGroupId(), dependency.getArtifactId(), dependency.getType(),
826                       dependency.getClassifier() );
827     }
828 
829     private String getId( String groupId, String artifactId, String type, String classifier )
830     {
831         return groupId + ":" + artifactId + ":" + type + ":" + ( ( classifier != null ) ? classifier : "" );
832     }
833 
834     public boolean updateExcludesInDeps( MavenProject project,
835                                          List dependencies,
836                                          List transitiveDeps )
837         throws DependencyTreeBuilderException
838     {
839         DependencyNode node = dependencyTreeBuilder.buildDependencyTree(
840                                                   project,
841                                                   localRepository,
842                                                   artifactFactory,
843                                                   artifactMetadataSource,
844                                                   null,
845                                                   artifactCollector );
846         boolean modified = false;
847         Iterator it = node.getChildren().listIterator();
848         while ( it.hasNext() )
849         {
850             DependencyNode n2 = (DependencyNode) it.next();
851             Iterator it2 = n2.getChildren().listIterator();
852             while ( it2.hasNext() )
853             {
854                 DependencyNode n3 = (DependencyNode) it2.next();
855                 //anything two levels deep that is marked "included"
856                 //is stuff that was excluded by the original poms, make sure it
857                 //remains excluded IF promoting transitives.
858                 if ( n3.getState() == DependencyNode.INCLUDED )
859                 {
860                     //check if it really isn't in the list of original dependencies.  Maven
861                     //prior to 2.0.8 may grab versions from transients instead of
862                     //from the direct deps in which case they would be marked included
863                     //instead of OMITTED_FOR_DUPLICATE
864 
865                     //also, if not promoting the transitives, level 2's would be included
866                     boolean found = false;
867                     for ( int x = 0; x < transitiveDeps.size(); x++ )
868                     {
869                         Dependency dep = (Dependency) transitiveDeps.get( x );
870                         if ( dep.getArtifactId().equals( n3.getArtifact().getArtifactId() )
871                             && dep.getGroupId().equals( n3.getArtifact().getGroupId() ) )
872                         {
873                             found = true;
874                         }
875 
876                     }
877 
878                     if ( !found )
879                     {
880                         for ( int x = 0; x < dependencies.size(); x++ )
881                         {
882                             Dependency dep = (Dependency) dependencies.get( x );
883                             if ( dep.getArtifactId().equals( n2.getArtifact().getArtifactId() )
884                                 && dep.getGroupId().equals( n2.getArtifact().getGroupId() ) )
885                             {
886                                 Exclusion exclusion = new Exclusion();
887                                 exclusion.setArtifactId( n3.getArtifact().getArtifactId() );
888                                 exclusion.setGroupId( n3.getArtifact().getGroupId() );
889                                 dep.addExclusion( exclusion );
890                                 modified = true;
891                                 break;
892                             }
893                         }
894                     }
895                 }
896             }
897         }
898         return modified;
899     }
900 }