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