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.BufferedInputStream;
22  import java.io.ByteArrayOutputStream;
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.io.Reader;
26  import java.io.Writer;
27  import java.util.LinkedHashMap;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.jar.JarEntry;
31  import java.util.jar.JarOutputStream;
32  
33  import org.apache.maven.plugins.shade.relocation.Relocator;
34  import org.codehaus.plexus.util.ReaderFactory;
35  import org.codehaus.plexus.util.WriterFactory;
36  import org.codehaus.plexus.util.xml.Xpp3Dom;
37  import org.codehaus.plexus.util.xml.Xpp3DomBuilder;
38  import org.codehaus.plexus.util.xml.Xpp3DomWriter;
39  
40  /**
41   * A resource processor that aggregates plexus <code>components.xml</code> files.
42   */
43  public class ComponentsXmlResourceTransformer extends AbstractCompatibilityTransformer {
44      private Map<String, Xpp3Dom> components = new LinkedHashMap<>();
45  
46      private long time = Long.MIN_VALUE;
47  
48      public static final String COMPONENTS_XML_PATH = "META-INF/plexus/components.xml";
49  
50      @Override
51      public boolean canTransformResource(String resource) {
52          return COMPONENTS_XML_PATH.equals(resource);
53      }
54  
55      @Override
56      public void processResource(String resource, InputStream is, List<Relocator> relocators, long time)
57              throws IOException {
58          Xpp3Dom newDom;
59  
60          try {
61              BufferedInputStream bis = new BufferedInputStream(is) {
62                  @Override
63                  public void close() throws IOException {
64                      // leave ZIP open
65                  }
66              };
67  
68              Reader reader = ReaderFactory.newXmlReader(bis);
69  
70              newDom = Xpp3DomBuilder.build(reader);
71          } catch (Exception e) {
72              throw new IOException("Error parsing components.xml in " + is, e);
73          }
74  
75          // Only try to merge in components if there are some elements in the component-set
76          if (newDom.getChild("components") == null) {
77              return;
78          }
79  
80          Xpp3Dom[] children = newDom.getChild("components").getChildren("component");
81  
82          for (Xpp3Dom component : children) {
83              String role = getValue(component, "role");
84              role = getRelocatedClass(role, relocators);
85              setValue(component, "role", role);
86  
87              String roleHint = getValue(component, "role-hint");
88  
89              String impl = getValue(component, "implementation");
90              impl = getRelocatedClass(impl, relocators);
91              setValue(component, "implementation", impl);
92  
93              String key = role + ':' + roleHint;
94              if (components.containsKey(key)) {
95                  // TODO: use the tools in Plexus to merge these properly. For now, I just need an all-or-nothing
96                  // configuration carry over
97  
98                  Xpp3Dom dom = components.get(key);
99                  if (dom.getChild("configuration") != null) {
100                     component.addChild(dom.getChild("configuration"));
101                 }
102             }
103 
104             Xpp3Dom requirements = component.getChild("requirements");
105             if (requirements != null && requirements.getChildCount() > 0) {
106                 for (int r = requirements.getChildCount() - 1; r >= 0; r--) {
107                     Xpp3Dom requirement = requirements.getChild(r);
108 
109                     String requiredRole = getValue(requirement, "role");
110                     requiredRole = getRelocatedClass(requiredRole, relocators);
111                     setValue(requirement, "role", requiredRole);
112                 }
113             }
114 
115             components.put(key, component);
116         }
117 
118         if (time > this.time) {
119             this.time = time;
120         }
121     }
122 
123     @Override
124     public void modifyOutputStream(JarOutputStream jos) throws IOException {
125         JarEntry jarEntry = new JarEntry(COMPONENTS_XML_PATH);
126         jarEntry.setTime(time);
127 
128         byte[] data = getTransformedResource();
129 
130         jos.putNextEntry(jarEntry);
131 
132         jos.write(data);
133 
134         components.clear();
135     }
136 
137     @Override
138     public boolean hasTransformedResource() {
139         return !components.isEmpty();
140     }
141 
142     byte[] getTransformedResource() throws IOException {
143         ByteArrayOutputStream baos = new ByteArrayOutputStream(1024 * 4);
144 
145         try (Writer writer = WriterFactory.newXmlWriter(baos)) {
146             Xpp3Dom dom = new Xpp3Dom("component-set");
147 
148             Xpp3Dom componentDom = new Xpp3Dom("components");
149 
150             dom.addChild(componentDom);
151 
152             for (Xpp3Dom component : components.values()) {
153                 componentDom.addChild(component);
154             }
155 
156             Xpp3DomWriter.write(writer, dom);
157         }
158 
159         return baos.toByteArray();
160     }
161 
162     private String getRelocatedClass(String className, List<Relocator> relocators) {
163         if (className != null && className.length() > 0 && relocators != null) {
164             for (Relocator relocator : relocators) {
165                 if (relocator.canRelocateClass(className)) {
166                     return relocator.relocateClass(className);
167                 }
168             }
169         }
170 
171         return className;
172     }
173 
174     private static String getValue(Xpp3Dom dom, String element) {
175         Xpp3Dom child = dom.getChild(element);
176 
177         return (child != null && child.getValue() != null) ? child.getValue() : "";
178     }
179 
180     private static void setValue(Xpp3Dom dom, String element, String value) {
181         Xpp3Dom child = dom.getChild(element);
182 
183         if (child == null || value == null || value.length() <= 0) {
184             return;
185         }
186 
187         child.setValue(value);
188     }
189 }