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