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