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.resource;
20  
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.util.Arrays;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.jar.Attributes;
27  import java.util.jar.JarEntry;
28  import java.util.jar.JarFile;
29  import java.util.jar.JarOutputStream;
30  import java.util.jar.Manifest;
31  
32  import org.apache.maven.plugins.shade.relocation.Relocator;
33  
34  /**
35   * A resource processor that allows the arbitrary addition of attributes to
36   * the first MANIFEST.MF that is found in the set of JARs being processed, or
37   * to a newly created manifest for the shaded JAR.
38   *
39   * @author Jason van Zyl
40   * @since 1.2
41   */
42  public class ManifestResourceTransformer extends AbstractCompatibilityTransformer {
43      private final List<String> defaultAttributes =
44              Arrays.asList("Export-Package", "Import-Package", "Provide-Capability", "Require-Capability");
45  
46      // Configuration
47      private String mainClass;
48  
49      private Map<String, Object> manifestEntries;
50  
51      private List<String> additionalAttributes;
52  
53      // Fields
54      private boolean manifestDiscovered;
55  
56      private Manifest manifest;
57  
58      private long time = Long.MIN_VALUE;
59  
60      private String shade;
61  
62      public void setMainClass(String mainClass) {
63          this.mainClass = mainClass;
64      }
65  
66      public void setManifestEntries(Map<String, Object> manifestEntries) {
67          this.manifestEntries = manifestEntries;
68      }
69  
70      public void setAdditionalAttributes(List<String> additionalAttributes) {
71          this.additionalAttributes = additionalAttributes;
72      }
73  
74      @Override
75      public boolean canTransformResource(String resource) {
76          return JarFile.MANIFEST_NAME.equalsIgnoreCase(resource);
77      }
78  
79      @Override
80      public void processResource(String resource, InputStream is, List<Relocator> relocators, long time)
81              throws IOException {
82          // We just want to take the first manifest we come across as that's our project's manifest. This is the behavior
83          // now which is situational at best. Right now there is no context passed in with the processing so we cannot
84          // tell what artifact is being processed.
85          if (!manifestDiscovered) {
86              manifest = new Manifest(is);
87  
88              if (relocators != null && !relocators.isEmpty()) {
89                  final Attributes attributes = manifest.getMainAttributes();
90  
91                  for (final String attribute : defaultAttributes) {
92                      final String attributeValue = attributes.getValue(attribute);
93                      if (attributeValue != null) {
94                          String newValue = relocate(attributeValue, relocators);
95                          attributes.putValue(attribute, newValue);
96                      }
97                  }
98  
99                  if (additionalAttributes != null) {
100                     for (final String attribute : additionalAttributes) {
101                         final String attributeValue = attributes.getValue(attribute);
102                         if (attributeValue != null) {
103                             String newValue = relocate(attributeValue, relocators);
104                             attributes.putValue(attribute, newValue);
105                         }
106                     }
107                 }
108             }
109 
110             manifestDiscovered = true;
111 
112             if (time > this.time) {
113                 this.time = time;
114             }
115         }
116     }
117 
118     @Override
119     public boolean hasTransformedResource() {
120         return true;
121     }
122 
123     @Override
124     public void modifyOutputStream(JarOutputStream jos) throws IOException {
125         // If we didn't find a manifest, then let's create one.
126         if (manifest == null) {
127             manifest = new Manifest();
128         }
129 
130         Attributes attributes = manifest.getMainAttributes();
131 
132         if (mainClass != null) {
133             attributes.put(Attributes.Name.MAIN_CLASS, mainClass);
134         }
135 
136         if (manifestEntries != null) {
137             for (Map.Entry<String, Object> entry : manifestEntries.entrySet()) {
138                 if (entry.getValue() == null) {
139                     attributes.remove(new Attributes.Name(entry.getKey()));
140                 } else {
141                     attributes.put(new Attributes.Name(entry.getKey()), entry.getValue());
142                 }
143             }
144         }
145 
146         JarEntry jarEntry = new JarEntry(JarFile.MANIFEST_NAME);
147         jarEntry.setTime(time);
148         jos.putNextEntry(jarEntry);
149         manifest.write(jos);
150     }
151 
152     private String relocate(String originalValue, List<Relocator> relocators) {
153         String newValue = originalValue;
154         for (Relocator relocator : relocators) {
155             String value;
156             do {
157                 value = newValue;
158                 newValue = relocator.relocateClass(value);
159             } while (!value.equals(newValue));
160         }
161         return newValue;
162     }
163 
164     /**
165      * The shades to apply this transformer to or no shades if no filter is applied.
166      *
167      * @param shade {@code null}, {@code jar}, {@code test-jar}, {@code sources-jar} or {@code test-sources-jar}.
168      */
169     public void setForShade(String shade) {
170         this.shade = shade;
171     }
172 
173     public boolean isForShade(String shade) {
174         return isUsedForDefaultShading() || this.shade.equalsIgnoreCase(shade);
175     }
176 
177     public boolean isUsedForDefaultShading() {
178         return this.shade == null || this.shade.isEmpty();
179     }
180 }