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