1 package org.apache.maven.plugin.surefire.booterclient;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import org.apache.maven.surefire.shared.compress.archivers.zip.Zip64Mode;
23 import org.apache.maven.surefire.shared.compress.archivers.zip.ZipArchiveEntry;
24 import org.apache.maven.surefire.shared.compress.archivers.zip.ZipArchiveOutputStream;
25 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.Commandline;
26 import org.apache.maven.plugin.surefire.booterclient.output.InPluginProcessDumpSingleton;
27 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
28 import org.apache.maven.surefire.booter.Classpath;
29 import org.apache.maven.surefire.booter.StartupConfiguration;
30 import org.apache.maven.surefire.booter.SurefireBooterForkException;
31 import org.apache.maven.surefire.extensions.ForkNodeFactory;
32
33 import javax.annotation.Nonnull;
34 import javax.annotation.Nullable;
35 import java.io.BufferedOutputStream;
36 import java.io.File;
37 import java.io.FileOutputStream;
38 import java.io.IOException;
39 import java.io.OutputStream;
40 import java.io.UnsupportedEncodingException;
41 import java.net.URLEncoder;
42 import java.nio.charset.Charset;
43 import java.nio.file.Path;
44 import java.nio.file.Paths;
45 import java.util.Iterator;
46 import java.util.List;
47 import java.util.Map;
48 import java.util.Properties;
49 import java.util.jar.Manifest;
50 import java.util.zip.Deflater;
51
52 import static java.nio.charset.StandardCharsets.UTF_8;
53 import static java.nio.file.Files.isDirectory;
54 import static org.apache.maven.plugin.surefire.SurefireHelper.escapeToPlatformPath;
55 import static org.apache.maven.plugin.surefire.booterclient.JarManifestForkConfiguration.ClasspathElementUri.absolute;
56 import static org.apache.maven.plugin.surefire.booterclient.JarManifestForkConfiguration.ClasspathElementUri.relative;
57 import static org.apache.maven.surefire.shared.utils.StringUtils.isNotBlank;
58 import static org.apache.maven.surefire.api.util.internal.StringUtils.NL;
59
60
61
62
63
64 public final class JarManifestForkConfiguration
65 extends AbstractClasspathForkConfiguration
66 {
67 @SuppressWarnings( "checkstyle:parameternumber" )
68 public JarManifestForkConfiguration( @Nonnull Classpath bootClasspath, @Nonnull File tempDirectory,
69 @Nullable String debugLine, @Nonnull File workingDirectory,
70 @Nonnull Properties modelProperties, @Nullable String argLine,
71 @Nonnull Map<String, String> environmentVariables,
72 @Nonnull String[] excludedEnvironmentVariables,
73 boolean debug,
74 int forkCount, boolean reuseForks, @Nonnull Platform pluginPlatform,
75 @Nonnull ConsoleLogger log,
76 @Nonnull ForkNodeFactory forkNodeFactory )
77 {
78 super( bootClasspath, tempDirectory, debugLine, workingDirectory, modelProperties, argLine,
79 environmentVariables, excludedEnvironmentVariables, debug, forkCount, reuseForks, pluginPlatform, log,
80 forkNodeFactory );
81 }
82
83 @Override
84 protected void resolveClasspath( @Nonnull Commandline cli,
85 @Nonnull String booterThatHasMainMethod,
86 @Nonnull StartupConfiguration config,
87 @Nonnull File dumpLogDirectory )
88 throws SurefireBooterForkException
89 {
90 try
91 {
92 File jar = createJar( toCompleteClasspath( config ), booterThatHasMainMethod, dumpLogDirectory );
93 cli.createArg().setValue( "-jar" );
94 cli.createArg().setValue( escapeToPlatformPath( jar.getAbsolutePath() ) );
95 }
96 catch ( IOException e )
97 {
98 throw new SurefireBooterForkException( "Error creating archive file", e );
99 }
100 }
101
102
103
104
105
106
107
108
109
110
111 @Nonnull
112 private File createJar( @Nonnull List<String> classPath, @Nonnull String startClassName,
113 @Nonnull File dumpLogDirectory )
114 throws IOException
115 {
116 File file = File.createTempFile( "surefirebooter", ".jar", getTempDirectory() );
117 if ( !isDebug() )
118 {
119 file.deleteOnExit();
120 }
121 Path parent = file.getParentFile().toPath();
122 OutputStream fos = new BufferedOutputStream( new FileOutputStream( file ), 64 * 1024 );
123
124 try ( ZipArchiveOutputStream zos = new ZipArchiveOutputStream( fos ) )
125 {
126 zos.setUseZip64( Zip64Mode.Never );
127 zos.setLevel( Deflater.NO_COMPRESSION );
128
129 ZipArchiveEntry ze = new ZipArchiveEntry( "META-INF/MANIFEST.MF" );
130 zos.putArchiveEntry( ze );
131
132 Manifest man = new Manifest();
133
134 boolean dumpError = true;
135
136
137
138 StringBuilder cp = new StringBuilder();
139 for ( Iterator<String> it = classPath.iterator(); it.hasNext(); )
140 {
141 Path classPathElement = Paths.get( it.next() );
142 ClasspathElementUri classpathElementUri =
143 toClasspathElementUri( parent, classPathElement, dumpLogDirectory, dumpError );
144
145 dumpError &= !classpathElementUri.absolute;
146 cp.append( classpathElementUri.uri );
147 if ( isDirectory( classPathElement ) && !classpathElementUri.uri.endsWith( "/" ) )
148 {
149 cp.append( '/' );
150 }
151
152 if ( it.hasNext() )
153 {
154 cp.append( ' ' );
155 }
156 }
157
158 man.getMainAttributes().putValue( "Manifest-Version", "1.0" );
159 man.getMainAttributes().putValue( "Class-Path", cp.toString().trim() );
160 man.getMainAttributes().putValue( "Main-Class", startClassName );
161
162 man.write( zos );
163
164 zos.closeArchiveEntry();
165
166 return file;
167 }
168 }
169
170 static String relativize( @Nonnull Path parent, @Nonnull Path child )
171 throws IllegalArgumentException
172 {
173 return parent.relativize( child )
174 .toString();
175 }
176
177 static String toAbsoluteUri( @Nonnull Path absolutePath )
178 {
179 return absolutePath.toUri()
180 .toASCIIString();
181 }
182
183 static ClasspathElementUri toClasspathElementUri( @Nonnull Path parent,
184 @Nonnull Path classPathElement,
185 @Nonnull File dumpLogDirectory,
186 boolean dumpError )
187 {
188 try
189 {
190 String relativePath = relativize( parent, classPathElement );
191 return relative( escapeUri( relativePath, UTF_8 ) );
192 }
193 catch ( IllegalArgumentException e )
194 {
195 if ( dumpError )
196 {
197 String error = "Boot Manifest-JAR contains absolute paths in classpath '"
198 + classPathElement
199 + "'"
200 + NL
201 + "Hint: <argLine>-Djdk.net.URLClassPath.disableClassPathURLCheck=true</argLine>";
202
203 if ( isNotBlank( e.getLocalizedMessage() ) )
204 {
205 error += NL;
206 error += e.getLocalizedMessage();
207 }
208
209 InPluginProcessDumpSingleton.getSingleton()
210 .dumpStreamText( error, dumpLogDirectory );
211 }
212
213 return absolute( toAbsoluteUri( classPathElement ) );
214 }
215 }
216
217 static final class ClasspathElementUri
218 {
219 final String uri;
220 final boolean absolute;
221
222 private ClasspathElementUri( String uri, boolean absolute )
223 {
224 this.uri = uri;
225 this.absolute = absolute;
226 }
227
228 static ClasspathElementUri absolute( String uri )
229 {
230 return new ClasspathElementUri( uri, true );
231 }
232
233 static ClasspathElementUri relative( String uri )
234 {
235 return new ClasspathElementUri( uri, false );
236 }
237 }
238
239 static String escapeUri( String input, Charset encoding )
240 {
241 try
242 {
243 String uriFormEncoded = URLEncoder.encode( input, encoding.name() );
244
245 String uriPathEncoded = uriFormEncoded.replaceAll( "\\+", "%20" );
246 uriPathEncoded = uriPathEncoded.replaceAll( "%2F|%5C", "/" );
247
248 return uriPathEncoded;
249 }
250 catch ( UnsupportedEncodingException e )
251 {
252 throw new IllegalStateException( "avoided by using Charset" );
253 }
254 }
255 }