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