View Javadoc
1   package org.apache.maven.plugin.surefire.booterclient;
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 org.apache.maven.plugin.surefire.booterclient.lazytestprovider.OutputStreamFlushableCommandline;
23  import org.apache.maven.plugin.surefire.booterclient.output.InPluginProcessDumpSingleton;
24  import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
25  import org.apache.maven.surefire.booter.Classpath;
26  import org.apache.maven.surefire.booter.StartupConfiguration;
27  import org.apache.maven.surefire.booter.SurefireBooterForkException;
28  
29  import javax.annotation.Nonnull;
30  import javax.annotation.Nullable;
31  import java.io.File;
32  import java.io.FileOutputStream;
33  import java.io.IOException;
34  import java.net.URI;
35  import java.net.URISyntaxException;
36  import java.nio.file.Path;
37  import java.nio.file.Paths;
38  import java.util.Iterator;
39  import java.util.List;
40  import java.util.Map;
41  import java.util.Properties;
42  import java.util.jar.JarEntry;
43  import java.util.jar.JarOutputStream;
44  import java.util.jar.Manifest;
45  
46  import static java.nio.file.Files.isDirectory;
47  import static org.apache.maven.plugin.surefire.SurefireHelper.escapeToPlatformPath;
48  
49  /**
50   * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
51   * @since 2.21.0.Jigsaw
52   */
53  public final class JarManifestForkConfiguration
54      extends AbstractClasspathForkConfiguration
55  {
56      @SuppressWarnings( "checkstyle:parameternumber" )
57      public JarManifestForkConfiguration( @Nonnull Classpath bootClasspath, @Nonnull File tempDirectory,
58                                           @Nullable String debugLine, @Nonnull File workingDirectory,
59                                           @Nonnull Properties modelProperties, @Nullable String argLine,
60                                           @Nonnull Map<String, String> environmentVariables, boolean debug,
61                                           int forkCount, boolean reuseForks, @Nonnull Platform pluginPlatform,
62                                           @Nonnull ConsoleLogger log )
63      {
64          super( bootClasspath, tempDirectory, debugLine, workingDirectory, modelProperties, argLine,
65                  environmentVariables, debug, forkCount, reuseForks, pluginPlatform, log );
66      }
67  
68      @Override
69      protected void resolveClasspath( @Nonnull OutputStreamFlushableCommandline cli,
70                                       @Nonnull String booterThatHasMainMethod,
71                                       @Nonnull StartupConfiguration config,
72                                       @Nonnull File dumpLogDirectory )
73              throws SurefireBooterForkException
74      {
75          try
76          {
77              File jar = createJar( toCompleteClasspath( config ), booterThatHasMainMethod, dumpLogDirectory );
78              cli.createArg().setValue( "-jar" );
79              cli.createArg().setValue( escapeToPlatformPath( jar.getAbsolutePath() ) );
80          }
81          catch ( IOException e )
82          {
83              throw new SurefireBooterForkException( "Error creating archive file", e );
84          }
85      }
86  
87      /**
88       * Create a jar with just a manifest containing a Main-Class entry for BooterConfiguration and a Class-Path entry
89       * for all classpath elements.
90       *
91       * @param classPath      List&lt;String&gt; of all classpath elements.
92       * @param startClassName The class name to start (main-class)
93       * @return file of the jar
94       * @throws IOException When a file operation fails.
95       */
96      @Nonnull
97      private File createJar( @Nonnull List<String> classPath, @Nonnull String startClassName,
98                              @Nonnull File dumpLogDirectory )
99              throws IOException
100     {
101         File file = File.createTempFile( "surefirebooter", ".jar", getTempDirectory() );
102         if ( !isDebug() )
103         {
104             file.deleteOnExit();
105         }
106         Path parent = file.getParentFile().toPath();
107         FileOutputStream fos = new FileOutputStream( file );
108         try ( JarOutputStream jos = new JarOutputStream( fos ) )
109         {
110             jos.setLevel( JarOutputStream.STORED );
111             JarEntry je = new JarEntry( "META-INF/MANIFEST.MF" );
112             jos.putNextEntry( je );
113 
114             Manifest man = new Manifest();
115 
116             // we can't use StringUtils.join here since we need to add a '/' to
117             // the end of directory entries - otherwise the jvm will ignore them.
118             StringBuilder cp = new StringBuilder();
119             for ( Iterator<String> it = classPath.iterator(); it.hasNext(); )
120             {
121                 Path classPathElement = Paths.get( it.next() );
122                 String uri = toClasspathElementUri( parent, classPathElement, dumpLogDirectory );
123                 cp.append( uri );
124                 if ( isDirectory( classPathElement ) && !uri.endsWith( "/" ) )
125                 {
126                     cp.append( '/' );
127                 }
128 
129                 if ( it.hasNext() )
130                 {
131                     cp.append( ' ' );
132                 }
133             }
134 
135             man.getMainAttributes().putValue( "Manifest-Version", "1.0" );
136             man.getMainAttributes().putValue( "Class-Path", cp.toString().trim() );
137             man.getMainAttributes().putValue( "Main-Class", startClassName );
138 
139             man.write( jos );
140 
141             jos.closeEntry();
142             jos.flush();
143 
144             return file;
145         }
146     }
147 
148     static String relativize( @Nonnull Path parent, @Nonnull Path child )
149             throws IllegalArgumentException
150     {
151         return parent.relativize( child )
152                 .toString();
153     }
154 
155     static String toAbsoluteUri( @Nonnull Path absolutePath )
156     {
157         return absolutePath.toUri()
158                 .toASCIIString();
159     }
160 
161     static String toClasspathElementUri( @Nonnull Path parent,
162                                          @Nonnull Path classPathElement,
163                                          @Nonnull File dumpLogDirectory )
164             throws IOException
165     {
166         try
167         {
168             String relativeUriPath = relativize( parent, classPathElement )
169                     .replace( '\\', '/' );
170 
171             return new URI( null, relativeUriPath, null )
172                     .toASCIIString();
173         }
174         catch ( IllegalArgumentException e )
175         {
176             String error = "Boot Manifest-JAR contains absolute paths in classpath " + classPathElement;
177             InPluginProcessDumpSingleton.getSingleton()
178                     .dumpException( e, error, dumpLogDirectory );
179 
180             return toAbsoluteUri( classPathElement );
181         }
182         catch ( URISyntaxException e )
183         {
184             // This is really unexpected, so fail
185             throw new IOException( "Could not create a relative path "
186                     + classPathElement
187                     + " against "
188                     + parent, e );
189         }
190     }
191 }