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.booterclient.lazytestprovider.OutputStreamFlushableCommandline;
23 import org.apache.maven.plugin.surefire.booterclient.output.InPluginProcessDumpSingleton;
24 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
25 import org.apache.maven.surefire.booter.Classpath;
26 import org.apache.maven.surefire.booter.StartupConfiguration;
27 import org.apache.maven.surefire.booter.SurefireBooterForkException;
28
29 import javax.annotation.Nonnull;
30 import javax.annotation.Nullable;
31 import java.io.File;
32 import java.io.FileOutputStream;
33 import java.io.IOException;
34 import java.net.URI;
35 import java.net.URISyntaxException;
36 import java.nio.file.Path;
37 import java.nio.file.Paths;
38 import java.util.Iterator;
39 import java.util.List;
40 import java.util.Map;
41 import java.util.Properties;
42 import java.util.jar.JarEntry;
43 import java.util.jar.JarOutputStream;
44 import java.util.jar.Manifest;
45
46 import static java.nio.file.Files.isDirectory;
47 import static org.apache.maven.plugin.surefire.SurefireHelper.escapeToPlatformPath;
48 import static org.apache.maven.surefire.util.internal.StringUtils.NL;
49
50
51
52
53
54 public final class JarManifestForkConfiguration
55 extends AbstractClasspathForkConfiguration
56 {
57 @SuppressWarnings( "checkstyle:parameternumber" )
58 public JarManifestForkConfiguration( @Nonnull Classpath bootClasspath, @Nonnull File tempDirectory,
59 @Nullable String debugLine, @Nonnull File workingDirectory,
60 @Nonnull Properties modelProperties, @Nullable String argLine,
61 @Nonnull Map<String, String> environmentVariables, boolean debug,
62 int forkCount, boolean reuseForks, @Nonnull Platform pluginPlatform,
63 @Nonnull ConsoleLogger log )
64 {
65 super( bootClasspath, tempDirectory, debugLine, workingDirectory, modelProperties, argLine,
66 environmentVariables, debug, forkCount, reuseForks, pluginPlatform, log );
67 }
68
69 @Override
70 protected void resolveClasspath( @Nonnull OutputStreamFlushableCommandline cli,
71 @Nonnull String booterThatHasMainMethod,
72 @Nonnull StartupConfiguration config,
73 @Nonnull File dumpLogDirectory )
74 throws SurefireBooterForkException
75 {
76 try
77 {
78 File jar = createJar( toCompleteClasspath( config ), booterThatHasMainMethod, dumpLogDirectory );
79 cli.createArg().setValue( "-jar" );
80 cli.createArg().setValue( escapeToPlatformPath( jar.getAbsolutePath() ) );
81 }
82 catch ( IOException e )
83 {
84 throw new SurefireBooterForkException( "Error creating archive file", e );
85 }
86 }
87
88
89
90
91
92
93
94
95
96
97 @Nonnull
98 private File createJar( @Nonnull List<String> classPath, @Nonnull String startClassName,
99 @Nonnull File dumpLogDirectory )
100 throws IOException
101 {
102 File file = File.createTempFile( "surefirebooter", ".jar", getTempDirectory() );
103 if ( !isDebug() )
104 {
105 file.deleteOnExit();
106 }
107 Path parent = file.getParentFile().toPath();
108 FileOutputStream fos = new FileOutputStream( file );
109 try ( JarOutputStream jos = new JarOutputStream( fos ) )
110 {
111 jos.setLevel( JarOutputStream.STORED );
112 JarEntry je = new JarEntry( "META-INF/MANIFEST.MF" );
113 jos.putNextEntry( je );
114
115 Manifest man = new Manifest();
116
117 boolean dumpError = true;
118
119
120
121 StringBuilder cp = new StringBuilder();
122 for ( Iterator<String> it = classPath.iterator(); it.hasNext(); )
123 {
124 Path classPathElement = Paths.get( it.next() );
125 ClasspathElementUri classpathElementUri =
126 toClasspathElementUri( parent, classPathElement, dumpLogDirectory, dumpError );
127
128 dumpError &= !classpathElementUri.absolute;
129 cp.append( classpathElementUri.uri );
130 if ( isDirectory( classPathElement ) && !classpathElementUri.uri.endsWith( "/" ) )
131 {
132 cp.append( '/' );
133 }
134
135 if ( it.hasNext() )
136 {
137 cp.append( ' ' );
138 }
139 }
140
141 man.getMainAttributes().putValue( "Manifest-Version", "1.0" );
142 man.getMainAttributes().putValue( "Class-Path", cp.toString().trim() );
143 man.getMainAttributes().putValue( "Main-Class", startClassName );
144
145 man.write( jos );
146
147 jos.closeEntry();
148 jos.flush();
149
150 return file;
151 }
152 }
153
154 static String relativize( @Nonnull Path parent, @Nonnull Path child )
155 throws IllegalArgumentException
156 {
157 return parent.relativize( child )
158 .toString();
159 }
160
161 static String toAbsoluteUri( @Nonnull Path absolutePath )
162 {
163 return absolutePath.toUri()
164 .toASCIIString();
165 }
166
167 static ClasspathElementUri toClasspathElementUri( @Nonnull Path parent,
168 @Nonnull Path classPathElement,
169 @Nonnull File dumpLogDirectory,
170 boolean dumpError )
171 throws IOException
172 {
173 try
174 {
175 String relativeUriPath = relativize( parent, classPathElement )
176 .replace( '\\', '/' );
177
178 return new ClasspathElementUri( new URI( null, relativeUriPath, null ) );
179 }
180 catch ( IllegalArgumentException e )
181 {
182 if ( dumpError )
183 {
184 String error = "Boot Manifest-JAR contains absolute paths in classpath '"
185 + classPathElement
186 + "'"
187 + NL
188 + "Hint: <argLine>-Djdk.net.URLClassPath.disableClassPathURLCheck=true</argLine>";
189 InPluginProcessDumpSingleton.getSingleton()
190 .dumpStreamText( error, dumpLogDirectory );
191 }
192
193 return new ClasspathElementUri( toAbsoluteUri( classPathElement ) );
194 }
195 catch ( URISyntaxException e )
196 {
197
198 throw new IOException( "Could not create a relative path "
199 + classPathElement
200 + " against "
201 + parent, e );
202 }
203 }
204
205 static final class ClasspathElementUri
206 {
207 final String uri;
208 final boolean absolute;
209
210 ClasspathElementUri( String uri )
211 {
212 this.uri = uri;
213 absolute = true;
214 }
215
216 ClasspathElementUri( URI uri )
217 {
218 this.uri = uri.toASCIIString();
219 absolute = false;
220 }
221 }
222 }