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