View Javadoc
1   package org.apache.maven.plugins.shade.resource;
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.BufferedReader;
23  import java.io.ByteArrayInputStream;
24  import java.io.ByteArrayOutputStream;
25  import java.io.IOException;
26  import java.io.InputStream;
27  import java.io.InputStreamReader;
28  import java.io.PrintWriter;
29  import java.io.StringReader;
30  import java.util.HashMap;
31  import java.util.List;
32  import java.util.Map;
33  import java.util.jar.JarEntry;
34  import java.util.jar.JarOutputStream;
35  
36  import org.apache.commons.io.IOUtils;
37  import org.apache.maven.plugins.shade.relocation.Relocator;
38  
39  import com.google.common.io.LineReader;
40  
41  /**
42   * Resources transformer that relocates classes in META-INF/services and appends entries in META-INF/services resources
43   * into a single resource. For example, if there are several META-INF/services/org.apache.maven.project.ProjectBuilder
44   * resources spread across many JARs the individual entries will all be concatenated into a single
45   * META-INF/services/org.apache.maven.project.ProjectBuilder resource packaged into the resultant JAR produced by the
46   * shading process.
47   */
48  public class ServicesResourceTransformer
49      implements ResourceTransformer
50  {
51  
52      private static final String SERVICES_PATH = "META-INF/services";
53  
54      private Map<String, ServiceStream> serviceEntries = new HashMap<String, ServiceStream>();
55  
56      private List<Relocator> relocators;
57  
58      public boolean canTransformResource( String resource )
59      {
60          if ( resource.startsWith( SERVICES_PATH ) )
61          {
62              return true;
63          }
64  
65          return false;
66      }
67  
68      public void processResource( String resource, InputStream is, final List<Relocator> relocators )
69          throws IOException
70      {
71          ServiceStream out = serviceEntries.get( resource );
72          if ( out == null )
73          {
74              out = new ServiceStream();
75              serviceEntries.put( resource, out );
76          }
77  
78          final ServiceStream fout = out;
79  
80          final String content = IOUtils.toString( is );
81          StringReader reader = new StringReader( content );
82          LineReader lineReader = new LineReader( reader );
83          String line;
84          while ( ( line = lineReader.readLine() ) != null )
85          {
86              String relContent = line;
87              for ( Relocator relocator : relocators )
88              {
89                  if ( relocator.canRelocateClass( relContent ) )
90                  {
91                      relContent = relocator.applyToSourceContent( relContent );
92                  }
93              }
94              fout.append( relContent + "\n" );
95          }
96  
97          if ( this.relocators == null )
98          {
99              this.relocators = relocators;
100         }
101     }
102     public boolean hasTransformedResource()
103     {
104         return serviceEntries.size() > 0;
105     }
106 
107     public void modifyOutputStream( JarOutputStream jos )
108         throws IOException
109     {
110         for ( Map.Entry<String, ServiceStream> entry : serviceEntries.entrySet() )
111         {
112             String key = entry.getKey();
113             ServiceStream data = entry.getValue();
114 
115             if ( relocators != null )
116             {
117                 key = key.substring( SERVICES_PATH.length() + 1 );
118                 for ( Relocator relocator : relocators )
119                 {
120                     if ( relocator.canRelocateClass( key ) )
121                     {
122                         key = relocator.relocateClass( key );
123                         break;
124                     }
125                 }
126 
127                 key = SERVICES_PATH + '/' + key;
128             }
129 
130             jos.putNextEntry( new JarEntry( key ) );
131 
132 
133             //read the content of service file for candidate classes for relocation
134             PrintWriter writer = new PrintWriter( jos );
135             InputStreamReader streamReader = new InputStreamReader( data.toInputStream() );
136             BufferedReader reader = new BufferedReader( streamReader );
137             String className;
138 
139             while ( ( className = reader.readLine() ) != null )
140             {
141 
142                 if ( relocators != null )
143                 {
144                     for ( Relocator relocator : relocators )
145                     {
146                         //if the class can be relocated then relocate it
147                         if ( relocator.canRelocateClass( className ) )
148                         {
149                             className = relocator.applyToSourceContent( className );
150                             break;
151                         }
152                     }
153                 }
154 
155                 writer.println( className );
156                 writer.flush();
157             }
158 
159             reader.close();
160             data.reset();
161         }
162     }
163 
164     static class ServiceStream
165         extends ByteArrayOutputStream
166     {
167 
168         ServiceStream()
169         {
170             super( 1024 );
171         }
172 
173         public void append( String content )
174             throws IOException
175         {
176             if ( count > 0 && buf[count - 1] != '\n' && buf[count - 1] != '\r' )
177             {
178                 write( '\n' );
179             }
180 
181             byte[] contentBytes = content.getBytes( "UTF-8" );
182             this.write( contentBytes );
183         }
184 
185         public InputStream toInputStream()
186         {
187             return new ByteArrayInputStream( buf, 0, count );
188         }
189 
190     }
191 
192 }