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.plugin.surefire.AbstractSurefireMojo;
23 import org.apache.maven.plugin.surefire.JdkAttributes;
24 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.OutputStreamFlushableCommandline;
25 import org.apache.maven.plugin.surefire.util.Relocator;
26 import org.apache.maven.surefire.booter.Classpath;
27 import org.apache.maven.surefire.booter.ForkedBooter;
28 import org.apache.maven.surefire.booter.StartupConfiguration;
29 import org.apache.maven.surefire.booter.SurefireBooterForkException;
30 import org.apache.maven.surefire.util.internal.ImmutableMap;
31
32 import java.io.File;
33 import java.io.FileOutputStream;
34 import java.io.IOException;
35 import java.util.Collections;
36 import java.util.Iterator;
37 import java.util.List;
38 import java.util.Map;
39 import java.util.Properties;
40 import java.util.jar.JarEntry;
41 import java.util.jar.JarOutputStream;
42 import java.util.jar.Manifest;
43
44 import static org.apache.maven.plugin.surefire.SurefireHelper.escapeToPlatformPath;
45 import static org.apache.maven.shared.utils.StringUtils.join;
46
47
48
49
50
51
52
53
54 public class ForkConfiguration
55 {
56 public static final String FORK_ONCE = "once";
57
58 public static final String FORK_ALWAYS = "always";
59
60 public static final String FORK_NEVER = "never";
61
62 public static final String FORK_PERTHREAD = "perthread";
63
64 private final int forkCount;
65
66 private final boolean reuseForks;
67
68 private final Classpath bootClasspathConfiguration;
69
70 private final JdkAttributes jdk;
71
72 private final Properties modelProperties;
73
74 private final String argLine;
75
76 private final Map<String, String> environmentVariables;
77
78 private final File workingDirectory;
79
80 private final File tempDirectory;
81
82 private final boolean debug;
83
84 private final String debugLine;
85
86 private final Platform pluginPlatform;
87
88 @SuppressWarnings( "checkstyle:parameternumber" )
89 public ForkConfiguration( Classpath bootClasspathConfiguration, File tmpDir, String debugLine,
90 JdkAttributes jdk, File workingDirectory, Properties modelProperties, String argLine,
91 Map<String, String> environmentVariables, boolean debugEnabled, int forkCount,
92 boolean reuseForks, Platform pluginPlatform )
93 {
94 this.bootClasspathConfiguration = bootClasspathConfiguration;
95 this.tempDirectory = tmpDir;
96 this.debugLine = debugLine;
97 this.jdk = jdk;
98 this.workingDirectory = workingDirectory;
99 this.modelProperties = modelProperties;
100 this.argLine = argLine;
101 this.environmentVariables = toImmutable( environmentVariables );
102 this.debug = debugEnabled;
103 this.forkCount = forkCount;
104 this.reuseForks = reuseForks;
105 this.pluginPlatform = pluginPlatform;
106 }
107
108 public Classpath getBootClasspath()
109 {
110 return bootClasspathConfiguration;
111 }
112
113 public static String getEffectiveForkMode( String forkMode )
114 {
115 if ( "pertest".equalsIgnoreCase( forkMode ) )
116 {
117 return FORK_ALWAYS;
118 }
119 else if ( "none".equalsIgnoreCase( forkMode ) )
120 {
121 return FORK_NEVER;
122 }
123 else if ( forkMode.equals( FORK_NEVER ) || forkMode.equals( FORK_ONCE )
124 || forkMode.equals( FORK_ALWAYS ) || forkMode.equals( FORK_PERTHREAD ) )
125 {
126 return forkMode;
127 }
128 else
129 {
130 throw new IllegalArgumentException( "Fork mode " + forkMode + " is not a legal value" );
131 }
132 }
133
134
135
136
137
138
139
140
141
142 public OutputStreamFlushableCommandline createCommandLine( List<String> classPath, StartupConfiguration config,
143 int threadNumber )
144 throws SurefireBooterForkException
145 {
146 boolean useJar = config.getClassLoaderConfiguration().isManifestOnlyJarRequestedAndUsable();
147
148 boolean shadefire = config.isShadefire();
149
150 String providerThatHasMainMethod =
151 config.isProviderMainClass() ? config.getActualClassName() : ForkedBooter.class.getName();
152
153 return createCommandLine( classPath, useJar, shadefire, providerThatHasMainMethod, threadNumber );
154 }
155
156 OutputStreamFlushableCommandline createCommandLine( List<String> classPath, boolean useJar, boolean shadefire,
157 String providerThatHasMainMethod, int threadNumber )
158 throws SurefireBooterForkException
159 {
160 OutputStreamFlushableCommandline cli = new OutputStreamFlushableCommandline();
161
162 cli.setExecutable( jdk.getJvmExecutable() );
163
164 String jvmArgLine =
165 replaceThreadNumberPlaceholder( stripNewLines( replacePropertyExpressions() ), threadNumber );
166
167 if ( jdk.isJava9AtLeast() && !jvmArgLine.contains( "--add-modules" ) )
168 {
169 if ( jvmArgLine.isEmpty() )
170 {
171 jvmArgLine = "--add-modules java.se.ee";
172 }
173 else
174 {
175 jvmArgLine = "--add-modules java.se.ee " + jvmArgLine;
176 }
177 }
178
179 if ( !jvmArgLine.isEmpty() )
180 {
181 cli.createArg().setLine( jvmArgLine );
182 }
183
184 for ( Map.Entry<String, String> entry : environmentVariables.entrySet() )
185 {
186 String value = entry.getValue();
187 cli.addEnvironment( entry.getKey(), value == null ? "" : value );
188 }
189
190 if ( getDebugLine() != null && !getDebugLine().isEmpty() )
191 {
192 cli.createArg().setLine( getDebugLine() );
193 }
194
195 if ( useJar )
196 {
197 try
198 {
199 File jarFile = createJar( classPath, providerThatHasMainMethod );
200 cli.createArg().setValue( "-jar" );
201 cli.createArg().setValue( escapeToPlatformPath( jarFile.getAbsolutePath() ) );
202 }
203 catch ( IOException e )
204 {
205 throw new SurefireBooterForkException( "Error creating archive file", e );
206 }
207 }
208 else
209 {
210 cli.addEnvironment( "CLASSPATH", join( classPath.iterator(), File.pathSeparator ) );
211
212 final String forkedBooter =
213 providerThatHasMainMethod != null ? providerThatHasMainMethod : ForkedBooter.class.getName();
214
215 cli.createArg().setValue( shadefire ? new Relocator().relocate( forkedBooter ) : forkedBooter );
216 }
217
218 cli.setWorkingDirectory( getWorkingDirectory( threadNumber ).getAbsolutePath() );
219
220 return cli;
221 }
222
223 private File getWorkingDirectory( int threadNumber )
224 throws SurefireBooterForkException
225 {
226 File cwd = new File( replaceThreadNumberPlaceholder( workingDirectory.getAbsolutePath(), threadNumber ) );
227 if ( !cwd.exists() && !cwd.mkdirs() )
228 {
229 throw new SurefireBooterForkException( "Cannot create workingDirectory " + cwd.getAbsolutePath() );
230 }
231 if ( !cwd.isDirectory() )
232 {
233 throw new SurefireBooterForkException(
234 "WorkingDirectory " + cwd.getAbsolutePath() + " exists and is not a directory" );
235 }
236 return cwd;
237 }
238
239 private String replaceThreadNumberPlaceholder( String argLine, int threadNumber )
240 {
241 return argLine.replace( AbstractSurefireMojo.THREAD_NUMBER_PLACEHOLDER,
242 String.valueOf( threadNumber ) ).replace( AbstractSurefireMojo.FORK_NUMBER_PLACEHOLDER,
243 String.valueOf( threadNumber ) );
244 }
245
246
247
248
249
250
251
252
253 private String replacePropertyExpressions()
254 {
255 if ( argLine == null )
256 {
257 return "";
258 }
259
260 String resolvedArgLine = argLine.trim();
261
262 if ( resolvedArgLine.isEmpty() )
263 {
264 return "";
265 }
266
267 for ( final String key : modelProperties.stringPropertyNames() )
268 {
269 String field = "@{" + key + "}";
270 if ( argLine.contains( field ) )
271 {
272 resolvedArgLine = resolvedArgLine.replace( field, modelProperties.getProperty( key, "" ) );
273 }
274 }
275
276 return resolvedArgLine;
277 }
278
279
280
281
282
283
284
285
286
287
288 private File createJar( List<String> classPath, String startClassName )
289 throws IOException
290 {
291 File file = File.createTempFile( "surefirebooter", ".jar", tempDirectory );
292 if ( !debug )
293 {
294 file.deleteOnExit();
295 }
296 FileOutputStream fos = new FileOutputStream( file );
297 JarOutputStream jos = new JarOutputStream( fos );
298 try
299 {
300 jos.setLevel( JarOutputStream.STORED );
301 JarEntry je = new JarEntry( "META-INF/MANIFEST.MF" );
302 jos.putNextEntry( je );
303
304 Manifest man = new Manifest();
305
306
307
308 StringBuilder cp = new StringBuilder();
309 for ( Iterator<String> it = classPath.iterator(); it.hasNext(); )
310 {
311 File file1 = new File( it.next() );
312 String uri = file1.toURI().toASCIIString();
313 cp.append( uri );
314 if ( file1.isDirectory() && !uri.endsWith( "/" ) )
315 {
316 cp.append( '/' );
317 }
318
319 if ( it.hasNext() )
320 {
321 cp.append( ' ' );
322 }
323 }
324
325 man.getMainAttributes().putValue( "Manifest-Version", "1.0" );
326 man.getMainAttributes().putValue( "Class-Path", cp.toString().trim() );
327 man.getMainAttributes().putValue( "Main-Class", startClassName );
328
329 man.write( jos );
330
331 jos.closeEntry();
332 jos.flush();
333
334 return file;
335 }
336 finally
337 {
338 jos.close();
339 }
340 }
341
342 public boolean isDebug()
343 {
344 return debug;
345 }
346
347 public String getDebugLine()
348 {
349 return debugLine;
350 }
351
352 public File getTempDirectory()
353 {
354 return tempDirectory;
355 }
356
357 public int getForkCount()
358 {
359 return forkCount;
360 }
361
362 public boolean isReuseForks()
363 {
364 return reuseForks;
365 }
366
367 public Platform getPluginPlatform()
368 {
369 return pluginPlatform;
370 }
371
372 private static String stripNewLines( String argLine )
373 {
374 return argLine.replace( "\n", " " ).replace( "\r", " " );
375 }
376
377
378
379
380
381
382
383
384
385 private static <K, V> Map<K, V> toImmutable( Map<K, V> map )
386 {
387 return map == null ? Collections.<K, V>emptyMap() : new ImmutableMap<K, V>( map );
388 }
389 }