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.LinkedHashSet;
33  import java.util.List;
34  import java.util.Map;
35  import java.util.Set;
36  
37  import org.apache.maven.artifact.Artifact;
38  import org.apache.maven.artifact.factory.ArtifactFactory;
39  import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
40  import org.apache.maven.artifact.repository.ArtifactRepository;
41  import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
42  import org.apache.maven.artifact.resolver.ArtifactResolutionException;
43  import org.apache.maven.artifact.resolver.ArtifactResolver;
44  import org.apache.maven.execution.MavenSession;
45  import org.apache.maven.model.Dependency;
46  import org.apache.maven.model.Exclusion;
47  import org.apache.maven.model.Model;
48  import org.apache.maven.plugin.AbstractMojo;
49  import org.apache.maven.plugin.MojoExecutionException;
50  import org.apache.maven.plugins.annotations.Component;
51  import org.apache.maven.plugins.annotations.LifecyclePhase;
52  import org.apache.maven.plugins.annotations.Mojo;
53  import org.apache.maven.plugins.annotations.Parameter;
54  import org.apache.maven.plugins.annotations.ResolutionScope;
55  import org.apache.maven.plugins.shade.ShadeRequest;
56  import org.apache.maven.plugins.shade.Shader;
57  import org.apache.maven.plugins.shade.filter.Filter;
58  import org.apache.maven.plugins.shade.filter.MinijarFilter;
59  import org.apache.maven.plugins.shade.filter.SimpleFilter;
60  import org.apache.maven.plugins.shade.pom.PomWriter;
61  import org.apache.maven.plugins.shade.relocation.Relocator;
62  import org.apache.maven.plugins.shade.relocation.SimpleRelocator;
63  import org.apache.maven.plugins.shade.resource.ResourceTransformer;
64  import org.apache.maven.project.DefaultProjectBuildingRequest;
65  import org.apache.maven.project.MavenProject;
66  import org.apache.maven.project.MavenProjectHelper;
67  import org.apache.maven.project.ProjectBuilder;
68  import org.apache.maven.project.ProjectBuildingException;
69  import org.apache.maven.project.ProjectBuildingRequest;
70  import org.apache.maven.project.ProjectBuildingResult;
71  import org.apache.maven.shared.dependency.graph.DependencyGraphBuilder;
72  import org.apache.maven.shared.dependency.graph.DependencyGraphBuilderException;
73  import org.apache.maven.shared.dependency.graph.DependencyNode;
74  import org.codehaus.plexus.PlexusConstants;
75  import org.codehaus.plexus.PlexusContainer;
76  import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
77  import org.codehaus.plexus.context.Context;
78  import org.codehaus.plexus.context.ContextException;
79  import org.codehaus.plexus.personality.plexus.lifecycle.phase.Contextualizable;
80  import org.codehaus.plexus.util.IOUtil;
81  import org.codehaus.plexus.util.WriterFactory;
82  
83  /**
84   * Mojo that performs shading delegating to the Shader component.
85   *
86   * @author Jason van Zyl
87   * @author Mauro Talevi
88   * @author David Blevins
89   * @author Hiram Chirino
90   */
91  // CHECKSTYLE_OFF: LineLength
92  @Mojo( name = "shade", defaultPhase = LifecyclePhase.PACKAGE, threadSafe = true, requiresDependencyResolution = ResolutionScope.RUNTIME )
93  // CHECKSTYLE_ON: LineLength
94  public class ShadeMojo
95      extends AbstractMojo
96      implements Contextualizable
97  {
98      /**
99       * The current Maven session.
100      */
101     @Parameter( defaultValue = "${session}", readonly = true, required = true )
102     private MavenSession session;
103 
104     /**
105      * The current Maven project.
106      */
107     @Parameter( defaultValue = "${project}", readonly = true, required = true )
108     private MavenProject project;
109 
110     @Component
111     private MavenProjectHelper projectHelper;
112 
113     @Component( hint = "default", role = org.apache.maven.plugins.shade.Shader.class )
114     private Shader shader;
115 
116     /**
117      * The dependency graph builder to use.
118      */
119     @Component
120     private DependencyGraphBuilder dependencyGraphBuilder;
121 
122     /**
123      * ProjectBuilder, needed to create projects from the artifacts.
124      */
125     @Component
126     private ProjectBuilder projectBuilder;
127 
128     /**
129      * The artifact metadata source to use.
130      */
131     @Component
132     private ArtifactMetadataSource artifactMetadataSource;
133 
134     /**
135      * Remote repositories which will be searched for source attachments.
136      */
137     @Parameter( readonly = true, required = true, defaultValue = "${project.remoteArtifactRepositories}" )
138     protected List<ArtifactRepository> remoteArtifactRepositories;
139 
140     /**
141      * Local maven repository.
142      */
143     @Parameter( readonly = true, required = true, defaultValue = "${localRepository}" )
144     protected ArtifactRepository localRepository;
145 
146     /**
147      * Artifact factory, needed to download source jars for inclusion in classpath.
148      */
149     @Component
150     protected ArtifactFactory artifactFactory;
151 
152     /**
153      * Artifact resolver, needed to download source jars for inclusion in classpath.
154      */
155     @Component
156     protected ArtifactResolver artifactResolver;
157 
158     /**
159      * Artifacts to include/exclude from the final artifact. Artifacts are denoted by composite identifiers of the
160      * general form <code>groupId:artifactId:type:classifier</code>. Since version 1.3, the wildcard characters '*' and
161      * '?' can be used within the sub parts of those composite identifiers to do pattern matching. For convenience, the
162      * syntax <code>groupId</code> is equivalent to <code>groupId:*:*:*</code>, <code>groupId:artifactId</code> is
163      * equivalent to <code>groupId:artifactId:*:*</code> and <code>groupId:artifactId:classifier</code> is equivalent to
164      * <code>groupId:artifactId:*:classifier</code>. For example:
165      * 
166      * <pre>
167      * &lt;artifactSet&gt;
168      *   &lt;includes&gt;
169      *     &lt;include&gt;org.apache.maven:*&lt;/include&gt;
170      *   &lt;/includes&gt;
171      *   &lt;excludes&gt;
172      *     &lt;exclude&gt;*:maven-core&lt;/exclude&gt;
173      *   &lt;/excludes&gt;
174      * &lt;/artifactSet&gt;
175      * </pre>
176      */
177     @Parameter
178     private ArtifactSet artifactSet;
179 
180     /**
181      * Packages to be relocated. For example:
182      * 
183      * <pre>
184      * &lt;relocations&gt;
185      *   &lt;relocation&gt;
186      *     &lt;pattern&gt;org.apache&lt;/pattern&gt;
187      *     &lt;shadedPattern&gt;hidden.org.apache&lt;/shadedPattern&gt;
188      *     &lt;includes&gt;
189      *       &lt;include&gt;org.apache.maven.*&lt;/include&gt;
190      *     &lt;/includes&gt;
191      *     &lt;excludes&gt;
192      *       &lt;exclude&gt;org.apache.maven.Public*&lt;/exclude&gt;
193      *     &lt;/excludes&gt;
194      *   &lt;/relocation&gt;
195      * &lt;/relocations&gt;
196      * </pre>
197      * 
198      * <em>Note:</em> Support for includes exists only since version 1.4.
199      */
200     @SuppressWarnings( "MismatchedReadAndWriteOfArray" )
201     @Parameter
202     private PackageRelocation[] relocations;
203 
204     /**
205      * Resource transformers to be used. Please see the "Examples" section for more information on available
206      * transformers and their configuration.
207      */
208     @Parameter
209     private ResourceTransformer[] transformers;
210 
211     /**
212      * Archive Filters to be used. Allows you to specify an artifact in the form of a composite identifier as used by
213      * {@link #artifactSet} and a set of include/exclude file patterns for filtering which contents of the archive are
214      * added to the shaded jar. From a logical perspective, includes are processed before excludes, thus it's possible
215      * to use an include to collect a set of files from the archive then use excludes to further reduce the set. By
216      * default, all files are included and no files are excluded. If multiple filters apply to an artifact, the
217      * intersection of the matched files will be included in the final JAR. For example:
218      * 
219      * <pre>
220      * &lt;filters&gt;
221      *   &lt;filter&gt;
222      *     &lt;artifact&gt;junit:junit&lt;/artifact&gt;
223      *     &lt;includes&gt;
224      *       &lt;include&gt;org/junit/**&lt;/include&gt;
225      *     &lt;/includes&gt;
226      *     &lt;excludes&gt;
227      *       &lt;exclude&gt;org/junit/experimental/**&lt;/exclude&gt;
228      *     &lt;/excludes&gt;
229      *   &lt;/filter&gt;
230      * &lt;/filters&gt;
231      * </pre>
232      */
233     @SuppressWarnings( "MismatchedReadAndWriteOfArray" )
234     @Parameter
235     private ArchiveFilter[] filters;
236 
237     /**
238      * The destination directory for the shaded artifact.
239      */
240     @Parameter( defaultValue = "${project.build.directory}" )
241     private File outputDirectory;
242 
243     /**
244      * The name of the shaded artifactId.
245      * <p/>
246      * If you like to change the name of the native artifact, you may use the &lt;build>&lt;finalName> setting. If this
247      * is set to something different than &lt;build>&lt;finalName>, no file replacement will be performed, even if
248      * shadedArtifactAttached is being used.
249      */
250     @Parameter
251     private String finalName;
252 
253     /**
254      * The name of the shaded artifactId. So you may want to use a different artifactId and keep the standard version.
255      * If the original artifactId was "foo" then the final artifact would be something like foo-1.0.jar. So if you
256      * change the artifactId you might have something like foo-special-1.0.jar.
257      */
258     @Parameter( defaultValue = "${project.artifactId}" )
259     private String shadedArtifactId;
260 
261     /**
262      * If specified, this will include only artifacts which have groupIds which start with this.
263      */
264     @Parameter
265     private String shadedGroupFilter;
266 
267     /**
268      * Defines whether the shaded artifact should be attached as classifier to the original artifact. If false, the
269      * shaded jar will be the main artifact of the project
270      */
271     @Parameter
272     private boolean shadedArtifactAttached;
273 
274     /**
275      * Flag whether to generate a simplified POM for the shaded artifact. If set to <code>true</code>, dependencies that
276      * have been included into the uber JAR will be removed from the <code>&lt;dependencies&gt;</code> section of the
277      * generated POM. The reduced POM will be named <code>dependency-reduced-pom.xml</code> and is stored into the same
278      * directory as the shaded artifact. Unless you also specify dependencyReducedPomLocation, the plugin will create a
279      * temporary file named <code>dependency-reduced-pom.xml</code> in the project basedir.
280      */
281     @Parameter( defaultValue = "true" )
282     private boolean createDependencyReducedPom;
283 
284     /**
285      * Where to put the dependency reduced pom. Note: setting a value for this parameter with a directory other than
286      * ${basedir} will change the value of ${basedir} for all executions that come after the shade execution. This is
287      * often not what you want. This is considered an open issue with this plugin.
288      *
289      * @since 1.7
290      */
291     @Parameter( defaultValue = "${basedir}/dependency-reduced-pom.xml" )
292     private File dependencyReducedPomLocation;
293 
294     /**
295      * Create a dependency-reduced POM in ${basedir}/drp-UNIQUE.pom. This avoids build collisions of parallel builds
296      * without moving the dependency-reduced POM to a different directory. The property
297      * maven.shade.dependency-reduced-pom is set to the generated filename.
298      *
299      * @since 1.7.2
300      */
301     @Parameter( defaultValue = "false" )
302     private boolean generateUniqueDependencyReducedPom;
303 
304     /**
305      * When true, dependencies are kept in the pom but with scope 'provided'; when false, the dependency is removed.
306      */
307     @Parameter
308     private boolean keepDependenciesWithProvidedScope;
309 
310     /**
311      * When true, transitive deps of removed dependencies are promoted to direct dependencies. This should allow the
312      * drop in replacement of the removed deps with the new shaded jar and everything should still work.
313      */
314     @Parameter
315     private boolean promoteTransitiveDependencies;
316 
317     /**
318      * The name of the classifier used in case the shaded artifact is attached.
319      */
320     @Parameter( defaultValue = "shaded" )
321     private String shadedClassifierName;
322 
323     /**
324      * When true, it will attempt to create a sources jar as well
325      */
326     @Parameter
327     private boolean createSourcesJar;
328 
329     /**
330      * When true, it will attempt to shade the contents of the java source files when creating the sources jar. When
331      * false, it will just relocate the java source files to the shaded paths, but will not modify the actual contents
332      * of the java source files.
333      */
334     @Parameter( property = "shadeSourcesContent", defaultValue = "false" )
335     private boolean shadeSourcesContent;
336 
337     /**
338      * When true, dependencies will be stripped down on the class level to only the transitive hull required for the
339      * artifact. <em>Note:</em> Usage of this feature requires Java 1.5 or higher.
340      *
341      * @since 1.4
342      */
343     @Parameter
344     private boolean minimizeJar;
345 
346     /**
347      * The path to the output file for the shaded artifact. When this parameter is set, the created archive will neither
348      * replace the project's main artifact nor will it be attached. Hence, this parameter causes the parameters
349      * {@link #finalName}, {@link #shadedArtifactAttached}, {@link #shadedClassifierName} and
350      * {@link #createDependencyReducedPom} to be ignored when used.
351      *
352      * @since 1.3
353      */
354     @Parameter
355     private File outputFile;
356 
357     /**
358      * You can pass here the roleHint about your own Shader implementation plexus component.
359      *
360      * @since 1.6
361      */
362     @Parameter
363     private String shaderHint;
364 
365     /**
366      * When true, the version of each dependency of the reduced pom will be based on the baseVersion of the original
367      * dependency instead of its resolved version. For example, if the original pom (transitively) depends on
368      * a:a:2.7-SNAPSHOT, if useBaseVersion is set to false, the reduced pom will depend on a:a:2.7-20130312.222222-12
369      * whereas if useBaseVersion is set to true, the reduced pom will depend on a:a:2.7-SNAPSHOT
370      *
371      * @since 3.0
372      */
373     @Parameter( defaultValue = "false" )
374     private boolean useBaseVersion;
375 
376     @Parameter( defaultValue = "false" )
377     private boolean shadeTestJar;
378 
379     /**
380      * @since 1.6
381      */
382     private PlexusContainer plexusContainer;
383 
384     public void contextualize( Context context )
385         throws ContextException
386     {
387         plexusContainer = (PlexusContainer) context.get( PlexusConstants.PLEXUS_KEY );
388     }
389 
390     /**
391      * @throws MojoExecutionException
392      */
393     public void execute()
394         throws MojoExecutionException
395     {
396 
397         setupHintedShader();
398 
399         Set<File> artifacts = new LinkedHashSet<File>();
400         Set<String> artifactIds = new LinkedHashSet<String>();
401         Set<File> sourceArtifacts = new LinkedHashSet<File>();
402         Set<File> testArtifacts = new LinkedHashSet<File>();
403 
404         ArtifactSelector artifactSelector =
405             new ArtifactSelector( project.getArtifact(), artifactSet, shadedGroupFilter );
406 
407         if ( artifactSelector.isSelected( project.getArtifact() ) && !"pom".equals( project.getArtifact().getType() ) )
408         {
409             if ( invalidMainArtifact() )
410             {
411                 createErrorOutput();
412                 throw new MojoExecutionException( "Failed to create shaded artifact, "
413                     + "project main artifact does not exist." );
414             }
415 
416             artifacts.add( project.getArtifact().getFile() );
417 
418             if ( createSourcesJar )
419             {
420                 File file = shadedSourcesArtifactFile();
421                 if ( file.isFile() )
422                 {
423                     sourceArtifacts.add( file );
424                 }
425             }
426 
427             if ( shadeTestJar )
428             {
429                 File file = shadedTestArtifactFile();
430                 if ( file.isFile() )
431                 {
432                     testArtifacts.add( file );
433                 }
434             }
435         }
436 
437         processArtifactSelectors( artifacts, artifactIds, sourceArtifacts, artifactSelector );
438 
439         File outputJar = ( outputFile != null ) ? outputFile : shadedArtifactFileWithClassifier();
440         File sourcesJar = shadedSourceArtifactFileWithClassifier();
441         File testJar = shadedTestArtifactFileWithClassifier();
442 
443         // Now add our extra resources
444         try
445         {
446             List<Filter> filters = getFilters();
447 
448             List<Relocator> relocators = getRelocators();
449 
450             List<ResourceTransformer> resourceTransformers = getResourceTransformers();
451 
452             ShadeRequest shadeRequest = shadeRequest( artifacts, outputJar, filters, relocators, resourceTransformers );
453 
454             shader.shade( shadeRequest );
455 
456             if ( createSourcesJar )
457             {
458                 ShadeRequest shadeSourcesRequest =
459                     createShadeSourcesRequest( sourceArtifacts, sourcesJar, filters, relocators, resourceTransformers );
460 
461                 shader.shade( shadeSourcesRequest );
462             }
463 
464             if ( shadeTestJar )
465             {
466 
467                 ShadeRequest shadeSourcesRequest =
468                     createShadeSourcesRequest( testArtifacts, testJar, filters, relocators, resourceTransformers );
469 
470                 shader.shade( shadeSourcesRequest );
471             }
472 
473             if ( outputFile == null )
474             {
475                 boolean renamed = false;
476 
477                 // rename the output file if a specific finalName is set
478                 // but don't rename if the finalName is the <build><finalName>
479                 // because this will be handled implicitly later
480                 if ( finalName != null && finalName.length() > 0 //
481                     && !finalName.equals( project.getBuild().getFinalName() ) )
482                 {
483                     String finalFileName = finalName + "." + project.getArtifact().getArtifactHandler().getExtension();
484                     File finalFile = new File( outputDirectory, finalFileName );
485                     replaceFile( finalFile, outputJar );
486                     outputJar = finalFile;
487 
488                     renamed = true;
489                 }
490 
491                 if ( shadedArtifactAttached )
492                 {
493                     getLog().info( "Attaching shaded artifact." );
494                     projectHelper.attachArtifact( project, project.getArtifact().getType(), shadedClassifierName,
495                                                   outputJar );
496                     if ( createSourcesJar )
497                     {
498                         projectHelper.attachArtifact( project, "java-source", shadedClassifierName + "-sources",
499                                                       sourcesJar );
500                     }
501                 }
502                 else if ( !renamed )
503                 {
504                     getLog().info( "Replacing original artifact with shaded artifact." );
505                     File originalArtifact = project.getArtifact().getFile();
506                     if ( originalArtifact != null )
507                     {
508                         replaceFile( originalArtifact, outputJar );
509 
510                         if ( createSourcesJar )
511                         {
512                             getLog().info( "Replacing original source artifact with shaded source artifact." );
513                             File shadedSources = shadedSourcesArtifactFile();
514 
515                             replaceFile( shadedSources, sourcesJar );
516 
517                             projectHelper.attachArtifact( project, "java-source", "sources", shadedSources );
518                         }
519 
520                         if ( shadeTestJar )
521                         {
522                             getLog().info( "Replacing original test artifact with shaded test artifact." );
523                             File shadedTests = shadedTestArtifactFile();
524 
525                             replaceFile( shadedTests, testJar );
526 
527                             projectHelper.attachArtifact( project, "jar", "tests", shadedTests );
528                         }
529 
530                         if ( createDependencyReducedPom )
531                         {
532                             createDependencyReducedPom( artifactIds );
533                         }
534                     }
535                 }
536             }
537         }
538         catch ( Exception e )
539         {
540             throw new MojoExecutionException( "Error creating shaded jar: " + e.getMessage(), e );
541         }
542     }
543 
544     private void createErrorOutput()
545     {
546         getLog().error( "The project main artifact does not exist. This could have the following" );
547         getLog().error( "reasons:" );
548         getLog().error( "- You have invoked the goal directly from the command line. This is not" );
549         getLog().error( "  supported. Please add the goal to the default lifecycle via an" );
550         getLog().error( "  <execution> element in your POM and use \"mvn package\" to have it run." );
551         getLog().error( "- You have bound the goal to a lifecycle phase before \"package\". Please" );
552         getLog().error( "  remove this binding from your POM such that the goal will be run in" );
553         getLog().error( "  the proper phase." );
554         getLog().error( "- You removed the configuration of the maven-jar-plugin that produces the main artifact." );
555     }
556 
557     private ShadeRequest shadeRequest( Set<File> artifacts, File outputJar, List<Filter> filters,
558                                        List<Relocator> relocators, List<ResourceTransformer> resourceTransformers )
559     {
560         ShadeRequest shadeRequest = new ShadeRequest();
561         shadeRequest.setJars( artifacts );
562         shadeRequest.setUberJar( outputJar );
563         shadeRequest.setFilters( filters );
564         shadeRequest.setRelocators( relocators );
565         shadeRequest.setResourceTransformers( resourceTransformers );
566         return shadeRequest;
567     }
568 
569     private ShadeRequest createShadeSourcesRequest( Set<File> testArtifacts, File testJar, List<Filter> filters,
570                                                     List<Relocator> relocators,
571                                                     List<ResourceTransformer> resourceTransformers )
572     {
573         ShadeRequest shadeSourcesRequest =
574             shadeRequest( testArtifacts, testJar, filters, relocators, resourceTransformers );
575         shadeSourcesRequest.setShadeSourcesContent( shadeSourcesContent );
576         return shadeSourcesRequest;
577     }
578 
579     private void setupHintedShader()
580         throws MojoExecutionException
581     {
582         if ( shaderHint != null )
583         {
584             try
585             {
586                 shader = (Shader) plexusContainer.lookup( Shader.ROLE, shaderHint );
587             }
588             catch ( ComponentLookupException e )
589             {
590                 throw new MojoExecutionException( "unable to lookup own Shader implementation with hint:'" + shaderHint
591                     + "'", e );
592             }
593         }
594     }
595 
596     private void processArtifactSelectors( Set<File> artifacts, Set<String> artifactIds, Set<File> sourceArtifacts,
597                                            ArtifactSelector artifactSelector )
598     {
599         for ( Artifact artifact : project.getArtifacts() )
600         {
601             if ( !artifactSelector.isSelected( artifact ) )
602             {
603                 getLog().info( "Excluding " + artifact.getId() + " from the shaded jar." );
604 
605                 continue;
606             }
607 
608             if ( "pom".equals( artifact.getType() ) )
609             {
610                 getLog().info( "Skipping pom dependency " + artifact.getId() + " in the shaded jar." );
611                 continue;
612             }
613 
614             getLog().info( "Including " + artifact.getId() + " in the shaded jar." );
615 
616             artifacts.add( artifact.getFile() );
617             artifactIds.add( getId( artifact ) );
618 
619             if ( createSourcesJar )
620             {
621                 File file = resolveArtifactSources( artifact );
622                 if ( file != null )
623                 {
624                     sourceArtifacts.add( file );
625                 }
626             }
627         }
628     }
629 
630     private boolean invalidMainArtifact()
631     {
632         return project.getArtifact().getFile() == null || !project.getArtifact().getFile().isFile();
633     }
634 
635     private void replaceFile( File oldFile, File newFile )
636         throws MojoExecutionException
637     {
638         getLog().info( "Replacing " + oldFile + " with " + newFile );
639 
640         File origFile = new File( outputDirectory, "original-" + oldFile.getName() );
641         if ( oldFile.exists() && !oldFile.renameTo( origFile ) )
642         {
643             // try a gc to see if an unclosed stream needs garbage collecting
644             System.gc();
645             System.gc();
646 
647             if ( !oldFile.renameTo( origFile ) )
648             {
649                 // Still didn't work. We'll do a copy
650                 try
651                 {
652                     copyFiles( oldFile, origFile );
653                 }
654                 catch ( IOException ex )
655                 {
656                     // kind of ignorable here. We're just trying to save the original
657                     getLog().warn( ex );
658                 }
659             }
660         }
661         if ( !newFile.renameTo( oldFile ) )
662         {
663             // try a gc to see if an unclosed stream needs garbage collecting
664             System.gc();
665             System.gc();
666 
667             if ( !newFile.renameTo( oldFile ) )
668             {
669                 // Still didn't work. We'll do a copy
670                 try
671                 {
672                     copyFiles( newFile, oldFile );
673                 }
674                 catch ( IOException ex )
675                 {
676                     throw new MojoExecutionException( "Could not replace original artifact with shaded artifact!", ex );
677                 }
678             }
679         }
680     }
681 
682     private void copyFiles( File source, File target )
683         throws IOException
684     {
685         FileOutputStream fout = new FileOutputStream( target );
686         FileInputStream fin = new FileInputStream( source );
687         try
688         {
689             IOUtil.copy( fin, fout );
690         }
691         finally
692         {
693             IOUtil.close( fin );
694             IOUtil.close( fout );
695         }
696     }
697 
698     private File resolveArtifactSources( Artifact artifact )
699     {
700 
701         Artifact resolvedArtifact =
702             artifactFactory.createArtifactWithClassifier( artifact.getGroupId(), artifact.getArtifactId(),
703                                                           artifact.getVersion(), "java-source", "sources" );
704 
705         try
706         {
707             artifactResolver.resolve( resolvedArtifact, remoteArtifactRepositories, localRepository );
708         }
709         catch ( ArtifactNotFoundException e )
710         {
711             // ignore, the jar has not been found
712         }
713         catch ( ArtifactResolutionException e )
714         {
715             getLog().warn( "Could not get sources for " + artifact );
716         }
717 
718         if ( resolvedArtifact.isResolved() )
719         {
720             return resolvedArtifact.getFile();
721         }
722         return null;
723     }
724 
725     private List<Relocator> getRelocators()
726     {
727         List<Relocator> relocators = new ArrayList<Relocator>();
728 
729         if ( relocations == null )
730         {
731             return relocators;
732         }
733 
734         for ( PackageRelocation r : relocations )
735         {
736             relocators.add( new SimpleRelocator( r.getPattern(), r.getShadedPattern(), r.getIncludes(), r.getExcludes(),
737                                                  r.isRawString() ) );
738         }
739 
740         return relocators;
741     }
742 
743     private List<ResourceTransformer> getResourceTransformers()
744     {
745         if ( transformers == null )
746         {
747             return Collections.emptyList();
748         }
749 
750         return Arrays.asList( transformers );
751     }
752 
753     private List<Filter> getFilters()
754         throws MojoExecutionException
755     {
756         List<Filter> filters = new ArrayList<Filter>();
757         List<SimpleFilter> simpleFilters = new ArrayList<SimpleFilter>();
758 
759         if ( this.filters != null && this.filters.length > 0 )
760         {
761             Map<Artifact, ArtifactId> artifacts = new HashMap<Artifact, ArtifactId>();
762 
763             artifacts.put( project.getArtifact(), new ArtifactId( project.getArtifact() ) );
764 
765             for ( Artifact artifact : project.getArtifacts() )
766             {
767                 artifacts.put( artifact, new ArtifactId( artifact ) );
768             }
769 
770             for ( ArchiveFilter filter : this.filters )
771             {
772                 ArtifactId pattern = new ArtifactId( filter.getArtifact() );
773 
774                 Set<File> jars = new HashSet<File>();
775 
776                 for ( Map.Entry<Artifact, ArtifactId> entry : artifacts.entrySet() )
777                 {
778                     if ( entry.getValue().matches( pattern ) )
779                     {
780                         Artifact artifact = entry.getKey();
781 
782                         jars.add( artifact.getFile() );
783 
784                         if ( createSourcesJar )
785                         {
786                             File file = resolveArtifactSources( artifact );
787                             if ( file != null )
788                             {
789                                 jars.add( file );
790                             }
791                         }
792                     }
793                 }
794 
795                 if ( jars.isEmpty() )
796                 {
797                     getLog().info( "No artifact matching filter " + filter.getArtifact() );
798 
799                     continue;
800                 }
801 
802                 simpleFilters.add( new SimpleFilter( jars, filter.getIncludes(), filter.getExcludes() ) );
803             }
804         }
805 
806         filters.addAll( simpleFilters );
807 
808         if ( minimizeJar )
809         {
810             getLog().info( "Minimizing jar " + project.getArtifact() );
811 
812             try
813             {
814                 filters.add( new MinijarFilter( project, getLog(), simpleFilters ) );
815             }
816             catch ( IOException e )
817             {
818                 throw new MojoExecutionException( "Failed to analyze class dependencies", e );
819             }
820         }
821 
822         return filters;
823     }
824 
825     private File shadedArtifactFileWithClassifier()
826     {
827         Artifact artifact = project.getArtifact();
828         final String shadedName = shadedArtifactId + "-" + artifact.getVersion() + "-" + shadedClassifierName + "."
829             + artifact.getArtifactHandler().getExtension();
830         return new File( outputDirectory, shadedName );
831     }
832 
833     private File shadedSourceArtifactFileWithClassifier()
834     {
835         Artifact artifact = project.getArtifact();
836         final String shadedName = shadedArtifactId + "-" + artifact.getVersion() + "-" + shadedClassifierName
837             + "-sources." + artifact.getArtifactHandler().getExtension();
838         return new File( outputDirectory, shadedName );
839     }
840 
841     private File shadedTestArtifactFileWithClassifier()
842     {
843         Artifact artifact = project.getArtifact();
844         final String shadedName = shadedArtifactId + "-" + artifact.getVersion() + "-" + shadedClassifierName
845             + "-tests." + artifact.getArtifactHandler().getExtension();
846         return new File( outputDirectory, shadedName );
847     }
848 
849     private File shadedSourcesArtifactFile()
850     {
851         Artifact artifact = project.getArtifact();
852 
853         String shadedName;
854 
855         if ( project.getBuild().getFinalName() != null )
856         {
857             shadedName = project.getBuild().getFinalName() + "-sources." + artifact.getArtifactHandler().getExtension();
858         }
859         else
860         {
861             shadedName = shadedArtifactId + "-" + artifact.getVersion() + "-sources."
862                 + artifact.getArtifactHandler().getExtension();
863         }
864 
865         return new File( outputDirectory, shadedName );
866     }
867 
868     private File shadedTestArtifactFile()
869     {
870         Artifact artifact = project.getArtifact();
871 
872         String shadedName;
873 
874         if ( project.getBuild().getFinalName() != null )
875         {
876             shadedName = project.getBuild().getFinalName() + "-tests." + artifact.getArtifactHandler().getExtension();
877         }
878         else
879         {
880             shadedName = shadedArtifactId + "-" + artifact.getVersion() + "-tests."
881                 + artifact.getArtifactHandler().getExtension();
882         }
883 
884         return new File( outputDirectory, shadedName );
885     }
886 
887     // We need to find the direct dependencies that have been included in the uber JAR so that we can modify the
888     // POM accordingly.
889     private void createDependencyReducedPom( Set<String> artifactsToRemove )
890         throws IOException, DependencyGraphBuilderException, ProjectBuildingException
891     {
892         List<Dependency> dependencies = new ArrayList<Dependency>();
893 
894         boolean modified = false;
895 
896         List<Dependency> transitiveDeps = new ArrayList<Dependency>();
897 
898         // NOTE: By using the getArtifacts() we get the completely evaluated artifacts
899         // including the system scoped artifacts with expanded values of properties used.
900         for ( Artifact artifact : project.getArtifacts() )
901         {
902             if ( "pom".equals( artifact.getType() ) )
903             {
904                 // don't include pom type dependencies in dependency reduced pom
905                 continue;
906             }
907 
908             // promote
909             Dependency dep = createDependency( artifact );
910 
911             // we'll figure out the exclusions in a bit.
912             transitiveDeps.add( dep );
913         }
914         List<Dependency> origDeps = project.getDependencies();
915 
916         if ( promoteTransitiveDependencies )
917         {
918             origDeps = transitiveDeps;
919         }
920 
921         Model model = project.getOriginalModel();
922         // MSHADE-185: We will remove all system scoped dependencies which usually
923         // have some kind of property usage. At this time the properties within
924         // such things are already evaluated.
925         List<Dependency> originalDependencies = model.getDependencies();
926         removeSystemScopedDependencies( artifactsToRemove, originalDependencies );
927 
928         for ( Dependency d : origDeps )
929         {
930             dependencies.add( d );
931 
932             String id = getId( d );
933 
934             if ( artifactsToRemove.contains( id ) )
935             {
936                 modified = true;
937 
938                 if ( keepDependenciesWithProvidedScope )
939                 {
940                     d.setScope( "provided" );
941                 }
942                 else
943                 {
944                     dependencies.remove( d );
945                 }
946             }
947         }
948 
949         // MSHADE-155
950         model.setArtifactId( shadedArtifactId );
951 
952         // MSHADE-185: We will add those system scoped dependencies
953         // from the non interpolated original pom file. So we keep
954         // things like this: <systemPath>${tools.jar}</systemPath> intact.
955         addSystemScopedDependencyFromNonInterpolatedPom( dependencies, originalDependencies );
956 
957         // Check to see if we have a reduction and if so rewrite the POM.
958         rewriteDependencyReducedPomIfWeHaveReduction( dependencies, modified, transitiveDeps, model );
959     }
960 
961     private void rewriteDependencyReducedPomIfWeHaveReduction( List<Dependency> dependencies, boolean modified,
962                                                                List<Dependency> transitiveDeps, Model model )
963                                                                    throws IOException, ProjectBuildingException,
964                                                                    DependencyGraphBuilderException
965     {
966         if ( modified )
967         {
968             while ( modified )
969             {
970 
971                 model.setDependencies( dependencies );
972 
973                 if ( generateUniqueDependencyReducedPom )
974                 {
975                     dependencyReducedPomLocation =
976                         File.createTempFile( "dependency-reduced-pom-", ".xml", project.getBasedir() );
977                     project.getProperties().setProperty( "maven.shade.dependency-reduced-pom",
978                                                          dependencyReducedPomLocation.getAbsolutePath() );
979                 }
980                 else
981                 {
982                     if ( dependencyReducedPomLocation == null )
983                     {
984                         // MSHADE-123: We can't default to 'target' because it messes up uses of ${project.basedir}
985                         dependencyReducedPomLocation = new File( project.getBasedir(), "dependency-reduced-pom.xml" );
986                     }
987                 }
988 
989                 File f = dependencyReducedPomLocation;
990                 getLog().info( "Dependency-reduced POM written at: " + f.getAbsolutePath() );
991 
992                 if ( f.exists() )
993                 {
994                     // noinspection ResultOfMethodCallIgnored
995                     f.delete();
996                 }
997 
998                 Writer w = WriterFactory.newXmlWriter( f );
999 
1000                 String replaceRelativePath = null;
1001                 if ( model.getParent() != null )
1002                 {
1003                     replaceRelativePath = model.getParent().getRelativePath();
1004 
1005                 }
1006 
1007                 if ( model.getParent() != null )
1008                 {
1009                     File parentFile =
1010                         new File( project.getBasedir(), model.getParent().getRelativePath() ).getCanonicalFile();
1011                     if ( !parentFile.isFile() )
1012                     {
1013                         parentFile = new File( parentFile, "pom.xml" );
1014                     }
1015 
1016                     parentFile = parentFile.getCanonicalFile();
1017 
1018                     String relPath = RelativizePath.convertToRelativePath( parentFile, f );
1019                     model.getParent().setRelativePath( relPath );
1020                 }
1021 
1022                 try
1023                 {
1024                     PomWriter.write( w, model, true );
1025                 }
1026                 finally
1027                 {
1028                     if ( model.getParent() != null )
1029                     {
1030                         model.getParent().setRelativePath( replaceRelativePath );
1031                     }
1032                     w.close();
1033                 }
1034 
1035                 ProjectBuildingRequest projectBuildingRequest =
1036                     new DefaultProjectBuildingRequest( session.getProjectBuildingRequest() );
1037                 projectBuildingRequest.setLocalRepository( localRepository );
1038                 projectBuildingRequest.setRemoteRepositories( remoteArtifactRepositories );
1039 
1040                 ProjectBuildingResult result = projectBuilder.build( f, projectBuildingRequest );
1041 
1042                 getLog().debug( "updateExcludesInDeps()" );
1043                 modified = updateExcludesInDeps( result.getProject(), dependencies, transitiveDeps );
1044             }
1045 
1046             project.setFile( dependencyReducedPomLocation );
1047         }
1048     }
1049 
1050     private void removeSystemScopedDependencies( Set<String> artifactsToRemove, List<Dependency> originalDependencies )
1051     {
1052         for ( Dependency dependency : originalDependencies )
1053         {
1054             if ( dependency.getScope() != null && dependency.getScope().equalsIgnoreCase( "system" ) )
1055             {
1056                 artifactsToRemove.add( getId( dependency ) );
1057             }
1058         }
1059     }
1060 
1061     private void addSystemScopedDependencyFromNonInterpolatedPom( List<Dependency> dependencies,
1062                                                                   List<Dependency> originalDependencies )
1063     {
1064         for ( Dependency dependency : originalDependencies )
1065         {
1066             if ( dependency.getScope() != null && dependency.getScope().equalsIgnoreCase( "system" ) )
1067             {
1068                 dependencies.add( dependency );
1069             }
1070         }
1071     }
1072 
1073     private Dependency createDependency( Artifact artifact )
1074     {
1075         Dependency dep = new Dependency();
1076         dep.setArtifactId( artifact.getArtifactId() );
1077         if ( artifact.hasClassifier() )
1078         {
1079             dep.setClassifier( artifact.getClassifier() );
1080         }
1081         dep.setGroupId( artifact.getGroupId() );
1082         dep.setOptional( artifact.isOptional() );
1083         dep.setScope( artifact.getScope() );
1084         dep.setType( artifact.getType() );
1085         if ( useBaseVersion )
1086         {
1087             dep.setVersion( artifact.getBaseVersion() );
1088         }
1089         else
1090         {
1091             dep.setVersion( artifact.getVersion() );
1092         }
1093         return dep;
1094     }
1095 
1096     private String getId( Artifact artifact )
1097     {
1098         return getId( artifact.getGroupId(), artifact.getArtifactId(), artifact.getType(), artifact.getClassifier() );
1099     }
1100 
1101     private String getId( Dependency dependency )
1102     {
1103         return getId( dependency.getGroupId(), dependency.getArtifactId(), dependency.getType(),
1104                       dependency.getClassifier() );
1105     }
1106 
1107     private String getId( String groupId, String artifactId, String type, String classifier )
1108     {
1109         return groupId + ":" + artifactId + ":" + type + ":" + ( ( classifier != null ) ? classifier : "" );
1110     }
1111 
1112     public boolean updateExcludesInDeps( MavenProject project, List<Dependency> dependencies,
1113                                          List<Dependency> transitiveDeps )
1114                                              throws DependencyGraphBuilderException
1115     {
1116         DependencyNode node = dependencyGraphBuilder.buildDependencyGraph( project, null );
1117         boolean modified = false;
1118         for ( DependencyNode n2 : node.getChildren() )
1119         {
1120             for ( DependencyNode n3 : n2.getChildren() )
1121             {
1122                 // check if it really isn't in the list of original dependencies. Maven
1123                 // prior to 2.0.8 may grab versions from transients instead of
1124                 // from the direct deps in which case they would be marked included
1125                 // instead of OMITTED_FOR_DUPLICATE
1126 
1127                 // also, if not promoting the transitives, level 2's would be included
1128                 boolean found = false;
1129                 for ( Dependency dep : transitiveDeps )
1130                 {
1131                     if ( dep.getArtifactId().equals( n3.getArtifact().getArtifactId() )
1132                         && dep.getGroupId().equals( n3.getArtifact().getGroupId() )
1133                         && ( dep.getType() == null || dep.getType().equals( n3.getArtifact().getType() ) ) )
1134                     {
1135                         found = true;
1136                         break;
1137                     }
1138                 }
1139 
1140                 if ( !found )
1141                 {
1142                     for ( Dependency dep : dependencies )
1143                     {
1144                         if ( dep.getArtifactId().equals( n2.getArtifact().getArtifactId() )
1145                             && dep.getGroupId().equals( n2.getArtifact().getGroupId() )
1146                             && ( dep.getType() == null || dep.getType().equals( n2.getArtifact().getType() ) ) )
1147                         {
1148                             Exclusion exclusion = new Exclusion();
1149                             exclusion.setArtifactId( n3.getArtifact().getArtifactId() );
1150                             exclusion.setGroupId( n3.getArtifact().getGroupId() );
1151                             dep.addExclusion( exclusion );
1152                             modified = true;
1153                             break;
1154                         }
1155                     }
1156                 }
1157             }
1158         }
1159         return modified;
1160     }
1161 }