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