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.IOException;
23  import java.io.InputStream;
24  import java.nio.charset.StandardCharsets;
25  import java.util.ArrayList;
26  import java.util.HashMap;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.Scanner;
30  import java.util.jar.JarEntry;
31  import java.util.jar.JarOutputStream;
32  
33  import org.apache.commons.io.IOUtils;
34  import org.apache.maven.plugins.shade.relocation.Relocator;
35  
36  /**
37   * Resources transformer that relocates classes in META-INF/services and appends entries in META-INF/services resources
38   * into a single resource. For example, if there are several META-INF/services/org.apache.maven.project.ProjectBuilder
39   * resources spread across many JARs the individual entries will all be concatenated into a single
40   * META-INF/services/org.apache.maven.project.ProjectBuilder resource packaged into the resultant JAR produced by the
41   * shading process.
42   */
43  public class ServicesResourceTransformer
44      extends AbstractCompatibilityTransformer
45  {
46  
47      private static final String SERVICES_PATH = "META-INF/services";
48  
49      private final Map<String, ArrayList<String>> serviceEntries = new HashMap<>();
50  
51      private List<Relocator> relocators;
52  
53      private long time = Long.MIN_VALUE;
54  
55      public boolean canTransformResource( String resource )
56      {
57          return resource.startsWith( SERVICES_PATH );
58      }
59  
60      public void processResource( String resource, InputStream is, final List<Relocator> relocators, long time )
61          throws IOException
62      {
63          ArrayList<String> out = serviceEntries.get( resource );
64          if ( out == null )
65          {
66              out = new ArrayList<>();
67              serviceEntries.put( resource, out );
68          }
69  
70          Scanner scanner = new Scanner( is, StandardCharsets.UTF_8.name() );
71          while ( scanner.hasNextLine() )
72          {
73              String relContent = scanner.nextLine();
74              for ( Relocator relocator : relocators )
75              {
76                  if ( relocator.canRelocateClass( relContent ) )
77                  {
78                      relContent = relocator.applyToSourceContent( relContent );
79                  }
80              }
81              out.add( relContent );
82          }
83  
84          if ( this.relocators == null )
85          {
86              this.relocators = relocators;
87          }
88  
89          if ( time > this.time )
90          {
91              this.time = time;        
92          }
93      }
94  
95      public boolean hasTransformedResource()
96      {
97          return !serviceEntries.isEmpty();
98      }
99  
100     public void modifyOutputStream( JarOutputStream jos )
101         throws IOException
102     {
103         for ( Map.Entry<String, ArrayList<String>> entry : serviceEntries.entrySet() )
104         {
105             String key = entry.getKey();
106             ArrayList<String> data = entry.getValue();
107 
108             if ( relocators != null )
109             {
110                 key = key.substring( SERVICES_PATH.length() + 1 );
111                 for ( Relocator relocator : relocators )
112                 {
113                     if ( relocator.canRelocateClass( key ) )
114                     {
115                         key = relocator.relocateClass( key );
116                         break;
117                     }
118                 }
119 
120                 key = SERVICES_PATH + '/' + key;
121             }
122 
123             JarEntry jarEntry = new JarEntry( key );
124             jarEntry.setTime( time );
125             jos.putNextEntry( jarEntry );
126 
127             IOUtils.writeLines( data, "\n", jos, StandardCharsets.UTF_8 );
128             jos.flush();
129             data.clear();
130         }
131     }
132 }