View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.plugins.shade.mojo;
20  
21  import javax.inject.Inject;
22  
23  import java.io.File;
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.io.OutputStream;
27  import java.io.Writer;
28  import java.nio.file.Files;
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  import java.util.stream.Collectors;
39  
40  import org.apache.maven.RepositoryUtils;
41  import org.apache.maven.artifact.Artifact;
42  import org.apache.maven.artifact.DefaultArtifact;
43  import org.apache.maven.artifact.versioning.VersionRange;
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.LifecyclePhase;
51  import org.apache.maven.plugins.annotations.Mojo;
52  import org.apache.maven.plugins.annotations.Parameter;
53  import org.apache.maven.plugins.annotations.ResolutionScope;
54  import org.apache.maven.plugins.shade.ShadeRequest;
55  import org.apache.maven.plugins.shade.Shader;
56  import org.apache.maven.plugins.shade.filter.Filter;
57  import org.apache.maven.plugins.shade.filter.MinijarFilter;
58  import org.apache.maven.plugins.shade.filter.SimpleFilter;
59  import org.apache.maven.plugins.shade.pom.PomWriter;
60  import org.apache.maven.plugins.shade.relocation.Relocator;
61  import org.apache.maven.plugins.shade.relocation.SimpleRelocator;
62  import org.apache.maven.plugins.shade.resource.ManifestResourceTransformer;
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.codehaus.plexus.util.IOUtil;
72  import org.codehaus.plexus.util.WriterFactory;
73  import org.eclipse.aether.RepositorySystem;
74  import org.eclipse.aether.collection.CollectRequest;
75  import org.eclipse.aether.collection.CollectResult;
76  import org.eclipse.aether.collection.DependencyCollectionException;
77  import org.eclipse.aether.graph.DependencyNode;
78  import org.eclipse.aether.resolution.ArtifactRequest;
79  import org.eclipse.aether.resolution.ArtifactResolutionException;
80  
81  import static org.apache.maven.plugins.shade.resource.UseDependencyReducedPom.createPomReplaceTransformers;
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(
93          name = "shade",
94          defaultPhase = LifecyclePhase.PACKAGE,
95          threadSafe = true,
96          requiresDependencyResolution = ResolutionScope.RUNTIME)
97  // CHECKSTYLE_ON: LineLength
98  public class ShadeMojo extends AbstractMojo {
99      /**
100      * The current Maven session.
101      */
102     @Parameter(defaultValue = "${session}", readonly = true, required = true)
103     private MavenSession session;
104 
105     /**
106      * The current Maven project.
107      */
108     @Parameter(defaultValue = "${project}", readonly = true, required = true)
109     private MavenProject project;
110 
111     /**
112      * Artifacts to include/exclude from the final artifact. Artifacts are denoted by composite identifiers of the
113      * general form <code>groupId:artifactId:type:classifier</code>. Since version 1.3, the wildcard characters '*' and
114      * '?' can be used within the sub parts of those composite identifiers to do pattern matching. For convenience, the
115      * syntax <code>groupId</code> is equivalent to <code>groupId:*:*:*</code>, <code>groupId:artifactId</code> is
116      * equivalent to <code>groupId:artifactId:*:*</code> and <code>groupId:artifactId:classifier</code> is equivalent to
117      * <code>groupId:artifactId:*:classifier</code>. For example:
118      *
119      * <pre>
120      * &lt;artifactSet&gt;
121      *   &lt;includes&gt;
122      *     &lt;include&gt;org.apache.maven:*&lt;/include&gt;
123      *   &lt;/includes&gt;
124      *   &lt;excludes&gt;
125      *     &lt;exclude&gt;*:maven-core&lt;/exclude&gt;
126      *   &lt;/excludes&gt;
127      * &lt;/artifactSet&gt;
128      * </pre>
129      */
130     @Parameter
131     private ArtifactSet artifactSet;
132 
133     /**
134      * Packages to be relocated. For example:
135      *
136      * <pre>
137      * &lt;relocations&gt;
138      *   &lt;relocation&gt;
139      *     &lt;pattern&gt;org.apache&lt;/pattern&gt;
140      *     &lt;shadedPattern&gt;hidden.org.apache&lt;/shadedPattern&gt;
141      *     &lt;includes&gt;
142      *       &lt;include&gt;org.apache.maven.*&lt;/include&gt;
143      *     &lt;/includes&gt;
144      *     &lt;excludes&gt;
145      *       &lt;exclude&gt;org.apache.maven.Public*&lt;/exclude&gt;
146      *     &lt;/excludes&gt;
147      *   &lt;/relocation&gt;
148      * &lt;/relocations&gt;
149      * </pre>
150      *
151      * <em>Note:</em> Support for includes exists only since version 1.4.
152      */
153     @SuppressWarnings("MismatchedReadAndWriteOfArray")
154     @Parameter
155     private PackageRelocation[] relocations;
156 
157     /**
158      * Resource transformers to be used. Please see the "Examples" section for more information on available
159      * transformers and their configuration.
160      */
161     @Parameter
162     private ResourceTransformer[] transformers;
163 
164     /**
165      * Archive Filters to be used. Allows you to specify an artifact in the form of a composite identifier as used by
166      * {@link #artifactSet} and a set of include/exclude file patterns for filtering which contents of the archive are
167      * added to the shaded jar. From a logical perspective, includes are processed before excludes, thus it's possible
168      * to use an include to collect a set of files from the archive then use excludes to further reduce the set. By
169      * default, all files are included and no files are excluded. If multiple filters apply to an artifact, the
170      * intersection of the matched files will be included in the final JAR. For example:
171      *
172      * <pre>
173      * &lt;filters&gt;
174      *   &lt;filter&gt;
175      *     &lt;artifact&gt;junit:junit&lt;/artifact&gt;
176      *     &lt;includes&gt;
177      *       &lt;include&gt;org/junit/**&lt;/include&gt;
178      *     &lt;/includes&gt;
179      *     &lt;excludes&gt;
180      *       &lt;exclude&gt;org/junit/experimental/**&lt;/exclude&gt;
181      *     &lt;/excludes&gt;
182      *   &lt;/filter&gt;
183      * &lt;/filters&gt;
184      * </pre>
185      */
186     @SuppressWarnings("MismatchedReadAndWriteOfArray")
187     @Parameter
188     private ArchiveFilter[] filters;
189 
190     /**
191      * The destination directory for the shaded artifact.
192      */
193     @Parameter(defaultValue = "${project.build.directory}")
194     private File outputDirectory;
195 
196     /**
197      * The name of the shaded artifactId.
198      * <p/>
199      * If you like to change the name of the native artifact, you may use the &lt;build>&lt;finalName> setting. If this
200      * is set to something different than &lt;build>&lt;finalName>, no file replacement will be performed, even if
201      * shadedArtifactAttached is being used.
202      */
203     @Parameter
204     private String finalName;
205 
206     /**
207      * The name of the shaded artifactId. So you may want to use a different artifactId and keep the standard version.
208      * If the original artifactId was "foo" then the final artifact would be something like foo-1.0.jar. So if you
209      * change the artifactId you might have something like foo-special-1.0.jar.
210      */
211     @Parameter(defaultValue = "${project.artifactId}")
212     private String shadedArtifactId;
213 
214     /**
215      * If specified, this will include only artifacts which have groupIds which start with this.
216      */
217     @Parameter
218     private String shadedGroupFilter;
219 
220     /**
221      * Defines whether the shaded artifact should be attached as classifier to the original artifact. If false, the
222      * shaded jar will be the main artifact of the project
223      */
224     @Parameter
225     private boolean shadedArtifactAttached;
226 
227     /**
228      * Flag whether to generate a simplified POM for the shaded artifact. If set to <code>true</code>, dependencies that
229      * have been included into the uber JAR will be removed from the <code>&lt;dependencies&gt;</code> section of the
230      * generated POM. The reduced POM will be named <code>dependency-reduced-pom.xml</code> and is stored into the same
231      * directory as the shaded artifact. Unless you also specify dependencyReducedPomLocation, the plugin will create a
232      * temporary file named <code>dependency-reduced-pom.xml</code> in the project basedir.
233      */
234     @Parameter(defaultValue = "true")
235     private boolean createDependencyReducedPom;
236 
237     /**
238      * Where to put the dependency reduced pom. Note: setting a value for this parameter with a directory other than
239      * ${basedir} will change the value of ${basedir} for all executions that come after the shade execution. This is
240      * often not what you want. This is considered an open issue with this plugin.
241      *
242      * @since 1.7
243      */
244     @Parameter(defaultValue = "${basedir}/dependency-reduced-pom.xml")
245     private File dependencyReducedPomLocation;
246 
247     /**
248      * Create a dependency-reduced POM in ${basedir}/drp-UNIQUE.pom. This avoids build collisions of parallel builds
249      * without moving the dependency-reduced POM to a different directory. The property
250      * maven.shade.dependency-reduced-pom is set to the generated filename.
251      *
252      * @since 1.7.2
253      */
254     @Parameter(defaultValue = "false")
255     private boolean generateUniqueDependencyReducedPom;
256 
257     /**
258      * Add dependency reduced POM to the JAR instead of the original one provided by the project.
259      * If {@code createDependencyReducedPom} is {@code false} this parameter will be ignored.
260      *
261      * @since 3.3.0
262      */
263     @Parameter(defaultValue = "false")
264     private boolean useDependencyReducedPomInJar;
265 
266     /**
267      * When true, dependencies are kept in the pom but with scope 'provided'; when false, the dependency is removed.
268      */
269     @Parameter
270     private boolean keepDependenciesWithProvidedScope;
271 
272     /**
273      * When true, transitive deps of removed dependencies are promoted to direct dependencies. This should allow the
274      * drop in replacement of the removed deps with the new shaded jar and everything should still work.
275      */
276     @Parameter
277     private boolean promoteTransitiveDependencies;
278 
279     /**
280      * The name of the classifier used in case the shaded artifact is attached.
281      */
282     @Parameter(defaultValue = "shaded")
283     private String shadedClassifierName;
284 
285     /**
286      * When true, it will attempt to create a sources jar as well
287      */
288     @Parameter
289     private boolean createSourcesJar;
290 
291     /**
292      * When true, it will attempt to create a test sources jar.
293      */
294     @Parameter
295     private boolean createTestSourcesJar;
296 
297     /**
298      * When true, it will attempt to shade the contents of Java source files when creating the sources JAR. When false,
299      * it will just relocate the Java source files to the shaded paths, but will not modify the actual source file
300      * contents.
301      * <p>
302      * <b>Please note:</b> This feature uses a heuristic search & replace approach which covers many, but definitely not
303      * all possible cases of source code shading and its excludes. There is no full Java parser behind this
304      * functionality, which would be the only way to get this right for Java language elements. As for matching within
305      * Java string constants, this is next to impossible to get 100% right, trying to guess if they are used in
306      * reflection or not.
307      * <p>
308      * Please understand that the source shading feature is not meant as a source code generator anyway, merely as a
309      * tool creating reasonably plausible source code when navigating to a relocated library class from an IDE,
310      * hopefully displaying source code which makes 95% sense - no more, no less.
311      */
312     @Parameter(property = "shadeSourcesContent", defaultValue = "false")
313     private boolean shadeSourcesContent;
314 
315     /**
316      * When true, dependencies will be stripped down on the class level to only the transitive hull required for the
317      * artifact. See also {@link #entryPoints}, if you wish to further optimize JAR minimization.
318      * <p>
319      * <em>Note:</em> This feature uses
320      * <a href="https://github.com/tcurdt/jdependency">jdependency</a>. Its accuracy therefore depends on
321      * jdependency's limitations.
322      *
323      * @since 1.4
324      */
325     @Parameter
326     private boolean minimizeJar;
327 
328     /**
329      * Use this option in order to fine-tune {@link #minimizeJar}: By default, all of the target module's classes are
330      * kept and used as entry points for JAR minimization. By explicitly limiting the set of entry points, you can
331      * further minimize the set of classes kept in the shaded JAR. This affects both classes in the module itself and
332      * dependency classes. If {@link #minimizeJar} is inactive, this option has no effect either.
333      * <p>
334      * <em>Note:</em> This feature requires Java 1.8 or higher due to its use of
335      * <a href="https://github.com/tcurdt/jdependency">jdependency</a>. Its accuracy therefore also depends on
336      * jdependency's limitations.
337      * <p>
338      * Configuration example:
339      * <pre>{@code
340      * <minimizeJar>true</minimizeJar>
341      * <entryPoints>
342      *   <entryPoint>org.acme.Application</entryPoint>
343      *   <entryPoint>org.acme.OtherEntryPoint</entryPoint>
344      * </entryPoints>
345      * }</pre>
346      *
347      * @since 3.5.0
348      */
349     @Parameter
350     private Set<String> entryPoints;
351 
352     /**
353      * The path to the output file for the shaded artifact. When this parameter is set, the created archive will neither
354      * replace the project's main artifact nor will it be attached. Hence, this parameter causes the parameters
355      * {@link #finalName}, {@link #shadedArtifactAttached}, {@link #shadedClassifierName} and
356      * {@link #createDependencyReducedPom} to be ignored when used.
357      *
358      * @since 1.3
359      */
360     @Parameter
361     private File outputFile;
362 
363     /**
364      * You can pass here the roleHint about your own Shader implementation plexus component.
365      *
366      * @since 1.6
367      */
368     @Parameter
369     private String shaderHint;
370 
371     /**
372      * When true, the version of each dependency of the reduced pom will be based on the baseVersion of the original
373      * dependency instead of its resolved version. For example, if the original pom (transitively) depends on
374      * a:a:2.7-SNAPSHOT, if useBaseVersion is set to false, the reduced pom will depend on a:a:2.7-20130312.222222-12
375      * whereas if useBaseVersion is set to true, the reduced pom will depend on a:a:2.7-SNAPSHOT
376      *
377      * @since 3.0
378      */
379     @Parameter(defaultValue = "false")
380     private boolean useBaseVersion;
381 
382     /**
383      * When true, creates a shaded test-jar artifact as well.
384      */
385     @Parameter(defaultValue = "false")
386     private boolean shadeTestJar;
387 
388     /**
389      * When true, skips the execution of this MOJO.
390      * @since 3.3.0
391      */
392     @Parameter(defaultValue = "false")
393     private boolean skip;
394 
395     /**
396      * Extra JAR files to infuse into shaded result. Accepts list of files that must exists. If any of specified
397      * files does not exist (or is not a file), Mojo will fail.
398      * <p>
399      * Extra JARs will be processed in same way as main JAR (if any) is: applied relocation, resource transformers
400      * but <em>not filtering</em>.
401      * <p>
402      * Note: this feature should be used lightly, is not meant as ability to replace dependency hull! It is more
403      * just a feature to be able to slightly "differentiate" shaded JAR from main only.
404      *
405      * @since 3.6.0
406      */
407     @Parameter
408     private List<File> extraJars;
409 
410     /**
411      * Extra Artifacts to infuse into shaded result. Accepts list of GAVs in form of
412      * {@code <groupId>:<artifactId>[:<extension>[:<classifier>]]:<version>} that will be resolved. If any of them
413      * cannot be resolved, Mojo will fail.
414      * <p>
415      * The artifacts will be resolved (not transitively), and will be processed in same way as dependency JARs
416      * are (if any): applied relocation, resource transformers and filtering.
417      * <p>
418      * Note: this feature should be used lightly, is not meant as ability to replace dependency hull! It is more
419      * just a feature to be able to slightly "differentiate" shaded JAR from main only.
420      *
421      * @since 3.6.0
422      */
423     @Parameter
424     private List<String> extraArtifacts;
425 
426     @Inject
427     private MavenProjectHelper projectHelper;
428 
429     @Inject
430     private Shader shader;
431 
432     @Inject
433     private RepositorySystem repositorySystem;
434 
435     /**
436      * ProjectBuilder, needed to create projects from the artifacts.
437      */
438     @Inject
439     private ProjectBuilder projectBuilder;
440 
441     /**
442      * All the present Shaders.
443      */
444     @Inject
445     private Map<String, Shader> shaders;
446 
447     /**
448      * @throws MojoExecutionException in case of an error.
449      */
450     @SuppressWarnings("checkstyle:methodlength")
451     @Override
452     public void execute() throws MojoExecutionException {
453         if (skip) {
454             getLog().info("Shading has been skipped.");
455             return;
456         }
457 
458         setupHintedShader();
459 
460         Set<File> artifacts = new LinkedHashSet<>();
461         Set<String> artifactIds = new LinkedHashSet<>();
462         Set<File> sourceArtifacts = new LinkedHashSet<>();
463         Set<File> testArtifacts = new LinkedHashSet<>();
464         Set<File> testSourceArtifacts = new LinkedHashSet<>();
465 
466         ArtifactSelector artifactSelector = new ArtifactSelector(project.getArtifact(), artifactSet, shadedGroupFilter);
467 
468         if (artifactSelector.isSelected(project.getArtifact())
469                 && !"pom".equals(project.getArtifact().getType())) {
470             if (invalidMainArtifact()) {
471                 createErrorOutput();
472                 throw new MojoExecutionException(
473                         "Failed to create shaded artifact, " + "project main artifact does not exist.");
474             }
475 
476             artifacts.add(project.getArtifact().getFile());
477 
478             if (extraJars != null && !extraJars.isEmpty()) {
479                 for (File extraJar : extraJars) {
480                     if (!Files.isRegularFile(extraJar.toPath())) {
481                         throw new MojoExecutionException(
482                                 "Failed to create shaded artifact: parameter extraJars contains path " + extraJar
483                                         + " that is not a file (does not exist or is not a file)");
484                     }
485                     artifacts.add(extraJar);
486                 }
487             }
488 
489             if (createSourcesJar) {
490                 File file = shadedSourcesArtifactFile();
491                 if (file.isFile()) {
492                     sourceArtifacts.add(file);
493                 }
494             }
495 
496             if (shadeTestJar) {
497                 File file = shadedTestArtifactFile();
498                 if (file.isFile()) {
499                     testArtifacts.add(file);
500                 }
501             }
502 
503             if (createTestSourcesJar) {
504                 File file = shadedTestSourcesArtifactFile();
505                 if (file.isFile()) {
506                     testSourceArtifacts.add(file);
507                 }
508             }
509         }
510 
511         List<Artifact> processedArtifacts = processArtifactSelectors(
512                 artifacts, artifactIds, sourceArtifacts, testArtifacts, testSourceArtifacts, artifactSelector);
513 
514         File outputJar = (outputFile != null) ? outputFile : shadedArtifactFileWithClassifier();
515         File sourcesJar = shadedSourceArtifactFileWithClassifier();
516         File testJar = shadedTestArtifactFileWithClassifier();
517         File testSourcesJar = shadedTestSourceArtifactFileWithClassifier();
518 
519         // Now add our extra resources
520         try {
521             List<Filter> filters = getFilters(processedArtifacts);
522 
523             List<Relocator> relocators = getRelocators();
524 
525             List<ResourceTransformer> resourceTransformers = getResourceTransformers();
526 
527             if (createDependencyReducedPom) {
528                 createDependencyReducedPom(artifactIds);
529 
530                 if (useDependencyReducedPomInJar) {
531                     // In some cases the used implementation of the resourceTransformers is immutable.
532                     resourceTransformers = new ArrayList<>(resourceTransformers);
533                     resourceTransformers.addAll(createPomReplaceTransformers(project, dependencyReducedPomLocation));
534                 }
535             }
536 
537             ShadeRequest shadeRequest =
538                     shadeRequest("jar", artifacts, outputJar, filters, relocators, resourceTransformers);
539 
540             shader.shade(shadeRequest);
541 
542             if (createSourcesJar) {
543                 ShadeRequest shadeSourcesRequest = createShadeSourcesRequest(
544                         "sources-jar", sourceArtifacts, sourcesJar, filters, relocators, resourceTransformers);
545 
546                 shader.shade(shadeSourcesRequest);
547             }
548 
549             if (shadeTestJar) {
550                 ShadeRequest shadeTestRequest =
551                         shadeRequest("test-jar", testArtifacts, testJar, filters, relocators, resourceTransformers);
552 
553                 shader.shade(shadeTestRequest);
554             }
555 
556             if (createTestSourcesJar) {
557                 ShadeRequest shadeTestSourcesRequest = createShadeSourcesRequest(
558                         "test-sources-jar",
559                         testSourceArtifacts,
560                         testSourcesJar,
561                         filters,
562                         relocators,
563                         resourceTransformers);
564 
565                 shader.shade(shadeTestSourcesRequest);
566             }
567 
568             if (outputFile == null) {
569                 boolean renamed = false;
570 
571                 // rename the output file if a specific finalName is set
572                 // but don't rename if the finalName is the <build><finalName>
573                 // because this will be handled implicitly later
574                 if (finalName != null
575                         && finalName.length() > 0 //
576                         && !finalName.equals(project.getBuild().getFinalName())) {
577                     String finalFileName = finalName + "."
578                             + project.getArtifact().getArtifactHandler().getExtension();
579                     File finalFile = new File(outputDirectory, finalFileName);
580                     replaceFile(finalFile, outputJar);
581                     outputJar = finalFile;
582 
583                     // Also support the sources JAR
584                     if (createSourcesJar) {
585                         finalFileName = finalName + "-sources.jar";
586                         finalFile = new File(outputDirectory, finalFileName);
587                         replaceFile(finalFile, sourcesJar);
588                         sourcesJar = finalFile;
589                     }
590 
591                     // Also support the test JAR
592                     if (shadeTestJar) {
593                         finalFileName = finalName + "-tests.jar";
594                         finalFile = new File(outputDirectory, finalFileName);
595                         replaceFile(finalFile, testJar);
596                         testJar = finalFile;
597                     }
598 
599                     if (createTestSourcesJar) {
600                         finalFileName = finalName + "-test-sources.jar";
601                         finalFile = new File(outputDirectory, finalFileName);
602                         replaceFile(finalFile, testSourcesJar);
603                         testSourcesJar = finalFile;
604                     }
605 
606                     renamed = true;
607                 }
608 
609                 if (shadedArtifactAttached) {
610                     getLog().info("Attaching shaded artifact.");
611                     projectHelper.attachArtifact(
612                             project, project.getArtifact().getType(), shadedClassifierName, outputJar);
613                     if (createSourcesJar) {
614                         projectHelper.attachArtifact(
615                                 project, "java-source", shadedClassifierName + "-sources", sourcesJar);
616                     }
617 
618                     if (shadeTestJar) {
619                         projectHelper.attachArtifact(project, "test-jar", shadedClassifierName + "-tests", testJar);
620                     }
621 
622                     if (createTestSourcesJar) {
623                         projectHelper.attachArtifact(
624                                 project, "java-source", shadedClassifierName + "-test-sources", testSourcesJar);
625                     }
626                 } else if (!renamed) {
627                     getLog().info("Replacing original artifact with shaded artifact.");
628                     File originalArtifact = project.getArtifact().getFile();
629                     if (originalArtifact != null) {
630                         replaceFile(originalArtifact, outputJar);
631 
632                         if (createSourcesJar) {
633                             getLog().info("Replacing original source artifact with shaded source artifact.");
634                             File shadedSources = shadedSourcesArtifactFile();
635 
636                             replaceFile(shadedSources, sourcesJar);
637 
638                             projectHelper.attachArtifact(project, "java-source", "sources", shadedSources);
639                         }
640 
641                         if (shadeTestJar) {
642                             getLog().info("Replacing original test artifact with shaded test artifact.");
643                             File shadedTests = shadedTestArtifactFile();
644 
645                             replaceFile(shadedTests, testJar);
646 
647                             projectHelper.attachArtifact(project, "test-jar", shadedTests);
648                         }
649 
650                         if (createTestSourcesJar) {
651                             getLog().info("Replacing original test source artifact "
652                                     + "with shaded test source artifact.");
653                             File shadedTestSources = shadedTestSourcesArtifactFile();
654 
655                             replaceFile(shadedTestSources, testSourcesJar);
656 
657                             projectHelper.attachArtifact(project, "java-source", "test-sources", shadedTestSources);
658                         }
659                     }
660                 }
661             }
662         } catch (Exception e) {
663             throw new MojoExecutionException("Error creating shaded jar: " + e.getMessage(), e);
664         }
665     }
666 
667     private void createErrorOutput() {
668         getLog().error("The project main artifact does not exist. This could have the following");
669         getLog().error("reasons:");
670         getLog().error("- You have invoked the goal directly from the command line. This is not");
671         getLog().error("  supported. Please add the goal to the default lifecycle via an");
672         getLog().error("  <execution> element in your POM and use \"mvn package\" to have it run.");
673         getLog().error("- You have bound the goal to a lifecycle phase before \"package\". Please");
674         getLog().error("  remove this binding from your POM such that the goal will be run in");
675         getLog().error("  the proper phase.");
676         getLog().error("- You removed the configuration of the maven-jar-plugin that produces the main artifact.");
677     }
678 
679     private ShadeRequest shadeRequest(
680             String shade,
681             Set<File> artifacts,
682             File outputJar,
683             List<Filter> filters,
684             List<Relocator> relocators,
685             List<ResourceTransformer> resourceTransformers) {
686         ShadeRequest shadeRequest = new ShadeRequest();
687         shadeRequest.setJars(artifacts);
688         shadeRequest.setUberJar(outputJar);
689         shadeRequest.setFilters(filters);
690         shadeRequest.setRelocators(relocators);
691         shadeRequest.setResourceTransformers(toResourceTransformers(shade, resourceTransformers));
692         return shadeRequest;
693     }
694 
695     private ShadeRequest createShadeSourcesRequest(
696             String shade,
697             Set<File> testArtifacts,
698             File testJar,
699             List<Filter> filters,
700             List<Relocator> relocators,
701             List<ResourceTransformer> resourceTransformers) {
702         ShadeRequest shadeSourcesRequest =
703                 shadeRequest(shade, testArtifacts, testJar, filters, relocators, resourceTransformers);
704         shadeSourcesRequest.setShadeSourcesContent(shadeSourcesContent);
705         return shadeSourcesRequest;
706     }
707 
708     private void setupHintedShader() throws MojoExecutionException {
709         if (shaderHint != null) {
710             shader = shaders.get(shaderHint);
711 
712             if (shader == null) {
713                 throw new MojoExecutionException(
714                         "unable to lookup own Shader implementation with hint: '" + shaderHint + "'");
715             }
716         }
717     }
718 
719     private List<Artifact> processArtifactSelectors(
720             Set<File> artifacts,
721             Set<String> artifactIds,
722             Set<File> sourceArtifacts,
723             Set<File> testArtifacts,
724             Set<File> testSourceArtifacts,
725             ArtifactSelector artifactSelector)
726             throws MojoExecutionException {
727 
728         List<Artifact> excludedArtifacts = new ArrayList<>();
729         List<Artifact> pomArtifacts = new ArrayList<>();
730         List<Artifact> emptySourceArtifacts = new ArrayList<>();
731         List<Artifact> emptyTestArtifacts = new ArrayList<>();
732         List<Artifact> emptyTestSourceArtifacts = new ArrayList<>();
733 
734         ArrayList<Artifact> processedArtifacts = new ArrayList<>();
735         if (extraArtifacts != null && !extraArtifacts.isEmpty()) {
736             processedArtifacts.addAll(extraArtifacts.stream()
737                     .map(org.eclipse.aether.artifact.DefaultArtifact::new)
738                     .map(RepositoryUtils::toArtifact)
739                     .collect(Collectors.toList()));
740 
741             for (Artifact artifact : processedArtifacts) {
742                 try {
743                     org.eclipse.aether.artifact.Artifact resolved =
744                             resolveArtifact(RepositoryUtils.toArtifact(artifact));
745                     if (resolved.getFile() != null) {
746                         artifact.setFile(resolved.getFile());
747                     }
748                 } catch (ArtifactResolutionException e) {
749                     throw new MojoExecutionException(
750                             "Failed to create shaded artifact: parameter extraArtifacts contains artifact "
751                                     + artifact.getId() + " that is not resolvable",
752                             e);
753                 }
754             }
755         }
756         processedArtifacts.addAll(project.getArtifacts());
757 
758         // for loop over COPY; as we add to the list in this loop
759         for (Artifact artifact : new ArrayList<>(processedArtifacts)) {
760             if (!artifactSelector.isSelected(artifact)) {
761                 excludedArtifacts.add(artifact);
762                 continue;
763             }
764 
765             if ("pom".equals(artifact.getType())) {
766                 pomArtifacts.add(artifact);
767                 continue;
768             }
769 
770             getLog().debug("Including " + artifact.getId() + " in the shaded jar.");
771 
772             artifacts.add(artifact.getFile());
773             artifactIds.add(getId(artifact));
774 
775             if (createSourcesJar) {
776                 Artifact sources = resolveArtifactForClassifier(artifact, "sources");
777                 if (sources != null) {
778                     if (sources.getFile().length() > 0) {
779                         sourceArtifacts.add(sources.getFile());
780                         processedArtifacts.add(sources);
781                     } else {
782                         emptySourceArtifacts.add(artifact);
783                     }
784                 }
785             }
786 
787             if (shadeTestJar) {
788                 Artifact tests = resolveArtifactForClassifier(artifact, "tests");
789                 if (tests != null) {
790                     if (tests.getFile().length() > 0) {
791                         testArtifacts.add(tests.getFile());
792                         processedArtifacts.add(tests);
793                     } else {
794                         emptyTestArtifacts.add(artifact);
795                     }
796                 }
797             }
798 
799             if (createTestSourcesJar) {
800                 Artifact testSources = resolveArtifactForClassifier(artifact, "test-sources");
801                 if (testSources != null) {
802                     testSourceArtifacts.add(testSources.getFile());
803                     processedArtifacts.add(testSources);
804                 } else {
805                     emptyTestSourceArtifacts.add(artifact);
806                 }
807             }
808         }
809 
810         processedArtifacts.removeAll(excludedArtifacts);
811         processedArtifacts.removeAll(pomArtifacts);
812         processedArtifacts.removeAll(emptySourceArtifacts);
813         processedArtifacts.removeAll(emptyTestArtifacts);
814         processedArtifacts.removeAll(emptyTestSourceArtifacts);
815 
816         for (Artifact artifact : excludedArtifacts) {
817             getLog().debug("Excluding " + artifact.getId() + " from the shaded jar.");
818         }
819         for (Artifact artifact : pomArtifacts) {
820             getLog().debug("Skipping pom dependency " + artifact.getId() + " in the shaded jar.");
821         }
822         for (Artifact artifact : emptySourceArtifacts) {
823             getLog().warn("Skipping empty source jar " + artifact.getId() + ".");
824         }
825         for (Artifact artifact : emptyTestArtifacts) {
826             getLog().warn("Skipping empty test jar " + artifact.getId() + ".");
827         }
828         for (Artifact artifact : emptyTestSourceArtifacts) {
829             getLog().warn("Skipping empty test source jar " + artifact.getId() + ".");
830         }
831         return processedArtifacts;
832     }
833 
834     private boolean invalidMainArtifact() {
835         return project.getArtifact().getFile() == null
836                 || !project.getArtifact().getFile().isFile();
837     }
838 
839     private void replaceFile(File oldFile, File newFile) throws MojoExecutionException {
840         getLog().debug("Replacing " + oldFile + " with " + newFile);
841 
842         File origFile = new File(outputDirectory, "original-" + oldFile.getName());
843         if (oldFile.exists() && !oldFile.renameTo(origFile)) {
844             // try a gc to see if an unclosed stream needs garbage collecting
845             System.gc();
846             System.gc();
847 
848             if (!oldFile.renameTo(origFile)) {
849                 // Still didn't work. We'll do a copy
850                 try {
851                     copyFiles(oldFile, origFile);
852                 } catch (IOException ex) {
853                     // kind of ignorable here. We're just trying to save the original
854                     getLog().warn(ex);
855                 }
856             }
857         }
858         if (!newFile.renameTo(oldFile)) {
859             // try a gc to see if an unclosed stream needs garbage collecting
860             System.gc();
861             System.gc();
862 
863             if (!newFile.renameTo(oldFile)) {
864                 // Still didn't work. We'll do a copy
865                 try {
866                     copyFiles(newFile, oldFile);
867                 } catch (IOException ex) {
868                     throw new MojoExecutionException("Could not replace original artifact with shaded artifact!", ex);
869                 }
870             }
871         }
872     }
873 
874     private void copyFiles(File source, File target) throws IOException {
875         try (InputStream in = Files.newInputStream(source.toPath());
876                 OutputStream out = Files.newOutputStream(target.toPath())) {
877             IOUtil.copy(in, out);
878         }
879     }
880 
881     private Artifact resolveArtifactForClassifier(Artifact artifact, String classifier) {
882         Artifact toResolve = new DefaultArtifact(
883                 artifact.getGroupId(),
884                 artifact.getArtifactId(),
885                 artifact.getVersionRange() == null
886                         ? VersionRange.createFromVersion(artifact.getVersion())
887                         : artifact.getVersionRange(),
888                 artifact.getScope(),
889                 artifact.getType(),
890                 classifier,
891                 artifact.getArtifactHandler(),
892                 artifact.isOptional());
893         try {
894             org.eclipse.aether.artifact.Artifact resolved = resolveArtifact(RepositoryUtils.toArtifact(toResolve));
895             if (resolved.getFile() != null) {
896                 toResolve.setFile(resolved.getFile());
897                 return toResolve;
898             }
899             return null;
900         } catch (ArtifactResolutionException e) {
901             getLog().warn("Could not get " + classifier + " for " + artifact);
902             return null;
903         }
904     }
905 
906     private org.eclipse.aether.artifact.Artifact resolveArtifact(org.eclipse.aether.artifact.Artifact artifact)
907             throws ArtifactResolutionException {
908         return repositorySystem
909                 .resolveArtifact(
910                         session.getRepositorySession(),
911                         new ArtifactRequest(artifact, project.getRemoteProjectRepositories(), "shade"))
912                 .getArtifact();
913     }
914 
915     private List<Relocator> getRelocators() {
916         List<Relocator> relocators = new ArrayList<>();
917 
918         if (relocations == null) {
919             return relocators;
920         }
921 
922         for (PackageRelocation r : relocations) {
923             relocators.add(new SimpleRelocator(
924                     r.getPattern(), r.getShadedPattern(), r.getIncludes(), r.getExcludes(), r.isRawString()));
925         }
926 
927         return relocators;
928     }
929 
930     private List<ResourceTransformer> getResourceTransformers() throws MojoExecutionException {
931         if (transformers == null) {
932             return Collections.emptyList();
933         }
934         for (ResourceTransformer transformer : transformers) {
935             if (transformer == null) {
936                 throw new MojoExecutionException(
937                         "Failed to create shaded artifact: parameter transformers contains null (double-check XML attribute)");
938             }
939         }
940         return Arrays.asList(transformers);
941     }
942 
943     private List<Filter> getFilters(List<Artifact> artifactCollection) throws MojoExecutionException {
944         List<Filter> filters = new ArrayList<>();
945         List<SimpleFilter> simpleFilters = new ArrayList<>();
946 
947         if (this.filters != null && this.filters.length > 0) {
948             Map<Artifact, ArtifactId> artifacts = new HashMap<>();
949 
950             // artifactCollection does not contain project; that must also be subjected to filtering
951             artifacts.put(project.getArtifact(), new ArtifactId(project.getArtifact()));
952 
953             for (Artifact artifact : artifactCollection) {
954                 artifacts.put(artifact, new ArtifactId(artifact));
955             }
956 
957             for (ArchiveFilter filter : this.filters) {
958                 ArtifactId pattern = new ArtifactId(filter.getArtifact());
959 
960                 Set<File> jars = new HashSet<>();
961 
962                 for (Map.Entry<Artifact, ArtifactId> entry : artifacts.entrySet()) {
963                     if (entry.getValue().matches(pattern)) {
964                         Artifact artifact = entry.getKey();
965 
966                         jars.add(artifact.getFile());
967 
968                         if (createSourcesJar) {
969                             Artifact sources = resolveArtifactForClassifier(artifact, "sources");
970                             if (sources != null) {
971                                 jars.add(sources.getFile());
972                             }
973                         }
974 
975                         if (shadeTestJar) {
976                             Artifact tests = resolveArtifactForClassifier(artifact, "tests");
977                             if (tests != null) {
978                                 jars.add(tests.getFile());
979                             }
980                         }
981                     }
982                 }
983 
984                 if (jars.isEmpty()) {
985                     getLog().debug("No artifact matching filter " + filter.getArtifact());
986 
987                     continue;
988                 }
989 
990                 simpleFilters.add(new SimpleFilter(jars, filter));
991             }
992         }
993 
994         filters.addAll(simpleFilters);
995 
996         if (minimizeJar) {
997             if (entryPoints == null) {
998                 entryPoints = new HashSet<>();
999             }
1000             getLog().info("Minimizing jar " + project.getArtifact()
1001                     + (entryPoints.isEmpty() ? "" : " with entry points"));
1002 
1003             try {
1004                 filters.add(new MinijarFilter(project, getLog(), simpleFilters, entryPoints));
1005             } catch (IOException e) {
1006                 throw new MojoExecutionException("Failed to analyze class dependencies", e);
1007             }
1008         }
1009 
1010         return filters;
1011     }
1012 
1013     private File shadedArtifactFileWithClassifier() {
1014         Artifact artifact = project.getArtifact();
1015         final String shadedName = shadedArtifactId + "-" + artifact.getVersion() + "-" + shadedClassifierName + "."
1016                 + artifact.getArtifactHandler().getExtension();
1017         return new File(outputDirectory, shadedName);
1018     }
1019 
1020     private File shadedSourceArtifactFileWithClassifier() {
1021         return shadedArtifactFileWithClassifier("sources");
1022     }
1023 
1024     private File shadedTestSourceArtifactFileWithClassifier() {
1025         return shadedArtifactFileWithClassifier("test-sources");
1026     }
1027 
1028     private File shadedArtifactFileWithClassifier(String classifier) {
1029         Artifact artifact = project.getArtifact();
1030         final String shadedName = shadedArtifactId + "-" + artifact.getVersion() + "-" + shadedClassifierName + "-"
1031                 + classifier + "." + artifact.getArtifactHandler().getExtension();
1032         return new File(outputDirectory, shadedName);
1033     }
1034 
1035     private File shadedTestArtifactFileWithClassifier() {
1036         return shadedArtifactFileWithClassifier("tests");
1037     }
1038 
1039     private File shadedSourcesArtifactFile() {
1040         return shadedArtifactFile("sources");
1041     }
1042 
1043     private File shadedTestSourcesArtifactFile() {
1044         return shadedArtifactFile("test-sources");
1045     }
1046 
1047     private File shadedArtifactFile(String classifier) {
1048         Artifact artifact = project.getArtifact();
1049 
1050         String shadedName;
1051 
1052         if (project.getBuild().getFinalName() != null) {
1053             shadedName = project.getBuild().getFinalName() + "-" + classifier + "."
1054                     + artifact.getArtifactHandler().getExtension();
1055         } else {
1056             shadedName = shadedArtifactId + "-" + artifact.getVersion() + "-" + classifier + "."
1057                     + artifact.getArtifactHandler().getExtension();
1058         }
1059 
1060         return new File(outputDirectory, shadedName);
1061     }
1062 
1063     private File shadedTestArtifactFile() {
1064         return shadedArtifactFile("tests");
1065     }
1066 
1067     // We need to find the direct dependencies that have been included in the uber JAR so that we can modify the
1068     // POM accordingly.
1069     private void createDependencyReducedPom(Set<String> artifactsToRemove)
1070             throws IOException, ProjectBuildingException, DependencyCollectionException {
1071         List<Dependency> transitiveDeps = new ArrayList<>();
1072 
1073         // NOTE: By using the getArtifacts() we get the completely evaluated artifacts
1074         // including the system scoped artifacts with expanded values of properties used.
1075         for (Artifact artifact : project.getArtifacts()) {
1076             if ("pom".equals(artifact.getType())) {
1077                 // don't include pom type dependencies in dependency reduced pom
1078                 continue;
1079             }
1080 
1081             // promote
1082             Dependency dep = createDependency(artifact);
1083 
1084             // we'll figure out the exclusions in a bit.
1085             transitiveDeps.add(dep);
1086         }
1087 
1088         Model model = project.getOriginalModel();
1089 
1090         // MSHADE-413: Must not use objects (for example `Model` or `Dependency`) that are "owned
1091         // by Maven" and being used by other projects/plugins. Modifying those will break the
1092         // correctness of the build - or cause an endless loop.
1093         List<Dependency> origDeps = new ArrayList<>();
1094         List<Dependency> source = promoteTransitiveDependencies ? transitiveDeps : project.getDependencies();
1095         for (Dependency d : source) {
1096             origDeps.add(d.clone());
1097         }
1098         model = model.clone();
1099 
1100         // MSHADE-185: We will remove all system scoped dependencies which usually
1101         // have some kind of property usage. At this time the properties within
1102         // such things are already evaluated.
1103         List<Dependency> originalDependencies = model.getDependencies();
1104         removeSystemScopedDependencies(artifactsToRemove, originalDependencies);
1105 
1106         List<Dependency> dependencies = new ArrayList<>();
1107         boolean modified = false;
1108         for (Dependency d : origDeps) {
1109             if (artifactsToRemove.contains(getId(d))) {
1110                 if (keepDependenciesWithProvidedScope) {
1111                     if (!"provided".equals(d.getScope())) {
1112                         modified = true;
1113                         d.setScope("provided");
1114                     }
1115                 } else {
1116                     modified = true;
1117                     continue;
1118                 }
1119             }
1120 
1121             dependencies.add(d);
1122         }
1123 
1124         // MSHADE-155
1125         model.setArtifactId(shadedArtifactId);
1126 
1127         // MSHADE-185: We will add those system scoped dependencies
1128         // from the non interpolated original pom file. So we keep
1129         // things like this: <systemPath>${tools.jar}</systemPath> intact.
1130         addSystemScopedDependencyFromNonInterpolatedPom(dependencies, originalDependencies);
1131 
1132         // Check to see if we have a reduction and if so rewrite the POM.
1133         rewriteDependencyReducedPomIfWeHaveReduction(dependencies, modified, transitiveDeps, model);
1134     }
1135 
1136     private void rewriteDependencyReducedPomIfWeHaveReduction(
1137             List<Dependency> dependencies, boolean modified, List<Dependency> transitiveDeps, Model model)
1138             throws IOException, ProjectBuildingException, DependencyCollectionException {
1139         if (modified) {
1140             for (int loopCounter = 0; modified; loopCounter++) {
1141 
1142                 model.setDependencies(dependencies);
1143 
1144                 if (generateUniqueDependencyReducedPom) {
1145                     dependencyReducedPomLocation = Files.createTempFile(
1146                                     project.getBasedir().toPath(), "dependency-reduced-pom-", ".xml")
1147                             .toFile();
1148                     project.getProperties()
1149                             .setProperty(
1150                                     "maven.shade.dependency-reduced-pom",
1151                                     dependencyReducedPomLocation.getAbsolutePath());
1152                 } else {
1153                     if (dependencyReducedPomLocation == null) {
1154                         // MSHADE-123: We can't default to 'target' because it messes up uses of ${project.basedir}
1155                         dependencyReducedPomLocation = new File(project.getBasedir(), "dependency-reduced-pom.xml");
1156                     }
1157                 }
1158 
1159                 File f = dependencyReducedPomLocation;
1160                 // MSHADE-225
1161                 // Works for now, maybe there's a better algorithm where no for-loop is required
1162                 if (loopCounter == 0) {
1163                     getLog().info("Dependency-reduced POM written at: " + f.getAbsolutePath());
1164                 }
1165 
1166                 if (f.exists()) {
1167                     // noinspection ResultOfMethodCallIgnored
1168                     f.delete();
1169                 }
1170 
1171                 Writer w = WriterFactory.newXmlWriter(f);
1172 
1173                 String replaceRelativePath = null;
1174                 if (model.getParent() != null) {
1175                     replaceRelativePath = model.getParent().getRelativePath();
1176                 }
1177 
1178                 if (model.getParent() != null) {
1179                     File parentFile =
1180                             new File(project.getBasedir(), model.getParent().getRelativePath()).getCanonicalFile();
1181                     if (!parentFile.isFile()) {
1182                         parentFile = new File(parentFile, "pom.xml");
1183                     }
1184 
1185                     parentFile = parentFile.getCanonicalFile();
1186 
1187                     String relPath = RelativizePath.convertToRelativePath(parentFile, f);
1188                     model.getParent().setRelativePath(relPath);
1189                 }
1190 
1191                 try {
1192                     PomWriter.write(w, model, true);
1193                 } finally {
1194                     if (model.getParent() != null) {
1195                         model.getParent().setRelativePath(replaceRelativePath);
1196                     }
1197                     w.close();
1198                 }
1199 
1200                 synchronized (session.getProjectBuildingRequest()) { // Lock critical section to fix MSHADE-467
1201                     ProjectBuildingRequest projectBuildingRequest =
1202                             new DefaultProjectBuildingRequest(session.getProjectBuildingRequest());
1203                     projectBuildingRequest.setLocalRepository(session.getLocalRepository());
1204                     projectBuildingRequest.setRemoteRepositories(project.getRemoteArtifactRepositories());
1205 
1206                     ProjectBuildingResult result = projectBuilder.build(f, projectBuildingRequest);
1207 
1208                     getLog().debug("updateExcludesInDeps()");
1209                     modified = updateExcludesInDeps(result.getProject(), dependencies, transitiveDeps);
1210                 }
1211             }
1212 
1213             project.setFile(dependencyReducedPomLocation);
1214         }
1215     }
1216 
1217     private void removeSystemScopedDependencies(Set<String> artifactsToRemove, List<Dependency> originalDependencies) {
1218         for (Dependency dependency : originalDependencies) {
1219             if (dependency.getScope() != null && dependency.getScope().equalsIgnoreCase("system")) {
1220                 artifactsToRemove.add(getId(dependency));
1221             }
1222         }
1223     }
1224 
1225     private void addSystemScopedDependencyFromNonInterpolatedPom(
1226             List<Dependency> dependencies, List<Dependency> originalDependencies) {
1227         for (Dependency dependency : originalDependencies) {
1228             if (dependency.getScope() != null && dependency.getScope().equalsIgnoreCase("system")) {
1229                 dependencies.add(dependency);
1230             }
1231         }
1232     }
1233 
1234     private Dependency createDependency(Artifact artifact) {
1235         Dependency dep = new Dependency();
1236         dep.setArtifactId(artifact.getArtifactId());
1237         if (artifact.hasClassifier()) {
1238             dep.setClassifier(artifact.getClassifier());
1239         }
1240         dep.setGroupId(artifact.getGroupId());
1241         dep.setOptional(artifact.isOptional());
1242         dep.setScope(artifact.getScope());
1243         dep.setType(artifact.getType());
1244         if (useBaseVersion) {
1245             dep.setVersion(artifact.getBaseVersion());
1246         } else {
1247             dep.setVersion(artifact.getVersion());
1248         }
1249         return dep;
1250     }
1251 
1252     private String getId(Artifact artifact) {
1253         return getId(artifact.getGroupId(), artifact.getArtifactId(), artifact.getType(), artifact.getClassifier());
1254     }
1255 
1256     private String getId(Dependency dependency) {
1257         return getId(
1258                 dependency.getGroupId(), dependency.getArtifactId(), dependency.getType(), dependency.getClassifier());
1259     }
1260 
1261     private String getId(String groupId, String artifactId, String type, String classifier) {
1262         return groupId + ":" + artifactId + ":" + type + ":" + ((classifier != null) ? classifier : "");
1263     }
1264 
1265     public boolean updateExcludesInDeps(
1266             MavenProject project, List<Dependency> dependencies, List<Dependency> transitiveDeps)
1267             throws DependencyCollectionException {
1268         CollectRequest collectRequest = new CollectRequest();
1269         collectRequest.setRootArtifact(RepositoryUtils.toArtifact(project.getArtifact()));
1270         collectRequest.setRepositories(project.getRemoteProjectRepositories());
1271         collectRequest.setDependencies(project.getDependencies().stream()
1272                 .map(d -> RepositoryUtils.toDependency(
1273                         d, session.getRepositorySession().getArtifactTypeRegistry()))
1274                 .collect(Collectors.toList()));
1275         if (project.getDependencyManagement() != null) {
1276             collectRequest.setManagedDependencies(project.getDependencyManagement().getDependencies().stream()
1277                     .map(d -> RepositoryUtils.toDependency(
1278                             d, session.getRepositorySession().getArtifactTypeRegistry()))
1279                     .collect(Collectors.toList()));
1280         }
1281         CollectResult result = repositorySystem.collectDependencies(session.getRepositorySession(), collectRequest);
1282         boolean modified = false;
1283         if (result.getRoot() != null) {
1284             for (DependencyNode n2 : result.getRoot().getChildren()) {
1285                 String artifactId2 = getId(RepositoryUtils.toArtifact(n2.getArtifact()));
1286 
1287                 for (DependencyNode n3 : n2.getChildren()) {
1288                     // stupid m-a Artifact that has no idea what it is: dependency or artifact?
1289                     Artifact artifact3 = RepositoryUtils.toArtifact(n3.getArtifact());
1290                     artifact3.setScope(n3.getDependency().getScope());
1291                     String artifactId3 = getId(artifact3);
1292 
1293                     // check if it really isn't in the list of original dependencies. Maven
1294                     // prior to 2.0.8 may grab versions from transients instead of
1295                     // from the direct deps in which case they would be marked included
1296                     // instead of OMITTED_FOR_DUPLICATE
1297 
1298                     // also, if not promoting the transitives, level 2's would be included
1299                     boolean found = false;
1300                     for (Dependency dep : transitiveDeps) {
1301                         if (getId(dep).equals(artifactId3)) {
1302                             found = true;
1303                             break;
1304                         }
1305                     }
1306 
1307                     // MSHADE-311: do not add exclusion for provided transitive dep
1308                     //       note: MSHADE-31 introduced the exclusion logic for promoteTransitiveDependencies=true,
1309                     //             but as of 3.2.1 promoteTransitiveDependencies has no effect for provided deps,
1310                     //             which makes this fix even possible (see also MSHADE-181)
1311                     if (!found && !"provided".equals(artifact3.getScope())) {
1312                         getLog().debug(String.format(
1313                                 "dependency %s (scope %s) not found in transitive dependencies",
1314                                 artifactId3, artifact3.getScope()));
1315                         for (Dependency dep : dependencies) {
1316                             if (getId(dep).equals(artifactId2)) {
1317                                 // MSHADE-413: First check whether the exclusion has already been added,
1318                                 // because it's meaningless to add it more than once. Certain cases
1319                                 // can end up adding the exclusion "forever" and cause an endless loop
1320                                 // rewriting the whole dependency-reduced-pom.xml file.
1321                                 if (!dependencyHasExclusion(dep, artifact3)) {
1322                                     getLog().debug(String.format(
1323                                             "Adding exclusion for dependency %s (scope %s) " + "to %s (scope %s)",
1324                                             artifactId3, artifact3.getScope(), getId(dep), dep.getScope()));
1325                                     Exclusion exclusion = new Exclusion();
1326                                     exclusion.setArtifactId(artifact3.getArtifactId());
1327                                     exclusion.setGroupId(artifact3.getGroupId());
1328                                     dep.addExclusion(exclusion);
1329                                     modified = true;
1330                                     break;
1331                                 }
1332                             }
1333                         }
1334                     }
1335                 }
1336             }
1337         }
1338         return modified;
1339     }
1340 
1341     private boolean dependencyHasExclusion(Dependency dep, Artifact exclusionToCheck) {
1342         boolean containsExclusion = false;
1343         for (Exclusion existingExclusion : dep.getExclusions()) {
1344             if (existingExclusion.getGroupId().equals(exclusionToCheck.getGroupId())
1345                     && existingExclusion.getArtifactId().equals(exclusionToCheck.getArtifactId())) {
1346                 containsExclusion = true;
1347                 break;
1348             }
1349         }
1350         return containsExclusion;
1351     }
1352 
1353     private List<ResourceTransformer> toResourceTransformers(
1354             String shade, List<ResourceTransformer> resourceTransformers) {
1355         List<ResourceTransformer> forShade = new ArrayList<>();
1356         ManifestResourceTransformer lastMt = null;
1357         for (ResourceTransformer transformer : resourceTransformers) {
1358             if (!(transformer instanceof ManifestResourceTransformer)) {
1359                 forShade.add(transformer);
1360             } else if (((ManifestResourceTransformer) transformer).isForShade(shade)) {
1361                 final ManifestResourceTransformer mt = (ManifestResourceTransformer) transformer;
1362                 if (mt.isUsedForDefaultShading() && lastMt != null && !lastMt.isUsedForDefaultShading()) {
1363                     continue; // skip, we already have a specific transformer
1364                 }
1365                 if (!mt.isUsedForDefaultShading() && lastMt != null && lastMt.isUsedForDefaultShading()) {
1366                     forShade.remove(lastMt);
1367                 } else if (!mt.isUsedForDefaultShading() && lastMt != null) {
1368                     getLog().warn("Ambiguous manifest transformer definition for '" + shade + "': " + mt + " / "
1369                             + lastMt);
1370                 }
1371                 if (lastMt == null || !mt.isUsedForDefaultShading()) {
1372                     lastMt = mt;
1373                 }
1374                 forShade.add(transformer);
1375             }
1376         }
1377         return forShade;
1378     }
1379 }